Spring boot配置mybatis多数据源

 根据业务要求,需要实现数据库的读写分离,即一个应用配置多个数据库。网上看了一些案例,总结了一个最简单的配置方法。

原理: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);
    }

 

posted on 2018-05-22 14:49 郭ds 阅读() 评论() 编辑 收藏

版权声明:本文为guods原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/guods/p/9071749.html