我正在尝试使用Spring AOP在DB表中实现日志记录。通过“登录表”我的意思是在域对象的常用表中写入有关创建/更新/删除的记录的特殊日志表信息。
我编写了部分代码并且除了一件事之外一切正常 - 当回滚事务时,日志表中的更改仍然成功提交。这对我来说很奇怪,因为在我的AOP建议中,同样的事务正在我的业务和DAO层中使用。 (根据我的AOP建议,我使用事务传播MANDATORY调用了特殊管理器类的方法,并且我在业务层,dao层和AOP建议中检查了事务名称 TransactionSynchronizationManager.getCurrentTransactionName(),它们是相同的)。
有没有人试图在实践中实施类似的东西?是否可以在AOP建议中使用与业务层相同的事务,并在业务层中发生某些错误时在AOP建议中进行回滚更改?
提前感谢您的回复。
修改
我想澄清一下,回滚问题仅发生在AOP建议的更改中。在DAO层中进行的所有更改都将成功回滚。我的意思是,例如,如果抛出一些异常,那么DAO层中所做的更改将成功回滚,但是在日志表中信息将被保存(提交)。但我无法理解为什么会这样,因为正如我在AOP建议中所写的那样,同一个交易正在使用。
编辑2
我用调试器检查了我在AOP建议中写入日志表的代码片段,在我看来,JdbcTemplate的update方法执行外部事务,因为更改已在执行语句后直接提交给DB在交易方法完成之前。
编辑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;
}
}
以下是我的db日志管理器类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);
}
}
答案 0 :(得分:2)
可能与建议的顺序有关 - 您希望您的@Transaction相关建议在您的日志记录相关建议周围(或之前和之后)生效。如果您正在使用Spring AOP,您可以使用建议的order属性来控制它 - 为您的事务相关建议提供最高优先级,以便它在出路时执行最后一次。
答案 1 :(得分:0)
与AOP无关,将datasource属性autocommit
设置为false,如:
<bean id="datasource" ...>
<property name="autoCommit" value="false/>
</bean>
如果您使用的是xml配置