mysql: datasource: readSize: 1 #讀庫個數 type: com.alibaba.druid.pool.DruidDataSource write: url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false username: root password: 123123 driver-class-name: com.mysql.cj.jdbc.Driver minIdle: 5 maxActive: 100 initialSize: 10 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 50 removeAbandoned: true filters: stat read01: url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false username: root password: 123123 driver-class-name: com.mysql.cj.jdbc.Driver minIdle: 5 maxActive: 100 initialSize: 10 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 50 removeAbandoned: true filters: stat read02: url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false username: root password: 123123 driver-class-name: com.mysql.cj.jdbc.Driver minIdle: 5 maxActive: 100 initialSize: 10 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 50 removeAbandoned: true filters: stat
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public [@interface](https://my.oschina.net/u/996807) ReadDataSource { }
import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; /** * name:DataSourceConfiguration * <p></p> * @author:lipeng * @data:2018年6月27日 下午5:55:35 * @version 1.0 */ @Configuration public class DataSourceConfiguration { private static Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class); @Value("${mysql.datasource.type}") private Class<? extends DataSource> dataSourceType; /** * 寫庫 數據源配置 * @return */ @Bean(name = "writeDataSource") @Primary @ConfigurationProperties(prefix = "mysql.datasource.write") public DataSource writeDataSource() { log.info("-------------------- writeDataSource init ---------------------"); return DataSourceBuilder.create().type(dataSourceType).build(); } /** * 有多少個從庫就要配置多少個 * @return */ @Bean(name = "readDataSource01") @ConfigurationProperties(prefix = "mysql.datasource.read01") public DataSource readDataSourceOne() { log.info("-------------------- read01 DataSourceOne init ---------------------"); return DataSourceBuilder.create().type(dataSourceType).build(); } @Bean(name = "readDataSource02") @ConfigurationProperties(prefix = "mysql.datasource.read02") public DataSource readDataSourceTwo() { log.info("-------------------- read01 DataSourceOne init ---------------------"); return DataSourceBuilder.create().type(dataSourceType).build(); } @Bean("readDataSources") public List<DataSource> readDataSources(){ List<DataSource> dataSources=new ArrayList<>(); dataSources.add(readDataSourceOne()); dataSources.add(readDataSourceTwo()); return dataSources; } }
/** * name:DataSourceType * <p></p> * @author:lipeng * @data:2018年6月28日 上午9:25:44 * @version 1.0 */ public enum DataSourceType { read("read", "從庫"), write("write", "主庫"); private String type; private String name; DataSourceType(String type, String name) { this.type = type; this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
因爲涉及到事務處理,可能會遇到事務中同時用到讀庫和寫庫,可能會有延時形成髒讀,因此增長了線程變量設置,來保證一個事務內讀寫都是同一個庫java
/** * name:DataSourceContextHolder * <p></p> * @author:lipeng * @data:2018年6月27日 下午5:57:39 * @version 1.0 */ public class DataSourceContextHolder { private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class); //線程本地環境 private static final ThreadLocal<String> local = new ThreadLocal<String>(); public static ThreadLocal<String> getLocal() { return local; } /** * 讀庫 */ public static void setRead() { local.set(DataSourceType.read.getType()); log.info("數據庫切換到讀庫..."); } /** * 寫庫 */ public static void setWrite() { local.set(DataSourceType.write.getType()); log.info("數據庫切換到寫庫..."); } public static String getReadOrWrite() { return local.get(); } public static void clear(){ local.remove(); } }
若是在註解在service層而且聲明式事務也在service層,這個得保證攔截器優先級在聲明式事務前面mysql
/** * name:DataSourceAopInService * 在service層以爲數據源 * 必須在事務AOP以前執行,因此實現Ordered,order的值越小,越先執行 * 若是一旦開始切換到寫庫,則以後的讀都會走寫庫 * * @author:lipeng * @data:2018年6月27日 下午5:59:17 * @version 1.0 */ @Aspect @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true) @Component public class DataSourceAopInService implements PriorityOrdered{ private static Logger log = LoggerFactory.getLogger(DataSourceAopInService.class); @Before("@annotation(com.sangfor.quote.datasource.annotation.ReadDataSource) ") public void setReadDataSourceType() { //若是已經開啓寫事務了,那以後的全部讀都從寫庫讀 if(!DataSourceType.write.getType().equals(DataSourceContextHolder.getReadOrWrite())){ DataSourceContextHolder.setRead(); } } @Before("@annotation(com.sangfor.quote.datasource.annotation.WriteDataSource) ") public void setWriteDataSourceType() { DataSourceContextHolder.setWrite(); } @Override public int getOrder() { /** * 值越小,越優先執行 * 要優於事務的執行 * 在啓動類中加上了@EnableTransactionManagement(order = 10) */ return 1; } }
而且在啓動類或者配置類中增長註解order配置 @EnableTransactionManagement(order = 10)spring
mybatis配置sql
@Configuration @AutoConfigureAfter(DataSourceConfiguration.class) @MapperScan(basePackages = "com.sangfor.springboot") public class MybatisConfiguration { private static Logger log = LoggerFactory.getLogger(MybatisConfiguration.class); @Value("${mysql.datasource.readSize}") private String readDataSourceSize; @Autowired @Qualifier("writeDataSource") private DataSource writeDataSource; @Autowired @Qualifier("readDataSources") private List<DataSource> readDataSources; @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy()); sqlSessionFactoryBean.setTypeAliasesPackage("com.sangfor.quote.model"); //設置mapper.xml文件所在位置 Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"); sqlSessionFactoryBean.setMapperLocations(resources); sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); return sqlSessionFactoryBean.getObject(); } /** * 有多少個數據源就要配置多少個bean * * @return */ @Bean public AbstractRoutingDataSource roundRobinDataSouceProxy() { int size = Integer.parseInt(readDataSourceSize); MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size); Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); // DataSource writeDataSource = SpringContextHolder.getBean("writeDataSource"); // 寫 targetDataSources.put(DataSourceType.write.getType(), writeDataSource); // targetDataSources.put(DataSourceType.read.getType(),readDataSource); // 多個讀數據庫時 for (int i = 0; i < size; i++) { targetDataSources.put(i, readDataSources.get(i)); } proxy.setDefaultTargetDataSource(writeDataSource); proxy.setTargetDataSources(targetDataSources); return proxy; } }
多數據源切換數據庫
/** * 多數據源切換 * name:MyAbstractRoutingDataSource * <p></p> * @author:lipeng * @data:2018年6月27日 下午6:57:34 * @version 1.0 */ public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource { private final int dataSourceNumber; private AtomicInteger count = new AtomicInteger(0); public MyAbstractRoutingDataSource(int dataSourceNumber) { this.dataSourceNumber = dataSourceNumber; } @Override protected Object determineCurrentLookupKey() { String typeKey = DataSourceContextHolder.getReadOrWrite(); if(StringUtils.isBlank(typeKey)||typeKey.equals(DataSourceType.write.getType())) { return DataSourceType.write.getType(); } // 讀 簡單負載均衡 int number = count.getAndAdd(1); int lookupKey = number % dataSourceNumber; return new Integer(lookupKey); } }
@Configuration @EnableTransactionManagement(order = 10) @Slf4j @AutoConfigureAfter({ MybatisConfiguration.class }) public class TransactionConfiguration extends DataSourceTransactionManagerAutoConfiguration { @Bean @Autowired public DataSourceTransactionManager transactionManager(MyAbstractRoutingDataSource roundRobinDataSouceProxy) { log.info("事物配置"); return new DataSourceTransactionManager(roundRobinDataSouceProxy); } }