在Mybatis-spring上基于注解的数据源实现方案
https://tech.youzan.com/zai-mybatis-springshang-ji-yu-zhu-jie-de-shu-ju-yuan-shi-xian-fang-an/
一、遇到的痛點
??????最近在學(xué)習(xí)Spring-boot過程中,涉及到操作數(shù)據(jù)庫。按照DOC引入mybatis-spring-boot-starter,然后按照套路配置application.properties、碼Mapper、dataobject、xxx-mapper.xml的代碼就OK了。這個時候,采用DataSourceAutoConfiguration默認(rèn)方式實現(xiàn)的,這時單數(shù)據(jù)源可用了。這種方式,網(wǎng)上有很Blog。?
??????但是,我是測試開發(fā)工程師,自動化工程經(jīng)常要連N個數(shù)據(jù)源。對于多數(shù)據(jù)源,網(wǎng)上提供了重寫DataSourceAutoConfiguration的方式。代碼如下:
??????這個方式,確實可用,不足在于,需要根據(jù)不同數(shù)據(jù)源建立不同的package,一旦數(shù)據(jù)源發(fā)生變更,需要更改所在的package。也看過了動態(tài)數(shù)據(jù)源,那也不是我想要的。
二、方案探索
??????我在思考能不能基于注解來指定數(shù)據(jù)源呢??
??????然后開始寫個注解DataSourceRoute。
??????之后,寫了AOP處理器來檢測這個注解,一直無法正確切入。那我在想是不是可以通過重寫mybatis啟動掃描方式實現(xiàn)多數(shù)據(jù)源呢?然后,閱讀了下mybatis-spring的源碼。org.mybatis.spring.mapper.ClassPathMapperScanner.processBeanDefinitions發(fā)現(xiàn),啟動時,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看下生成的對象,驗證對代碼的理解。那就朝著創(chuàng)建MapperFactoryBean去就好了。
三、具體方案實現(xiàn)
3.1 知識儲備
??????請通過網(wǎng)絡(luò)等途徑了解下BeanDefinition、BeanDefinitionRegistryPostProcessor、ApplicationContextAware、BeanFactoryPostProcessor、InitializingBean、MapperFactoryBean、MapperProxyFactory、ClassPathMapperScanner、GenericBeanDefinition。前面這些,在你閱讀mybatis源碼時會看到,請先了解。
3.2 實現(xiàn)內(nèi)容
- 實現(xiàn)多數(shù)據(jù)源的加載
- Mapper對象掃描加載
- 生成MapperFactoryBean對象與裝配
下面直接上代碼。
3.2.1 讀取配置文件公共類
12345678910111213141516171819202122232425262728293031323334353.2.2 實現(xiàn)多數(shù)據(jù)源的加載
第一步、構(gòu)造多數(shù)據(jù)源的DataSource
/*** youzan.com Inc.* Copyright (c) 2012-2017 All Rights Reserved.** @author: lvguoyong@youzan.com 無影* @date 17/9/20 下午1:20* @desc*/ 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586第二步、構(gòu)造SqlSessionFactoryBean對象
12345678910111213141516171819202122232425262728第三步、構(gòu)造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文件,并轉(zhuǎn)換成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的標(biāo)準(zhǔn)文件,轉(zhuǎn)成 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的標(biāo)準(zhǔn)文件,轉(zhuǎn)成java標(biāo)準(zhǔn)的類名稱 * * @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、根據(jù)類地址,獲取類的Alais,即根據(jù)名稱,按照駝峰規(guī)則,生成可作為變量的名稱 * * @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、將字符串的第一個字母轉(zhuǎn)小寫 * * @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集合,同時實現(xiàn)了多數(shù)據(jù)源的加載。這里通過org.mybatis.spring.mapper.MapperFactoryBean把DAO、數(shù)據(jù)源模板進行綁定,并注入到Spring Bean工程池了。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778793.2.5 應(yīng)用
???????這時,我們就可以修改DAO的實現(xiàn)。指定的數(shù)據(jù)源名稱為配置文件里數(shù)據(jù)庫配置信息的第一段名稱,例如:「master.datasource.url=jdbc:mysql://127.0.0.1:3006/testdb」,這時名稱就是master。同時去掉了Spring-boot指導(dǎo)方案中的@Mapper注解。
123456789???????修改Spring-boot啟動的入口Application類,排除DataSourceAutoConfiguration的加載。
1234567???????至此,就可以啟動測試了。?
???????這個方案,只是做個引子,沒有完全按照Spring的標(biāo)準(zhǔn)實現(xiàn)。Spring的標(biāo)準(zhǔn)要求,應(yīng)該把DataSoure、SqlSessionFactoryBean、SqlSessionTemplate注入Spring工程池里,并給所有DAO類指定Bean的生命周期等。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/9238957.html
總結(jié)
以上是生活随笔為你收集整理的在Mybatis-spring上基于注解的数据源实现方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 有赞API网关实践
- 下一篇: 有赞客户行为收集与实时处理系统设计