摘要
看完本文你将掌握如下知识点:
Spring Boot对JDBC的支持
Spring Boot项目多数据源的配置
Spring Boot的事务管理
Spring Boot项目多数据源的事务管理
Spring Boot项目中使用Hibernate4的方法
Spring Boot项目中使用Mybatis的方法
SpringBoot系列 :Spring Boot学习笔记
前言
Spring Boot针对企业开发场景提供了各种『开箱即用』的spring-boot-starter-xxx 自动配置依赖模块,这就使得我们开发Spring应用更加快速和高效。比如我们前面创建web项目时使用到的spring-boot-starter-web 。
这些spring-boot-starter-xxx 不但包含了对该功能的全部依赖包,同时也提供了该功能的自动配置类。我们本节要讨论的『数据访问』就是基于这些spring-boot-starter-xxx 的自动配置依赖模块。
环境准备
jdk版本 :java version “1.8.0_31”
数据库 :10.1.16-MariaDB
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 创建库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' );# 创建库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的支持
创建项目
新建一个springboot项目,依赖选择web和jdbc
项目创建成功后查看pom,会看到添加了spring-boot-starter-jdbc 的依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency >
配置项目
在pom中增加MySQL依赖
1 2 3 4 5 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.37</version > </dependency >
在application.properties 中添加数据源配置信息
1 2 3 4 5 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
项目代码
本例只做简单演示,所以只创建如下3个类,并用一个单元测试类进行测试
Model:Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Person implements Serializable { private static final long serialVersionUID = -1L ; private Long id; private String name; private Integer age; @Override public String toString () { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}' ; } }
Dao:PersonDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @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的自动配置功能了。
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 4.3.4.RELEASE</version > </dependency >
启动类中去掉对DataSourceAutoConfiguration 的自动配置支持
1 2 3 4 5 6 7 @SpringBootApplication(exclude={DataSourceAutoConfiguration.class}) public class SpringbootjdbcdemoApplication { public static void main (String[] args) { SpringApplication.run(SpringbootjdbcdemoApplication.class, args); } }
创建DataSourceConfig 配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @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 只能支持单一的数据源配置,如果项目中需要关联多个数据源,就需要我们自己处理了。
比如我们在环境准备中创建了两个数据库,接下来我们在项目中增加多数据源的配置。
在application.properties 中添加数据源配置信息
1 2 3 4 5 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 配置类中增加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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框架提供的,配置方法参考下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class }) public class PersonService { @Transactional(readOnly = true) public List<Person> getAllPersonList () { } public int savePserson (Person person) { } }
如果在测试类上声明**@Transactional**,则会开启自动回滚,不会产生脏数据
1 2 3 4 @RunWith(SpringRunner.class) @SpringBootTest @Transactional public class SpringbootjdbcdemoApplicationTests {…………}
如果希望自己配置事务,可以在配置类中增加事务管理器的配置,比如,我们在DataSourceConfig 中增加如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @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; @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 进行了演示,接下来我们添加多个事务管理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @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 = "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**注解中指定要使用哪一个事务管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @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(); } }
说明
这样做并不美好,不能对多个数据源同时进行事务管理,比如,我们在一个业务方法里同时对两个数据源进行操作,我们希望只要有一个发生异常,则两个数据源的数据都进行回滚。
那要怎么做呢,我们接着往下看。
多数据源事务管理
这里推荐使用**Atomikos **,Atomikos支持Mysql、Oracle等多种数据库,可与多种ORM框架集成,如MyBatis、JPA、Hibernate等等,同时支持各种容器下JNDI的多数据源管理。Atomikos官网提供了各种情况下使用Atomikos的Example,本文只对使用JDBC时的情况进行说明。
目前maven中央仓库的最新版本是4.0.4,使用Atomikos,需要在项目中加入如下依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <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 进行改造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 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 @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" ); 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; } }
项目编译路径下可以创建一个jta.properties 文件,用于对Atomikos的相关属性进行配置,不过也可以不加这个文件,因为所有的属性都有默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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
1 2 3 4 5 <dependency > <groupId > org.hibernate</groupId > <artifactId > hibernate-core</artifactId > <version > 4.3.5.Final</version > </dependency >
DataSourceConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 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" ); 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" ); 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 (); dao.setSessionFactory(localSessionFactoryBean().getObject()); return dao; } @Bean(name = "hibernateDAO2") public CP_HibernateDAO hibernate4Dao2 () { System.out.println("hibernateDAO2" ); CP_Hibernate4DAOImpl dao = new CP_Hibernate4DAOImpl (); dao.setSessionFactory(localSessionFactoryBean2().getObject()); return dao; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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; }
CP_HibernateDAO 是我们自定义的Hibernate的通用Dao接口,其定义的方法和和实现类CP_Hibernate4DAOImpl 代码如下:
1 2 3 4 5 6 7 8 9 10 11 package com.example.hibernate;import java.util.List;public interface CP_HibernateDAO { public List<?> findAll(Class<?> entityClazz, String... str); public void save (Object entity) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 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; } public void setSessionFactory (SessionFactory sessionFactory) { this .sessionFactory = sessionFactory; } private Session getHibernateSession () { Session session = sessionFactory.openSession(); return session; } @Override public List<?> findAll(Class<?> entityClazz, String... str) { DetachedCriteria dc = DetachedCriteria.forClass(entityClazz); List<?> list = findAllByCriteria(dc); return list; } @Override public void save (Object entity) { getHibernateSession().save(entity); getHibernateSession().flush(); } public List<?> findAllByCriteria(DetachedCriteria detachedCriteria) { Criteria criteria = detachedCriteria .getExecutableCriteria(getHibernateSession()); return criteria.list(); } }
说明
需要注意两点:
session必须使用sessionFactory.openSession()的方式获得,不能使用sessionFactory.getCurrentSession()。
更新操作必须调用session.flush()方法。
Spring配置文件的方式,可以参考:Spring4+Hibernate4+Atomikos3.3多数据源事务管理
Spring Boot中Mybitas的使用
创建项目时,我们可以选择mybatis-spring-boot-starter 依赖,这样可以激活SpringBoot对Mybatis的自动配置类。
pom 中添加依赖
1 2 3 4 5 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 1.1.1</version > </dependency >
application.properties 中添加mybaits的自动配置属性,可以查看MybatisProperties 了解可以配置哪些属性
1 2 mybatis.mapper-locations=classpath:mapper/*.xml
Mapper接口上要配置**@Mapper注解,因为 mybatis-spring-boot-starter的自动配置会扫描 @Mapper**注解来注册Mapper接口。
1 2 3 4 @Mapper public interface PersonMapper { }
此时同样可以使用**@Transactional**注解
说明
可以使用maven的mybatis-generator插件自动生成代码,参考maven插件–MyBatis自动生成代码
mybatis-spring-boot-starter 不利于扩展,所以还是我们自己实现个mybitas的配置类吧。
pom 中去掉mybatis-spring-boot-starter 的依赖,增加mybatis的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <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 >
创建MyBatisConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @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()); 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**注解
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 4.3.4.RELEASE</version > </dependency > <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 @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; } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactoryBean () { SqlSessionFactoryBean bean = new SqlSessionFactoryBean (); bean.setDataSource(dataSource()); 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()); } @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Configuration @AutoConfigureAfter(MyBatisConfig.class) public class MyBatisMapperScannerConfig { @Bean public MapperScannerConfigurer mapperScannerConfigurer () { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer (); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory" ); mapperScannerConfigurer.setBasePackage("com.example.mapper.ds1" ); return mapperScannerConfigurer; } @Bean public MapperScannerConfigurer mapperScannerConfigurer2 () { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer (); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory2" ); mapperScannerConfigurer.setBasePackage("com.example.mapper.ds2" ); return mapperScannerConfigurer; } }
这里要说明的是,如果两个数据源下的Mapper起了相同的类名,虽然他们在不同的包路径下,启动也会报错了,因为默认注册Mapper时使用的是类名称(不含包名),此时可以在Mapper上加上**@Component(“personMapper”)**注解
写在后面的话
Spring Boot为我们提供了大量的spring-boot-starter-xxx 来加快我们的开发流程,创建项目时就可以看到可供选择的各种spring-boot-starter-xxx ,那么这么多的spring-boot-starter-xxx ,我们是否都需要了解呢,如果项目中需要用到某一个功能,是否就应该加入这个spring-boot-starter-xxx 呢?
笔者人为,spring-boot-starter-xxx 提供的完整jar包依赖和自动配置固然很好,但是当我们要在项目中加入某一个功能时,作为开发人员,是应该清楚的知道该功能的依赖关系和配置逻辑的,所以并不一定需要引入SpringBoot的spring-boot-starter-xxx ,而且SpringBoot对这些spring-boot-starter-xxx 做的自动配置,如果我们并不熟悉和十分清楚,往往会给我们开发人员造成不明所以的困扰,所以,笔者建议,在对SpringBoot提供的某一个spring-boot-starter-xxx 所提供的功能并不十分清楚时,还是使用配置类的方式吧。
还有,由于某些自动配置类的激活是根据项目中是否包含某个class或容器中是否注册了某个bean,所以笔者建议,如果项目中引入了新的jar包,或者手工注册了某个bean,都要通过debug的方式查看是否开启了某个自动配置。
另外,本文代码只是为了辅助说明,比如DriverManagerDataSource 正式环境不建议使用,请更换为其它数据源,比如BasicDataSource 。
本文示例代码下载地址:https://github.com/hanqunfeng/SpringBootStudy