Spring Boot + MyBatisでPlatformTransactionManagerをカスタム定義するとデータ挿入が反映されない問題とその解決方法

今回は、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定義を削除すると、挿入が正常に行われるようになりました。

## なぜこの問題が発生したのか？


1. PlatformTransactionManagerをカスタム定義するとSpring Bootの自動設定が上書きされる
Spring Bootは、多くのコンポーネントを自動的に設定してくれる「自動設定」機能を持っています。データベース操作に関しても、DataSourceが存在する場合、DataSourceTransactionManagerが自動的に設定されます。しかし、ユーザーが明示的にPlatformTransactionManagerのBeanを定義すると、Spring Bootの自動設定はこのカスタム定義を優先し、デフォルトのトランザクションマネージャーを上書きします。

2. トランザクション管理が適切に有効化されていない
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を使用したアプリケーションでのトランザクション管理を適切に行い、データの整合性を保ちながら正しくデータベース操作を実行できるようになります。