Бросьте исключение при пропущенном @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 над методами сервиса.