前言

数据源是 JDBC 规范中用来获取关系型数据库连接的一个接口,主要通过池化技术来复用连接。

简单点的 Java 项目或者拆分比较彻底的微服务模块中只会用到一个数据库实例,对应一个数据源。稍复杂点的项目,由于各种原因可能会使用到多个数据库实例,这些数据库可能属于不同的业务模块,可能用于分库分表,也可能用于读写分离。

使用多个数据库的情况下需要为每个数据库配置一个数据源,这本身并没有什么技术难度,重点在于如何在使用多数据源的情况下还能加入 Spring 的事务管理,这篇文章会详细介绍各种持久化技术如何在 Spring 中达到这个目标。

方案选型

在实现多数据源加入 Spring 事务管理这个大目标的前提下,我们需要做进一步的拆解。

主要需要考虑到多个数据源是否需要加入到一个事务中。

  • 如果每个数据源对应一个事务可以使用单机事务,这个与在单数据源的情况下是类似的。
  • 如果多个数据源需要加入到一个事务则只能使用分布式事务。

单机事务

在单机事务的情况下,每个数据源可以分别对应一个 Spring 事务管理器,也可以多个数据源使用一个事务管理器。由于 Spring 的事务管理会把数据源作为 key 存入线程上下文,所以一个线程下只能有一个数据源加入事务。

多事务管理器

单机事务情况下使用多个事务管理器,可以让每个数据源分别对应一个事务管理器,这和在单数据源的情况下是类似的,可以使用如下的图来表述。

不管哪种持久化技术,多个数据源配置多个事务管理器,在具体配置和使用事务方面都是类似的,可以概括为如下的流程。

  1. 为每个数据库分别配置一个数据源。
  2. 为每个数据源配置具体持久化技术操作数据库的核心类。
  3. 为每个数据源配置一个事务管理器。
  4. 为要加入事务的方法指定使用哪个事务管理器。

Spring 中常用的持久化技术主要就是 JdbcTemplate、MyBatis、Hibernate、JPA,本篇中的示例假定单数据源情况下你对 Spring 整合这些持久化技术具有一定的了解,限于篇幅本篇不会在细节上涉及太多,可点击链接了解更多内容。

下面看各持久化技术的多数据源多事务管理器如何进行配置与使用,不感兴趣的内容可直接跳过。

JdbcTemplate

如果业务比较简单,又不想引入其他依赖,可以使用 Spring 自带的 JdbcTemplate,假定有两个数据源,可以做如下配置。

@Configuration
@EnableTransactionManagement
public class JdbcTemplateConfiguration {// 第一个数据源的相关配置@Beanpublic DataSource dataSource1() {DataSource dataSource = ...;return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate1() {return new JdbcTemplate(dataSource1());}@Beanpublic TransactionManager transactionManager1() {PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource1());return transactionManager;}// 第二个数据源的相关配置@Beanpublic DataSource dataSource2() {DataSource dataSource = ...;return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate2() {return new JdbcTemplate(dataSource2());}@Beanpublic TransactionManager transactionManager2() {PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource2());return transactionManager;}
}

主要就是每个数据源配置一套 DataSourceJdbcTemplateTransactionManager,对应关系如下。

数据源 JdbcTemplate 事务管理器
dataSource1 jdbcTemplate1 transactionManager1
dataSource2 jdbcTemplate2 transactionManager2

一个方法内可以使用不同的数据源操作数据库,那么具体加入哪个事务管理器管理的事务呢呢?可以在 @Transactional 注解上指定事务管理器。

@Service
public class UserService {@Qualifier("jdbcTemplate1")@Autowiredprivate JdbcTemplate jdbcTemplate1;@Qualifier("jdbcTemplate2")@Autowiredprivate JdbcTemplate jdbcTemplate2;@Transactional(transactionManager = "transactionManager1")public List<User> list1() {return jdbcTemplate1.query("select * from user", new UserRowMapper());}@Transactional(transactionManager = "transactionManager2")public List<User> list2() {return jdbcTemplate2.query("select * from user", new UserRowMapper());}
}

不过不建议在一个方法内使用多个数据源,Spring 基于线程上下文的事务设计导致只有指定的事务管理器内部的数据源会加入事务中。

MyBatis

除了 JdbcTemplate,另一个最常用的 ORM 框架是 MyBatis,多个数据源的情况下在 Spring 中可以做如下的配置。

