我正在尝试使用 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(对于所有其他表),现在一切正常。
谢谢大家的帮助,很抱歉打扰了。
如果有人感兴趣,这里有一个简化的例子来说明我的方法。
考虑具有保存方法的 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;
}
}
这是我的数据库日志管理器类 LooksLike:
@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);
}
}