SpringBoot Mybatis-Plus mysql读写分离 SpringBoot+Mybatis-Plus实现mysql读写分离方案的代码实例
Abean_Y 人气:2想了解SpringBoot+Mybatis-Plus实现mysql读写分离方案的代码实例的相关内容吗,Abean_Y在本文为您仔细讲解SpringBoot Mybatis-Plus mysql读写分离的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:SpringBoot,Mybatis-Plus,mysql,读写分离,下面大家一起来学习吧。
1. 引入mybatis-plus相关包,pom.xml文件
2. 配置文件application.property增加多库配置
mysql 数据源配置
spring.datasource.primary.jdbc-url=jdbc:mysql://xx.xx.xx.xx:3306/portal?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=utf8&serverTimezone=GMT%2B8 spring.datasource.primary.username=root spring.datasource.primary.password=root spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver #mysql slave 数据源配置 spring.datasource.slave.jdbc-url=jdbc:mysql://xx.xx.xx.xx:3306/portal?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=utf8&serverTimezone=GMT%2B8 spring.datasource.slave.username=root spring.datasource.slave.password=root spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
3. 配置数据源及注解
数据源配置 MultiDataSourceConfig.Java
/** * 配置多数据源 */ @Profile("dev")//开发模式配置文件 @Configuration @MapperScan(basePackages = "com.csc.portal.mapper")//扫描包 public class MultiDataSourceConfig { /** * 主数据源 * @return */ @Bean @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } /** * 从数据源 * @return */ @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } /** * 路由数据源,前面两个数据源是为了创建此数据源 * @param masterDataSource 主数据源 * @param slaveDataSource 从数据源 * @return */ @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(slaveDataSource);//设置默认数据源 myRoutingDataSource.setTargetDataSources(targetDataSources);//设置路由表,使用map的key,value方式得到对应数据源 return myRoutingDataSource; }
数据库枚举类
public enum DBTypeEnum { MASTER, SLAVE; }
注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Master { }
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Slave { }
4. Mybatis-plus配置
@EnableTransactionManagement @Configuration @MapperScan(basePackages = "com.csc.portal.mapper") public class MybatisPlusConfig { /** * 分页插件 */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } @Resource(name = "myRoutingDataSource") private DataSource myRoutingDataSource; /** * 使用MyBatis Plus的sqlSessionFactory代替, * 此处注意mybatis与mybatisPlus的配置不同,不然扫描不到对数据操作的方法。会报未绑定错误 * @return sqlSessionFactory * @throws Exception */ @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(myRoutingDataSource); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); MybatisConfiguration mybatisConfiguration = new MybatisConfiguration(); sqlSessionFactoryBean.setConfiguration(mybatisConfiguration); return sqlSessionFactoryBean.getObject(); } /** * 此处为使用mybatis时的sqlsessionFactory配置 * @return * @throws Exception */ /* @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(myRoutingDataSource); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); return sqlSessionFactoryBean.getObject(); } */ /** * 事务配置 * @return 事务管理器 */ @Bean public DataSourceTransactionManager transactionManager() { DataSourceTransactionManager tx = new DataSourceTransactionManager(); tx.setDataSource(myRoutingDataSource); return tx; }
5. 增加数据源管理类
DBContextHolder.java
public class DBContextHolder { /** * 外部一个请求将会产生一个线程与之对应,每个线程的变量可用ThreadLocal进行存储 */ private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>(); 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() { set(DBTypeEnum.SLAVE); System.out.println("切换到slave"); } }
指定选择数据源
MyRoutingDataSource.java 方法determineCurrentLookupKey决定最终使用哪个数据源进行操作,若为空则使用默认数据源。
public class MyRoutingDataSource extends AbstractRoutingDataSource { @Nullable @Override protected Object determineCurrentLookupKey() { System.out.println("线程名:"+Thread.currentThread().getName()+":"+DBContextHolder.get()); return DBContextHolder.get(); /* if (DBContextHolder.get() != null) { System.out.println("线程名:"+Thread.currentThread().getName()+":"+DBContextHolder.get()); return DBContextHolder.get(); } else { System.out.println("未匹配到指定数据库,默认切换到Master"); return DBTypeEnum.MASTER; }*/ //return DBContextHolder.get(); } }
6. 增加aop切面
@Aspect @Component @Order(0)//配置注解优先级,优于事物注解@Transactional先进行数据源切换, //不然在事物中进行数据源切换无效 public class DataSourceAop { @Pointcut(/*"!@annotation(com.csc.portal.annotation.Master) " + "&& (execution(* com.csc.portal.service..*.select*(..)) " + "|| execution(* com.csc.portal.service..*.get*(..))"+*/ " @annotation(com.csc.portal.annotation.Slave)") public void readPointcut() { } @Pointcut("@annotation(com.csc.portal.annotation.Master) " //+ /* "|| execution(* com.csc.portal.service..*.insert*(..)) " + "|| execution(* com.csc.portal.service..*.add*(..)) " + "|| execution(* com.csc.portal.service..*.update*(..)) " + "|| execution(* com.csc.portal.service..*.edit*(..)) " + "|| execution(* com.csc.portal.service..*.delete*(..)) " + "|| execution(* com.csc.portal.service..*.remove*(..))"*/) public void writePointcut() { } @Before("readPointcut()") public void read() { //获取拦截类 DBContextHolder.slave(); System.out.println(Thread.currentThread().getName()+DBContextHolder.get()); } @Before("writePointcut()") public void write() { //获取拦截类 /* String className = pjp.getTarget().getClass().getName(); System.out.println("当前线程"+Thread.currentThread().getName()+" 拦截类为:" + className); //获取拦截的方法名 MethodSignature msig = (MethodSignature) pjp.getSignature(); Method currentMethod = null; try { currentMethod = pjp.getTarget().getClass().getMethod(msig.getName(), msig.getParameterTypes()); } catch (NoSuchMethodException e) { e.printStackTrace(); } String methodName = currentMethod.getName(); System.out.println("拦截方法名为:" + methodName);*/ DBContextHolder.master(); System.out.println(Thread.currentThread().getName()+DBContextHolder.get()); } }
6. 实际应用
- 在service层方法前增加注解@Master表示使用主库,进行增删改的操作使用主库。
- 在service层方法前增加注解@Slave表示使用从库,进行查的操作使用从库,默认使用从库,可不配置。
- @ Transactional注解加到service层,增加了@Transactional注解后,启用事务后,一个事务内部的connection是复用的,所以就算AOP切了数据源字符串,但是数据源并不会被真正修改。所以@Transactional注解不要写在controller层,不然在service层也切换不了数据源。
- @Transactional与@Master可同时使用,已经配置@Master注解的优先级较高,先切换数据源后执行事务。
加载全部内容