以springboot2.x+mybatis+mysql+HikariCP为例,记录下自己的开发记录。
说点废话
从四种读写分离的方式看到了读写分离有四种实现方式。
我这里使用的是第三种方式实现的。目前实现的是一个主(Master),两个从(Slave)。废话少说,讲下我的思路。
- 定义四个数据源,为什么是四个呢? Master1 ,Slave2,统一的数据源*1(一下称为DynamicDatasource,这个数据源是其他三个数据源的总和,可以执行它使用哪个数据源(Master,Slave1,Slave2)进行操作).
- 定义一个DataSourceHolder,存放的是三个数据源(Master,Slave1,Slave2),可以按照条件去获取指定类型的数据源之一。
- 将DynamicDataSource替换掉SpringBoot默认提供的数据源。
- 在方法上使用注解或者使用某一类方法调用前指定使用的数据源。
上代码
- 在pom.xml中加上如下内容。
<!--- mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- HikariCP 数据库连接池-->
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.3.0</version>
</dependency>
- 我们首先定义好三个特定的数据源
Master*1,Slave*2
public enum DBTypeEnum {
/**
* 主数据源
*/
Master,
/**
* 从数据源1
*/
Slave1,
/**
* 从数据源2
*/
Slave2
}
- 然后,设置DataSourceHolder。这样我们就定义好了数据源的调用的模型了。
@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);
log.debug("使用的数据源是: master");
}
public static void slave() {
int index = counter.getAndIncrement() % 2;
if (counter.get() > 99) {
counter.set(-1);
}
if (index == 0) {
set(DBTypeEnum.Slave1);
log.debug("使用的数据源是: slave1");
} else {
set(DBTypeEnum.Slave2);
log.debug("使用的数据源是: slave2");
}
}
}
- 设置动态数据源,可以指定上面的任何一种数据源去真正的操作数据库。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
- 创建四种数据源的实例,ConfigurationProperties注解中的配置项是在application.propreties中配置的。
@Configuration
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public HikariDataSource masterDataSource() {
return new HikariDataSource();
}
@Bean
@ConfigurationProperties("spring.datasource.slave1")
public HikariDataSource slave1DataSource() {
return new HikariDataSource();
}
@Bean
@ConfigurationProperties("spring.datasource.slave2")
public HikariDataSource slave2DataSource() {
return new HikariDataSource();
}
@Bean
public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slave1DataSource") DataSource slave1DataSource,
@Qualifier("slave2DataSource") DataSource slave2DataSource) {
Map<Object, Object> targetDataSources = new HashMap<>(3);
// 设置所有的数据源
targetDataSources.put(DBTypeEnum.Master, masterDataSource);
targetDataSources.put(DBTypeEnum.Slave1, slave1DataSource);
targetDataSources.put(DBTypeEnum.Slave2, slave2DataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 将写库设置为默认的数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
- 指定我们使用的数据源配置,url,username,password等。
spring.datasource.master.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
spring.datasource.master.username=root
spring.datasource.master.password=123456
#spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave1.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
spring.datasource.slave1.username=root
spring.datasource.slave1.password=123456
#spring.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave2.jdbc-url=jdbc:mysql://10.1.14.177:3306/dor_human?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
spring.datasource.slave2.username=root
spring.datasource.slave2.password=123456
#spring.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
- 这是我们要替换掉springboot默认的数据源
@EnableTransactionManagement
@Configuration
@Slf4j
public class MyBatisConfig {
@Autowired
private DataSource dynamicDataSource;
/**
* SqlSessionFactory
*
* @return SqlSessionFactory
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.fxb.doraemon.human.entity");
sqlSessionFactoryBean.setConfigLocation(
new PathMatchingResourcePatternResolver().getResource("classpath:mapper/mybatis-config.xml"));
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml"));
log.debug("初始化SqlSessionFactory");
return sqlSessionFactoryBean.getObject();
}
/**
* 事务管理器
*
* @return PlatformTransactionManager
*/
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(dynamicDataSource);
}
}
好了,这样我们的读写分离的准备工作就做好了,接下来要做的就是使用了,这里我们使用AOP的方式,来规定什么时候使用什么数据源。
@Aspect
@Component
@Slf4j
public class DataSourceAop {
/**
* 织入读库数据源
*/
@Pointcut("execution(public * com.fxb.doraemon.human.service..*.get*(..)) " +
"|| execution(public * com.fxb.doraemon.human.service..*.select*(..))")
public void readPointcut() {
}
/***
* @1:这里有点小问题.https://blog.csdn.net/wxy540843763/article/details/90112975
*/
@Pointcut("@annotation(com.fxb.doraemon.human.annotation.Master) " +
"|| execution(public * com.fxb.doraemon.human.service..*.save*(..)) " +
"|| execution(public * com.fxb.doraemon.human.service..*.insert*(..)) " +
"|| execution(public * com.fxb.doraemon.human.service..*.update*(..)) " +
"|| execution(public * com.fxb.doraemon.human.service..*.edit*(..)) " +
"|| execution(public * com.fxb.doraemon.human.service..*.delete*(..)) " +
"|| execution(public * com.fxb.doraemon.human.service..*.del*(..)) " +
"|| execution(public * com.fxb.doraemon.human.service..*.remove*(..)) ")
public void writePointcut() {
}
@Before("readPointcut()")
public void read() {
DBContextHolder.slave();
}
@Before("writePointcut()")
public void write() {
DBContextHolder.master();
}
}
- 最后,最重要的一步,不然你会发现,aop怎么都切入不了。那是因为没有启动织入代理方式。在你的启动类上加上下面的这个注解。
@EnableAspectJAutoProxy
验证一下我们的结果:
好了,这样我们的数据分离就配置好了。
其他的三种方式,我也会陆续学习的。再来总结自己的感悟。
下一篇文章,我们来解释一下这个@1的这个问题。
参考文章
最后
如果你觉得写的还不错,就关注下公众号呗,关注后,有点小礼物回赠给你。
你可以获得5000+电子书,java,springCloud,adroid,python等各种视频教程,IT类经典书籍,各种软件的安装及破解教程。
希望一块学习,一块进步!