无法在 Spring 引导应用程序的方面回滚数据库更改
Not able to rollback DB changes in Aspect in Spring boot application
我已经围绕一项服务编写了一个方面 class。方面,我在做before部分的一些操作,如果封装的服务方法出现异常,我想回滚。
服务class如下:
@Service
@Transactional
class ServiceA {
...
public void doSomething() {
...
}
...
}
看点如下:
@Aspect
@Order(2)
public class TcStateManagementAspect {
...
@Around(value = "applicationServicePointcut()", argNames = "joinPoint")
public Object process(ProceedingJoinPoint joinPoint)
throws Throwable {
...
*/Before section */
do some processing and persist in DB
...
Object object = joinPoint.proceed();
...
do some post-processing
}
}
我在 Begin Section
中看到服务方法中的异常未回滚数据库操作。我试着把 @Transactional
放在 @Around
上,但没有用。
在这种情况下,我浏览了以下帖子:
- Spring @Transactional in an Aspect (AOP)
但是对于如何实现这一点,我无法得到任何具体的想法。有人可以在这里帮忙吗?谢谢。
正如我在评论中所说,您的 around 建议所做的也必须声明为事务性的。您不能直接这样做,因为 @Transactional
在内部通过动态代理使用 Spring AOP。但是,Spring AOP方面不能成为其他方面的目标。但是您可以简单地创建一个新的助手 @Component
,您将建议的操作委托给它。
让我们假设目标是记录您的方面所针对的 @Transactional
方法的参数。然后只需这样做:
package com.example.managingtransactions;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TxLogAspect {
private final static Logger logger = LoggerFactory.getLogger(TxLogAspect.class);
@Autowired
TxLogService txLogService;
@Pointcut(
"@annotation(org.springframework.transaction.annotation.Transactional) && " +
"!within(com.example.managingtransactions.TxLogService)"
)
public void applicationServicePointcut() {}
@Around("applicationServicePointcut()")
public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info(joinPoint.toString());
// Delegate to helper component in order to be able to use @Transactional
return txLogService.logToDB(joinPoint);
}
}
package com.example.managingtransactions;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
/**
* Helper component to delegate aspect advice execution to in order to make the
* advice transactional.
* <p>
* Aspect methods themselves cannot be @Transactional, because Spring AOP aspects
* cannot be targeted by other aspects. Delegation is a simple and elegant
* workaround.
*/
@Component
public class TxLogService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public Object logToDB(ProceedingJoinPoint joinPoint) throws Throwable {
jdbcTemplate.update(
"insert into TX_LOG(MESSAGE) values (?)",
Arrays.deepToString(joinPoint.getArgs())
);
return joinPoint.proceed();
}
public List<String> findAllTxLogs() {
return jdbcTemplate.query(
"select MESSAGE from TX_LOG",
(rs, rowNum) -> rs.getString("MESSAGE")
);
}
}
看到了吗?我们通过连接点实例传递给辅助组件自己的 @Transactional
方法,这意味着事务在进入该方法时启动,并根据 joinPoint.proceed()
的结果提交或回滚。 IE。如果方面的目标方法出现问题,方面助手写入数据库本身的内容也将回滚。
顺便说一句,因为我以前从未使用过Spring事务,所以我只是简单地从https://spring.io/guides/gs/managing-transactions/中获取示例并添加了上面的两个类。之前,我也将此添加到 schema.sql
:
create table TX_LOG(ID serial, MESSAGE varchar(255) NOT NULL);
接下来,我添加确保 TxLogService
被注入 AppRunner
:
private final BookingService bookingService;
private final TxLogService txLogService;
public AppRunner(BookingService bookingService, TxLogService txLogger) {
this.bookingService = bookingService;
this.txLogService = txLogger;
}
如果那么在AppRunner.run(String...)
的末尾添加这两个语句
logger.info("BOOKINGS: " + bookingService.findAllBookings().toString());
logger.info("TX_LOGS: " + txLogService.findAllTxLogs().toString());
您应该在控制台日志的末尾看到类似这样的内容:
c.e.managingtransactions.AppRunner : BOOKINGS: [Alice, Bob, Carol]
c.e.managingtransactions.AppRunner : TX_LOGS: [[[Alice, Bob, Carol]]]
即您会看到只有成功的预订交易才会将日志消息写入数据库,而不是两个失败的交易。
我已经围绕一项服务编写了一个方面 class。方面,我在做before部分的一些操作,如果封装的服务方法出现异常,我想回滚。
服务class如下:
@Service
@Transactional
class ServiceA {
...
public void doSomething() {
...
}
...
}
看点如下:
@Aspect
@Order(2)
public class TcStateManagementAspect {
...
@Around(value = "applicationServicePointcut()", argNames = "joinPoint")
public Object process(ProceedingJoinPoint joinPoint)
throws Throwable {
...
*/Before section */
do some processing and persist in DB
...
Object object = joinPoint.proceed();
...
do some post-processing
}
}
我在 Begin Section
中看到服务方法中的异常未回滚数据库操作。我试着把 @Transactional
放在 @Around
上,但没有用。
在这种情况下,我浏览了以下帖子:
- Spring @Transactional in an Aspect (AOP)
但是对于如何实现这一点,我无法得到任何具体的想法。有人可以在这里帮忙吗?谢谢。
正如我在评论中所说,您的 around 建议所做的也必须声明为事务性的。您不能直接这样做,因为 @Transactional
在内部通过动态代理使用 Spring AOP。但是,Spring AOP方面不能成为其他方面的目标。但是您可以简单地创建一个新的助手 @Component
,您将建议的操作委托给它。
让我们假设目标是记录您的方面所针对的 @Transactional
方法的参数。然后只需这样做:
package com.example.managingtransactions;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TxLogAspect {
private final static Logger logger = LoggerFactory.getLogger(TxLogAspect.class);
@Autowired
TxLogService txLogService;
@Pointcut(
"@annotation(org.springframework.transaction.annotation.Transactional) && " +
"!within(com.example.managingtransactions.TxLogService)"
)
public void applicationServicePointcut() {}
@Around("applicationServicePointcut()")
public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info(joinPoint.toString());
// Delegate to helper component in order to be able to use @Transactional
return txLogService.logToDB(joinPoint);
}
}
package com.example.managingtransactions;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
/**
* Helper component to delegate aspect advice execution to in order to make the
* advice transactional.
* <p>
* Aspect methods themselves cannot be @Transactional, because Spring AOP aspects
* cannot be targeted by other aspects. Delegation is a simple and elegant
* workaround.
*/
@Component
public class TxLogService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public Object logToDB(ProceedingJoinPoint joinPoint) throws Throwable {
jdbcTemplate.update(
"insert into TX_LOG(MESSAGE) values (?)",
Arrays.deepToString(joinPoint.getArgs())
);
return joinPoint.proceed();
}
public List<String> findAllTxLogs() {
return jdbcTemplate.query(
"select MESSAGE from TX_LOG",
(rs, rowNum) -> rs.getString("MESSAGE")
);
}
}
看到了吗?我们通过连接点实例传递给辅助组件自己的 @Transactional
方法,这意味着事务在进入该方法时启动,并根据 joinPoint.proceed()
的结果提交或回滚。 IE。如果方面的目标方法出现问题,方面助手写入数据库本身的内容也将回滚。
顺便说一句,因为我以前从未使用过Spring事务,所以我只是简单地从https://spring.io/guides/gs/managing-transactions/中获取示例并添加了上面的两个类。之前,我也将此添加到 schema.sql
:
create table TX_LOG(ID serial, MESSAGE varchar(255) NOT NULL);
接下来,我添加确保 TxLogService
被注入 AppRunner
:
private final BookingService bookingService;
private final TxLogService txLogService;
public AppRunner(BookingService bookingService, TxLogService txLogger) {
this.bookingService = bookingService;
this.txLogService = txLogger;
}
如果那么在AppRunner.run(String...)
的末尾添加这两个语句
logger.info("BOOKINGS: " + bookingService.findAllBookings().toString());
logger.info("TX_LOGS: " + txLogService.findAllTxLogs().toString());
您应该在控制台日志的末尾看到类似这样的内容:
c.e.managingtransactions.AppRunner : BOOKINGS: [Alice, Bob, Carol]
c.e.managingtransactions.AppRunner : TX_LOGS: [[[Alice, Bob, Carol]]]
即您会看到只有成功的预订交易才会将日志消息写入数据库,而不是两个失败的交易。