SQLのバッチ実行

大量にUPDATE, DELETE, INSERTする場合JDBCのバッチ実行機能を使用しないとパフォーマンスが悪化する。

バッチ実行でなくとも、INSERTはまとめて実行できるし、DELETEはWHERE句でINを使って絞ることできれば一度の実行で済むことも多い。ただ複数のUPDATEを一度のSQL実行で済ますことは難しいことが多い。

MyBatisでもExecutorType.BATCHを設定したSqlSessionを利用することでバッチ処理ができる。

MyBatisの通常の実行モード

ただ通常アプリ内でバッチ実行する箇所は限られているため、SpringにDIさせるSqlSessionはデフォルトの実行モードのものにするのが普通。

Spring Bootで特に設定をしなければ、UserMapperはデフォルトのExecutorType.SIMPLEを使い、ステートメントを実行するたびに新しいPreparedStatementを作成するようになっている。

@Autowired
UserMapper userMapper;

public void insert() {
    userMapper.insert(user);
}

バッチモードに変更する

特定の処理クラスでバッチ実行に変更したい場合、ExecutorType.BATCHを設定したSqlSessionTemplateが注入されたMapperクラスを取得する必要がある。

Spring BootのJava ConfigでそのようなSqlSessionTemplateを取得できるように設定を書き、そのSqlSessionTemplateが注入される専用のMapperクラスを用意することもできるが、バッチ実行する箇所が1, 2箇所と限られている場合はその場所だけでアドホックにSqlSessionFactoryからExecutorType.BATCHを設定したSqlSessionTemplateを生成するのが簡単。

@Service
public class BatchService {

    private final SqlSession sqlSession;
    
    public BatchService(@Autowired SqlSessionFactory sqlSessionFactory) {
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
    }
    
    @Transactional
    public void insertBatch() {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        
        int count = 0;
        int size = users.size();
        for (var user : users) {
            count++;
            userMapper.insert(user);
            // 1000件ごとと最後にフラッシュ
            if (count % 1000 == 0 || count == size) {
                sqlSession.flushStatements();
            }
        }
    }
}