【问题标题】:Spring JDBC Transactional side effectsSpring JDBC 事务的副作用
【发布时间】:2015-01-20 17:04:10
【问题描述】:

我有 2 个 MySQL 表:作业和摘要。

我正在尝试检查事务管理是否确实按预期工作:

下面是函数:

@Transactional("mysqlTransactionManager")
public void sumLicenseDayUsage(List<LicenseUsage> usages)
{
        Job job = startJob();

        calculateDailyUsage(usages);

        updateJob(usages, job);
        completeJob(job);
}

我确保 calculateDailyUsage() 会引发异常。这里的不同功能是使用 MyBatis 映射器来执行实际的 SQL 命令。

我希望当抛出异常时,作业表中不会存在任何行,尽管它们是在 startJob() 中创建的。仍然始终创建一行,并且从不回滚。

我想我是根据文档在书中使用它,但显然我一定遗漏了一些东西。

与我使用 MyBatis 的事实有什么关系吗?还是有 2 个表,因此有 2 个不同的映射器?从文档和各种测试来看,IT 看起来并不像它。

这是我的 XML 配置:

<tx:annotation-driven transaction-manager="mysqlTransactionManager"/>

<bean id="mysqlBuilder" class="com.company.project.mysqlutils.EmbeddedMysqlDatabaseBuilder">
    <constructor-arg value="sql/create_sum_tables.sql"/>
</bean>


<bean id="billingDB" class="com.company.project.mysqlutils.EmbeddedMysqlDatabase" factory-bean="mysqlBuilder" factory-method="build"
      destroy-method="shutdown"/>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="billingDB" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="billingDB" />
    <property name="mapperLocations" value="classpath*:META-INF/mappers/mysql/**.xml" />
</bean>

<bean id="mysqlTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="billingDB" />
</bean>

<bean id="sumLicenseDayMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.company.project.sumtables.mappers.SumLicenseDayMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

<bean id="jobsMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.company.project.sumtables.mappers.JobsMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

提前谢谢

【问题讨论】:

    标签: java mysql spring mybatis spring-jdbc


    【解决方案1】:

    回答我自己的问题,因为我认为这个问题被许多人所忽视,并且可能对应用程序的事务方案产生巨大影响

    这个问题原来与 Spring 如何管理事务无关。这实际上非常有效。

    然而,数据库(本例中为 MySQL)的行为是导致事务过早提交的原因。

    根据位于http://dev.mysql.com/doc/refman/5.5/en/implicit-commit.html 的 MySQL 文档,某些 DML 和 DDL 语句会导致当前事务的隐式提交。

    这对于 Oracle 和 Postgres 来说似乎也是如此,基于他们的文档以及 MySQL 报告的错误/功能,它指出 Oracle 的行为相同:http://bugs.mysql.com/bug.php?id=22857

    在我的例子中,函数 calculateDailyUsage() 中有一个“CREATE TABLE”语句,它导致了一个隐式提交,因此不可能回滚之前发生的所有 SQL 语句,以防在该语句之后出现问题.

    为了进一步解释它的重要性,假设我们设计了一个计费应用程序,其中过度计费是最严重的罪过,请参见下面的示例:

    @Transactional("mysqlTransactionManager")
    public void sumLicenseDayUsage(List<LicenseUsage> usages)
    {
            insertBillingData();
    
            createIntermediateTable();
    
            summarizeData();
    }
    

    在上面,如果summarizeData() 出现问题,我们肯定希望insertBillingData() 插入的数据能够回滚。但这实际上不会发生,因为中间语句 createIntermediateTable() 将实际提交事务,而 summariseData() 将在不同的事务中完成。这很糟糕,设计人员应该注意这一点,因为在应用过程中创建临时表是很常见的。

    克服的方法和我的解决方法是将所有 DDL/DML 语句放在函数的开头,以便它们在其事务中执行,而函数的其余部分在另一个可以滚动的事务中完成在出现问题时正确返回,如下所示:

    @Transactional("mysqlTransactionManager")
    public void sumLicenseDayUsage(List<LicenseUsage> usages)
    {
            createIntermediateTable();
    
            insertBillingData();
    
            summarizeData();
    }
    

    最后,要记住的一件事是,有时文档并不准确,例如 MySQL 文档指出“CREATE TEMPORARY TABLE”不会执行隐式提交,尽管在我彻底测试后它会执行。

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 2019-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多