๋งค์ผ๋ฉ์ผ ๋ฐฑ์๋ ์ง๋ฌธ์ ์ฐธ๊ณ ํด ๊ฐ์ธ์ ์ผ๋ก ํ์ตํ ๋ด์ฉ์ ์ ๋ฆฌํ์์ต๋๋ค.
์ค๋ฅ๊ฐ ์๋ค๋ฉด ์ธ์ ๋ ํผ๋๋ฐฑ ์ฃผ์๋ฉด ๋ฐ๋ก ๋ฐ์ํ๊ฒ ์ต๋๋ค..!
์์ํ๊ธฐ์ ์์
JPA๋?
JPA(Java Persistence API)๋ ์๋ฐ ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ๋ถ๋ฌ์ค๋ ๋ฐฉ๋ฒ์ ์ ์ํ ํ์ค API์
=> ORM(Object-Relational Mapping) ๊ธฐ์ ์ ์ ๊ณตํ๋ ์๋ฐ์ ๊ณต์ ํ์ค์
JPA๋ ์์ฒด์ ์ผ๋ก ๋์ํ์ง ์๊ณ Hibernate์ ๊ฐ์ ๊ตฌํ์ฒด๊ฐ ํ์ํจ
EntityManager๋?
EntityManager์ ๋ํด ์๊ธฐ ์ํด์ Persistence Context์ ๋ํด ์์์ผํจ
Persistence Context๋ ์ํฐํฐ๋ฅผ ์๊ตฌ ์ ์ฅํ๋ ํ๊ฒฝ์ผ๋ก 1์ฐจ ์บ์ฑ, ์ฐ๊ธฐ ์ง์ฐ, ๋ณ๊ฒฝ ๊ฐ์ง๋ฅผ ํตํด ์์ ๋ก์ง์ ํจ์จ์ ์ผ๋ก ํ ์ ์๊ฒ ํด์ค
=> ์ด๋ฌํ ํจ์จ์ ์ธ ์์ ๋ก์ง ์ํ์ ์ํด์ ์ํฐํฐ๋ Persistence Context(์์์ฑ ์ปจํ
์คํธ)์ ๊ด๋ฆฌ๋์ด์ผํจ
์ด๋ฐ ์์
์ ๋์์ฃผ๋ ๊ฒ์ด ๋ฐ๋ก EntityManager ์
=> ์ํฐํฐ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ณ , Persistence Context์ ์ํธ์์ฉํจ์ผ๋ก์จ ์์ ๋ก์ง์ ์ํํ๋ ์ญํ ์ ๊ฐ์ง๊ณ ์์
์ํฐํฐ๋ Persistence Context์ ๊ด๋ จํ์ฌ 4๊ฐ์ง ์ํ(๋น์์, ์์, ์ค์์, ์ญ์ )๋ฅผ ๊ฐ์ง ์ ์์
1. ๋น์์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ ์๋ก ์์ฑ๋์์ง๋ง, ์์ง Persistence Context์ ์ฐ๊ด๋์ง ์์ ์ํ์
(๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ด๋ จ์ด ์์ผ๋ฉฐ, ์ํฐํฐ ๊ฐ์ฒด๋ ๋ฉ๋ชจ๋ฆฌ ์์๋ง ์กด์ฌ)
ex.
Member member = new Member("JunGyo");
2. ์์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ Persistence Context์ ๊ด๋ฆฌ๋๊ณ ์๋ ์ํ์
(์ํฐํฐ์ ๋ณ๊ฒฝ ์ฌํญ์ด ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์๋จ)
ex.
em.persist(member); // ์์์ฑ ์ปจํ ์คํธ๊ฐ ๊ด๋ฆฌํ๋ ์ํฐํฐ๊ฐ๋จ
em.merge(detachedMember); // ์ค์์ ์ํ์ ์ํฐํฐ (detached ์ํ)๋ฅผ ๋ค์ ์์ ์ํ๋ก ๋ณ๊ฒฝ
em.find(Member.class, 1L); // ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ํฐํฐ๋ฅผ ์กฐํ
3. ์ค์์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ ํ ๋ฒ Persistence Context์ ์ํด ๊ด๋ฆฌ๋์์ง๋ง, ํ์ฌ๋ Persistence Context์ ๋ถ๋ฆฌ๋ ์ํ์
(์ํฐํฐ ๊ฐ์ฒด์ ๋ณ๊ฒฝ ์ฌํญ์ด ๋ ์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์๋์ง ์์)
=> ์์์ฑ ์ปจํ ์คํธ ์ข ๋ฃ, ํธ๋์ญ์ ์ข ๋ฃ ๋ฑ์ผ๋ก๋ ์ค์์ ์ํ๋ก ์ ํ๋จ
ex.
em.detach(member);
em.clear();
em.close();
4. ์ญ์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ Persistence Context์์ ์ ๊ฑฐ๋ ์ํ์
(์ํฐํฐ ๊ฐ์ฒด๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ญ์ ๋จ)
ex.
em.remove(member);
๊ฒฐ๋ก ์ ์ผ๋ก EntityManager๋ Persistence Context์ 1์ฐจ ์บ์๋ก๋ถํฐ ์ํฐํฐ๋ฅผ ์กฐํํ ์ ์์ผ๋ฉฐ, ์ฐ๊ธฐ ์ง์ฐ ์ ์ฅ์์ ์๋ ์ฟผ๋ฆฌ๋ค์ flushํ์ฌ DB์ ๋๊ธฐ์ํฌ ์ ์์
๋ํ, JPQL์ด๋ Native Query๋ฅผ ์ด์ฉํด ์ง์ DB๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ์ ๋ ์์
JPQL์ JPA๊ฐ ์ง์ํ๋ ๋ค์ํ ์ฟผ๋ฆฌ ๋ฐฉ๋ฒ ์ค ํ๋๋ก SQL์ด ํ ์ด๋ธ์ ๋์์ผ๋ก ์ฟผ๋ฆฌํ๋ค๋ฉด ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌํ๋ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ์
=> ๊ฒฐ๊ตญ JPQL์ SQL๋ก ๋ณํ๋จ
Hibernate ๋?
Hibernate๋ JPA์ ๊ตฌํ์ฒด ์ค ํ๋๋ก, ๊ฐ์ฒด์ RDB๋ฅผ ๋งคํํ๋ ORM ํ๋ ์์ํฌ์
- Hibernate๋ JPA์ ํ์ค ๋ช
์ธ๋ฅผ ๊ตฌํํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
- JPA๋ฅผ ์ฌ์ฉํ๋ฉด ๊ตฌํ์ฒด๋ฅผ Hibernate, EclipseLink, OpenJPA ๋ฑ์ผ๋ก ์ฝ๊ฒ ๊ต์ฒด ๊ฐ๋ฅ
- @Entity, @Table, @Column ๊ฐ์ ์ด๋
ธํ
์ด์
์ ํ์ฉํด์ DB ํ
์ด๋ธ๊ณผ Java ํด๋์ค๋ฅผ ์ฐ๊ฒฐํ ์ ์์
- Hibernate๋ SQL์ด ์๋๋ผ ๊ฐ์ฒด ์งํฅ์ ์ธ ์ฟผ๋ฆฌ ์ธ์ด JPQL์ ์ฌ์ฉํจ
(SQL๋ ์ฌ์ฉ์ ๊ฐ๋ฅ) - Lazy Loading (์ง์ฐ ๋ก๋ฉ) ์ง์
=> ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํ์ํ ๋๋ง ์กฐํํ๋๋ก ์ค์ ํ ์ ์์
์๋ฅผ ๋ค์ด User์ Post๋ผ๋ ๋ ์ํฐํฐ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ์ ๋
User๋ ์ฌ๋ฌ ๊ฐ์ Post๋ฅผ ๊ฐ์ง ์ ์๋ @OnetoMany ๊ด๊ณ์ผ ์ ์์
์ด ๋ Lazy Loading ์๋ ๋ฐฉ์์
User ๊ฐ์ฒด๋ฅผ ์กฐํํ ๋ posts ๋ชฉ๋ก์ ๋ฐ๋ก ์กฐํ๋์ง ์๊ณ User ๊ฐ์ฒด๋ง DB์์ ๊ฐ์ ธ์ค๊ณ
์ดํ, user.getPosts()๋ฅผ ํธ์ถํ๋ ค๊ณ ํ๋ฉด, ์ค์ DB์์ Post ๋ฐ์ดํฐ๋ฅผ ์กฐํํจ
Lazy Loading์ ์ฅ์ ์ผ๋ก๋
์ฒ์์ ํ์ํ ๋ฐ์ดํฐ๋ง ๋ก๋ํ๋ฏ๋ก ๋ถํ์ํ DB ์กฐํ๋ฅผ ์ค์ฌ ์ฑ๋ฅ์ ์ต์ ํ ํ ์ ์๊ณ
๊ด๋ จ๋ ๋ฐ์ดํฐ๊ฐ ์ค์ ๋ก ํ์ํ ๋๋ง ๋ก๋๋๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ ์ค์ผ ์ ์์
LazyLoading์ ๋จ์ ์ผ๋ก๋
N+1 ๋ฌธ์ ๊ฐ ์์
=> ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ๋ก๋ํ ํ์ ์์ ์ํฐํฐ๋ฅผ ์ ๊ทผํ ๋ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ฌ ์ฑ๋ฅ ์ ํ๊ฐ ์ผ์ด๋ ์ ์์
- Hibernate๋ ์๋์ผ๋ก ํธ๋์ญ์ ์ ๊ด๋ฆฌํ๊ณ , ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฑ๋ฅ์ ๋์ด๊ธฐ ์ํด 1์ฐจ ์บ์, 2์ฐจ ์บ์๋ฅผ ์ง์ํจ
Spring Data JPA๋?
Spring Data JPA๋ JPA๋ฅผ ๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ฃผ๋ Spring ๊ธฐ๋ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
=> ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ์ฒด(Entity) ๊ฐ์ ๋งคํ์ ๋ ํธํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋๋ก JPA๋ฅผ ๊ฐ์ธ๋ ๊ธฐ์ ์
JPA์ ๋จ์ ๋ฐ๋ณต์ ์ธ ์์ ์ ์๋ํํด ์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๊ณ ๋ณด๋ฉด๋จ
=> JpaRepository๋ง ๋ง๋ค๋ฉด ๋ฉ์๋ ์ด๋ฆ๋ง์ผ๋ก๋ ์ฟผ๋ฆฌ๋ฅผ ์๋ ์์ฑ ํด์ฃผ๊ธฐ์ ์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํด์ง๊ณ ์ ์ง๋ณด์๊ฐ ์ฌ์์ ธ์ ์์ฐ์ฑ์ด ํฅ์๋จ
(EntityManager๋ฅผ ์ง์ ์ฐ์ง ์์๋ JpaRepository๊ฐ ์์์ ์ฒ๋ฆฌํด์ค)
๊ฒฐ๋ก ์ Spring์์ JPA๋ฅผ ์ฌ์ฉํ ๊ฑฐ๋ฉด Spirng Data JPA๋ ํจ๊ป ์ฐ์!
JPA(Spring Data JPA)๋ฅผ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ๋ ์ด๋ค์์ผ๋ก?
JPA(Spring Data JPA)๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ SQL์ ์ฃผ๊ณ ๋ฐ์์ผํจ
=> JPA๋ ์๋ฐ ๊ฐ์ฒด(Entity)์ DB ํ
์ด๋ธ์ ์๋์ผ๋ก ๋งคํํด์ฃผ๊ธฐ์ SQL์ ์ง์ ์์ฑํ ํ์ X
JPA ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ ํฌ๊ฒ 2๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฐพ์ ์ ์์์
- JDBC(Java Database Connectivity)๋ฅผ ์ฌ์ฉ
=> JDBC๋ SQL์ ์ง์ ์จ์ผ ํ๊ณ , Connection, Statement, ResultSet์ ๊ด๋ฆฌํด์ผ ํด์ JPA์ ๋นํด ์๋์ ์ผ๋ก ์ฝ๋๊ฐ ๋ณต์ก
(JPA๋ ๊ฒฐ๊ตญ ๋ด๋ถ์ ์ผ๋ก JDBC๋ฅผ ์ฌ์ฉํจ) - MyBatis ์ฌ์ฉ (SQL Mapper)
=> MyBatis๋ SQL์ XML ํ์ผ์ด๋ ์ด๋ ธํ ์ด์ ์ผ๋ก ๋ฐ๋ก ๊ด๋ฆฌํด์, SQL์ ์ฝ๊ฒ ์์ฑํ ์ ์์
(๋๊ฐ์ด SQL์ ์ง์ ์์ฑํด์ผํ์ง๋ง JDBC๋ณด๋ค ๊น๋ํ๊ฒ SQL์ ๊ด๋ฆฌํ ์ ์์)
๊ทธ๋ ๋ค๋ฉด ๋ฌด์กฐ๊ฑด JPA๋ง ์จ์ผํ ๊น?
JPA๋ฅผ ์ฐ๋ฉด ์ข์ ๊ฒฝ์ฐ
- CRUD๊ฐ ๋ง๊ณ SQL์ ์ต๋ํ ์๋ํ ํ๊ณ ์ถ์ ๋ (Spring Data JPA ์ฌ์ฉ)
- ๊ฐ์ฒด ์ค์ฌ์ผ๋ก ๊ฐ๋ฐํ๊ณ ์ถ์ ๋ (OOP ์คํ์ผ ์ ์ง ๊ฐ๋ฅ)
=> SQL ์์ด ๊ฐ์ฒด ์ค์ฌ์ผ๋ก ๊ฐ๋ฐ ๊ฐ๋ฅ - ์ ์ง๋ณด์๋ฅผ ์ฝ๊ฒ ํ๊ณ ์ถ์ ๋ (SQL์ ์ต์ํํ๊ณ ์ฝ๋๋ง ๊ด๋ฆฌ)
JPA๋ฅผ ์ ์ฐ๋ ๊ฒฝ์ฐ
- ๋ณต์กํ SQL ํ๋์ด ํ์ํ ๊ฒฝ์ฐ
- ๋ ๊ฑฐ์ ํ๋ก์ ํธ์์ ๊ธฐ์กด SQL์ ์ ์งํด์ผ ํ ๋
- ๋จ์ํ ๋ฐ์ดํฐ ์กฐํ๋ง ํ์ํ๊ณ , ORM์ด ์คํ๋ ค ์ค๋ฒํค๋๊ฐ ํด ๋

