springboot基于注解动态配置多数据源以及多数据源的事务统一
参考文档:https://www.cnblogs.com/zhangboyu/p/7622412.html
https://blog.csdn.net/qq_34322777/article/details/80833935
一、动态注入多数据源
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 class DataSourceConfig { /** * 存储dataSource、SqlSessionTemplate、DataSourceTransactionManager */ private Map<String,Map<String,Object>>mapMap; /** * 获取mybatis扫描的指定接口包(所有数据源的接口放在同一的父包下面) */ private String mapperInterfacePackage; public DataSourceConfig(){ mapMap = new HashMap<>(); InputStream in = DataSourceConfig.class.getClassLoader(). getResourceAsStream("application-db.properties"); Properties properties = new Properties(); try { properties.load(in); } catch (IOException e) { e.printStackTrace(); } Set<String> 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<String, Object> map = mapMap.get(key); map.put(s,properties.get(s)); }else{ Map<String,Object>map = new HashMap<>(); map.put(s,properties.get(s)); mapMap.put(key,map); } } } public String getMapperInterfacePackage() { return mapperInterfacePackage; } /** * 获取SqlSessionTemplate * @return * @throws Exception */ public Map<String,Object>getSqlSessionTemplateAndDataSource() throws Exception { Set<Map.Entry<String, Map<String, Object>>> entries = this.mapMap.entrySet(); Map<String,Object>result = new HashMap<>(entries.size()); for (Map.Entry<String, Map<String, Object>> entry : entries) { String key = entry.getKey(); Map<String, Object> 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 = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //设置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",new SqlSessionTemplate(sqlSessionFactoryBean.getObject())); result.put(s+"DataSource",dataSource); result.put(s+"DataSourceTransactionManager",dataSourceTransactionManager); } return result; } }
3、使用注解(DataSourceRoute),确定每个mapper接口使用哪个数据源
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DataSourceRoute { String name() default "first"; }
@DataSourceRoute public interface SepUserMapper { List<Map<String,Object>> findByUserLevel(Long userLevel); void insert(Map<String,Object>map); }
@DataSourceRoute(name = "second") public interface IDiDataItemMapper { @Select("SELECT dataitem_id,name FROM di_dataitem WHERE dataitem_id=#{dataItemId}") Map<String,Object>selectOne(Long dataItemId); void insert(Map<String, Object> map); }
4、扫描指定包下面的mapper接口
public class ClassScanner { public static Map<String,Class<?>>getMapperInterface(String mapperInterfacePackage) throws Exception { Map<String,Class<?>>classMap = new HashMap<>(); ClassLoader loader = Thread.currentThread().getContextClassLoader(); //将"."替换成"/" String packagePath = mapperInterfacePackage.replace(".", "/"); URL url = loader.getResource(packagePath); List<String> 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)); } return classMap; } /** * 读取package下的所有类文件 * @param filePath * @param className * @param childPackage * @return */ private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) { List<String> myClassName = new ArrayList<>(); File file = new File(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); } } } return myClassName; } /** * 将Mapper的标准文件,转成 Mapper Class * @param classPath * @return * @throws Exception */ private static Map<String, Class<?>> getClassByPath(String classPath) throws Exception{ ClassLoader loader = Thread.currentThread().getContextClassLoader(); Map<String, Class<?>> classMap = new HashMap<>(); classMap.put(getClassAlias(classPath),loader.loadClass(getFullClassName(classPath))); return classMap; } /** * 将Mapper的标准文件,转成java标准的类名称 * @param classPath * @return * @throws Exception */ private static String getFullClassName(String classPath) throws Exception{ int comIndex = classPath.indexOf("com"); classPath = classPath.substring(comIndex); classPath = classPath.replaceAll("\\/", "."); return classPath; } /** * 根据类地址,获取类的Alais,即根据名称,按照驼峰规则,生成可作为变量的名称 * @param classPath * @return * @throws Exception */ private static String getClassAlias(String classPath) throws Exception{ String split = "\\/"; String[] classTmp = classPath.split(split); String className = classTmp[classTmp.length-1]; return toLowerFisrtChar(className); } /** * 将字符串的第一个字母转小写 * @param className * @return */ private static String toLowerFisrtChar(String className){ String fisrtChar = className.substring(0,1); fisrtChar = fisrtChar.toLowerCase(); return fisrtChar+className.substring(1); }
5、使用BeanFactoryPostProcessor动态插入数据源
@Component public class DataSourceBean implements BeanFactoryPostProcessor{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { System.out.println("在spring处理bean前,将自定义的bean注册到容器中======================"); DataSourceConfig dataSourceConfig = new DataSourceConfig(); try { Map<String, Object> sqlSessionTemplateAndDataSource = dataSourceConfig.getSqlSessionTemplateAndDataSource(); Map<String, Class<?>> mapperInterface = ClassScanner.getMapperInterface(dataSourceConfig.getMapperInterfacePackage()); Set<Map.Entry<String, Class<?>>> entries = mapperInterface.entrySet(); for (Map.Entry<String, Class<?>> entry : entries) { MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(); 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 @interface CustomTransaction { String[] name() default {"firstDataSourceTransactionManager"}; }
2、创建aop切面进行事务控制
@Component @Aspect public class TransactionAop { @Pointcut(value = "@annotation(com.example.demo.annon.CustomTransaction)") public void pointCut(){} @Around(value = "pointCut()&&@annotation(annotation)") public Object twiceAsOld(ProceedingJoinPoint point, CustomTransaction annotation) throws Throwable { Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<DataSourceTransactionManager>(); Stack<TransactionStatus> transactionStatuStack = new Stack<TransactionStatus>(); try { if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) { return null; } Object ret = point.proceed(); commit(dataSourceTransactionManagerStack, transactionStatuStack); return ret; } catch (Throwable e) { rollback(dataSourceTransactionManagerStack, transactionStatuStack); throw e; } } /** * 开启事务处理方法 * * @param dataSourceTransactionManagerStack * @param transactionStatuStack * @param multiTransactional * @return */ private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatuStack, 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(new DefaultTransactionDefinition()); transactionStatuStack.push(transactionStatus); dataSourceTransactionManagerStack.push(dataSourceTransactionManager); } return true; } /** * 提交处理方法 * * @param dataSourceTransactionManagerStack * @param transactionStatuStack */ private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatuStack) { while (!dataSourceTransactionManagerStack.isEmpty()) { dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop()); } } /** * 回滚处理方法 * * @param dataSourceTransactionManagerStack * @param transactionStatuStack */ private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatuStack) { while (!dataSourceTransactionManagerStack.isEmpty()) { dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop()); } } }
3、在service层指定使用哪个事务
//注意事务的命名规则
@CustomTransaction(name = {"firstDataSourceTransactionManager","secondDataSourceTransactionManager"}) public void setSepUserMapper(){ //操作数据源2 Map<String,Object>mm = new HashMap<>(2); mm.put("dataitemId",1L); mm.put("name","测试"); diDataItemMapper.insert(mm); //操作数据源1 Map<String,Object>map = new HashMap<>(3); map.put("userId",1L); map.put("userName","张三"); map.put("name","平台管理员"); sepUserMapper.insert(map); throw new RuntimeException("sfsa"); }
辅助类:SpringContextUtil
@Component public class SpringContextUtil implements ApplicationContextAware{ private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtil.applicationContext = applicationContext; } /** * @Description: 获取spring容器中的bean,通过bean名称获取 * @param beanName bean名称 * @return: Object 返回Object,需要做强制类型转换 * @author: zongf * @time: 2018-12-26 10:45:07 */ public static Object getBean(String beanName){ return applicationContext.getBean(beanName); } /** * @Description: 获取spring容器中的bean, 通过bean类型获取 * @param beanClass bean 类型 * @return: T 返回指定类型的bean实例 * @author: zongf * @time: 2018-12-26 10:46:31 */ public static <T> T getBean(Class<T> beanClass) { return applicationContext.getBean(beanClass); } /** * @Description: 获取spring容器中的bean, 通过bean名称和bean类型精确获取 * @param beanName bean 名称 * @param beanClass bean 类型 * @return: T 返回指定类型的bean实例 * @author: zongf * @time: 2018-12-26 10:47:45 */ public static <T> T getBean(String beanName, Class<T> beanClass){ return applicationContext.getBean(beanName,beanClass); }