数据库系列——基于MySQL主从同步实现读写分离

文章介绍了如何在SpringBoot应用中使用DruidDataSource和MyBatis实现多数据源(读写分离)配置,包括配置多个读写数据源、自定义SqlSessionFactory、事务管理以及利用注解和切面进行数据源选择。
摘要由CSDN通过智能技术生成

package com.gary.dbrw.config;

import com.alibaba.druid.pool.DruidDataSource;

import com.gary.dbrw.common.Properties;

import com.gary.dbrw.datachange.DbContextHolder;

import com.gary.dbrw.datachange.MyAbstractRoutingDataSource;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.springframework.core.io.support.ResourcePatternResolver;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;

import java.sql.SQLException;

import java.util.HashMap;

import java.util.Map;

@MapperScan(basePackages = “com.gary.dbrw.mapper”, sqlSessionFactoryRef = “sqlSessionFactory”)

@Configuration

public class DataSourceConfig {

// @Value(“${mysql.datasource.type-aliases-package}”)

// private String typeAliasesPackage;

//

// @Value(“${mysql.datasource.mapper-locations}”)

// private String mapperLocation;

//

// @Value(“${mysql.datasource.config-location}”)

// private String configLocation;

/**

  • 写数据源

  • @Primary 标志这个 Bean 如果在多个同类 Bean 候选时,该 Bean 优先被考虑。

  • 多数据源配置的时候注意,必须要有一个主数据源,用 @Primary 标志该 Bean

*/

@Primary

@Bean

@ConfigurationProperties(prefix = “mysql.datasource.write”)

public DataSource writeDataSource() {

return new DruidDataSource();

}

/**

  • 读数据源

*/

@Bean

@ConfigurationProperties(prefix = “mysql.datasource.read1”)

public DataSource read1() {

return new DruidDataSource();

}

/**

  • 多数据源需要自己设置sqlSessionFactory

*/

@Bean

public SqlSessionFactory sqlSessionFactory() throws Exception {

SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

bean.setDataSource(routingDataSource());

ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

// 实体类对应的位置

bean.setTypeAliasesPackage(Properties.typeAliasesPackage);

// mybatis的XML的配置

bean.setMapperLocations(resolver.getResources(Properties.mapperLocation));

bean.setConfigLocation(resolver.getResource(Properties.configLocation));

return bean.getObject();

}

/**

  • 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理

*/

@Bean

public DataSourceTransactionManager dataSourceTransactionManager() {

return new DataSourceTransactionManager(routingDataSource());

}

/**

  • 设置数据源路由,通过该类中的determineCurrentLookupKey决定使用哪个数据源

*/

@Bean

public AbstractRoutingDataSource routingDataSource() {

MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource();

Map<Object, Object> targetDataSources = new HashMap<>(2);

targetDataSources.put(DbContextHolder.WRITE, writeDataSource());

targetDataSources.put(DbContextHolder.READ + “1”, read1());

proxy.setDefaultTargetDataSource(writeDataSource());

proxy.setTargetDataSources(targetDataSources);

return proxy;

}

}

注意有多少个读库就要设置多少个读数据源,Bean名为read+序号。

最后设置数据源,使用的是我们之前写的MyAbstractRoutingDataSource类

/**

  • 设置数据源路由,通过该类中的determineCurrentLookupKey决定使用哪个数据源

*/

@Bean

public AbstractRoutingDataSource routingDataSource() {

MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource();

Map<Object, Object> targetDataSources = new HashMap<>(2);

targetDataSources.put(DbContextHolder.WRITE, writeDataSource());

targetDataSources.put(DbContextHolder.READ + “1”, read1());

proxy.setDefaultTargetDataSource(writeDataSource());

proxy.setTargetDataSources(targetDataSources);

return proxy;

}

接着需要设置sqlSessionFactory,配置MyBatis的sqlSessionFactoryRef 引用

/**

  • 多数据源需要自己设置sqlSessionFactory

*/

