요구사항
1) 회원가입 시 User는 필수 약관 동의를 동의하지 않으면 가입할 수 없다.
2) 약관의 최초 생성 일자만 남기면 된다. (약관의 수정 일자는 저장하지 않아도 된다.)
3) 필수 약관 생성 또는 수정 시, 약관에 따라 User에게 동의를 구하지 않고 동의로 체크한다 .
4) 이 때, 공지사항은 약관 생성 또는 수정이 반영되기 이전에 관리자가 직접 게시한다.
1) 필수 약관 생성 Entity 생성
[module-common & module-admin] → TermsCondition
@Entity
@NoArgsConstructor
@Table(name = "terms_condition")
public class TermsCondition {
@Id
@Column(name = "terms_condition_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title; // 약관 제목
private String content; // 약관 내용
private LocalDate createdAt; // 최초 생성 일자
}
2) 필수 약관 멤버 동의 여부를 저장하는 Entity 생성
[module-common & module-admin] → MemberTermsAgreement
: Member의 필수 동의 여부 저장 Entity. 무조건 필수 동의라 isAgreed 필드는 필요없을 것 같은데 명시성을 위해 작성했음
@Entity
@NoArgsConstructor
@Table(name = "member_terms_agreement")
public class MemberTermsAgreement {
@Id
@Column(name = "member_terms_agreement_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "terms_condition_id")
private TermsCondition termsCondition;
@Column(name = "is_agreed", nullable = false)
private Boolean isAgreed; // 동의 여부를 나타내는 필드
@Column(name = "agreed_at")
private LocalDate agreedAt; // 동의 일자
@Builder
public MemberTermsAgreement(Member member, TermsCondition termsCondition, Boolean isAgreed, LocalDate agreedAt) {
this.member = member;
this.termsCondition = termsCondition;
this.isAgreed = isAgreed;
this.agreedAt = agreedAt;
}
}
3) 필수 약관을 저장하는 로직 생성
→ 필수 약관 생성 시 무조건 필수 약관을 동의하는 MemberTermsAgreement 가 생성되어야 함
[module-admin] → TermsConditionServiceImpl
아래의 방식은 모든 Member에 대한 업데이트인데 하나씩 insert하는 방식은 성능상 좋지 않다
@Override
@Transactional
public void createTermsCondition(CreateTermsConditionRequest request) {
TermsCondition termsCondition = TermsCondition.builder()
.title(request.getTitle())
.content(request.getContent())
.createdAt(LocalDate.now())
.build();
termsConditionRepository.save(termsCondition);
// 필수 동의 체크를 위한 MemberTermsCondition -> 자동 생성/ User의 동의를 구하지 않음
// 모든 멤버에 대해 동의 사항 생성
updateAllMembersToAgreeToTerms(termsCondition);
}
@Transactional
private void updateAllMembersToAgreeToTerms(TermsCondition termsCondition){
List<Member> members = memberService.findAllMember();
for (Member member : members) {
memberTermsAgreementRepository.save(MemberTermsAgreement.builder()
.member(member)
.termsCondition(termsCondition)
.isAgreed(true)
.agreedAt(LocalDate.now())
.build()
);
};
}
성능 최적화 방법
가정: 3천명
1) Batch 처리
- 장점: JPA의 편리함을 유지하면서도 데이터를 일정 크기(batch size)로 나누어 처리할 수 있습니다. 메모리 사용량을 제한하고 성능을 최적화할 수 있습니다.
- 예시: 50개씩 처리하는 배치 처리 방식을 사용하면, 3천 명의 유저를 60개의 배치로 나누어 처리하게 됩니다. flush() 및 clear()를 통해 메모리 사용을 줄일 수 있습니다.
2) Native Query 사용
- 장점: SQL을 직접 사용하므로, 성능이 뛰어나고 데이터베이스에 대한 추가적인 부하가 적습니다. 특히, 대량의 데이터를 한 번에 처리할 수 있습니다.
- 예시: 모든 유저를 한 번의 쿼리로 업데이트하는 방식입니다. 이 방식은 간단하고, 3천 명의 유저를 한 번의 트랜잭션으로 처리할 수 있습니다.
updateAlMemberToAgreeToTerms 메소드 수정
: Native Query 사용
@Transactional
private void updateAllMembersToAgreeToTerms(TermsCondition termsCondition){
// Native SQL 쿼리 작성
String query = "INSERT INTO member_terms_agreement (member_id, terms_condition_id, is_agreed, agreed_at) " +
"SELECT m.id, :termsConditionId, true, CURRENT_DATE " +
"FROM member m.member_id " +
"ON CONFLICT (member_id, terms_condition_id) " +
"DO UPDATE SET is_agreed = true, agreed_at = CURRENT_DATE";
entityManager.createNativeQuery(query)
.setParameter("termsConditionId", termsCondition.getId())
.executeUpdate();
}
해당 오류 발생
원인: ON CONFLICT에 고유 제약 조건을 추가했지만 Entity를 생성할 때 고유 제약 조건으로 설정하지 않았기 때문
해결: MemberTermsAgreement 테이블에 고유 제약 조건 추가
"trace": "org.hibernate.exception.SQLGrammarException: JDBC exception executing SQL [INSERT INTO member_terms_agreement (member_id, terms_condition_id, is_agreed, agreed_at) SELECT m.member_id, ?, true, CURRENT_DATE FROM member m ON CONFLICT (member_id, terms_condition_id) DO UPDATE SET is_agreed = true, agreed_at = CURRENT_DATE] [ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification]
@Table(name = "member_terms_agreement",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"member_id", "terms_condition_id"})
}
)
[테스트 성공]
id값이 8인 동의약관이 생성됨
모든 멤버에 대한 약관동의가 생성됨
4) 필수 약관 수정 로직 생성
약관 수정
[module-admin] → TermsConditionServiceImpl
@Override
@Transactional
public void updateTermsCondition(UpdateTermsConditionRequest request) {
TermsCondition termsCondition = findTermsConditionById(request.getId());
termsCondition.setTitle(request.getTitle());
termsCondition.setContent(request.getContent());
termsConditionRepository.save(termsCondition);
}
테스트
: id 8의 제목과 내용 수정 완료
5) 회원가입 시 필수 약관을 동의했는지 체크하는 로직 추가
[module-common] → AuthServiceImpl 수정
: 회원가입 시 필수 동의 여부 체크와 동의한 것에 대한 MemberTermsAgreement 생성 로직 추가
{
(기존 코드)
.
.
//회원가입 시 필수 동의 여부 체크
// 필수 약관 ID 목록
List<Long> requiredTermsIds = List.of(1L, 2L, 3L);
// 사용자가 동의한 약관 ID 리스트
List<Long> agreedTermsIds = request.getAgreedTermsIds();
// 필수 약관에 동의했는지 확인
if (!agreedTermsIds.containsAll(requiredTermsIds)) {
throw new IllegalArgumentException("모든 필수 약관에 동의해야 합니다.");
}
.
.
// MemberTermsAgreement 생성 로직
for (Long termsId : agreedTermsIds) {
TermsCondition termsCondition = termsConditionService.findTermsConditionById(termsId);
memberTermsAgreementService.saveMemberTermsAgreement(
MemberTermsAgreement.builder()
.member(member)
.termsCondition(termsCondition)
.isAgreed(true)
.agreedAt(LocalDate.now())
.build());
}
.
.
}
'개발' 카테고리의 다른 글
[스프링부트] FCM (Firebase Cloud Messaging) - 개별 전송 (1) | 2024.10.02 |
---|---|
[스프링부트] 마케팅 수신 동의 멤버 조회, @Cache- (0) | 2024.10.02 |
[스프링부트] JPA Specification 이용한 멤버 조회 구현 (0) | 2024.09.13 |
[스프링부트] LazyInitializationException 지연 로딩 오류 (0) | 2024.09.12 |
[스프링부트] 탈퇴 회원 처리 (Postgres DB 분리, @Scheduled) (0) | 2024.09.10 |