@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = "com.zzuhkp.mybatis.mapper1", sqlSessionFactoryRef = "sqlSessionFactory1")
@MapperScan(basePackages = "com.zzuhkp.mybatis.mapper2", sqlSessionFactoryRef = "sqlSessionFactory2")
public class MyBatisConfiguration {// 第一个数据源的相关配置@Beanpublic DataSource dataSource1() {DataSource dataSource = ...;return dataSource;}@Beanpublic SqlSessionFactoryBean sqlSessionFactory1() {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();// 设置数据源sqlSessionFactoryBean.setDataSource(dataSource1());...省略部分代码return sqlSessionFactoryBean;}@Beanpublic TransactionManager transactionManager1() {TransactionManager transactionManager = new DataSourceTransactionManager(dataSource1());return transactionManager;}// 第二个数据源的相关配置@Beanpublic DataSource dataSource2() {DataSource dataSource = ...;return dataSource;}@Beanpublic SqlSessionFactoryBean sqlSessionFactory2() {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();// 设置数据源sqlSessionFactoryBean.setDataSource(dataSource2());...省略部分代码return sqlSessionFactoryBean;}@Beanpublic TransactionManager transactionManager2() {TransactionManager transactionManager = new DataSourceTransactionManager(dataSource2());return transactionManager;}
}

主要就是为每个数据源配置一套 DataSourceSqlSessionFactoryBeanTransactionManager,以及配置 @MapperScan 注解。

@MapperScan 中的 basePackagessqlSessionFactoryRef 属性将不同包下面 Mapper 底层的 SqlSession 区分开。

经过上面的配置后,各组件对应关系如下。

数据源 SqlSessionFactory 事务管理器 Mapper
dataSource1 sqlSessionFactory1 transactionManager1 com.zzuhkp.mybatis.mapper1
dataSource2 sqlSessionFactory2 transactionManager2 com.zzuhkp.mybatis.mapper2

由于 Spring 容器中存在多个事务管理器,必须要在事务方法的 @Transactional 注解上指定使用哪个事务管理器,示例如下。

@Service
public class UserService {@Autowiredprivate UserMapper1 userMapper1;@Autowiredprivate UserMapper2 userMapper2;@Transactional(transactionManager = "transactionManager1")public List<User> list1() {return userMapper1.list();}@Transactional(transactionManager = "transactionManager2")public List<User> list2() {return userMapper2.list();}}

Hibernate

Hibernate 在前些年使用比较多,两个数据源的情况下在 Spring 中可以做如下配置。

@Configuration
@EnableTransactionManagement
public class HibernateConfiguration {// 第一个数据源的相关配置@Beanpublic DataSource dataSource1() {DataSource dataSource = ...return dataSource;}@Beanpublic LocalSessionFactoryBean sessionFactory1() {LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();...省略配置内容return factoryBean;}@Beanpublic TransactionManager transactionManager1(@Qualifier("sessionFactory1") SessionFactory sessionFactory1) {HibernateTransactionManager transactionManager = new HibernateTransactionManager();transactionManager.setSessionFactory(sessionFactory1);transactionManager.setDataSource(dataSource1());return transactionManager;}// 第二个数据源的相关配置@Beanpublic DataSource dataSource2() {DataSource dataSource = ...return dataSource;}@Beanpublic LocalSessionFactoryBean sessionFactory2() {LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();...省略配置内容return factoryBean;}@Beanpublic TransactionManager transactionManager2(@Qualifier("sessionFactory2") SessionFactory sessionFactory2) {HibernateTransactionManager transactionManager = new HibernateTransactionManager();transactionManager.setSessionFactory(sessionFactory2);transactionManager.setDataSource(dataSource1());return transactionManager;}
}

为每套数据源分别配置了 DataSourceLocalSessionFactoryBeanTransactionManager,对应关系如下。

数据源 LocalSessionFactoryBean 事务管理器
dataSource1 sessionFactory1 transactionManager1
dataSource2 sessionFactory2 transactionManager2

同样需要在事务方法的 @Transactional 注解上指定要使用的事务管理器。

@Service
public class UserService {@Qualifier("sessionFactory1")@Autowiredprivate SessionFactory sessionFactory1;@Qualifier("sessionFactory2")@Autowiredprivate SessionFactory sessionFactory2;@Qualifier("jdbcTemplate2")@Autowiredprivate JdbcTemplate jdbcTemplate2;@Transactional(transactionManager = "transactionManager1")public List<User> list1() {return sessionFactory1.getCurrentSession().createSQLQuery("select * from user").list();}@Transactional(transactionManager = "transactionManager2")public List<User> list2() {return sessionFactory2.getCurrentSession().createSQLQuery("select * from user").list();}
}

JPA

