spring-boot动态数据源
配置步骤
- 创建读和写的数据源
- 将读和写的数据源注册到RoutingDataSource
- 使用AOP技术拦截业务逻辑层方法,判断方法的前缀是否需要做读或者写
功能实现
首先创建一个spring boot项目
项目导包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
<artifactId>spring-boot-mycat</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</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>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.23</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
由于之前已经搭建好读写分离的mycat,所以这里在项目中配置:
spring:
datasource:
## 可读数据源
select:
jdbc-url: jdbc:mysql://localhost:8066/mycat_testdb
driver-class-name: com.mysql.jdbc.Driver
username: user
password: *******
## 可写数据源
update:
jdbc-url: jdbc:mysql://localhost:8066/mycat_testdb
driver-class-name: com.mysql.jdbc.Driver
username: root
password: ******
type: com.alibaba.druid.pool.DruidDataSource
之后在项目中创建需要的实体类、dao层和service层方法
/**
* UserEntity
*
* @author liurui
* @Description:
* @date 2019/11/24
*/
@Data
public class UserEntity {
private String userName;
}
import com.felton.db.UserEntity;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT * FROM user_info ")
public List<UserEntity> findUser();
@Select("insert into user_info values (#{userName}); ")
public List<UserEntity> insertUser(@Param("userName") String userName);
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<UserEntity> findUser() {
return userMapper.findUser();
}
public List<UserEntity> insertUser(String userName) {
return userMapper.insertUser(userName);
}
}
配置多数据源
/**
* DataSourceConfig
*
* @author liurui
* @Description:
* @date 2019/11/24
*/
@Configuration
public class DataSourceConfig {
@Bean(name = "selectDataSource")
@ConfigurationProperties(prefix = "spring.datasource.select")
public DataSource dataSource1(){
return DataSourceBuilder.create().build();
}
@Bean(name = "updateDataSource")
@ConfigurationProperties(prefix = "spring.datasource.update")
public DataSource dataSource2(){
return DataSourceBuilder.create().build();
}
}
创建一个改变数据源的类,采用ThreadLocal 保存本地多数据源
@Component
@Lazy(false)
public class DataSourceContextHolder {
// 采用ThreadLocal 保存本地多数据源
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源类型
public static void setDbType(String dbType) {
contextHolder.set(dbType);
}
public static String getDbType() {
return contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
创建动态数据源类,继承AbstractRoutingDataSource
,重写改变数据源的方法determineCurrentLookupKey()
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
@Autowired
@Qualifier("selectDataSource")
private DataSource selectDataSource;
@Autowired
@Qualifier("updateDataSource")
private DataSource updateDataSource;
/**
* 这个是主要的方法,返回的是生效的数据源名称
*/
@Override
protected Object determineCurrentLookupKey() {
System.out.println("DataSourceContextHolder:::" + DataSourceContextHolder.getDbType());
return DataSourceContextHolder.getDbType();
}
/**
* 配置数据源信息
*/
@Override
public void afterPropertiesSet() {
Map<Object, Object> map = new HashMap<>();
map.put("selectDataSource", selectDataSource);
map.put("updateDataSource", updateDataSource);
setTargetDataSources(map);
setDefaultTargetDataSource(updateDataSource);
super.afterPropertiesSet();
}
}
最后的是使用AOP来区分判断读写
@Aspect
@Component
@Lazy(false)
@Order(0) // Order设定AOP执行顺序 使之在数据库事务上先执行
public class SwitchDataSourceAOP {
// 这里切到你的方法目录
@Before("execution(* com.mayikt.service.*.*(..))")
public void process(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
if (methodName.startsWith("get") || methodName.startsWith("count") || methodName.startsWith("find")
|| methodName.startsWith("list") || methodName.startsWith("select") || methodName.startsWith("check")) {
DataSourceContextHolder.setDbType("selectDataSource");
} else {
// 切换dataSource
DataSourceContextHolder.setDbType("updateDataSource");
}
}
}