一、application.yml配置文件
server:
port: 8080
servlet:
context-path: /cm
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
username: username
password: password
url: jdbc:mysql://xxxxx:xxxx/test?autoReconnect=true&useCompression=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT
initial-size: 1
min-idle: 1
max-active: 50
max-wait: 60000
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
username: xxxx
password: xxxxxx
url: jdbc:mysql://xxxx:xxxx/test?autoReconnect=true&useCompression=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT
initial-size: 1
min-idle: 1
max-active: 50
max-wait: 60000
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
二、编写配置类DataSourceConfig,配置多个数据源
package com.cm.server.busi.config;
import com.cm.server.busi.enums.DataSourceTypeEnum;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* DataSourceConfig
*
* @author admin
* @date 2021-10-27 17:19
*/
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.type}")
public Class<? extends DataSource> dataSourceType;
@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
*
* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
* @param masterDataSource
* @param slaveDataSource
* @return
*/
@Bean
public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource){
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceTypeEnum.master, masterDataSource);
targetDataSources.put(DataSourceTypeEnum.slave, slaveDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
三、mybatis配置
package com.cm.server.busi.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
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 org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* MybatisConfig
*
* @author admin
* @date 2021-10-27 17:26
*/
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
@AutoConfigureAfter({ DataSourceConfig.class })
public class MybatisConfig {
@Resource(name = "routingDataSource")
private DataSource routingDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(routingDataSource);
// 下面的不可少,这个有了,就不用配置文件里的
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(routingDataSource);
}
}
四、编写AbstractRoutingDataSource的实现类,并实现determineCurrentLookupKey方法,达到动态切换数据源的效果
package com.cm.server.busi.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* DynamicDataSource
*
* @author admin
* @date 2021-10-27 17:18
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 利用DatabaseContextHolder 获取当前线程的 datasourcetype
* 动态数据源(需要继承AbstractRoutingDataSource)
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getDataBaseType();
}
}
五、通过ThreadLocal将数据源设置到每个线程上下文中
package com.cm.server.busi.config;
import com.cm.server.busi.enums.DataSourceTypeEnum;
/**
* DatabaseContextHolder
*
* @author admin
* @date 2021-10-27 17:16
*/
public class DatabaseContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
/**
* 设置数据源
*
* @param dataBaseType
*/
public static void setDataBaseType(DataSourceTypeEnum dataBaseType) {
if (dataBaseType == null) {
throw new NullPointerException();
}
contextHolder.set(dataBaseType);
}
/**
* 取得当前数据源
*
* @return
*/
public static DataSourceTypeEnum getDataBaseType() {
return contextHolder.get() == null ? DataSourceTypeEnum.master : (DataSourceTypeEnum) contextHolder.get();
}
/**
* 清除上下文数据
*/
public static void clearDataBaseType() {
contextHolder.remove();
}
}
六、通过自动义注解,实现AOP拦截,达到切换数据源的目的。@EnableAspectJAutoProxy(exposeProxy=true, proxyTargetClass=true)
在SpringBoot
中,默认使用的就是CGLIB
方式来创建代理。在它的配置文件中,spring.aop.proxy-target-class
默认是true ,即proxyTargetClass=true,如果改为false则使用JDK
动态代理机制(代理所有实现了的接口)。
TIPS:spring boot 2.0 以后默认使用Cglib 动态代理,之前版本有接口就用 JDK动态代理,没有接口就用 Cglib 动态代理,spring默认使用JDK动态代理,没有接口就报错。
1、Jdk动态代理:利用拦截器(必须实现InvocationHandler接口)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
2、 Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来进行代理
package com.cm.server.busi.annotation;
import com.cm.server.busi.config.DatabaseContextHolder;
import com.cm.server.busi.enums.DataSourceTypeEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* DataSourceAop
*
* @author admin
* @date 2021-10-27 18:05
*/
@Aspect
@Component
// @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
public class DataSourceAop {
@Pointcut("@annotation(com.cm.server.busi.annotation.ReadDataSource) && execution(* com.cm.server.busi.service.impl..*.*(..))")
public void readPointcut(){}
@Pointcut("@annotation(com.cm.server.busi.annotation.MasterDataSource) && execution(* com.cm.server.busi.service.impl..*.*(..))")
public void writePointcut(){}
@Before("readPointcut()")
public void readBefore(JoinPoint joinPoint) {
DatabaseContextHolder.setDataBaseType(DataSourceTypeEnum.slave);
System.out.println("USE DATASOURCE SLAVE");
}
@Before("writePointcut()")
public void writeBefore(JoinPoint joinPoint) {
DatabaseContextHolder.setDataBaseType(DataSourceTypeEnum.master);
System.out.println("USE DATASOURCE MASTER");
}
@After("readPointcut() || writePointcut()")
public void after() {
DatabaseContextHolder.clearDataBaseType();
}
}
访问只读库注解
package com.cm.server.busi.annotation;
import java.lang.annotation.*;
/**
*
* @author
* @date 2021/10/27
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ReadDataSource {
}
访问读写库注解
package com.cm.server.busi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* MasterDataSource
*
* @author admin
* @date 2021-10-27 18:03
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MasterDataSource {
}
附上dataType枚举类
package com.cm.server.busi.enums;
/**
*
* @author
* @date 2021/10/27
*/
public enum DataSourceTypeEnum {
master,slave;
}
7、最后在业务层代码加上相应的注解即可
package com.cm.server.busi.service.impl;
import com.cm.server.busi.annotation.MasterDataSource;
import com.cm.server.busi.annotation.ReadDataSource;
import com.cm.server.busi.mapper.CmServerBusiMapper;
import com.cm.server.busi.model.DataVo;
import com.cm.server.busi.service.ICmServerBusiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* CmServerBusiServiceImpl
*
* @author admin
* @date 2021-10-27 10:48
*/
@Service
public class CmServerBusiServiceImpl implements ICmServerBusiService {
@Autowired
private CmServerBusiMapper cmServerBusiMapper;
@Override
@ReadDataSource
public Map<String, Object> selectDataInfo() {
return cmServerBusiMapper.selectDataInfo();
}
@Override
@MasterDataSource
public void updateDataInfo(DataVo dataVo) {
cmServerBusiMapper.updateDataInfo(dataVo);
}
}
八、测试