这里就详细讲解一下"SpringBoot+MyBatis+AOP实现读写分离"的完整攻略。本文会介绍什么是读写分离,如何使用SpringBoot、Mybatis和AOP实现读写分离,以及两个示例说明。
什么是读写分离
首先,我们需要了解一下什么是读写分离。在高并发的系统中,读取数据库的操作通常是多余写入的操作的。因此,将查询请求分发到只读数据库,减少了对主数据库的压力,提高了系统的可扩展性,稳定性和性能。这就是所谓的读写分离。通常一个复杂的系统,前端的请求经过负载均衡后,将查询请求转发到从数据库,写操作转发到主数据库。
实现读写分离
接下来,我们来介绍一下如何使用SpringBoot、Mybatis和AOP实现读写分离。
我们先定义两个数据库,一个主数据库master
,一个从数据库slave
。在SpringBoot的配置文件中,我们需要配置主数据库和从数据库的数据源。在这里我们使用Druid连接池,配置如下:
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/db?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3307/db?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
接下来,我们使用Mybatis作为数据源,并添加读写分离的AOP切面。
首先,我们需要定义两个数据源。在此,我们可以利用Spring Boot提供的一个注解:@Primary
来标识默认数据源,如下:
@Configuration
@MapperScan(basePackages= {"com.example.test.mapper"}, sqlSessionFactoryRef = "testSqlSessionFactory")
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
}
注:需要先在pom.xml中引入druid依赖
定义完两个数据源后,在此基础上定义SqlSessionFactory用于执行SQL语句。我们需要创建两个SqlSessionFactory,分别使用主从两个数据源。如下:
@Bean(name = "testSqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("slaveDataSource") DataSource slaveDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
MultipleDataSource multipleDataSource = new MultipleDataSource();
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DbType.MASTER.name(), masterDataSource);
targetDataSources.put(DbType.SLAVE.name(), slaveDataSource);
//设置数据源
multipleDataSource.setTargetDataSources(targetDataSources);
//设置默认数据源
multipleDataSource.setDefaultTargetDataSource(masterDataSource);
bean.setDataSource(multipleDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));//因为同时使用了多个sqlSessionFactory,所以不加这行会出现找不到mapper文件夹的错误
return bean.getObject();
}
最后一步是定义AOP切面,用于动态决定到哪个数据源中进行读/写操作。我们定义一个DataSourceAspect
类,利用@Around
注解定义具体的切面函数,代码如下:
@Aspect
@Component
public class DataSourceAspect {
@Around("execution(* com.example.test.mapper..*.*(..))")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
//获取目标函数上的注解
if (methodSignature.getMethod().isAnnotationPresent(DataSource.class)
&& methodSignature.getMethod().getDeclaringClass().isAnnotationPresent(DataSource.class)) {
DataSource methodAnno = methodSignature.getMethod().getAnnotation(DataSource.class);
DataSource classAnno = methodSignature.getMethod().getDeclaringClass().getAnnotation(DataSource.class);
if (methodAnno != null)
DynamicDataSourceContextHolder.setDataSource(methodAnno.value().name(), null);
else if (classAnno != null)
DynamicDataSourceContextHolder.setDataSource(classAnno.value().name(), null);
//当前线程执行目标方法
Object result = point.proceed();
//情况本地数据源设置,将数据源设置为默认
DynamicDataSourceContextHolder.clearDataSource();
return result;
}
Class<?> clazz = point.getTarget().getClass();
DataSource ds = clazz.getAnnotation(DataSource.class);
if (ds != null) {
DynamicDataSourceContextHolder.setDataSource(ds.value().name(), null);
}
Object result = point.proceed();
DynamicDataSourceContextHolder.clearDataSource();
return result;
}
}
这个切面函数是基于方法上的@DataSource注解来选择数据源的。如果方法注解存在,则使用方法注解指定的数据源,否则,使用类注解指定的数据源。如果一个方法既没有注解也没有在类上注解,则使用默认数据源。
两个示例
下面,本文以添加数据和获取数据两个示例来演示读写分离。
示例1:添加数据
首先我们在mapper中定义一个添加数据的函数和一个查询数据的函数,代码如下:
public interface DemoMapper {
int insert(Demo record);
@DataSource(DbType.SLAVE)
List<Demo> listDemosSelective(Demo record);
}
在上述代码中,我们为listDemosSelective
函数添加了@DataSource(DbType.SLAVE)
注解,用于向从库中执行查询操作。如下:
@Service
public class DemoService {
@Autowired
DemoMapper demoMapper;
@DataSource(DbType.MASTER)
public boolean insert(Demo demo){
int result = demoMapper.insert(demo);
if (result > 0)
return true;
return false;
}
@DataSource(DbType.SLAVE)
public List<Demo> listDemosSelective(Demo record){
return demoMapper.listDemosSelective(record);
}
}
在DemoService
中,为了向主数据源中添加数据,我们为insert
函数指定了@DataSource(DbType.MASTER)
注解。同时使用了@DataSource(DbType.SLAVE)
注解来向从库查询数据。
示例2:获取数据
我们还可以添加一个简单的API,用于获取数据。代码如下:
@SpringBootApplication
@MapperScan(basePackages = "com.example.test.mapper")
@RestController
public class TestApplication {
@Autowired
DemoService demoService;
@GetMapping("/listDemo")
public List<Demo> listDemo(){
Demo demo = new Demo();
demo.setId(1);
return demoService.listDemosSelective(demo);
}
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
在上述代码中,API是使用GET方法实现的,调用了DemoService
中的listDemosSelective
函数,用于从从库中查询数据。
以上就是一个简单的例子,演示了Spring Boot,Mybatis和AOP实现读写分离的原理和实现方法。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot+MyBatis+AOP实现读写分离的示例代码 - Python技术站