@Bean

public SqlSessionFactory sqlSessionFactory() throws Exception {

SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

bean.setDataSource(routingDataSource());

ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

// 实体类对应的位置

bean.setTypeAliasesPackage(Properties.typeAliasesPackage);

// mybatis的XML的配置

bean.setMapperLocations(resolver.getResources(Properties.mapperLocation));

bean.setConfigLocation(resolver.getResource(Properties.configLocation));

return bean.getObject();

}

@MapperScan(basePackages = “com.gary.dbrw.mapper”, sqlSessionFactoryRef = “sqlSessionFactory”)

@Configuration

public class DataSourceConfig {

最后还得配置下事务,否则事务不生效

/**

  • 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理

*/

@Bean

public DataSourceTransactionManager dataSourceTransactionManager() {

return new DataSourceTransactionManager(routingDataSource());

}

4)、选择数据源

多数据源配置好了,但是代码层面如何选择选择数据源呢?

采用现在流行的注解加切面。

首先定义一个只读注解,被这个注解方法使用读库,其他使用写库,如果项目是中途改造成读写分离可使用这个方法,无需修改业务代码,只要在只读的service方法上加一个注解即可。

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface ReadOnly {

}

然后写一个切面来切换数据使用哪种数据源,重写getOrder保证本切面优先级高于事务切面优先级,(这样才可以使用事务)在启动类加上@EnableTransactionManagement(order = 10),为了代码如下:

@Aspect

@Component

public class ReadOnlyInterceptor implements Ordered {

private static final Logger log = LoggerFactory.getLogger(ReadOnlyInterceptor.class);

@Around(“@annotation(readOnly)”)

public Object setRead(ProceedingJoinPoint joinPoint, ReadOnly readOnly) throws Throwable {

try {

DbContextHolder.setDbType(DbContextHolder.READ);

return joinPoint.proceed();

} finally {

//清楚DbType一方面为了避免内存泄漏,更重要的是避免对后续在本线程上执行的操作产生影响

DbContextHolder.clearDbType();

log.info(“清除threadLocal”);

}

}

@Override

public int getOrder() {

return 0;

}

}

配置测试类:

@Service

public class StudentServiceImpl implements StudentService {

@Resource

StudentMapper studentMapper;

@ReadOnly

@Override

public List selectAllList() {

return studentMapper.selectAll();

}

@Override

public int addOneStudent(Student student) {

return studentMapper.insertSelective(student);

}

}

@RestController

@RequestMapping(“user”)

public class StudentController {

@Resource

StudentService studentService;

@GetMapping(“/testRW”)

public String DbRead(Integer dbType) {

System.out.println(“dbType=:” + dbType);

List students = studentService.selectAllList();

return “ReadDB=>” + students;

}

@PostMapping(“/testRW”)

public String DbWrite(Student student) {

int count = studentService.addOneStudent(student);

总结

如果你选择了IT行业并坚定的走下去,这个方向肯定是没有一丝问题的,这是个高薪行业,但是高薪是凭自己的努力学习获取来的,这次我把P8大佬用过的一些学习笔记(pdf)都整理在本文中了

《Java中高级核心知识全面解析》

小米商场项目实战,别再担心面试没有实战项目:

tln(“dbType=:” + dbType);

List students = studentService.selectAllList();

return “ReadDB=>” + students;

}

@PostMapping(“/testRW”)

public String DbWrite(Student student) {

int count = studentService.addOneStudent(student);

总结

如果你选择了IT行业并坚定的走下去,这个方向肯定是没有一丝问题的,这是个高薪行业,但是高薪是凭自己的努力学习获取来的,这次我把P8大佬用过的一些学习笔记(pdf)都整理在本文中了

《Java中高级核心知识全面解析》

[外链图片转存中…(img-EygOOV4t-1714720928647)]

小米商场项目实战,别再担心面试没有实战项目:

[外链图片转存中…(img-ICS899Mr-1714720928648)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值