sprinboot mybatis使用继承AbstractRoutingDataSource获取多数据源
AbstractRoutingDataSource:这个类是实现多数据源的关键,他的作用就是动态切换数据源,实质:有多少个数据源就存多少个数据源在targetDataSources(是AbstractRoutingDataSource的一个map类型的属性,其中value为每个数据源,key表示每个数据源的名字)这个属性中,然后根据determineCurrentLookupKey()这个方法获取当前数据源在map中的key值,然后determineTargetDataSource()方法中动态获取当前数据源,如果当前数据源不存并且默认数据源也不存在就抛出异常。
一、application.properties配置文件
# 配置多数据源
spring.datasource.db1.url=jdbc:mysql://127.0.0.1:3306/kou
spring.datasource.db1.username=root
spring.datasource.db1.password=admin
# 使用druid数据源
spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db1.max-idle=10
spring.datasource.db1.max-wait=10000
spring.datasource.db1.min-idle=5
spring.datasource.db1.initial-size=5
# 第二个数据库
spring.datasource.db2.url=jdbc:mysql://127.0.0.1:3306/zheng
spring.datasource.db2.username=root
spring.datasource.db2.password=admin
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
# mapper映射文件
mybatis.type-aliases-package=com.test.spike.*
mybatis.mapper-locations=classpath*:com/test/spike/**/*.xml
#开启驼峰命名转换
mybatis.configuration.map-underscore-to-camel-case=true
二、定义数据库
枚举出多数据源
package com.test.spike.datasources.aop;
/**
* 列出所有数据源key(常用数据库名称来命名)
* 注意:
* 1)这里数据源与数据库是一对一的
* 2)DatabaseType中的变量名称就是数据库的名称
*/
public enum DataSourceType {
kou, zheng
}
三、使用线程保存当前所需要的数据源
package com.test.spike.datasources.aop;
/**
* 构建一个DataSourceType容器,并提供了向其中设置和获取DataSourceType的方法
* 保存一个线程安全的DatabaseType容器
*/
public class DataBaseContextHolder {
private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();
/**
* 向当前线程里添加数据源类型
* @param type
*/
public static void setDataSourceType(DataSourceType type) {
if (type == null) {
throw new NullPointerException();
}
contextHolder.set(type);
}
/**
* 获取数据源类型
* @return
*/
public static DataSourceType getDataSourceType() {
DataSourceType type = contextHolder.get();
if (type == null) {
// 如果当前数据源类型为空,取默认数据源
type = DataSourceType.kou;
}
System.err.println("[获取当前数据源的类型为]:" + type);
return type;
}
/**
* 清空数据源类型
*/
public static void clearDataSourceType() {
contextHolder.remove();
}
}
四、定义一个动态数据源:继承AbstractRoutingDataSource 抽象类,并重写determineCurrentLookupKey()方法
package com.test.spike.datasources.aop;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源(需要继承AbstractRoutingDataSource)
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataBaseContextHolder.getDataSourceType();
}
}
五、定义多数据源
package com.test.spike.datasources.aop;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* springboot集成mybatis的基本入口
* 1)创建数据源(如果采用的是默认的tomcat-jdbc数据源,则不需要)
* 2)创建SqlSessionFactory 3)配置事务管理器,除非需要使用事务,否则不用配置
* @MapperScan(basePackages) basePackages 要写只有dao层接口的路径,不然其他bean也会被变成ibatis的bean了,直接会报
* org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.test.spike.biz.SeckillService.getSeckillListPH
*/
@Configuration
@MapperScan(basePackages = {"com.test.spike.dao", "com.test.spike.db2"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
@Autowired
private Environment env;
/**
* 设置默认数据库
* @return
*/
@Primary
@Bean(name="kouDataSource")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource getDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name="zhengDataSource")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource getDataSource1() {
return DataSourceBuilder.create().build();
}
/**
* 创建数据源
* @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
* @param kouDataSource
* @param zhengDataSource
* @return
*/
@Bean
public DynamicDataSource dynamicDataSource(@Qualifier("kouDataSource") DataSource kouDataSource,
@Qualifier("zhengDataSource") DataSource zhengDataSource) {
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DataSourceType.kou, kouDataSource);
targetDataSource.put(DataSourceType.zheng, zhengDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
// 设置默认数据库
dataSource.setDefaultTargetDataSource(kouDataSource);
return dataSource;
}
/**
* 根据数据源创建SqlSessionFactory
* @return
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// 指定数据源
bean.setDataSource(dynamicDataSource);
// 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
// bean.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));
// bean.setTypeAliasesPackage(env.getProperty("type-aliases-package"));
String mybatisMapperLocations = "";
if (null != env.getProperty("mybatis.mapperLocations")) {
mybatisMapperLocations = env.getProperty("mybatis.mapperLocations");
} else if (null != env.getProperty("mybatis.mapper-locations")) {
mybatisMapperLocations = env.getProperty("mybatis.mapper-locations");
} else {
mybatisMapperLocations = "classpath*:com/test/spike/**/*.xml";
}
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mybatisMapperLocations));
return bean.getObject();
}
/**
* 配置事务管理器
* @return
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}
注意:
1、@Primary 要写在数据源上,不能写在
public DynamicDataSource dynamicDataSource 方法上,写在这里会报
The dependencies of some of the beans in the application context form a cycle:
2、@MapperScan(basePackages = {"com.test.spike.dao", "com.test.spike.db2"} 要写dao层接口文件目录,不然其他bean也会被变成ibatis的bean了,直接会报
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.test.spike.biz.SeckillService.getSeckillListPH
SeckillService 是service接口bean,getSeckillListPH这个是接口的方法,报这个错原因看过了,被代理成ibatis了
六、调用sql时动态添加sql所在的数据源,可以使用aop切入也可以使用在调用之前加 DataBaseContextHolder.setDataSourceType(DataSourceType.kou); 设置数据库
aop切入dao层接口
package com.test.spike.datasources.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 多数据源切面
*/
@Aspect
@Component
public class DataSourceAop {
@Before("execution(* com.test.spike.dao..*.*(..))")
public void setKouDataSource() {
System.out.println("kou数据库");
DataBaseContextHolder.setDataSourceType(DataSourceType.kou);
}
@Before("execution(* com.test.spike.db2..*.*(..))")
public void setZhengDataSource() {
System.out.println("zheng数据库");
DataBaseContextHolder.setDataSourceType(DataSourceType.zheng);
}
}
七、文件目录截图