1. ์๋ก
์งํ์ค์ธ ํ๋ก์ ํธ์์ ์๋ ์ฌ์ง๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ฒ ๋์๋ค.
๊ธฐ๋ฅ ์ค๋ช ๋ถํฐ ๊ฐ๋จํ ํ๋ฉด
๋ณธ์ธ์ด ์ฌ๋ฆฐ ๊ณต๊ณ ๊ธ์ด ์์ผ๋ฉด ํน์ ๋ ธ๋์์๊ฒ ์ผ์๋ฆฌ๋ฅผ ์ ์ํ ์ ์๋ ๊ธฐ๋ฅ์ด๋ค.
์ด๋ ๋ ธ๋์์ ์ถ์ญ ๊ฐ๋ฅํ ๋ ์ง์ ๊ณต๊ณ ๊ธ์ ์ถ์ญ ๋ ์ง๋ฅผ ๋น๊ตํ์ฌ ์ ์ํ ์ ์๋ค.
๊ธฐ์ ์ ๋ ธ๋์์๊ฒ ์ผ์๋ฆฌ๋ฅผ ์ ์ํ ๋ ์ฌ๋ฌ ๊ฐ์ ๊ณต๊ณ ๊ธ์ ์ ์ํ ์ ์๋ค. ๋์์ ๊ฐ ๊ณต๊ณ ๊ธ์ ์ฌ๋ฌ ๋ ์ ์ ํํ์ฌ ์ ์ํ ์ ์๋ค.
์๋ฅผ ๋ค์ด 1,2,3์ผ์ ์ผํ๋ ๊ณต๊ณ ๊ธ A์ 2,3,4์ผ์ ์ผํ๋ ๊ณต๊ณ ๊ธ B๊ฐ ์๊ณ , C๋ผ๋ ๋ ธ๋์๋ 2์ผ๋ ์ถ์ญ์ด ํ์ ๋ ์ํฉ์ด ์๋ค๊ณ ๊ฐ์ ํ์.
๊ทธ๋ผ ๊ธฐ์ ์ C๋ ธ๋์์๊ฒ ๊ณต๊ณ A์ 1,3์ผ, ๊ณต๊ณ B์ 3,4์ผ์ ์ ์ํ ์ ์๋ ๊ฒ์ด๋ค.
์์ฒญ JSON์ ๋ค์๊ณผ ๊ฐ๋ค.
{
"resumeId": 1,
"offerJobPostRequest": [
{
"jobPostId": 1,
"workDateIdList": [1,2]
},
{
"jobPostId": 1,
"workDateIdList": [3,4]
}
]
์ด์ ๋ด๊ฐ ํด์ผํ ์ผ์ ์์ฒญ์จ pk ๊ฐ๋ค์ด ์ค์ ๋ก ์๋ ๊ฐ์ธ์ง ํ์ธํด์ผ ํ๊ณ ๊ฐ workDateId ๊ฐ๋ค์ด jobPostId์ ์ฐ๊ด๊ด๊ณ๊ฐ ๋งบ์ด์ ธ์๋์ง ํ์ธํด์ผํ๋ค.
๋ฆฌํํ ๋ง ์ ์ฝ๋๋ ์๋์ ๊ฐ์ด ์์ฑํ์๋ค.
public void offerJobPost(Long companyId, OfferRequest request) {
Member company = memberRepository.findById(companyId)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
Resume resume = resumeRepository.findByIdWithMember(request.getResumeId())
.orElseThrow(() -> new CustomException(ErrorCode.RESUME_NOT_FOUND));
Member worker = resume.getMember();
// ์ฝ๋ ์๋ต
for (OfferJobPostRequest offerJobPostRequest : request.getOfferJobPostRequest()) {
// jobPost ์กฐํ
JobPost jobPost = jobPostRepository.findById(offerJobPostRequest.getJobPostId())
.orElseThrow(() -> new CustomException(ErrorCode.JOB_POST_NOT_FOUND));
// ์ฝ๋ ์๋ต
// jobPostId, workDateId๋ก workDate ์กฐํ
List<WorkDate> workDateEntityList = workDateRepository.findByIdList(offerJobPostRequest.getWorkDateIdList(), jobPost.getId())
// ์ฝ๋ ์๋ต
}
// ์ฝ๋ ์๋ต
}
2. ๋ฌธ์ ์
๋ฐ๋ณต๋ฌธ ์์ ์ฟผ๋ฆฌ ํธ์ถ ๋ก์ง์ด ๋ค์ด๊ฐ์์ด N๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ํธ์ถ๋์๋ค. ์์ ๊ฐ์ ๊ฒฝ์ฐ๋ ๋จ์ํ fetch join์ ์ฌ์ฉํ๊ฑฐ๋ default_batch_fetch_size๋ฅผ ์ ์ฉํด ํด๊ฒฐํ ์ ์๋ ๋ฌธ์ ๊ฐ ์๋๋ค.
๊ทธ๋ฅ ๊ฒ์ฆ ๊ณผ์ ์์ ๊ทธ๋ ๊ฒ ํธ์ถ๋ ์ ๋ฐ์ ์๋ ์ํฉ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
3. ํด๊ฒฐ ์๋(1)
์ฐ์ db์์ idList ๊ฐ์ผ๋ก ๋ฏธ๋ฆฌ ์ ๋ถ ์กฐํํ ๋ค์ id ๊ฐ์ key๋ก ๊ฐ๊ณ ๋ณธ์ธ์ value๋ก ๊ฐ๋ Map์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ๋ ค ์๋ํ๋ค. request ์์ JobPostIdList ์ WorkDateIdList๋ฅผ ์ถ์ถํ ๋ค์ Map์ผ๋ก ๋ณํ์์ผ ์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ฐ๋ณต๋ฌธ์์ ๊ฐ์ด ํ์ํ ๋ db์์ ์กฐํํ๋ ๊ฒ์ด ์๋ map์์ ์กฐํํ๋ค.
public void offerJobPost_(Long companyId, OfferRequest request) {
Member company = memberRepository.findById(companyId)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
Resume resume = resumeRepository.findByIdWithMember(request.getResumeId())
.orElseThrow(() -> new CustomException(ErrorCode.RESUME_NOT_FOUND));
Member worker = resume.getMember();
// ์ฝ๋ ์๋ต
List<Long> jobPostIdList = request.getOfferJobPostRequest().stream()
.map(OfferJobPostRequest::getJobPostId)
.collect(Collectors.toList());
List<Long> workDateIdList = request.getOfferJobPostRequest().stream()
.flatMap(o -> o.getWorkDateIdList().stream())
.collect(Collectors.toList());
Map<Long, JobPost> jobPostMap = jobPostRepository.findByIdList(jobPostIdList)
.stream()
.collect(Collectors.toMap(JobPost::getId, Function.identity()));
Map<Long, WorkDate> workDateMap = workDateRepository.findByIdList(workDateIdList)
.stream()
.collect(Collectors.toMap(WorkDate::getId, Function.identity()));
for (OfferJobPostRequest offerJobPostRequest : request.getOfferJobPostRequest()) {
// jobPost ์กฐํ
JobPost jobPost = jobPostMap.get(offerJobPostRequest.getJobPostId());
// ์ฝ๋ ์๋ต
// jobPostId, workDateId๋ก workDate ์กฐํ
List<WorkDate> workDateEntityList = offerJobPostRequest.getWorkDateIdList().stream()
.map(workDateMap::get)
.collect(Collectors.toList());
// ์ฝ๋ ์๋ต
}
// ์ฝ๋ ์๋ต
}
์ด๋ป๊ฒ ๋ณด๋ฉด Redis ๊ฐ์ ์บ์๋ฅผ ์ด์ฉํ๋ ๋๋์ด์๋ค.
์ ์ฒ๋ผ ์ฝ๋๋ฅผ ๋ฐ๊พผ ํ ์ฟผ๋ฆฌ๋ ๋์ด์ N๋ฒ ๋๊ฐ์ง ์์์ง๋ง ํ์ ์ด ์์๋ค. ์กฐํํ workDate ๊ฐ์ด JobPost์ ์ฐ๊ด๊ด๊ณ๊ฐ ๋งบ์ด์ง์ง ์์, ์ฆ JobPost์๋ ์ ํ ์๋ฑํ workDate๊ฐ ์กฐํ๋๋ค ํด๋ ์ด๋ฅผ ๋ฐ๊ฒฌํ ๋ฐฉ๋ฒ์ด ์๋ค.
๊ณ ์ฌ ๋์ JPA 1์ฐจ ์บ์ฑ์ ๋ ์ฌ๋ ธ๋ค.
3. ํด๊ฒฐ ์๋(2)
JPA 1์ฐจ ์บ์ฑ์ ์ด์ฉํด๋ณด๊ธฐ๋ก ํ๋ค.
๊ฒ์ฆ ๋ก์ง ์ด์ ์ id๊ฐ๋ค์ ํด๋นํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ญ์์ฑ ์ปจํ ์คํธ์ ๋ฃ์ ํ ๋ค์ ์กฐํํ๋ฉด ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ์ง ์์ ๊ฒ์ผ๋ก ์์ํ๋ค.
JPA 1์ฐจ ์บ์๋?
1์ฐจ ์บ์๋ ์์์ฑ ์ปจํ ์คํธ(Persistence Context) ๋ด์์ ์ํฐํฐ๋ฅผ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ๋ ์บ์์ด๋ค.
1์ฐจ ์บ์์ ๊ณผ์ ์ ์ธ ๊ฐ์ง ๊ณผ์ ๊ณผ ๊ฐ๋ค.
- ์กฐํ ์ ์ฒ์ 1์ฐจ ์บ์์ ํด๋น ๋ฐ์ดํฐ๊ฐ ์๋์ง ํ์์ ํ๋ค. -> ๋ง์ฝ ์์ผ๋ฉด ๋ฐ๋ก ๋ฆฌํด
- ์กฐํ ๊ฒฐ๊ณผ 1์ฐจ ์บ์์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํด ๊ฐ์ ํ์ํ๋ค.
- ํ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ก ๋ฆฌํดํ๋ ๊ฒ์ด ์๋ ๋ค์ ํ์์์ ์ฌ์ฌ์ฉํ ์ ์๋๋ก 1์ฐจ ์บ์์ ์ ์ฅํ๋ค.
์กฐํํ ์ํฐํฐ๋ค์ 1์ฐจ ์บ์์ ํญ์ ์ ์ฅํ๊ณ ์๋ ๊ฒ์ด ์๋๋ค. EntityManager๋ ํธ๋์ญ์ ๋จ์์ด๊ธฐ ๋๋ฌธ์ ํธ๋์ญ์ ์ด ๋๋๋ฉด 1์ฐจ ์บ์๋ ์ง์๋ฒ๋ฆฐ๋ค. ๊ต์ฅํ ์งง์ ์ฐฐ๋์ ์๊ฐ์๋ง ์ฅ์ ์ด ์๋ค.
public void offerJobPost_(Long companyId, OfferRequest request) {
Member company = memberRepository.findById(companyId)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
Resume resume = resumeRepository.findByIdWithMember(request.getResumeId())
.orElseThrow(() -> new CustomException(ErrorCode.RESUME_NOT_FOUND));
Member worker = resume.getMember();
// ์ฝ๋ ์๋ต
List<Long> jobPostIdList = request.getOfferJobPostRequest().stream()
.map(OfferJobPostRequest::getJobPostId)
.collect(Collectors.toList());
List<Long> workDateIdList = request.getOfferJobPostRequest().stream()
.flatMap(o -> o.getWorkDateIdList().stream())
.collect(Collectors.toList());
// 1์ฐจ ์บ์ฑ ์ฒ๋ฆฌ
jobPostRepository.findByIdList(jobPostIdList);
workDateRepository.findByIdList(workDateIdList);
for (OfferJobPostRequest offerJobPostRequest : request.getOfferJobPostRequest()) {
// jobPost ์กฐํ
JobPost jobPost = jobPostRepository.findById(offerJobPostRequest.getJobPostId())
.orElseThrow(() -> new CustomException(ErrorCode.JOB_POST_NOT_FOUND));
// ์ฝ๋ ์๋ต
// jobPostId, workDateId๋ก workDate ์กฐํ
List<WorkDate> workDateEntityList = workDateRepository.findByIdList(workDateIdList, jobPost.getId());
// ์ฝ๋ ์๋ต
}
// ์ฝ๋ ์๋ต
}
๋์ผ ํธ๋์ญ์ ์์ ์ด์ ์ ์กฐํํ ์ํฐํฐ์ ๋ํด์ DB์์ ์กฐํํ๋ ๊ฒ์ด ์๋ ์์์ฑ ์ปจํ ์คํธ์ ์ ์ฅ๋์ด์๋ ์ํฐํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ for๋ฌธ ์์์ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ์ง ์์ ๊ฒ์ผ๋ก ์์ํ๋ค.
ํ์ง๋ง ๋ด ์๊ฐ๋๋ก ๋์ํ์ง ์์๋ค.
๊ฒฐ๊ณผ๋ jobPost๋ 1์ฐจ ์บ์์์ ๊ฐ์ ธ์ฌ ์ ์์์ง๋ง workDate๋ ๊ทธ๋ฌ์ง ๋ชปํ๊ณ ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ๋ค.
์ด์ ๋ฅผ ์ฐพ์๋ณด๋ 1์ฐจ ์บ์๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ๋ช๊ฐ์ง ์กฐ๊ฑด์ด ์์๋ค.
๊ฒฐ๋ก ๋ถํฐ ๋งํ์๋ฉด
1์ฐจ ์บ์๋ PK๊ฐ ์๋๋ฉด ์๋ํ์ง ์๋๋ค
1์ฐจ ์บ์ ๋์ ์กฐ๊ฑด
- ์บ์์ ์ ์ฅ๋๋๊ฑด ์ปค์คํ ์ฟผ๋ฆฌ(JPQL)์ ์ฌ์ฉํด๋ ๋ฌธ์ ์์ด 1์ฐจ ์บ์์ ์ ์ฅ๋๋ค.
- ๋จ, ์บ์์์ ๊ฐ์ ๊ฐ์ ธ์ค๋๊ฑด PK๋ฅผ ๊ธฐ๋ฐ์ผ๋กํ find (entity manager๋ฅผ ์ธ๋ find() ๋ฉ์๋ ํน์ data-jpa์ findById)์์๋ง ๋์ํ๋ค.
- select c from Comment c where c.id = :paramId ์ ๊ฐ์ PK๊ธฐ๋ฐ JPQL๋ ์ฟผ๋ฆฌ๊ฐ ๋ ์๊ฐ๋ค. (em.createQuery ๊ฐ ํธ์ถ๋จ)
- ํธ๋์ญ์ ์ด ์ข ๋ฃ๋๊ฑฐ๋, flush()๋ ๊ฒฝ์ฐ ์บ์๋ ์ด๊ธฐํ๋๋ค.
- PK ๊ธฐ๋ฐ find ์กฐํ๋ฅผ ํ๋๋ผ๋, ์ฒ์ ๋ถ๋ฌ์ค๋ ์ํฐํฐ๋ฉด ์ฟผ๋ฆฌ๊ฐ ๋ ์๊ฐ๋ค.
2, 3๋ฒ ์กฐ๊ฑด์ด ์ค์ํ๋ค.
JobPost๋ฅผ ์กฐํํ ๋ findById๋ก ์กฐํํ๊ธฐ ๋๋ฌธ์ 1์ฐจ ์บ์์์ ์ํฐํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์์ง๋ง, WorkDate๋ฅผ ์กฐํํ ๋ ์ปค์คํ JPQL์ ์ฌ์ฉํ์ ๋ฟ๋๋ฌ pk๋ง์ ์ฌ์ฉํ ์กฐํ๋ ์๋์๊ธฐ ๋๋ฌธ์ 1์ฐจ ์บ์ ์กฐํ๊ฐ ๋์ง ์์๋ ๊ฒ์ด๋ค.
3. ํด๊ฒฐ ์๋ (3) - ์ต์ข
์ต์ข ์ผ๋ก JPA 1์ฐจ ์บ์ฑ๊ณผ Map ๋ ๋ฐฉ๋ฒ์ ๋ชจ๋ ์ด์ฉํ์ฌ ํด๊ฒฐํ๋ค. ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
public void offerJobPost(Long companyId, OfferRequest request) {
Member company = memberRepository.findById(companyId)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
Resume resume = resumeRepository.findByIdWithMember(request.getResumeId())
.orElseThrow(() -> new CustomException(ErrorCode.RESUME_NOT_FOUND));
Member worker = resume.getMember();
List<Long> jobPostIdList = new ArrayList<>();
List<Long> workDateIdList = new ArrayList<>();
for (OfferJobPostRequest offerJobPostRequest : request.getOfferJobPostRequest()) {
jobPostIdList.add(offerJobPostRequest.getJobPostId());
workDateIdList.addAll(offerJobPostRequest.getWorkDateIdList());
}
// jpa 1์ฐจ ์บ์ฑ์ ์ํ ์กฐํ ๋ฐ ์บ์ Map ์์ฑ
Map<Long, List<WorkDate>> workDateMap = getWorkDateMap(jobPostIdList, workDateIdList);
for (OfferJobPostRequest offerJobPostRequest : request.getOfferJobPostRequest()) {
JobPost jobPost = jobPostRepository.findById(offerJobPostRequest.getJobPostId())
.orElseThrow(() -> new CustomException(ErrorCode.JOB_POST_NOT_FOUND));
// ์ฝ๋ ์๋ต
List<WorkDate> workDateEntityList = workDateMap.get(jobPost.getId());
if (workDateEntityList.size() != offerJobPostRequest.getWorkDateIdList().size()) {
throw new CustomException(ErrorCode.WORK_DATE_NOT_MATCH);
}
}
// ์ฝ๋ ์๋ต
}
private Map<Long, List<WorkDate>> getWorkDateMap(List<Long> jobPostIdList, List<Long> workDateIdList) {
jobPostRepository.findByIdList(jobPostIdList);
List<WorkDate> workDateList = workDateRepository.findByIdList(workDateIdList);
Map<Long, List<WorkDate>> workDateMap = new HashMap<>();
for (WorkDate workDate : workDateList) {
Long jobPostId = workDate.getJobPost().getId();
// Key๊ฐ jobPostId์ธ List ๊ฐ์ ธ์ค๊ฑฐ๋, ์๋ก ์์ฑ
List<WorkDate> innerList = workDateMap.computeIfAbsent(jobPostId, k -> new ArrayList<>());
// innerMap์ workDateId์ workDate๋ฅผ ์ถ๊ฐ
innerList.add(workDate);
}
return workDateMap;
}
์ด์ ์ JobPost์ WorkDate ๊ฐ๊ฐ์ pk๊ฐ์ key๋ก ์ค์ ํ๊ณ value๋ฅผ ๊ฐ ์ํฐํฐ๋ก ์ค์ ํ๋ค๋ฉด ์ด๋ฒ์
JobPost๋ 1์ฐจ ์บ์๋ฅผ ์ด์ฉํ์ฌ ์กฐํํ๊ณ
WorkDate๋ ์ฐ๊ด๊ด๊ณ๊ฐ ๋งบ์ด์ง JobPost์ pk ๊ฐ์ key๋ก ์ค์ ํ๊ณ List<WorkDate>๋ฅผ value๋ก ์ค์ ํด์ฃผ์ด ์บ์ ์ญํ ์ ํ๋ Map์ ๋ง๋ค์ด์ฃผ์๋ค. ์ด๋ ๊ฒ JobPost์๋ ๊ด๊ณ๊ฐ ์๋ WorkDate๊ฐ ์กฐํ๋๋ ๊ฒ์ ๋ง์ ์ ์์๋ค.
4. ๊ฒฐ๋ก
JPA ์์์ฑ ์ปจํ ์คํธ๋ฅผ ๋ฐฐ์ฐ๋ฉฐ ์์์ฑ ์ปจํ ์คํธ์ ํน์ง์ผ๋ก 1์ฐจ ์บ์ฑ, ์ฐ๊ธฐ ์ง์ฐ, ๋ณ๊ฒฝ ๊ฐ์ง.. ๋ฑ ํน์ง๋ค์ ํ์ตํ๊ณ ๊ธฐ์ตํ๊ณ ์์๋ค. ํ์ง๋ง 1์ฐจ ์บ์๋ฅผ ์ด์ฉํด์ผ๊ฒ ๋ค๋ ์๊ฐ์ ํ์ง ๋ชป ํ๊ณ ์ง๊ธ๊น์ง ์ฌ์ฉํด๋ณธ์ ๋ ์์๋ค. ์ ํํ ์ฌ์ฉํ ์ผ์ด ์์๋ ๊ฒ ๊ฐ๋ค.
๋์ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ฐ๋ฐํ ์ํฉ์ด ์๋ค๋ฉด 1์ฐจ ์บ์๋ฅผ ์ ๊ทน ์ด์ฉํ๋๊ฑธ ์ถ์ฒํ๋ค. ๋ ์บ์ ๊ธฐ๋ฅ์ ํ ์ ์๋ Map์ ์์๋ก ๋ง๋ค์ด ์ฌ์ฉํ๋ ๊ฒ๋ ์ข์ ๋ณด์ธ๋ค. Map์ ์๊ฐ๋ณด๋ค ์ธ ์ํฉ์ด ์์ฃผ ์๊ธฐ๋ ๊ฒ ๊ฐ๋ค.
๋จ, Map์ ์๋ชป ์ฌ์ฉํ ์ ๋ฉ๋ชจ๋ฆฌ ๋์์ ์์ธ์ด ๋๋ฏ๋ก ์ ์ฌ์ฉํด์ผ ํ๋ค.
์ฐธ๊ณ
https://velog.io/@im_h_jo/JPA-1%EC%B0%A8-%EC%BA%90%EC%8B%9C-%EC%9E%91%EB%8F%99-%EC%A1%B0%EA%B1%B4