就像我在评论中所说,您的周围建议所做的也必须声明为事务性的。您不能直接这样做,因为@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")
);
}
}
看到了吗?我们通过 joinpoint 实例传递给辅助组件自己的@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;
}
如果 then 在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]]]
即您会看到,只有成功的预订事务才会向 DB 写入日志消息,而不是两个失败的事务。