SpringBoot多数据源配置以及动态切换
原理:利用切面编程,自定义注解实现多数据源的配置以及动态切换
1.核心依赖和yml文件的配置
核心依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
ym配置文件
server:
port: 8080
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
druid:
test:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
connection-properties: clientEncoding=UTF-8;allowMultiQueries=true
user:
url: jdbc:mysql://localhost:3306/user
username: root
password: 123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,此处是filter修改的地方
filters:
commons-log.connection-logger-name: stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
# 配置 DruidStatFilter
web-stat-filter:
enabled: true
url-pattern:
mybatis:
#实体类所做包
#type-aliases-package:
#mapper.xml所在位置
mapper-locations: classpath:mappers
3.定义枚举类(或者常量类或者接口)设置数据源标识
public enum DataSourceType {
TEST,
USER
}
4.自定义注解,用于切换数据源
@Target({ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
DataSourceType value() default DataSourceType.TEST;
}
5 维护一个ThreadLocal来存取数据源标识
@Slf4j
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
log.info("已切换到{}数据源", dataSourceType);
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void removeDataSourceType() {
contextHolder.remove();
}
}
ps: ThreadLocal类似存放线程的局部变量,实际是维护了一个ThreadLocalMap,ThreadLocalMap通过ThreadLocal的静态内部类实现,其key为ThreadLocal对象,是弱引用;其value一般为强引用对象,每次调用set,get,remove方法都会清除ThreadLocalMap中key为null的键值对。 详请可参考该博客:
6 获取数据源
定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
public static DynamicDataSource build() {
return new DynamicDataSource();
}
@Override
protected Object determineCurrentLookupKey() {
log.info("获取数据源:"+DynamicDataSourceContextHolder.getDataSourceType());
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
7.配置数据源
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.test")
public javax.sql.DataSource testDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.user")
public javax.sql.DataSource userDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dynamicDataSource(javax.sql.DataSource testDataSource, javax.sql.DataSource userDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
DynamicDataSource dynamicDataSource = DynamicDataSource.build();
targetDataSources.put(DataSourceType.TEST.name(), testDataSource);
targetDataSources.put(DataSourceType.USER.name(), userDataSource);
dynamicDataSource.setDefaultTargetDataSource(testDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.afterPropertiesSet();
return dynamicDataSource;
}
}
8 多数据源切面配置
@Aspect
@Component
@Order(1)
@Slf4j
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.yfx.task.config.datasource.DataSource)"
+ "|| @within(com.yfx.task.config.datasource.DataSource)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method targetMethod = this.getTargetMethod(point);
DataSource dataSource = targetMethod.getAnnotation(DataSource.class);
if (dataSource != null) {
System.out.println("代理:"+dataSource.value().name());
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
}
finally {
DynamicDataSourceContextHolder.removeDataSourceType();
}
}
private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
return methodSignature.getMethod();
}
}
9 结果调试
需要注释掉以下注解
否则可能出现以下错误,不能正常切换数据源:
PS:本文还用于练习Mybatis一对一和一对多练习
DTO类结构
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Integer uid;
private String username;
private String password;
private String nickname;
private String gender;
private String photo;
private List<Role> roleList;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StuDTO {
private Integer sid;
private String sno;
private String sname;
private Integer age;
private Integer sex;
private Integer classes;
private Score score;
}
xml配置
<resultMap id="userDTO" type="com.yfx.task.pojo.dto.UserDTO">
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="nickname" column="nickname"/>
<result property="gender" column="gender"/>
<result property="photo" column="photo"/>
<collection property="roleList" ofType="com.yfx.task.pojo.entity.Role">
<id property="id" column="rid"/>
<result property="name" column="name"/>
</collection>
</resultMap>
<resultMap id="StuDTO" type="com.yfx.task.pojo.dto.StuDTO">
<id property="sid" column="sid"/>
<result property="sno" column="sno"/>
<result property="sname" column="sname"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="classes" column="classes"/>
<association property="score" javaType="com.yfx.task.pojo.entity.Score">
<id property="scid" column="scid"/>
<result property="sid" column="sid"/>
<result property="cid" column="cid"/>
<result property="score" column="score"/>
</association>
</resultMap>
<sql id="userDTO_columns">
user.uid, user.username, user.password, user.nickname, user.gender, user.photo, role.id rid, role.name
</sql>
<sql id="StuDTO_columns">
stu.*,score.score
</sql>
<select id="oneToMany" resultMap="userDTO">
select <include refid="userDTO_columns"/> from role
left join user_role on role.id = user_role.rid
left join user on user.uid = user_role.uid
limit 0 ,10
</select>
<select id="oneToOne" resultMap="StuDTO">
select <include refid="StuDTO_columns"/> from stu
left join score on stu.sid = score.sid
</select>
|