当我们的项目需要同时连接2个数据库时,这时就要用来双数据源的配置了
网上有很多实现方法,相比较来说基于注解方式是最优雅的一种方式了,
这次我就讲下mybatis基于注解方式实现双数据源配置
spring:
jmx:
default-domain: mybatis
datasource:
db1:
jdbc-url: jdbc:mysql://localhost:3306/mybatis-demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
max-idle: 10
max-wait: 10000
min-idle: 5
initial-size: 5
dialect: mysql
db2:
jdbc-url: jdbc:mysql://localhost:3306/mybatis-demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
max-idle: 10
max-wait: 10000
min-idle: 5
initial-size: 5
dialect: mysql
mybatis:
typeAliasesPackage: com.wg.demo.po
mapper-locations: classpath*:mapper/*.xml
configuration:
cache-enabled: true
配置了2个数据源,db1和db2
DataSourceConfig负责加载配置文件中数据源的配置信息
/**
* @Author: wanggang.io
* @Date: 2018/12/28 9:50
* @todo
*/
@Configuration
public class DataSourceConfig {
//数据源1
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.db1") // application.properteis中对应属性的前缀
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
//数据源2
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.db2") // application.properteis中对应属性的前缀
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap();
dsMap.put("db1", dataSource1());
dsMap.put("db2", dataSource2());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 配置@Transactional注解事物
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
DataSourceContextHolder用来获取及设置当前数据源
/**
* @Author: wanggang.io
* @Date: 2018/12/28 9:50
* @todo
*/
public class DataSourceContextHolder {
static Logger logger = LoggerFactory.getLogger("DataSourceContextHolder");
/**
* 默认数据源
*/
public static final String DEFAULT_DS = DataSourceEnum.DB1.getValue();
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
String dbsource = contextHolder.get();
if(dbsource == null ){
dbsource = "db1";
logger.info("数据源为NULL 返回默认数据源"+ dbsource);
}
return dbsource;
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
枚举类
public enum DataSourceEnum {
DB1("db1"),DB2("db2");
private String value;
DataSourceEnum(String value){this.value=value;}
public String getValue() {
return value;
}
}
自定义DS注解
/**
* @Author: wanggang.io
* @Date: 2018/12/28 9:50
* @todo 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DS {
String value() default "db1";
}
注解切面类,根据接口上的注解动态切换数据源
/**
* @Author: wanggang.io
* @Date: 2018/12/28 15:50
* @todo自定义注解 + AOP的方式实现数据源动态切换。
*/
@Aspect
@Component
public class DynamicDataSourceAspect {
private Logger logger = LoggerFactory.getLogger(getClass().getName());
public DynamicDataSourceAspect() {}
@Before("@annotation(DS)")
public void beforeSwitchDS(JoinPoint point) {
try {
Object methodInvocation = FunUtil.getPrivateField(point, "methodInvocation", point.getClass());
Method method = (Method) FunUtil.getPrivateField(methodInvocation, "method", methodInvocation.getClass());
DS annotation = method.getAnnotation(DS.class);
DataSourceContextHolder.setDB(annotation.value());
} catch (Exception e) {
e.printStackTrace();
}
logger.info("当前数据源为" + DataSourceContextHolder.getDB());
}
@After("@annotation(DS)")
public void afterSwitchDS(JoinPoint point) {
DataSourceContextHolder.clearDB();
}
}
动态数据源,重载函数用于返回当前数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
private Logger logger = LoggerFactory.getLogger(getClass().getName());
@Override
protected Object determineCurrentLookupKey() {
String dbsource = DataSourceContextHolder.getDB();
if(dbsource == null ){
dbsource = "db1";
logger.info("数据源为NULL 返回数据源"+DataSourceContextHolder.getDB());
}
return dbsource;
}
}
其他工具类:
public class FunUtil {
public static Object getPrivateField(Object obj, String fieldName, Class classs) {
try {
Field field = classs.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
try {
classs = classs.getSuperclass();
Field field = classs.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception ex) {
return null;
}
}
}
}
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}/*禁止springboot自动注入数据源配置*/)
@MapperScan({"com.wg.demo.dao"})
@EnableTransactionManagement
@EnableCaching
public class MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class, args);
}
}
Mapper接口文件定义2个接口,分别对应2个数据源的查询操作
@DS(“db1”) 表示该查询使用的是数据源1
@DS(“db2”) 表示该查询使用的是数据源2
@DS("db1")
Employee selectFromDb1(Long id);
@DS("db2")
Employee selectFromDb2(Long id);
XML定义如下:
<select id="selectFromDb2" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from employee
where id = #{id,jdbcType=BIGINT}
select>
<select id="selectFromDb1" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from employee
where id = #{id,jdbcType=BIGINT}
select>
controller层定义如下:
@ApiOperation(value = "从数据源1查询")
@GetMapping("getfromdb1")
public ResultMsg getEmployeeFromDb1(Long id){
return ResultMsg.getMsg(employeeMapper.selectFromDb1(id));
}
@ApiOperation(value = "从数据源2查询")
@GetMapping("getfromdb2")
public ResultMsg getEmployeeFromDb2(Long id){
return ResultMsg.getMsg(employeeMapper.selectFromDb2(id));
}
console打印信息如下:
2019-09-18 11:26:43.645 INFO 1952 --- [nio-9393-exec-7] com.wg.demo.common.aop.LogAspect : Request : {url='http://localhost:9393/mybatis/employee/getfromdb1', ip='0:0:0:0:0:0:0:1', classMethod='com.wg.demo.controller.EmployeeController.getEmployeeFromDb1', args=[1]}
2019-09-18 11:26:43.646 INFO 1952 --- [nio-9393-exec-7] com.wg.demo.common.aop.LogAspect : request Param: [1]
2019-09-18 11:26:43.653 INFO 1952 --- [nio-9393-exec-7] c.w.d.c.d.DynamicDataSourceAspect : 当前数据源为db1
2019-09-18 11:26:44.207 DEBUG 1952 --- [nio-9393-exec-7] c.w.d.dao.EmployeeMapper.selectFromDb1 : ==> Preparing: select id, name, age, gender, dept_id, address, create_time from employee where id = ?
2019-09-18 11:26:44.228 DEBUG 1952 --- [nio-9393-exec-7] c.w.d.dao.EmployeeMapper.selectFromDb1 : ==> Parameters: 1(Long)
2019-09-18 11:26:44.268 DEBUG 1952 --- [nio-9393-exec-7] c.w.d.dao.EmployeeMapper.selectFromDb1 : <== Total: 1
2019-09-18 11:31:33.066 INFO 1952 --- [nio-9393-exec-9] com.wg.demo.common.aop.LogAspect : Request : {url='http://localhost:9393/mybatis/employee/getfromdb2', ip='0:0:0:0:0:0:0:1', classMethod='com.wg.demo.controller.EmployeeController.getEmployeeFromDb2', args=[2]}
2019-09-18 11:31:33.082 INFO 1952 --- [nio-9393-exec-9] com.wg.demo.common.aop.LogAspect : request Param: [2]
2019-09-18 11:31:33.082 INFO 1952 --- [nio-9393-exec-9] c.w.d.c.d.DynamicDataSourceAspect : 当前数据源为db2
2019-09-18 11:31:33.082 INFO 1952 --- [nio-9393-exec-9] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
2019-09-18 11:31:33.088 INFO 1952 --- [nio-9393-exec-9] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed.
2019-09-18 11:31:33.088 DEBUG 1952 --- [nio-9393-exec-9] c.w.d.dao.EmployeeMapper.selectFromDb2 : ==> Preparing: select id, name, age, gender, dept_id, address, create_time from employee where id = ?
通过日志可以看到数据源切换正常
至此完毕,喜欢的朋友记得点赞哦
项目地址:
https://github.com/bdqx007/Mybatis_demo/tree/master/mybatis-demo%E5%8F%8C%E6%95%B0%E6%8D%AE%E6%BA%90