📕 JPA의 일대다 매핑 관계
📗 엔티티 설계
다음과 같이 Spiring JPA와 Lombok을 사용한
Member 와 Characters 간 1:N 관계가 있다고 가정을 하자
[Member]
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member extends BaseTimeEntity {
/**
* 회원 목록 테이블
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private long id;
@Column(unique = true)
private String username;
private String password;
@Enumerated(EnumType.STRING)
private Role role;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Character> characters = new ArrayList<>();
}
여기서
private List<Character> characters = new ArrayList<>();
이 부분은 characters 변수를 ArrayList 객체로 초기화하는 부분이다.
초기화 한 객체에 연관 관계 메서드를 통해 character 엔티티들을 이 리스트에 추가할 수 있다.
// user 엔티티에 character 리스트 저장
public Character addCharacter(Character character) {
characters.add(character);
character.setMember(this);
return character;
}
-> character에도 멤버가 저장된다.
[Characters]
@Entity
@Data
@Table(name = "characters")
public class Character extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "characters_id")
private long id;
@NotNull
private String serverName;
@NotNull
@Column(unique = true)
private String characterName;
@NotNull
private int characterLevel; //전투레벨
@NotNull
private String characterClassName; //캐릭터 클래스
@NotNull
private String characterImage; //캐릭터 이미지 url
@NotNull
private double itemLevel; //아이템레벨
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
@JsonBackReference //순환참조 방지
private Member member;
}
📗 Member 엔티티 저장
간단히 회원 정보를 받아 저장한다고 하자
외래키가 Character에 있는 Member는 연관관계의 주인이 아니기 때문에
character 객체 없이 저장을 할 수 있다.
[MemberSignupDto]
public class MemberSignupDto {
String username;
String password;
String apiKey;
}
[Member]
// 회원가입 생성자
public Member(MemberSignupDto signupDto) {
this.apiKey = signupDto.getApiKey();
this.username = signupDto.getUsername();
this.password = signupDto.getPassword();
this.role = Role.USER;
}
[Save]
public Member signup(MemberSignupDto signupDto) {
return memberRepository.save(new Member(signupDto));
}
📗 Character 엔티티 저장
여기서 Member를 외래키로 갖는 Character 객체를 어떻게 만들어야 할까?
- Member 객체 호출
- Character 객체 생성
- Member 클래스에 있는 addCharacter 메소드 사용
[Member 객체 호출]
public Member findMember(String username) {
return memberRepository.findByUsername(username)
.orElseThrow(() -> new IllegalArgumentException(username + "은(는) 없는 회원 입니다."));
}
-> 하나의 Member 객체를 호출한다.
[Character 객체 생성]
//초기 JSONObject로 만드는 생성자
public Character(JSONObject jsonObject) {
characterName = jsonObject.get("CharacterName").toString();
characterLevel = Integer.parseInt(jsonObject.get("CharacterLevel").toString());
characterClassName = jsonObject.get("CharacterClassName").toString();
serverName = jsonObject.get("ServerName").toString();
itemLevel = Double.parseDouble(jsonObject.get("ItemMaxLevel").toString().replace(",", ""));
}
Character character = new Character(jsonObject);
-> 다른 API로 불러온 JSONObject 객체로 Character객체를 생성한다.
[addCharacter 메소드]
member.addCharacter(character);
따로 레포지토리에서 save 호출할 필요없이 JPA의 더티 체킹(Dirty Checking) 으로 Character 객체가 저장된다.
📕 빌더 패턴
그렇다면 똑같은 방식으로 빌더 패턴을 사용하면 어떻게 될까?
📗 기존 방식
[Save]
public Member signup(MemberSignupDto signupDto) {
Member member = Member.builder()
.username(signupDto.getUsername())
.password(passwordEncoder.encode(signupDto.getPassword()))
.apiKey(signupDto.getApiKey())
.role(Role.USER)
.build();
characterList.stream().map(
character -> member.addCharacter(character))
.collect(Collectors.toList());
return memberRepository.save(member);
}
기존 저장하는 메소드에
Dto를 매개변수로 생성자를 만드는 것이 아닌 빌더 패턴을 이용하였다.
이상태에서 addCharacter 메소드를 이용해서 character 엔티티를 저장하면 어떻게 될까?
빌더 패턴에서 변수를 넣지 않으면 자동적으로 null값이 들어가기 때문에,
위에서 만들어진 Member객체에 Characters가 null이라
addCharacter가 정상적으로 작동하지 않는다.
📗 변경된 방식
그렇다면 Member 엔티티의 characters가 null이 아니면 되지 않을까?
public Member signup(MemberSignupDtoV2 signupDto, List<Character> characterList) {
Member member = Member.builder()
.username(signupDto.getUsername())
.password(passwordEncoder.encode(signupDto.getPassword()))
.apiKey(signupDto.getApiKey())
.characters(new ArrayList<>())
.role(Role.USER)
.build();
characterList.stream().map(
character -> member.addCharacter(character))
.collect(Collectors.toList());
return memberRepository.save(member);
}
-> characters를 빌드할 때 ArrayList로 초기화 해준다.
그럼 Member객체의 characters가 빈 ArrayList가 되기 때문에 addCharacter메소드를 사용할 수 있다.
초기화 하나로 3시간넘게 삽질....