基于 JPA 规范操作数据库可以随时替换实现,是 Hibernate 的另一个选择。使用 spring-data-jpa 模块,第一个数据源的配置如下。

@Configuration
@EnableJpaRepositories(basePackages = "com.zzuhkp.jpa.repository1",entityManagerFactoryRef = "entityManagerFactory1",transactionManagerRef = "transactionManager1")
@EnableTransactionManagement
public class JpaConfiguration1 {@Beanpublic DataSource dataSource1() {DataSource dataSource = ...return dataSource;}@Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory1() {LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();entityManagerFactoryBean.setDataSource(dataSource1());...省略部分配置return entityManagerFactoryBean;}@Beanpublic TransactionManager transactionManager1(@Qualifier("entityManagerFactory1") EntityManagerFactory entityManagerFactory1) {JpaTransactionManager transactionManager = new JpaTransactionManager();transactionManager.setEntityManagerFactory(entityManagerFactory1);transactionManager.setDataSource(dataSource1());return transactionManager;}
}

第二个数据源与第一个数据源的配置类似,不再列出,主要就是为每个数据源配置一套 DataSourceEntityManagerFactoryTransactionManager,以及为创建 Repository 接口实例提供 @EnableJpaRepositories,由于 @EnableJpaRepositories 不支持重复注解,可为每个数据源分别提供一个配置类,各组件对应关系如下。

数据源 EntityManagerFactory 事务管理器 Repository
dataSource1 entityManagerFactory1 transactionManager1 com.zzuhkp.jap.repository1
dataSource2 entityManagerFactory2 transactionManager2 com.zzuhkp.jap.repository2

事务方法配置事务管理器的示例如下。

@Service
public class UserService {@Autowiredprivate UserRepository1 userRepository1;@Autowiredprivate UserRepository2 userRepository2;@Transactional(transactionManager = "transactionManager1")public List<User> list1() {return userRepository1.findAll();}@Transactional(transactionManager = "transactionManager2")public List<User> list2() {return userRepository2.findAll();}
}

单事务管理器

多个数据源配置单个事务管理器的好处是可以避免在事务方法上指定要使用的事务管理器。由于每个事务管理器内部只有一个数据源,因此只能使用一个动态数据源,使得在进入事务方法前,Spring 事务管理使用动态数据源路由到的目标数据源获取连接。

多个事务管理器与单个事务管理器的区别可以用如下的图示来表示。
我们的重点也将放在如何在动态数据源内部路由目标数据源。Spring 内部提供了一个 AbstractRoutingDataSource 数据源类用于路由目标数据源,实现比较简单,主要就是将目标数据源以 key-value 的形式存入到内部,由用户决定获取目标数据源的 key。

JdbcTemplate 持久化技术为例,可以修改上述示例中的配置如下。

