点击上方 Java后端,选择 设为星标

来自 | 韩数
链接 | juejin.im/post/5d830944f265da03963bd153
0、前言
1、数据库准备

2、环境准备
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.5.RELEASE</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId></dependency><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version></dependency>
3、代码部分
首先呢,在我们Springboot的配置文件中配置我们的datasourse,和以往不一样的是,因为我们有两个数据源,所以要指定相关数据库的名称,其中主数据源为primary,次数据源为secondary如下:
#配置主数据库spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=falsespring.datasource.primary.username=rootspring.datasource.primary.password=rootspring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
##配置次数据库spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=falsespring.datasource.secondary.username=rootspring.datasource.secondary.password=rootspring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.http.encoding.charset=UTF-8spring.http.encoding.enabled=truespring.http.encoding.force=true
需要我们注意的是,Springboot2.0 在配置数据库连接的时候需要使用jdbc-url,如果只使用url的话会报jdbcUrl is required with driverClassName.错误。
新建一个配置文件,DynamicDataSourceConfig 用来配置我们相关的bean,代码如下
@Configuration@MapperScan(basePackages = "com.mzd.multipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages 我们接口文件的地址public class DynamicDataSourceConfig {
// 将这个对象放入Spring容器中 @Bean(name = "PrimaryDataSource") // 表示这个数据源是默认数据源 @Primary // 读取application.properties中的配置参数映射成为一个对象 // prefix表示参数的前缀 @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource getDateSource1() { return DataSourceBuilder.create().build(); }
@Bean(name = "SecondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource getDateSource2() { return DataSourceBuilder.create().build(); }
@Bean(name = "dynamicDataSource") public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource, @Qualifier("SecondaryDataSource") DataSource secondaryDataSource) {
//这个地方是比较核心的targetDataSource 集合是我们数据库和名字之间的映射 Map<Object, Object> targetDataSource = new HashMap<>(); targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource); targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSource); dataSource.setDefaultTargetDataSource(primaryDataSource);//设置默认对象 return dataSource; }
@Bean(name = "SqlSessionFactory") public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dynamicDataSource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*/*.xml"));//设置我们的xml文件路径 return bean.getObject(); }}
而在这所有的配置中,最核心的地方就是DynamicDataSource这个类了,DynamicDataSource是我们自定义的动态切换数据源的类,该类继承了AbstractRoutingDataSource 类并重写了它的determineCurrentLookupKey()方法。
AbstractRoutingDataSource 类内部维护了一个名为targetDataSources的Map,并提供的setter方法用于设置数据源关键字与数据源的关系,实现类被要求实现其determineCurrentLookupKey()方法,由此方法的返回值决定具体从哪个数据源中获取连接。同时AbstractRoutingDataSource类提供了程序运行时动态切换数据源的方法,在dao类或方法上标注需要访问数据源的关键字,路由到指定数据源,获取连接。
DynamicDataSource代码如下:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override protected Object determineCurrentLookupKey() { DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType(); return dataBaseType; }
}
DataSourceType类的代码如下:
public class DataSourceType {
//内部枚举类,用于选择特定的数据类型 public enum DataBaseType { Primary, Secondary }
// 使用ThreadLocal保证线程安全 private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();
// 往当前线程里设置数据源类型 public static void setDataBaseType(DataBaseType dataBaseType) { if (dataBaseType == null) { throw new NullPointerException(); } TYPE.set(dataBaseType); }
// 获取数据源类型 public static DataBaseType getDataBaseType() { DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get(); return dataBaseType; }
// 清空数据类型 public static void clearDataBaseType() { TYPE.remove(); }
}
接下来编写我们相关的Mapper和xml文件,代码如下:
@Component@Mapperpublic interface PrimaryUserMapper {
List<User> findAll();}
@Component@Mapperpublic interface SecondaryUserMapper {
List<User> findAll();}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jdkcb.mybatisstuday.mapper.one.PrimaryUserMapper">
<select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User"> select * from sys_user; </select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jdkcb.mybatisstuday.mapper.two.SecondaryUserMapper">
<select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User"> select * from sys_user2; </select>
</mapper>
相关接口文件编写好之后,就可以编写我们的aop代码了:
@Aspect@Componentpublic class DataSourceAop { //在primary方法前执行 @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.primary(..))") public void setDataSource2test01() { System.err.println("Primary业务"); DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary); }
//在secondary方法前执行 @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.secondary(..))") public void setDataSource2test02() { System.err.println("Secondary业务"); DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary); }}
编写我们的测试 UserController, 代码如下:
@RestControllerpublic class UserController {
@Autowired private PrimaryUserMapper primaryUserMapper; @Autowired private SecondaryUserMapper secondaryUserMapper;
@RequestMapping("primary") public Object primary(){ List<User> list = primaryUserMapper.findAll(); return list; } @RequestMapping("secondary") public Object secondary(){ List<User> list = secondaryUserMapper.findAll(); return list; }
}
4、测试
启动项目,在浏览器中分别输入http://127.0.0.1:8080/primary 和http://127.0.0.1:8080/primary ,结果如下:
[{"user_id":1,"user_name":"张三","user_age":25}][{"user_id":1,"user_name":"李四","user_age":30}]
5、等等
等等,啧啧啧,我看你这不行啊,还不够灵活,几个菜啊,喝成这样,这就算灵活了?
那肯定不能的,aop也有aop的好处,比如两个包下的代码分别用两个不同的数据源,就可以直接用aop表达式就可以完成了,但是,如果想本例中方法级别的拦截,就显得优点不太灵活了,这个适合就需要我们的注解上场了。
6、配合注解实现
首先自定义我们的注解 @DataSource
/** * 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别 */@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataSource { String value() default "primary"; //该值即key值,默认使用默认数据库}
通过使用aop拦截,获取注解的属性value的值。如果value的值并没有在我们DataBaseType里面,则使用我们默认的数据源,如果有的话,则切换为相应的数据源。
@Aspect@Componentpublic class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(dataSource)")//拦截我们的注解 public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable { String value = dataSource.value(); if (value.equals("primary")){ DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary); }else if (value.equals("secondary")){ DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary); }else { DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);//默认使用主数据库 }
}
@After("@annotation(dataSource)") //清除数据源的配置 public void restoreDataSource(JoinPoint point, DataSource dataSource) { DataSourceType.clearDataBaseType();
}}
7、测试
修改我们的mapper,添加我们的自定义的@DataSouse注解,并注解掉我们DataSourceAop类里面的内容。如下:
@Component@Mapperpublic interface PrimaryUserMapper {
@DataSource List<User> findAll();}
@Component@Mapperpublic interface SecondaryUserMapper {
@DataSource("secondary")//指定数据源为:secondary List<User> findAll();}
启动项目,在浏览器中分别输入http://127.0.0.1:8080/primary 和http://127.0.0.1:8080/primary ,结果如下:
[{"user_id":1,"user_name":"张三","user_age":25}][{"user_id":1,"user_name":"李四","user_age":30}]
到此,就算真正的大功告成啦。
8、源码
github.com/hanshuaikang/HanShu-Note
学Java,请关注公众号:Java后端

本文分享自微信公众号 - Java后端(web_resource)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
转载请注明:Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置 | 胖虎的工具箱-编程导航

