是否可以在Spring AOP建议中使用事务?

时间:2012-06-26 09:29:33

标签: java spring transactions spring-aop

我正在尝试使用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);
        }
    }

2 个答案:

答案 0 :(得分:2)

可能与建议的顺序有关 - 您希望您的@Transaction相关建议在您的日志记录相关建议周围(或之前和之后)生效。如果您正在使用Spring AOP,您可以使用建议的order属性来控制它 - 为您的事务相关建议提供最高优先级,以便它在出路时执行最后一次。

答案 1 :(得分:0)

与AOP无关,将datasource属性autocommit设置为false,如:

<bean id="datasource" ...>
    <property name="autoCommit" value="false/>
</bean>

如果您使用的是xml配置