Бросьте исключение при пропущенном @Transactional с Spring и MyBatis

Я пытаюсь настроить свое веб-приложение, которое использует Spring и MyBatis.

Вот важные фрагменты кода.

Maven зависимости в pom.xml:

    ...
    <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:

@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 аннотация, это, вероятно, ошибка. Было бы хорошо, если в таком случае выдается исключение, вынуждающее меня исправить мой код, а не создавать какую-либо транзакцию без вывода сообщений.

Есть ли способ достичь этого, пожалуйста?

Спасибо за помощь.

2 ответа

Я бы предложил установить все транзакции только для чтения для достижения вашего последнего случая и только аннотировать методы, которые должны записываться в базу данных. Например, ваш сервис будет выглядеть так:

@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
  }
}

Поведение транзакции управляется самой Spring, и Spring возвращает транзакцию в случае RuntimeException.

Если вы не предоставите аннотацию @Transactional, Spring не будет рассматривать ее как транзакцию и ничего не сделает в случае RuntimeException. Итак, сделайте хорошую практику для размещения аннотации @Transactional над методами сервиса.

Другие вопросы по тегам