首先呢,我们这里使用MySQL的数据库,可以简单配置一下主从备份来实现两个数据库的数据同步
项目的配置目录大概是这样的作为参考
第一步、定义一个枚举类声明当前的数据源类型
package com.yuxuan66.ehi.workcloud.config.db;
/**
* 数据源类型枚举
* @author Sir丶雨轩
* @since 1.0
*/
public enum DBTypeEnum {
MASTER, SLAVE;
}
第二步、定义我们的数据源路由
package com.yuxuan66.ehi.workcloud.config.db;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
/**
* @author Sir丶雨轩
* @since 1.0
*/
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}}
第三步、配置所有的数据源,包含一个数据源的路由
package com.yuxuan66.ehi.workcloud.config.db;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 数据源配置,用来实现读写分离
*
* @author Sir丶雨轩
* @since 1.0
*/
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.type}")
private Class<? extends DataSource> type;
/**
* 主数据库
* @return DataSource
*/
@Bean("masterDataSource")
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(type).build();
}
/**
* 从数据库
* @return DataSource
*/
@Bean("slaveDataSource")
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(type).build();
}
/**
* 数据源路由
* @param masterDataSource 主数据库
* @param slaveDataSource 从数据库
* @return DataSource
*/
@Bean
public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
targetDataSources.put(DBTypeEnum.SLAVE, slaveDataSource);
MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
myRoutingDataSource.setTargetDataSources(targetDataSources);
return myRoutingDataSource;
}}
第四步、数据源的上下文管理切换
package com.yuxuan66.ehi.workcloud.config.db;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Sir丶雨轩
* @since 1.0
*/
@Slf4j
public class DBContextHolder {
private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
private static final AtomicInteger counter = new AtomicInteger(-1);
public static void set(DBTypeEnum dbType) {
contextHolder.set(dbType);
}
public static DBTypeEnum get() {
return contextHolder.get();
}
public static void master() {
set(DBTypeEnum.MASTER);
if(log.isDebugEnabled()){
log.debug("数据源切换至master");
}
}
public static void slave() {
set(DBTypeEnum.SLAVE);
if(log.isDebugEnabled()){
log.debug("数据源切换至slave");
}
}}
第五步、配置SqlSessionFactory
package com.yuxuan66.ehi.workcloud.config.db;
/**
* @author Sir丶雨轩
* @since 1.0
*/
@Configuration
@MapperScan("com.yuxuan66.ehi.workcloud.**.mapper*")
public class MybatisPlusConfig {
/**
* xml文件所在路径
*/
@Value("${mybatis-plus.mapper-locations}")
private String mapperLocations;
/**
* 别名扫描包名
*/
@Value("${mybatis-plus.type-aliases-package}")
private String typeAliasesPackage;
/**
* 是否显示banner
*/
@Value("${mybatis-plus.global-config.banner}")
private Boolean banner;
/**
* 是否开启转驼峰
*/
@Value("${mybatis-plus.configuration.map-underscore-to-camel-case}")
private Boolean mapUnderscoreToCamelCase;
/**
* 数据源路由
*/
@Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource;
/**
* 使用MyBatis Plus的sqlSessionFactory代替
*
* @return sqlSessionFactory
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
mybatisConfiguration.setMapUnderscoreToCamelCase(mapUnderscoreToCamelCase);
mybatisConfiguration.setObjectWrapperFactory(new MapWrapperFactory());
sqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
GlobalConfig config = new GlobalConfig();
config.setBanner(banner);
sqlSessionFactoryBean.setGlobalConfig(config);
sqlSessionFactoryBean.setPlugins(new Interceptor\[\]{new PaginationInterceptor()});
return sqlSessionFactoryBean.getObject();
}
/**
* 事务配置
*
* @return 事务管理器
*/
@Bean
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager tx = new DataSourceTransactionManager();
tx.setDataSource(myRoutingDataSource);
return tx;
}}
第六步、使用Aop和注解来控制数据源的切换
package com.yuxuan66.ehi.workcloud.config.db;
/**
* @author Sir丶雨轩
* @since 1.0
*/
@Aspect
@Component
public class DataSourceAop {
/**
* 使用从库查询
*/
@Pointcut("@annotation(com.yuxuan66.ehi.workcloud.config.db.anno.Slave) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.select*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.list*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.query*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.find*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.get*(..))")
public void readPointcut() {
}
/**
* 使用主库插入更新
*/
@Pointcut("@annotation(com.yuxuan66.ehi.workcloud.config.db.anno.Master) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.login(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.insert*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.add*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.update*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.edit*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.delete*(..)) " +
"|| execution(* com.yuxuan66.ehi.workcloud..service..*.remove*(..))")
public void writePointcut() {
}
@Before("readPointcut()")
public void read() {
DBContextHolder.slave();
}
@Before("writePointcut()")
public void write() {
DBContextHolder.master();
}}
第七步、定义注解
package com.yuxuan66.ehi.workcloud.config.db.anno;
/**
* 强制使用读数据库
* @author Sir丶雨轩
* @since 1.0
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Master {}
package com.yuxuan66.ehi.workcloud.config.db.anno;
/**
* 强制使用从数据库
*
* @author Sir丶雨轩
* @since 1.0
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Slave {}
评论区