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实战教程,里面的课程内容细致全面,通俗易懂,适合小白学习,希望对大家能够有所帮助哦。
你适合学Java吗?4大专业测评方法
代码逻辑 吸收能力 技术学习能力 综合素质
先测评确定适合在学习