1, 数据库类型
package com.mine.cloakroom.config;
/**
* 采用枚举的方法列出所有的数据源key(常用数据库名称来命名)
* 数据源个数和数据库个数保持一致
*/
public enum DatabaseType {
firstdb,seconddb
}
2,动态数据源ContextHolder(需要继承AbstractRoutingDataSource)
package com.mine.cloakroom.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
// 动态数据源(需要继承AbstractRoutingDataSource)
public class DynamicDataSourceContextHolder extends AbstractRoutingDataSource {
//定义一个线程安全的DatabaseType容器
private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
public static DatabaseType getDatabaseType() {
return contextHolder.get();
}
public static void setDatabaseType(DatabaseType type) {
contextHolder.set(type);
}
//获取当前线程的DatabaseType
@Override
protected Object determineCurrentLookupKey() {
return getDatabaseType();
}
public static void removeDataSource() {
contextHolder.remove();
}
}
3,动态数据源配置
package com.mine.cloakroom.config;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 动态数据源配置
*/
@Configuration
@MapperScan(basePackages = "com.mine.cloakroom.dao,com.mine.cloakroom.dao3", sqlSessionFactoryRef = "dynamicSqlSessionFactory")// sqlSessionFactoryRef 一定要加上, 指定用哪个sqlSessionFactory扫描dao
public class DynamicDataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return new HikariDataSource();
}
@Bean(name = "secondDataSource")
@ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return new HikariDataSource();
}
/**
* 注入数据源
*/
@Bean(name = "dynamicDataSource")
public DynamicDataSourceContextHolder dataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("secondDataSource") DataSource secondDataSource) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put(DatabaseType.firstdb, primaryDataSource);
targetDataSources.put(DatabaseType.seconddb, secondDataSource);
DynamicDataSourceContextHolder dataSource = new DynamicDataSourceContextHolder();
dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
dataSource.setDefaultTargetDataSource(primaryDataSource);// 默认的datasource设置为myTestDbDataSource
return dataSource;
}
/**
* 根据数据源创建SqlSessionFactory
*/
@Bean(name = "dynamicSqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper3/*/*Mapper.xml"));
return bean.getObject();
}
/**
* 配置事务管理器
*/
@Bean
public DataSourceTransactionManager testTransactionManager(@Qualifier("dynamicDataSource") DynamicDataSourceContextHolder dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
}
4,定义一个注解(使用dao方法上)
package com.mine.cloakroom.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
DatabaseType value();
}
5,对注解切面到方法
package com.mine.cloakroom.config;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
@Aspect
@Slf4j
public class DataSourceAspect {
//切换放在mapper接口的方法上,所以这里要配置AOP切面的切入点
// @Pointcut("execution( * com.mine.cloakroom.service.*.*(..))")
@Pointcut("@annotation(com.mine.cloakroom.config.TargetDataSource)")
public void dataSourcePointCut() {
}
@Before("dataSourcePointCut()")
public void before(JoinPoint joinPoint) {
//获得切面当中方法签名
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
//获得签名方法
Method method = methodSignature.getMethod();
//获得在方法上面的注解
TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
try {
if(DatabaseType.firstdb.equals(targetDataSource.value()) || DatabaseType.seconddb.equals(targetDataSource.value())){
DatabaseType type = targetDataSource.value();
DynamicDataSourceContextHolder.setDatabaseType(type);
log.debug("current thread " + Thread.currentThread().getName() + " add " + type + " to ThreadLocal");
}else{
log.debug("switch datasource fail,use default");
}
} catch (Exception e) {
log.error("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
}
}
//执行完切面后,将线程共享中的数据源名称清空
@After("dataSourcePointCut()")
public void after(JoinPoint joinPoint){
DynamicDataSourceContextHolder.removeDataSource();
}
}
6, dao层示例
package com.mine.cloakroom.dao3.user;
import com.mine.cloakroom.bean.entity.user.UserDO;
import com.mine.cloakroom.config.DatabaseType;
import com.mine.cloakroom.config.TargetDataSource;
import java.util.Map;
public interface UserDao3 {
int deleteByPrimaryKey(Integer id);
int insert(UserDO record);
int insertSelective(UserDO record);
@TargetDataSource(DatabaseType.seconddb)
UserDO selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(UserDO record);
int updateByPrimaryKey(UserDO record);
Map<String, String> selectUserMap();
}
7,application.yml 配置
spring:
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/cloakroom_business?characterEncoding=utf8&serverTimezone=GMT%2b8&connectTimeout=5000&socketTimeout=30000&allowMultiQueries=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
poolName: testOneDB
maximum-pool-size: 15
max-lifetime: 180000
idle-timeout: 50000
connection-timeout: 10000
minimum-idle: 5
connection-test-query: SELECT 1
second:
jdbc-url: jdbc:mysql://198.101.566.66:3306/cloakroom_business?characterEncoding=utf8&serverTimezone=GMT%2b8&connectTimeout=5000&socketTimeout=30000&allowMultiQueries=true
username: root
password: 123456789
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
poolName: testSecondDB
maximum-pool-size: 15
max-lifetime: 180000
idle-timeout: 50000
connection-timeout: 10000
minimum-idle: 5
connection-test-query: SELECT 1
8,测试类
public class UserServiceTest {
@Autowired
private UserDao3 userDao3;
// @Autowired
// private UserDao2 userDao2;
@Test
public void mapTest() {
// DynamicDataSource.setDatabaseType(DatabaseType.seconddb);// 可以直接在代码中设计数据源,也可通过注解实现
UserDO userDO2 = userDao3.selectByPrimaryKey(2);
System.out.println(userDO2);
//设置数据源
// DynamicDataSource.setDatabaseType(DatabaseType.firstdb);
UserDO userDO = userDao3.selectByPrimaryKey(2);
System.out.println(userDO.toString());
}
}