spring boot動態資料來源切換
阿新 • • 發佈:2018-12-22
參考網址:https://www.cnblogs.com/java-zhao/p/5413845.html
1.配置檔案application.properties中增加其他資料來源設定 #the first datasource jdbc.driverClassName = com.mysql.jdbc.Driver jdbc.url = jdbc: mysql://xxx:3306/mytestdb?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
package com.xxx.firstboot.common.datasource; /** * 列出所有的資料來源key(常用資料庫名稱來命名) * 注意: * 1)這裡資料來源與資料庫是一對一的 * 2)DatabaseType中的變數名稱就是資料庫的名稱 */ public enum DatabaseType { mytestdb,mytestdb2 }
3.DatabaseContextHolder構建DatabaseType容器,並提供了向其中設定和獲取DatabaseType的方法
package com.xxx.firstboot.common.datasource; /** * 作用: * 1、儲存一個執行緒安全的DatabaseType容器 */ public class DatabaseContextHolder { private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>(); public static void setDatabaseType(DatabaseType type){ contextHolder.set(type); } public static DatabaseType getDatabaseType(){ return contextHolder.get(); } }
4.建立DynamicDataSource類,使用DatabaseContextHolder獲取當前執行緒的DatabaseType
package com.xxx.firstboot.common.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 動態資料來源(需要繼承AbstractRoutingDataSource) */ public class DynamicDataSource extends AbstractRoutingDataSource { protected Object determineCurrentLookupKey() { return DatabaseContextHolder.getDatabaseType(); } }
5.配置MyBatisConfig,通過application.properties檔案生成兩個資料來源,使用這兩個資料來源構造動態的dataSource @Primary:指定在同一個介面有多個實現類可以注入的時候,預設選擇哪一個 @Qualifier:指定名稱的注入,當一個介面有多個實現類時使用 @Bean:生成的bean示例的名稱
package com.xxx.firstboot.common; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.xxx.firstboot.common.datasource.DatabaseType; import com.xxx.firstboot.common.datasource.DynamicDataSource; /** * springboot整合mybatis的基本入口 1)建立資料來源(如果採用的是預設的tomcat-jdbc資料來源,則不需要) * 2)建立SqlSessionFactory 3)配置事務管理器,除非需要使用事務,否則不用配置 */ @Configuration // 該註解類似於spring配置檔案 @MapperScan(basePackages = "com.xxx.firstboot.mapper") public class MyBatisConfig { @Autowired private Environment env; /** * 建立資料來源(資料來源的名稱:方法名可以取為XXXDataSource(),XXX為資料庫名稱,該名稱也就是資料來源的名稱) */ @Bean public DataSource myTestDbDataSource() throws Exception { Properties props = new Properties(); props.put("driverClassName", env.getProperty("jdbc.driverClassName")); props.put("url", env.getProperty("jdbc.url")); props.put("username", env.getProperty("jdbc.username")); props.put("password", env.getProperty("jdbc.password")); return DruidDataSourceFactory.createDataSource(props); } @Bean public DataSource myTestDb2DataSource() throws Exception { Properties props = new Properties(); props.put("driverClassName", env.getProperty("jdbc2.driverClassName")); props.put("url", env.getProperty("jdbc2.url")); props.put("username", env.getProperty("jdbc2.username")); props.put("password", env.getProperty("jdbc2.password")); return DruidDataSourceFactory.createDataSource(props); } /** * @Primary 該註解表示在同一個介面有多個實現類可以注入的時候,預設選擇哪一個,而不是讓@autowire註解報錯 * @Qualifier 根據名稱進行注入,通常是在具有相同的多個型別的例項的一個注入(例如有多個DataSource型別的例項) */ @Bean @Primary public DynamicDataSource dataSource(@Qualifier("myTestDbDataSource") DataSource myTestDbDataSource, @Qualifier("myTestDb2DataSource") DataSource myTestDb2DataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DatabaseType.mytestdb, myTestDbDataSource); targetDataSources.put(DatabaseType.mytestdb2, myTestDb2DataSource); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources);// 該方法是AbstractRoutingDataSource的方法 dataSource.setDefaultTargetDataSource(myTestDbDataSource);// 預設的datasource設定為myTestDbDataSource return dataSource; } /** * 根據資料來源建立SqlSessionFactory */ @Bean public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception { SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); fb.setDataSource(ds);// 指定資料來源(這個必須有,否則報錯) // 下邊兩句僅僅用於*.xml檔案,如果整個持久層操作不需要使用到xml檔案的話(只用註解就可以搞定),則不加 fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包 fb.setMapperLocations( new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));// return fb.getObject(); } /** * 配置事務管理器 */ @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception { return new DataSourceTransactionManager(dataSource); } }
6.使用例項
package com.xxx.firstboot.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.xxx.firstboot.domain.Shop; import com.xxx.firstboot.service.ShopService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @RestController @RequestMapping("/shop") @Api("shopController相關api") public class ShopController { @Autowired private ShopService service; @ApiOperation("獲取shop資訊,測試多資料來源") @RequestMapping(value = "/getShop", method = RequestMethod.GET) public Shop getShop(@RequestParam("id") int id) { return service.getShop(id); } }
package com.xxx.firstboot.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.xxx.firstboot.dao.ShopDao; import com.xxx.firstboot.domain.Shop; @Service public class ShopService { @Autowired private ShopDao dao; public Shop getShop(int id) { return dao.getShop(id); } }
package com.xxx.firstboot.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.xxx.firstboot.common.datasource.DatabaseContextHolder; import com.xxx.firstboot.common.datasource.DatabaseType; import com.xxx.firstboot.domain.Shop; import com.xxx.firstboot.mapper.ShopMapper; @Repository public class ShopDao { @Autowired private ShopMapper mapper; /** * 獲取shop */ public Shop getShop(int id) { DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb2); return mapper.getShop(id); } }
package com.xxx.firstboot.mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import com.xxx.firstboot.domain.Shop; public interface ShopMapper { @Select("SELECT * FROM t_shop WHERE id = #{id}") @Results(value = { @Result(id = true, column = "id", property = "id"), @Result(column = "shop_name", property = "shopName") }) public Shop getShop(@Param("id") int id); }
7.通過AOP實現資料來源切換
package com.xxx.firstboot.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.xxx.firstboot.domain.Shop; import com.xxx.firstboot.mapper.ShopMapper; @Repository public class ShopDao { @Autowired private ShopMapper mapper; /** * 獲取shop */ public Shop getShop(int id) { return mapper.getShop(id); } }
package com.xxx.firstboot.common.datasource; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import com.xxx.firstboot.dao.ShopDao; @Aspect @Component public class DataSourceAspect { @Before("execution(* com.xxx.firstboot.dao.*.*(..))") public void setDataSourceKey(JoinPoint point){ //連線點所屬的類例項是ShopDao if(point.getTarget() instanceof ShopDao){ DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb2); }else{//連線點所屬的類例項是UserDao(當然,這一步也可以不寫,因為defaultTargertDataSource就是該類所用的mytestdb) DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb); } } // @Around("execution(* com.xxx.firstboot.dao.*.*(..))") // public Object setDataSourceKeyByAround(ProceedingJoinPoint point) throws Throwable{ // if(point.getTarget() instanceof ShopDao){ // DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb2); // }else{//連線點所屬的類例項是UserDao(當然,這一步也可以不寫,因為defaultTargertDataSource就是該類所用的mytestdb) // DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb); // } // return point.proceed();//執行目標方法 // } }
或者
package com.xxx.firstboot.common.datasource; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import com.xxx.firstboot.dao.ShopDao; @Aspect @Component public class DataSourceAspect { /** * 使用空方法定義切點表示式 */ @Pointcut("execution(* com.xxx.firstboot.dao.*.*(..))") public void declareJointPointExpression() { } /** * 使用定義切點表示式的方法進行切點表示式的引入 */ @Before("declareJointPointExpression()") public void setDataSourceKey(JoinPoint point) { // 連線點所屬的類例項是ShopDao if (point.getTarget() instanceof ShopDao) { DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb2); } else {// 連線點所屬的類例項是UserDao(當然,這一步也可以不寫,因為defaultTargertDataSource就是該類所用的mytestdb) DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb); } } }