하지만 setter는 호출하면 데이터가 변하므로 막 열어두면 Entity가 도대체 왜 변경되는지 추적하기가 힘들다.
따라서 Entity를 변경할 때는 setter 대신 변경만을 위한 비즈니스 메서드를 따로 제공해야 한다.
회원 Entity
@Entity@Getter@SetterpublicclassMember { @Id @GeneratedValue @Column(name ="member_id")privateLong id;privateString name;// 내장 타입을 사용할 때 붙인다.// Embedded, Embeddable 둘 중 하나만 있으면 되지만 명시적으로 둘 다 적는다. @EmbeddedprivateAddress address;// mappedBy로 매핑되는 읽기 전용이라고 명시한다. @OneToMany(mappedBy ="member")privateList<Order> orders =newArrayList<>();}
// 내장 타입 정의@Embeddable@GetterpublicclassAddress { @Column(length =10)privateString city; @Column(length =20)privateString street; @Column(length =5)privateString zipcode;}
Entity 식별자는 id를 사용하고 PK 칼럼은 member_id를 사용했다.
Entity는 Member라는 타입이 있으니까 id라는 이름만으로도 구별이 가능하다.
테이블은 타입이 없어 어떤 데이터의 id인지 구별이 힘들다.
관례상 테이블명 + id를 많이 사용한다.
객체에서도 id 대신 memberId를 써도 된다. 중요한 건 일관성이다.
주문 Entity
객체는 가지고 있는 Entity 둘 다 데이터를 변경해야 한다.
DB는 FK로 한 번만 변경해주면 된다.
따라서 FK가 있는 곳을 연관 관계의 주인으로 정한다.
해당 FK로 관련된 값을 함께 변경한다는 의미다.
Order가 연관 관계의 주인이다.
Order 테이블에 MEMBER_ID라는 FK가 있다.
@Entity@Getter@Setter@Table(name ="orders") // DB에 order by가 예약어로 걸려있어서 테이블명을 따로 지정해준다.publicclassOrder { @Id @GeneratedValue @Column(name ="order_id")privateLong id;// 누가 주문했는지 알기 위한 용도// 회원에게 주문은 여러 개지만, 해당 주문을 갖고있는 회원은 한 명이다. @ManyToOne @JoinColumn(name ="member_id")privateMember member; @OneToMany(mappedBy ="order")privateList<OrderItem> orderItems =newArrayList<>(); @OneToOne(fetch =FetchType.LAZY) @JoinColumn(name ="delivery_id")privateDelivery delivery;privateLocalDateTime orderDate; @Enumerated(EnumType.STRING)privateOrderStatus status;}
주문 상품 Entity
@Entity@Getter@SetterpublicclassOrderItem { @Id @GeneratedValue @Column(name ="order_item_id")privateLong id; @ManyToOne @JoinColumn(name ="item_id")privateItem item; @ManyToOne @JoinColumn(name ="order_id")privateOrder order;// 주문 당시의 가격. 때마다 바뀔 수 있다.privateint orderPrice;privateint count;}
하나의 주문 아이디에 여러 주문 상품이 들어가므로 FK로 order_id와 item_id를 갖는다.
@Entity@Getter@SetterpublicclassCategory { @Id @GeneratedValue @Column(name ="category_id")privateLong id;privateString name;// 실무에서는 중간 테이블에 추가적인 필드를 넣을 수가 없어서 @ManyToMany를 많이 쓰지 않는다. @ManyToMany// 중간 테이블에 매핑해주어야 한다. @JoinTable(name ="category_item",// 중간 테이블 이름 joinColumns = @JoinColumn(name ="category_id"),// 중간 테이블에 FK로 있는 id inverseJoinColumns = @JoinColumn(name ="item_id")) // 반대편(item)에서 가져올 FKprivateList<Item> items =newArrayList<>();// 카테고리는 계층 구조이므로 부모와 자식을 만들어준다. @ManyToOne @JoinColumn(name ="parent_id")privateCategory parent;// parent를 기준으로 매핑된다.// 셀프로 양방향 연관 관계를 맺는 방식이다. @OneToMany(mappedBy ="parent")privateList<Category> child =newArrayList<>();}
주소 Entity
@Embeddable@GetterpublicclassAddress { @Column(length =10)privateString city; @Column(length =20)privateString street; @Column(length =5)privateString zipcode;// setter 대신 생성자publicAddress(String city,String street,String zipcode) {this.city= city;this.street= street;this.zipcode= zipcode; }// 기본 생성자를 꼭 같이 만들어준다.protectedAddress() { }}
값 타입은 기본적으로 immutable하게 즉, 변경이 되지 않아야 한다.
그래서 setter를 사용하지 않고 무조건 새로 생성해서 쓰도록 생성자를 사용한다.
생성자를 만들 때 기본 생성자도 함께 만들어야 한다.
JPA가 생성 시에 리플렉션이나 프록시 같은 기술을 써야하는데 기본 생성자가 없으면 사용할 수가 없다.
JPA 스펙 상 Entity나 임베디드 타입은 기본 생성자를 public이나 protected로 설정해야 한다.