【问题标题】:Throw exception on missing @Transactional with Spring and MyBatis使用 Spring 和 MyBatis 在缺少 @Transactional 时抛出异常
【发布时间】:2017-01-20 16:20:58
【问题描述】:

我正在尝试设置使用 Spring 和 MyBatis 的网络应用程序。

这里是重要的sn-ps代码。

pom.xml 中的 Maven 依赖项:

    ...
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>4.3.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>9.4.1212.jre7</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.2</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.1</version>
    </dependency>
    ...

以及Spring bean的配置:

@Configuration
@EnableTransactionManagement
public class DatabaseConfiguration {

    @Value("classpath:db/mybatis/mybatis-configuration.xml")
    private Resource myBatisConfiguration;

    @Bean
    public DataSource dataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/ehdb");
        dataSource.setUsername(/*my username*/);
        dataSource.setPassword(/*my password*/);
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        final DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource());
        transactionManager.setValidateExistingTransaction(true);
        return transactionManager;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() {
        final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource());
        sqlSessionFactory.setConfigLocation(myBatisConfiguration);
        return sqlSessionFactory;
    }

    @Bean
    public SqlSession sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactoryBean().getObject());
    }
}

这里有一个服务应该在事务方法中调用一些 MyBatis 语句:

@Service
public class HelloManagerImpl implements HelloManager {
    private final HelloDao helloDao;

    public HelloManagerImpl(@Autowired final HelloDao helloDao) {
        this.helloDao = helloDao;
    }

    @Override
    @Transactional
    public String doSomething() {
        helloDao.insertRow(); // a row is inserted into DB table via MyBatis; bean sqlSession is autowired in HelloDao
        throw new RuntimeException(); // transaction will be rolled back here
    }
}

如果我调用方法doSomething,它会按预期工作。由于抛出RuntimeException,事务被回滚,数据库表中没有新行出现。

如果我注释掉 throw 语句并重复实验,数据库表中会出现一个新行。这又是预期的行为。

现在,如果我另外注释掉@Transactional 注释并调用doSomething(),则该方法成功并在表中插入新行。如果不存在事务,MyBatis 似乎会自动为INSERT 语句创建一个事务。

我宁愿在最后一种情况下失败。如果我忘记写@Transactional注解,那很可能是一个错误。如果在这种情况下抛出异常迫使我修复我的代码而不是静默创建一些事务,那会很好。

请问有办法实现吗?

感谢您的帮助。

【问题讨论】:

  • 你的数据库支持事务吗?如您所知,MYISAM 引擎没有。
  • @Forward 是的,我使用的是 Postgres 9.6。

标签: spring mybatis spring-mybatis


【解决方案1】:

我建议将所有事务设置为只读以实现您的最后一种情况,并且只注释应该写入数据库的方法。例如,您的服务如下所示:

@Service
@Transactional(readOnly = true)
public class HelloManagerImpl implements HelloManager {
  private final HelloDao helloDao;

  public HelloManagerImpl(@Autowired final HelloDao helloDao) {
    this.helloDao = helloDao;
  }

  @Override
  @Transactional(readOnly = false)
  public String doSomething() {
    helloDao.insertRow(); // a row is inserted into DB table via MyBatis; bean sqlSession is autowired in HelloDao
    throw new RuntimeException(); // transaction will be rolled back here
  }
}

【讨论】:

  • +1这不是我最初寻找的 MyBatis 配置,但它是一个合理的解决方法。我也在考虑在 DAO 类或方法上使用@Transactional(propagation = Propagation.MANDATORY)
【解决方案2】:

事务的行为由 Spring 自己管理,Spring 会在 RuntimeException 的情况下回滚事务。

如果你不提供@Transactional注解,spring不会把它当作一个事务,并且在RuntimeException的情况下什么也不做。因此,请养成将@Transactional 注解放在服务方法之上的好习惯。

【讨论】:

  • 是的,我知道 Spring 无法在没有 @Transactional 注释的情况下回滚事务。 MyBatis(或我自己的自定义 MyBatis 的代码)应该抛出异常。如果我调用 MyBatis(例如sqlSession.insert("statementId")),如果没有事务正在进行,则应该抛出异常。我正在开发另一个使用 Hibernate 而不是 MyBatis 的项目,并且在类似的情况下会引发异常。
  • 你是在MyBatis任务完成后抛出RuntimeException,所以,没有影响RuntimeException
  • 我知道。 :) helloDao.insertRow() 的实现只调用了sqlSession.insert("myStatementId");。方法insert 应该抛出异常。在将任何 SQL 语句发送到数据库之前,MyBatis 应该抛出异常。我正在寻找类似MyBatisConfiguration.setThrowExceptionWheneverAnyMethodOnSqlSessionIsCalledWithoutPreceedingTransactionalAnnotation(true); 的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
  • 2013-10-27
  • 1970-01-01
  • 2022-10-24
  • 1970-01-01
  • 2015-05-01
  • 1970-01-01
相关资源
最近更新 更多