在Mybatis-spring上基于注解的数据源实现方案
https://tech.youzan.com/zai-mybatis-springshang-ji-yu-zhu-jie-de-shu-ju-yuan-shi-xian-fang-an/
一、遇到的痛點
??????最近在學習Spring-boot過程中,涉及到操作數據庫。按照DOC引入mybatis-spring-boot-starter,然后按照套路配置application.properties、碼Mapper、dataobject、xxx-mapper.xml的代碼就OK了。這個時候,采用DataSourceAutoConfiguration默認方式實現的,這時單數據源可用了。這種方式,網上有很Blog。?
??????但是,我是測試開發工程師,自動化工程經常要連N個數據源。對于多數據源,網上提供了重寫DataSourceAutoConfiguration的方式。代碼如下:
??????這個方式,確實可用,不足在于,需要根據不同數據源建立不同的package,一旦數據源發生變更,需要更改所在的package。也看過了動態數據源,那也不是我想要的。
二、方案探索
??????我在思考能不能基于注解來指定數據源呢??
??????然后開始寫個注解DataSourceRoute。
??????之后,寫了AOP處理器來檢測這個注解,一直無法正確切入。那我在想是不是可以通過重寫mybatis啟動掃描方式實現多數據源呢?然后,閱讀了下mybatis-spring的源碼。org.mybatis.spring.mapper.ClassPathMapperScanner.processBeanDefinitions發現,啟動時,mybatis生成了MapperFactoryBean對象。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } definition.getConstructorArgumentValues() .addGenericArgumentValue(definition.getBeanClassName()); definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues() .add("addToConfig",this.addToConfig); 1234567891011121314151617??????然后,我通過Debug看下生成的對象,驗證對代碼的理解。那就朝著創建MapperFactoryBean去就好了。
三、具體方案實現
3.1 知識儲備
??????請通過網絡等途徑了解下BeanDefinition、BeanDefinitionRegistryPostProcessor、ApplicationContextAware、BeanFactoryPostProcessor、InitializingBean、MapperFactoryBean、MapperProxyFactory、ClassPathMapperScanner、GenericBeanDefinition。前面這些,在你閱讀mybatis源碼時會看到,請先了解。
3.2 實現內容
- 實現多數據源的加載
- Mapper對象掃描加載
- 生成MapperFactoryBean對象與裝配
下面直接上代碼。
3.2.1 讀取配置文件公共類
12345678910111213141516171819202122232425262728293031323334353.2.2 實現多數據源的加載
第一步、構造多數據源的DataSource
/*** youzan.com Inc.* Copyright (c) 2012-2017 All Rights Reserved.** @author: lvguoyong@youzan.com 無影* @date 17/9/20 下午1:20* @desc*/ 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586第二步、構造SqlSessionFactoryBean對象
12345678910111213141516171819202122232425262728第三步、構造SqlSessionFactoryBean對象
/*** youzan.com Inc.* Copyright (c) 2012-2017 All Rights Reserved.** @author: lvguoyong@youzan.com 無影* @date 17/9/20 下午2:31* @desc*/ 123456789101112131415161718192021222324252627282930313233343.2.3 Mapper對象掃描加載
/**** youzan.com Inc.* Copyright (c) 2012-2017 All Rights Reserved.** @author: lvguoyong@youzan.com 無影* @date 17/9/20 下午3:29* @desc* 1、掃描指定package路徑下的類文件列表*/ public class ClassScanner { /** * 掃描的包路徑 */ String scanpPackage ; /** * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午6:49 * @modify history: * * @desc: * 1、掃描指定package下的所有*DAO文件,并轉換成Class<?> * * @return Map<String, Class<?>> * key:為DAO的alais,例如 AppInfoDao,key則為appInfoDao。 * value: Class類型的類信息,非實例化的 * * @throws Exception */ public Map<String, Class<?>> scan() throws Exception{ Config config = new Config(); scanpPackage = config.getDaoPath(); Map<String,Class<?>> classMap = new HashMap<>(); ClassLoader loader = Thread.currentThread().getContextClassLoader(); String packagePath = scanpPackage.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(this.getClassByPath(classPath)); } return classMap; } /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午6:51 * @modify history: * * @desc: * 1、讀取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<String>(); 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; } /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午6:52 * @modify history: * * @desc: * 1、將DAO的標準文件,轉成 DAO Class * * @param classPath * @return * @throws Exception */ public Map<String, Class<?>> getClassByPath(String classPath) throws Exception{ ClassLoader loader = Thread.currentThread().getContextClassLoader(); Map<String, Class<?>> classMap = new HashMap<>(); classMap.put(this.getClassAlias(classPath),loader.loadClass(this.getFullClassName(classPath))); return classMap; } /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午6:53 * @modify history: * * @desc: * 1、將DAO的標準文件,轉成java標準的類名稱 * * @param classPath * @return * @throws Exception */ private String getFullClassName(String classPath) throws Exception{ int comIndex = classPath.indexOf("com"); classPath = classPath.substring(comIndex); classPath = classPath.replaceAll("\\/", "."); return classPath; } /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午6:54 * @modify history: * * @desc: * 1、根據類地址,獲取類的Alais,即根據名稱,按照駝峰規則,生成可作為變量的名稱 * * @param classPath * @return * @throws Exception */ private String getClassAlias(String classPath) throws Exception{ String split = "\\/"; String[] classTmp = classPath.split(split); String className = classTmp[classTmp.length-1]; return this.toLowerFisrtChar(className); } /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午6:55 * @modify history: * * @desc: * 1、將字符串的第一個字母轉小寫 * * @param className * @return */ private String toLowerFisrtChar(String className){ String fisrtChar = className.substring(0,1); fisrtChar = fisrtChar.toLowerCase(); return fisrtChar+className.substring(1); } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741753.2.4 生成MapperFactoryBean對象與裝配
???????前面獲取了所有DAO類的Map集合,同時實現了多數據源的加載。這里通過org.mybatis.spring.mapper.MapperFactoryBean把DAO、數據源模板進行綁定,并注入到Spring Bean工程池了。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778793.2.5 應用
???????這時,我們就可以修改DAO的實現。指定的數據源名稱為配置文件里數據庫配置信息的第一段名稱,例如:「master.datasource.url=jdbc:mysql://127.0.0.1:3006/testdb」,這時名稱就是master。同時去掉了Spring-boot指導方案中的@Mapper注解。
123456789???????修改Spring-boot啟動的入口Application類,排除DataSourceAutoConfiguration的加載。
1234567???????至此,就可以啟動測試了。?
???????這個方案,只是做個引子,沒有完全按照Spring的標準實現。Spring的標準要求,應該把DataSoure、SqlSessionFactoryBean、SqlSessionTemplate注入Spring工程池里,并給所有DAO類指定Bean的生命周期等。
轉載于:https://www.cnblogs.com/davidwang456/articles/9238957.html
總結
以上是生活随笔為你收集整理的在Mybatis-spring上基于注解的数据源实现方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 有赞API网关实践
- 下一篇: 有赞客户行为收集与实时处理系统设计