【问题标题】:Is it possible to use transactions in Spring AOP advice?是否可以在 Spring AOP 建议中使用事务?
【发布时间】: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


【解决方案1】:

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

【讨论】:

  • 谢谢。我考虑了这一点,并试图改变建议的顺序。但这没有帮助。
【解决方案2】:

与AOP无关,将数据源属性autocommit设置为false,如:

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

如果你使用的是xml配置

【讨论】:

  • 可能我理解错了,但在我看来,将 autocommit 设置为 true 对我没有帮助。只有当 tarnsaction 成功完成时,我才需要提交更改(也在 AOP 建议中进行)。但是,如果我将 autoCommit 设置为 true,那么每个语句将在执行后自动提交。这将破坏业务逻辑(在我的情况下,我需要在一个事务中的几个操作成功完成后才需要在 DB 中提交更改)。
  • @dimas :是的,我错了,实际上您需要将其设置为 false。默认情况下它是真的。编辑了答案。
  • 我正在使用 c3p0 数据源,并且 setAutoCommitOnClose 探测器默认为 false。 (但在 dbcp BasicDataSource 默认自动提交值为 true,你是对的。)顺便说一下,在我的情况下,回滚操作对于从业务层和 DAO 层运行的代码是正确的,问题只发生在运行的代码上来自 AOP 的建议。
猜你喜欢
  • 2014-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多