データベースの読み取りと書き込みの分離とサブデータベースのサブテーブルの設計を研究したいので、マスタースレーブ設計を実現するために、springboot + druid + mybatis + aopのセットを作成しました。
最初のステップ:まず、あなたは、データ・ソースの設定項目、springbootのデフォルトの解像度が付けられspring.datasourceをカスタマイズする必要があります。次の設定項目を、順番にない紛争に直接データソースを定義します。私たちの接頭辞として、
@ConfigurationProperties(接頭辞= " datasource.write「)は、負荷の設定項目に使用することができ接頭辞を指定し、それは非常に便利です
ドルイドの使用は、型データソースを指定するために必要な時間を生成する必要があるため。
DataSourceBuilder.create().type(dataSourceType).build()
READSIZEは、ライブラリデータソースから番号を設定することですどのように多くのライブラリーから、ライブラリーのサイズを定義するために使用される
負荷分散プールから、主にMyAbstractRoutingDataSourceこのクラス:ステップ2
springboot、MyBatisの棚パッケージMybatisAutoConfigurationを書いてから、第三の工程を、クラスはSqlSessionFactoryメソッドを作成し、その中のデータソースをカスタムのAbstractRoutingDataSourceの
4番目のステップで置き換えます。カスタムトランザクションMyDataSourceTransactionManagerAutoConfiguration
完全なコードと単体テスト:
github:https : //github.com/ggj2010/javabase.git
メインラックパッケージ
<!-- jdbc driver begin-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- jdbc driver end-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
カスタムデータソース構成項目:
#多数据源 1主2从
datasource:
#从库数量
readSize: 2
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
#主库
write:
url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQueryTimeout: 900000
validationQuery: SELECT SYSDATE() from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
read1:
url: jdbc:mysql://localhost:3306/slave1?useUnicode=true&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQueryTimeout: 900000
validationQuery: SELECT SYSDATE() from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
read2:
url: jdbc:mysql://localhost:3306/slave2?useUnicode=true&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQueryTimeout: 900000
validationQuery: SELECT SYSDATE() from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
構成項目の解析:
@Configuration
@Slf4j
public class DataSourceConfiguration {
@Value("${datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Bean(name = "writeDataSource")
@Primary
@ConfigurationProperties(prefix = "datasource.write")
public DataSource writeDataSource() {
log.info("-------------------- writeDataSource init ---------------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* 有多少个从库就要配置多少个
* @return
*/
@Bean(name = "readDataSource1")
@ConfigurationProperties(prefix = "datasource.read1")
public DataSource readDataSourceOne() {
log.info("-------------------- readDataSourceOne init ---------------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "readDataSource2")
@ConfigurationProperties(prefix = "datasource.read2")
public DataSource readDataSourceTwo() {
log.info("-------------------- readDataSourceTwo init ---------------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
}
SqlSessionFactoryを書き換える
@Configuration
@AutoConfigureAfter({ DataSourceConfiguration.class })
@Slf4j
public class MybatisConfiguration extends MybatisAutoConfiguration {
@Value("${datasource.readSize}")
private String dataSourceSize;
@Bean
public SqlSessionFactory sqlSessionFactorys() throws Exception {
log.info("-------------------- 重载父类 sqlSessionFactory init ---------------------");
return super.sqlSessionFactory(roundRobinDataSouceProxy());
}
/**
* 有多少个数据源就要配置多少个bean
* @return
*/
@Bean
public AbstractRoutingDataSource roundRobinDataSouceProxy() {
int size = Integer.parseInt(dataSourceSize);
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size);
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
DataSource writeDataSource = SpringContextHolder.getBean("writeDataSource");
// 写
targetDataSources.put(DataSourceType.write.getType(), SpringContextHolder.getBean("writeDataSource"));
for (int i = 0; i < size; i++) {
targetDataSources.put(i, SpringContextHolder.getBean("readDataSource" + (i + 1)));
}
proxy.setDefaultTargetDataSource(writeDataSource);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
}
ローカルスレッドのグローバル変数
public class DataSourceContextHolder {
private static final ThreadLocal<String> local = new ThreadLocal<String>();
public static ThreadLocal<String> getLocal() {
return local;
}
/**
* 读可能是多个库
*/
public static void read() {
local.set(DataSourceType.read.getType());
}
/**
* 写只有一个库
*/
public static void write() {
local.set(DataSourceType.write.getType());
}
public static String getJdbcType() {
return local.get();
}
}
マルチデータソースの切り替え
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.getJdbcType();
if (typeKey.equals(DataSourceType.write.getType()))
return DataSourceType.write.getType();
// 读 简单负载均衡
int number = count.getAndAdd(1);
int lookupKey = number % dataSourceNumber;
return new Integer(lookupKey);
}
}
列挙型
public enum DataSourceType {
read("read", "从库"), write("write", "主库");
@Getter
private String type;
@Getter
private String name;
DataSourceType(String type, String name) {
this.type = type;
this.name = name;
}
}
aopインターセプトセットのローカルスレッド変数
@Aspect
@Component
@Slf4j
public class DataSourceAop {
@Before("execution(* com.ggj.encrypt.modules.*.dao..*.find*(..)) or execution(* com.ggj.encrypt.modules.*.dao..*.get*(..))")
public void setReadDataSourceType() {
DataSourceContextHolder.read();
log.info("dataSource切换到:Read");
}
@Before("execution(* com.ggj.encrypt.modules.*.dao..*.insert*(..)) or execution(* com.ggj.encrypt.modules.*.dao..*.update*(..))")
public void setWriteDataSourceType() {
DataSourceContextHolder.write();
log.info("dataSource切换到:write");
}
}
カスタムトランザクション
@Configuration
@EnableTransactionManagement
@Slf4j
public class MyDataSourceTransactionManagerAutoConfiguration extends DataSourceTransactionManagerAutoConfiguration {
/**
* 自定义事务
* MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
* @return
*/
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManagers() {
log.info("-------------------- transactionManager init ---------------------");
return new DataSourceTransactionManager(SpringContextHolder.getBean("roundRobinDataSouceProxy"));
}
}