赞
踩
现如今复杂的业务系统,一般会将数据库按业务拆开,比如产品系统的数据库放在product db中,订单系统的数据库放在order db中...,然后,如果量大了,可能每个库还要考虑做读、写分离,以进一步提高系统性能,下面就来看看如何处理:
核心思路:配置多个数据源,然后利用RoutingDataSource结合AOP来动态切不同的库。
存在一下几个问题:
1、配置文件中,多数据源的配置节点如何设计?
- #yml文件中进行如下配置
- druid:
- type: com.alibaba.druid.pool.DruidDataSource
- study:
- master: #study库的主库名
- url: jdbc:mysql://localhost:3306/study?useSSL=false&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: 1234 #加密很好
- initial-size: 5
- min-idle: 1
- max-active: 20
- test-on-borrow: true
- slave: #study库的从库名
- url: jdbc:mysql://localhost:3306/study_slave?useSSL=false&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: A1b2c3@def.com
- initial-size: 5
- min-idle: 1
- max-active: 20
- test-on-borrow: true
- product:
- master: #product库的主库名
- url: jdbc:mysql://localhost:3306/product?useSSL=false&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: 1234 #加密
- initial-size: 5
- min-idle: 1
- max-active: 20
- test-on-borrow: true
- slave: #product库的从库
- url: jdbc:mysql://localhost:3306/product_slave?useSSL=false&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: 1234
- initial-size: 5
- min-idle: 1
- max-active: 20
- test-on-borrow: true
上面的配置写法供参数,如果slave节点数要扩展,按这个格式,改造成slave1,slave2... 自行扩展。
- package com.aspire.sh.db.config;
-
- import com.aspire.sh.db.datasource.DbContextHolder;
- import com.aspire.sh.db.datasource.MasterSlaveRoutingDataSource;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.Primary;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- import org.springframework.transaction.annotation.EnableTransactionManagement;
-
- import javax.sql.DataSource;
- import java.util.HashMap;
- import java.util.Map;
-
-
- @Configuration
- @EnableTransactionManagement
- public class DataSourceConfiguration {
-
- @Value("${druid.type}")
- private Class<? extends DataSource> dataSourceType;
-
- @Bean(name = "studyMasterDataSource")
- @ConfigurationProperties(prefix = "druid.study.master")
- public DataSource studyMasterDataSource() {
- return DataSourceBuilder.create().type(dataSourceType).build();
- }
-
- @Bean(name = "studySlaveDataSource")
- @ConfigurationProperties(prefix = "druid.study.slave")
- public DataSource studySlaveDataSource1() {
- return DataSourceBuilder.create().type(dataSourceType).build();
- }
-
- @Bean(name = "productMasterDataSource")
- @ConfigurationProperties(prefix = "druid.product.master")
- public DataSource productMasterDataSource() {
- return DataSourceBuilder.create().type(dataSourceType).build();
- }
-
- @Bean(name = "productSlaveDataSource")
- @ConfigurationProperties(prefix = "druid.product.slave")
- public DataSource productSlaveDataSource1() {
- return DataSourceBuilder.create().type(dataSourceType).build();
- }
-
- @Bean(name = "dataSource")
- @Primary
- public AbstractRoutingDataSource dataSource() {
- MasterSlaveRoutingDataSource proxy = new MasterSlaveRoutingDataSource();
- Map<Object, Object> targetDataResources = new HashMap<>();
- targetDataResources.put(DbContextHolder.DbType.PRODUCT_MASTER, productMasterDataSource());
- targetDataResources.put(DbContextHolder.DbType.PRODUCT_SLAVE, productSlaveDataSource1());
- targetDataResources.put(DbContextHolder.DbType.STUDY_MASTER, studyMasterDataSource());
- targetDataResources.put(DbContextHolder.DbType.STUDY_SLAVE, studySlaveDataSource1());
- proxy.setDefaultTargetDataSource(productMasterDataSource());
- proxy.setTargetDataSources(targetDataResources);
- proxy.afterPropertiesSet();
- return proxy;
- }
-
- }
注:@Primary一定要在动态数据源上,否则事务回滚无效!
3、根据什么来切换db?
a、用约定的方法前缀,比如:get/query/list开头的约定为读从库,其它为主库,但是这样还要考虑不同业务库的切换(即:何时切换到product库,何时切换到order库,可以再用不同的Scanner来处理,略复杂)
b、用自定义注解来处理,比如 @ProductMaster注解,表示切换到product的master库,这样同时把业务库,以及主还是从,一次性解决了,推荐这种。
这里,我定义了4个注解,代表product,study二个库的主及从。
4、aop在哪里拦截,如何拦截?
service层和mapper层都可以拦截,推荐在服务层拦截,否则如果一个业务方法里,即有读又有写,还得考虑如果遇到事务,要考虑的东西更多。
当然,如果拦截特定的注解,就不用过多考虑在哪个层,只认注解就行(当然,注解还是建议打在服务层上)。
dubbo-starter的一个小坑:spring boot中,只有managed bean才能用aop拦截,而dubbo-starter中的@service注解不是spring中的注解(是阿里package下的自定义注解),生成的service provider实例,aop拦截不到,解决办法,再加一个注解让spring认识它.
Aop拦截类的参考代码如下:
- package com.aspire.sh.db.aspect;
-
- import com.aspire.sh.db.annotation.ProductMaster;
- import com.aspire.sh.db.annotation.ProductSlave;
- import com.aspire.sh.db.annotation.StudyMaster;
- import com.aspire.sh.db.annotation.StudySlave;
- import com.aspire.sh.db.datasource.DbContextHolder;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.core.Ordered;
- import org.springframework.stereotype.Component;
-
-
- @Aspect
- @Component
- public class MasterSlaveAspect implements Ordered {
-
- public static final Logger logger = LoggerFactory.getLogger(MasterSlaveAspect.class);
-
-
- /**
- * 切换到product主库
- *
- * @param proceedingJoinPoint
- * @param productMaster
- * @return
- * @throws Throwable
- */
- @Around("@annotation(productMaster)")
- public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ProductMaster productMaster) throws Throwable {
- try {
- logger.info("set database connection to product-master only");
- DbContextHolder.setDbType(DbContextHolder.DbType.PRODUCT_MASTER);
- Object result = proceedingJoinPoint.proceed();
- return result;
- } finally {
- DbContextHolder.clearDbType();
- logger.info("restore database connection");
- }
- }
-
-
- /**
- * 切换到product从库
- *
- * @param proceedingJoinPoint
- * @param productSlave
- * @return
- * @throws Throwable
- */
- @Around("@annotation(productSlave)")
- public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ProductSlave productSlave) throws Throwable {
- try {
- logger.info("set database connection to product-slave only");
- DbContextHolder.setDbType(DbContextHolder.DbType.PRODUCT_SLAVE);
- Object result = proceedingJoinPoint.proceed();
- return result;
- } finally {
- DbContextHolder.clearDbType();
- logger.info("restore database connection");
- }
- }
-
- /**
- * 切换到study主库
- *
- * @param proceedingJoinPoint
- * @param studyMaster
- * @return
- * @throws Throwable
- */
- @Around("@annotation(studyMaster)")
- public Object proceed(ProceedingJoinPoint proceedingJoinPoint, StudyMaster studyMaster) throws Throwable {
- try {
- logger.info("set database connection to study-master only");
- DbContextHolder.setDbType(DbContextHolder.DbType.STUDY_MASTER);
- Object result = proceedingJoinPoint.proceed();
- return result;
- } finally {
- DbContextHolder.clearDbType();
- logger.info("restore database connection");
- }
- }
-
- /**
- * 切换到study从库
- *
- * @param proceedingJoinPoint
- * @param studySlave
- * @return
- * @throws Throwable
- */
- @Around("@annotation(studySlave)")
- public Object proceed(ProceedingJoinPoint proceedingJoinPoint, StudySlave studySlave) throws Throwable {
- try {
- logger.info("set database connection to study-slave only");
- DbContextHolder.setDbType(DbContextHolder.DbType.STUDY_SLAVE);
- Object result = proceedingJoinPoint.proceed();
- return result;
- } finally {
- DbContextHolder.clearDbType();
- logger.info("restore database connection");
- }
- }
-
- @Override
- public int getOrder() {
- return 0;
- }
- }
5、其它事项
启用类上,一定要排除spring-boot自带的datasource配置,即:
- @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
- @EnableAspectJAutoProxy
- @ComponentScan("com.aspire.sh")
- @MapperScan(basePackages = "com.aspire.sh.dao.mapper")
- public class ServiceProvider {
- public static void main(String[] args) {
- SpringApplication.run(ServiceProvider.class, args);
- }
- }
6、日志中如何输出格式化且带参数值的sql?
是不是更友好!
方法:加一个mybtais的拦截器即可 (代码块在最前面)
这里面还用了hibernate的一个小工具,用于格式化sql(代码块在最前面)
接下来,把这个拦截器配置在mybatis-config.xml里
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-config.dtd">
- <configuration>
- <settings>
- <setting name="cacheEnabled" value="true"/>
- </settings>
-
- <plugins>
- <plugin interceptor="com.aspire.sh.db.interceptor.MybatisInterceptor">
- </plugin>
- </plugins>
-
- </configuration>
最后在application.yml里指定mybatis-config.xml所在的路径:
注意:包路径可能会有些差异com.cnblogs.yjmyzz与com.aspire.sh二者取其一
实现方式二:
在pom.xml中增加相关依赖:
- <!-- aop -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context-support</artifactId>
- </dependency>
- <!-- mysql -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-jdbc</artifactId>
- </dependency>
- <!-- druid -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid-spring-boot-starter</artifactId>
- <version>1.1.10</version>
- </dependency>
- <!--如果不添加此依赖,自定义druid属性则会绑定失败-->
- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.2.17</version>
- </dependency>
- <!-- mybatis -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- </dependency>
- <!-- slf4j -->
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </dependency>
2、配置application.yml
- server:
- port: 8818
- spring:
- application:
- name: read-write-separationp
- aop:
- proxy-target-class: true
- auto: true
- datasource:
- type: com.alibaba.druid.pool.DruidDataSourceC3P0Adapter
- druid:
- master:
- url: jdbc:mysql://207.148.33.32:3306/captain?useSSL=true&characterEncoding=UTF-8&serverTimezone=UTC
- username: captainLii
- password: Captain@1689
- slave:
- url: jdbc:mysql://45.32.120.84:3306/captain?useSSL=true&characterEncoding=UTF-8&serverTimezone=UTC
- username: captainLii
- password: Captain@1689
- # 配置初始化大小(默认0)、最小、最大(默认8)
- initial-size: 1
- min-idle: 1
- max-active: 20
- # 配置获取连接等待超时的时间
- max-wait: 60000
- # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大。 默认为false
- pool-prepared-statements: true
- # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
- max-open-prepared-statements: 20
- # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
- time-between-eviction-runs-millis: 60000
- # 配置一个连接在池中最小和最大生存的时间,单位是毫秒
- min-evictable-idle-time-millis: 300000
- max-evictable-idle-time-millis: 900000
- # 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。
- # 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
- validation-query: SELECT 'X'
- # 申请连接时执行validationQuery检测连接是否有效 默认为true
- test-on-borrow: true
- # 归还连接时执行validationQuery检测连接是否有效 默认为false
- test-on-return: false
- # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
- test-while-idle: true
-
- # Mybatis
- mybatis:
- mapper-locations: classpath:mapping/*.xml
- type-aliases-package: com.captain.readwriteseparation.entity
3、 定义数据源枚举类
- package com.captain.readwriteseparation.dbconfig;
-
- /**
- * @author
- * @description 数据源枚举
- */
- public enum DataSourceTypeEnum {
- master("master"), slave("slave");
- private String value;
-
- DataSourceTypeEnum(String value) {
- this.value = value;
- }
-
- public String getValue() {
- return value;
- }
- }
4、设置获取数据源
- package com.aspire.sh.dbconfig;
-
- /**
- * @description 设置获取数据源
- */
- public class DataSourceHolder {
- private static final ThreadLocal contextHolder = new ThreadLocal<>();
-
- /**
- * 设置数据源
- *
- * @param dbTypeEnum
- */
- public static void setDbType(DataSourceTypeEnum dbTypeEnum) {
- contextHolder.set(dbTypeEnum.getValue());
- }
-
- /**
- * 取得当前数据源
- *
- * @return
- */
- public static String getDbType() {
- return (String) contextHolder.get();
- }
-
- /**
- * 清除上下文数据
- */
- public static void clearDbType() {
- contextHolder.remove();
- }
- }
5、数据源切换(切入点和切面)
- package com.captain.readwriteseparation.dbconfig;
-
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Component;
-
- /**
- * @author splendor.s
- * @description 数据源切换(切入点和切面)
- */
- @Aspect
- @Component
- public class DataSourceAop {
- static Logger logger = LoggerFactory.getLogger(DataSourceAop.class);
-
- @Before("execution(* com.captain.readwriteseparation.mapper.*.insert*(..)) || execution(* com.captain.readwriteseparation.mapper.*.update*(..)) || execution(* com.captain.readwriteseparation.mapper.*.delete*(..))")
- public void setWriteDataSourceType() {
- DataSourceHolder.setDbType(DataSourceTypeEnum.master);
- logger.info("change -------- write ------------");
- }
-
- @Before("execution(* com.captain.readwriteseparation.mapper.*.select*(..)) || execution(* com.captain.readwriteseparation.mapper.*.count*(..))")
- public void setReadDataSourceType() {
- DataSourceHolder.setDbType(DataSourceTypeEnum.slave);
- logger.info("change -------- read ------------");
- }
-
- }
- package com.captain.readwriteseparation.dbconfig;
-
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
-
- /**
- * @authorsplendor.s
- * @description 动态数据源决策
- */
- public class DynamicDataSource extends AbstractRoutingDataSource {
- @Override
- protected Object determineCurrentLookupKey() {
- return DataSourceHolder.getDbType();
- }
- }
- package com.aspire.sh.dbconfig;
-
- import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
- import com.alibaba.druid.support.http.StatViewServlet;
- import com.alibaba.druid.support.http.WebStatFilter;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.boot.web.servlet.FilterRegistrationBean;
- import org.springframework.boot.web.servlet.ServletRegistrationBean;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.Primary;
-
- import javax.sql.DataSource;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * @author splendor.s
- * @description 数据库(源)配置
- */
- @Configuration
- public class DruidDataSourceConfig {
- static Logger logger = LoggerFactory.getLogger(DruidDataSourceConfig.class);
-
- @Value("${spring.datasource.type}")
- private Class<? extends DataSource> dataSourceType;
-
- @Bean
- public ServletRegistrationBean staViewServlet() {
- ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
- Map<String, String> initParams = new HashMap<>();
- //设置servlet初始化参数
- initParams.put("loginUsername", "admin");//登陆名
- initParams.put("loginPassword", "123456");//密码
- initParams.put("allow", "");//默认就是允许所有访问
- initParams.put("deny", "192.168.10.17");//拒绝相对应的id访问
- //加载到容器中
- bean.setInitParameters(initParams);
- return bean;
- }
-
- @Bean
- public FilterRegistrationBean filterRegistrationBean() {
- FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
- filterRegistrationBean.addUrlPatterns("/*");
- filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico," + "/druid/*");
- return filterRegistrationBean;
- }
-
- @Bean(name = "master")
- @ConfigurationProperties(prefix = "spring.datasource.druid.master")
- public DataSource master() {
- logger.info("-------------------- master init ---------------------");
- return DruidDataSourceBuilder.create().build();
- }
-
- @Bean(name = "slave")
- @ConfigurationProperties(prefix = "spring.datasource.druid.slave")
- public DataSource slaveOne() {
- logger.info("-------------------- slave init ---------------------");
- return DruidDataSourceBuilder.create().build();
- }
-
- // slave 多个时,可进行负载(另行处理)
-
- @Bean
- @Primary
- public DataSource multipleDataSource(@Qualifier("master") DataSource master,
- @Qualifier("slave") DataSource slave) {
- DynamicDataSource dynamicDataSource = new DynamicDataSource();
- Map<Object, Object> targetDataSources = new HashMap<>();
- targetDataSources.put(DataSourceTypeEnum.master.getValue(), master);
- targetDataSources.put(DataSourceTypeEnum.slave.getValue(), slave);
- dynamicDataSource.setTargetDataSources(targetDataSources);
- dynamicDataSource.setDefaultTargetDataSource(slave);
- return dynamicDataSource;
- }
-
- }
- package com.aspire.sh.dbconfig;
-
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.transaction.annotation.EnableTransactionManagement;
- import javax.sql.DataSource;
- import javax.annotation.Resource;
-
- /**
- * @author splendor.s
- * @description 事务控制
- */
- @Configuration
- @EnableTransactionManagement
- public class DataSourceTransactionManager extends DataSourceTransactionManagerAutoConfiguration {
-
- static Logger logger = LoggerFactory.getLogger(DataSourceTransactionManager.class);
-
- @Resource(name = "master")
- private DataSource dataSource;
-
- /**
- * 自定义事务
- * MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
- *
- * @return
- */
- @Bean(name = "transactionManager")
- public org.springframework.jdbc.datasource.DataSourceTransactionManager transactionManagers() {
- logger.info("-------------------- transactionManager init ---------------------");
- return new org.springframework.jdbc.datasource.DataSourceTransactionManager(dataSource);
- }
- }
实现方式三:
这里配置了4个数据源,1个master,2两个slave,1个路由数据源。前3个数据源都是为了生成第4个数据源,而且后续我们只用这最后一个路由数据源。
-
- /**
- * 关于数据源配置,参考SpringBoot官方文档第79章《Data Access》
- * 79. Data Access
- * 79.1 Configure a Custom DataSource
- * 79.2 Configure Two DataSources
- * 这里配置了4个数据源,1个master,2两个slave,1个路由数据源。前3个数据源都是为了生成第4个数据源,而且后续我们只用这最后一个路由数据源。
- */
-
- @Configuration
- public class DataSourceConfig {
-
- @Bean
- @ConfigurationProperties("spring.datasource.master")
- public DataSource masterDataSource() {
- return DataSourceBuilder.create().build();
- }
-
- @Bean
- @ConfigurationProperties("spring.datasource.slave1")
- public DataSource slave1DataSource() {
- return DataSourceBuilder.create().build();
- }
-
- @Bean
- @ConfigurationProperties("spring.datasource.slave2")
- public DataSource slave2DataSource() {
- return DataSourceBuilder.create().build();
- }
-
- @Bean
- public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
- @Qualifier("slave1DataSource") DataSource slave1DataSource,
- @Qualifier("slave2DataSource") DataSource slave2DataSource) {
- Map<Object, Object> targetDataSources = new HashMap<>();
- targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
- targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
- targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
- MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
- myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
- myRoutingDataSource.setTargetDataSources(targetDataSources);
- return myRoutingDataSource;
- }
-
- }
配置Mybatis指定数据源
- /**
- * 由于Spring容器中现在有4个数据源,所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。
- */
- @EnableTransactionManagement
- @Configuration
- public class MyBatisConfig {
-
- @Resource(name = "myRoutingDataSource")
- private DataSource myRoutingDataSource;
-
- @Bean
- public SqlSessionFactory sqlSessionFactory() throws Exception {
- SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
- sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
- sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
- return sqlSessionFactoryBean.getObject();
- }
-
- @Bean
- public PlatformTransactionManager platformTransactionManager() {
- return new DataSourceTransactionManager(myRoutingDataSource);
- }
- }
定义一个枚举类来代表这三个数据源
-
-
- /**
- * 定义一个枚举来代表这三个数据源
- */
- public enum DBTypeEnum {
- MASTER, SLAVE1, SLAVE2;
- }
通过ThreadLocal
将数据源绑定到每个线程上下文中
- /**
- * 通过ThreadLocal将数据源设置到每个线程上下文中
- */
- public class DBContextHolder {
-
- private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
-
- private static final AtomicInteger counter = new AtomicInteger(-1);
-
- public static void set(DBTypeEnum dbType) {
- contextHolder.set(dbType);
- }
-
- public static DBTypeEnum get() {
- return contextHolder.get();
- }
-
- public static void master() {
- set(DBTypeEnum.MASTER);
- System.out.println("切换到master");
- }
-
- public static void slave() {
- // 轮询
- int index = counter.getAndIncrement() % 2;
- if (counter.get() > 9999) {
- counter.set(-1);
- }
- if (index == 0) {
- set(DBTypeEnum.SLAVE1);
- System.out.println("切换到slave1");
- }else {
- set(DBTypeEnum.SLAVE2);
- System.out.println("切换到slave2");
- }
- }
-
- }
通过Aop的前置通知来设置要使用的路由key(数据源)
- /**
- * 默认情况下,所有的查询都走从库,插入/修改/删除走主库。我们通过方法名来区分操作类型(CRUD)
- *
- */
- @Aspect
- @Component
- public class DataSourceAop {
-
- @Pointcut("!@annotation(huaxin.annotation.Master) " +
- "&& (execution(* huaxin.service.*.select*(..)) " +
- "|| execution(* huaxin.service..*.find*(..)))")
- public void readPointcut() {
-
- }
-
- @Pointcut("@annotation(huaxin.annotation.Master) " +
- "|| execution(* huaxin.service..*.save*(..)) " +
- "|| execution(* huaxin.service..*.add*(..)) " +
- "|| execution(* huaxin.service..*.update*(..)) " +
- "|| execution(* huaxin.service..*.edit*(..)) " +
- "|| execution(* huaxin..*.delete*(..)) " +
- "|| execution(* huaxin..*.remove*(..))")
- public void writePointcut() {
-
- }
-
- @Before("readPointcut()")
- public void read() {
- DBContextHolder.slave();
- }
-
- @Before("writePointcut()")
- public void write() {
- DBContextHolder.master();
- }
-
-
- /**
- * 另一种写法:if...else... 判断哪些需要读从数据库,其余的走主数据库
- */
- // @Before("execution(* com.cjs.example.service.impl.*.*(..))")
- // public void before(JoinPoint jp) {
- // String methodName = jp.getSignature().getName();
- //
- // if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
- // DBContextHolder.slave();
- // }else {
- // DBContextHolder.master();
- // }
- // }
- }
获取当前线程上绑定的路由key
- /**
- * 获取路由key
- */
- public class MyRoutingDataSource extends AbstractRoutingDataSource {
- @Nullable
- @Override
- protected Object determineCurrentLookupKey() {
- return DBContextHolder.get();
- }
- }
特殊情况下我们需要强制读主库,针对这种情况,我们定义一个注解,用该注解标注的就读主库
public @interface Master {}
给查询所有添加@Master注解
启动入口加上@MapperScan("xx.xx")
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。