首先,我们需要了解一下Spring AOP是什么,以及它是如何实现的。Spring AOP是基于JDK动态代理(基于接口)和CGLIB(基于类)实现的面向切面编程的一种框架。通过将横切逻辑与业务逻辑分离,可以更加灵活和方便地对系统进行管理,提高系统的可维护性、可扩展性和代码质量。
接下来,我们将使用Spring Boot AOP来实现读写分离和事务配置:
第一步:引入相关依赖
在pom.xml中加入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:配置读写分离
在application.yml中进行如下配置:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true
username: root
password: root
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
pool-name: DataCenter-HikariCP
test:
query: SELECT 1
jpa:
show-sql: false
hibernate:
ddl-auto: create-drop
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
id:
new_generator_mappings: true
jdbc:
fetch_size: 100
batch_size: 50
javax:
persistence:
query:
timeout: 10000
aop:
proxy-target-class: true
第三步:编写切面逻辑
在Spring Boot项目中,使用@Aspect注解表明该类是切面类,然后再使用@Pointcut来定义切点,@Before、@After等注解来定义切面逻辑。
比如在我们的业务代码中,读操作以“find”开头,写操作以“save”、“delete”、“update”开头,所以我们可以使用正则表达式来匹配方法名,判断该方法是读操作还是写操作。
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("execution(* com.example.demo.dao.*.find*(..))")
public void readPointcut(){
}
@Pointcut("execution(* com.example.demo.dao.*.save*(..)) " +
"|| execution(* com.example.demo.dao.*.delete*(..)) " +
"|| execution(* com.example.demo.dao.*.update*(..))")
public void writePointcut(){
}
@Before("readPointcut()")
public void switchSlaveDataSource(JoinPoint point){
//从库读取
DBContextHolder.slave();
}
@Before("writePointcut()")
public void switchMasterDataSource(JoinPoint point){
//主库写入
DBContextHolder.master();
}
}
这里的DBContextHolder是根据线程来判断当前使用的是主库还是从库,我们也需要在代码中编写两个数据源的配置。
public class DBContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String dataSource){
contextHolder.set(dataSource);
}
public static String getDataSourceKey(){
return contextHolder.get();
}
public static void master(){
setDataSourceKey("master");
}
public static void slave(){
setDataSourceKey("slave");
}
public static void clear(){
contextHolder.remove();
}
}
第四步:配置事务管理器
在事务管理中,我们需要使用@Transactional
注解来定义需要进行事务管理的方法。需要注意的是,事务管理只能应用在public方法上,并且它们必须是由其他对象调用的。
对于事务的配置,我们可以将事务管理器作为一个Bean来注入到Spring容器中。
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {
@Bean(name = "master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource getMasterDataSource(){
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "slave")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource getSlaveDataSource(){
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "routingDataSource")
public DataSource getRoutingDataSource(@Qualifier("master") DataSource masterDataSource,
@Qualifier("slave") DataSource slaveDataSource){
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(masterDataSource);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("routingDataSource") DataSource
routingDataSource) {
return new DataSourceTransactionManager(routingDataSource);
}
}
第五步:编写业务逻辑
最后,我们需要编写我们的业务逻辑代码。业务逻辑中的读操作以“find”开头,写操作以“save”、“delete”、“update”开头,方法名与切面逻辑中定义的正则表达式保持一致。
以简单的增删改查为例:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Transactional
@Override
public void save(User user) {
userRepository.save(user);
}
@Transactional
@Override
public void deleteById(Long id) {
userRepository.deleteById(id);
}
@Transactional
@Override
public void update(User user) {
userRepository.save(user);
}
@Override
public User findById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("Not found!"));
}
}
综上所述,我们通过引入相关依赖、配置读写分离和事务管理器、编写切面逻辑和业务逻辑,就可以实现Spring Boot AOP对读写分离和事务管理的配置。
示例一:实现读写分离
假设我们有一个User实体类和一个UserRepository,如下:
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByUsername(String username);
}
我们可以使用以下测试类来测试读写分离是否配置成功:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class RoutingDataSourceTest {
@Autowired
private UserService userService;
@Test
public void test() {
User user = new User();
user.setUsername("test");
user.setPassword("test");
userService.save(user);
for (int i = 0; i < 100; i++) {
userService.findById(Long.valueOf(i+1));
}
}
}
在这个测试类中,我们通过在循环中调用findById方法,来检查读操作是否都是在从库上进行。
示例二:实现事务管理
我们可以通过在Service层加上@Transactional注解,来实现事务管理。比如:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Transactional
@Override
public void save(User user) {
userRepository.save(user);
}
}
在这个示例中,我们对save方法加上了@Transactional注解,使其具有事务管理的功能。具体来说,当我们在执行该方法时,如果发现有任何一步操作失败,那么整个事务都会回滚。
再例如删除用户的操作:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Transactional
@Override
public void deleteById(Long id) {
userRepository.deleteById(id);
}
}
在这个示例中,我们同样使用了@Transactional注解来表明该方法是需要进行事务管理的。如果在执行删除操作时出现了任何异常,那么整个事务都会进行回滚,防止数据不一致的问题出现。
综上所述,我们通过在Service层中引入@Transactional注解,就可以将业务逻辑代码与事务管理逻辑代码分离开来,提高代码的可维护性和可扩展性。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:使用springboot aop来实现读写分离和事物配置 - Python技术站