=> JPA๋ ๊ธฐ๋ณธ์ ์ผ๋ก RDB์ ๋งคํ์ ์ํ ๊ธฐ์ ์ด๊ธฐ์ NoSQL์ JPA๋ฅผ ํ์ฅํ์ฌ NoSQL ์ง์์ ์ถ๊ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (ex. Hibernate-OGM) ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ ์ฉ ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (ex. MongoDB Java Driver) ๋ฅผ ์ฌ์ฉ
๋ํ, ๊ทธ๋ฆผ์ ์ดํด๋ณด๋ฉด JPA๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ JDBC ์ฌ์ด์์ ๋์ํ๊ณ ์๋๋ฐ
๊ฐ๋ฐ์๊ฐ JPA๋ฅผ ์ฌ์ฉํ๋ฉด, JPA ๋ด๋ถ์์ JDBC API๋ฅผ ์ฌ์ฉํ์ฌ SQL์ ํธ์ถํ์ฌ DB์ ํต์ ํจ
(๊ฐ๋ฐ์๊ฐ ์ง์ JDBC ์ฌ์ฉ X)
JPA์ ddl-auto ์ต์ ์ ๊ฐ๊ฐ ์ด๋ค ๋์์ ํ๊ณ ์ด๋ค ์ํฉ์์ ์ฌ์ฉํด์ผ ํ ๊น?
ddl-auto ์ต์ ์ ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ Hibernate์ ๊ฐ์ JPA ๊ตฌํ์ฒด๋ฅผ ์ฌ์ฉํ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ๊ด๋ฆฌ๋ฅผ ์ ์ดํ๋ ์ค์ ์
(application.properties ๋๋ application.yml ํ์ผ์์ ์ค์ ํ ์ ์์)
=> ๋ค์ํ ๊ฐ์ ๋ฐ๋ผ DB ์คํค๋ง์ ๋ํด ๋ค๋ฅธ ๋์์ ์ํํจ
- none
=> ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง์ ๊ด๋ จ๋ ์ด๋ ํ ์์ ๋ ์ํํ์ง ์์
(์๋์ผ๋ก ๊ด๋ฆฌํ๊ณ ์ถ์ ๋ ์ ์ฉํ๋ฉฐ, ํ๋ก๋์ ํ๊ฒฝ์์ ์ฃผ๋ก ์ฌ์ฉ) - validate
=> ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋, ์ํฐํฐ ๋งคํ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง์ ์ผ์นํ๋์ง ๊ฒ์ฆํ๋ฉฐ ์คํค๋ง ๋ณ๊ฒฝ์ ๋ฐ๋ก ์ํ X
(ํ๋ก๋์ ํ๊ฒฝ์์ ์ํฐํฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๊ฐ ์ผ์นํ๋์ง ํ์ธํ๊ณ ์ถ์ ๋ ์ฌ์ฉ) - update
=> ์ํฐํฐ ๋งคํ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ๋น๊ตํ์ฌ ํ์ํ ๊ฒฝ์ฐ ์คํค๋ง๋ฅผ ์ ๋ฐ์ดํธํจ
(๊ธฐ์กด ๋ฐ์ดํฐ๋ ์ ์ง๋์ง๋ง, ์๋ก์ด ์ํฐํฐ๋ ๋ณ๊ฒฝ๋ ์ํฐํฐ ํ๋๋ ์คํค๋ง์ ๋ฐ์๋๋ฏ๋ก ํ๋ก๋์ ํ๊ฒฝ์์๋ ์๊ธฐ์น ์์ ์คํค๋ง ๋ณ๊ฒฝ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฃผ์๊ฐ ํ์ํจ) - create
=> ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋ ๊ธฐ์กด ์คํค๋ง๋ฅผ ์ญ์ ํ๊ณ ์๋ก ์์ฑํจ
(๊ธฐ์กด ๋ฐ์ดํฐ๊ฐ ๋ชจ๋ ์ญ์ ๋๋ฏ๋ก ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฌ์ฉํ์ง ์๊ณ ๊ฐ๋ฐ ์ด๊ธฐ์ ๋น ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์์ฑํด์ผํ ๋ ์ ์ฉํจ) - create-drop
=> create์ ์ ์ฌํ์ง๋ง, ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ข ๋ฃ๋ ๋ ์คํค๋ง๋ฅผ ์ญ์ ํจ
(๋ง์ฐฌ๊ฐ์ง๋ก ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฌ์ฉํ์ง ์๊ณ ํ ์คํธ ํ๊ฒฝ์์ ์ผ์์ ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๊ฐ ํ์ํ ๊ฒฝ์ฐ ์ ์ฉํ๋ฉฐ, ๋งค ํ ์คํธ ์คํ ์๋ง๋ค ๊นจ๋ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ๋ฅผ ์ ์งํ๊ณ ์ ํ ๋ ์ฌ์ฉํจ)
ํ๋ก๋์ ํ๊ฒฝ์์ ์คํค๋ง ๋ณ๊ฒฝ์ ์ด๋ป๊ฒ ํด์ผํ ๊น?
์คํค๋ง ๋ณ๊ฒฝ์ด ํ์ํ ๋๋ ์ ์ ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ๋๊ตฌ(Flyway, Liquibase ๋ฑ)์ ์ฌ์ฉํ์ฌ ์ ์ด๋ ๋ฐฉ์์ผ๋ก ์คํค๋ง๋ฅผ ๊ด๋ฆฌํ๊ฑฐ๋, ์ฌ์ฉ์๊ฐ ์๋ ์๋ฒฝ์ ์คํค๋ง ๋ณ๊ฒฝ ์์ ์ ์๋์ผ๋ก ์งํํ๋ ๊ฒ์ด ๋ ์์ ํ ์ ์์
JPA์ N + 1 ๋ฌธ์
์ฐ๊ด ๊ด๊ณ๊ฐ ์ค์ ๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๊ฒฝ์ฐ์, ์กฐํ๋ ๋ฐ์ดํฐ ๊ฐ์(N) ๋งํผ ์ฐ๊ด ๊ด๊ณ์ ์กฐํ ์ฟผ๋ฆฌ๊ฐ ์ถ๊ฐ๋ก ๋ฐ์ํ๋ ํ์์
์๋ฅผ ๋ค์ด, ๋ธ๋ก๊ทธ ๊ฒ์๊ธ๊ณผ ๋๊ธ์ด ์๋ ๊ฒฝ์ฐ, ๊ฒ์๊ธ์ ์กฐํํ ํ ๊ฐ ๊ฒ์๊ธ๋ง๋ค ๋๊ธ์ ์กฐํํ๊ธฐ ์ํ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์์
์ด๋ฅผ N + 1 ๋ฌธ์ ๋ผ๊ณ ํจ
์ฝ๊ฒ ๋งํ๋ฉด ์กฐํ ์ 1๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์๊ฐํ๊ณ ์ค๊ณํ๋๋ฐ ๋์ค์ง ์์๋ ๋๋ ์กฐํ์ ์ฟผ๋ฆฌ๊ฐ N๊ฐ๊ฐ ๋ ๋ฐ์ํ๋ ๋ฌธ์
findAll ๋ฉ์๋์ ๊ธ๋ก๋ฒ ํจ์น ์ ๋ต ๋ณ N + 1 ๋ฌธ์ ์ํฉ
findAll ๋ฉ์๋๋ spring data jpa์์ ์ ๊ณตํ๋ repository์ ๋ฉ์๋์ด๊ณ
๊ธ๋ก๋ฒ ํจ์น ์ ๋ต(Global Fetch Strategy)์ JPA์์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ด๋ป๊ฒ ๊ฐ์ ธ์ฌ ๊ฒ์ธ์ง๋ฅผ ๊ฒฐ์ ํ๋ ์ ๋ต์
=> ์ฆ์๋ก๋ฉ, ์ง์ฐ๋ก๋ฉ ๋ฐฉ์์ด ์์
๊ธ๋ก๋ฒ ํจ์น ์ ๋ต์ ์ฆ์๋ก๋ฉ์ผ๋ก ์ค์ ํ๊ณ findAll()์ ์คํํ๋ฉด N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํจ
=> findAll()์ด select u from User u ๋ผ๋ JPQL ๊ตฌ๋ฌธ์ ์์ฑํด์ ์คํํ๊ธฐ ๋๋ฌธ
(JPQL์ ๊ธ๋ก๋ฒ ํจ์น ์ ๋ต์ ๊ณ ๋ คํ์ง ์๊ณ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋๋ฐ ๋ชจ๋ User๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ ์คํ ํ, ์ฆ์๋ก๋ฉ ์ค์ ์ ๋ณด๊ณ ์ฐ๊ด๊ด๊ณ์ ์๋ ๋ชจ๋ ์ํฐํฐ๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๊ฒ์)
=> N + 1 ๋ฌธ์ ๋ ๊ฒฐ๊ตญ ์ฟผ๋ฆฌ์ ๊ฐ์๊ฐ ๋ฌธ์ ์ฌ์ ์ฆ์๋ก๋ฉ์ด ๋ฌด์จ ์๊ด์ด์ง๋ผ๊ณ ์๊ฐํ๋๋ฐ ์ฆ์๋ก๋ฉ์ด ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๊ฒ ์๋๋ผ (์ด๊ฑด fetch join) ์๋์ผ๋ก ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ๋ฐ๋ก ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ผ๋ก ์ฌ๋ฌ ๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋๊ฑธ๋ก ์ดํดํ์์
๊ธ๋ก๋ฒ ํจ์น ์ ๋ต์ ์ง์ฐ ๋ก๋ฉ์ผ๋ก ์ค์ ํ๊ณ findAll()์ ์คํํ๋ฉด N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์
=> ์ฐ๊ด๊ด๊ณ์ ์๋ ์ํฐํฐ๋ฅผ ์ค์ ๊ฐ์ฒด ๋์ ํ๋ก์ ๊ฐ์ฒด๋ก ์์ฑํ์ฌ ์ฃผ์
ํ๊ธฐ ๋๋ฌธ
(ํ๋ก์ ๊ฐ์ฒด๋ ๋์ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ๊ฐ์ง๋ก ๋ง๋ค์ด์ง ๊ฐ์ฒด๋ผ๊ณ ์๊ฐํ๋ฉด๋จ)
ํ์ง๋ง ์ด๋ ๊ฒ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ์ ์ค์ ๋ฐ์ดํฐ๊ฐ ํ์ํ์ฌ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ณ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์
์ฆ์ ๋ก๋ฉ(EAGER)์ findAll() ์คํ์ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ณ ,
์ง์ฐ ๋ก๋ฉ(LAZY)์ findAll() ์คํ๋ง์ผ๋ก๋ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ง๋ง, ์ดํ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ฉด ๋ฐ์ํจ
N + 1 ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ
N + 1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ fetch join, @EntityGraph๋ฅผ ์ฌ์ฉํด๋ณผ ์ ์์
fetch join์ ์ฐ๊ด ๊ด๊ณ์ ์๋ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์ฆ์ ๋ก๋ฉํ๋ ๊ตฌ๋ฌธ์
(JOIN์ ๊ฐ์ ํ์ฌ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํด๊ฒฐ)
select distinct u
from User u
left join fetch u.posts
@EnttiyGraph๋ ๋น์ทํ ํจ๊ณผ๋ฅผ ๋ง๋ค์ด๋ด๋ฉฐ, ์ฟผ๋ฆฌ ๋ฉ์๋์ ํด๋น ์ด๋
ธํ
์ด์
์ ์ถ๊ฐํด ์ฌ์ฉํ ์ ์์
@EntityGraph(attributePaths = {"posts"}, type = EntityGraphType.FETCH)
List<User> findAll();
Spring Data JPA์์ ์๋ก์ด Entity์ธ์ง ํ๋จํ๋ ๋ฐฉ๋ฒ์ ๋ฌด์์ผ๊น?
@Override
public boolean isNew(T entity) {
if(versionAttribute.isEmpty()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
(JpaMetamodelEntityInformation -> @Version ํ๋์ ๊ฐ์ด null ์ธ์ง ์ฌ๋ถ๋ก ํ๋จ)
์๋ก์ด Entity์ธ์ง ์ฌ๋ถ๋ JpaEntityInformation์ isNew(T entity)์ ์ํด ํ๋จ๋จ
=> ๋ค๋ฅธ ์ค์ ์ด ์์ผ๋ฉด JpaEntityInformation์ ๊ตฌํ์ฒด ์ค JpaMetamodelEntityInformation ํด๋์ค๊ฐ ๋์
@Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ ์๊ฑฐ๋ @Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ primitive ํ์ (๊ธฐ๋ณธํ ex. int, long ๋ฑ)์ด๋ฉด AbstractEntityInformation์ isNew()๋ฅผ ํธ์ถํจ
=> @Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ wrapper class(ex. Integer, Long ๋ฑ)์ด๋ฉด null ์ฌ๋ถ๋ฅผ ํ์ธ
Primitive Type์ Stack์ ์์นํ๋ฉฐ null์ด ํ์ฉ๋์ง ์๊ณ (๊ฐ์ฒด X) Wrapper Class๋ Heap์ ์์นํ๋ฉฐ null์ด ํ์ฉ๋จ (๊ฐ์ฒด O)
@Version์ด๋ Optimistic Lock์ ์ ์ฉํ ๋ ์ฌ์ฉํ๋ ์ด๋ ธํ ์ด์ ์ผ๋ก ๋์์ฑ ์ ์ด๋ฅผ ์ํด ์ฌ์ฉ๋๋ฉฐ, ํธ๋์ญ์ ์ถฉ๋์ ๋ฐฉ์งํ ์ ์์
=> ์ํฐํฐ๊ฐ ์ ์ฅ๋ ๋๋ง๋ค @Version ํ๋ ๊ฐ์ด ์ฆ๊ฐํ๊ธฐ์ @Version ํ๋๊ฐ null์ด๋ฉด ์๋ก์ด ์ํฐํฐ๋ผ๊ณ ํ๋จํ ์ ์์
public boolean isNew(T entity) {
Id id = getId(entity);
Class<ID> idType = getIdType();
if (!idType.isPrimitive()) {
return id == null;
}
if (id instanceof Number) {
return ((Number) id).longValue() == 0L;
}
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
}
(AbstractEntityInformation -> @Id ๊ฐ์ด null์ธ์ง ์ฌ๋ถ๋ก ํ๋จ)
@Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ ์์ด์ AbstractEntityInformation ํด๋์ค๊ฐ ๋์ํ๋ฉด @Id ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ํ๋๋ฅผ ํ์ธํด์ primitive ํ์ ์ด ์๋๋ผ๋ฉด null ์ฌ๋ถ, Number์ ํ์ ํ์ ์ด๋ฉด 0์ธ์ง ์ฌ๋ถ๋ฅผ ํ์ธํจ
=> ์ ์ฝ๋์์ primitive ํ์ ์ ์ด๋ป๊ฒ ๋ค๋ฃจ๋๊ฑธ๊น ๊ถ๊ธํ๋๋ฐ getId(entity) ๋ฉ์๋๊ฐ ์๋์ผ๋ก Wrapper Class๋ก Auto-Boxing (๋ณํ) ๋๋ค๊ณ ํจ ๊ทธ๋์, instanceof๋ก Number ๋ผ๋ฉด 0์ธ์ง ํ์ธ (long, int ๊ฐ์ primitive ํ์ ์ด 0์ผ๋ก ์ด๊ธฐํ ๋๋ฏ๋ก)
@GeneratedValue ์ด๋ ธํ ์ด์ ์ผ๋ก ํค ์์ฑ ์ ๋ต์ ์ฌ์ฉํ๋ฉด DB์ ์ ์ฅ๋ ๋ id๊ฐ ํ ๋น๋๊ธฐ ๋๋ฌธ์ DB์ ์ ์ฅ๋๊ธฐ ์ ๋ฉ๋ชจ๋ฆฌ์์ ์์ฑ๋ ๊ฐ์ฒด id๊ฐ ๋น์ด์์
=> isNew()๋ true๊ฐ ๋์ด ์๋ก์ด entity๋ก ํ๋จ
์ง์ ID๋ฅผ ํ ๋นํ๋ ๊ฒฝ์ฐ์๋ ์ด๋ค์์ผ๋ก ๋์ํ ๊น?
public class JpaPersistableEntityInformation<T extends Persistable<ID>, ID>
extends JpaMetamodelEntityInformation<T, ID> {
public JpaPersistableEntityInformation(Class<T> domainClass, Metamodel metamodel,
PersistenceUnitUtil persistenceUnitUtil) {
super(domainClass, metamodel, persistenceUnitUtil);
}
@Override
public boolean isNew(T entity) {
return entity.isNew();
}
@Nullable
@Override
public ID getId(T entity) {
return entity.getId();
}
}
JpaPersistableEntityInformation์ isNew()๊ฐ ๋์ํจ
ํค ์์ฑ ์ ๋ต์ ์ฌ์ฉํ์ง ์๊ณ ์ง์ ID๋ฅผ ํ ๋นํ๋ ๊ฒฝ์ฐ ์๋ก์ด entity๋ก ๊ฐ์ฃผ๋์ง ์์
=> ์์์๋ ์ ์ด๋จ์ง๋ง @GeneratedValue ์ด๋
ธํ
์ด์
์ผ๋ก ๋ง๋ ํค๋ DB์ ์ ์ฅ๋๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ์์ ์์ฑ๋ ๊ฐ์ฒด์ id๊ฐ ๋น์ด์์ง๋ง ์ด๋ ๊ฒ id๋ฅผ ์๋์ผ๋ก ํ ๋นํ๋ฉด, entity๊ฐ ๋ฉ๋ชจ๋ฆฌ์์๋ถํฐ id๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ์ ๊ธฐ๋ณธ JPA ํ๋จ ๊ธฐ์ค๋๋ก๋ผ๋ฉด ID๊ฐ ์กด์ฌํ๋ฏ๋ก "๊ธฐ์กด ๋ฐ์ดํฐ"๋ก ๊ฐ์ฃผ๋จ
public interface Persistable<ID> {
ID getId();
boolean isNew(); // ์๋ก์ด ์ํฐํฐ์ธ์ง ํ์ธํ๋ ๋ฉ์๋
}
import org.springframework.data.domain.Persistable;
import jakarta.persistence.*;
@Entity
public class MyEntity implements Persistable<Long> {
@Id
private Long id;
@Transient // JPA๊ฐ ์ด ํ๋๋ฅผ DB ์ปฌ๋ผ์ผ๋ก ์ ์ฅํ์ง ์๋๋ก ํจ
private boolean isNew = false;
public MyEntity(Long id) {
this.id = id;
this.isNew = true; // ์๋์ผ๋ก ID ํ ๋นํ ๋ ์๋ก์ด ์ํฐํฐ๋ก ๊ฐ์ฃผ
}
@Override
public boolean isNew() {
return this.isNew;
}
@Override
public Long getId() {
return this.id;
}
@PostPersist // JPA์์ ์ํฐํฐ๊ฐ ์ฒ์ persist๋ ํ ์คํ๋๋ ๋ฉ์๋
@PostLoad // JPA์์ ์ํฐํฐ๊ฐ DB์์ ์กฐํ(๋ก๋ฉ)๋ ํ ์คํ๋๋ ๋ฉ์๋
public void markNotNew() {
this.isNew = false; // ์ ์ฅ๋๊ฑฐ๋ ๋ก๋๋ ํ์๋ ๊ธฐ์กด ์ํฐํฐ๋ก ๊ฐ์ฃผ
}
}
์ด ๋๋ ์์ ๊ฐ์ด Entity์์ Persistable<T> ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ JpaMetamodelEntityInformation ํด๋์ค๊ฐ ์๋ JpaPersistableEntityInformation์ isNew()๊ฐ ๋์ํ๋๋ก ํด์ผํจ
Spring Data JPA๊ฐ isNew()๋ฅผ ํธ์ถํด์ฃผ๋ ๋ถ๋ถ์ ์ ๊ณต๋์ง๋ง, isNew()๊ฐ ์ด๋ป๊ฒ ๋์ํ ์ง๋ ์ง์ ๊ตฌํํด์ผ ํจ
๊ทธ๋ผ ์ฌ๊ธฐ์ ๊ถ๊ธ์ฆ์ด ๋๋ ๋ถ๋ถ์ด ์์ ์ ์์
Entity๊ฐ Persistable์ ๊ตฌํํ๋์ง ์ด๋ป๊ฒ ํ๋ณํ๊ณ ํ๋ฆ์ด ๋ญ๊น?
Spring Data JPA๊ฐ Entity ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํ๋ ํ๋ฆ
์ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ JpaRepositoryFactory๊ฐ SimpleJpaRepository๋ฅผ ์์ฑํ๊ฒ๋จ
Spring Data JPA์์๋ JpaRepository๋ฅผ ๊ตฌํํ๋ ์ธํฐํ์ด์ค๊ฐ ์์ผ๋ฉด, ์๋์ผ๋ก ํ๋ก์(proxy) ๊ฐ์ฒด๋ฅผ ์์ฑํด์ JpaRepositoryFactory์์ ์ฒ๋ฆฌํ๋๋กํจ
JpaRepositoryFactory๋ ์์ฒญ๋ฐ์ ์ํฐํฐ ํ์ ์ ๋ง๊ฒ ์ ์ ํ ๋ฆฌํฌ์งํ ๋ฆฌ ๊ตฌํ์ฒด๋ฅผ ์์ฑํ๋๋ฐ, ์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก SimpleJpaRepository๋ฅผ ์ฌ์ฉํจ
์ฆ, ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ JpaRepositoryFactory๊ฐ SimpleJpaRepository๋ฅผ ์์ฑํด์ ๋น(bean)์ผ๋ก ๋ฑ๋ก
public class JpaRepositoryFactory extends RepositoryFactorySupport {
private final EntityManager em;
private final JpaMetamodelMappingContext context;
public JpaRepositoryFactory(EntityManager entityManager) {
this.em = entityManager;
this.context = new JpaMetamodelMappingContext(entityManager.getMetamodel());
}
@Override
protected <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
Metamodel metamodel = em.getMetamodel();
PersistentEntity<T, ID> entity = context.getRequiredPersistentEntity(domainClass);
return Persistable.class.isAssignableFrom(domainClass) // โ
Persistable ์ธํฐํ์ด์ค ๊ตฌํ ์ฌ๋ถ ํ์ธ
? new JpaPersistableEntityInformation<>(domainClass, metamodel, em.getPersistenceUnitUtil()) // โ
Persistable ์ฌ์ฉ ์
: new JpaMetamodelEntityInformation<>(domainClass, metamodel, em.getPersistenceUnitUtil()); // โ
์ผ๋ฐ ์ํฐํฐ
}
@Override
protected Object getTargetRepository(RepositoryInformation information) {
JpaEntityInformation<?, ?> entityInformation = getEntityInformation(information.getDomainType()); // โ
์ฌ๊ธฐ์ ํธ์ถ๋จ
return new SimpleJpaRepository<>(entityInformation, em); // โ
SimpleJpaRepository ์์ฑ
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleJpaRepository.class;
}
}
getEntityInformation ๋ถ๋ถ์์ A.class.isAssignableFrom(B.class) => B๊ฐ A๋ฅผ ์์ ๋๋ ๊ตฌํํ๋ค๋ฉด ture
์ด ๋, Spring Data JPA๋ Repository๋ฅผ ๋ง๋ค ๋ Entity ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๊ณผ์ ์์ ํด๋น Entity๊ฐ Persistable์ ๊ตฌํํ๋์ง ์๋์ผ๋ก ํ๋จํจ
- Persistable์ ๊ตฌํํ๋ค๋ฉด JpaPersistableEntitiyInformation ์ฌ์ฉ
- Persistable์ ๊ตฌํํ์ง ์์๋ค๋ฉด JpaMetamodleEntityInformation ์ฌ์ฉ
์ด๋ ๊ฒ ์์ฑ๋ JpaEntityInformation ๊ฐ์ฒด๋ SimpleJpaRepository์ ์์ฑ์๋ก ์ ๋ฌ๋จ (๋ฐ์ ์ฝ๋ ์ฐธ๊ณ )
public class SimpleJpaRepository<T, ID> implements JpaRepository<T, ID> {
private final JpaEntityInformation<T, ID> entityInformation;
private final EntityManager em;
public SimpleJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager em) {
this.entityInformation = entityInformation; // โ
์์ฑ์์์ ํ ๋น
this.em = em;
}
@Override
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
}
save ํธ์ถ ์์๋ SimpleJpaRepository์ save ๋ฉ์๋๊ฐ ํธ์ถ๋จ (์๋ก์ด entity๋ฉด persist, ์๋๋ฉด merge)
์ดํ ์ํฐํฐ ์ ์ฅ ์์ฒญ์ด ์ค๋ฉด, SimpleJpaRepository.save()๊ฐ ์คํ๋๋๋ฐ
entityInformation.isNew(entity)๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๊ฒ๋จ
์๋ก์ด Entity์ธ์ง ํ๋จํ๋๊ฒ ์ค์ํ ์ด์
์์ SimpleJpaRepository์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ, SimpleJpaRepository์ save() ๋ฉ์๋์์ isNew()๋ฅผ ์ฌ์ฉํ์ฌ persist๋ฅผ ์ํํ ์ง merge๋ฅผ ์ํํ ์ง ๊ฒฐ์ ํจ
=> ๋ง์ฝ ID๋ฅผ ์ง์ ์ง์ ํด์ฃผ๋ ๊ฒฝ์ฐ์๋ ์ ๊ท entity๋ผ๊ณ ํ๋จํ์ง ์๊ธฐ ๋๋ฌธ์ merge๋ฅผ ์ํ
์ด ๋ ํด๋น entity๋ ์ ๊ท์์๋ ๋ถ๊ตฌํ๊ณ DB๋ฅผ ์กฐํํ๊ธฐ ๋๋ฌธ์ ๋นํจ์จ์ ์
๋ฐ๋ผ์, ์๋ก์ด entity์ธ์ง ํ๋จํ๋ ๊ฒ์ ์ค์ํ ๋ถ๋ถ์
์์ ์ฝ๋๋ GPT ์ ์๋์ ํ์ ๋น๋ ธ์ต๋๋ค.
'๐ซ Backend > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Boot์์ Supabase ์ฌ์ฉํ๊ธฐ (Kotlin) (0) | 2025.02.25 |
---|---|
Spring boot ์์ H2 ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉํ๊ธฐ (Kotlin) (0) | 2025.02.20 |
[Spring Boot] Docker๋ฅผ ์ด์ฉํด EC2์ ๋ฐฐํฌํด๋ณด๊ธฐ (4) | 2024.08.27 |
[Spring Boot] Custom Exception ์ค์ (0) | 2024.08.19 |
[Spring Boot] MySQL JPA ์ฐ๋ (3) | 2024.07.22 |
๋งค์ผ๋ฉ์ผ ๋ฐฑ์๋ ์ง๋ฌธ์ ์ฐธ๊ณ ํด ๊ฐ์ธ์ ์ผ๋ก ํ์ตํ ๋ด์ฉ์ ์ ๋ฆฌํ์์ต๋๋ค.
์ค๋ฅ๊ฐ ์๋ค๋ฉด ์ธ์ ๋ ํผ๋๋ฐฑ ์ฃผ์๋ฉด ๋ฐ๋ก ๋ฐ์ํ๊ฒ ์ต๋๋ค..!
์์ํ๊ธฐ์ ์์
JPA๋?
JPA(Java Persistence API)๋ ์๋ฐ ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ๋ถ๋ฌ์ค๋ ๋ฐฉ๋ฒ์ ์ ์ํ ํ์ค API์
=> ORM(Object-Relational Mapping) ๊ธฐ์ ์ ์ ๊ณตํ๋ ์๋ฐ์ ๊ณต์ ํ์ค์
JPA๋ ์์ฒด์ ์ผ๋ก ๋์ํ์ง ์๊ณ Hibernate์ ๊ฐ์ ๊ตฌํ์ฒด๊ฐ ํ์ํจ
EntityManager๋?
EntityManager์ ๋ํด ์๊ธฐ ์ํด์ Persistence Context์ ๋ํด ์์์ผํจ
Persistence Context๋ ์ํฐํฐ๋ฅผ ์๊ตฌ ์ ์ฅํ๋ ํ๊ฒฝ์ผ๋ก 1์ฐจ ์บ์ฑ, ์ฐ๊ธฐ ์ง์ฐ, ๋ณ๊ฒฝ ๊ฐ์ง๋ฅผ ํตํด ์์ ๋ก์ง์ ํจ์จ์ ์ผ๋ก ํ ์ ์๊ฒ ํด์ค
=> ์ด๋ฌํ ํจ์จ์ ์ธ ์์ ๋ก์ง ์ํ์ ์ํด์ ์ํฐํฐ๋ Persistence Context(์์์ฑ ์ปจํ
์คํธ)์ ๊ด๋ฆฌ๋์ด์ผํจ
์ด๋ฐ ์์
์ ๋์์ฃผ๋ ๊ฒ์ด ๋ฐ๋ก EntityManager ์
=> ์ํฐํฐ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ณ , Persistence Context์ ์ํธ์์ฉํจ์ผ๋ก์จ ์์ ๋ก์ง์ ์ํํ๋ ์ญํ ์ ๊ฐ์ง๊ณ ์์
์ํฐํฐ๋ Persistence Context์ ๊ด๋ จํ์ฌ 4๊ฐ์ง ์ํ(๋น์์, ์์, ์ค์์, ์ญ์ )๋ฅผ ๊ฐ์ง ์ ์์
1. ๋น์์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ ์๋ก ์์ฑ๋์์ง๋ง, ์์ง Persistence Context์ ์ฐ๊ด๋์ง ์์ ์ํ์
(๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ด๋ จ์ด ์์ผ๋ฉฐ, ์ํฐํฐ ๊ฐ์ฒด๋ ๋ฉ๋ชจ๋ฆฌ ์์๋ง ์กด์ฌ)
ex.
Member member = new Member("JunGyo");
2. ์์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ Persistence Context์ ๊ด๋ฆฌ๋๊ณ ์๋ ์ํ์
(์ํฐํฐ์ ๋ณ๊ฒฝ ์ฌํญ์ด ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์๋จ)
ex.
em.persist(member); // ์์์ฑ ์ปจํ ์คํธ๊ฐ ๊ด๋ฆฌํ๋ ์ํฐํฐ๊ฐ๋จ
em.merge(detachedMember); // ์ค์์ ์ํ์ ์ํฐํฐ (detached ์ํ)๋ฅผ ๋ค์ ์์ ์ํ๋ก ๋ณ๊ฒฝ
em.find(Member.class, 1L); // ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ํฐํฐ๋ฅผ ์กฐํ
3. ์ค์์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ ํ ๋ฒ Persistence Context์ ์ํด ๊ด๋ฆฌ๋์์ง๋ง, ํ์ฌ๋ Persistence Context์ ๋ถ๋ฆฌ๋ ์ํ์
(์ํฐํฐ ๊ฐ์ฒด์ ๋ณ๊ฒฝ ์ฌํญ์ด ๋ ์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์๋์ง ์์)
=> ์์์ฑ ์ปจํ ์คํธ ์ข ๋ฃ, ํธ๋์ญ์ ์ข ๋ฃ ๋ฑ์ผ๋ก๋ ์ค์์ ์ํ๋ก ์ ํ๋จ
ex.
em.detach(member);
em.clear();
em.close();
4. ์ญ์ ์ํ๋ ์ํฐํฐ ๊ฐ์ฒด๊ฐ Persistence Context์์ ์ ๊ฑฐ๋ ์ํ์
(์ํฐํฐ ๊ฐ์ฒด๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ญ์ ๋จ)
ex.
em.remove(member);
๊ฒฐ๋ก ์ ์ผ๋ก EntityManager๋ Persistence Context์ 1์ฐจ ์บ์๋ก๋ถํฐ ์ํฐํฐ๋ฅผ ์กฐํํ ์ ์์ผ๋ฉฐ, ์ฐ๊ธฐ ์ง์ฐ ์ ์ฅ์์ ์๋ ์ฟผ๋ฆฌ๋ค์ flushํ์ฌ DB์ ๋๊ธฐ์ํฌ ์ ์์
๋ํ, JPQL์ด๋ Native Query๋ฅผ ์ด์ฉํด ์ง์ DB๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ์ ๋ ์์
JPQL์ JPA๊ฐ ์ง์ํ๋ ๋ค์ํ ์ฟผ๋ฆฌ ๋ฐฉ๋ฒ ์ค ํ๋๋ก SQL์ด ํ ์ด๋ธ์ ๋์์ผ๋ก ์ฟผ๋ฆฌํ๋ค๋ฉด ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌํ๋ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ์
=> ๊ฒฐ๊ตญ JPQL์ SQL๋ก ๋ณํ๋จ
Hibernate ๋?
Hibernate๋ JPA์ ๊ตฌํ์ฒด ์ค ํ๋๋ก, ๊ฐ์ฒด์ RDB๋ฅผ ๋งคํํ๋ ORM ํ๋ ์์ํฌ์
- Hibernate๋ JPA์ ํ์ค ๋ช
์ธ๋ฅผ ๊ตฌํํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
- JPA๋ฅผ ์ฌ์ฉํ๋ฉด ๊ตฌํ์ฒด๋ฅผ Hibernate, EclipseLink, OpenJPA ๋ฑ์ผ๋ก ์ฝ๊ฒ ๊ต์ฒด ๊ฐ๋ฅ
- @Entity, @Table, @Column ๊ฐ์ ์ด๋
ธํ
์ด์
์ ํ์ฉํด์ DB ํ
์ด๋ธ๊ณผ Java ํด๋์ค๋ฅผ ์ฐ๊ฒฐํ ์ ์์
- Hibernate๋ SQL์ด ์๋๋ผ ๊ฐ์ฒด ์งํฅ์ ์ธ ์ฟผ๋ฆฌ ์ธ์ด JPQL์ ์ฌ์ฉํจ
(SQL๋ ์ฌ์ฉ์ ๊ฐ๋ฅ) - Lazy Loading (์ง์ฐ ๋ก๋ฉ) ์ง์
=> ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํ์ํ ๋๋ง ์กฐํํ๋๋ก ์ค์ ํ ์ ์์
์๋ฅผ ๋ค์ด User์ Post๋ผ๋ ๋ ์ํฐํฐ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ์ ๋
User๋ ์ฌ๋ฌ ๊ฐ์ Post๋ฅผ ๊ฐ์ง ์ ์๋ @OnetoMany ๊ด๊ณ์ผ ์ ์์
์ด ๋ Lazy Loading ์๋ ๋ฐฉ์์
User ๊ฐ์ฒด๋ฅผ ์กฐํํ ๋ posts ๋ชฉ๋ก์ ๋ฐ๋ก ์กฐํ๋์ง ์๊ณ User ๊ฐ์ฒด๋ง DB์์ ๊ฐ์ ธ์ค๊ณ
์ดํ, user.getPosts()๋ฅผ ํธ์ถํ๋ ค๊ณ ํ๋ฉด, ์ค์ DB์์ Post ๋ฐ์ดํฐ๋ฅผ ์กฐํํจ
Lazy Loading์ ์ฅ์ ์ผ๋ก๋
์ฒ์์ ํ์ํ ๋ฐ์ดํฐ๋ง ๋ก๋ํ๋ฏ๋ก ๋ถํ์ํ DB ์กฐํ๋ฅผ ์ค์ฌ ์ฑ๋ฅ์ ์ต์ ํ ํ ์ ์๊ณ
๊ด๋ จ๋ ๋ฐ์ดํฐ๊ฐ ์ค์ ๋ก ํ์ํ ๋๋ง ๋ก๋๋๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ ์ค์ผ ์ ์์
LazyLoading์ ๋จ์ ์ผ๋ก๋
N+1 ๋ฌธ์ ๊ฐ ์์
=> ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ๋ก๋ํ ํ์ ์์ ์ํฐํฐ๋ฅผ ์ ๊ทผํ ๋ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ฌ ์ฑ๋ฅ ์ ํ๊ฐ ์ผ์ด๋ ์ ์์
- Hibernate๋ ์๋์ผ๋ก ํธ๋์ญ์ ์ ๊ด๋ฆฌํ๊ณ , ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฑ๋ฅ์ ๋์ด๊ธฐ ์ํด 1์ฐจ ์บ์, 2์ฐจ ์บ์๋ฅผ ์ง์ํจ
Spring Data JPA๋?
Spring Data JPA๋ JPA๋ฅผ ๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ฃผ๋ Spring ๊ธฐ๋ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
=> ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ์ฒด(Entity) ๊ฐ์ ๋งคํ์ ๋ ํธํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋๋ก JPA๋ฅผ ๊ฐ์ธ๋ ๊ธฐ์ ์
JPA์ ๋จ์ ๋ฐ๋ณต์ ์ธ ์์ ์ ์๋ํํด ์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๊ณ ๋ณด๋ฉด๋จ
=> JpaRepository๋ง ๋ง๋ค๋ฉด ๋ฉ์๋ ์ด๋ฆ๋ง์ผ๋ก๋ ์ฟผ๋ฆฌ๋ฅผ ์๋ ์์ฑ ํด์ฃผ๊ธฐ์ ์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํด์ง๊ณ ์ ์ง๋ณด์๊ฐ ์ฌ์์ ธ์ ์์ฐ์ฑ์ด ํฅ์๋จ
(EntityManager๋ฅผ ์ง์ ์ฐ์ง ์์๋ JpaRepository๊ฐ ์์์ ์ฒ๋ฆฌํด์ค)
๊ฒฐ๋ก ์ Spring์์ JPA๋ฅผ ์ฌ์ฉํ ๊ฑฐ๋ฉด Spirng Data JPA๋ ํจ๊ป ์ฐ์!
JPA(Spring Data JPA)๋ฅผ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ๋ ์ด๋ค์์ผ๋ก?
JPA(Spring Data JPA)๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ SQL์ ์ฃผ๊ณ ๋ฐ์์ผํจ
=> JPA๋ ์๋ฐ ๊ฐ์ฒด(Entity)์ DB ํ
์ด๋ธ์ ์๋์ผ๋ก ๋งคํํด์ฃผ๊ธฐ์ SQL์ ์ง์ ์์ฑํ ํ์ X
JPA ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ ํฌ๊ฒ 2๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฐพ์ ์ ์์์
- JDBC(Java Database Connectivity)๋ฅผ ์ฌ์ฉ
=> JDBC๋ SQL์ ์ง์ ์จ์ผ ํ๊ณ , Connection, Statement, ResultSet์ ๊ด๋ฆฌํด์ผ ํด์ JPA์ ๋นํด ์๋์ ์ผ๋ก ์ฝ๋๊ฐ ๋ณต์ก
(JPA๋ ๊ฒฐ๊ตญ ๋ด๋ถ์ ์ผ๋ก JDBC๋ฅผ ์ฌ์ฉํจ) - MyBatis ์ฌ์ฉ (SQL Mapper)
=> MyBatis๋ SQL์ XML ํ์ผ์ด๋ ์ด๋ ธํ ์ด์ ์ผ๋ก ๋ฐ๋ก ๊ด๋ฆฌํด์, SQL์ ์ฝ๊ฒ ์์ฑํ ์ ์์
(๋๊ฐ์ด SQL์ ์ง์ ์์ฑํด์ผํ์ง๋ง JDBC๋ณด๋ค ๊น๋ํ๊ฒ SQL์ ๊ด๋ฆฌํ ์ ์์)
๊ทธ๋ ๋ค๋ฉด ๋ฌด์กฐ๊ฑด JPA๋ง ์จ์ผํ ๊น?
JPA๋ฅผ ์ฐ๋ฉด ์ข์ ๊ฒฝ์ฐ
- CRUD๊ฐ ๋ง๊ณ SQL์ ์ต๋ํ ์๋ํ ํ๊ณ ์ถ์ ๋ (Spring Data JPA ์ฌ์ฉ)
- ๊ฐ์ฒด ์ค์ฌ์ผ๋ก ๊ฐ๋ฐํ๊ณ ์ถ์ ๋ (OOP ์คํ์ผ ์ ์ง ๊ฐ๋ฅ)
=> SQL ์์ด ๊ฐ์ฒด ์ค์ฌ์ผ๋ก ๊ฐ๋ฐ ๊ฐ๋ฅ - ์ ์ง๋ณด์๋ฅผ ์ฝ๊ฒ ํ๊ณ ์ถ์ ๋ (SQL์ ์ต์ํํ๊ณ ์ฝ๋๋ง ๊ด๋ฆฌ)
JPA๋ฅผ ์ ์ฐ๋ ๊ฒฝ์ฐ
- ๋ณต์กํ SQL ํ๋์ด ํ์ํ ๊ฒฝ์ฐ
- ๋ ๊ฑฐ์ ํ๋ก์ ํธ์์ ๊ธฐ์กด SQL์ ์ ์งํด์ผ ํ ๋
- ๋จ์ํ ๋ฐ์ดํฐ ์กฐํ๋ง ํ์ํ๊ณ , ORM์ด ์คํ๋ ค ์ค๋ฒํค๋๊ฐ ํด ๋

