Spring boot配置mybatis多数据源
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); }