日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

在Mybatis-spring上基于注解的数据源实现方案

發布時間:2025/4/5 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在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的方式。代碼如下:

@Configuration @MapperScan(basePackages = "com.youzan.springboot.dal.master", sqlSessionTemplateRef = "masterSST") public class MasterSouceConfig { private String localMapper = "classpath:mapper/*.xml"; @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource") @Primary public DataSource buildDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "masterSSF") @Primary public SqlSessionFactory buildSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean; bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(localMapper)); return bean.getObject(); } @Bean(name = "masterTM") @Primary public DataSourceTransactionManager buildTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "masterSST") @Primary public SqlSessionTemplate buildSqlSessionTemplate(@Qualifier("masterSSF") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } 1234567891011121314151617181920212223242526272829303132333435

??????這個方式,確實可用,不足在于,需要根據不同數據源建立不同的package,一旦數據源發生變更,需要更改所在的package。也看過了動態數據源,那也不是我想要的。

二、方案探索

??????我在思考能不能基于注解來指定數據源呢??
??????然后開始寫個注解DataSourceRoute。

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DataSourceRoute {String name() default "master"; } 123456

??????之后,寫了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 讀取配置文件公共類

@Data public class Config { // dao的package,現在只支持一個包名 private String daoPath; // *-mapper.xml的目錄信息 private String mapperPath; /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午6:56 * @modify history: * * @desc: * 1、讀取數據庫、DAO初始化需要的一些配置信息 * */ public Config() { InputStream in = this.getClass().getClassLoader() .getResourceAsStream("application-db.properties"); if (in != null) { Properties properties = new Properties(); try { properties.load(in); } catch (IOException e) { throw new BeanInitializationException("加載屬性配置文件過程失敗。", e); } daoPath = properties.getProperty("mybatis.dao-path"); mapperPath = properties.getProperty("mybatis.mapper-locations"); } } } 1234567891011121314151617181920212223242526272829303132333435

3.2.2 實現多數據源的加載

第一步、構造多數據源的DataSource

/*** youzan.com Inc.* Copyright (c) 2012-2017 All Rights Reserved.** @author: lvguoyong@youzan.com 無影* @date 17/9/20 下午1:20* @desc*/ @Data public class DataSourceBuilder { /** * 存儲實例化后的多數據元對象 */ private Map<String, DataSource> dataSourceMap = new HashMap<>(); /** * 存儲數據庫別名,在DAO類中,只能使用這些別名 */ private List<String> dataSourceAlias = new ArrayList<>(); /** * * 存儲數據源配置信息,按照數據源分組 */ private Map<String, Map<String, String>> dataSourceProperties = new HashMap<>(); /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午2:10 * @modify history: * * @desc: * 1、讀取系統classpath環境下,application-db.properties文件的數據庫配置 * 2、將數據庫配置按照數據源進行分組 * 3、實例化javax.sql.DataSource對象 * * @return DataSourceBuilder * */ public DataSourceBuilder builder() { InputStream in = this.getClass().getClassLoader(). getResourceAsStream("application-db.properties"); if (in != null) { Properties properties = new Properties(); try { properties.load(in); } catch (IOException e) { throw new BeanInitializationException("read property file error!", e); } //結束數據庫配置信息 Iterator<String> propertyKeys = properties.stringPropertyNames().iterator(); while (propertyKeys.hasNext()) { String key = propertyKeys.next(); String value = properties.getProperty(key); String[] keys = key.split("[.]"); if (dataSourceProperties.containsKey(keys[0])) { dataSourceProperties.get(keys[0]).put(key, value); } else { Map<String, String> innerMap = new HashMap<>(); innerMap.put(key, value); dataSourceProperties.put(keys[0], innerMap); dataSourceAlias.add(keys[0]); } } /** * 生成數據源 */ Iterator<String> DSNames = dataSourceProperties.keySet().iterator(); while (DSNames.hasNext()) { String dsName = DSNames.next(); Map<String, String> dsconfig = dataSourceProperties.get(dsName); DataSource dataSource = org.springframework.boot.autoconfigure.jdbc .DataSourceBuilder.create() .type(MysqlDataSource.class). .driverClassName(dsconfig.get(dsName + ".datasource.driver-class-name") .url(dsconfig.get(dsName + ".datasource.url")) .username(dsconfig.get(dsName + ".datasource.username")) .password(dsconfig.get(dsName + ".datasource.password")).build(); dataSourceMap.put(dsName, dataSource); } } return this; } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586

第二步、構造SqlSessionFactoryBean對象

@Data public class SqlSessionFactoryBuilder { /** * 數據庫與實體對象間映射文件目錄 */ private String localMapper = "classpath:mapper/*.xml"; /** * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午2:28 * @modify history: * @desc: * 1、創建一個SqlSessionFactoryBean實例對象 * * @param dbAlias * @param dataSource * @return */ public SqlSessionFactoryBean builder(String dbAlias, DataSource dataSource)throws Exception{ SqlSessionFactoryBean bean; bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(localMapper)); bean.afterPropertiesSet(); return bean; } } 12345678910111213141516171819202122232425262728

第三步、構造SqlSessionFactoryBean對象

/*** youzan.com Inc.* Copyright (c) 2012-2017 All Rights Reserved.** @author: lvguoyong@youzan.com 無影* @date 17/9/20 下午2:31* @desc*/ @Data public class SqlSessionTemplateBuilder { /** * SqlSessionFactory構建實體 */ SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder(); /** * * @author: lvguoyong@youzan.com 無影 * @date: 17/9/20 下午2:31 * @modify history: * * @desc: * 1、創建一個SqlSessionFactoryBean實例對象 * * @param dbAlias * @param dataSource * @return */ public SqlSessionTemplate builder(String dbAlias, DataSource dataSource)throws Exception{ SqlSessionFactoryBean bean = ssfb.builder(dbAlias,dataSource); return new SqlSessionTemplate(bean.getObject()); } } 12345678910111213141516171819202122232425262728293031323334

3.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); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175

3.2.4 生成MapperFactoryBean對象與裝配

???????前面獲取了所有DAO類的Map集合,同時實現了多數據源的加載。這里通過org.mybatis.spring.mapper.MapperFactoryBean把DAO、數據源模板進行綁定,并注入到Spring Bean工程池了。

@Component public class MapperScanner implements BeanFactoryPostProcessor, InitializingBean { /** * SqlSessionTemplate集合,按照數據庫Alias分組 */ Map<String, SqlSessionTemplate> sstMap = new HashMap<>(); @Override public void afterPropertiesSet() throws Exception { } public void buildSqlSessionTemplate(Map<String, DataSource> dataSourceMap) throws Exception { Iterator<String> dataSourceIter = dataSourceMap.keySet().iterator(); while (dataSourceIter.hasNext()) { String dbAlias = dataSourceIter.next(); DataSource db = dataSourceMap.get(dbAlias); SqlSessionTemplateBuilder sstb = new SqlSessionTemplateBuilder(); sstMap.put(dbAlias, sstb.builder(dbAlias, db)); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { /** * 加載所有到DAO類 */ ClassScanner classScanner = new ClassScanner(); Map<String, Class<?>> daoClasses = new HashMap<>(); try { daoClasses = classScanner.scan(); } catch (Exception e) { throw new BeanInstantiationException(this.getClass(), e.getMessage()); } /** * 加載多數據源 */ DataSourceBuilder dsBuiler = new DataSourceBuilder(); Map<String, DataSource> dataSourceMap = dsBuiler.builder().getDataSourceMap(); try { this.buildSqlSessionTemplate(dataSourceMap); } catch (Exception e) { throw new BeanInstantiationException(this.getClass(), e.getMessage()); } /** * 生命可執行數據庫DAO代理對象 */ try { Iterator<String> classIter = daoClasses.keySet().iterator(); while (classIter.hasNext()) { String classAlias = classIter.next(); Class<?> classBean = daoClasses.get(classAlias); /** * 獲取該類上的數據源注解 */ DataSourceRoute annotation = classBean.getAnnotation(DataSourceRoute.class); //實例化MapperFactory MapperFactoryBean bean = new MapperFactoryBean(); // 給MapperFactory指定其應該使用的數據庫模 String dbAlias = annotation.name(); bean.setSqlSessionTemplate(sstMap.get(dbAlias)); // 指定DAO bean.setMapperInterface(classBean); // 刷新 bean.afterPropertiesSet(); // 寫入Spring Bean工廠里 beanFactory.registerSingleton(classAlias, bean.getObject()); } } catch (Exception e) { throw new BeanInstantiationException(this.getClass(), e.getMessage()); } } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879

3.2.5 應用

???????這時,我們就可以修改DAO的實現。指定的數據源名稱為配置文件里數據庫配置信息的第一段名稱,例如:「master.datasource.url=jdbc:mysql://127.0.0.1:3006/testdb」,這時名稱就是master。同時去掉了Spring-boot指導方案中的@Mapper注解。

@DataSourceRoute(name="master") public interface AppInfoDAO { int delete(Integer id); int insert(AppInfoDO appInfoDO); int insertSelective(AppInfoDO appInfoDO); AppInfoDO select(Integer id); int updateByPrimaryKeySelective(AppInfoDO appInfoDO); int update(AppInfoDO appInfoDO); } 123456789

???????修改Spring-boot啟動的入口Application類,排除DataSourceAutoConfiguration的加載。

@SpringBootApplication @EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}) public class Bootstrap { public static void main(String[] args) { SpringApplication.run(Bootstrap.class,args); } } 1234567

???????至此,就可以啟動測試了。?
???????這個方案,只是做個引子,沒有完全按照Spring的標準實現。Spring的標準要求,應該把DataSoure、SqlSessionFactoryBean、SqlSessionTemplate注入Spring工程池里,并給所有DAO類指定Bean的生命周期等。

轉載于:https://www.cnblogs.com/davidwang456/articles/9238957.html

總結

以上是生活随笔為你收集整理的在Mybatis-spring上基于注解的数据源实现方案的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。