一、动态注入多数据源
1、配置多数据源配置文件(application-db.properties)
######多数据源配置文件####################
###第一个####
spring.datasource.first.name=first
spring.datasource.first.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
spring.datasource.first.username=sepcore
spring.datasource.first.password=sepcore
spring.datasource.first.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.first.mapperLocations=classpath:mappers/*Mapper.xml
####第二个####
spring.datasource.second.name=second
spring.datasource.second.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
spring.datasource.second.username=root
spring.datasource.second.password=123456
spring.datasource.second.driverClassName=com.mysql.jdbc.Driver
spring.datasource.second.mapperLocations=classpath:mappers/*Mapper.xml
#####mapper接口所在包#######
scanner.mapperInterfacePackage=com.example.demo.mappers
2、读取配置文件类(DataSourceConfig)
public classDataSourceConfig {/*** 存储dataSource、SqlSessionTemplate、DataSourceTransactionManager*/
private Map>mapMap;/*** 获取mybatis扫描的指定接口包(所有数据源的接口放在同一的父包下面)*/
privateString mapperInterfacePackage;publicDataSourceConfig(){
mapMap= new HashMap<>();
InputStream in= DataSourceConfig.class.getClassLoader().
getResourceAsStream("application-db.properties");
Properties properties= newProperties();try{
properties.load(in);
}catch(IOException e) {
e.printStackTrace();
}
Set set =properties.stringPropertyNames();for(String s : set) {//判断是否是mapper接口指定包路径
if (s.contains("mapperInterfacePackage")){
mapperInterfacePackage=properties.get(s).toString();continue;
}
String key= s.substring(0, s.lastIndexOf("."));if(mapMap.containsKey(key)){
Map map =mapMap.get(key);
map.put(s,properties.get(s));
}else{
Mapmap = new HashMap<>();
map.put(s,properties.get(s));
mapMap.put(key,map);
}
}
}publicString getMapperInterfacePackage() {returnmapperInterfacePackage;
}/*** 获取SqlSessionTemplate
*@return*@throwsException*/
public MapgetSqlSessionTemplateAndDataSource() throwsException {
Set>> entries = this.mapMap.entrySet();
Mapresult = new HashMap<>(entries.size());for (Map.Entry>entry : entries) {
String key=entry.getKey();
Map map =entry.getValue();
DataSource dataSource= DataSourceBuilder.create().url(map.get(key+".url").toString()).
username(map.get(key+".username").toString()).password(map.get(key+".password").toString()).
driverClassName(map.get(key+".driverClassName").toString()).
build();//为每个数据源设置事务
DataSourceTransactionManager dataSourceTransactionManager = newDataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
SqlSessionFactoryBean sqlSessionFactoryBean= newSqlSessionFactoryBean();//设置dataSource数据源
sqlSessionFactoryBean.setDataSource(dataSource);//设置*mapper.xml路径
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(map.get(key+".mapperLocations").toString()));
String s= map.get(key + ".name").toString();
result.put(s+"SqlSessionTemplate",newSqlSessionTemplate(sqlSessionFactoryBean.getObject()));
result.put(s+"DataSource",dataSource);
result.put(s+"DataSourceTransactionManager",dataSourceTransactionManager);
}returnresult;
}
}
3、使用注解(DataSourceRoute),确定每个mapper接口使用哪个数据源
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)public @interfaceDataSourceRoute {
String name()default "first";
}
@DataSourceRoutepublic interfaceSepUserMapper {
List>findByUserLevel(Long userLevel);void insert(Mapmap);
}
@DataSourceRoute(name = "second")public interfaceIDiDataItemMapper {
@Select("SELECT dataitem_id,name FROM di_dataitem WHERE dataitem_id=#{dataItemId}")
MapselectOne(Long dataItemId);void insert(Mapmap);
}
4、扫描指定包下面的mapper接口
public classClassScanner {public static Map>getMapperInterface(String mapperInterfacePackage) throwsException {
Map>classMap = new HashMap<>();
ClassLoader loader=Thread.currentThread().getContextClassLoader();//将"."替换成"/"
String packagePath = mapperInterfacePackage.replace(".", "/");
URL url=loader.getResource(packagePath);
List fileNames = null;if (url != null) {
String type=url.getProtocol();if ("file".equals(type)) {
fileNames= getClassNameByFile(url.getPath(), null, true);
}
}for(String classPath : fileNames) {
classMap.putAll(getClassByPath(classPath));
}returnclassMap;
}/*** 读取package下的所有类文件
*@paramfilePath
*@paramclassName
*@paramchildPackage
*@return
*/
private static List getClassNameByFile(String filePath, List className, booleanchildPackage) {
List myClassName = new ArrayList<>();
File file= newFile(filePath);
File[] childFiles=file.listFiles();for(File childFile : childFiles) {if(childFile.isDirectory()) {if(childPackage) {
myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));
}
}else{
String childFilePath=childFile.getPath();if (childFilePath.endsWith(".class")) {
childFilePath= childFilePath.substring(childFilePath.indexOf("\\classes") + 9,
childFilePath.lastIndexOf("."));
childFilePath= childFilePath.replace("\\", ".");
myClassName.add(childFilePath);
}
}
}returnmyClassName;
}/*** 将Mapper的标准文件,转成 Mapper Class
*@paramclassPath
*@return*@throwsException*/
private static Map>getClassByPath(String classPath)throwsException{
ClassLoader loader=Thread.currentThread().getContextClassLoader();
Map> classMap = new HashMap<>();
classMap.put(getClassAlias(classPath),loader.loadClass(getFullClassName(classPath)));returnclassMap;
}/*** 将Mapper的标准文件,转成java标准的类名称
*@paramclassPath
*@return*@throwsException*/
private staticString getFullClassName(String classPath)throwsException{int comIndex = classPath.indexOf("com");
classPath=classPath.substring(comIndex);
classPath= classPath.replaceAll("\\/", ".");returnclassPath;
}/*** 根据类地址,获取类的Alais,即根据名称,按照驼峰规则,生成可作为变量的名称
*@paramclassPath
*@return*@throwsException*/
private staticString getClassAlias(String classPath)throwsException{
String split= "\\/";
String[] classTmp=classPath.split(split);
String className= classTmp[classTmp.length-1];returntoLowerFisrtChar(className);
}/*** 将字符串的第一个字母转小写
*@paramclassName
*@return
*/
private staticString toLowerFisrtChar(String className){
String fisrtChar= className.substring(0,1);
fisrtChar=fisrtChar.toLowerCase();return fisrtChar+className.substring(1);
}
5、使用BeanFactoryPostProcessor动态插入数据源
@Componentpublic class DataSourceBean implementsBeanFactoryPostProcessor{
@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throwsBeansException {
System.out.println("在spring处理bean前,将自定义的bean注册到容器中======================"); DataSourceConfig dataSourceConfig = newDataSourceConfig();try{
Map sqlSessionTemplateAndDataSource =dataSourceConfig.getSqlSessionTemplateAndDataSource();
Map> mapperInterface =ClassScanner.getMapperInterface(dataSourceConfig.getMapperInterfacePackage());
Set>> entries =mapperInterface.entrySet();for (Map.Entry>entry : entries) {
MapperFactoryBean mapperFactoryBean= newMapperFactoryBean();
Class> value =entry.getValue();
DataSourceRoute dataSourceRoute= value.getAnnotation(DataSourceRoute.class);if (null==dataSourceConfig){continue;
}
String name=dataSourceRoute.name();
SqlSessionTemplate template= (SqlSessionTemplate) sqlSessionTemplateAndDataSource.get(name + "SqlSessionTemplate");
mapperFactoryBean.setMapperInterface(value);
mapperFactoryBean.setSqlSessionTemplate(template);
mapperFactoryBean.afterPropertiesSet();
configurableListableBeanFactory.registerSingleton(name+"MapperFactory",mapperFactoryBean.getObject());
configurableListableBeanFactory.registerSingleton(name+"DataSource",sqlSessionTemplateAndDataSource.get(name + "DataSource"));
configurableListableBeanFactory.registerSingleton(name+"SqlSessionTemplate",template);
configurableListableBeanFactory.registerSingleton(name+"DataSourceTransactionManager",sqlSessionTemplateAndDataSource.get(name+"DataSourceTransactionManager"));
}
}catch(Exception e) {
System.err.println(e.getMessage());
}
}
}
至此多数据源动态加载就完成了。
二、多数据源统一事务控制
当使用多数据源时,单一的事务会出现问题(当在service层同时操作两个数据源时,当发生异常,只会回滚离抛出异常最近的数据源的数据)
1、自定义事务注解
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)public @interfaceCustomTransaction {
String[] name()default {"firstDataSourceTransactionManager"};
}
2、创建aop切面进行事务控制
@Component
@Aspectpublic classTransactionAop {
@Pointcut(value= "@annotation(com.example.demo.annon.CustomTransaction)")public voidpointCut(){}
@Around(value= "pointCut()&&@annotation(annotation)")public Object twiceAsOld(ProceedingJoinPoint point, CustomTransaction annotation) throwsThrowable {
Stack dataSourceTransactionManagerStack = new Stack();
Stack transactionStatuStack = new Stack();try{if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {return null;
}
Object ret=point.proceed();
commit(dataSourceTransactionManagerStack, transactionStatuStack);returnret;
}catch(Throwable e) {
rollback(dataSourceTransactionManagerStack, transactionStatuStack);throwe;
}
}/*** 开启事务处理方法
*
*@paramdataSourceTransactionManagerStack
*@paramtransactionStatuStack
*@parammultiTransactional
*@return
*/
private boolean openTransaction(StackdataSourceTransactionManagerStack,
StacktransactionStatuStack, CustomTransaction multiTransactional) {
String[] transactionMangerNames=multiTransactional.name();if(ArrayUtils.isEmpty(multiTransactional.name())) {return false;
}for(String beanName : transactionMangerNames) {//根据事务名称获取具体的事务
DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil
.getBean(beanName);
TransactionStatus transactionStatus=dataSourceTransactionManager
.getTransaction(newDefaultTransactionDefinition());
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
}return true;
}/*** 提交处理方法
*
*@paramdataSourceTransactionManagerStack
*@paramtransactionStatuStack*/
private void commit(StackdataSourceTransactionManagerStack,
StacktransactionStatuStack) {while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop());
}
}/*** 回滚处理方法
*
*@paramdataSourceTransactionManagerStack
*@paramtransactionStatuStack*/
private void rollback(StackdataSourceTransactionManagerStack,
StacktransactionStatuStack) {while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
}
}
}
3、在service层指定使用哪个事务
//注意事务的命名规则
@CustomTransaction(name = {"firstDataSourceTransactionManager","secondDataSourceTransactionManager"})public voidsetSepUserMapper(){//操作数据源2
Mapmm = new HashMap<>(2);
mm.put("dataitemId",1L);
mm.put("name","测试");
diDataItemMapper.insert(mm);//操作数据源1
Mapmap = new HashMap<>(3);
map.put("userId",1L);
map.put("userName","张三");
map.put("name","平台管理员");
sepUserMapper.insert(map);throw new RuntimeException("sfsa");
}
辅助类:SpringContextUtil
@Componentpublic class SpringContextUtil implementsApplicationContextAware{private staticApplicationContext applicationContext;
@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {
SpringContextUtil.applicationContext=applicationContext;
}/*** @Description: 获取spring容器中的bean,通过bean名称获取
*@parambeanName bean名称
*@return: Object 返回Object,需要做强制类型转换
*@author: zongf
* @time: 2018-12-26 10:45:07*/
public staticObject getBean(String beanName){returnapplicationContext.getBean(beanName);
}/*** @Description: 获取spring容器中的bean, 通过bean类型获取
*@parambeanClass bean 类型
*@return: T 返回指定类型的bean实例
*@author: zongf
* @time: 2018-12-26 10:46:31*/
public static T getBean(ClassbeanClass) {returnapplicationContext.getBean(beanClass);
}/*** @Description: 获取spring容器中的bean, 通过bean名称和bean类型精确获取
*@parambeanName bean 名称
*@parambeanClass bean 类型
*@return: T 返回指定类型的bean实例
*@author: zongf
* @time: 2018-12-26 10:47:45*/
public static T getBean(String beanName, ClassbeanClass){returnapplicationContext.getBean(beanName,beanClass);
}