【发布时间】:2012-06-26 09:29:33
【问题描述】:
我正在尝试使用 Spring AOP 在 DB 表中实现日志记录。通过“登录表”,我的意思是在域对象的常用表中写入有关在常规表中创建/更新/删除的记录的特殊日志表信息。
我编写了部分代码,除了一件事外,一切都运行良好 - 当事务回滚时,日志表中的更改仍然成功提交。这对我来说很奇怪,因为在我的 AOP 建议中,我的业务和 DAO 层使用了相同的事务。 (根据我的 AOP 建议,我使用事务传播 MANDATORY 调用了特殊管理器类的方法,并且我在业务层、dao 层和 AOP 建议中检查了事务名称 TransactionSynchronizationManager.getCurrentTransactionName(),它是相同的)。
有没有人尝试在实践中实现类似的东西?是否可以在 AOP 建议中使用与业务层相同的事务,如果业务层发生错误,是否可以回滚 AOP 建议中所做的更改?
提前感谢您的回答。
编辑
我想澄清回滚问题仅发生在根据 AOP 建议所做的更改。在 DAO 层中所做的所有更改都已成功回滚。我的意思是,例如,如果抛出一些异常,那么在 DAO 层所做的更改将被成功回滚,但在日志表中的信息将被保存(提交)。但我不明白为什么会这样,因为正如我上面在 AOP 建议中所写的那样,正在使用同一个事务。
编辑 2
我用调试器检查了我在 AOP 建议中写入日志表的代码片段,在我看来,JdbcTemplate 的更新方法在事务外部执行,因为在执行语句后直接将更改提交到数据库,并且在事务方法完成之前。
编辑 3
我解决了这个问题。其实那是我愚蠢的错。我正在使用 MySQL。创建日志表后,我没有更改数据库引擎,HeidySQL 默认设置了 MyIsam。但是 MyIsam 不支持事务,所以我将 DB 引擎更改为 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);
}
}
【问题讨论】:
-
如果您包含一个 SSCCE 示例来证明您是交易问题,这将非常有帮助。
-
好的。我将添加一个示例,但稍后会添加,因为我有紧急任务。谢谢
标签: java spring transactions spring-aop