更新時間:2022-04-06 09:58:38 來源:動力節點 瀏覽2635次
MyBatis讀寫分離是什么?對于初學者來說可能還不是很了解,下面動力節點小編來告訴大家。
ShardingSphere由JDBC、Proxy和Sidecar組成(規劃中),可以獨立部署,支持混合部署。ShardingSphere Proxy 與 MyCat 定位相同,而 ShardingSphere JDBC 在 Java 的 JDBC 層提供了額外的服務。
SpringBoot 集成 shardingsphere JDBC 也非常方便。引入包和編寫配置文件后即可使用。但是事務中有個小問題,就是事務中的寫操作之后,后面的讀操作都是從主庫中讀取的;也就是說,在寫操作之前,事務中的讀仍然是從庫中讀取的,可能會造成臟寫。
大部分代碼層面的讀寫分離都是通過判斷sql的讀寫類型來攔截sql并重定向數據庫。Shardingsphere JDBC 也不例外。
Mybatis 允許我們自定義 Interceptor。我們需要實現Interceptor接口,在自定義的Interceptor類上添加@Intercepts注解。在@Intercepts注解中,我們可以指定攔截方式。
由于讀寫分離是在代碼層面進行的,所以必須有讀寫庫。這里使用了多數據源功能。不用mybatis/mybatis plus默認的多數據源生成方式,多數據源自己配置。其實也可以使用默認的生成方式。自己寫的目的是為了更好的理解里面的原理。【配置文件中配置的格式是根據mybatis配置的格式加上多個數據源來配置的】
代碼
多數據源配置
/**
* 主數據庫
*/
@ConfigurationProperties("spring.datasource.dynamic.datasource.master")
公共數據源 masterDataSource(){
log.info("加載主數據源主數據源。");
返回 DruidDataSourceBuilder.create().build();
}
/**
* 數據庫從庫
*/
@ConfigurationProperties("spring.datasource.dynamic.datasource.slave1")
公共數據源 slave1DataSource(){
log.info("從數據源 slave1 DataSource 加載。");
返回 DruidDataSourceBuilder.create().build();
}
/**
* 動態數據源
*/
@豆角,扁豆
公共數據源 myRoutingDataSource(@Qualifier("masterDataSource") 數據源 masterDataSource,
@Qualifier("slave1DataSource") 數據源 slave1DataSource) {
log.info("load[masterDataSource-slave1DataSource]設置為動態數據源DynamicDataSource。");
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
動態數據源 dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
動態數據源.setTargetDataSources(targetDataSources);
返回動態數據源;
}
DBTypeEnum
public enum DBTypeEnum {
/**Main library*/
MASTER,
/**From library 1*/
SLAVE1
}
動態數據源
此處指定數據源的鍵。在每條sql語句執行之前,都會執行determineCurrentLookupKey獲取數據源。DbContextHolder.get()是獲取當前線程中指定數據源的key,會在自定義攔截器中指定。
公共類 DynamicDataSource 擴展 AbstractRoutingDataSource {
@Nullable @Override
受保護對象 determineCurrentLookupKey() {
return DbContextHolder.get();
}
}
公共類 DbContextHolder {
私有靜態最終 ThreadLocal<DBTypeEnum> CONTEXT_HOLDER = new ThreadLocal<>();
私有靜態最終 AtomicInteger COUNTER = new AtomicInteger(-1);
public static void set(DBTypeEnum dbType) {
log.debug("切換到{}", dbType.name());
CONTEXT_HOLDER.set(dbType);
}
公共靜態 DBTypeEnum get() {
return CONTEXT_HOLDER.get();
}
公共靜態 DBTypeEnum getMaster() {
return DBTypeEnum.MASTER;
}
public static DBTypeEnum getSlave() {
// 可以輪詢多個從庫
int index = COUNTER.getAndIncrement() % 2;
if (COUNTER.get() > 9999) {
COUNTER.set(-1);
}
返回 DBTypeEnum.SLAVE1;
}
}
在上一步中,我們定義了多個數據源并設置了數據源選擇的基礎(DbContextHolder.get())。這一步就是按照一定的規則在攔截器中設置這個基礎。
代碼
攔截器
@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靜態最終字符串 REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
private static final Map<String, DBTypeEnum> CACHE_MAP = new ConcurrentHashMap<>();
@覆蓋
公共對象攔截(調用調用)拋出 Throwable {
String methodName = invocation.getMethod().getName();
字符串 closeMethodName = "關閉";
布爾同步活動 = TransactionSynchronizationManager.isSynchronizationActive();
DBTypeEnum 數據庫類型 = 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();
} 別的 {
數據庫類型 = DbContextHolder.getSlave();
}
}
}else{
數據庫類型 = DbContextHolder.getMaster();
}
log.debug("設置方法[{}]使用[{}]策略,SqlCommandType [{}]..", ms.getId(), databaseType.name(), ms.getSqlCommandType().name()) ;
CACHE_MAP.put(ms.getId(), databaseType);
}
} else {
if (synchronizationActive) {
log.debug("事務使用 [{}] 策略", DBTypeEnum.MASTER.name());
} 別的 {
log.debug("關閉方法重置為 [{}] 策略", DBTypeEnum.MASTER.name());
}
數據庫類型 = DbContextHolder.getMaster();
}
DbContextHolder.set(databaseType);
返回調用.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
返回目標;
}
}
}
這段代碼比較長,但核心邏輯只有三個:
如果事務啟動,則使用主數據庫;
如果當前連接已關閉,則重置到主庫;【ps:忘記不加會怎樣】
其他情況根據sql語句中的關鍵字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);
設置 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織入。如果大家想了解更多相關知識,可以關注一下動力節點的Mybatis實戰教程,里面的課程內容細致全面,通俗易懂,適合小白學習,希望對大家能夠有所幫助哦。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習