현재 진행 중인 프로젝트에서 회원 고유번호와 팬미팅 고유 번호 두 개만을 외래 키로 받아서 구성된 테이블이 하나 있는데 해당 테이블을 만들면서 오류 폭탄을 받으면서 배운 정보를 기록해두려고 한다.
구성하려는 테이블
사용한 방법 (@IdClass)
1. 식별자 클래스 생성
신청자 테이블의 복합 키를 담고 있는 식별자 클래스 ApplicantID 를 먼저 생성한다.
식별자 클래스는 생성 시에 조건이 몇 개 있다.
- 식별자 클래스를 생성할 때 Entity클래스와 동일한 변수명으로 생성해야 한다.
- 기본 생성자가 있어야 한다.
- 접근 지정자는 public 이어야 한다.
- Serializable 상속받아야 한다.
- equals, hashCode 를 구현
위 조건에 맞게 아래와 같이 구성하였다.
// ApplicantID.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data // equals, hashCode 생성용 Lombok Annotation
@NoArgsConstructor // 기본 생성자 생성용 Lombok Annotation
@AllArgsConstructor
public class ApplicantID implements Serializable {
private int meetingId;
private int memberId;
}
2. Entity 클래스 생성
식별자 클래스를 생성한 이후에 Entity 클래스를 생성해준다.
변수명은 식별자 클래스와 동일해야 한다.
추가적으로 테이블에 applicant_warn_count 라는 컬럼이 있어서 추가해주었다.
// Applicant.java
import lombok.*;
import javax.persistence.*;
@Entity
@Getter
@Setter
@IdClass(ApplicantID.class)
@Table(name = "applicant")
public class Applicant {
@Id
@Column(name = "meeting_id")
private int meetingId;
@Id
@Column(name = "member_id")
private int memberId;
@Column(name = "applicant_warn_count")
private int applicantWarnCount;
}
3. Repository 생성
보통 JPA Repository는 JpaRepository<Entity명, 기본키 타입> 을 상속받아서 사용한다.
하지만 우리는 복합 키를 사용하므로 아래와 같이 구성하면 된다.
// ApplicantRepository.java
import com.ssafy.yourstar.domain.meeting.db.entity.Applicant;
import com.ssafy.yourstar.domain.meeting.db.entity.ApplicantID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ApplicantRepository extends JpaRepository<Applicant, ApplicantID> {
}
컬럼 생성 및 삭제 테스트 (Spring JAVA)
1. Request와 Response 클래스 파일 생성
// MeetingApplyByUserPostReq.java
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@ApiModel(value = "MeetingApplyByUserPostReq", description = "팬이 팬미팅 신청시 필요한 정보")
public class MeetingApplyByUserPostReq {
@ApiModelProperty(value = "팬미팅 구분 번호", required = true, example = "3")
int meetingId;
@ApiModelProperty(value = "회원 구분 번호", required = true, example = "23")
int memberId;
}
// BaseResponseBody.java
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@ApiModel("BaseResponseBody")
public class BaseResponseBody {
@ApiModelProperty(name="응답 메시지", example = "정상")
String message = null;
@ApiModelProperty(name="응답 코드", example = "200")
Integer statusCode = null;
public BaseResponseBody() {}
public BaseResponseBody(Integer statusCode){
this.statusCode = statusCode;
}
public BaseResponseBody(Integer statusCode, String message){
this.statusCode = statusCode;
this.message = message;
}
public static BaseResponseBody of(Integer statusCode, String message) {
BaseResponseBody body = new BaseResponseBody();
body.message = message;
body.statusCode = statusCode;
return body;
}
}
2. Service 구성
생성과 삭제에 사용할 메서드를 구성 및 구현
// MeetingService.java
import com.ssafy.yourstar.domain.meeting.request.MeetingApplyByUserPostReq;
public interface MeetingService {
void meetingApplyByUser(MeetingApplyByUserPostReq meetingApplyByUserPostReq); // 생성용
boolean meetingRemoveByUser(int memberId, int meetingId); // 삭제용
}
// MeetingServiceImpl.java
import com.ssafy.yourstar.domain.meeting.db.entity.Applicant;
import com.ssafy.yourstar.domain.meeting.db.entity.ApplicantID;
import com.ssafy.yourstar.domain.meeting.db.repository.ApplicantRepository;
import com.ssafy.yourstar.domain.meeting.request.MeetingApplyByUserPostReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MeetingServiceImpl implements MeetingService {
@Autowired
ApplicantRepository applicantRepository;
@Override
public void meetingApplyByUser(MeetingApplyByUserPostReq meetingApplyByUserPostReq) {
Applicant applicant = new Applicant();
applicant.setMeetingId(meetingApplyByUserPostReq.getMeetingId());
applicant.setMemberId(meetingApplyByUserPostReq.getMemberId());
applicant.setApplicantWarnCount(0); // 신청 했을 때 경고 횟수는 0이다.
applicantRepository.save(applicant);
}
@Override
public boolean meetingRemoveByUser(int memberId, int meetingId) {
// 복합키이기 때문에 ID에 내용을 등록 후 사용
ApplicantID applicantID = new ApplicantID();
applicantID.setMemberId(memberId);
applicantID.setMeetingId(meetingId);
// 해당 팬미팅이 존재하는지 조회 후 있을 때 삭제
if (applicantRepository.findById(applicantID).isPresent()) {
applicantRepository.deleteById(applicantID);
return true;
}
return false;
}
}
3. Controller 구성
// MeetingController.java
import com.ssafy.yourstar.domain.meeting.request.MeetingApplyByUserPostReq;
import com.ssafy.yourstar.domain.meeting.service.MeetingService;
import com.ssafy.yourstar.global.model.response.BaseResponseBody;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@Api("팬미팅 관련 API")
@Slf4j
@RestController
@RequestMapping("/api/meetings")
public class MeetingController {
@Autowired
MeetingService meetingService;
@ApiOperation(value = "팬이 팬미팅 신청")
@PostMapping("/fan-applicant")
public ResponseEntity<? extends BaseResponseBody> meetingApplyByUser
(@RequestBody MeetingApplyByUserPostReq meetingApplyByUserPostReq) {
log.info("meetingApplyByUser - Call");
meetingService.meetingApplyByUser(meetingApplyByUserPostReq);
return ResponseEntity.status(201).body(BaseResponseBody.of(201, "Success"));
}
@ApiOperation(value = "팬이 신청한 팬미팅 취소")
@DeleteMapping("/fan-applicant/{memberId}/{meetingId}")
public ResponseEntity<? extends BaseResponseBody> meetingRemoveByUser
(@ApiParam(value = "회원 구분 번호") @PathVariable("memberId") int memberId,
@ApiParam(value = "팬미팅 번호") @PathVariable("meetingId") int meetingId) {
log.info("meetingRemoveByUser - Call");
if (meetingService.meetingRemoveByUser(memberId, meetingId)) {
return ResponseEntity.status(200).body(BaseResponseBody.of(200, "Success"));
} else {
log.error("meetingRemoveByUser - This MeetingId or MemberId doesn't exist");
return ResponseEntity.status(400).body(BaseResponseBody.of(400, "This MeetingId or MemberId doesn't exist"));
}
}
}
4. 확인
Swagger 를 통해 생성 확인
DB에도 문제없이 잘 들어간 것을 확인
삭제는 아래와 같이 Swaager을 이용 Success 메시지를 잘 반환하는 것을 확인하였다.
글보다 코딩한 내용을 보는 게 이해가 더 잘되는 편이라 참고한 블로그 글을 참고해 현재 프로젝트에서 작성한 코드를 가지고 설명했는데 도움이 되었으면 좋겠다.
코딩이 늘 그렇듯 문제를 만나면 막막하지만, 해결하고 나면 재밌다고 느껴진다.
JPA를 처음 사용해보면서 익숙하지 않아서 문제가 발생하면 어떻게 해결해야 할지 막막하다고 많이 느끼는데 계속 사용하면서 익숙해지니까 MyBatis를 쓸 때 보다 훨씬 편하다고 느껴진다.
JPA에 대해 어느 정도 이해하고 잘 다루게 되면 JPA가 무엇인지 기본 사용법은 어떤지도 작성해보려고 한다.
참고한 블로그 : https://jforj.tistory.com/84
'Backend > JPA' 카테고리의 다른 글
[JPA] 특정 날짜 기준으로 조회 및 정렬하기 (0) | 2022.02.21 |
---|