๊ฐ์
Hibernate์ @Formula ์ด๋ ธํ ์ด์ ์ ์ํฐํฐ ํด๋์ค ๋ด์์ ์ค์ ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง์ ์กด์ฌํ์ง ์๋ '๊ฐ์ ์ปฌ๋ผ'์ ์ ์ํ ์ ์๋ ๊ธฐ๋ฅ์ ๋๋ค. @Formula๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค๋ฅธ ์ปฌ๋ผ๋ค์ ๊ฐ์ ๊ธฐ๋ฐํ์ฌ ๊ณ์ฐ๋ ๊ฐ์ ํํํ ์ ์์ผ๋ฉฐ, ์ด ๊ฐ์ ์ํฐํฐ๋ฅผ ์กฐํํ ๋๋ง ๊ณ์ฐ๋์ด ์ฌ์ฉ๋ฉ๋๋ค.
์์ ์ฝ๋
@Formula ์ด๋ ธํ ์ด์ ์ ์ ํ์ ์ธ ์ฌ์ฉ ์ฌ๋ก๋ ์ฐ๊ด๋ ๋ฐ์ดํฐ์ ์ง๊ณ๋ฅผ ์ํํ๋ ๊ฒฝ์ฐ์ ๋๋ค. ์๋ฅผ ๋ค์ด, ๊ฒ์๊ธ๊ณผ ์ฐ๊ฒฐ๋ ๋๊ธ์ ์๋ฅผ ๊ณ์ฐํ๋ ๊ฒฝ์ฐ๋ฅผ ๋ค ์ ์์ต๋๋ค.
์๋๋ ๊ฐ๋จํ ์์ ์ฝ๋์ ๋๋ค. ์ ์ฒด ์ฝ๋๋ ๊นํ๋ธ์์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค.
@Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String title; @Column private String content; @Formula("(select count(c.id) from Comment c where c.post_id = id)") private int commentCount; }
์ ์ฝ๋์์ 'commentCount'๋ Post ์ํฐํฐ์ ์ถ๊ฐ๋ ๊ฐ์ ์ปฌ๋ผ์ ๋๋ค. @Formula ์ด๋ ธํ ์ด์ ์ ์ ์๋ ๋ค์ดํฐ๋ธ SQL ์ฟผ๋ฆฌ๋ฅผ ํตํด ํด๋น ๊ฒ์๊ธ๊ณผ ์ฐ๊ฒฐ๋ ๋๊ธ ์๋ฅผ ๊ณ์ฐํ๋ฉฐ, 'commentCount'๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์์๋ ์ค์ ๋ก ์กด์ฌํ์ง ์์ง๋ง ์ํฐํฐ ๋ด์์๋ ์ค์ ์ปฌ๋ผ์ฒ๋ผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ธ์ ์ฌ์ฉํ๋ฉด ์ข์๊น?
@Formula ์ด๋ ธํ ์ด์ ์ ํนํ ์ํฐํฐ ๊ฐ์ ์ฐ๊ด ๊ด๊ณ๊ฐ ์์ ๋, ์ง๊ณ ์ฐ์ฐ์ ์ํํ๋ ๋ฐ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
์๋ฅผ๋ค์ด ๊ฒ์๊ธ(Post)๊ณผ ๋๊ธ(Comment)๊ณผ ๊ฐ์ด 1:N ๊ด๊ณ๋ฅผ ๊ฐ์ง๋ ์ํฐํฐ์์ ๊ฒ์๊ธ์ ๋ฌ๋ฆฐ ๋๊ธ์ ์๋ฅผ ์กฐํํ๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด ๋ด ์๋ค. ์ด๋ ๋ง์ฝ ๋๊ธ์ ์๋ฅผ ์ป๊ธฐ ์ํด ๋จ์ํ Post ์ํฐํฐ ๋ด๋ถ์ comments ๋ฆฌ์คํธ์ ํฌ๊ธฐ๋ฅผ ์ง์ ์กฐํํ๋ค๋ฉด Lazy Loading ์ค์ ์ผ๋ก ์ธํด ๋๊ธ์ ์ ์ฒด ์ปฌ๋ ์ ์ด ๋ก๋๋๋ ์ํฉ์ด ๋ฐ์ํฉ๋๋ค.
@Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String title; @Column private String content; @OneToMany(mappedBy = "post") private List<Comment> comments = new ArrayList<>(); public int getCommentCount() { return comments.size(); } }
์์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ฉด, ๋๊ธ์ ์๋ฅผ ์ป๊ธฐ ์ํด ํด๋น Post์ ๋ชจ๋ ๋๊ธ ๋ฐ์ดํฐ(์ปฌ๋ผ)๋ฅผ ๋ก๋ํด์ผ ํฉ๋๋ค. ์ด๋ก ์ธํด ์คํ ์๋๊ฐ ๋๋ ค์ง ์ ์๊ณ , ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ํ ๋ง์์ง๋ฉฐ ๊ฒฐ๊ณผ์ ์ผ๋ก ์์คํ ์ ์ฒด์ ์ฑ๋ฅ์ ๋ถ์ ์ ์ธ ์ํฅ์ ์ค ์ ์์ต๋๋ค.
์ด๋ @Formula ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ๋๊ธ ์๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ๋ฅผ ํตํด ์ง์ ๊ณ์ฐํ์ฌ ๊ฐ์ ธ์ ๋ถํ์ํ ๋ฐ์ดํฐ ๋ก๋๋ฅผ ๋ฐฉ์งํ๊ณ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์ฌ ์ฑ๋ฅ์ ๊ฐ์ ํ ์ ์์ต๋๋ค.
@Entity public class Post { // ๋๊ธ ๋ฆฌ์คํธ - ์ฐ๊ด ๊ด๊ณ ์ ์ @OneToMany(mappedBy = "post") private List<Comment> comments = new ArrayList<>(); // @Formula๋ฅผ ํตํ ๋๊ธ ์ ์ง์ ๊ณ์ฐ @Formula("(SELECT COUNT(*) FROM comment c WHERE c.post_id = id)") private int commentCount; // ๋๊ธ ์๋ฅผ ๋ฐํํ๋ ๋ฉ์๋๋ ์ด์ ๋ ์ด์ comments ์ปฌ๋ ์
์ ๋ก๋ํ์ง ์์ public int getCommentCount() { return this.commentCount; } }
๋ฌผ๋ก ์ํฉ์ ๋ฐ๋ผ์๋ @Formula ์ด๋ ธํ ์ด์ ์ด ์คํ๋ ค ๋ถ์์ฉ์ ์ผ์ผํฌ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋๊ธ ๋ฆฌ์คํธ ์ ์ฒด๋ฅผ ์กฐํํ๋ ํ๋ฉด์ด ํ์ํ ๊ฒฝ์ฐ์๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๋๋ง๋ค ๋ฌด์กฐ๊ฑด์ ์ผ๋ก @Formula๊ฐ ์คํ๋๊ธฐ ๋๋ฌธ์ ๋ถํ์ํ ์ฟผ๋ฆฌ๊ฐ ์คํ๋ ์ ์์ต๋๋ค.
๋ํ @Formula์ ์ง์ฐ ๋ก๋ฉ(Lazy Loading)์ ์ ์ฉํ๊ณ ์ถ์ด๋ Hibernate๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์์ ํ์ (primitive type)์ ๋ํด ์ง์ฐ ๋ก๋ฉ์ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด @Basic(fetch = FetchType.LAZY)์ ํจ๊ป ๋ณ๋์ ๋ฐ์ดํธ ์ฝ๋ ์กฐ์(ByteCodeEnhancement)์ ํตํด ์ ์ฉํด์ผ ํฉ๋๋ค.
๋ฐ๋ผ์ @Formula ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉ์ ํน์ ์ํฉ์์์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ๊ฒ์ด๋ฉฐ, ๊ทธ ์ธ์ ๊ฒฝ์ฐ์๋ ์ ์คํ๊ฒ ๊ฒํ ํด์ผ ํฉ๋๋ค.
์ฃผ์์
@Formula ์ด๋ ธํ ์ด์ ์ผ๋ก ์ ์๋ ํ๋๋ ํด๋น ์ํฐํฐ๊ฐ ์กฐํ๋ ๋๋ง๋ค SQL ํํ์์ด ์คํ๋๋ฉฐ ๊ทธ ๊ฒฐ๊ณผ๊ฐ์ผ๋ก ์ฑ์์ง๋๋ค. ๋ฐ๋ผ์, ๋ง์ฝ ๋๊ธ์ด ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋ ๊ฒฝ์ฐ์๋, @Formula๋ฅผ ํตํด ๊ณ์ฐ๋ ํ๋ ๊ฐ์ ์๋์ผ๋ก ๊ฐฑ์ ๋์ง ์์ต๋๋ค.
๋ํ @Formula๋ก ์ ์๋ ํ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ณ์ฐ๋ ๊ฐ์ ์ฝ์ด์ค๋ ๋ฐ์๋ง ์ฌ์ฉ๋ฉ๋๋ค. ์ด ํ๋์ ๋ํ ๋ณ๊ฒฝ์ ์๋ํ๋ฉด Hibernate๋ ์ด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํ์ง ์์ผ๋ฉฐ, ์ค์ ๋ก ์ด ํ๋๋ฅผ ์ ๋ฐ์ดํธํ๋ ค ํ๋ฉด ๋ฌด์๋๊ฑฐ๋ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๋ฐ๋ผ์ @Formula๋ก ์์ฑ๋ ํ๋๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐ์๋ง ์ ํฉํ๋ฉฐ, ์ํฐํฐ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ก์ง์์๋ ์ฌ์ฉ์ ํผํด์ผ ํฉ๋๋ค. ๋ง์ฝ @Formula๊ฐ ์ ์ฉ๋ ํ๋์ ๊ฐ์ ๊ฐฑ์ ํ๊ณ ์ถ๋ค๋ฉด, ๋ณ๋๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋ฆฌ๊ฑฐ๋ฅผ ์ค์ ํ๊ฑฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ค์์ ๋ก์ง์ ๊ตฌํํด์ผ ํฉ๋๋ค.
ํ ์คํธ
@SpringBootTest @Transactional class FormulaApplicationTests { @Autowired private PostRepository postRepository; @Autowired private JdbcTemplate jdbcTemplate; private Long postId = 1L; @BeforeEach void setUp() { // Post ํ
์ด๋ธ์ ๋ ์ฝ๋ ์ถ๊ฐ jdbcTemplate.update("INSERT INTO post (id, title, content) VALUES (?, ?, ?)", 1L, "๊ธ ์ ๋ชฉ", "๊ธ ๋ด์ฉ"); // Comment ํ
์ด๋ธ์ ๋ ์ฝ๋ ์ถ๊ฐ jdbcTemplate.update("INSERT INTO comment (id, content, post_id) VALUES (?, ?, ?)", 2L, "์ฒซ ๋ฒ์งธ ๋๊ธ", 1); jdbcTemplate.update("INSERT INTO comment (id, content, post_id) VALUES (?, ?, ?)", 3L, "๋ ๋ฒ์งธ ๋๊ธ", 1); } @Test void shouldReturnCorrectCommentCount() { // when Post post = postRepository.findById(postId).orElseThrow(() -> new AssertionError("Post not found")); // then Assertions.assertThat(post.getCommentCountFromCollections()).isEqualTo(2); Assertions.assertThat(post.getCommentCountFromFormula()).isEqualTo(2); } @DisplayName("Colletctions.size() ์ฟผ๋ฆฌ ํ์ธ") @Test void shouldQueryWhenGettingCountFromCollections() { Post post = postRepository.findById(postId).orElseThrow(() -> new AssertionError("Post not found")); post.getCommentCountFromCollections(); /** * * Hibernate: * select * post0_.id as id1_1_0_, * post0_.content as content2_1_0_, * post0_.title as title3_1_0_, * (select * count(*) * from * comment c * where * c.post_id = post0_.id) as formula1_0_ * from * post post0_ * where * post0_.id=? * Hibernate: * select * comments0_.post_id as post_id3_0_0_, * comments0_.id as id1_0_0_, * comments0_.id as id1_0_1_, * comments0_.content as content2_0_1_, * comments0_.post_id as post_id3_0_1_ * from * comment comments0_ * where * comments0_.post_id=? */ } @DisplayName("Formula ์ฟผ๋ฆฌ ํ์ธ") @Test void shouldQueryWhenGettingCountFromFormula() { Post post = postRepository.findById(postId).orElseThrow(() -> new AssertionError("Post not found")); post.getCommentCountFromFormula(); /** * * Hibernate: * select * post0_.id as id1_1_0_, * post0_.content as content2_1_0_, * post0_.title as title3_1_0_, * (select * count(*) * from * comment c * where * c.post_id = post0_.id) as formula1_0_ * from * post post0_ * where * post0_.id=? */ } }
๋๊ธ