<Spring & JPA 웹서비스 만들기 > 구현 (5) - 달력 만들기
by BFine누가 추첨됐는지 결과를 표시하는 달력기능을 추가해보았다..
가. Table 구성
a. 엔티티 관계도
- 일반 달력테이블을 만들고 달력테이블의 PK를 참조하는 달력데이터 테이블을 만들었다.
- JPA로 Calendar를 불러오면 Data 한번에 가져올수 있도록 해보았다.
나. JPA Entity
a. Calendar
- @IdClass를 활용해 year, month, day 를 복합키로 구성했다.
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "Calendar")
@IdClass(CalendarPK.class)
public class Calendar {
@Id
private int year;
@Id
private int month;
@Id
private int day;
@MapsId
@OneToMany(fetch = FetchType.EAGER)
@JoinColumns({
@JoinColumn(name = "year",referencedColumnName = "year"),
@JoinColumn(name = "month",referencedColumnName = "month"),
@JoinColumn(name = "day",referencedColumnName = "day")
})
List<CalendarData> calendarDataList;
}
b. CalendarData
- 결과가 저장되며 Calendar의 PK를 FK로 가지도록 만들었다.
- Postgresql은 시퀀스를 생성할 수 있어서 시퀀스전략으로 PK를 생성했다.
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class)
@IdClass(CalendarDataPK.class)
@SequenceGenerator(
name = "CALENDAR_DATA_GENERATOR",
sequenceName = "CALENDAR_DATA_SEQUENCE",
initialValue = 1,
allocationSize = 1)
@Table(name = "CalendarData")
public class CalendarData {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CALENDAR_DATA_SEQUENCE")
private long seq;
private int year;
private int month;
private int day;
private String name;
private long userId;
private String title;
@ColumnDefault("0")
private int bet;
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
다. 로직
a. JPQL Fetch Join
@Transactional
public List<Calendar> getAll(CalendarDTO calendarDTO){
/* String jpql = "select distinct c, cd from Calendar c left join c.calendarDataList cd"
+ " on c.year = cd.year and c.month = cd.month and c.day = cd.day"
+ " where c.year=:year and c.month=:month"; */ //테스트의 흔적...
String jpql = "select distinct c from Calendar c"
+"left join fetch c.calendarDataList"
+" where c.year=:year and c.month=:month"
+" order by c.day asc";
Query createQuery = em.createQuery(jpql);
createQuery.setParameter("year", calendarDTO.getYear());
createQuery.setParameter("month", calendarDTO.getMonth());
return (List<Calendar>) createQuery.getResultList();
}
- 처음에는 그냥 left Join 으로 하면 되겠지라는 생각을 했다...
=> 단순 left Join으로 할 경우 결과가 Object로 나오기 때문에 ClassCastException이 발생한다..
- 공부했었는데 막상 이렇게 만들때 Fetch Join이 생각이 나지않았다... 이래서 실제로 만들어야 하나보다..
- Fetch Join을 사용하면 모든 참조 객체들을 가져오는데 left Join의 특성상 중복결과가 발생할 수 있으니
distinct 키워드를 추가해야한다.
b. 달력 생성
- 달력을 만드는데 유의사항이 있다.
1. 윤달, 윤년
2. 첫번째 시작 요일 (최초 1일이 시작되는 시점)
- 찾아보니 둘다 Java에서 만들어준 훌륭한 API를 사용하면 된다.
public CalendarDTO addCalendar(int year, int month){
if(year == DEFAULT){
year = LocalDateTime.now().getYear();
month = LocalDateTime.now().getMonth().getValue();
}
if(calendarRepository.getCount(year, month) == 0){
List<Calendar> calendarList = new ArrayList<>();
int lengthOfMonth = YearMonth.of(year, month).lengthOfMonth();
for(int day = 1; day <= lengthOfMonth; day++){
Calendar calendar = Calendar.builder()
.year(year)
.month(month)
.day(day)
.build();
calendarList.add(calendar);
}
calendarRepository.saveAll(calendarList);
}
CalendarDTO calendarDTO = new CalendarDTO();
calendarDTO.setStartDay(LocalDate.of(year, month, 1).getDayOfWeek().getValue());
calendarDTO.setYear(year);
calendarDTO.setMonth(month);
return calendarDTO;
}
라. 트러블 슈팅
a. FK 인식 문제
- ERROR LOG DETAIL: (year, month, day)= (2021, 2, 10) 키가 "calendar" 에 없습니다.
=> Calendar 테이블에 데이터가 있는데도 FK 제약 오류가 발생했었다...
- 원인은 @JoinColumn에 referencedColumnName를 추가하지 않아서 발생했다.
=> 추가없이 사용할 경우 랜덤한 순서로 PK가 생성되어 테이블간 맵핑이 정확하게 되지않았다.
- Hibernate 로그를 확인했더니 순서가 다른걸 확인 할수있었다.
## 추가전
Hibernate: create table calendar (day int4 not null, month int4 not null, year int4 not null,
primary key (day, month, year))
...
Hibernate: alter table if exists calendar_data add constraint FKd5kpvvc1pj0r9rifkqnf89ui9
foreign key (year, month, day) references calendar
## 추가후
Hibernate: create table calendar (day int4 not null, month int4 not null, year int4 not null,
primary key (day, month, year))
...
Hibernate: alter table if exists calendar_data add constraint FKd5kpvvc1pj0r9rifkqnf89ui9
foreign key (day, month, year) references calendar
마. 실행
a. 화면 렌더링
- thymeleaf의 th를 활용해서 받은 데이터를 렌더링 해주었다.
- 모양을 잡기위해 badge는 Id값을 나눈 나머지로 유저별로 동일하게 가져가도록 부여했다.
<li th:each="num :${#numbers.sequence(1,{calendarDTO.startDay})}">-</li>
<th:block th:each="calendar: ${calendarDTO.calendarList}">
<li>[[${calendar.day}]]
<th:block th:each=" data : ${calendar.calendarDataList}">
<th:block th:switch="${data.userId%4}">
<div th:case="0" class="badge-info">[[${data.name}]]</div>
<div th:case="1" class="badge-success">[[${data.name}]]</div>
<div th:case="2" class="badge-danger">[[${data.name}]]</div>
<div th:case="3" class="badge-primary">[[${data.name}]]</div>
</th:bock>
</th:block>
</li>
</th:block>
</li>
b. 테스트
- 결과가 달력에 입력되는지 테스트 해보면...
'공부 > JPA' 카테고리의 다른 글
<JPA> 낙관적락(Optimistic Lock), 비관적락(Pessimistic Lock) (0) | 2022.03.05 |
---|---|
<JPA> 영속성 컨텍스트 (0) | 2021.03.05 |
<Spring & JPA 웹서비스 만들기 > 구현 (4) - 화면 구성 (0) | 2021.02.06 |
<Spring & JPA 웹서비스 만들기 > 구현 (3) - Redis 자동완성 (0) | 2021.01.31 |
<Spring & JPA 웹서비스 만들기 > 구현 (2) - 서비스's (0) | 2021.01.31 |
블로그의 정보
57개월 BackEnd
BFine