๊ฐ์
Spring Data JPA ๋ฅผ ์ฌ์ฉํด์ DB ๋ฅผ ์กฐํํ๋ฉด ์ง์ฐ ๋ก๋ฉ์ผ๋ก ์ค์ ๋์ด ์๋ ์ฐ๊ด ๊ด๊ณ ๋ฐ์ดํฐ๋ฅผ ์ ์ธํ๊ณ ์ฒ์๋ถํฐ ๋ชจ๋ ๋ฐ์ดํฐ ์ปฌ๋ผ ๊ฐ๋ค์ ๋ถ๋ฌ์จ๋ค. JPA ํน์ ์ ์ฒ์์ ๋ชจ๋ ๊ฐ๋ค์ ์ด๊ธฐํ ์ํค๊ณ ์ค๋ ์ต์ ๊ธฐ์ค์ผ๋ก ๋ํฐ ์ฒดํน์ ํด์ผํ๊ธฐ ๋๋ฌธ์ด๋ค. ํ์ง๋ง readOnly = true์ผ ๊ฒฝ์ฐ๋ ๋ํฐ์ฒดํน์ ํ ํ์๊ฐ ์๊ณ ์ํ๋ ๊ฐ๋ง ์ป์ผ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ๊ฐ์ ์ด๊ธฐํํด์ ์ป์ ํ์๋ ์๋ค.
์๋ฅผ ๋ค์ด DocumentDB ์ ๊ฐ์ NoSQL์ ์ฌ์ฉํ์ง ์๊ณ , RDB๋ก ์ํฐํด ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค๊ณ ์น์.
@Entity
public class Article {
@Id
@GeneratedValue
private Long id;
private String title;
@Column(columnDefinition = "TEXT")
private String content; // ๋๋ ๋ฐ์ดํฐ (๋ณธ๋ฌธ ๋ด์ฉ)
private String author;
}
์ํฐํด์ ๋ณธ๋ฌธ ๋ด์ฉ์ ๊ธธ์ด๋ฅผ ์์ธกํ ์ ์์ผ๋ SQL ์๋ฃํ์ค์ ๋ง์ ๋ฌธ์๋ฅผ ์ ์ฅํ ์ ์๋ TEXT ์๋ฃํ์ ๊ฐ์ ธ์ผ ํ๋ค.
์ด๋ ์ํฐํด ๋ชฉ๋ก์ ์กฐํํ๊ธฐ ์ํด์ ๊ฐ๋จํ๊ฒ ๋ค์์ฒ๋ผ JPA ๋ฅผ ํ์ฉํ ์ ์๋ค.
List<Article> articles = articleRepository.findAll();
์ํฐํด ๋ฆฌ์คํธ๋ฅผ ๊ฐ๊ณตํ์ฌ ๋ฆฌ์คํฐ์ค๋ก ๋ณ๊ฒฝํ ์๋ต๊ฐ์ผ๋ก ์ค ์ ์๋ค. ๋งค๋ฒ ์๋ฌด๋ ์ง ์๊ฒ ๋ค๋ค ์ด๋ ๊ฒ ์ฌ์ฉํ ๊ฒ์ด๋ค. ํ์ง๋ง ์๋ฌด ์๊ฐ ์์ด ๊ทธ๋ฅ ์ฌ์ฉํ๋ค๊ฐ ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น์ ์ฑ๋ฅ ์ ํ๋ฅผ ์ผ์ผํฌ ์ ์๋ค.
SELECT id, title, content, author FROM articles;
์ ๋ฐฉ์๋๋ก ์ฌ์ฉํ๋ฉด ๋ชจ๋ ์ํฐํด๋ง๋ค ๋ณธ๋ฌธ ๋ด์ฉ์ ๊ฐ์ง๊ณ ์๊ฒ ๋๋ค. ๋ณธ๋ฌธ ๋ด์ฉ์ ๋ค๋ฅธ ํ๋ ๋ณด๋ค ๋ ํฐ ๋ฐ์ดํฐ ์์ ๊ฐ๊ณ ์๊ณ , ๋์ฉ๋ ๋ฐ์ดํฐ๋ ๊ทธ์ ๋น๋กํ์ฌ ๋์ ๋คํธ์ํฌ ๋์ญํญ๊ณผ ๋ถํ์ํ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ ๊ฐ๊ฒ ๋๋ค.
TEXT, BLOB์ฒ๋ผ ๋ฐ์ดํฐ ํฌ๊ธฐ๊ฐ ํฐ ์ปฌ๋ผ์ ์กฐํ ๋น์ฉ์ด ๋์ผ๋ฏ๋ก, ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ํฐ ์ํฅ์ ์ค ์ ์๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ชจ๋ ํ๋๋ฅผ ๊ฐ์ ธ์ค์ง ์๊ณ , ํ์ํ ๋ฐ์ดํฐ๋ง ์ ํ์ ์ผ๋ก ๊ฐ์ ธ์ค๋ ๊ฒ์ ํจ์จ์ ์ธ ์์ ์ฌ์ฉ์ ์ํด ์ค์ํ ํ๋ ๊ธฐ๋ฒ์ด๋ค. ๋ฐ๋ผ์ DTO๋ฅผ ์ฌ์ฉํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ง ์กฐํํ๋๋ก ์ค๊ณ๋ฅผ ๋ฐ๊ฟ ๋ฐ์ดํฐ ์ ์ก๊ณผ ๋ก๋ฉ ์๊ฐ์ ์ค์ฌ์ฃผ๋ ๊ฒ์ด ์ข๋ค.
public class ArticleDTO {
private Long id;
private String title;
private String author;
public ArticleDTO(Long id, String title, String author) {
this.id = id;
this.title = title;
this.author = author;
}
}
ํ์ํ ๊ฐ๋ง ํ๋๋ก ๊ฐ๋ DTO๋ฅผ ๋ง๋ค์ด ์ค๋ค.
@Query("SELECT new com.example.dto.ArticleDTO(a.id, a.title, a.author) FROM Article a")
List<ArticleDTO> findArticlesWithoutContent();
SELECT id, title, author FROM articles;
๊ทธ๋ฆฌ๊ณ JPQL๋ก ํ์ํ ์ปฌ๋ผ๋ง ์กฐํํ๋ค.
List<ArticleDTO> articles = articleRepository.findArticlesWithoutContent();
ORM ์ ์ํด ํ ์ด๋ธ์ ์ปฌ๋ผ๋ค์ DTO ์ ํ๋์ ์๋ง๊ฒ ํ์ฑ๋๋ค. ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๋ ์ปฌ๋ผ์ ์ ์ธํ์ผ๋ฏ๋ก ์ ์ก๋๋ ๋ฐ์ดํฐ ์์ด ์ค์ด๋ค๊ณ , ์ฟผ๋ฆฌ ์คํ ์๋๊ฐ ํฅ์๋๋ค.
์ด๋ ๊ฒ DTO๋ก ํ์ํ ์ปฌ๋ผ๋ง์ ๊ฐ๋ ์ฟผ๋ฆฌ ํ๋ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ๊ฐ๋๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค I/O ๊ฐ์
- TEXT ๋๋ BLOB ๊ฐ์ ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด๋ค์ด๋ ๋น์ฉ์ ์ ์ฝํ ์ ์๋ค.
- ๋คํธ์ํฌ ์ ์ก๋ ๊ฐ์
- ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์ ์กํ์ง ์์ผ๋ฏ๋ก ๋คํธ์ํฌ ๋ถํ๊ฐ ์ค์ด๋ ๋ค.
- ์ ํ๋ฆฌ์ผ์ด์
๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์ต์ ํ
- ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋ํ์ง ์์ผ๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ค์ด๋ ๋ค.
- ์ฟผ๋ฆฌ ์คํ ์๊ฐ ๋จ์ถ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ฒ๋ฆฌํด์ผ ํ ๋ฐ์ดํฐ ์์ด ์ค์ด๋ค์ด ์ฟผ๋ฆฌ ์คํ ์๊ฐ์ด ๋จ์ถ๋๋ค.
ํ์ง๋ง ๋ชจ๋ ์กฐํ๋ฅผ ๋ค DTO๋ฅผ ์จ์ ์กฐํํ๋ ๊ฒ ํญ์ ์ข์ ๊ฒ์ ์๋๋ค. ํน์ ๊ฒฝ์ฐ์๋ ์ํฐํฐ์ ํ๋ ๋ด์ฉ์ ๋ชจ๋ ํฌํจํ์ฌ ์กฐํํ๋ ๊ฒ์ด ๋ ๋์ ์ ์๋ค.
์กฐํ ์๋๋ฆฌ์ค๋ฅผ ๋ฐ์ ธ๋ด์ ์ถ๊ฐ ์ฟผ๋ฆฌ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ์๋ ์ง ํ์ธํด์ผํ๋ค.
- ์ ์ธ ํ๋ ํ๋๊ฐ ํ์ํ ์์ ์์ ๋ค์ ์กฐํํด์ผ ํ๋ฏ๋ก ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์๋ค. (N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ฃผ์)
๋ํ DTO ์ค๊ณ๋ ์ ์ํด์ผํ๋ค. DTO ํด๋์ค๋ฅผ ์ค๊ณํ๊ณ ์ ์ง๋ณด์ํด์ผ ํ๋ฏ๋ก, DTO ์ฌ์ฉ์ด ํญ์ ๊ฐ๋จํ์ง ์์ ์ ์๋ค.
DTO ์ค๊ณ
public class ChatRoomUserDTO {
private Long chatRoomId;
private String chatRoomName;
private String userName;
public ChatRoomUserDTO(Long chatRoomId, String chatRoomName, String userName) {
this.chatRoomId = chatRoomId;
this.chatRoomName = chatRoomName;
this.userName = userName;
}
}
@Query("SELECT new com.example.dto.ChatRoomUserDTO(cr.id, cr.name, u.name) " +
"FROM ChatRoom cr JOIN cr.user u")
List<ChatRoomUserDTO> findChatRoomsWithUsers();
์์ ๊ฐ์ ๋ฐฉ์์ ์๋ก ๋ค๋ฉด DTO ์์ฑ์๋ฅผ ํตํด์ ์กฐํ๊ฐ ์ผ์ด๋๊ณ ์๋ค.
ํ์ง๋ง MyBatis ์์ ํด๋์ค๋ฅผ ๊ฒฝ๋ก๋ฅผ ์ ๋ ๊ฒ ๊ฐ์ ๋ฐฉ์๋ง ์๋ ๊ฑด ์๋๋ค.
1. ์คํ๋ง ๋ฐ์ดํฐ JPA์ ๊ธฐ๋ณธ ๊ท์น ํ์ฉ
Spring Data JPA์์๋ ๋ฐํ ํ์ ์ผ๋ก DTO ํด๋์ค๋ฅผ ์ง์ ํ๋ฉด, ์๋์ผ๋ก ํจํค์ง ๊ฒฝ๋ก๋ฅผ ๊ฐ์งํ๋ค. DTO ํด๋์ค๊ฐ ๊ฐ์ ํจํค์ง์ ์๊ฑฐ๋ ๋ฆฌํฌ์งํ ๋ฆฌ์ ๋์ผํ ๋ชจ๋ ๋ด์ ์กด์ฌํ ๊ฒฝ์ฐ ํจํค์ง ๊ฒฝ๋ก๋ฅผ ์๋ตํ ์ ์๋ค.
ChatRoomUserDTO๊ฐ ๋ฆฌํฌ์งํ ๋ฆฌ์ ๋์ผํ ํจํค์ง์ด๊ฑฐ๋, ํด๋์ค ๊ฒฝ๋ก๋ฅผ Spring Data JPA๊ฐ ์ฐพ์ ์ ์๋ ๋ฒ์์ ์์ด์ผ ํ๋ค.
// ChatRoomUserDTO ํด๋์ค๊ฐ ๊ฐ์ ํจํค์ง์ ์๋ค๊ณ ๊ฐ์
@Query("SELECT new ChatRoomUserDTO(cr.id, cr.name, u.name) " +
"FROM ChatRoom cr JOIN cr.user u")
List<ChatRoomUserDTO> findChatRoomsWithUsers();
์ ๋ฐฉ์์ Spring Data JPA์์ ์์ฐ์ค๋ฌ์ด ํตํฉํ์ฌ ๋ฑํ ์ถ๊ฐ ์ค์ ๋ ํ์์๊ณ , DTO์ ๋น์ฆ๋์ค ๋ก์ง์ ์๋๋ฅผ ๋ช ํํ ๊ตฌ๋ถํ ์ ์๋ค. ๋ํ DTO๋ ๋ ๋ฆฝ์ ์ธ POJO๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ๋จ์ ํ ์คํธ๊ฐ ์ฉ์ดํ๋ค. DTO ํด๋์ค์ ์ถ๊ฐ ํ๋๋ฅผ ์ฝ๊ฒ ํ์ฅํ ์ ์๋ค.
2. Projection ์ธํฐํ์ด์ค ์ฌ์ฉ
Spring Data JPA์์ Projection์ ์ฌ์ฉํ๋ฉด DTO ๊ฐ์ฒด๋ฅผ ์ง์ ์์ฑํ์ง ์๊ณ ๋ ํน์ ํ๋๋ง ์กฐํํ์ฌ ๋งคํํ ์ ์๋ค.
public interface ChatRoomUserProjection {
Long getChatRoomId();
String getChatRoomName();
String getUserName();
}
์ฟผ๋ฆฌ์์ ์ธํฐํ์ด์ค ๋ฐํ
Spring Data JPA๋ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ Projection ์ธํฐํ์ด์ค์ ๋ง๊ฒ ์๋์ผ๋ก ๋งคํํ๋ค.
@Query("SELECT cr.id AS chatRoomId, cr.name AS chatRoomName, u.name AS userName " +
"FROM ChatRoom cr JOIN cr.user u")
List<ChatRoomUserProjection> findChatRoomsWithUsers();
List<ChatRoomUserProjection> results = chatRoomRepository.findChatRoomsWithUsers();
์ฅ์ ์ผ๋ก๋ DTO ๊ฐ์ฒด๋ฅผ ๋ช ์์ ์ผ๋ก ์์ฑํ์ง ์์๋ ๋๋ค. ํ์ง๋ง Projection์ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก, DTO์ ๋ก์ง์ด๋ ๋ณํ ์์ ์ด ํ์ํ ๊ฒฝ์ฐ ์ ํฉํ์ง ์๋ค.
3. ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ์ @SqlResultSetMapping ํ์ฉ
DTO์ ์์ฑ์ ํธ์ถ์ ํผํ๊ณ , ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ์ @SqlResultSetMapping์ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ ์ผ๋ก DTO๋ก ๋งคํํ ์ ์๋ค. JPA ํ์ค ๊ธฐ๋ฅ์ผ๋ก, ์ผ๋ฐ JPA์์ ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ๋ฅผ DTO๋ ์ํฐํฐ์ ๋งคํํ ๋ ์ฌ์ฉํ๋ค. Spring Data JPA์์๋ ์ง์ ์ ์ผ๋ก ์ฐ๋๋์ง ์์ผ๋ฉฐ, ์ฌ์ฉํ๋ ค๋ฉด EntityManager๋ฅผ ํตํด ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๋ฅผ ์คํํด์ผ ํ๋ค. EntityManager.createNativeQuery() ๋ฉ์๋์์ ์ฟผ๋ฆฌ์ @SqlResultSetMapping ์ด๋ฆ์ ์ฐ๊ฒฐํ์ฌ ๋งคํ์ ์ํํ๋ค.
public class ChatRoomUserDTO {
private Long chatRoomId;
private String chatRoomName;
private String userName;
public ChatRoomUserDTO(Long chatRoomId, String chatRoomName, String userName) {
this.chatRoomId = chatRoomId;
this.chatRoomName = chatRoomName;
this.userName = userName;
}
}
@Entity
@SqlResultSetMapping(
name = "ChatRoomUserMapping",
classes = @ConstructorResult(
targetClass = ChatRoomUserDTO.class,
columns = {
@ColumnResult(name = "chatRoomId", type = Long.class),
@ColumnResult(name = "chatRoomName", type = String.class),
@ColumnResult(name = "userName", type = String.class)
}
)
)
public class ChatRoom {
@Id
@GeneratedValue
private Long id;
private String name;
//๋ค๋ฅธ ํ๋๋ค
}
@Repository
public class ChatRoomRepository {
@PersistenceContext
private EntityManager entityManager;
public List<ChatRoomUserDTO> findChatRoomsWithUsers() {
return entityManager.createNativeQuery(
"SELECT cr.id AS chatRoomId, cr.name AS chatRoomName, u.name AS userName " +
"FROM chat_rooms cr JOIN users u ON cr.user_id = u.id",
"ChatRoomUserMapping"
).getResultList();
}
}
๋งค์ฐ ๋ณต์กํ ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๊ฐ ์กด์ฌํ ๋ ์ฌ์ฉํ ๋ง ํ๋ค. JPQL๋ก ์์ฑํ๊ธฐ ์ด๋ ค์ด ๋ณต์กํ SQL์ ๋ค๋ค์ผ ํ ๋ ์ฌ์ฉ๋๋ค.(e.g. ์๋ธ์ฟผ๋ฆฌ, ๋ณต์กํ ์กฐํฉ์ด๋ ํต๊ณ ์ฟผ๋ฆฌ) ๋ํ ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ ๊ฒฝ์ฐ์ ์ฌ์ฉ ๊ฐ๋ฅํ๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค I/O๋ฅผ ์ต์ํํ๊ฑฐ๋, ๋ค์ดํฐ๋ธ SQL์์ JPA์ ์ ํ์ ๋ฒ์ด๋์ผ ํ ๋ ์ฌ์ฉ๋๋ค.
๊ฒฐ๋ก
DTO๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ์ปฌ๋ผ๋ง ์กฐํํ๊ณ , ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ธํ๋ ๋ฐฉ์์ ํจ๊ณผ์ ์ธ ์ฟผ๋ฆฌ ํ๋ ๊ธฐ๋ฒ์ด๋ค. ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค I/O์ ๋คํธ์ํฌ ์ ์ก๋์ ์ค์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํ๋ค.
์ค๋ฌด์์๋ ๋ฆฌ์คํธํ ํ๋ฉด์ด๋ ์ฝ๊ธฐ ์ ์ฉ API์์ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์ต์ ํ ๊ธฐ๋ฒ ์ค ํ๋์ด๋ค. ํ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ธํจ์ผ๋ก์จ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ์ ์์ ์ฌ์ฉ์ ์ค์ด๊ณ ์ฑ๋ฅ์ ๊ฐ์ ํ ์ ์๋ค.
๋๊ธ