🎉

JpaRepository で saveAndFlush しても commit されないですよね?

2022/03/26に公開

結論

されません。

背景

SpringBoot アプリケーションで @Transactional をつけたメソッド内で JpaRepositorysaveAndFlush() を呼び出すような実装をした。
コードレビューをしていただいた際に 「saveAndFlush() って変更を commit しないですかね?(ロールバックできなくなったりしないか心配)」 とのコメントをもらった。
flushcommit はしないだろうと思っていたものの、JPA 関連のドキュメント等を眺めて見ると「flushcommit しません」みたいなことはどこにも明言されておらず[1]、なんだか不安になってしまったので動作確認ベースで一応調査しておこう、となった。

動作確認

@Transactional をつけたメソッド内で JpaRepositorysaveAndFlush() を呼び出し、その後非検査例外を発生させ、非検査例外発生前に行った変更がロールバックされるか確認する。
よくあるユーザー追加のユースケースを例に考える。
動作確認で使用したコードはすべて以下のリポジトリに置いてあります。
https://github.com/yutatanamoto/jpa-flush-rollback-test

主要なアプリケーションコードと設定ファイルたち

# UserControler.java
 リクエスト(AddUserRequest: firstName と lastName) を受け取ってユーザー追加処理 (userService.add) を呼び出すだけ
@RestController
@RequiredArgsConstructor
class UserControler {
	private final UserService userService;
	
	@RequestMapping("/users", RequestMethod.POST)
	public void save(@RequestBody AddUserRequest request) {
		userService.add(request.getFirstName(), request.getLastName());
	}
}
# AddUserRequest.java
@Value
class AddUserRequest {
	String firstName;
	String lastName;
}
# UserService.java
ユーザー追加処理の実装
ここが動作確認のメインとなる処理
@Service
@RequiredArgsConstructor
class UserService {
	private final UserRepositpry userRepositpry;
	
	@Transactional
	public void add(String firstName, String lastName) {
		User user = new User(firstName, lastName)
		userRepositpry.saveAndFlush(user);
		// ここで非検査例外発生
		throw new RuntimeException();
	}
}
# User.java
@Entity
@Table("users")
public class User {
	@GeneratedValue
	Long id;
	String firstName;
	String lastName;
}
# UserRepositpry.java
public interface UserRepositpry implements JpaRepository<Long, User> {}
# docker-compose.yml
version: "3.8"
services:
  mysql-container:
    container_name: mysql-container
    image: mysql:8.0.22
    environment:
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_DATABASE: "sample"
      MYSQL_USER: "user"
      MYSQL_PASSWORD: "password"
    networks:
      - default
    volumes:
      - ./docker/volumes/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./docker/volumes/mysql/initdb.d:/docker-entrypoint-initdb.d
    ports:
      - "3306:3306"
# create_tables.sql
CREATE TABLE users
(
    id         BIGINT AUTO_INCREMENT,
    first_name  VARCHAR(100) NOT NULL,
    last_name   VARCHAR(100) NOT NULL,
    PRIMARY KEY (id)
);

手順

  1. DB のコンテナを立ち上げる。
$ docker compose up -d
  1. SpringBoot アプリケーションを実行する。
    IntelliJ のスタートボタンポチしました。
  2. 以下の CURL コマンドを実行して UserService#add を呼び出す。
$ curl -X POST -H "Content-Type: application/json" -d '{"firstName":"Daniel", "lastName": "Caesar"}' localhost:8080/users
  1. 最後に以下のコマンドで DB の中身を確認する。
    ロールバックされていれば DB は空のはずである。
$ docker mysql-container mysql -uuser -ppassword `select count(*) from users;`

結果

$ docker mysql-container mysql -uuser -ppassword `select * from users;`
0

レコードが 0 件なので、ちゃんとロールバックされることが確認できました。
めでたしめでたし。

参考資料

脚注
  1. 見落としているのかもしれない...。stackoverflow には 「flushcommit しないよ」 とのコメントがちらほら。 ↩︎

Discussion