摘要
看完本文你将掌握如下知识点:
- 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
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的依赖
org.springframework.boot
spring-boot-starter-jdbc
配置项目
在pom中增加MySQL依赖
mysql
mysql-connector-java
5.1.37
在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
项目代码
本例只做简单演示,所以只创建如下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 getAllPersonList(){
String sql = "select * from person s";
List list = jdbcTemplate.query(sql,new PersonMapper());
return list;
}
class PersonMapper implements RowMapper{
@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 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 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的自动配置功能了。
org.springframework
spring-jdbc
4.3.4.RELEASE
启动类中去掉对DataSourceAutoConfiguration的自动配置支持
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class SpringbootjdbcdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootjdbcdemoApplication.class, args);
}
}
创建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只能支持单一的数据源配置,如果项目中需要关联多个数据源,就需要我们自己处理了。
比如我们在环境准备中创建了两个数据库,接下来我们在项目中增加多数据源的配置。
在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框架提供的,配置方法参考下面的代码
//一般我们会在业务实现类上声明事务注解
//当前表示需要在事务中运行,可以执行更新和删除操作,遇到异常则回滚
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public class PersonService{
//方法上也可以标注事务注解,方法上注解声明会覆盖类上的
//一般查询操作readOnly设置为true,增删该操作设置为false
@Transactional(readOnly = true)
public List 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 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 getAllPersonList2(){
return personDao.getAllPersonList2();
}
}
说明
这样做并不美好,不能对多个数据源同时进行事务管理,比如,我们在一个业务方法里同时对两个数据源进行操作,我们希望只要有一个发生异常,则两个数据源的数据都进行回滚。
那要怎么做呢,我们接着往下看。
多数据源事务管理
这里推荐使用Atomikos,Atomikos支持Mysql、Oracle等多种数据库,可与多种ORM框架集成,如MyBatis、JPA、Hibernate等等,同时支持各种容器下JNDI的多数据源管理。Atomikos官网提供了各种情况下使用Atomikos的Example,本文只对使用JDBC时的情况进行说明。
目前maven中央仓库的最新版本是4.0.4,使用Atomikos,需要在项目中加入如下依赖:
com.atomikos
transactions-jdbc
4.0.4
com.atomikos
transactions-jta
4.0.4
com.atomikos
transactions
4.0.4
com.atomikos
atomikos-util
4.0.4
javax.transaction
jta
1.1
对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;
}
}
项目编译路径下可以创建一个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
org.hibernate
hibernate-core
4.3.5.Final
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);
//注意这里一定要执行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的使用
创建项目时,我们可以选择mybatis-spring-boot-starter依赖,这样可以激活SpringBoot对Mybatis的自动配置类。
pom中添加依赖
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.1.1
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的依赖
org.mybatis
mybatis
3.4.0
org.mybatis
mybatis-spring
1.3.0
org.springframework
spring-jdbc
4.3.4.RELEASE
创建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
org.springframework
spring-jdbc
4.3.4.RELEASE
org.mybatis
mybatis
3.4.0
org.mybatis
mybatis-spring
1.3.0
com.atomikos
transactions-jdbc
4.0.4
com.atomikos
transactions-jta
4.0.4
com.atomikos
transactions
4.0.4
com.atomikos
atomikos-util
4.0.4
javax.transaction
jta
1.1
mysql
mysql-connector-java
5.1.37
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来加快我们的开发流程,创建项目时就可以看到可供选择的各种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