更新時間:2022-04-06 09:58:38 來源:動力節(jié)點 瀏覽3142次
MyBatis讀寫分離是什么?對于初學(xué)者來說可能還不是很了解,下面動力節(jié)點小編來告訴大家。
ShardingSphere由JDBC、Proxy和Sidecar組成(規(guī)劃中),可以獨立部署,支持混合部署。ShardingSphere Proxy 與 MyCat 定位相同,而 ShardingSphere JDBC 在 Java 的 JDBC 層提供了額外的服務(wù)。
SpringBoot 集成 shardingsphere JDBC 也非常方便。引入包和編寫配置文件后即可使用。但是事務(wù)中有個小問題,就是事務(wù)中的寫操作之后,后面的讀操作都是從主庫中讀取的;也就是說,在寫操作之前,事務(wù)中的讀仍然是從庫中讀取的,可能會造成臟寫。
大部分代碼層面的讀寫分離都是通過判斷sql的讀寫類型來攔截sql并重定向數(shù)據(jù)庫。Shardingsphere JDBC 也不例外。
Mybatis 允許我們自定義 Interceptor。我們需要實現(xiàn)Interceptor接口,在自定義的Interceptor類上添加@Intercepts注解。在@Intercepts注解中,我們可以指定攔截方式。
由于讀寫分離是在代碼層面進(jìn)行的,所以必須有讀寫庫。這里使用了多數(shù)據(jù)源功能。不用mybatis/mybatis plus默認(rèn)的多數(shù)據(jù)源生成方式,多數(shù)據(jù)源自己配置。其實也可以使用默認(rèn)的生成方式。自己寫的目的是為了更好的理解里面的原理?!九渲梦募信渲玫母袷绞歉鶕?jù)mybatis配置的格式加上多個數(shù)據(jù)源來配置的】
代碼
多數(shù)據(jù)源配置
/**
* 主數(shù)據(jù)庫
*/
@ConfigurationProperties("spring.datasource.dynamic.datasource.master")
公共數(shù)據(jù)源 masterDataSource(){
log.info("加載主數(shù)據(jù)源主數(shù)據(jù)源。");
返回 DruidDataSourceBuilder.create().build();
}
/**
* 數(shù)據(jù)庫從庫
*/
@ConfigurationProperties("spring.datasource.dynamic.datasource.slave1")
公共數(shù)據(jù)源 slave1DataSource(){
log.info("從數(shù)據(jù)源 slave1 DataSource 加載。");
返回 DruidDataSourceBuilder.create().build();
}
/**
* 動態(tài)數(shù)據(jù)源
*/
@豆角,扁豆
公共數(shù)據(jù)源 myRoutingDataSource(@Qualifier("masterDataSource") 數(shù)據(jù)源 masterDataSource,
@Qualifier("slave1DataSource") 數(shù)據(jù)源 slave1DataSource) {
log.info("load[masterDataSource-slave1DataSource]設(shè)置為動態(tài)數(shù)據(jù)源DynamicDataSource。");
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
動態(tài)數(shù)據(jù)源 dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
動態(tài)數(shù)據(jù)源.setTargetDataSources(targetDataSources);
返回動態(tài)數(shù)據(jù)源;
}
DBTypeEnum
public enum DBTypeEnum {
/**Main library*/
MASTER,
/**From library 1*/
SLAVE1
}
動態(tài)數(shù)據(jù)源
此處指定數(shù)據(jù)源的鍵。在每條sql語句執(zhí)行之前,都會執(zhí)行determineCurrentLookupKey獲取數(shù)據(jù)源。DbContextHolder.get()是獲取當(dāng)前線程中指定數(shù)據(jù)源的key,會在自定義攔截器中指定。
公共類 DynamicDataSource 擴(kuò)展 AbstractRoutingDataSource {
@Nullable @Override
受保護(hù)對象 determineCurrentLookupKey() {
return DbContextHolder.get();
}
}
公共類 DbContextHolder {
私有靜態(tài)最終 ThreadLocal<DBTypeEnum> CONTEXT_HOLDER = new ThreadLocal<>();
私有靜態(tài)最終 AtomicInteger COUNTER = new AtomicInteger(-1);
public static void set(DBTypeEnum dbType) {
log.debug("切換到{}", dbType.name());
CONTEXT_HOLDER.set(dbType);
}
公共靜態(tài) DBTypeEnum get() {
return CONTEXT_HOLDER.get();
}
公共靜態(tài) DBTypeEnum getMaster() {
return DBTypeEnum.MASTER;
}
public static DBTypeEnum getSlave() {
// 可以輪詢多個從庫
int index = COUNTER.getAndIncrement() % 2;
if (COUNTER.get() > 9999) {
COUNTER.set(-1);
}
返回 DBTypeEnum.SLAVE1;
}
}
在上一步中,我們定義了多個數(shù)據(jù)源并設(shè)置了數(shù)據(jù)源選擇的基礎(chǔ)(DbContextHolder.get())。這一步就是按照一定的規(guī)則在攔截器中設(shè)置這個基礎(chǔ)。
代碼
攔截器
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }),
@Signature(type = Executor.class, method = "close", args = {boolean.class})
})
public class DbSelectorInterceptor implements Interceptor {
private靜態(tài)最終字符串 REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
private static final Map<String, DBTypeEnum> CACHE_MAP = new ConcurrentHashMap<>();
@覆蓋
公共對象攔截(調(diào)用調(diào)用)拋出 Throwable {
String methodName = invocation.getMethod().getName();
字符串 closeMethodName = "關(guān)閉";
布爾同步活動 = TransactionSynchronizationManager.isSynchronizationActive();
DBTypeEnum 數(shù)據(jù)庫類型 = null;
if(!synchronizationActive && !closeMethodName.equals(methodName)) {
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) 對象[0];
if((databaseType = CACHE_MAP.get(ms.getId())) == null) {
//讀取方法
if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
//! selectKey是自增ID查詢主鍵(SELECT LAST_INSERT_ID())方法,使用主庫
if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
databaseType = DbContextHolder.getMaster();
} else {
BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", "");
if(sql.matches(REGEX)) {
databaseType = DbContextHolder.getMaster();
} 別的 {
數(shù)據(jù)庫類型 = DbContextHolder.getSlave();
}
}
}else{
數(shù)據(jù)庫類型 = DbContextHolder.getMaster();
}
log.debug("設(shè)置方法[{}]使用[{}]策略,SqlCommandType [{}]..", ms.getId(), databaseType.name(), ms.getSqlCommandType().name()) ;
CACHE_MAP.put(ms.getId(), databaseType);
}
} else {
if (synchronizationActive) {
log.debug("事務(wù)使用 [{}] 策略", DBTypeEnum.MASTER.name());
} 別的 {
log.debug("關(guān)閉方法重置為 [{}] 策略", DBTypeEnum.MASTER.name());
}
數(shù)據(jù)庫類型 = DbContextHolder.getMaster();
}
DbContextHolder.set(databaseType);
返回調(diào)用.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
返回目標(biāo);
}
}
}
這段代碼比較長,但核心邏輯只有三個:
如果事務(wù)啟動,則使用主數(shù)據(jù)庫;
如果當(dāng)前連接已關(guān)閉,則重置到主庫;【ps:忘記不加會怎樣】
其他情況根據(jù)sql語句中的關(guān)鍵字select、update、delete判斷;
配置攔截器
這里,攔截器是基于mybatis plus配置的。
@Bean
public MybatisSqlSessionFactoryBean sqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slave1DataSource") DataSource slave1DataSource) throws Exception {
log.info("自定義配置mybatis-plus of SqlSessionFactory.");
MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();
mybatisPlus.setDataSource(myRoutingDataSource(masterDataSource, slave1DataSource));
MybatisConfiguration 配置 = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
///自定義配置
mybatisPlus.setConfiguration(configuration);
設(shè)置 mapper.xml 文件路徑
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
org.springframework.core.io.Resource[] resource = resolver.getResources("classpath:mapper/webservice/*.xml");
mybatisPlus.setMapperLocations(resource);
//給SqlSessionFactory添加一個插件生效
mybatisPlus.setPlugins(paginationInterceptor(), new DbSelectorInterceptor());
globalConfig.setMetaObjectHandler(this);
mybatisPlus.setGlobalConfig(globalConfig);
返回 mybatisPlus;
}
實際上,它指的是com baomidou。mybatisplus。自動配置。mybatisplusautoconfiguration #sqlsessionfactory,將DbSelectorInterceptor織入。如果大家想了解更多相關(guān)知識,可以關(guān)注一下動力節(jié)點的Mybatis實戰(zhàn)教程,里面的課程內(nèi)容細(xì)致全面,通俗易懂,適合小白學(xué)習(xí),希望對大家能夠有所幫助哦。