1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Springのバッチ処理をMySQLで排他制御する

Posted at

複数サーバで稼働するSpringBootアプリケーションのバッチ処理をMySQLで排他制御したので備忘録として残します。

目的・前提

  • 複数のサーバ上で同じSpringBootアプリケーションを稼働する。
  • バッチ処理でDBのデータを操作するため排他制御する必要がある。
  • バッチ処理は @crontab で起動する。

ライブラリ等

  • spring-boot-starter: 2.6.4
  • mybatis-spring-boot-starter: 2.2.2
  • lombok: 1.18.22

結論

  • MySQLのロック関数で排他制御が可能。
  • ただしバッチ中にMySQLのセッションが変わることがあるので、 @Transactional で同一セッションであることを明示する。

実装

ロック関数を実行するRepository, Serviceを作成

  • get_lock, release_lock 等をするServiceを作成する。
/src/main/java/{name-space}/repository/LockRepository.java
@Repository
public interface LockRepository {

    Integer isFreeLock(@Param("str") String str);

    Integer getLock(@Param("str") String str, @Param("timeout") int timeout);

    Integer releaseLock(@Param("str") String str);
}

/src/main/resource/mapper/LockRepository.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="{package-name}.repository.LockRepository">

  <select id="isFreeLock" resultType="Integer">
    select is_free_lock(#{str})
  </select>

  <select id="getLock" resultType="Integer">
    select get_lock(#{str}, #{timeout})
  </select>

  <select id="releaseLock" resultType="Integer">
    select release_lock(#{str})
  </select>
</mapper>
/src/main/java/{package-name}/service/LockService.java
@Service
@RequiredArgsConstructor
public class LockService {

    public enum LockResult {
        SUCCESS,
        FAILURE
    }

    private final LockRepository lockRepository;

    public boolean isFreeLock(String str) {
        Integer result = lockRepository.isFreeLock(str);
        if (result == null) {
            throw new ApplicationException("Database error");
        }
        return result == 1;
    }

    public LockResult getLock(String str, int timeout) {
        Integer result = lockRepository.getLock(str, timeout);
        if (result == null) {
            throw new ApplicationException("Database error");
        }
        return result == 1 ? LockResult.SUCCESS : LockResult.FAILURE;
    }

    public void releaseLock(String str) {
        Integer result = lockRepository.releaseLock(str);
        if (result == null) {
            throw new ApplicationException("Unknown lock");
        }
        if (result == 0) {
            throw new ApplicationException("Can not release lock");
        }
    }
}

バッチを作成

/src/main/java/{package-name}/batch/HourlyBatch.java
@Component
@RequiredArgsConstructor
public class HourlyBatch {

    private static final String LOCK_STR = "hourly_batch";
    private static final int LOCK_TIMEOUT = 0;

    private final LockService lockService;

    @Scheduled(cron = "0 0 * * * *")
    @Transactional
    public void execute() {
        // ロック確認
        try {
            LockResult lockResult = lockService.getLock(LOCK_STR, LOCK_TIMEOUT);
            if (lockResult == LockResult.FAILURE) {
                // 別プロセスがロック済み
                return;
            }
        } catch (Exception e) {
            // エラー処理
            return;
        }

        try {
            // データベース操作
        } finally {
            try {
                lockService.releaseLock(LOCK_STR);
            } catch (Exception e) {
                // エラー処理
            }
        }
    }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?