@Configuration
@EnableTransactionManagement
public class JdbcTemplateConfiguration {// 第一个数据源的相关配置@Beanpublic DataSource dataSource1() {DataSource dataSource = ...;return dataSource;}@Beanpublic DataSource dataSource2() {DataSource dataSource = ...;return dataSource;}@Bean@Primarypublic DataSource primaryDataSource() {// 目标数据源Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("dataSource1", dataSource1());targetDataSources.put("dataSource2", dataSource2());AbstractRoutingDataSource dataSource = new AbstractRoutingDataSource() {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceKeyHolder.get();}};dataSource.setTargetDataSources(targetDataSources);dataSource.setDefaultTargetDataSource(dataSource1());return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate1() {return new JdbcTemplate(primaryDataSource());}@Beanpublic TransactionManager transactionManager() {PlatformTransactionManager transactionManager = new DataSourceTransactionManager(primaryDataSource());return transactionManager;}}

和前面的示例相比,主要是加了一个主数据源,由主数据源路由目标数据源,两个 JdbcTemplate 合并为一个使用主数据源的 JdbcTemplate,两个事务管理器合并为一个使用主数据源的事务管理器。

主数据源是一个实现 AbstractRoutingDataSource 抽象类的动态数据源,这个类只有一个用于获取目标数据源 key 值的 determineCurrentLookupKey 方法需要实现,此外再设置一下可用的目标数据源以及默认数据源就可以了。

在获取 key 值的时候则使用到了我们自定义的 DataSourceKeyHolder.get() 方法,这个方法也比较简单,主要就是将 key 放在线程上下文中,可以在进行事务方法前手动设置 key,也可以利用 AOP 拦截方法,根据方法所在的包名、类名、或者方法上的注解动态设置 key。

DataSourceKeyHolder 定义如下。

public class DataSourceKeyHolder {private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static String get() {return threadLocal.get();}public static void set(String key) {threadLocal.set(key);}
}

假如我们想利用 AOP,在不同的包下面使用不同的数据源,可以配置如下的切面。

@Aspect
@Component
public class DataSourceAop {@Around("execution(* com.zzuhkp.template.business1..*.*(..))")public Object dataSource1(ProceedingJoinPoint joinPoint) throws Throwable {return advice(joinPoint, "datasource1");}@Around("execution(* com.zzuhkp.template.business2..*.*(..))")public Object dataSource2(ProceedingJoinPoint joinPoint) throws Throwable {return advice(joinPoint, "datasource2");}private Object advice(ProceedingJoinPoint joinPoint, String key) throws Throwable {String prevKey = DataSourceKeyHolder.get();DataSourceKeyHolder.set(key);try {return joinPoint.proceed();} catch (Exception e) {throw e;} finally {DataSourceKeyHolder.set(prevKey);}}
}

经过这样的配置,com.zzuhkp.template.business1 包将使用 dataSource1 数据源,com.zzuhkp.template.business2 包将使用 dataSource2 数据源。

service 示例如下。

package com.zzuhkp.template.business1;@Service
public class Business1Service {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactional(rollbackFor = Exception.class)public void doSomething(){jdbcTemplate.update("...");}
}

Business1Service 类在 business1 包中,将使用 dataSource1 作为数据源并加入 Spring 事务管理。

由于主数据源被设置为 Primary,spring-boot 环境下还能利用自动化配置的特性,用户只需要配置数据源就可以了,其他的工作可以由具体的持久化技术对应的 spring-boot-starter 直接完成。

分布式事务

如果想要多个数据源加入一个事务则需要使用分布式事务的一些解决方案,这些解决方案一般来说都比较复杂,能够使用单机事务的情况下务必不要使用分布式事务。

分布式事务的一些常见解决方案包括 XA、TCC、本地消息、事务消息、最大努力通知,等等…

这里介绍是是基于 XA 协议实现的 JTA 在 Spring 中的应用,由于 JTA 只是一套规范,在非 EJB 容器环境下我们可以使用 Atomikos 作为实现。

Spring 环境下的 Atomikos 配置如下。

@Configuration
@EnableTransactionManagement
public class JTAConfiguration {// 数据源一配置@Bean(initMethod = "init", destroyMethod = "close")public AtomikosDataSourceBean dataSource1() {AtomikosDataSourceBean ds = new AtomikosDataSourceBean();...省略配置return ds;}@Beanpublic JdbcTemplate jdbcTemplate1() {return new JdbcTemplate(dataSource1());}// 数据源二配置@Bean(initMethod = "init", destroyMethod = "close")public AtomikosDataSourceBean dataSource2() {AtomikosDataSourceBean ds = new AtomikosDataSourceBean();...省略配置return ds;}@Beanpublic JdbcTemplate jdbcTemplate2() {return new JdbcTemplate(dataSource2());}// Atomikos 对 JTA 事务管理器的实现@Bean(initMethod = "init", destroyMethod = "close")public UserTransactionManager userTransactionManager() throws SystemException {UserTransactionManager userTransactionManager = new UserTransactionManager();userTransactionManager.setTransactionTimeout(300);userTransactionManager.setForceShutdown(true);return userTransactionManager;}// Spring 事务管理器@Beanpublic JtaTransactionManager jtaTransactionManager() throws SystemException {JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();jtaTransactionManager.setTransactionManager(userTransactionManager());jtaTransactionManager.setUserTransaction(userTransactionManager());return jtaTransactionManager;}
}

我们为每个数据源分别配置了 Atomikos 提供的 DataSource 以及 JdbcTemplate,然后还配置了 Atomikos 提供的 JTA 事务管理器实现以及 Spring 的 JTA 事务管理器。

此时在 Spring 项目中已经可以正常使用事务了,与单机事务完全一致,示例如下。

@Service
public class UserService {@Qualifier("jdbcTemplate1")@Autowiredprivate JdbcTemplate jdbcTemplate1;@Qualifier("jdbcTemplate2")@Autowiredprivate JdbcTemplate jdbcTemplate2;@Transactional(rollbackFor = Exception.class)public List<User> list1() {List<User> list = new ArrayList<>();list.addAll(jdbcTemplate1.query("select * from user", new UserRowMapper()));list.addAll(jdbcTemplate2.query("select * from user", new UserRowMapper()));return list;}}

事务方法内可以使用多个数据源操作数据库,由 Spring 事务管理器协调将这些数据源对数据库的操作加入到一个 JTA 事务中。

总结

本文主要介绍了 Spring 项目中的多个数据源如何加入到 Spring 的事务管理中,包括了多种持久化技术以及单机事务与分布式事务的解决方案,内容相对比较全面了,如果你还有疑问,不妨留言交流。

Spring 多数据源配置详解相关推荐

