Spring Boot教程十四:基于自定义注解的AOP数据源自动切换
程序员文章站
2022-05-01 08:08:50
...
上一篇文章讲到了多数据源的配置和手动切换,手动切换费时费力,下面我们改进一下,改成基于注解的AOP数据源自动切换。
基础知识不在赘述,直接上代码:
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/*
*管理所有的数据源id;
*主要是为了判断数据源是否存在;
*/
public static List<String> dataSourceIds = new ArrayList<String>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean containsDataSourceType(String DataSourceId) {
return dataSourceIds.contains(DataSourceId);
}
}
/**
* @author Shuyu.Wang
* @package:com.ganinfo.datasource
* @className:
* @description:动态数据源(需要继承AbstractRoutingDataSource)
* @date 2018-08-01 15:47
**/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
System.err.println("DataSourceContextHolder.getDataSourceType() "+DataSourceContextHolder.getDataSourceType());
return DataSourceContextHolder.getDataSourceType();
}
}
@Aspect
@Order(-10)//保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.ganinfo.datasource.TargetDataSource)")
public void annotationPointcut(){
System.out.println("======我是一个切入点");
};
/*
* @Before("@annotation(ds)")
*的意思是:
* @Before:在方法执行之前进行执行:
* @annotation(targetDataSource):
*会拦截注解targetDataSource的方法,否则不拦截;
*/
@Before("annotationPointcut()")
public void changeDataSource(JoinPoint joinPoint) throws Throwable {
MethodSignature signature=(MethodSignature) joinPoint.getSignature();
Method method=signature.getMethod();
TargetDataSource targetDataSource=method.getAnnotation(TargetDataSource.class);
System.out.println("注解的拦截方法名注解内容前:"+targetDataSource.value());
//获取当前的指定的数据源;
String dsId = targetDataSource.value();
//如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
if (!DataSourceContextHolder.containsDataSourceType(dsId)) {
System.err.println("数据源[{}]不存在,使用默认数据源 > {}" + targetDataSource.value() + joinPoint.getSignature());
} else {
System.out.println("UseDataSource : {} > {}" + targetDataSource.value() +"====="+ joinPoint.getSignature());
//找到的话,那么设置到动态数据源上下文中。
DataSourceContextHolder.setDataSourceType(targetDataSource.value());
System.out.println("数据源切换为:"+ DataSourceContextHolder.getDataSourceType());
}
}
@After("annotationPointcut()")
public void restoreDataSource(JoinPoint joinPoint) {
MethodSignature signature=(MethodSignature) joinPoint.getSignature();
Method method=signature.getMethod();
TargetDataSource targetDataSource=method.getAnnotation(TargetDataSource.class);
System.out.println("注解的拦截方法名注解内容前:"+targetDataSource.value());
//方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
DataSourceContextHolder.clearDataSourceType();
System.out.println("lear后:"+ DataSourceContextHolder.getDataSourceType());
}
}
@Configuration // 该注解类似于spring配置文件
public class MyBatisConfig {
@Autowired
private Environment env;
private String MYBATIS_CONFIG = "config/mybatis.xml";
/**
* 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
*/
@Bean("masterDbDataSource")
public DataSource masterDbDataSource() throws Exception {
Properties props = new Properties();
props.put("driverClassName", env.getProperty("spring.datasource.driverClassName"));
props.put("url", env.getProperty("spring.datasource.url"));
props.put("username", env.getProperty("spring.datasource.username"));
props.put("password", env.getProperty("spring.datasource.password"));
return DruidDataSourceFactory.createDataSource(props);
}
@Bean("slaveDb2DataSource")
public DataSource slaveDb2DataSource() throws Exception {
Properties props = new Properties();
props.put("driverClassName", env.getProperty("custom.datasource.ds1.driverClassName"));
props.put("url", env.getProperty("custom.datasource.ds1.url"));
props.put("username", env.getProperty("custom.datasource.ds1.username"));
props.put("password", env.getProperty("custom.datasource.ds1.password"));
return DruidDataSourceFactory.createDataSource(props);
}
/**
* @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
*/
@Bean
@Primary
public DynamicDataSource dataSource(@Qualifier("masterDbDataSource") DataSource masterDbDataSource, @Qualifier("slaveDb2DataSource") DataSource slaveDb2DataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("masterDbDataSource", masterDbDataSource);
targetDataSources.put("slaveDb2DataSource", slaveDb2DataSource);
DataSourceContextHolder.dataSourceIds.add("masterDbDataSource");
DataSourceContextHolder.dataSourceIds.add("slaveDb2DataSource");
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
dataSource.setDefaultTargetDataSource(slaveDb2DataSource);// 默认的datasource设置为masterDbDataSource
return dataSource;
}
/**
* 根据数据源创建SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDbDataSource") DataSource masterDbDataSource, @Qualifier("slaveDb2DataSource") DataSource slaveDb2DataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
/* sqlSessionFactoryBean.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));//*/
/** 设置mybatis configuration 扫描路径 */
System.out.println(env.getProperty("mybatis.configLocation"));
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG));
// 指定数据源(这个必须有,否则报错)
sqlSessionFactoryBean.setDataSource(this.dataSource(masterDbDataSource, slaveDb2DataSource));
/** 添加mapper 扫描路径 */
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
return sqlSessionFactoryBean.getObject();
}
/**
* 配置事务管理器
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
}
/**
* @author Shuyu.Wang
* @package:com.ganinfo.datasource
* @className:
* @description:在方法上使用,用于指定使用哪个数据源
* @date 2018-08-01 20:56
**/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
package com.ganinfo.datasource.mapper;
import com.ganinfo.datasource.TargetDataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* @author Shuyu.Wang
* @package:com.ganinfo.datasource.mapper
* @className:
* @description:
* @date 2018-08-01 16:03
**/
@Mapper
public interface Test {
@TargetDataSource(value ="masterDbDataSource")
@Select("SELECT name FROM t_user limit 1")
List<Map<String,Object>> getList();
@TargetDataSource(value ="slaveDb2DataSource")
@Select("SELECT * FROM t_base_place limit 1")
List<Map<String,Object>> getList2();
}
@Service
public class TestService {
@Autowired
private Test test;
@TargetDataSource(value = "masterDbDataSource")
public List<Map<String, Object>> getList() {
return test.getList();
}
// @TargetDataSource(value = "ds1")
@TargetDataSource(value ="slaveDb2DataSource")
public List<Map<String, Object>> getList2() {
return test.getList2();
}
}
#主数据源,默认的
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://172.16.1.28:3306/closedauth?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
spring.datasource.username = cweserver
spring.datasource.password = cweserveryzhh
#更多数据源
custom.datasource.names=ds1
custom.datasource.ds1.driverClassName = com.mysql.jdbc.Driver
custom.datasource.ds1.url = jdbc:mysql://172.16.1.28:3306/closeddata?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
custom.datasource.ds1.username = cweserver
custom.datasource.ds1.password = cweserveryzhh
这里切记注解要在service方法中加,不能再mapper中添加,不然无法被aop拦截;
测试方法如下:
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestTest {
@Autowired
private com.ganinfo.datasource.mapper.Test test;
@Autowired
private TestService testService;
@Test
public void getList2() throws Exception {
List<Map<String, Object>> list = testService.getList2();
System.out.println(GsonUtil.GsonString(list));
// log.info("");
}
@org.junit.Test
public void getList() throws Exception {
List<Map<String, Object>> list = testService.getList();
System.out.println(GsonUtil.GsonString(list));
}
}
上一篇: jar包部署到服务器
下一篇: div自适应高度