前言
当在项目中想要实现读写分离,但是项目又不太好进行改造,又不想用其他中间插件,这个时候就想到可以利用spring中的aop和注解来实现读写分离,对于项目改造也是很小的,你只需要在想要它读写分离的代码上用上注解就可以实现,没有使用注解的就还是用默认的数据库。
1、创建一个自定义注解ReadWriteDataSource
,用于标识需要读数据源还是写数据源
package com.example.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReadWriteDataSource {
String value() default "readDataSource";
}
2、创建一个切面类ReadWriteDataSourceAspect
,在该切面类中定义切点和通知,根据解析SQL语句来确定使用读库还是写库。
package com.example.aspect;
import com.example.annotation.ReadWriteDataSource;
import com.example.datasource.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.regex.Pattern;
@Aspect
@Component
public class ReadWriteDataSourceAspect {
// 定义切点,拦截所有使用了ReadWriteDataSource注解的方法
@Pointcut("@annotation(com.example.annotation.ReadWriteDataSource)")
public void dataSourcePointcut() {}
// 前置通知,在方法执行之前设置数据源类型
@Before("dataSourcePointcut()")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
ReadWriteDataSource annotation = signature.getMethod().getAnnotation(ReadWriteDataSource.class);
String sql = extractSqlFromJoinPoint(joinPoint); // 从切点中解析出SQL语句
// 根据解析的SQL语句判断是读操作还是写操作
if (isReadOperation(sql)) {
DataSourceContextHolder.setDataSourceType("readDataSource");
} else {
DataSourceContextHolder.setDataSourceType("writeDataSource");
}
}
// 后置通知,清除数据源类型
@After("dataSourcePointcut()")
public void after(JoinPoint joinPoint) {
DataSourceContextHolder.clearDataSourceType();
}
// 从切点中解析出SQL语句
private String extractSqlFromJoinPoint(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
return args[0].toString(); // 这里假设SQL语句是方法的第一个参数
}
return null;
}
// 判断是否为读操作
private boolean isReadOperation(String sql) {
// 根据具体的SQL语句解析逻辑来判断是读操作还是写操作
// 这里简单地通过判断SELECT关键字来判断是读操作
return Pattern.matches("(?i)\\s*SELECT.*", sql);
}
}
3、在application.properties
(或application.yml
)配置文件中配置读写库的数据源。
# 数据源配置
spring.datasource.read.url=jdbc:mysql://localhost:3306/db_read?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.read.username=username_read
spring.datasource.read.password=password_read
spring.datasource.write.url=jdbc:mysql://localhost:3306/db_write?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.write.username=username_write
spring.datasource.write.password=password_write
4、创建两个数据源的配置类,分别为读库和写库。
package com.example.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
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.jdbc.datasource.DriverManagerDataSource;
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "readDataSource")
@ConfigurationProperties(prefix = "spring.datasource.read")
public DataSource readDataSource() {
return new DriverManagerDataSource();
}
@Bean(name = "writeDataSource")
@ConfigurationProperties(prefix = "spring.datasource.write")
public DataSource writeDataSource() {
return new DriverManagerDataSource();
}
}
5、配置AOP,将切面类加入到Spring Boot项目中。
package com.example.config;
import com.example.aspect.ReadWriteDataSourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public ReadWriteDataSourceAspect readWriteDataSourceAspect() {
return new ReadWriteDataSourceAspect();
}
}
6、在需要进行读写分离的方法上添加ReadWriteDataSource
注解,指定数据源类型。
@ReadWriteDataSource("readDataSource")
public List<User> findUsers() {
// 查询数据库操作...
}
@ReadWriteDataSource("writeDataSource")
public void saveUser(User user) {
// 保存数据库操作...
}
7、总结
通过以上步骤,就可以在Spring Boot项目中使用AOP和注解来实现解析SQL语句来实现读写分离啦。
如果一个方法没有标记@ReadWriteDataSource
注解,那么切面拦截器将不会触发,也就不会改变数据源的选择。此时,该方法将继续使用之前已经配置好的默认数据源。
注意的是这只是一个简单的示例,实际应用中可能需要更复杂的SQL解析逻辑和判断条件,所以需要根据具体情况进行适当的调整和扩展。
关于使用AOP和注解来实现读写分离对性能产生一些影响
在Spring Boot项目中使用AOP和注解来实现解析SQL语句以实现读写分离可能会对性能产生一些影响,但这取决于具体的实现方式和应用场景。以下是一些常见的性能影响因素:
-
切面拦截:AOP会在方法调用前后进行拦截和处理,这会增加一定的执行时间。尽管这个时间通常非常短暂,但在高并发环境下可能会有一些额外的开销。
-
SQL解析和判断:根据SQL语句来判断是读操作还是写操作可能需要进行复杂的解析和匹配逻辑。这可能会增加一些CPU和内存的消耗,特别是对于复杂的SQL语句解析。
-
数据源切换:根据SQL解析的结果来选择合适的数据源进行连接。数据源的切换可能涉及一些上下文切换和资源释放操作,这可能会带来一些额外的开销。
-
连接池管理:如果使用了连接池来管理数据库连接,那么每次切换数据源都需要从连接池中获取新的连接。这可能会导致一些连接的创建和销毁的开销。
要准确评估这些性能影响,建议进行针对性的性能测试和基准测试。可以通过模拟并发请求和大量数据操作来测量系统的响应时间、吞吐量和资源利用率等指标,并对比使用和不使用AOP和注解的情况。
此外,为了最小化性能影响,可以考虑以下几点优化:
- 优化SQL解析逻辑,使用高效的算法和数据结构,避免不必要的正则表达式匹配或字符串处理操作。
- 合理配置连接池的参数,如最大连接数、最小空闲连接数等,以平衡性能和资源消耗。
- 对于频繁访问的读操作,可以考虑使用缓存技术来减少对数据库的访问次数,从而提高性能。
总之,在实际应用中,需要根据具体情况评估性能影响并进行合适的优化。读写分离的好处通常会超过潜在的性能开销,特别是在高负载和大规模数据操作场景下。