多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库,怎么搞,直接来个最简单的实现。
1. pom.xml 所需依赖
<dependencies>
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
2. application.yml 配置文件
spring:
datasource:
master:
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
username: root
password: 123
slave:
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
username: root
password: 123
3. 主要几个java
3.1 枚举类
public enum DBTypeEnum {
MASTER, SLAVE;
}
3.2 线程切换数据源
@Slf4j
public class DynamicHelper {
private static final ThreadLocal<DBTypeEnum> dynamicDadaLocal = new ThreadLocal<>();
/**
* 主库
*/
public static void master() {
set(DBTypeEnum.MASTER);
}
public static void set(DBTypeEnum dbType) {
log.info("--------------------> 切换数据源:{}", dbType.name());
dynamicDadaLocal.set(dbType);
}
public static DBTypeEnum get() {
return dynamicDadaLocal.get();
}
public static void remove() {
dynamicDadaLocal.remove();
}
}
3.3 覆写切换数据源类
public class RoutingDataSource extends AbstractRoutingDataSource {
/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
*/
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicHelper.get();
}
}
3.4 mybatis 配置
@ComponentScan(basePackages = {"com.xx.xx.entity"})
@MapperScan({"com.xx.xx.mapper"})
@Configuration
public class MybatisConfig{
@Bean("master")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource master() {
return DataSourceBuilder.create().build();
}
@Bean("slave")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slave() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DBTypeEnum.MASTER, master());
targetDataSources.put(DBTypeEnum.SLAVE, slave());
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(master());
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
sessionFactory.setDataSource(routingDataSource());
// sessionFactory.setTypeAliasesPackage("com.xx.**.entity");
// PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// sessionFactory.setMapperLocations(resolver.getResources("classpath*:**/mapper/*.xml"));
return sessionFactory;
}
@Bean
public PlatformTransactionManager transactionManager() {
// 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
return new DataSourceTransactionManager(routingDataSource());
}
}
4 单元测试(打完收工)
@RunWith(SpringRunner.class)
@SpringBootTest(classes = xxApplication.class)
@Slf4j
public class TestDynamicData {
@Autowired
private MasterMapper masterMapper;
@Autowired
private SlaverMapper slaveMapper;
@Test
public void testDynamic() {
//
DynamicHelper.master();
final Integer masterCount = masterMapper.countxx();
DynamicHelper.set(DBTypeEnum.SLAVE);
final Integer slaveCount = slaveMapper.countXX();
log.info("测试动态数据源。masterCount :{},slaveCount :{}", masterCount , slaveCount );
}
}
5 aop 增强使用
5.1 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface DBType {
DBTypeEnum type();
}
5.2 aop拦截
@Aspect
@Component
public class DataSourceAop {
/**
* 通过切面实现数据源的切换
*
* @param jp
*/
@Around("@annotation(com.xx.xx.DBType)")
public Object datasourceSwitch(ProceedingJoinPoint jp) throws Throwable {
Class<?> clazz = jp.getTarget().getClass();
Method method = ((MethodSignature) jp.getSignature()).getMethod();
if (method.isAnnotationPresent(DBType.class)) {
//使用在方法上
DynamicHelper.set(method.getAnnotation(DBType.class).type());
} else if (clazz.isAnnotationPresent(DBType.class)) {
//使用来类上
DynamicHelper.set(clazz.getAnnotation(DBType.class).type());
} else {
//默认数据源
DynamicHelper.master();
}
final Object proceed = jp.proceed();
//清除
DynamicHelper.remove();
return proceed;
}
}
网友评论