最近有个数据归集的需求用到了多数据源,在业务库保存后同时向归集库插入或数据。之前好像还没做过这块的东西,简单记录下防止下次又忘记了~
踩过的几个坑都是某些知识点不熟悉导致的,而且都是框架配置相关的..
先上代码,再扯淡
两个库都是mysql,不同数据库应该就是配置不一样,使用的druid数据库连接池
#jdbc configureconnection.url=${db.url}
connection.username=${db.username}
connection.password=${db.password}
connection.driver=${connection.driver_class}#删掉此配置 退回单数据源
connection.url2=${tradding.db.url2}
connection.username2=${tradding.db.username2}
connection.password2=${tradding.db.password2}
connection.driver2=${connection.driver_class2}
package cn.xxx.datasource;import java.lang.annotation.*;/*** @author zj* @creatTime 2022-11-22* @description*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceAnnotation {String value();String primary = "primary";String secend= "secend";}
package cn.xxx.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** @author zj* @creatTime 2022-11-18* @description 配置多个数据源*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 数据源标识,保存在线程变量中,避免多线程操作数据源时互相干扰*/private static final ThreadLocal key = new ThreadLocal();@Overrideprotected Object determineCurrentLookupKey() {return key.get();}/*** 设置数据源** @param dataSource 数据源名称*/public static void setDataSource(String dataSource) {key.set(dataSource);}/*** 获取数据源** @return*/public static String getDatasource() {return key.get();}/*** 清除数据源*/public static void clearDataSource() {key.remove();}}
package cn.xxx.datasource;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** @author zj* @creatTime 2022-11-22* @description*/
@Component
public class DynamicDataSourceAspect implements MethodBeforeAdvice, AfterReturningAdvice {Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);@Overridepublic void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {//这里做一个判断,有使用DataSourceAnnotation注解时才关闭数据源,有一个主要的数据源,就没有必要每次都去关闭if (method.isAnnotationPresent(DataSourceAnnotation.class)) {DynamicDataSource.clearDataSource();log.debug("数据源已关闭");}}/*** 拦截目标方法,获取由@DataSourceAnnotation指定的数据源标识,设置到线程存储中以便切换数据源*/@Overridepublic void before(Method method, Object[] objects, Object o) throws Throwable {if (method.isAnnotationPresent(DataSourceAnnotation.class)) {DataSourceAnnotation dataSourceAnnotation = method.getAnnotation(DataSourceAnnotation.class);DynamicDataSource.setDataSource(dataSourceAnnotation.value());log.debug("数据源切换为:" + DynamicDataSource.getDatasource());}}
}
这里需要注意的是order值越小,优先级越高,所以切换数据源order的值要比事务切面的值小,否则会出现数据源切换失败!
在需要切换为非默认数据的方法上加@DataSourceAnnotation(DataSourceAnnotation.secend)就可以完成数据源的切换了。
controller层:
/*** @author zj* @creatTime 2022-11-18* @description*/
@Controller
public class TestController {@Autowiredprivate TPProjectMapper projectMapper;@Autowiredprivate TestSourceService testSourceService;@RequestMapping("/ocx/saveOrUpdateTest")@ResponseBodypublic Result saveOrUpdateTest(){Result result = new Result();//先测试插入 监督库TPProject project = new TPProject();project.setProjectId(111221212L);project.setProjectName("test测试多数据源——监督库");ContextFacade.initEntity(project);projectMapper.insert(project);//再测试插入 交易库testSourceService.saveOrUpdateTest();//再测试修改 监督库TPProject project1 = projectMapper.selectByPrimaryKey(111221212L);project1.setProjectId(111221212L);project1.setProjectName("test测试多数据源——监督库__二次修改结果");projectMapper.updateByPrimaryKey(project1);return result;}}
service层:
package cn.xxx.test.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author zj* @creatTime 2022-11-18* @description*/
@Service
public class TestSourceService {@Autowiredprivate TPProjectMapper tpProjectMapper;@DataSourceAnnotation(DataSourceAnnotation.secend)public Result saveOrUpdateTest(){Result result = new Result();TPProject project = new TPProject();project.setProjectId(111221213L);project.setProjectName("test测试多数据源——交易库");ContextFacade.initEntity(project);tpProjectMapper.insert(project);return result;}
}
主库:
副库:
这里为了方便用了同一个表,只是不同数据库,所以mapper和sql也一样的,实际根据需求来就行。
1、SpringAOP切面类不运行的问题 ,注意配置写在spring-config.xml而不是spring-mvc.xml中。
2、aop:pointcut的配置说明
其中expression="execution(* com.aop.service..*(..))"的配置规则如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 参数名(参数模式)稍微复杂一些:
execution(方法的操作权限 返回值类型模式 方法所在的包 方法名 (参数名) 异常)
() 匹配不带参数的方法,
(..) 匹配任何数量(零个或多个)参数。
(*) 模式匹配采用任何类型的一个参数的方法
(*,String) 匹配一个带有两个参数的方法。第一个可以是任何类型,而第二个必须是String
返回值,方法名,参数名,必须有,其他可选
执行任何公共方法:
execution(public * *(..))
执行名称以以下开头的任何方法set:
execution(* set*(..))
执行AccountService接口定义的任何方法:
execution(* com.xyz.service.AccountService.*(..))
执行service包中定义的任何方法:
execution(* com.xyz.service.*.*(..))
执行服务包或其子包中定义的任何方法:
execution(* com.xyz.service..*.*(..))