1、前言
在实际项目中,数据库是至关重要的组成部分。为了提高性能和可用性,常见的数据库优化策略之一是将数据库读和写操作分离,以降低数据库服务器的负载。
MyBatis Plus是一个流行的Java持久化框架,它提供了读写分离的支持,结合Spring Boot 集成多数据源的特性可以帮助你轻松地实现读写分离策略。
Springboot项目中操作数据库,使用MP方便快捷;如何使用MP实现读写分离,以提高你的应用程序性能和可用性。我们将分为以下几个步骤来介绍这一过程。
2、配置多数据源
在 Spring Boot 配置文件中配置多个数据源,通常至少包括一个主数据库(写库)和一个或多个从数据库(读库)。以下是一个示例的配置:
spring.datasource.write.url=jdbc:mysql://write-server:3306/write_db spring.datasource.write.username=root spring.datasource.write.password=root spring.datasource.read.url=jdbc:mysql://read1-server:3306/read1_db spring.datasource.read.username=root spring.datasource.read.password=root
上面配置中,write
数据源代表主数据库,read
数据源代表从数据库。你可以根据项目需求配置更多的从数据库。
3、创建数据源和数据库连接池 Bean
在 Spring Boot 配置中,为每个数据源创建一个数据源和数据库连接池 Bean。
@Bean(name = "writeDataSource") @ConfigurationProperties(prefix = "spring.datasource.write") public DataSource writeDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "readDataSource") @ConfigurationProperties(prefix = "spring.datasource.read") public DataSource readDataSource() { return DataSourceBuilder.create().build(); }
4、配置 MyBatis Plus数据源
配置 MyBatis Plus 的数据源和 SqlSessionFactoryBean,分别指向主数据源和从数据源。
@Bean(name = "writeSqlSessionFactory") public SqlSessionFactory writeSqlSessionFactory(@Qualifier("writeDataSource") DataSource writeDataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean(); sessionFactoryBean.setDataSource(writeDataSource); // 配置 MyBatis Plus的一些参数 return sessionFactoryBean.getObject(); } @Bean(name = "readSqlSessionFactory") public SqlSessionFactory readSqlSessionFactory(@Qualifier("readDataSource") DataSource read1DataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean(); sessionFactoryBean.setDataSource(readDataSource); // 配置 MyBatis Plus的一些参数 return sessionFactoryBean.getObject(); }
5、配置动态数据源路由
创建一个动态数据源路由,用于根据操作类型(读操作或写操作)选择主数据源或从数据源。
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
6、配置数据源切换注解
创建自定义注解(如 @ReadDataSource
和 @WriteDataSource
),并创建拦截器,根据注解来选择数据源。
首先,创建 @ReadDataSource
注解:
import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ReadDataSource { }
接下来,创建 @WriteDataSource
注解:
import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WriteDataSource { }
然后,创建数据源切换拦截器,该拦截器将根据注解选择数据源:
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; @Aspect @Component public class DataSourceSwitchAspect { @Before("execution(* com.example.service.*.*(..))") public void switchDataSource(JoinPoint joinPoint) { Class<?> targetClass = joinPoint.getTarget().getClass(); // 检查方法上是否有数据源注解 ReadDataSource readDataSource = AnnotationUtils.findAnnotation(targetClass, ReadDataSource.class); WriteDataSource writeDataSource = AnnotationUtils.findAnnotation(targetClass, WriteDataSource.class); if (readDataSource != null) { DataSourceContextHolder.setDataSource(DataSourceType.READ); } else if (writeDataSource != null) { DataSourceContextHolder.setDataSource(DataSourceType.WRITE); } else { // 没有注解,默认使用写数据源 DataSourceContextHolder.setDataSource(DataSourceType.WRITE); } } }
这个拦截器会在方法执行前根据注解来选择数据源,如果方法上有 @ReadDataSource
注解,则选择读数据源,如果方法上有 @WriteDataSource
注解,则选择写数据源,否则默认使用写数据源。
最后,创建一个数据源上下文持有器 DataSourceContextHolder
,用于存储当前选择的数据源:
public class DataSourceContextHolder { private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>(); public static void setDataSource(DataSourceType dataSourceType) { contextHolder.set(dataSourceType); } public static DataSourceType getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }
DataSourceType
是一个自定义的枚举,用于表示数据源类型(读或写):
public enum DataSourceType { READ, WRITE }
通过以上配置,你可以在 Service 层的方法上使用 @ReadDataSource
和 @WriteDataSource
注解来标识读操作和写操作,拦截器会根据注解选择相应的数据源。
7、编写 Service 层代码
在 Service 层的方法中,根据操作类型(读操作或写操作),选择数据源。可以使用自定义注解或其他方式标识读操作和写操作的方法。
@Service public class UserService { @Autowired private UserService userService; // MyBatis Plus 提供的 Service @ReadDataSource // 自定义的注解,用于标识读操作 public User getUserById(Long id) { return userService.getById(id); // 直接使用 MyBatis Plus 提供的查询方法 } @WriteDataSource // 自定义的注解,用于标识写操作 public void createUser(User user) { userService.save(user); // 直接使用 MyBatis Plus 提供的插入方法 } }
8、总结
MyBatis Plus的读写分离功能使数据库优化变得轻而易举。通过将读和写操作分离到不同的数据源中,你可以降低数据库负载,提高应用程序性能和可用性。需要注意的是,具体的实现方式和注解可以根据项目需求和业务逻辑进行调整和扩展。
已经是最低谷了,怎么走都是向上,所以,你怕什么。