๊ฐ์
Hibernate(JPA)์์๋ ๋ ์ฝ๋๋ฅผ ์ญ์ ํ ์ ์๋ ์๋์ ๋ค์ํ ๋ฉ์๋๋ค์ ์ง์ํ๋ค.
- delete(),deleteById()
- deleteAll(), deleteAllById()
- deleteInBatch()
- deleteAllInBatch(), deleteAllByIdInBatch()
๋ค์ํ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ์ข์ง๋ง, ๋ค์ด๋ฐ๋ง ๋ณด๊ณ ํผ๋์ค๋ฌ์ด ๊ฒ๋ค์ด ๋ช๋ช ์๋ค. ๋ํ์ ์ผ๋ก deleteInBatch()์ deleteAllInBatch(), ๊ทธ๋ฆฌ๊ณ deleteAll()๊ณผ deleteAllInBatch()์ ๊ฐ์ ๋ฉ์๋๋ค์ด๋ค.
์ด๋ฒ ๊ฒ์๊ธ์์๋ ์ ๋ฉ์๋๋ค์ด ์ค์ ๋ก ์ด๋ป๊ฒ ์ญ์ ๋ฅผ ์ฒ๋ฆฌํ๋์ง๋ฅผ ์ดํด๋ณผ ๊ฒ์ด๊ณ , ์ฐ๊ด๊ด๊ณ๊ฐ ์๋ ๊ฒฝ์ฐ์ ์ฐ๊ด๊ด๊ณ๊ฐ ์๋ ๊ฒฝ์ฐ๋ก ๋๋์ด ์ญ์ ๋์์ ๋ถ์ํด ๋ณผ ๊ฒ์ด๋ค.
์ฌ์ ์ธํ
DB๋ MySQL์ ์ฌ์ฉํ์ผ๋ฉฐ, MySQL์์ ๋ฐฐ์น ์ญ์ ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ JDBC URL์ด ํ์ํ๋ค.
jdbc:mysql:localhost:3306/db ?cachePreStmts=true &useServerPrepStmts=true &rewriteBatchedStatements=true
๊ฐ ์ค์ ์ ๋ํ ๊ฐ๋จํ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ๋ค.
- cachePrepStmts: PreparedStatement์ ์บ์ฑ์ ํ์ฑํํ๋ค. cachePrepStmts=true๋ก ์ค์ ํ๋ฉด, ๋์ผํ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์คํํ ๋ ์ฑ๋ฅ์ด ํฅ์๋๋ค. ์ด ์บ์๋ prepStmtCacheSize ๋ฐ prepStmtCacheSqlLimit๊ณผ ํจ๊ป ์๋ํ์ฌ, ์บ์ ํ PreparedStatement์ ์์ SQL ๋ฌธ์ฅ์ ์ต๋ ๊ธธ์ด๋ฅผ ์ค์ ํ ์ ์๋ค.
- useServerPrepStmts: ์๋ฒ ์ธก์์ PreparedStatement๋ฅผ ์ฌ์ฉํ๋๋ก ์ค์ ํ๋ค. MySQL์ ๊ธฐ๋ณธ์ ์ผ๋ก ํด๋ผ์ด์ธํธ ์ธก์์ PreparedStatement๋ฅผ ์ฒ๋ฆฌํ์ง๋ง, useServerPrepStmts=true๋ก ์ค์ ํ๋ฉด ์๋ฒ์์ PreparedStatement๋ฅผ ๊ด๋ฆฌํ๊ฒ ๋์ด, ์๋ฒ์ ๋ฆฌ์์ค๋ฅผ ํ์ฉํ ํจ์จ์ ์ธ ์ฟผ๋ฆฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ง๋ค.
- rewriteBatchedStatements: ์ด ์์ฑ์ด ํ์ฑํ๋๋ฉด, ์ฌ๋ฌ ๊ฐ์ SQL ๋ฌธ์ ํ๋์ ๋ฌธ์์ด ๋ฒํผ๋ก ์ฌ์์ฑํ์ฌ ํ๋์ ์์ฒญ์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ํ๋ค. ์ด๋ฅผ ํตํด ๋ฐฐ์น ์์ ์ ์ฑ๋ฅ์ด ํฌ๊ฒ ํฅ์๋ ์ ์๋ค.
์ฌ๊ธฐ์ ํนํ rewriteBatchedStatements ์์ฑ์ ํ์ฑํํ๋ฉด, ์ฌ๋ฌ SQL ๋ฌธ์ด ํ๋์ ๋ฌธ์์ด ๋ฒํผ๋ก ์ฌ์์ฑ๋์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ํ๋์ ์์ฒญ์ผ๋ก ์ ์ก๋๋๋ฐ, ์ด ์ค์ ์ ํ์ฑํ ์ฌ๋ถ์ ๋ฐ๋ผ ์ฟผ๋ฆฌ๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด ๋ฌ๋ผ์ง๊ฒ ๋๋ค.
-- rewriteBatchedStatements = false insert into author (name, id) values ('Jane Austen', 1); insert into author (name, id) values ('Nathaniel Hawthorne', 2); ... -- rewriteBatchedStatements = true insert into author (name, id) values ('Jane Austen', 1), ('Nathaniel Hawthorne', 2), ...;
์ถ๊ฐ๋ก ๋ฐฐ์น ํฌ๊ธฐ๋ฅผ ์ค์ ํ๋ ค๋ฉด spring.jpa.properties.hibernate.jdbc.batch_size ์์ฑ์ ์ฌ์ฉํ๊ณ , ๋ฒ์ ์ ๊ฐ์ง ์ํฐํฐ์ ๊ฒฝ์ฐ spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true๋ก ์ง์ ํ์ฌ ์ฒ๋ฆฌํ ์ ์๋ค.
์ฐ๊ด๊ด๊ณ๊ฐ ์๋ ๊ฒฝ์ฐ
๋ค์๊ณผ ๊ฐ์ด ์ฐ๊ด๊ด๊ณ๊ฐ ์๋ ๊ฐ๋จํ ์ํฐํฐ๋ฅผ ์ฌ์ฉํ์ฌ 1,000๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ํ ์คํธ๋ฅผ ์งํํ๋ค.
@Entity @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; } ...
SET @i = 1; WHILE @i <= 1000 DO INSERT INTO author (name) VALUES (CONCAT('Author ', @i)); SET @i = @i + 1; END WHILE;
1. deleteAllInBatch() ์ญ์
- ์ํฐํฐ๋ฅผ ๋ฐ๋ ๊ฒฝ์ฐ: SimpleJpaRepository.java#L274
- ์ํฐํฐ๋ฅผ ๋ฐ์ง ์๋ ๊ฒฝ์ฐ: SimpleJpaRepository.java#L298
@Transactional public void deleteAuthorsDeleteAllInBatch() { authorRepository.deleteAllInBatch(); }
delete a1_0 from author a1_0
๊ฐ๋จํ๊ฒ ํ๋์ DELETE ์ฟผ๋ฆฌ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ์ญ์ ํ๋ฉฐ, ๋ฒํฌ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋์๋ค.
- ๋ฒํฌ(Bulk): ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋ค์์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์
- ๋ฐฐ์น(Batch): ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ๋ฒ์ ๋๋์ด ์ฒ๋ฆฌํ๋ ๋ฐฉ์
์ผ๋ฐ์ ์ผ๋ก ๋ฒํฌ ์์ ์ ๋ฐฐ์น ์ฒ๋ฆฌ๋ณด๋ค ๋น ๋ฅด๊ณ ์ธ๋ฑ์ค๋ฅผ ํ์ฉํ ์ ์๋ ์ฅ์ ์ด ์์ง๋ง, ์ ์ด ๋ฉ์ปค๋์ฆ(cascadeType.ALL)์ด๋ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ค ๋๊ด์ ์ ๊ธ ๋ฉ์ปค๋์ฆ(@Version)์ ์ฅ์ ์ ์ป์ง ๋ชปํ๋ฉฐ, ์ํฐํฐ์ ๋ํ ์์ ์ฌํญ์ด ์์์ฑ ์ปจํ ์คํธ์ ์๋์ผ๋ก ๋ฐ์๋์ง ์๋๋ค๋ ์ ๋ ์กด์ฌํ๋ค. ์๋ฅผ ๋ค์ด, @Version ํ๋๋ฅผ ๊ฐ์ง๋ ์ํฐํฐ์ ๋ํด ์ญ์ ๋ฅผ ์งํํ ๊ฒฝ์ฐ ํด๋น ์ํฐํฐ์ ๋ฒ์ ์ ๋ณด๋ ์ ๋ฐ์ดํธ๋์ง ์์ผ๋ฉฐ, ๋ค๋ฅธ ํธ๋์ญ์ ์์ ์ด ์ํฐํฐ๋ฅผ ์ฐธ์กฐํ๊ณ ์๋ ๊ฒฝ์ฐ ๋๊ด์ ์ ๊ธ ์ถฉ๋์ ๊ฐ์งํ์ง ๋ชปํ ์๋ ์๋ค.
2. deleteInBatch(Iterable<T> entities) ์ญ์
@Transactional public void deleteAuthorsDeleteInBatch() { List<Author> authors = authorRepository.findAll(); authorRepository.deleteInBatch(authors); }
delete a1_0 from author a1_0 where a1_0.id=? or a1_0.id=? or a1_0.id=? ...
์ผ๋จ ์ด deleteInBatch() ๋ฉ์๋๋ ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ํ๋ ์๋๋ฐ, IN ์ ์ด ์๋ OR ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ WHERE ์ ์ ํด๋นํ๋ ID๋ค์ ๋จ์ํ ์ฐ๊ฒฐํ๋ค. MySQL 8 ๋ฒ์ ๊ธฐ์ค์ผ๋ก IN์ ์ 500,000๊ฐ์ row๋ฅผ ๋ฌธ์ ์์ด ์ญ์ ํ ์ ์๋ ๋ฐ๋ฉด, OR ์ฐ์ฐ์๋ 10,000๊ฐ ์ด์์ ํ์ ์ญ์ ํ๋ ค๊ณ ํ ๋ StackOverflowError๊ฐ ๋ฐ์ํ ์ ์๋ค.
์ค์ ๋ก 10,000๊ฐ ์ด์์ row ์ญ์ ๋ฅผ ์๋ํ์ ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
java.lang.StackOverflowError: null at org.antlr.v4.runtime.ParserRuleContext.getRuleContext(ParserRuleContext.java:277) ~[antlr4-runtime-4.13.0.jar:4.13.0] at org.hibernate.grammars.hql.HqlParser$OrPredicateContext.predicate(HqlParser.java:6509) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitOrPredicate(SemanticQueryBuilder.java:2382) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitOrPredicate(SemanticQueryBuilder.java:275) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.grammars.hql.HqlParser$OrPredicateContext.accept(HqlParser.java:6523) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitOrPredicate(SemanticQueryBuilder.java:2382) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final] ...
deleteAllInBatch()์ ๋ง์ฐฌ๊ฐ์ง๋ก deleteInBatch()๋ executeUpdate()๋ฅผ ํตํด ๋ฒํฌ ์์ ์ ์ํํ๋ค. ๋ฐ๋ผ์ ๋ชจ๋ ๋ ์ฝ๋๋ฅผ ์ญ์ ํด์ผ ํ ๋๋ deleteInBatch() ๋์ deleteAllInBatch()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค. ๋ค๋ง ํน์ ํํฐ๋ง ๊ธฐ์ค์ ๋ฐ๋ผ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ๋๋ ์ด ๋ฐฉ๋ฒ๋ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ค ๋๊ด์ ์ ๊ธ ๋ฉ์ปค๋์ฆ์ ์ด์ ์ ์ป์ง ๋ชปํ ์ ์๋ค๋ ๊ฒ์ ์ฃผ์ํด์ผ ํ๋ค.
๋ง์ฝ ์์ฑ๋ ์ฟผ๋ฆฌ์ ๊ธธ์ด์ ๊ด๋ จ๋ ๋ฌธ์ ๊ฐ ์๋ ๊ฒฝ์ฐ, ๋์์ผ๋ก ์๋์ฒ๋ผ ์ง์ IN ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ฒ๋ ์ข์ ์ ํ์ด ๋ ์ ์๋ค.
@Modifying(flushAutomatically = true, clearAutomatically = true) @Query("delete from Author a WHERE a IN ?1") int deleteInBulk(List<Author> authors);
3. deleteAll() ์ญ์
- ์ํฐํฐ๋ฅผ ๋ฐ๋ ๊ฒฝ์ฐ: SimpleJpaRepository.java#L263
- ์ํฐํฐ๋ฅผ ๋ฐ์ง ์๋ ๊ฒฝ์ฐ: SimpleJpaRepository.java#L289
@Transactional public void deleteAuthorsDeleteAll() { authorRepository.deleteAll(); }
delete from author where id=? delete from author where id=? delete from author where id=? ...
์ฌ๋ฌ ๊ฐ์ ๋ฐ๋ณต์ ์ธ delete()๊ฐ ์ํ๋์๋ค. ๊ทธ ์ด์ ๋ ์์ ๋งํฌ๋ ์ค์ ๊ตฌํ์ฝ๋๋ฅผ ๋ณด๋ฉด ์ ์ ์๋๋ฐ, deleteAll(Iterable <? extends T> extends) ๋ฉ์๋๊ฐ ๋ด๋ถ์ ์ผ๋ก delete(T entity) ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ธ์๋ฅผ ๋ฐ์ง ์๋ deleteAll() ๋ฉ์๋๋ ๋ด๋ถ์ ์ผ๋ก findAll()์ ํธ์ถํ์ฌ, ๋ฐํ๋ ์ํฐํฐ ์งํฉ์ ๋ฐ๋ณตํ๋ฉด์ ๊ฐ ์ํฐํฐ์ ๋ํด delete(T entity) ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. ๊ฐ๋ฐ์๊ฐ deleteAll()๋ deleteAll(Iterable entities) ๋ฉ์๋ ๋๋ delete(T entity)๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฐฐ์น ์ฒ๋ฆฌ๊ฐ ์ฌ์ฉ๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก ๋ชจ๋ ๋ ์ฝ๋๋ฅผ ์ญ์ ํด์ผ ํ ๊ฒฝ์ฐ์๋ deleteAllInBatch()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ด๋ค. ๊ทธ ์ธ์ deleteInBatch()๋ deleteAll() ์ฌ์ด์ ๊ฒฐ์ ์ ๊ฐ๊ฐ์ ๋ฉ์๋๊ฐ ํธ๋ฆฌ๊ฑฐํ๋ ์ญ์ ๋ฐฉ์(์ ์ด ๋ฉ์ปค๋์ฆ), ์ฑ๋ฅ ์ฐจ์ด, ๊ทธ๋ฆฌ๊ณ ๋๊ด์ ์ ๊ธ ๋ฉ์ปค๋์ฆ์ ์ ์ฉ ์ฌ๋ถ๋ค์ ๋ํด ๊ณ ๋ คํด์ ์ ํํด์ผ ํ๋ค.
์ฐ๊ด๊ด๊ณ๊ฐ ์๋ ๊ฒฝ์ฐ
๋ค์๊ณผ ๊ฐ์ด ์ง์ฐ ์๋ฐฉํฅ @OneToMany ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ฐ๊ณ ์๋ค๊ณ ๊ฐ์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ํ ์ด๋ธ์ ๋ฌผ๋ฆฌ์ ์ผ๋ก FK๊ฐ ๊ฑธ๋ ค์๋ค๊ณ ๊ฐ์ ํ์ง๋ง, ๊ฐ ๋ด์ฉ์์ ๊ฑธ๋ ค์์ง ์์ ๊ฒฝ์ฐ์ ์ํ๋๋ ๋์๋ ์ดํด๋ณผ ๊ฒ์ด๋ค.
@Entity @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "author_id") private Author author; private String title; private String isbn; }
@Entity @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private int age; @OneToMany(cascade = CascadeType.ALL, mappedBy = "author", orphanRemoval = true) private List<Book> books = new ArrayList<>(); }
SET @i = 1; WHILE @i <= 1000 DO INSERT INTO author (name) VALUES (CONCAT('Author ', @i)); SET @i = @i + 1; END WHILE; SET @author_id = 1; WHILE @author_id <= 1000 DO INSERT INTO book (author_id, title, isbn) VALUES (@author_id, CONCAT('Book Title ', @author_id, '-1'), CONCAT('ISBN', @author_id, '-1')); INSERT INTO book (author_id, title, isbn) VALUES (@author_id, CONCAT('Book Title ', @author_id, '-2'), CONCAT('ISBN', @author_id, '-2')); INSERT INTO book (author_id, title, isbn) VALUES (@author_id, CONCAT('Book Title ', @author_id, '-3'), CONCAT('ISBN', @author_id, '-3')); INSERT INTO book (author_id, title, isbn) VALUES (@author_id, CONCAT('Book Title ', @author_id, '-4'), CONCAT('ISBN', @author_id, '-4')); INSERT INTO book (author_id, title, isbn) VALUES (@author_id, CONCAT('Book Title ', @author_id, '-5'), CONCAT('ISBN', @author_id, '-5')); SET @author_id = @author_id + 1; END WHILE;

