SpringBoot+MyBatis+MySQL读写分离实现( 三 )

由于Spring容器中现在有4个数据源 , 所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源 。
3.3. 设置路由key / 查找数据源
目标数据源就是那前3个这个我们是知道的 , 但是使用的时候是如果查找数据源的呢?
首先 , 我们定义一个枚举来代表这三个数据源
package com.cjs.example.enums;public enum DBTypeEnum {MASTER, SLAVE1, SLAVE2;}接下来 , 通过ThreadLocal将数据源设置到每个线程上下文中
package com.cjs.example.bean;import com.cjs.example.enums.DBTypeEnum;import java.util.concurrent.atomic.AtomicInteger;public class DBContextHolder {private static final ThreadLocal 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);System.out.println("切换到master");}public static void slave() {//轮询int index = counter.getAndIncrement() % 2;if (counter.get() > 9999) {counter.set(-1);}if (index == 0) {set(DBTypeEnum.SLAVE1);System.out.println("切换到slave1");}else {set(DBTypeEnum.SLAVE2);System.out.println("切换到slave2");}}}获取路由key
package com.cjs.example.bean;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.lang.Nullable;public class MyRoutingDataSource extends AbstractRoutingDataSource {@Nullable@Overrideprotected Object determineCurrentLookupKey() {return DBContextHolder.get();}}设置路由key
默认情况下 , 所有的查询都走从库 , 插入/修改/删除走主库 。 我们通过方法名来区分操作类型(CRUD)
package com.cjs.example.aop;import com.cjs.example.bean.DBContextHolder;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect@Componentpublic class DataSourceAop {@Pointcut("!@annotation(com.cjs.example.annotation.Master) " +"}@Before("writePointcut()")public void write() {DBContextHolder.master();}/*** 另一种写法:if...else...判断哪些需要读从数据库 , 其余的走主数据库*///@Before("execution(* com.cjs.example.service.impl.*.*(..))")//public void before(JoinPoint jp) {//String methodName = jp.getSignature().getName();////if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {//DBContextHolder.slave();//}else {//DBContextHolder.master();//}//}}有一般情况就有特殊情况 , 特殊情况是某些情况下我们需要强制读主库 , 针对这种情况 , 我们定义一个主键 , 用该注解标注的就读主库
package com.cjs.example.annotation;public @interface Master {}例如 , 假设我们有一张表member
package com.cjs.example.service.impl;import com.cjs.example.annotation.Master;import com.cjs.example.entity.Member;import com.cjs.example.entity.MemberExample;import com.cjs.example.mapper.MemberMapper;import com.cjs.example.service.MemberService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class MemberServiceImpl implements MemberService {@Autowiredprivate MemberMapper memberMapper;@Transactional@Overridepublic int insert(Member member) {return memberMapper.insert(member);}@Master@Overridepublic int save(Member member) {return memberMapper.insert(member);}@Overridepublic List