2 数据源配置_论多数据源(读写分离)的实现方案
好的,作為一個合格的bug生產者,我們直接進入主題,多數據源和讀寫分離實現方案。
首先多數據源和讀寫分離什么時候我們才需要呢?
多數據源:一個單體項目過于復雜,需要操作多個業務庫的時候,就需要多數據源操作不同的數據讀寫分離:數據庫壓力較大時,我們考慮讀寫分離,主庫寫,從庫讀,減少數據庫的壓力。多個庫數據是一樣的。理解完使用場景后,再入主題,怎么實現呢?這里說三種實現方式
1、擴展Spring的AbstractRoutingDataSource 2、通過Mybatis 配置不同的 Mapper 使用不同的 SqlSessionTemplate 3、分庫分表中間件,比如Sharding-JDBC 、Mycat等。好的,再讓我們直入主題
擴展Spring的AbstractRoutingDataSource
多數據源
基于Spring AbstractRoutingDataSource做擴展,通過繼承AbstractRoutingDataSource抽象類,實現一個管理多個 DataSource的數據源管理類。Spring 在獲取數據源時,可以通過 數據源管理類 返回實際的 DataSource 。
然后我們可以定義一個注解,添加到service、dao上,表示一個實際的對應的datasource。
不過這個方式,對于spring事物的支持不好,多個數據源無法保障事物。這個問題是多數據源的通用問題了。
廢話不多說,下面我們說下具體實現把,首先pom要引入的依賴的話很簡單,就是一個springboot項目。
pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.2</version> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope> </dependency> <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions> </dependency> <!--實現對 Druid 連接池的自動化配置--> <dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.21</version> </dependency>application配置多數據源
server:port: 8080spring:application:name: dynamicdatasource:mall:url: jdbc:mysql://rm-xxxxx.mysql.rds.aliyuncs.com/luu_mall?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: root # 數據庫賬號password: root0319@ # 數據庫密碼type: com.alibaba.druid.pool.DruidDataSource # 設置類型為 DruidDataSourcemin-idle: 0 # 池中維護的最小空閑連接數,默認為 0 個。max-active: 20 # 池中最大連接數,包括閑置和使用中的連接,默認為 8 個。# 用戶數據源配置users:url: jdbc:mysql://rm-xxxxxx.mysql.rds.aliyuncs.com/luu_user_center?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: root # 數據庫賬號password: root0319@ # 數據庫密碼type: com.alibaba.druid.pool.DruidDataSource # 設置類型為 DruidDataSource# Druid 自定義配置,對應 DruidDataSource 中的 setting 方法的屬性min-idle: 0 # 池中維護的最小空閑連接數,默認為 0 個。max-active: 20 # 池中最大連接數,包括閑置和使用中的連接,默認為 8 個。# Druid 自定義配置,對應 DruidDataSource 中的 setting 方法的屬性druid: # 設置 Druid 連接池的自定義配置。然后 DruidDataSourceAutoConfigure 會自動化配置 Druid 連接池。filter:stat: # 配置 StatFilter ,對應文檔 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilterlog-slow-sql: true # 開啟慢查詢記錄slow-sql-millis: 5000 # 慢 SQL 的標準,單位:毫秒merge-sql: true # SQL合并配置stat-view-servlet: # 配置 StatViewServlet ,對應文檔 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AEenabled: true # 是否開啟 StatViewServletlogin-username: root # 賬號login-password: root # 密碼mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.luu.druid.druid_demo.entity.*配置多數據源DynamicDataSourceConfig
@Configuration public class DynamicDataSourceConfig {/*** 創建 orders 數據源*/@Bean(name = "mallDataSource")@ConfigurationProperties(prefix = "spring.datasource.mall") // 讀取 spring.datasource.orders 配置到 HikariDataSource 對象public DataSource ordersDataSource() {return DruidDataSourceBuilder.create().build();}/*** 創建 users 數據源*/@Bean(name = "usersDataSource")@ConfigurationProperties(prefix = "spring.datasource.users")public DataSource usersDataSource() {return DruidDataSourceBuilder.create().build();}@Bean@Primarypublic DynamiDataSource dataSource(DataSource mallDataSource, DataSource usersDataSource) {Map<Object, Object> targetDataSources = new HashMap<>(2);targetDataSources.put(DataSourceFlag.DATA_SOURCE_FLAG_MALL, mallDataSource);targetDataSources.put(DataSourceFlag.DATA_SOURCE_FLAG_USER, usersDataSource);// 還有數據源,在targetDataSources中繼續添加System.out.println("DataSources:" + targetDataSources);//默認的數據源是oneDataSourcereturn new DynamiDataSource(mallDataSource, targetDataSources);}}常量DataSourceFlag裝載這我們區分數據源的key
public interface DataSourceFlag {?? public static String DATA_SOURCE_FLAG_MALL = "mall";? public static String DATA_SOURCE_FLAG_USER = "user";? }DynamiDataSource用來繼承Spring AbstractRoutingDataSource來實現數據源切換,并且設置默認數據源。
public class DynamiDataSource extends AbstractRoutingDataSource {/*** 配置DataSource, defaultTargetDataSource為主數據庫*/public DynamiDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {//設置默認數據源super.setDefaultTargetDataSource(defaultTargetDataSource);//設置數據源列表super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceHolder.getRouteKey();} }通過DynamicDataSourceHolder操作ThreadLocal來保存當前線程操作的哪個數據源
/*** 數據源管路由*/ public class DynamicDataSourceHolder {private static ThreadLocal<String> routeKey = new ThreadLocal<String>();/*** 獲取當前線程的數據源路由的key*/public static String getRouteKey() {String key = routeKey.get();return key;}/*** 綁定當前線程數據源路由的key* 使用完成后必須調用removeRouteKey()方法刪除*/public static void setRouteKey(String key) {routeKey.set(key);}/*** 刪除與當前線程綁定的數據源路由的key*/public static void removeRouteKey() {routeKey.remove();} }到這里配置基本完成來,那要怎么用呢,如何切換數據源呢,這里我們上面有說到,通過注解,來切換數據源。所以定義一個注解ChangeDataSource,不同的key切換不同的數據源
@Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ChangeDataSource {String value() default DataSourceFlag.DATA_SOURCE_FLAG_MALL; }我們把注解用在mapper方法上
@Mapper public interface TestMapper {int test();String mallNoAnno();@ChangeDataSource(DataSourceFlag.DATA_SOURCE_FLAG_MALL)String mallExitAnno();@ChangeDataSource(DataSourceFlag.DATA_SOURCE_FLAG_USER)String userNoAnno();@ChangeDataSource(DataSourceFlag.DATA_SOURCE_FLAG_USER)String userExitAnno();}然后通過切面DataSourceAspect更換ThreadLocal中key實現數據源切換
@Aspect @Component public class DataSourceAspect implements Ordered {protected Logger logger = LoggerFactory.getLogger(getClass());/*** 切點: 所有配置 ChangeDataSource 注解的方法*/@Pointcut("@annotation(com.luu.druid.druid_demo.common.ChangeDataSource)")public void dataSourcePointCut() {}@Around("dataSourcePointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();ChangeDataSource ds = method.getAnnotation(ChangeDataSource.class);// 通過判斷 @ChangeDataSource注解 中的值來判斷當前方法應用哪個數據源DynamicDataSourceHolder.setRouteKey(ds.value());System.out.println("當前數據源: " + ds.value());logger.debug("set datasource is " + ds.value());try {return point.proceed();} finally {DynamicDataSourceHolder.removeRouteKey();logger.debug("clean datasource");}}@Overridepublic int getOrder() {return 1;} }單元測試
@SpringBootTest class DruidDemoApplicationTests {@AutowiredTestMapper testMapper;@Testvoid contextLoads() {int i = testMapper.test();String id = testMapper.mallNoAnno();String id2 = testMapper.mallExitAnno();String name = testMapper.userNoAnno();String name2 = testMapper.userExitAnno();} }到這里呢,代碼基本寫完來。這里就是多數據源的配置,然后還有讀寫分離怎么實現呢。
而上面我們說到事物上不起效果的,因為事物上要拿到數據源的連接對象,而這里我們在mapper層有更換數據源,所以是不行的,所以數據源無法切換成果,然后執行的時候會報錯的。但是如果我們整個是在Service上使用這個注解,整個方法上同一個數據源就可以的。
實現讀寫分離
其實讀寫分離的實現通過上面的方式稍微修改下就可以來,就是在切面中,不在通過注解,根據方法名的前綴來判斷是走主庫,還是走從庫。比如find、select這樣讀數據的就走從庫,而insert這樣的就走主庫。具體的代碼的話,摸一摸我發量不多的頭,算了,偷一偷就不貼來,反正思路就是這樣的。
通過Mybatis 配置不同的 Mapper 使用不同的 SqlSessionTemplate
根據不同操作類(就是mapper),然后創建不同的SqlSessionTemplate ,這樣每個SqlSessionTemplate 就可以設置不同的數據源和掃描不同的mapper咯。聽起來是不是很簡單呢。不用管什么切面不切面的,不像上面那么麻煩咯。但是多數據源的通病還是在滴,那就是多數據源事物用起來不方便啦。
多數據源
還是用剛才的springboot項目吧,改動一下咯。pom文件啥的就不說咯,跟上面一樣的。然后我們看下配置文件,數據源還是一樣,兩個數據源一樣的配置,只不過這里沒有mybatis的配置咯。
application
server:port: 8080spring:application:name: dynamicdatasource:mall:url: jdbc:mysql://rm-wz9yy0528x91z1iqdco.mysql.rds.aliyuncs.com/luu_mall?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.cj.jdbc.Driverusername: root # 數據庫賬號password: root0319@ # 數據庫密碼type: com.alibaba.druid.pool.DruidDataSource # 設置類型為 DruidDataSourcemin-idle: 0 # 池中維護的最小空閑連接數,默認為 0 個。max-active: 20 # 池中最大連接數,包括閑置和使用中的連接,默認為 8 個。# 用戶數據源配置users:url: jdbc:mysql://rm-wz9yy0528x91z1iqdco.mysql.rds.aliyuncs.com/luu_user_center?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.cj.jdbc.Driverusername: root # 數據庫賬號password: root0319@ # 數據庫密碼type: com.alibaba.druid.pool.DruidDataSource # 設置類型為 DruidDataSource# Druid 自定義配置,對應 DruidDataSource 中的 setting 方法的屬性min-idle: 0 # 池中維護的最小空閑連接數,默認為 0 個。max-active: 20 # 池中最大連接數,包括閑置和使用中的連接,默認為 8 個。# Druid 自定義配置,對應 DruidDataSource 中的 setting 方法的屬性druid: # 設置 Druid 連接池的自定義配置。然后 DruidDataSourceAutoConfigure 會自動化配置 Druid 連接池。filter:stat: # 配置 StatFilter ,對應文檔 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilterlog-slow-sql: true # 開啟慢查詢記錄slow-sql-millis: 5000 # 慢 SQL 的標準,單位:毫秒merge-sql: true # SQL合并配置stat-view-servlet: # 配置 StatViewServlet ,對應文檔 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AEenabled: true # 是否開啟 StatViewServletlogin-username: root # 賬號login-password: root # 密碼DBConstants常量類
public class DBConstants {public static final String TX_MANAGER_MALL = "malltransactionManager";public static final String TX_MANAGER_SER = "usertransactionManager"; }然后就是創建不同的SqlSessionTemplate和數據源了。
DataSourceMallConfig配置mall的數據源,并且mallSqlSessionTemplate設置了掃面mapper包位置
@Configuration @MapperScan(basePackages = "com.luu.druid.druid_demo.mapper.mall", sqlSessionTemplateRef = "mallSqlSessionTemplate") public class DataSourceMallConfig {/*** 創建 mall 數據源*/@Bean(name = "mallDataSource")@ConfigurationProperties(prefix = "spring.datasource.mall")public DataSource mallDataSource() {return DruidDataSourceBuilder.create().build();}/*** 創建 MyBatis SqlSessionFactory*/@Bean(name = "mallSqlSessionFactory")public SqlSessionFactory sqlSessionFactory(DataSource mallDataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();// <2.1> 設置 orders 數據源bean.setDataSource(mallDataSource);// <2.2> 設置 entity 所在包bean.setTypeAliasesPackage("com.luu.druid.druid_demo.entity.*");// <2.3> 設置 config 路徑 // bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));// <2.4> 設置 mapper 路徑bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/mall/*.xml"));return bean.getObject();}/*** 創建 MyBatis SqlSessionTemplate*/@Bean(name = "mallSqlSessionTemplate")public SqlSessionTemplate sqlSessionTemplate(DataSource mallDataSource) throws Exception {return new SqlSessionTemplate(this.sqlSessionFactory(mallDataSource));}/*** 創建 mall 數據源的 TransactionManager 事務管理器*/@Bean(name = DBConstants.TX_MANAGER_MALL)public PlatformTransactionManager transactionManager(DataSource mallDataSource) {return new DataSourceTransactionManager(mallDataSource);}}DataSourceUserConfig配置user的數據源,并且userSqlSessionTemplate設置了掃面mapper包位置
@Configuration @MapperScan(basePackages = "com.luu.druid.druid_demo.mapper.user", sqlSessionTemplateRef = "userSqlSessionTemplate") public class DataSourceUserConfig {/*** 創建 user 數據源*/@Bean(name = "userDataSource")@ConfigurationProperties(prefix = "spring.datasource.users")public DataSource userDataSource() {return DruidDataSourceBuilder.create().build();}/*** 創建 MyBatis SqlSessionFactory*/@Bean(name = "userSqlSessionFactory")public SqlSessionFactory sqlSessionFactory(DataSource userDataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();// <2.1> 設置 orders 數據源bean.setDataSource(userDataSource);// <2.2> 設置 entity 所在包bean.setTypeAliasesPackage("com.luu.druid.druid_demo.entity.*");// <2.3> 設置 config 路徑 // bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));// <2.4> 設置 mapper 路徑bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/user/*.xml"));return bean.getObject();}/*** 創建 MyBatis SqlSessionTemplate*/@Bean(name = "userSqlSessionTemplate")public SqlSessionTemplate sqlSessionTemplate(DataSource userDataSource) throws Exception {return new SqlSessionTemplate(this.sqlSessionFactory(userDataSource));}/*** 創建 user 數據源的 TransactionManager 事務管理器*/@Bean(name = DBConstants.TX_MANAGER_SER)public PlatformTransactionManager transactionManager(DataSource userDataSource) {return new DataSourceTransactionManager(userDataSource);}}到這多數據源配置就差不多了,不同的mapper對應不同的數據源。這里mapper,entity啥的就不貼出來了,秉承著能偷懶就偷懶的一貫風格。直接把單元測試貼一下看下。
@SpringBootTest class DruidDemoApplicationTests {@AutowiredUserMapper userMapper;@AutowiredTestMapper testMapper;@Testvoid contextLoads() {String id = testMapper.mallNoAnno();String id2 = testMapper.mallExitAnno();String name = userMapper.userNoAnno();String name2 = userMapper.userExitAnno();} }當然,上面說到說,依然是多數據源,所以呢對于事物的支持依然是有問題的。
讀寫分離
這種方式實現讀寫分離,就不用多說咯吧,我這個專業bug制造者都想的明白,各位大佬也能想明白的。
分庫分表中間件,比如Sharding-JDBC 、Mycat等
對于分庫分表的中間件,會解析我們編寫的 SQL ,路由操作到對應的數據源。那么,它們天然就支持多數據源。如此,我們僅需配置好每個表對應的數據源,中間件就可以透明的實現多數據源或者讀寫分離。Sharding-JDBC 、Mycat是比較常用的中間件,這里使用的話就不寫了,后面會專門寫如何去使用它們的,Sharding-JDBC并且支持分布式事物的。
如果需要可以下載代碼試試的dynamic-datasource-spring、dynamic-datasource-mybatis,到此完美收工:
https://github.com/servef-toto/luu_yinchuishiting.git?github.com總結
以上是生活随笔為你收集整理的2 数据源配置_论多数据源(读写分离)的实现方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 当退出python时是否释放全部内存_p
- 下一篇: oracle命令行原理,Oracle命令