SpringBoot+MyBatis+MySQL读写分离实现( 二 ) 2020-12-01 targetDataSources = new HashMap<>();targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);myRoutingDataSource.setTargetDataSources(targetDataSources);return myRoutingDataSource;}}这里 , 我们配置了4个数据源 , 1个master , 2两个slave , 1个路由数据源 。 前3个数据源都是为了生成第4个数据源 , 而且后续我们只用这最后一个路由数据源 。 MyBatis配置 package com.cjs.example.config;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.annotation.Resource;import javax.sql.DataSource;@EnableTransactionManagement@Configurationpublic class MyBatisConfig {@Resource(name = "myRoutingDataSource")private DataSource myRoutingDataSource;@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(myRoutingDataSource);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));return sqlSessionFactoryBean.getObject();}@Beanpublic PlatformTransactionManager platformTransactionManager() {return new DataSourceTransactionManager(myRoutingDataSource);}}由于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 上一页123下一页 订阅|手淘大改版:商家可被“订阅”内容种草与购买转化分离 分离基础|数据中心融合的过去、现在和未来 行业|工业气体需求增加,空气分离设备行业市场规模有望进一步扩大 python文件读写模式,覆盖写和清空写你清楚了吗 国产良心SSD读写不虚标,379元入手512G,8秒开机 群联第二代PCIe 4.0 SSD主控E18性能曝光:读写均超7GB/s 东风集团要猛攻换电?将与国网成立合资公司探索“车电分离”模式 荣耀终端有限公司|荣耀成立多家分公司 与华为分离构建新架构 设计数据库集群读写分离并非易事 集团|东风集团与国家电网战略合作,推出车电分离商业模式