์ ์๋ฅผ ์ญ์ ํ๋ฉด ์ฐ๊ด๋ ๋ชจ๋ ๋์๋ ํจ๊ป ์ญ์ ๋์ด์ผ ํ๋ค. ๋ฐ์ดํฐ๋ ์ ์ 1,000๋ช ์ ์ ์๋ง๋ค 5๊ฐ์ ์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋กํ๋ค.
1. deleteAllInBatch() ์ญ์
@Transactional public void deleteAuthorsAndBooksDeleteAllInBatch() { authorRepository.deleteAllInBatch(); }
delete a1_0 from author a1_0 java.sql.SQLIntegrityConstraintViolationException: (conn=109) Cannot delete or update a parent row: a foreign key constraint fails (`test`.`book`, CONSTRAINT `FKklnrv3weler2ftkweewlky958` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`)) ...
์ด ๊ฒฝ์ฐ author ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ DELETE ์ฟผ๋ฆฌ๋ ์์ฑ๋์ง๋ง, book์์ author_id๋ฅผ ์ฐธ์กฐํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์ธ๋ ํค ์ ์ฝ ์กฐ๊ฑด์ผ๋ก ์ธํด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ์ด๋ deleteAllInBatch()๊ฐ orphanRemoval ๋๋ ์ ์ด(cascade)๋ฅผ ์ฌ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋๋ฐ, deleteAllInBatch()๋ executeUpdate()๋ฅผ ํตํด ๋ฒํฌ ์ญ์ ๋ฅผ ํธ๋ฆฌ๊ฑฐ ํ์ง๋ง, JPA์ ์์์ฑ ์ปจํ ์คํธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ ๋๊ธฐํ๋ ์ด๋ฃจ์ด์ง์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ฝ๊ฒ ๋งํด deleteAllInBatch()์ ๊ฐ์ ๋ฒํฌ ์ฐ์ฐ์ JPA์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ๊ฑด๋๋ฐ๊ณ ์ง์ ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํด ์ญ์ ์์ ์ ์ํํ๊ธฐ ๋๋ฌธ์, orphanRemoval ๋ฐ cascade ์ต์ ์ด ๋ฌด์๋๋ค. ์ด๋ก ์ธํด ์ญ์ ํ๋ ค๋ ์ํฐํฐ๊ฐ ๋ค๋ฅธ ์ํฐํฐ์ ์ํด ์ฐธ์กฐ๋๊ณ ์์ ๊ฒฝ์ฐ ์ ์ฝ ์กฐ๊ฑด ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์๋ค.
๋ฌผ๋ฆฌ์ ์ผ๋ก ํ ์ด๋ธ์ ์ธ๋ํค๊ฐ ์ค์ ๋์ด ์์ง ์์ ๊ฒฝ์ฐ์๋ ์ธ๋ ํค ์ ์ฝ ์กฐ๊ฑด ๋ฌธ์ ๋ ๋ฐ์ํ์ง ์์ง๋ง, Author ํ ์ด๋ธ์ ๋ ์ฝ๋๋ง ์ญ์ ๋๊ณ Book ํ ์ด๋ธ์ ๋ ์ฝ๋๋ ์ญ์ ๋์ง ์๋๋ค.
๋ฐ๋ผ์ ์ด ๊ฒฝ์ฐ ๋ชจ๋ ๋์๋ฅผ ์ญ์ ํ๋ ๋ฐ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ์ด ๋ช ์์ ์ผ๋ก book๋ ์ ๊ฑฐํ๋ ๋ฐฉ๋ฒ๋ฟ์ด๋ค.
// ๋ฌผ๋ฆฌ์ ์ผ๋ก FK ๊ฑธ๋ ค์์ ์ book ์ญ์ ๊ฐ ์ฐ์ @Transactional public void deleteAuthorsAndBooksDeleteAllInBatch() { bookRepository.deleteAllInBatch(); authorRepository.deleteAllInBatch(); } // ๋ฌผ๋ฆฌ์ ์ผ๋ก FK ๊ฑธ๋ ค์์ง ์์ ์ ์์ ์๊ด ์์ @Transactional public void deleteAuthorsAndBooksDeleteAllInBatch() { authorRepository.deleteAllInBatch(); bookRepository.deleteAllInBatch(); }
2. deleteInBatch() ์ญ์
@Transactional public void deleteAuthorsAndBooksDeleteInBatch() { List<Author> authors = authorRepository.findAll(); authorRepository.deleteInBatch(authors); }
delete a1_0 from author a1_0 where a1_0.id=? or a1_0.id=? or a1_0.id=? or a1_0.id=? or a1_0.id=? or a1_0.id=? ... java.sql.SQLIntegrityConstraintViolationException: (conn=109) Cannot delete or update a parent row: a foreign key constraint fails (`test`.`book`, CONSTRAINT `FKklnrv3weler2ftkweewlky958` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`)) ...
๋ง์ฐฌ๊ฐ์ง๋ก IN ์ ์ด ์๋ OR ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ WHERE ์ ์ ํด๋นํ๋ ID๋ค์ ๋จ์ํ ์ฐ๊ฒฐํ๊ณ ์์ผ๋ฉฐ, deleteInBatch()๋ deleteAllInBatch()์ ๋ง์ฐฌ๊ฐ์ง๋ก orphanRemoval ๋๋ ์ ์ด(cascade)๋ฅผ ์ฌ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ ์ธ๋ ํค ์ ์ฝ ์กฐ๊ฑด์ผ๋ก ์ธํด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์ด ๊ฒฝ์ฐ์๋ ๋ฌผ๋ฆฌ์ ์ผ๋ก ํ ์ด๋ธ์ ์ธ๋ํค๊ฐ ์ค์ ๋์ด ์์ง ์์ ์ ์ธ๋ ํค ์ ์ฝ ์กฐ๊ฑด ๋ฌธ์ ๋ ๋ฐ์ํ์ง ์์ง๋ง, Author ํ ์ด๋ธ์ ๋ ์ฝ๋๋ง ์ญ์ ๋๊ณ Book ํ ์ด๋ธ์ ๋ ์ฝ๋๋ ์ญ์ ๋์ง ์๋๋ค.
3. deleteAll() ์ญ์
@Transactional public void deleteAuthorsAndBooksDeleteAll() { List<Author> authors = authorRepository.findAll(); authorRepository.deleteAll(authors); }
delete from book where id=? delete from book where id=? delete from book where id=? delete from book where id=? delete from book where id=? delete from author where id=? delete from book where id=? delete from book where id=? delete from book where id=? delete from book where id=? delete from book where id=? delete from author where id=? ...
deleteAll() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด, ๊ฐ Author ์ํฐํฐ๋ฅผ ์ญ์ ํ ๋๋ง๋ค ๊ด๋ จ๋ Book ์ํฐํฐ๊ฐ ๋ชจ๋ ์ ๊ฑฐ๋ ํ ํด๋น Author ์ํฐํฐ๊ฐ ์ญ์ ๋๋ค. ์์ ๊ฒฝ์ฐ ํ๋์ Author๊ฐ 5๊ฐ์ Book์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ๊ฐ Book์ ์ญ์ ํ๋ ์ฟผ๋ฆฌ๊ฐ 5๋ฒ ์คํ๋ ํ Author ์ญ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ์์ผ๋ก ๋ฐ๋ณต๋๋ค.
๋ค๋ง ์ด ๋ฐฉ์์ ๋ฐฐ์น ์ฒ๋ฆฌ๊ฐ ์ฌ์ฉ๋์ง๋ง ์ต์ ํ๋์ด์์ง๋ ์๋ค. DELETE๋ฌธ์ ์ ๋ ฌ๋์ง ์๊ธฐ ๋๋ฌธ์, ์ด ์์ ์ ํ์ํ ๊ฒ๋ณด๋ค ๋ ๋ง์ ๋ฐฐ์น๊ฐ ๋ฐ์ํ๋ค. ๋ฐฐ์น๋ ํ๋์ ํ ์ด๋ธ๋ง ๋์์ผ๋ก ํ ์ ์๊ธฐ ๋๋ฌธ์ Book๊ณผ Author ํ ์ด๋ธ์ ๋ฒ๊ฐ์ ์ฒ๋ฆฌํ๋ฉด 5๊ถ์ ๋์๋ฅผ ๊ฐ์ง 10๋ช ์ ์ ์๋ฅผ ์ญ์ ํ ๊ฒฝ์ฐ 10 * 2 = 20๊ฐ์ ๋ฐฐ์น๊ฐ ํ์ํ๊ฒ ๋๋ค. ๊ฐ ์ ์๊ฐ ๊ฐ์์ ๋ฐฐ์น์์ ์ญ์ ๋๊ณ ๊ทธ์ ๋ฐ๋ฅธ ๋์๊ฐ ๋ค๋ฅธ ๋ฐฐ์น์์ ์ญ์ ๋๊ธฐ ๋๋ฌธ์ ๋นํจ์จ์ ์ด๋ค.
๋ฐ๋ผ์ ์กฐ๊ธ ๋ ๋ฐฐ์น ์๋ฅผ ์ต์ ํํ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ ์ ์๋ค.
@Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Author { ... public void removeBooks() { Iterator<Book> iterator = this.books.iterator(); while (iterator.hasNext()) { Book book = iterator.next(); book.setAuthor(null); iterator.remove(); } } }
@Transactional public void deleteAuthorsAndBooksDeleteAll() { List<Author> authors = authorRepository.findAll(); authors.forEach(Author::removeBooks); authorRepository.flush(); authorRepository.deleteAll(authors); }
delete from book where id=? delete from book where id=? delete from book where id=? delete from book where id=? delete from book where id=? ... delete from author where id=? delete from author where id=? delete from author where id=? delete from author where id=? ...
์ ๋ณด๋ฉด ์ด์ ๊ณผ ๋ฌ๋ฆฌ ํ ์ด๋ธ์ ๋ชจ๋ Book ๋ ์ฝ๋๊ฐ ๋จผ์ ์ ๊ฑฐ๋ ํ Author๊ฐ ์ญ์ ๋๋ค. ์ฐ๊ด๋ ๋ชจ๋ ๋์๋ฅผ ์ญ์ ํ๋ ์์ ์ด ๋จผ์ ์ํ๋๋ฏ๋ก, orphanRemoval์ด true๋ก ์ค์ ๋์ด ์๋ ๊ฒฝ์ฐ ์ฐ๊ด ๊ด๊ณ๊ฐ ํด์ ๋ ๋ชจ๋ ๋์๊ฐ ์๋์ผ๋ก ์ญ์ ๋๋ฉฐ, ์์ฑ๋ DELETE๋ฌธ์ ๋ฐฐ์น๋ก ์ฒ๋ฆฌ๋๋ค(orphanRemoval์ด false๋ก ์ค์ ๋ ๊ฒฝ์ฐ, ์ญ์ ๋์ ์ฌ๋ฌ UPDATE ๋ฌธ์ด ์คํ๋จ).
์ฆ ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ฐ๋ ๋ชจ๋ ๋์๋ฅผ ์ญ์ ํ๋ ์์ ์ด ๋จผ์ ์ํ๋๋ค. ๋ฐ๋ผ์ ์ด ๋ฐฉ๋ฒ์ ํตํด ๋ฐฐ์น ์ฒ๋ฆฌ์ ํจ์จ์ฑ์ ๋์ฌ Author์ Book ๋ชจ๋์ ์ ์ฉํ ์ ์๋ค.
4. SQL, ON DELETE CASCADE ์ฌ์ฉ
์ฌ์ค ์ฐ๊ด ๊ด๊ณ๊ฐ ์์ ๋ deleteAllInBatch()๋ deleteInBatch()๋ฅผ ์ฌ์ฉํ์ฌ Author๋ง ์ญ์ ํ๋๋ผ๋ ์๋์ผ๋ก Book๊น์ง ์ญ์ ๋๋๋ก ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋๋ฐ, SQL์ ์ ์ด ์ญ์ ์ง์์์ธ ON DELETE CASCADE๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
ON DELETE CASCADE๋ ๋ถ๋ชจ ํ์ด ์ญ์ ๋ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์๋์ผ๋ก ์์ ํ์ ์ญ์ ํ๋ ๋ฐฉ์์ผ๋ก ํ์ด๋ฒ๋ค์ดํธ @OnDelete ์ด๋ ธํ ์ด์ ์ ํตํด ์ค์ ํ ์ ์๋ค.
@Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Author { ... @OneToMany(cascade = CascadeType.ALL, mappedBy = "author", orphanRemoval = true) @OnDelete(action = OnDeleteAction.CASCADE) // ์ถ๊ฐ private List<Book> books = new ArrayList<>(); }
ํ ์ด๋ธ์ ๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ค.
ALTER TABLE book ADD CONSTRAINT fk_author_book FOREIGN KEY (author_id) REFERENCES author (id) ON DELETE CASCADE;
์ด ์ค์ ์ ํตํด Author๊ฐ ์ญ์ ๋ ๋ ์ฐ๊ด๋ Book๋ ์๋์ผ๋ก ์ญ์ ๋๋ฏ๋ก, ์ด์ ์ deleteAllInBatch()๋ deleteInBatch()๋ฅผ ์ฌ์ฉํ ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋ ๋ ๋ฒจ์์ ๋ช ์์ ์ผ๋ก Book ์ํฐํฐ๋ฅผ ์ญ์ ํ ํ์๊ฐ ์๊ฒ๋๋ค.
'BackEnd๐ฑ > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring ํธ๋์ญ์ ์ ์ธ์ ์ด๋ป๊ฒ ๋กค๋ฐฑ ๋ ๊น? -2ํธ(feat. rollback-only) (0) | 2024.12.22 |
---|---|
Spring ํธ๋์ญ์ ์ ์ธ์ ์ด๋ป๊ฒ ๋กค๋ฐฑ ๋ ๊น? -1ํธ (2) | 2024.10.04 |
[gradle] implementation, api ์ฐจ์ด (0) | 2024.09.07 |
๋จ๋ฐฉํฅ @OneToMany์ ๋ฌธ์ ์ (2) | 2024.07.26 |
@TransactionalEventListener ์ฌ์ฉ ์ ์ ๋ฐ์ดํธ ์๋ ๋ (0) | 2024.07.03 |
ํ์ ์ ์ํด Swagger ์ข ๋ ์ ์ฌ์ฉํด๋ณด๊ธฐ (0) | 2024.05.15 |
๋๊ธ