โ JPA ๋?
JPA(Java Persistence API)๋ ์๋ฐ(Java) ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ์ง์ํ๋ ํ์ค API์ ๋๋ค. JPA๋ ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ด์ ๋ถ์ผ์น๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ค๊ณ๋ ORM(Object-Relational Mapping) ๊ธฐ์ ์ ํ ์ข ๋ฅ์ ๋๋ค.
JPA์ ์ฃผ์ ๊ฐ๋
- ORM(Object-Relational Mapping): ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ธ ์๋ฐ์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ ์ด๋ธ์ ๋งคํํ๋ ๊ธฐ์ ์ ๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ์ฒด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ ๋ฐ์ดํฐ ๋ณํ์ ์๋์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
- Entity: JPA์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ๊ณผ ๋งคํ๋๋ ์๋ฐ ํด๋์ค์ ๋๋ค. ์ด ํด๋์ค๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ ํ์ ํํํ๋ฉฐ, ๊ฐ ํ๋๋ ํ ์ด๋ธ์ ์ปฌ๋ผ์ ๋งคํ๋ฉ๋๋ค.
- Persistence Context: ์ํฐํฐ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํ๋ ํ๊ฒฝ์ ๋๋ค. Persistence Context๋ ์ํฐํฐ๋ฅผ ์์์ฑ ์ํ๋ก ์ ์งํ๊ณ , ๋ณ๊ฒฝ์ด ๋ฐ์ํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋๊ธฐํํ๋ ์ญํ ์ ํฉ๋๋ค.
- EntityManager: ์ํฐํฐ๋ฅผ ์์ฑ, ์์ , ์ญ์ , ์กฐํ ๋ฑ์ ์์ ์ ์ํํ๋ JPA์ ํต์ฌ ์ธํฐํ์ด์ค์ ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ ์ ์ดํ๊ณ , Persistence Context๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
- JPQL(Java Persistence Query Language): JPA์์ ์ ๊ณตํ๋ SQL๊ณผ ์ ์ฌํ ๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ธ์ด์ ๋๋ค. ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ํ๋ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์์ผ๋ฉฐ, SQL์ ๋นํด ๋ ๊ฐ์ฒด ์งํฅ์ ์ด๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ๋ฆฝ์ ์ ๋๋ค.
JPA์ ์ฅ์
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ๋ฆฝ์ฑ: JPA๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ข ์์ ์ด์ง ์์ ์ฝ๋ ์์ฑ์ด ๊ฐ๋ฅํ์ฌ, ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ข ์๋์ง ์๊ณ ์ฌ๋ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ ์ ํ์ด ์ฌ์์ง๋๋ค.
- ์์ฐ์ฑ ํฅ์: SQL์ ์ง์ ์์ฑํ์ง ์๊ณ , ๊ฐ์ฒด ์งํฅ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ ์ํํ ์ ์์ด ๊ฐ๋ฐ ์์ฐ์ฑ์ด ํฅ์๋ฉ๋๋ค.
- ์ ์ง๋ณด์ ์ฉ์ด์ฑ: ๊ฐ์ฒด ์งํฅ์ ์ธ ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ฐ์ดํฐ ์ ๊ทผ ๋ก์ง์ ๋ถ๋ฆฌํ ์ ์์ผ๋ฏ๋ก ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ๊ฐ์ ๋ฉ๋๋ค.
- ์ฑ๋ฅ ์ต์ ํ: 1์ฐจ ์บ์, ์ง์ฐ ๋ก๋ฉ(Lazy Loading) ๋ฑ ๋ค์ํ ์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฒ์ ์ง์ํฉ๋๋ค.
JPA์ ๊ด๋ จ๋ ๊ธฐ์
JPA๋ ์ธํฐํ์ด์ค ๊ธฐ๋ฐ์ ํ์ค API๋ก, ์ด๋ฅผ ๊ตฌํํ ์ฌ๋ฌ ํ๋ ์์ํฌ๊ฐ ์กด์ฌํฉ๋๋ค. ๊ฐ์ฅ ๋ํ์ ์ธ ๊ตฌํ์ฒด๋ก๋ Hibernate, EclipseLink, DataNucleus ๋ฑ์ด ์์ผ๋ฉฐ, Spring Data JPA๋ ์คํ๋ง ํ๋ ์์ํฌ์ JPA๋ฅผ ํตํฉํด ๋ณด๋ค ๊ฐํธํ๊ฒ JPA๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ค๋๋ค.
๊ฒฐ๋ก
JPA๋ ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ํธ์์ฉ์ ๋จ์ํํ๊ณ , ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ๋ฆฝ์ ์ธ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ์ ์๊ฒ ๋์์ฃผ๋ ๊ฐ๋ ฅํ ORM ๊ธฐ์ ์ ๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ์ฒด ์งํฅ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ค๋ฃฐ ์ ์์ผ๋ฉฐ, ๊ฐ๋ฐ ์์ฐ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ต๋๋ค.
package com.example.jpa.memo.repository;
import com.example.jpa.entity.MemberMemoDTO;
import com.example.jpa.entity.Memo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface MemoRepository extends JpaRepository<Memo, Long>, //์ํฐํฐํ์
, ID์ ๋ํ ํ์
MemoCustomRepository, //์ปค์คํ
๋ ํฌ์งํ ๋ฆฌ
QuerydslPredicateExecutor<Memo> { //์ฟผ๋ฆฌDSL์์ ์ ๊ณต๋๋ ๋ช๋ช ํจ์๋ค์ ์ ๊ณตํด์ฃผ๋ ์ธํฐํ์ด์ค
//JpaRepository๋ก ๋ถํฐ, ๋ช๊ฐ์ ์ถ์๋ฉ์๋๋ฅผ ์๋์ผ๋ก ์์๋ฐ๊ฒ ๋ฉ๋๋ค.
//์ฟผ๋ฆฌ๋ฉ์๋
Memo findByWriterAndText(String writer, String text);
List<Memo> findByMnoLessThan(Long mno);
// SELECT * FROM MEMO WHERE MNO = 11;
Memo findByMno(Long mno);
// SELECT * FROM MEMO WHERE MNO BETWEEN 10 AND 20;
List<Memo> findByMnoBetween(Long start, Long end);
// SELECT * FROM MEMO WHERE WRITER LIKE '%10%';
List<Memo> findByWriterLike(String str);
// SELECT * FROM MEMO WHERE WRITER = 'example1' ORDER BY WRITER DESC;
List<Memo> findByWriterOrderByWriterDesc(String writer);
// SELECT * FROM MEMO WHERE MNO IN (10,20,30,40,50);
List<Memo> findByMnoIn(List<Long> list);
//์ฟผ๋ฆฌ๋ฉ์๋์ ๋ง์ง๋ง ๋งค๊ฐ๋ณ์์ Pageable์ ์ฃผ๋ฉด, ํ์ด์ง์ฒ๋ฆฌํฉ๋๋ค.
List<Memo> findByMnoLessThanEqual(Long mno, Pageable pageable);
//////////////////////////////////////////////////////////////
//JPQL - SQL๊ณผ ๋น์ทํ๋, ์ํฐํฐ๋ฅผ ์ฌ์ฉํด์ sql๋ฌธ์ ์์ฑ
//select, update, delete๋ ์ ๊ณต๋๋๋ฐ, insert๋ ์ ๊ณตํ์ง ์์ต๋๋ค.
//1. ํ
์ด๋ธ๋ช
์ด ์๋๋ผ ์ํฐํฐ๊ฐ ์ฌ์ฉ๋จ
//2. ์์ฑ(ํ๋)์ ๋์๋ฌธ์๋ฅผ ์ ๋ถ ๊ตฌ๋ถ
//3. ๋ณ์นญ์ ํ์
//4. SQLํค์๋ ๊ตฌ๋ถ x
@Query("select m from Memo m order by m.mno desc")
List<Memo> getListDesc(); //๋ฉ์๋๋ช
์์
//JPQLํ๋ผ๋ฏธํฐ ์ ๋ฌ @Param(์ด๋ฆ), :์ด๋ฆ
@Query("select m from Memo m where m.mno > :num order by m.mno desc")
List<Memo> getListDesc2(@Param("num") Long mno);
//JPQL select๋ฌธ์ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์ ๋ณ์ ์ผ๋ก ๋ฐ์ผ๋ ค๋ฉด Object[]์ฌ์ฉ ํฉ๋๋ค.
@Query("select m.writer, m.text from Memo m where m.mno > :num order by m.mno desc")
List<Object[]> getListDesc3(@Param("num") Long mno);
//JPQL์
๋ฐ์ดํธ
@Transactional //ํธ๋์ญ์
๋ฐ์
@Modifying //์
๋ฐ์ดํธ์
@Query("update Memo m set m.writer = :a where m.mno = :b")
int updateMemo(@Param("a") String a, @Param("b") Long b);
//JPQL์
๋ฐ์ดํธ - ๊ฐ์ฒดํ๋ผ๋ฏธํฐ ๋ฅผ ๋๊ธฐ๋ ๋ฐฉ๋ฒ #{๊ฐ์ฒด}
@Transactional
@Modifying
@Query("update Memo m set m.writer = :#{#a.writer}, m.text = :#{#a.text} where m.mno = :#{#a.mno}")
int updateMemo(@Param("a") Memo memo);
//delete from memo where mno = 10;
//JPQL๋๋ฆฌํธ๋ฌธ -
@Transactional
@Modifying
@Query("delete from Memo m where m.mno = :a")
int deleteMemo(@Param("a") Long mno);
//JPQL ๋ง์ง๋ง๋งค๊ฐ๋ณ์์ Pageable์ ์ฃผ๋ฉด, ํ์ด์ง์ฒ๋ฆฌํฉ๋๋ค.
//page์ฒ๋ฆฌ์๋ countQuery๊ฐ ํ์ํฉ๋๋ค (countQuery๊ตฌ๋ฌธ์ ์ง์ ์์ฑํ๋๊ฒ ๊ฐ๋ฅํจ)
@Query(value = "select m from Memo m where m.mno <= :a",
countQuery = "select count(m) from Memo m where m.mno <= :a")
Page<Memo> getListJPQL(@Param("a") Long mno, Pageable pageable);
// select mno, writer, text, concat(writer, text) as col, current_timestamp
// from memo
// where mno <= 100;
// ํ์ด์ง๋ค์ด์
์ฒ๋ฆฌํ๋ JPQL์ผ๋ก
@Query("select m.mno, m.writer, m.text, concat(m.writer, m.text) as col, current_timestamp " +
"from Memo m where m.mno <= :a")
Page<Object[]> getListJPQL2(@Param("a") Long mno, Pageable pageable);
//๋ค์ดํฐ๋ธ์ฟผ๋ฆฌ - JPQL์ด ๋๋ฌด ์ด๋ ค์ฐ๋ฉด, SQL๋ฐฉ์์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ ์ ๊ณตํด์ค๋๋ค.
@Query(value = "select * from memo where mno = ?", nativeQuery = true)
Memo getNative(Long mno);
//๊ตฌํ์ฒด์ ๋ง๋๋ ๊ตฌ๋ฌธ์ ์ธํฐํ์ด์ค์์ ์ด๋ ๊ฒ ํธ์ถํ๋ ๊ฒ๊ณผ ๋์ผํฉ๋๋ค.
// @Query("select m from Memo m inner join m.member x where m.mno >= :a")
// List<Memo> mtoJoin1(@Param("a") long a);
@Query(value = "select new com.example.jpa.entity.MemberMemoDTO(x.id, x.name, x.signDate, m.mno, m.writer, m.text) " +
"from Memo m left join m.member x where m.text like %:text%"
,countQuery = "select count(m) from Memo m left join m.member x where m.text like %:text%"
)
Page<MemberMemoDTO> joinPage(@Param("text") String text, Pageable pageable);
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
}
th {
background-color: #f2f2f2;
}
tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
tbody tr:hover {
background-color: #f1f1f1;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 20px;
list-style: none;
padding: 0;
}
.pagination li {
margin: 0 5px;
}
.pagination a {
display: block;
padding: 8px 16px;
text-decoration: none;
color: #007bff;
border: 1px solid #ddd;
border-radius: 4px;
}
.pagination a.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.pagination a:hover {
background-color: #ddd;
}
</style>
</head>
<body>
<select name="searchType">
<option value="mno">๋ฒํธ</option>
<option value="text">๋ด์ฉ</option>
<option value="writer">์์ฑ์</option>
<option value="textWriter">๋ด์ฉ + ์์ฑ์</option>
</select>
<input type="text" name="searchName">
<button type="button" onclick="searchList()">๊ฒ์</button>
<table>
<thead>
<tr>
<th>MNO</th>
<th>Writer</th>
<th>Text</th>
<th>Id</th>
<th>Name</th>
<th>Sign Date</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- Rows will be inserted here by JavaScript -->
</tbody>
</table>
<ul class="pagination" id="pagination">
<!-- Pagination buttons will be inserted here by JavaScript -->
</ul>
<script>
var pagination = document.getElementById("pagination"); //ํ์ด์ง๋ค์ด์
ํ๊ทธ
var page = 1;
var amount = 10;
var start = 0; //์์ํ์ด์ง๋ค์ด์
๊ฐ
var end = 0; //๋ํ์ด์ง๋ค์ด์
๊ฐ
//๋ฐ์ดํฐ ajax์์ฒญ
function getList() {
//searchName๊ฐ
var searchName = document.querySelector("input[name=searchName]").value;
var url = "/getList?page=" + page + "&amount=" + amount + "&searchName=" + searchName;
fetch(url)
.then( function(response) {
return response.json()
})
.then( function(data) {
createBody(data.pageData) //๋ฐ๋ ์์ฑํจ์ ํธ์ถ
createPage(data); //ํ์ด์ง๋ค์ด์
์์ฑํจ์
})
} //end
//๋ด์ฉ ์์ฑ ํจ์
function createBody(list) { //item์ list์ผ ๊ฒ์
var tableBody = document.getElementById("tableBody");
var str = "";
list.forEach( function(data) {
str += "<tr>";
str += "<td>" + data.mno + "</td>";
str += "<td>" + data.writer + "</td>";
str += "<td>" + data.text + "</td>";
str += "<td>" + data.id + "</td>";
str += "<td>" + data.name + "</td>";
str += "<td>" + data.signDate + "</td>";
str += "</tr>";
})
tableBody.innerHTML = str; //tbody๋ฐ์ ์ถ๊ฐ
}
//ํ์ด์ง ๋ค์ด์
์์ฑํจ์
function createPage(item) {
console.log(item);
var pageList = item.pageList; //ํ์ด์ง๋ค์ด์
๊ฐ
var next = item.next; //๋ค์๋ฒํผ
var prev = item.prev; //์ด์ ๋ฒํผ
start = item.start; //ํ์ด์ง๋ค์ด์
์์
end = item.end; //ํ์ด์ง๋ค์ด์
๋
var str = "";
//์ด์ ๋ฒํผ
if(prev) {
str += "<a href='#' class='prev'>์ด์ </a>";
}
//ํ์ด์ง ๋ค์ด์
pageList.forEach( function(data) {
str += "<a href='#' class='number' >" + data + "</a>";
})
//๋ค์๋ฒํผ
if(next) {
str += "<a href='#' class='next'>๋ค์</a>";
}
pagination.innerHTML = str;
} //end
//ํ์ด์ง๋ค์ด์
์ด๋ฒคํธ -> ํ์ด์ง๋ค์ด์
์ ๋ค๋ฆ๊ฒ ๊ทธ๋ ค์ง๋ ํ๊ทธ ( ๋ถ๋ชจ์ ๊ฑธ๊ณ ์์์์ ์์ )
//์ด๋ฒคํธ๋ ๋ถ๋ชจ์ ๊ฒ~
pagination.addEventListener('click', function(e) {
e.preventDefault(); //a์ด๋ฒคํธ ์ค๋จ
if(e.target.className == 'pagination' ) return; //pagenationํด๋ฆญ์ ํจ์ ์ข
๋ฃ
if(e.target.className == 'prev') { //์ด์ ๋ฒํผ
page = start - 1; //์์๋ฒํธ์ 1
} else if(e.target.className == 'next') { //๋ค์๋ฒํผ
page = end + 1; //๋๋ฒํธ์ + 1
} else if(e.target.className == 'number') { //ํ์ด์ง๋ฒํธ
page = e.target.innerHTML; //aํ๊ทธ ์ฌ์ด๊ฐ์ ์ ์ญ๋ณ์ ์ ์ฅ
}
getList(); //๋ฐ์ดํฐ ์กฐํ ๋ฉ์๋ ํธ์ถ
});
//๊ฒ์๊ธฐ๋ฅ - ๊ฒ์์ ํ๋ค๋ ๊ฒ์~? ๋ค์ ํ์ด๋ก ๋์๊ฐ๋ค๋ ์๋ฏธ.
function searchList() {
page = 1;
amount = 10;
getList();
}
//์ฆ์์คํํจ์
(function() {
getList(); //๋ฐ์ดํฐ ์์ฒญ ํธ์ถ
})();
</script>
</body>
</html>
package com.example.jpa.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller
public class JpaController {
@GetMapping("/memoList")
public String main() {
return "memoList";
}
}
package com.example.jpa.entity;
import lombok.*;
import javax.persistence.*;
@Entity //jpa๊ฐ ์ํฐํฐ๋ก ๊ด๋ฆฌํ๋ค๋ ์๋ฏธ
@Table(name = "MEMO") //MEMOํ
์ด๋ธ
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Memo {
//์ํฐํฐ๋ฅผ ์ ์ํ๋ฉด, ํ์ด๋ฒ๋ค์ดํธ๊ฐ DDL๊ตฌ๋ฌธ์ ๋์ ์คํํด์ฃผ๋๋ฐ, spring.jpa.hibernate.ddl-auto=update ์ต์
@Id //pk
@GeneratedValue(strategy = GenerationType.IDENTITY) //auto_increment๋์
// ์ค๋ผํด์ ๋ต
// @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "์ด๋ฆ")
// @SequenceGenerator(name = "์ด๋ฆ", sequenceName = "์ํ์ค๋ช
", initialValue = 1, allocationSize = 1)
private long mno;
@Column(length = 200, nullable = false)
private String writer;
@Column(columnDefinition = "varchar(200) default 'y' ") //๋ง๋ค๊ณ ์ถ์ ์ ์ฝ์ ์ง์ ๋ช
์
private String text;
//N:1
//FK ์ปฌ๋ผ๋ช
์ ๋ช
์ํ์ง ์์ผ๋ฉด Member์ํฐํฐ์ member_์ฃผํค ๋ก ์๋ ์์ฑ๋ฉ๋๋ค.
// @ManyToOne(fetch = FetchType.LAZY) //manyToOne ๊ธฐ๋ณธ๊ฐ์ EAGER๋ฐฉ์
// @JoinColumn(name = "member_id") //Member์ํฐํฐ์ ์ฃผํค๋ฅผ member_id์ปฌ๋ผ์ ์ ์ฅํ๊ฒ ๋ค(FK)
// private Member member; //๋ฉค๋ฒ ์ํฐํฐ
//์๋ฐฉํฅ ๋งตํ
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
}