  1. Springboot多数据源配置详解

    Springboot多数据源配置详解 概念 配置 多数据源使用 概念 一般来说,我们正常的业务只涉及一个数据源,在特定的业务场景中需要使用多个数据源的情况,就需要配置多个数据源来满足特定的业务需求.本 ...

  2. SpringBoot配置文件中spring.profiles.active配置详解

    SpringBoot配置文件中spring.profiles.active配置详解 1.多环境配置 我们在开发Spring Boot应用时,通常同一套程序会被应用和安装到几个不同的环境,比如:开发.测 ...

  3. Spring拦截器配置详解(如何定义一个拦截器)

    一.前言 Spring和SpringBoot的拦截器也是对请求进行的系列验证或处理,关于拦截器和过滤器的区别此文不做介绍,之前我看到过一篇相关系列的文章,讲述的还比较详细,给大家参考参考:拦截器与过滤 ...

  4. hikari数据源配置类_SpringBoot多数据源配置详解

    开发环境:JDK1.8+SpringBoot2.1.4.RELEASE+Oracle 这里我们假设要使用两个数据源分别为:master和slave. pom.xml 依赖包 org.springfra ...

  5. Spring之——c3p0配置详解

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/51162560 今天,我们就来详细谈谈Spring中的c3p0配置问题,好了,不耽搁 ...

  6. Spring的datasource配置详解

    一句话,Spring对Hibernate的整合,是在applicationContext.xml中配置sessionFactory来实现的,其中sessionFactory中要装配dataSource ...

  7. Spring Hibernate Mybatis配置详解

    2019独角兽企业重金招聘Python工程师标准>>> 1. Spring + Hibernate4(spring-hibernate.xml) <!-- 引入jdbc pro ...

  8. Spring Boot Logback 配置详解

    1.Logback简介 Logback是由log4j创始人设计的又一个开源日志组件. 相比于log4j,Logback重写了内核,在一些关键执行路径上性能提升10倍以上.而且logback不仅性能提升 ...

  9. Spring的sessionFactory配置详解

    一句话,Spring对Hibernate的整合,是在applicationContext.xml中配置sessionFactory来实现的,其中sessionFactory中要装配dataSource ...

最新文章

  1. 解决ecilpse插件安装速度变得很慢
  2. mongodb授权登录,经过自己修改后的授权登录方式
  3. 【Oracle RAC故障分析与处理】
  4. MySql分区表性能测试及切换案例
  5. 四种类型转换 cast
  6. git rebase --skip_可冒充git大神的git tips
  7. IE7下面 overflow:hidden 无效
  8. 搜狐html源码,使用css和html模仿搜狐页面
  9. mysql表死锁查询
  10. 散粉在哪个步骤用_如何正确的使用散粉
  11. 获取一个类的信息(仿YYClassInfo类)
  12. 软件测试的错误优先级,软件测试典型错误
  13. 纯Css比较好看的中英文字体样式(持续整理版)
  14. cenos回到linux桌面快捷键,CentOS 常用命令及快捷键整理
  15. 国密SM算法有哪些?
  16. 蓝宝石英语怎么读,sapphire是什么意思_sapphire的翻译_音标_读音_用法_例句_爱词霸在线词典...
  17. 数论-FTT 和 NTT
  18. 淘宝旺旺智能回复软件 旺旺智能客服 旺旺聊天机器人
  19. CSS基础- 3.14 定位练习-弹出层
  20. 五位大神级人物带你多角度看懂程序化交易

热门文章

  1. 139邮箱发送邮件(python web自动化)
  2. 成龙在北大的演讲:值得每一个中国人看
  3. oracle asru,最佳实践之二:VSP+ORACLE
  4. 济南计算机学院排名,济南市最好的10所大学排名,山大第1,山东师范第3,济南大学第7...
  5. 易飞ERP控制返回数据笔数
  6. 1230v3配服务器内存性能提升,有点小失望 E3-1280 v3性能实测
  7. redis rdb文件恢复数据注意、踩坑
  8. Swift 网络请求库Moya的使用
  9. android+m3+max,Android Opencore OpenMAX学习(3)...
  10. SAP BW-Function Module 数据源ABAP开发