多数据源的应用场景比较多,前面已经分享了几种,今天我再通过拦截器的角度分享一次,这样也更贴近实战,更准确,今天主要验证拦截器,比如读写分离的场景,在拦截器接口判断此条接口是查询还是修改,如果是查询赋值读库的数据源,如果是修改就赋值写库的数据库,这样也能精准的处理读写分离的业务问题。废话少数,下面咱们开始:
1、pom文件引入相关jar:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--Druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、数据库配置,暂定两个固定数据库:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
master-db:
url: jdbc:mysql://127.0.0.1:3306/master-db?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
username: root
password: root
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
driver-class-name: com.mysql.cj.jdbc.Driver
slave-db:
url: jdbc:mysql://127.0.0.1:3306/slave-db?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
username: root
password: root
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
driver-class-name: com.mysql.cj.jdbc.Driver
3、数据库初始化配置类
package com.nandao.dynamic.datasource.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.nandao.dynamic.datasource.DynamicDataSource;
import com.nandao.dynamic.datasource.plugin.DynamicDataSourcePlugin;
import org.apache.ibatis.plugin.Interceptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/***
* @Author nandao
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master-db")
public DataSource dataSource1() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave-db")
public DataSource dataSource2() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean//或者在DynamicDataSourcePlugin类上添加@Component注解
public Interceptor dynamicDataSourcePlugin(){
return new DynamicDataSourcePlugin();
}
@Bean
public DataSourceTransactionManager transactionManager1(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
@Bean
public DataSourceTransactionManager transactionManager2(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
4、定义多数据源的实现类
package com.nandao.dynamic.datasource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/***
* @Author nandao
*/
@Component
@Primary // 将该Bean设置为主要注入Bean
public class DynamicDataSource extends AbstractRoutingDataSource {
// 当前使用的数据源标识
public static ThreadLocal<String> name = new ThreadLocal<>();
// 写
@Autowired
DataSource dataSource1;
// 读
@Autowired
DataSource dataSource2;
// 返回当前数据源标识
@Override
protected Object determineCurrentLookupKey() {
return name.get();
}
@Override
public void afterPropertiesSet() {
// 为targetDataSources初始化所有数据源
Map<Object, Object> targetDataSources=new HashMap<>();
targetDataSources.put("W",dataSource1);
targetDataSources.put("R",dataSource2);
super.setTargetDataSources(targetDataSources);
// 为defaultTargetDataSource 设置默认的数据源
super.setDefaultTargetDataSource(dataSource1);
super.afterPropertiesSet();
}
}
5、自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/***
* @Author nandao
*/
@Target({ElementType.METHOD,ElementType.TYPE})
// 方式
@Retention(RetentionPolicy.RUNTIME)
public @interface WR {
String value() default "W";
}
6、切面实现拦截数据源
package com.nandao.dynamic.datasource.aspect;
import com.nandao.dynamic.datasource.DynamicDataSource;
import com.nandao.dynamic.datasource.annotation.WR;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/***
* @Author nandao
*/
@Component
@Aspect
public class DynamicDataSourceAspect implements Ordered {
// 前置
@Before("within(com.nandao.dynamic.datasource.service.impl.*) && @annotation(wr)")
public void before(JoinPoint point, WR wr){
String name = wr.value();
DynamicDataSource.name.set(name);
System.out.println(name);
}
@Override
public int getOrder() {
return -1;
}
}
7、mybatis的拦截器处理数据源
package com.nandao.dynamic.datasource.plugin;
import com.nandao.dynamic.datasource.DynamicDataSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.util.Properties;
/***
* @Author nandao
*/
@Intercepts(
{@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class})})
@Component // 或者在配置类里添加一个@Bean对象
public class DynamicDataSourcePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拿到当前方法(update、query)所有参数
Object[] objects = invocation.getArgs();
// MappedStatement 封装CRUD所有的元素和SQL
MappedStatement ms = (MappedStatement) objects[0];
// 读方法
if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
DynamicDataSource.name.set("R");
} else {
// 写方法
DynamicDataSource.name.set("W");
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
}
8、业务伪代码参考
控制层:
package com.nandao.dynamic.datasource.controller;
import com.nandao.dynamic.datasource.entity.Frend;
import com.nandao.dynamic.datasource.service.FrendService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/***
* @Author nandao
*/
@RestController
@RequestMapping("frend")
@Slf4j
public class FrendController {
@Autowired
private FrendService frendService;
@GetMapping(value = "select")
public List<Frend> select(){
return frendService.list();
}
@GetMapping(value = "insert")
public void in(){
Frend frend = new Frend();
frend.setName("南道");
frendService.save(frend);
}
}
业务层:
package com.nandao.dynamic.datasource.service.impl;
import com.nandao.dynamic.datasource.annotation.WR;
import com.nandao.dynamic.datasource.mapper.FrendMapper;
import com.nandao.dynamic.datasource.entity.Frend;
import com.nandao.dynamic.datasource.service.FrendService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/***
* @Author nandao
*/
@Service
public class FrendImplService implements FrendService {
@Autowired
FrendMapper frendMapper;
@Override
@WR("R") //读库
public List<Frend> list() {
return frendMapper.list();
}
@Override
@WR("W") // 写库
public void save(Frend frend) {
frendMapper.save(frend);
}
}
mapper层:
package com.nandao.dynamic.datasource.mapper;
import com.nandao.dynamic.datasource.entity.Frend;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Auther nandao
*/
public interface FrendMapper {
@Select("SELECT * FROM Frend")
List<Frend> list();
@Insert("INSERT INTO frend(`name`) VALUES (#{name})")
void save(Frend frend);
}
postman测试完验证多数据源无误!在实际使用过程,mybtais 拦截器和AOP切面方式二选一。
到此,今天的多数据源分享完毕,多数据源分享暂时告一段落,下期我们分析mybatis的源码,敬请期待!