今回は、Spring BootとMyBatisを使用して開発している際に直面した、PlatformTransactionManagerをカスタム定義した際にデータベースへの挿入が反映されない問題について共有します。この問題の原因と解決策を詳しく解説しますので、同じような課題を抱えている方の参考になれば幸いです。
問題の背景
Spring BootプロジェクトでMyBatisを使用してデータベース操作を行っていました。以下のような設定クラスを作成し、PlatformTransactionManagerをカスタム定義しました。
java
package com.burgerhub.config;
import lombok.Data;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@ConfigurationProperties("spring.datasource")
@Configuration
@MapperScan("com.burgerhub.mybatis.order")
@Data
public class MyBatisConfig {
private String url;
private String username;
private String password;
private String driverClassName;
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.url(this.url)
.username(this.username)
.password(this.password)
.driverClassName(this.driverClassName)
.build();
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("/com/burgerhub/mybatis/order/CustomOrderMapper.xml"));
factoryBean.setConfigLocation(new ClassPathResource("/mybatis-config.xml"));
return factoryBean.getObject();
}
// トランザクションマネージャーのBeanを定義
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
この設定を行ったところ、データベースへのINSERT操作が行われないという問題が発生しました。実際にはSQLが発行されているものの、データベースにはデータが反映されませんでした。しかし、PlatformTransactionManagerのBean定義を削除すると、挿入が正常に行われるようになりました。
なぜこの問題が発生したのか?
-
PlatformTransactionManagerをカスタム定義するとSpring Bootの自動設定が上書きされる
Spring Bootは、多くのコンポーネントを自動的に設定してくれる「自動設定」機能を持っています。データベース操作に関しても、DataSourceが存在する場合、DataSourceTransactionManagerが自動的に設定されます。しかし、ユーザーが明示的にPlatformTransactionManagerのBeanを定義すると、Spring Bootの自動設定はこのカスタム定義を優先し、デフォルトのトランザクションマネージャーを上書きします。 -
トランザクション管理が適切に有効化されていない
PlatformTransactionManagerを定義するだけでは、Springはトランザクション管理を自動的に適用しません。トランザクション管理を有効にするためには、以下の2つのステップが必要です。
@EnableTransactionManagementの追加
@Transactionalアノテーションの適用
今回の設定では、PlatformTransactionManagerを定義しましたが、@EnableTransactionManagementが設定されていなかったため、Springはトランザクション管理を認識せず、@Transactionalアノテーションも無視されました。その結果、トランザクションが開始されず、データベースへのINSERT操作がコミットされずにロールバックされてしまったのです。
質問1: PlatformTransactionManagerを定義するとSpringのデフォルトが適用されず、なぜデータの挿入ができなくなったのか?
自動コミット設定の変更
デフォルト動作: Spring Bootの自動設定では、DataSourceの自動コミットモードが有効になっており、各SQL操作が即座にコミットされます。
カスタム定義時: PlatformTransactionManagerをカスタム定義すると、Springは自動コミットを無効化し、明示的なトランザクション管理を期待します。しかし、@EnableTransactionManagementや@Transactionalが設定されていないため、トランザクションが開始されてもコミットされず、結果としてロールバックされます。
トランザクション管理の有効化がされていない
@EnableTransactionManagementがないため、Springはトランザクション管理を有効化せず、@Transactionalアノテーションも認識しません。これにより、トランザクションの境界が設定されず、コミットが行われない状態になります。
解決策
@EnableTransactionManagementの追加
設定クラスに@EnableTransactionManagementを追加して、Springにトランザクション管理を有効化するよう指示します。
java
@Configuration
@EnableTransactionManagement
@MapperScan("com.burgerhub.mybatis.order")
@ConfigurationProperties("spring.datasource")
@Data
public class MyBatisConfig {
///コード
}
@Transactionalの適用
トランザクションが必要なサービス層やメソッドに@Transactionalアノテーションを付与します。
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
public void createOrder(Order order) {
orderMapper.insertOrder(order);
// 他のデータ操作
}
}
デフォルトのトランザクションマネージャーを使用する
特にカスタムのトランザクションマネージャーが必要ない場合、PlatformTransactionManagerのBean定義を削除し、Spring Bootの自動設定に任せることで問題を回避できます。
質問2: 自動コミットが無効な場合、@Transactionalが付与されていなくてもデータは挿入されるのではないか?
自動コミットとトランザクションの関係
自動コミットが有効な場合: 各SQL文が実行されると即座にコミットされ、データベースに反映されます。この場合、@Transactionalアノテーションがなくてもデータは挿入されます。
自動コミットが無効な場合: トランザクション内で明示的にコミット(またはロールバック)を行う必要があります。@Transactionalが付与されていないと、トランザクションが開始されてもコミットされず、接続がクローズされる際にロールバックされます。結果として、データベースにはデータが挿入されません。
なぜ自動コミットが無効になるのか?
PlatformTransactionManagerが定義されている場合、Springはトランザクション管理を行うためにデータソースの自動コミットを無効化します。これにより、@Transactionalが適用されていない場合、デフォルトでコミットが行われず、ロールバックされてしまうのです。
まとめ
PlatformTransactionManagerの定義: カスタムでPlatformTransactionManagerを定義すると、Springは自動コミットを無効化し、明示的なトランザクション管理を期待します。
トランザクション管理の有効化: @EnableTransactionManagementと@Transactionalを適切に設定することで、トランザクションが正しくコミットされ、データベースへの挿入が反映されます。
自動コミットとの関係: 自動コミットが無効な状態では、トランザクションが明示的にコミットされない限り、データはデータベースに反映されません。
Spring BootとMyBatisを使用する際に、PlatformTransactionManagerをカスタム定義すると、トランザクション管理が適切に有効化されていない場合、データベースへの挿入が反映されない問題が発生します。これを防ぐためには、以下の点に注意する必要があります。
@EnableTransactionManagementの設定: トランザクション管理を有効にするために、設定クラスに@EnableTransactionManagementを追加します。
@Transactionalの適用: トランザクションが必要なサービス層やメソッドに@Transactionalアノテーションを付与します。
デフォルト設定の利用: 特別な要件がない場合は、Spring Bootの自動設定に任せることで、トランザクション管理に関する設定ミスを防ぐことができます。
これらのポイントを押さえることで、Spring BootとMyBatisを使用したアプリケーションでのトランザクション管理を適切に行い、データの整合性を保ちながら正しくデータベース操作を実行できるようになります。