aop实现mysql读写分离_mysql读写分离(1)---springboot+aop+tk.mybatis实现对mysql的读写分离...

前言

实现mysql读写分离有两种,一种是基于代码实现(spring提供的抽象类AbstractRoutingDataSource),另外一种是基于中间件实现(MySQL Proxy、Mycat和Atlas等),本次使用第一种方式实现;

准备

安装mysql参考:centos7.5手动安装mysql5.6

主从复制配置参考:mysql主从复制配置

MySQL版本:mysql5.6

使用三台虚拟机:

192.168.1.111 master

192.168.1.112 slave

192.168.1.113 slave

pom文件

spring-boot-starter-parent版本2.1.0.RELEASE

org.springframework.boot

spring-boot-starter-web

org.projectlombok

lombok

org.springframework.boot

spring-boot-starter-aop

org.springframework.boot

spring-boot-starter-jdbc

org.springframework.boot

spring-boot-starter-web

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.2

mysql

mysql-connector-java

org.apache.commons

commons-lang3

tk.mybatis

mapper-spring-boot-starter

2.1.4

com.alibaba

fastjson

1.2.51

配置文件application.yml

server:

port: 8088

spring:

datasource:

master:

jdbc-url: jdbc:mysql://192.168.1.111:3306/test

username: root

password: 123456

driver-class-name: com.mysql.cj.jdbc.Driver

slave1:

jdbc-url: jdbc:mysql://192.168.1.112:3306/test

username: read

password: 123456

driver-class-name: com.mysql.cj.jdbc.Driver

slave2:

jdbc-url: jdbc:mysql://192.168.1.113:3306/test

username: read

password: 123456

driver-class-name: com.mysql.cj.jdbc.Driver

mapper:

mappers: tk.mybatis.mapper.common.Mapper,tk.mybatis.mapper.common.IdsMapper

identity: MYSQL

定义数据源全局持有对象(用于主从切换)

@Slf4j

public class DBContextHolder {

private static final ThreadLocal contextHolder = new ThreadLocal<>();

private static final AtomicInteger counter = new AtomicInteger(-1);

private static final int INCREMENT_MAX_COUNT = 9999;

public static void set(DBTypeEnum dbType) {

contextHolder.set(dbType);

}

public static DBTypeEnum get() {

return contextHolder.get();

}

public static void switchMaster() {

set(DBTypeEnum.MASTER);

log.info("数据源切换到master");

}

/**

* 两个slave轮询

*/

public static void switchSlave() {

int index = counter.getAndIncrement() % 2;

if (counter.get() > INCREMENT_MAX_COUNT) {

counter.set(-1);

}

if (index == 0) {

set(DBTypeEnum.SLAVE1);

log.info("数据源切换到slave1");

} else {

set(DBTypeEnum.SLAVE2);

log.info("数据源切换到slave2");

}

}

}

继承AbstractRoutingDataSource,定义路由数据源

@Slf4j

public class RoutingDataSource extends AbstractRoutingDataSource {

@Nullable

@Override

protected Object determineCurrentLookupKey() {

log.info("当前数据源key: {}", DBContextHolder.get());

return DBContextHolder.get();

}

}

数据源配置

@Configuration

public class DataSourceConfig {

@Bean

@ConfigurationProperties("spring.datasource.master")

public DataSource masterDataSource() {

return DataSourceBuilder.create().build();

}

@Bean

@ConfigurationProperties("spring.datasource.slave1")

public DataSource slave1DataSource() {

return DataSourceBuilder.create().build();

}

@Bean

@ConfigurationProperties("spring.datasource.slave2")

public DataSource slave2DataSource() {

return DataSourceBuilder.create().build();

}

/**

* 由上面的三个数据源组成的一个路由数据源,也就是说后面真正使用的其实是这个路由数据源

* @param masterDataSource master数据源

* @param slave1DataSource slave1数据源

* @param slave2DataSource slave2数据源

* @return

*/

@Bean

public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,

@Qualifier("slave1DataSource") DataSource slave1DataSource,

@Qualifier("slave2DataSource") DataSource slave2DataSource) {

Map targetDataSources = new HashMap<>();

targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);

targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);

targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);

RoutingDataSource routingDataSource = new RoutingDataSource();

routingDataSource.setDefaultTargetDataSource(masterDataSource);

routingDataSource.setTargetDataSources(targetDataSources);

return routingDataSource;

}

}

设置mybatis使用路由数据源

@Configuration

@EnableTransactionManagement

public class TkMybatisConfig {

@Resource(name = "routingDataSource")

private DataSource routingDataSource;

@Bean

public SqlSessionFactory sqlSessionFactory() throws Exception {

SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

sqlSessionFactoryBean.setDataSource(routingDataSource);

return sqlSessionFactoryBean.getObject();

}

@Bean

public PlatformTransactionManager platformTransactionManager() {

return new DataSourceTransactionManager(routingDataSource);

}

}

定义master注解,用于强制使用主数据源

public @interface Master {

}

基于注解定义aop切点和切面

@Aspect

@Component

public class DataSourceAspect {

/**

* master注解的切入点(强制使用主数据源)

*/

@Pointcut("@annotation(com.felix.learn.datasource.annotation.Master)")

public void masterPointcut() {}

/**

* 读操作切入点

*/

@Pointcut("(execution(* com.felix.learn.datasource.service..*.select*(..)) " +

"|| execution(* com.felix.learn.datasource.service..*.get*(..)) " +

"|| execution(* com.felix.learn.datasource.service..*.find*(..)))")

public void readPointcut() {}

/**

* 写操作切入点

*/

@Pointcut("execution(* com.felix.learn.datasource.service..*.insert*(..)) " +

"|| execution(* com.felix.learn.datasource.service..*.add*(..)) " +

"|| execution(* com.felix.learn.datasource.service..*.update*(..)) " +

"|| execution(* com.felix.learn.datasource.service..*.edit*(..)) " +

"|| execution(* com.felix.learn.datasource.service..*.delete*(..)) " +

"|| execution(* com.felix.learn.datasource.service..*.remove*(..))")

public void writePointcut() {}

@Before("!masterPointcut() && readPointcut()")

public void read() {

DBContextHolder.switchSlave();

}

@Before("masterPointcut() || writePointcut()")

public void write() {

DBContextHolder.switchMaster();

}

}

写例子测试(只展示service层)

@Service

public class StudentServiceImpl implements StudentService {

@Resource

private StudentMapper studentMapper;

@Override

public Student findById(Integer id) {

return studentMapper.selectByPrimaryKey(id);

}

@Override

public int insertStudent(Student student) {

studentMapper.insertSelective(student);

return student.getId();

}

@Override

public int deleteStudent(Integer id) {

return studentMapper.deleteByPrimaryKey(id);

}

@Override

public int updateStudent(Student student) {

return studentMapper.updateByPrimaryKeySelective(student);

}

@Master

@Override

public List selectList() {

List students = studentMapper.selectAll();

return students;

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值