我正在尝试使用 Spring AOP 实现数据库表中的日志记录。通过“登录表”,我的意思是在特殊日志表中写入有关在域对象的常用表中创建/更新/删除的记录的信息。
我编写了部分代码,一切正常,除了一件事 - 当事务回滚时,日志表中的更改仍然成功提交。这对我来说很奇怪,因为在我的 AOP 建议中,我的业务和 DAO 层使用了相同的事务。 (根据我的 AOP 建议,我使用事务传播 MANDATORY 调用了特殊管理器类的方法,并且我还在业务层、dao 层和 AOP 建议中检查了事务名称 TransactionSynchronizationManager.getCurrentTransactionName() ,它是相同的)。
有人尝试在实践中实现类似的事情吗?是否可以在 AOP 建议中使用与业务层中相同的事务,并在业务层发生某些错误时回滚 AOP 建议中所做的更改?
提前感谢您的回答。
编辑
我想澄清一下,只有根据 AOP 建议进行的更改才会出现回滚问题。 DAO 层中所做的所有更改均已成功回滚。我的意思是,例如,如果抛出一些异常,那么 DAO 层中所做的更改将成功回滚,但日志表信息将被保存(提交)。但我无法理解为什么会这样,因为正如我在上面的 AOP 建议中所写的,正在使用相同的事务。
编辑2
我用调试器检查了我在AOP建议中写入日志表的代码片段,在我看来,JdbcTemplate的更新方法在事务外执行,因为更改已在执行语句后直接提交到数据库,并且在事务方法完成之前。
编辑3
我解决了这个问题。事实上,那是我愚蠢的错误。我正在使用MySQL。创建日志表后,我没有更改数据库引擎,HeidySQL 默认设置 MyIsam。但 MyIsam 不支持事务,因此我将数据库引擎更改为 InnoDB(对于所有其他表),现在一切正常。
感谢大家的帮助,抱歉打扰。
如果有人感兴趣,这里是说明我的方法的简化示例。
考虑具有 save 方法的 DAO 类:
@Repository(value="jdbcUserDAO")
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true, rollbackFor=Exception.class)
public class JdbcUserDAO implements UserDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
@LoggedOperation(affectedRows = AffectedRows.ONE, loggedEntityClass = User.class, operationName = OperationName.CREATE)
@Transactional(propagation=Propagation.REQUIRED, readOnly=false, rollbackFor=Exception.class)
@Override
public User save(final User user) {
if (user == null || user.getRole() == null) {
throw new IllegalArgumentException("Input User object or nested Role object should not be null");
}
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection)
throws SQLException {
PreparedStatement ps = connection.prepareStatement(SQL_INSERT_USER, new String[]{"ID"});
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getFullName());
ps.setLong(4, user.getRole().getId());
ps.setString(5, user.geteMail());
return ps;
}
}, keyHolder);
user.setId((Long) keyHolder.getKey());
VacationDays vacationDays = user.getVacationDays();
vacationDays.setId(user.getId());
// Create related vacation days record.
vacationDaysDAO.save(vacationDays);
user.setVacationDays(vacationDays);
return user;
}
}
以下是方面的样子:
@Component
@Aspect
@Order(2)
public class DBLoggingAspect {
@Autowired
private DBLogManager dbLogManager;
@Around(value = "execution(* com.crediteuropebank.vacationsmanager.server.dao..*.*(..)) " +
"&& @annotation(loggedOperation)", argNames="loggedOperation")
public Object doOperation(final ProceedingJoinPoint joinPoint,
final LoggedOperation loggedOperation) throws Throwable {
Object[] arguments = joinPoint.getArgs();
/*
* This should be called before logging operation.
*/
Object retVal = joinPoint.proceed();
// Execute logging action
dbLogManager.logOperation(arguments,
loggedOperation);
return retVal;
}
}
这是我的数据库日志管理器类的样子:
@Component("dbLogManager")
public class DBLogManager {
@Autowired
private JdbcTemplate jdbcTemplate;
@InjectLogger
private Logger logger;
@Transactional(rollbackFor={Exception.class}, propagation=Propagation.MANDATORY, readOnly=false)
public void logOperation(final Object[] inputArguments, final LoggedOperation loggedOperation) {
try {
/*
* Prepare query and array of the arguments
*/
jdbcTemplate.update(insertQuery.toString(),
insertedValues);
} catch (Exception e) {
StringBuilder sb = new StringBuilder();
// Prepare log string
logger.error(sb.toString(), e);
}
}
最佳答案
这可能与建议的顺序有关 - 您希望与 @Transaction 相关的建议在与日志记录相关的建议周围(或之前和之后)生效。如果您使用 Spring AOP,您可能可以使用通知的 order 属性来控制它 - 为与事务相关的通知提供最高优先级,以便它在退出时最后执行。
关于java - 是否可以在 Spring AOP 建议中使用事务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11204390/