根据业务要求, 需要实现数据库的读写分离, 即一个应用配置多个数据库. 网上看了一些案例, 总结了一个最简单的配置方法.
原理: mybatis 通过 SqlSessionFactory 获取 session 从而执行 sql 语句, 而 SqlSessionFactory 是从 DataSource 获取 session 的. spring boot 提供了一个动态切换数据库的抽象类 AbstractRoutingDataSource, 我们只需继承该抽象类创建一个动态数据源, 并配置到 SqlSessionFactory 即可. AbstractRoutingDataSource 会根据 determineCurrentLookupKey()方法返回的数据源 key 获取对应的数据源.
配置过程:
一, 关闭数据源自动配置
在 @SpringBootApplication 注解上加 exclude= {DataSourceAutoConfiguration.class}即可. 否则 spring boot 会启用自动配置数据源.
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
如果不关闭会报异常: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?
二, 继承 AbstractRoutingDataSource 创建动态数据源类
只需实现 determineCurrentLookupKey()一个方法即可. 该方法是获取数据源的名字, AbstractRoutingDataSource 要通过这个名字去找对应的数据源. 这个方法怎么写, 且看稍后解释.
- public class DynamicDataSource extends AbstractRoutingDataSource {
- @Override
- protected Object determineCurrentLookupKey() {
- return DynamicDataSourceHolder.getDataSourceKey();
- }
- }
三, application.properties 配置文件配置数据源. 注意, mysql 的连接串名字是 jdbc-url, 而不是 url
- spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/test
- spring.datasource.master.username=root
- spring.datasource.master.password=root
- spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/test_slave
- spring.datasource.slave.username=root
- spring.datasource.slave.password=root
四, 配置 bean
1, 用 @ConfigurationProperties 读取 application.properties 的数据库连接串, 用户名, 密码, 配置每个 DataSource 的 bean. 使用 DataSourceBuilder 创建数据源时, 如果有 DBCP 的 jar 包, 则会自动启用 DBCP 连接池. 如果需要使用 druid 等其他连接池也可以通过配置对应的连接池 bean 实现.
2, 创建动态数据源类.
这个 bean 是动态数据源选择的核心. 实例化 DynamicDataSource, 把上面配置的两个实际数据源以 map 的形式配置到动态数据源上. 而上面第二步 determineCurrentLookupKey()返回的值就是这个 map 的 key, 通过这个 key 就能定位到实际数据源.
3, 配置 SqlSessionFactory
把 SqlSessionFactory 的数据源设置为上一步配置的动态数据源. 指定 mapperLocations, 即 mapper 对应的. xml 的位置. 如果不指定会报错找不到 mapper 对应的 statement.
这一步整个 bean 配置如下:
- @Configuration
- public class DynamicDataSourceConfig {
- //////////////////// 配置多个 DataSource
- @Bean
- @ConfigurationProperties(prefix = "spring.datasource.master")
- public DataSource masterDataSource() {
- return DataSourceBuilder.create().build();
- }
- @Bean
- @ConfigurationProperties(prefix = "spring.datasource.slave")
- public DataSource slaveDataSource() {
- return DataSourceBuilder.create().build();
- }
- //////////////////// 创建动态数据源
- @Bean
- public DataSource dataSource() {
- DynamicDataSource dynamicDataSource = new DynamicDataSource();
- // 创建默认数据源
- dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
- // 配置多数据源
- Map<Object, Object> dataSourceMap = new HashMap<>(4);
- dataSourceMap.put("master", masterDataSource());
- dataSourceMap.put("slave", slaveDataSource());
- dynamicDataSource.setTargetDataSources(dataSourceMap);
- return dynamicDataSource;
- }
- //////////////////// 配置 SqlSessionFactory
- @Bean
- public SqlSessionFactory sqlSessionFactory() throws Exception {
- SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
- // 设置数据源为动态数据源
- sqlSessionFactoryBean.setDataSource(dataSource());
- //mapper 的. xml 文件位置
- PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
- sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/*.xml"));
- return sqlSessionFactoryBean.getObject();
- }
- }
五, 数据源选择
为了使用方便, 我们使用注解 + AOP 的方式来实现数据源的选择.
1, 创建一个存放数据源 key 的 ThreadLocal,DynamicDataSourceHolder
- public class DynamicDataSourceHolder {
- private static final ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<String>();
- private static Set<String> dataSourceKeys = new HashSet<String>();
- static {
- dataSourceKeys.add("master");
- dataSourceKeys.add("slave");
- }
- public static void setDataSourceKey(String dataSourceKey) {
- dataSourceThreadLocal.set(dataSourceKey);
- }
- public static String getDataSourceKey() {
- return dataSourceThreadLocal.get();
- }
- public static void clearDataSourceKey() {
- dataSourceThreadLocal.remove();
- }
- public static boolean containsDataSourceKey(String dataSourceKey) {
- return dataSourceKeys.contains(dataSourceKey);
- }
- }
2, 创建注解, value 值为数据源的 key, 用于选择数据源
- @Target({ ElementType.METHOD })
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface TargetDataSource {
- String value() default "master";
- }
3,aop 读取注解, 把注解的 value 设置到 DynamicDataSourceHolder 的线程上下文上. 对应上面第二步的 determineCurrentLookupKey()方法, 选择数据源就是从这个线程上下文中取得这一步设置的数据源 key. 数据源必须在事务执行之前设置好, 所以这一步的 aop 的优先级必须在 @Transctional 之前.
- @Aspect
- @Order(-10) // 保证该 AOP 在 @Transactional 之前执行
- @Component
- public class DynamicDataSourceAspect {
- private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
- @Before("@annotation(targetDataSource)")
- public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
- // 获取当前的指定的数据源;
- String dataSourceKey = targetDataSource.value();
- // 如果不在我们注入的所有的数据源范围之内, 那么输出警告信息, 系统自动使用默认的数据源.
- if (!DynamicDataSourceHolder.containsDataSourceKey(dataSourceKey)) {
- log.info("数据源 [{}] 不存在, 使用默认数据源> {}", targetDataSource.value(), point.getSignature());
- } else {
- log.info("Use DataSource : {}> {}", targetDataSource.value(), point.getSignature());
- // 找到的话, 那么设置到动态数据源上下文中.
- DynamicDataSourceHolder.setDataSourceKey(targetDataSource.value());
- }
- }
- @After("@annotation(targetDataSource)")
- public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
- log.info("Revert DataSource : {}> {}", targetDataSource.value(), point.getSignature());
- // 方法执行完毕之后, 销毁当前数据源信息, 进行垃圾回收.
- DynamicDataSourceHolder.clearDataSourceKey();
- }
- }
六, 使用
在方法上加 @TargetDataSource 注解即可. 如果不加注解或者注解上的 value 不存在, 则使用默认数据源.
- @TargetDataSource("master")
- public BookItem getByIdMaster(Long id){
- return userMapper.selectByPrimaryKey(id);
- }
- @TargetDataSource("slave")
- public BookItem getByIdSlave(Long id){
- return userMapper.selectByPrimaryKey(id);
- }
来源: https://www.cnblogs.com/guods/p/9071749.html