摘要
看完本文你將掌握如下知識點:
- Spring Boot對JDBC的支持
- Spring Boot項目多數據源的配置
- Spring Boot的事務管理
- Spring Boot項目多數據源的事務管理
- Spring Boot項目中使用Hibernate4的方法
- Spring Boot項目中使用Mybatis的方法
SpringBoot系列:Spring Boot學習筆記
前言
Spring Boot針對企業(yè)開發(fā)場景提供了各種『開箱即用』的spring-boot-starter-xxx自動配置依賴模塊捐康,這就使得我們開發(fā)Spring應用更加快速和高效蛋欣。比如我們前面創(chuàng)建web項目時使用到的spring-boot-starter-web旗们。
這些spring-boot-starter-xxx不但包含了對該功能的全部依賴包,同時也提供了該功能的自動配置類御滩。我們本節(jié)要討論的『數據訪問』就是基于這些spring-boot-starter-xxx的自動配置依賴模塊。
環(huán)境準備
jdk版本:java version "1.8.0_31"
數據庫:10.1.16-MariaDB
腳本
# 創(chuàng)建庫1
CREATE SCHEMA `springboot1` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot1`.`person` (
`p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`p_name` VARCHAR(45) NULL COMMENT '姓名',
`p_age` INT NULL COMMENT '年齡',
PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人員信息表';
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '張三', '20');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');
# 創(chuàng)建庫2
CREATE SCHEMA `springboot2` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot2`.`person` (
`p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`p_name` VARCHAR(45) NULL COMMENT '姓名',
`p_age` INT NULL COMMENT '年齡',
PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人員信息表';
INSERT INTO `springboot2`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '張三', '20');
INSERT INTO `springboot2`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');
Spring Boot對JDBC的支持
創(chuàng)建項目
新建一個springboot項目他宛,依賴選擇web和jdbc
項目創(chuàng)建成功后查看pom奴愉,會看到添加了spring-boot-starter-jdbc的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
配置項目
在pom中增加MySQL依賴
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
在application.properties中添加數據源配置信息
#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd
項目代碼
本例只做簡單演示,所以只創(chuàng)建如下3個類嘶居,并用一個單元測試類進行測試
Model:Person
public class Person implements Serializable {
private static final long serialVersionUID = -1L;
private Long id;
private String name;
private Integer age;
//getter and setter
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
Dao:PersonDao
@Repository
public class PersonDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int savePerson(Person person){
String sql = "INSERT INTO `springboot1`.`person` (`p_name`, `p_age`) VALUES (?, ?)";
int result = jdbcTemplate.update(sql,new Object[]{person.getName(),person.getAge()});
return result;
}
public List<Person> getAllPersonList(){
String sql = "select * from person s";
List<Person> list = jdbcTemplate.query(sql,new PersonMapper());
return list;
}
class PersonMapper implements RowMapper<Person>{
@Override
public Person mapRow(ResultSet resultSet, int i) throws SQLException {
Person person = new Person();
person.setId(resultSet.getLong("p_id"));
person.setName(resultSet.getString("p_name"));
person.setAge(resultSet.getInt("p_age"));
return person;
}
}
}
Service:PersonService
@Service
public class PersonService {
@Autowired
private PersonDao personDao;
public int savePserson(Person person){
return personDao.savePerson(person);
}
public List<Person> getAllPersonList(){
return personDao.getAllPersonList();
}
}
單元測試:SpringbootjdbcdemoApplicationTests
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootjdbcdemoApplicationTests {
@Autowired
private PersonService personService;
@Test
public void savePerson(){
Person person = new Person();
person.setName("王五");
person.setAge(18);
int result = personService.savePserson(person);
Assert.assertEquals(1,result);
}
@Test
public void getAllPersonList(){
List<Person> list = personService.getAllPersonList();
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
}
說明
實際上罪帖,項目加入spring-boot-starter-jdbc的依賴后促煮,即可在項目代碼中通過@Autowired自動注入JdbcTemplate邮屁。而數據源的配置則在application.properties中進行配置。
如果不想使用spring-boot-starter-jdbc帶來的默認依賴和自動配置菠齿,那么采用如下的方式佑吝,效果是一樣的。
使用自定義的DataSourceConfig
修改pom中的依賴绳匀,去掉對spring-boot-starter-jdbc的依賴芋忿,并加入對spring-jdbc的依賴,這樣我們就失去了對JDBC的自動配置功能了疾棵。
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
啟動類中去掉對DataSourceAutoConfiguration的自動配置支持
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class SpringbootjdbcdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootjdbcdemoApplication.class, args);
}
}
創(chuàng)建DataSourceConfig配置類
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;
@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
}
其它代碼不需要任何修改戈钢,運行效果一致。
說明
為什么SpringBoot為我們提供了spring-boot-starter-jdbc的自動配置解決方案是尔,我們還要自己配置呢殉了,這是因為自動配置并不是那么的強大,spring-boot-starter-jdbc只能支持單一的數據源配置拟枚,如果項目中需要關聯多個數據源薪铜,就需要我們自己處理了众弓。
比如我們在環(huán)境準備中創(chuàng)建了兩個數據庫,接下來我們在項目中增加多數據源的配置隔箍。
在application.properties中添加數據源配置信息
#datasource2
spring.datasource.driver-class-name2=com.mysql.jdbc.Driver
spring.datasource.url2=jdbc:mysql://localhost:3306/springboot2?useUnicode=true&characterEncoding=utf-8
spring.datasource.username2=root
spring.datasource.password2=newpwd
然后在DataSourceConfig配置類中增加如下內容:
@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;
@Bean(name = "dataSource2")
public DataSource dataSource2() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass2);
dataSource.setUrl(url2);
dataSource.setUsername(userName2);
dataSource.setPassword(passWord2);
return dataSource;
}
@Bean(name = "jdbcTemplate2")
public JdbcTemplate jdbcTemplate2(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
return jdbcTemplate;
}
此時需要在Dao中將@Autowired注解替換成@Resource(name = "jdbcTemplate")
谓娃,來明確指定要使用哪一個jdbcTemplate對象。
說明
關于如何在項目中使用Hibernate4框架蜒滩,可以參考:SpringMVC4零配置
Spring Boot的事務管理
JDBC事務管理
如果我們項目中使用的是JDBC的數據訪問方案滨达,并且容器中只注冊了一個DataSource,那么SpringBoot就會為我們開啟DataSourceTransactionManagerAutoConfiguration的自動配置類俯艰,其會在容器中注冊一個DataSourceTransactionManager事務管理器族扰,同時會開啟對注解式事務@Transactional的支持。感興趣的可以看一下DataSourceTransactionManagerAutoConfiguration的源碼惊完。
@Transactional是Spring框架提供的膜蛔,配置方法參考下面的代碼
//一般我們會在業(yè)務實現類上聲明事務注解
//當前表示需要在事務中運行,可以執(zhí)行更新和刪除操作涩搓,遇到異常則回滾
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public class PersonService{
//方法上也可以標注事務注解污秆,方法上注解聲明會覆蓋類上的
//一般查詢操作readOnly設置為true,增刪該操作設置為false
@Transactional(readOnly = true)
public List<Person> getAllPersonList(){
//do something
}
//不加@Transactiona注解昧甘,則使用類上的設置
public int savePserson(Person person){
//do something
}
}
如果在測試類上聲明@Transactional良拼,則會開啟自動回滾,不會產生臟數據
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class SpringbootjdbcdemoApplicationTests {…………}
如果希望自己配置事務充边,可以在配置類中增加事務管理器的配置庸推,比如,我們在DataSourceConfig中增加如下配置:
@Configuration
//啟用注解事務管理浇冰,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;
@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
說明
上面的方法只是針對單一數據源進行事務管理的贬媒,但是項目中經常會用到多數據源的情況,那么要如何進行事務管理呢肘习?
我們上文講到了可以在項目中通過配置類际乘,自己配置多個數據源,并通過DataSourceConfig進行了演示漂佩,接下來我們添加多個事務管理器脖含。
@Configuration
//啟用注解事務管理,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;
@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;
@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean(name = "dataSource2")
public DataSource dataSource2() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass2);
dataSource.setUrl(url2);
dataSource.setUsername(userName2);
dataSource.setPassword(passWord2);
System.out.println(url2);
return dataSource;
}
@Bean(name = "jdbcTemplate2")
public JdbcTemplate jdbcTemplate2(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
return jdbcTemplate;
}
@Bean(name = "transactionManager2")
public DataSourceTransactionManager transactionManager2() {
return new DataSourceTransactionManager(dataSource2());
}
}
這時投蝉,我們必須在@Transactional注解中指定要使用哪一個事務管理器
@Service
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public class PersonService {
@Autowired
private PersonDao personDao;
public int savePserson(Person person){
return personDao.savePerson(person);
}
@Transactional(transactionManager = "transactionManager",readOnly = true)
public List<Person> getAllPersonList(){
return personDao.getAllPersonList();
}
@Transactional(transactionManager = "transactionManager2",propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public int savePserson2(Person person){
return personDao.savePerson2(person);
}
@Transactional(transactionManager = "transactionManager2",readOnly = true)
public List<Person> getAllPersonList2(){
return personDao.getAllPersonList2();
}
}
說明
這樣做并不美好养葵,不能對多個數據源同時進行事務管理,比如瘩缆,我們在一個業(yè)務方法里同時對兩個數據源進行操作关拒,我們希望只要有一個發(fā)生異常,則兩個數據源的數據都進行回滾。
那要怎么做呢夏醉,我們接著往下看爽锥。
多數據源事務管理
這里推薦使用Atomikos,Atomikos支持Mysql畔柔、Oracle等多種數據庫氯夷,可與多種ORM框架集成,如MyBatis靶擦、JPA腮考、Hibernate等等,同時支持各種容器下JNDI的多數據源管理玄捕。Atomikos官網提供了各種情況下使用Atomikos的Example踩蔚,本文只對使用JDBC時的情況進行說明。
目前maven中央倉庫的最新版本是4.0.4枚粘,使用Atomikos馅闽,需要在項目中加入如下依賴:
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
對DataSourceConfig進行改造:
package com.example;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
@Configuration
//啟用注解事務管理,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;
@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(300);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
@Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println("dataSource init");
//Oracle:oracle.jdbc.xa.client.OracleXADataSource
//Druid:com.alibaba.druid.pool.xa.DruidXADataSource
//Postgresql:org.postgresql.xa.PGXADataSource
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}
@Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource2() {
System.out.println("dataSource2 init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url2);
mysqlXaDataSource.setPassword(passWord2);
mysqlXaDataSource.setUser(userName2);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
@Bean(name = "jdbcTemplate2")
public JdbcTemplate jdbcTemplate2(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
return jdbcTemplate;
}
}
項目編譯路徑下可以創(chuàng)建一個jta.properties文件馍迄,用于對Atomikos的相關屬性進行配置福也,不過也可以不加這個文件,因為所有的屬性都有默認值攀圈。
com.atomikos.icatch.enable_logging=true
com.atomikos.icatch.force_shutdown_on_vm_exit=false
com.atomikos.icatch.automatic_resource_registration=true
com.atomikos.icatch.checkpoint_interval=500
com.atomikos.icatch.serial_jta_transactions=true
com.atomikos.icatch.default_jta_timeout=10000
com.atomikos.icatch.max_timeout=300000
com.atomikos.icatch.log_base_dir=./
com.atomikos.icatch.threaded_2pc=false
com.atomikos.icatch.max_actives=50
com.atomikos.icatch.log_base_name=tmlog
java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory
com.atomikos.icatch.client_demarcation=false
java.naming.provider.url=rmi://localhost:1099
com.atomikos.icatch.rmi_export_class=none
com.atomikos.icatch.trust_client_tm=false
com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout}
com.atomikos.icatch.oltp_max_retries=5
com.atomikos.icatch.oltp_retry_interval=10000
com.atomikos.icatch.allow_subtransactions=true
Spring Boot中Atomikos與Hibernate4多數據源集成方法
Atomikos與Hibernate4集成方法與JDBC類似暴凑,我們在pom中加入hibernate的依賴,并對DataSourceConfig進行改造
pom
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>
DataSourceConfig
package com.example;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.example.hibernate.CP_HibernateDAO;
import com.example.hibernate.impl.CP_Hibernate4DAOImpl;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import java.util.Properties;
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;
@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
System.out.println();
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
@Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println();
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}
@Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource2() {
System.out.println();
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url2);
mysqlXaDataSource.setPassword(passWord2);
mysqlXaDataSource.setUser(userName2);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}
@Bean(name = "sessionFactory")
public LocalSessionFactoryBean localSessionFactoryBean() {
System.out.println("sessionFactory");
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
//掃描實體對象的目錄赘来,不同的數據源现喳,實體要存放不同的目錄
String[] packagesToScan = new String[] { "com.example.model.ds1" };
sessionFactory.setPackagesToScan(packagesToScan);
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
hibernateProperties.setProperty("hibernate.show_sql", "true");
//開啟Hibernate對JTA的支持
hibernateProperties.setProperty("hibernate.current_session_context_class", "jta");
hibernateProperties.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");
sessionFactory.setHibernateProperties(hibernateProperties);
return sessionFactory;
}
@Bean(name = "sessionFactory2")
public LocalSessionFactoryBean localSessionFactoryBean2() {
System.out.println("sessionFactory2");
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource2());
//掃描實體對象的目錄,不同的數據源犬辰,實體要存放不同的目錄
String[] packagesToScan = new String[] { "com.example.model.ds2" };
sessionFactory.setPackagesToScan(packagesToScan);
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
hibernateProperties.setProperty("hibernate.show_sql", "true");
//開啟Hibernate對JTA的支持
hibernateProperties.setProperty("hibernate.current_session_context_class", "jta");
hibernateProperties.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");
sessionFactory.setHibernateProperties(hibernateProperties);
return sessionFactory;
}
@Bean(name = "hibernateDAO")
public CP_HibernateDAO hibernate4Dao() {
System.out.println("hibernateDAO");
CP_Hibernate4DAOImpl dao = new CP_Hibernate4DAOImpl();
//綁定SessionFactory
dao.setSessionFactory(localSessionFactoryBean().getObject());
return dao;
}
@Bean(name = "hibernateDAO2")
public CP_HibernateDAO hibernate4Dao2() {
System.out.println("hibernateDAO2");
CP_Hibernate4DAOImpl dao = new CP_Hibernate4DAOImpl();
//綁定SessionFactory2
dao.setSessionFactory(localSessionFactoryBean2().getObject());
return dao;
}
}
@Entity
@Table(name = "person")
public class Person implements Serializable {
private static final long serialVersionUID = -1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "p_id")
private Long id;
@Column(name = "p_name")
private String name;
@Column(name = "p_age")
private Integer age;
//setter and getter
}
CP_HibernateDAO是我們自定義的Hibernate的通用Dao接口嗦篱,其定義的方法和和實現類CP_Hibernate4DAOImpl代碼如下:
package com.example.hibernate;
import java.util.List;
public interface CP_HibernateDAO {
public List<?> findAll(Class<?> entityClazz, String... str);
public void save(Object entity);
}
package com.example.hibernate.impl;
import com.example.hibernate.CP_HibernateDAO;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import java.util.List;
public class CP_Hibernate4DAOImpl implements CP_HibernateDAO {
private SessionFactory sessionFactory;
public SessionFactory getSessionFactory() {
return sessionFactory;
}
//綁定SessionFactory
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
private Session getHibernateSession() {
Session session = sessionFactory.openSession();
return session;
}
/*
* @see com.example.hibernate.CP_HibernateDAO#findAll()
*/
@Override
public List<?> findAll(Class<?> entityClazz, String... str) {
DetachedCriteria dc = DetachedCriteria.forClass(entityClazz);
List<?> list = findAllByCriteria(dc);
return list;
}
/*
* @see com.example.hibernate.CP_HibernateDAO#save(java.lang.Object)
*/
@Override
public void save(Object entity) {
getHibernateSession().save(entity);
//注意這里一定要執(zhí)行flush方法
getHibernateSession().flush();
}
public List<?> findAllByCriteria(DetachedCriteria detachedCriteria) {
// TODO Auto-generated method stub
Criteria criteria = detachedCriteria
.getExecutableCriteria(getHibernateSession());
return criteria.list();
}
}
說明
需要注意兩點:
- session必須使用sessionFactory.openSession()的方式獲得,不能使用sessionFactory.getCurrentSession()忧风。
- 更新操作必須調用session.flush()方法默色。
Spring配置文件的方式球凰,可以參考:Spring4+Hibernate4+Atomikos3.3多數據源事務管理
Spring Boot中Mybitas的使用
創(chuàng)建項目時狮腿,我們可以選擇mybatis-spring-boot-starter依賴,這樣可以激活SpringBoot對Mybatis的自動配置類呕诉。
pom中添加依賴
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
application.properties中添加mybaits的自動配置屬性缘厢,可以查看MybatisProperties了解可以配置哪些屬性
#mapper配置文件路徑,如果是基于注解的形式可以不需要配置該屬性
mybatis.mapper-locations=classpath:mapper/*.xml
Mapper接口上要配置@Mapper注解甩挫,因為mybatis-spring-boot-starter的自動配置會掃描@Mapper注解來注冊Mapper接口贴硫。
@Mapper
public interface PersonMapper {
//………………
}
此時同樣可以使用@Transactional注解
說明
可以使用maven的mybatis-generator插件自動生成代碼,參考maven插件--MyBatis自動生成代碼
mybatis-spring-boot-starter不利于擴展,所以還是我們自己實現個mybitas的配置類吧英遭。
pom中去掉mybatis-spring-boot-starter的依賴间护,增加mybatis的依賴
<!--
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>-->
<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
創(chuàng)建MyBatisConfig
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MyBatisConfig {
@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;
@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource());
//添加XML目錄
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
MyBatisMapperScannerConfig,基于包掃描Mapper挖诸,此時不需要配置@Mapper注解
@Configuration
//必須在MyBatisConfig注冊后再加載MapperScannerConfigurer汁尺,否則會報錯
@AutoConfigureAfter(MyBatisConfig.class)
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage("com.example.mapper");
return mapperScannerConfigurer;
}
}
關閉DataSourceAutoConfiguration,因為這里我們配置了數據源多律,所以需要關閉該自動配置痴突,另外,MybatisAutoConfiguration也是基于DataSourceAutoConfiguration的狼荞,所以關閉了DataSourceAutoConfiguration也就同時關閉了MybatisAutoConfiguration辽装。
Spring Boot中Atomikos與Mybatis多數據源集成方法
pom
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
MyBatisConfig
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MyBatisConfig {
@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;
@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
@Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println("dataSource init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}
@Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource2() {
System.out.println("dataSource2 init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url2);
mysqlXaDataSource.setPassword(passWord2);
mysqlXaDataSource.setUser(userName2);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}
//基于xml式Mapper
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource());
//添加Mapper配置文件的目錄
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath:mapper/ds1/*.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate() {
return new SqlSessionTemplate(sqlSessionFactoryBean());
}
//基于注解式Mapper
@Bean(name = "sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactoryBean2() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource2());
try {
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Bean(name = "sqlSessionTemplate2")
public SqlSessionTemplate sqlSessionTemplate2() {
return new SqlSessionTemplate(sqlSessionFactoryBean2());
}
}
MyBatisMapperScannerConfig
@Configuration
//必須在MyBatisConfig注冊后再加載MapperScannerConfigurer,否則會報錯
@AutoConfigureAfter(MyBatisConfig.class)
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//綁定datasorce的sqlSessionFactory
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
//掃描ds1目錄來注冊Mapper接口
mapperScannerConfigurer.setBasePackage("com.example.mapper.ds1");
return mapperScannerConfigurer;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer2() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//綁定datasorce2的sqlSessionFactory
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory2");
//掃描ds2目錄來注冊Mapper接口
mapperScannerConfigurer.setBasePackage("com.example.mapper.ds2");
return mapperScannerConfigurer;
}
}
這里要說明的是相味,如果兩個數據源下的Mapper起了相同的類名拾积,雖然他們在不同的包路徑下,啟動也會報錯了丰涉,因為默認注冊Mapper時使用的是類名稱(不含包名)殷勘,此時可以在Mapper上加上@Component("personMapper")注解
寫在后面的話
Spring Boot為我們提供了大量的spring-boot-starter-xxx來加快我們的開發(fā)流程,創(chuàng)建項目時就可以看到可供選擇的各種spring-boot-starter-xxx昔搂,那么這么多的spring-boot-starter-xxx玲销,我們是否都需要了解呢,如果項目中需要用到某一個功能摘符,是否就應該加入這個spring-boot-starter-xxx呢贤斜?
筆者人為,spring-boot-starter-xxx提供的完整jar包依賴和自動配置固然很好逛裤,但是當我們要在項目中加入某一個功能時瘩绒,作為開發(fā)人員,是應該清楚的知道該功能的依賴關系和配置邏輯的带族,所以并不一定需要引入SpringBoot的spring-boot-starter-xxx锁荔,而且SpringBoot對這些spring-boot-starter-xxx做的自動配置,如果我們并不熟悉和十分清楚蝙砌,往往會給我們開發(fā)人員造成不明所以的困擾阳堕,所以,筆者建議择克,在對SpringBoot提供的某一個spring-boot-starter-xxx所提供的功能并不十分清楚時恬总,還是使用配置類的方式吧。
還有肚邢,由于某些自動配置類的激活是根據項目中是否包含某個class或容器中是否注冊了某個bean壹堰,所以筆者建議拭卿,如果項目中引入了新的jar包,或者手工注冊了某個bean贱纠,都要通過debug的方式查看是否開啟了某個自動配置峻厚。
另外,本文代碼只是為了輔助說明谆焊,比如DriverManagerDataSource正式環(huán)境不建議使用目木,請更換為其它數據源,比如BasicDataSource懊渡。