=> JPA๋ ๊ธฐ๋ณธ์ ์ผ๋ก RDB์ ๋งคํ์ ์ํ ๊ธฐ์ ์ด๊ธฐ์ NoSQL์ JPA๋ฅผ ํ์ฅํ์ฌ NoSQL ์ง์์ ์ถ๊ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (ex. Hibernate-OGM) ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ ์ฉ ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (ex. MongoDB Java Driver) ๋ฅผ ์ฌ์ฉ
๋ํ, ๊ทธ๋ฆผ์ ์ดํด๋ณด๋ฉด JPA๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ JDBC ์ฌ์ด์์ ๋์ํ๊ณ ์๋๋ฐ
๊ฐ๋ฐ์๊ฐ JPA๋ฅผ ์ฌ์ฉํ๋ฉด, JPA ๋ด๋ถ์์ JDBC API๋ฅผ ์ฌ์ฉํ์ฌ SQL์ ํธ์ถํ์ฌ DB์ ํต์ ํจ
(๊ฐ๋ฐ์๊ฐ ์ง์ JDBC ์ฌ์ฉ X)
JPA์ ddl-auto ์ต์ ์ ๊ฐ๊ฐ ์ด๋ค ๋์์ ํ๊ณ ์ด๋ค ์ํฉ์์ ์ฌ์ฉํด์ผ ํ ๊น?
ddl-auto ์ต์ ์ ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ Hibernate์ ๊ฐ์ JPA ๊ตฌํ์ฒด๋ฅผ ์ฌ์ฉํ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ๊ด๋ฆฌ๋ฅผ ์ ์ดํ๋ ์ค์ ์
(application.properties ๋๋ application.yml ํ์ผ์์ ์ค์ ํ ์ ์์)
=> ๋ค์ํ ๊ฐ์ ๋ฐ๋ผ DB ์คํค๋ง์ ๋ํด ๋ค๋ฅธ ๋์์ ์ํํจ
- none
=> ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง์ ๊ด๋ จ๋ ์ด๋ ํ ์์ ๋ ์ํํ์ง ์์
(์๋์ผ๋ก ๊ด๋ฆฌํ๊ณ ์ถ์ ๋ ์ ์ฉํ๋ฉฐ, ํ๋ก๋์ ํ๊ฒฝ์์ ์ฃผ๋ก ์ฌ์ฉ) - validate
=> ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋, ์ํฐํฐ ๋งคํ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง์ ์ผ์นํ๋์ง ๊ฒ์ฆํ๋ฉฐ ์คํค๋ง ๋ณ๊ฒฝ์ ๋ฐ๋ก ์ํ X
(ํ๋ก๋์ ํ๊ฒฝ์์ ์ํฐํฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๊ฐ ์ผ์นํ๋์ง ํ์ธํ๊ณ ์ถ์ ๋ ์ฌ์ฉ) - update
=> ์ํฐํฐ ๋งคํ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ๋น๊ตํ์ฌ ํ์ํ ๊ฒฝ์ฐ ์คํค๋ง๋ฅผ ์ ๋ฐ์ดํธํจ
(๊ธฐ์กด ๋ฐ์ดํฐ๋ ์ ์ง๋์ง๋ง, ์๋ก์ด ์ํฐํฐ๋ ๋ณ๊ฒฝ๋ ์ํฐํฐ ํ๋๋ ์คํค๋ง์ ๋ฐ์๋๋ฏ๋ก ํ๋ก๋์ ํ๊ฒฝ์์๋ ์๊ธฐ์น ์์ ์คํค๋ง ๋ณ๊ฒฝ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฃผ์๊ฐ ํ์ํจ) - create
=> ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋ ๊ธฐ์กด ์คํค๋ง๋ฅผ ์ญ์ ํ๊ณ ์๋ก ์์ฑํจ
(๊ธฐ์กด ๋ฐ์ดํฐ๊ฐ ๋ชจ๋ ์ญ์ ๋๋ฏ๋ก ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฌ์ฉํ์ง ์๊ณ ๊ฐ๋ฐ ์ด๊ธฐ์ ๋น ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์์ฑํด์ผํ ๋ ์ ์ฉํจ) - create-drop
=> create์ ์ ์ฌํ์ง๋ง, ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ข ๋ฃ๋ ๋ ์คํค๋ง๋ฅผ ์ญ์ ํจ
(๋ง์ฐฌ๊ฐ์ง๋ก ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฌ์ฉํ์ง ์๊ณ ํ ์คํธ ํ๊ฒฝ์์ ์ผ์์ ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๊ฐ ํ์ํ ๊ฒฝ์ฐ ์ ์ฉํ๋ฉฐ, ๋งค ํ ์คํธ ์คํ ์๋ง๋ค ๊นจ๋ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ๋ฅผ ์ ์งํ๊ณ ์ ํ ๋ ์ฌ์ฉํจ)
ํ๋ก๋์ ํ๊ฒฝ์์ ์คํค๋ง ๋ณ๊ฒฝ์ ์ด๋ป๊ฒ ํด์ผํ ๊น?
์คํค๋ง ๋ณ๊ฒฝ์ด ํ์ํ ๋๋ ์ ์ ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ๋๊ตฌ(Flyway, Liquibase ๋ฑ)์ ์ฌ์ฉํ์ฌ ์ ์ด๋ ๋ฐฉ์์ผ๋ก ์คํค๋ง๋ฅผ ๊ด๋ฆฌํ๊ฑฐ๋, ์ฌ์ฉ์๊ฐ ์๋ ์๋ฒฝ์ ์คํค๋ง ๋ณ๊ฒฝ ์์ ์ ์๋์ผ๋ก ์งํํ๋ ๊ฒ์ด ๋ ์์ ํ ์ ์์
JPA์ N + 1 ๋ฌธ์
์ฐ๊ด ๊ด๊ณ๊ฐ ์ค์ ๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๊ฒฝ์ฐ์, ์กฐํ๋ ๋ฐ์ดํฐ ๊ฐ์(N) ๋งํผ ์ฐ๊ด ๊ด๊ณ์ ์กฐํ ์ฟผ๋ฆฌ๊ฐ ์ถ๊ฐ๋ก ๋ฐ์ํ๋ ํ์์
์๋ฅผ ๋ค์ด, ๋ธ๋ก๊ทธ ๊ฒ์๊ธ๊ณผ ๋๊ธ์ด ์๋ ๊ฒฝ์ฐ, ๊ฒ์๊ธ์ ์กฐํํ ํ ๊ฐ ๊ฒ์๊ธ๋ง๋ค ๋๊ธ์ ์กฐํํ๊ธฐ ์ํ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์์
์ด๋ฅผ N + 1 ๋ฌธ์ ๋ผ๊ณ ํจ
์ฝ๊ฒ ๋งํ๋ฉด ์กฐํ ์ 1๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์๊ฐํ๊ณ ์ค๊ณํ๋๋ฐ ๋์ค์ง ์์๋ ๋๋ ์กฐํ์ ์ฟผ๋ฆฌ๊ฐ N๊ฐ๊ฐ ๋ ๋ฐ์ํ๋ ๋ฌธ์
findAll ๋ฉ์๋์ ๊ธ๋ก๋ฒ ํจ์น ์ ๋ต ๋ณ N + 1 ๋ฌธ์ ์ํฉ
findAll ๋ฉ์๋๋ spring data jpa์์ ์ ๊ณตํ๋ repository์ ๋ฉ์๋์ด๊ณ
๊ธ๋ก๋ฒ ํจ์น ์ ๋ต(Global Fetch Strategy)์ JPA์์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ด๋ป๊ฒ ๊ฐ์ ธ์ฌ ๊ฒ์ธ์ง๋ฅผ ๊ฒฐ์ ํ๋ ์ ๋ต์
=> ์ฆ์๋ก๋ฉ, ์ง์ฐ๋ก๋ฉ ๋ฐฉ์์ด ์์
๊ธ๋ก๋ฒ ํจ์น ์ ๋ต์ ์ฆ์๋ก๋ฉ์ผ๋ก ์ค์ ํ๊ณ findAll()์ ์คํํ๋ฉด N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํจ
=> findAll()์ด select u from User u ๋ผ๋ JPQL ๊ตฌ๋ฌธ์ ์์ฑํด์ ์คํํ๊ธฐ ๋๋ฌธ
(JPQL์ ๊ธ๋ก๋ฒ ํจ์น ์ ๋ต์ ๊ณ ๋ คํ์ง ์๊ณ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋๋ฐ ๋ชจ๋ User๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ ์คํ ํ, ์ฆ์๋ก๋ฉ ์ค์ ์ ๋ณด๊ณ ์ฐ๊ด๊ด๊ณ์ ์๋ ๋ชจ๋ ์ํฐํฐ๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๊ฒ์)
=> N + 1 ๋ฌธ์ ๋ ๊ฒฐ๊ตญ ์ฟผ๋ฆฌ์ ๊ฐ์๊ฐ ๋ฌธ์ ์ฌ์ ์ฆ์๋ก๋ฉ์ด ๋ฌด์จ ์๊ด์ด์ง๋ผ๊ณ ์๊ฐํ๋๋ฐ ์ฆ์๋ก๋ฉ์ด ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๊ฒ ์๋๋ผ (์ด๊ฑด fetch join) ์๋์ผ๋ก ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ๋ฐ๋ก ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ผ๋ก ์ฌ๋ฌ ๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋๊ฑธ๋ก ์ดํดํ์์
๊ธ๋ก๋ฒ ํจ์น ์ ๋ต์ ์ง์ฐ ๋ก๋ฉ์ผ๋ก ์ค์ ํ๊ณ findAll()์ ์คํํ๋ฉด N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์
=> ์ฐ๊ด๊ด๊ณ์ ์๋ ์ํฐํฐ๋ฅผ ์ค์ ๊ฐ์ฒด ๋์ ํ๋ก์ ๊ฐ์ฒด๋ก ์์ฑํ์ฌ ์ฃผ์
ํ๊ธฐ ๋๋ฌธ
(ํ๋ก์ ๊ฐ์ฒด๋ ๋์ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ๊ฐ์ง๋ก ๋ง๋ค์ด์ง ๊ฐ์ฒด๋ผ๊ณ ์๊ฐํ๋ฉด๋จ)
ํ์ง๋ง ์ด๋ ๊ฒ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ์ ์ค์ ๋ฐ์ดํฐ๊ฐ ํ์ํ์ฌ ์กฐํํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ณ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์
์ฆ์ ๋ก๋ฉ(EAGER)์ findAll() ์คํ์ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ณ ,
์ง์ฐ ๋ก๋ฉ(LAZY)์ findAll() ์คํ๋ง์ผ๋ก๋ N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ง๋ง, ์ดํ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ฉด ๋ฐ์ํจ
N + 1 ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ
N + 1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ fetch join, @EntityGraph๋ฅผ ์ฌ์ฉํด๋ณผ ์ ์์
fetch join์ ์ฐ๊ด ๊ด๊ณ์ ์๋ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์ฆ์ ๋ก๋ฉํ๋ ๊ตฌ๋ฌธ์
(JOIN์ ๊ฐ์ ํ์ฌ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํด๊ฒฐ)
select distinct u
from User u
left join fetch u.posts
@EnttiyGraph๋ ๋น์ทํ ํจ๊ณผ๋ฅผ ๋ง๋ค์ด๋ด๋ฉฐ, ์ฟผ๋ฆฌ ๋ฉ์๋์ ํด๋น ์ด๋
ธํ
์ด์
์ ์ถ๊ฐํด ์ฌ์ฉํ ์ ์์
@EntityGraph(attributePaths = {"posts"}, type = EntityGraphType.FETCH)
List<User> findAll();
Spring Data JPA์์ ์๋ก์ด Entity์ธ์ง ํ๋จํ๋ ๋ฐฉ๋ฒ์ ๋ฌด์์ผ๊น?
@Override
public boolean isNew(T entity) {
if(versionAttribute.isEmpty()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
(JpaMetamodelEntityInformation -> @Version ํ๋์ ๊ฐ์ด null ์ธ์ง ์ฌ๋ถ๋ก ํ๋จ)
์๋ก์ด Entity์ธ์ง ์ฌ๋ถ๋ JpaEntityInformation์ isNew(T entity)์ ์ํด ํ๋จ๋จ
=> ๋ค๋ฅธ ์ค์ ์ด ์์ผ๋ฉด JpaEntityInformation์ ๊ตฌํ์ฒด ์ค JpaMetamodelEntityInformation ํด๋์ค๊ฐ ๋์
@Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ ์๊ฑฐ๋ @Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ primitive ํ์ (๊ธฐ๋ณธํ ex. int, long ๋ฑ)์ด๋ฉด AbstractEntityInformation์ isNew()๋ฅผ ํธ์ถํจ
=> @Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ wrapper class(ex. Integer, Long ๋ฑ)์ด๋ฉด null ์ฌ๋ถ๋ฅผ ํ์ธ
Primitive Type์ Stack์ ์์นํ๋ฉฐ null์ด ํ์ฉ๋์ง ์๊ณ (๊ฐ์ฒด X) Wrapper Class๋ Heap์ ์์นํ๋ฉฐ null์ด ํ์ฉ๋จ (๊ฐ์ฒด O)
@Version์ด๋ Optimistic Lock์ ์ ์ฉํ ๋ ์ฌ์ฉํ๋ ์ด๋ ธํ ์ด์ ์ผ๋ก ๋์์ฑ ์ ์ด๋ฅผ ์ํด ์ฌ์ฉ๋๋ฉฐ, ํธ๋์ญ์ ์ถฉ๋์ ๋ฐฉ์งํ ์ ์์
=> ์ํฐํฐ๊ฐ ์ ์ฅ๋ ๋๋ง๋ค @Version ํ๋ ๊ฐ์ด ์ฆ๊ฐํ๊ธฐ์ @Version ํ๋๊ฐ null์ด๋ฉด ์๋ก์ด ์ํฐํฐ๋ผ๊ณ ํ๋จํ ์ ์์
public boolean isNew(T entity) {
Id id = getId(entity);
Class<ID> idType = getIdType();
if (!idType.isPrimitive()) {
return id == null;
}
if (id instanceof Number) {
return ((Number) id).longValue() == 0L;
}
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
}
(AbstractEntityInformation -> @Id ๊ฐ์ด null์ธ์ง ์ฌ๋ถ๋ก ํ๋จ)
@Version์ด ์ฌ์ฉ๋ ํ๋๊ฐ ์์ด์ AbstractEntityInformation ํด๋์ค๊ฐ ๋์ํ๋ฉด @Id ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ํ๋๋ฅผ ํ์ธํด์ primitive ํ์ ์ด ์๋๋ผ๋ฉด null ์ฌ๋ถ, Number์ ํ์ ํ์ ์ด๋ฉด 0์ธ์ง ์ฌ๋ถ๋ฅผ ํ์ธํจ
=> ์ ์ฝ๋์์ primitive ํ์ ์ ์ด๋ป๊ฒ ๋ค๋ฃจ๋๊ฑธ๊น ๊ถ๊ธํ๋๋ฐ getId(entity) ๋ฉ์๋๊ฐ ์๋์ผ๋ก Wrapper Class๋ก Auto-Boxing (๋ณํ) ๋๋ค๊ณ ํจ ๊ทธ๋์, instanceof๋ก Number ๋ผ๋ฉด 0์ธ์ง ํ์ธ (long, int ๊ฐ์ primitive ํ์ ์ด 0์ผ๋ก ์ด๊ธฐํ ๋๋ฏ๋ก)
@GeneratedValue ์ด๋ ธํ ์ด์ ์ผ๋ก ํค ์์ฑ ์ ๋ต์ ์ฌ์ฉํ๋ฉด DB์ ์ ์ฅ๋ ๋ id๊ฐ ํ ๋น๋๊ธฐ ๋๋ฌธ์ DB์ ์ ์ฅ๋๊ธฐ ์ ๋ฉ๋ชจ๋ฆฌ์์ ์์ฑ๋ ๊ฐ์ฒด id๊ฐ ๋น์ด์์
=> isNew()๋ true๊ฐ ๋์ด ์๋ก์ด entity๋ก ํ๋จ
์ง์ ID๋ฅผ ํ ๋นํ๋ ๊ฒฝ์ฐ์๋ ์ด๋ค์์ผ๋ก ๋์ํ ๊น?
public class JpaPersistableEntityInformation<T extends Persistable<ID>, ID>
extends JpaMetamodelEntityInformation<T, ID> {
public JpaPersistableEntityInformation(Class<T> domainClass, Metamodel metamodel,
PersistenceUnitUtil persistenceUnitUtil) {
super(domainClass, metamodel, persistenceUnitUtil);
}
@Override
public boolean isNew(T entity) {
return entity.isNew();
}
@Nullable
@Override
public ID getId(T entity) {
return entity.getId();
}
}
JpaPersistableEntityInformation์ isNew()๊ฐ ๋์ํจ
ํค ์์ฑ ์ ๋ต์ ์ฌ์ฉํ์ง ์๊ณ ์ง์ ID๋ฅผ ํ ๋นํ๋ ๊ฒฝ์ฐ ์๋ก์ด entity๋ก ๊ฐ์ฃผ๋์ง ์์
=> ์์์๋ ์ ์ด๋จ์ง๋ง @GeneratedValue ์ด๋
ธํ
์ด์
์ผ๋ก ๋ง๋ ํค๋ DB์ ์ ์ฅ๋๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ์์ ์์ฑ๋ ๊ฐ์ฒด์ id๊ฐ ๋น์ด์์ง๋ง ์ด๋ ๊ฒ id๋ฅผ ์๋์ผ๋ก ํ ๋นํ๋ฉด, entity๊ฐ ๋ฉ๋ชจ๋ฆฌ์์๋ถํฐ id๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ์ ๊ธฐ๋ณธ JPA ํ๋จ ๊ธฐ์ค๋๋ก๋ผ๋ฉด ID๊ฐ ์กด์ฌํ๋ฏ๋ก "๊ธฐ์กด ๋ฐ์ดํฐ"๋ก ๊ฐ์ฃผ๋จ
public interface Persistable<ID> {
ID getId();
boolean isNew(); // ์๋ก์ด ์ํฐํฐ์ธ์ง ํ์ธํ๋ ๋ฉ์๋
}
import org.springframework.data.domain.Persistable;
import jakarta.persistence.*;
@Entity
public class MyEntity implements Persistable<Long> {
@Id
private Long id;
@Transient // JPA๊ฐ ์ด ํ๋๋ฅผ DB ์ปฌ๋ผ์ผ๋ก ์ ์ฅํ์ง ์๋๋ก ํจ
private boolean isNew = false;
public MyEntity(Long id) {
this.id = id;
this.isNew = true; // ์๋์ผ๋ก ID ํ ๋นํ ๋ ์๋ก์ด ์ํฐํฐ๋ก ๊ฐ์ฃผ
}
@Override
public boolean isNew() {
return this.isNew;
}
@Override
public Long getId() {
return this.id;
}
@PostPersist // JPA์์ ์ํฐํฐ๊ฐ ์ฒ์ persist๋ ํ ์คํ๋๋ ๋ฉ์๋
@PostLoad // JPA์์ ์ํฐํฐ๊ฐ DB์์ ์กฐํ(๋ก๋ฉ)๋ ํ ์คํ๋๋ ๋ฉ์๋
public void markNotNew() {
this.isNew = false; // ์ ์ฅ๋๊ฑฐ๋ ๋ก๋๋ ํ์๋ ๊ธฐ์กด ์ํฐํฐ๋ก ๊ฐ์ฃผ
}
}
์ด ๋๋ ์์ ๊ฐ์ด Entity์์ Persistable<T> ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ JpaMetamodelEntityInformation ํด๋์ค๊ฐ ์๋ JpaPersistableEntityInformation์ isNew()๊ฐ ๋์ํ๋๋ก ํด์ผํจ
Spring Data JPA๊ฐ isNew()๋ฅผ ํธ์ถํด์ฃผ๋ ๋ถ๋ถ์ ์ ๊ณต๋์ง๋ง, isNew()๊ฐ ์ด๋ป๊ฒ ๋์ํ ์ง๋ ์ง์ ๊ตฌํํด์ผ ํจ
๊ทธ๋ผ ์ฌ๊ธฐ์ ๊ถ๊ธ์ฆ์ด ๋๋ ๋ถ๋ถ์ด ์์ ์ ์์
Entity๊ฐ Persistable์ ๊ตฌํํ๋์ง ์ด๋ป๊ฒ ํ๋ณํ๊ณ ํ๋ฆ์ด ๋ญ๊น?
Spring Data JPA๊ฐ Entity ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํ๋ ํ๋ฆ
์ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ JpaRepositoryFactory๊ฐ SimpleJpaRepository๋ฅผ ์์ฑํ๊ฒ๋จ
Spring Data JPA์์๋ JpaRepository๋ฅผ ๊ตฌํํ๋ ์ธํฐํ์ด์ค๊ฐ ์์ผ๋ฉด, ์๋์ผ๋ก ํ๋ก์(proxy) ๊ฐ์ฒด๋ฅผ ์์ฑํด์ JpaRepositoryFactory์์ ์ฒ๋ฆฌํ๋๋กํจ
JpaRepositoryFactory๋ ์์ฒญ๋ฐ์ ์ํฐํฐ ํ์ ์ ๋ง๊ฒ ์ ์ ํ ๋ฆฌํฌ์งํ ๋ฆฌ ๊ตฌํ์ฒด๋ฅผ ์์ฑํ๋๋ฐ, ์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก SimpleJpaRepository๋ฅผ ์ฌ์ฉํจ
์ฆ, ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ JpaRepositoryFactory๊ฐ SimpleJpaRepository๋ฅผ ์์ฑํด์ ๋น(bean)์ผ๋ก ๋ฑ๋ก
public class JpaRepositoryFactory extends RepositoryFactorySupport {
private final EntityManager em;
private final JpaMetamodelMappingContext context;
public JpaRepositoryFactory(EntityManager entityManager) {
this.em = entityManager;
this.context = new JpaMetamodelMappingContext(entityManager.getMetamodel());
}
@Override
protected <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
Metamodel metamodel = em.getMetamodel();
PersistentEntity<T, ID> entity = context.getRequiredPersistentEntity(domainClass);
return Persistable.class.isAssignableFrom(domainClass) // โ
Persistable ์ธํฐํ์ด์ค ๊ตฌํ ์ฌ๋ถ ํ์ธ
? new JpaPersistableEntityInformation<>(domainClass, metamodel, em.getPersistenceUnitUtil()) // โ
Persistable ์ฌ์ฉ ์
: new JpaMetamodelEntityInformation<>(domainClass, metamodel, em.getPersistenceUnitUtil()); // โ
์ผ๋ฐ ์ํฐํฐ
}
@Override
protected Object getTargetRepository(RepositoryInformation information) {
JpaEntityInformation<?, ?> entityInformation = getEntityInformation(information.getDomainType()); // โ
์ฌ๊ธฐ์ ํธ์ถ๋จ
return new SimpleJpaRepository<>(entityInformation, em); // โ
SimpleJpaRepository ์์ฑ
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleJpaRepository.class;
}
}
getEntityInformation ๋ถ๋ถ์์ A.class.isAssignableFrom(B.class) => B๊ฐ A๋ฅผ ์์ ๋๋ ๊ตฌํํ๋ค๋ฉด ture
์ด ๋, Spring Data JPA๋ Repository๋ฅผ ๋ง๋ค ๋ Entity ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๊ณผ์ ์์ ํด๋น Entity๊ฐ Persistable์ ๊ตฌํํ๋์ง ์๋์ผ๋ก ํ๋จํจ
- Persistable์ ๊ตฌํํ๋ค๋ฉด JpaPersistableEntitiyInformation ์ฌ์ฉ
- Persistable์ ๊ตฌํํ์ง ์์๋ค๋ฉด JpaMetamodleEntityInformation ์ฌ์ฉ
์ด๋ ๊ฒ ์์ฑ๋ JpaEntityInformation ๊ฐ์ฒด๋ SimpleJpaRepository์ ์์ฑ์๋ก ์ ๋ฌ๋จ (๋ฐ์ ์ฝ๋ ์ฐธ๊ณ )
public class SimpleJpaRepository<T, ID> implements JpaRepository<T, ID> {
private final JpaEntityInformation<T, ID> entityInformation;
private final EntityManager em;
public SimpleJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager em) {
this.entityInformation = entityInformation; // โ
์์ฑ์์์ ํ ๋น
this.em = em;
}
@Override
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
}
save ํธ์ถ ์์๋ SimpleJpaRepository์ save ๋ฉ์๋๊ฐ ํธ์ถ๋จ (์๋ก์ด entity๋ฉด persist, ์๋๋ฉด merge)
์ดํ ์ํฐํฐ ์ ์ฅ ์์ฒญ์ด ์ค๋ฉด, SimpleJpaRepository.save()๊ฐ ์คํ๋๋๋ฐ
entityInformation.isNew(entity)๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๊ฒ๋จ
์๋ก์ด Entity์ธ์ง ํ๋จํ๋๊ฒ ์ค์ํ ์ด์
์์ SimpleJpaRepository์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ, SimpleJpaRepository์ save() ๋ฉ์๋์์ isNew()๋ฅผ ์ฌ์ฉํ์ฌ persist๋ฅผ ์ํํ ์ง merge๋ฅผ ์ํํ ์ง ๊ฒฐ์ ํจ
=> ๋ง์ฝ ID๋ฅผ ์ง์ ์ง์ ํด์ฃผ๋ ๊ฒฝ์ฐ์๋ ์ ๊ท entity๋ผ๊ณ ํ๋จํ์ง ์๊ธฐ ๋๋ฌธ์ merge๋ฅผ ์ํ
์ด ๋ ํด๋น entity๋ ์ ๊ท์์๋ ๋ถ๊ตฌํ๊ณ DB๋ฅผ ์กฐํํ๊ธฐ ๋๋ฌธ์ ๋นํจ์จ์ ์
๋ฐ๋ผ์, ์๋ก์ด entity์ธ์ง ํ๋จํ๋ ๊ฒ์ ์ค์ํ ๋ถ๋ถ์
์์ ์ฝ๋๋ GPT ์ ์๋์ ํ์ ๋น๋ ธ์ต๋๋ค.
'๐ซ Backend > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Boot์์ Supabase ์ฌ์ฉํ๊ธฐ (Kotlin) (0) | 2025.02.25 |
---|---|
Spring boot ์์ H2 ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉํ๊ธฐ (Kotlin) (0) | 2025.02.20 |
[Spring Boot] Docker๋ฅผ ์ด์ฉํด EC2์ ๋ฐฐํฌํด๋ณด๊ธฐ (4) | 2024.08.27 |
[Spring Boot] Custom Exception ์ค์ (0) | 2024.08.19 |
[Spring Boot] MySQL JPA ์ฐ๋ (3) | 2024.07.22 |