완벽한 JPA 엔티티 생성
는 한동안 Hibernate만, (Implementation Hibernate)와 같은 .equals/ " ", equals/hashCode, ..."
그래서 각 이슈에 대한 일반적인 베스트 프랙티스를 찾아 개인적인 용도로 쓰기로 했습니다.
그러나 나는 누가 그것에 대해 언급하거나 내가 틀린 곳을 말하는 것을 꺼리지 않을 것이다.
엔티티 클래스
시리얼 가능한 구현
이유: 사양에는 필수라고 되어 있습니다만, 일부 JPA 프로바이더는 이것을 실시하지 않습니다. JPA 프로바이더로서 휴지 상태라고 해서 이것이 강제되는 것은 아닙니다만, Serializable이 실장되어 있지 않은 경우 ClassCastException에서는, 깊은 곳에서 장해가 발생할 가능성이 있습니다.
컨스트럭터
엔티티의 모든 필수 필드를 포함하는 생성자를 만듭니다.
이유: 컨스트럭터는 항상 정상 상태에서 생성된 인스턴스를 그대로 두어야 합니다.
이 생성자 외에: 패키지 개인 기본 생성자가 있습니다.
이유: 디폴트 컨스트럭터는 엔티티를 초기화하는 데 필요합니다.프라이빗은 허용되지만 런타임 프록시 생성 및 바이트 코드 인스트루먼테이션 없이 효율적인 데이터 취득을 위해서는 패키지 프라이빗(또는 퍼블릭) 가시성이 필요합니다.
필드/속성
일반적으로 필드 액세스 사용 및 필요할 때 속성 액세스 사용
이유: 둘 중 하나(프로퍼티 액세스와 필드 액세스)에 대한 명확하고 설득력 있는 논거가 없기 때문에 이것은 아마도 가장 논쟁의 여지가 있는 문제일 것입니다.다만, 필드 액세스는, 보다 명확한 코드, 보다 나은 캡슐화, 및 불변의 필드에 대한 세터를 작성할 필요가 없기 때문에, 일반적으로 선호되고 있는 것 같습니다.
불변 필드 설정 생략(액세스 유형 필드에는 필요 없음)
- 일 수 .
이유:이전에 보호 기능이 (하이버네이트) 퍼포먼스에 더 좋다고 들었습니다만, Web에서 확인할 수 있는 것은, 「하이버네이트」라고 하는 것은, 퍼블릭, 프라이빗, 및 보호된 액세스 방법 뿐만이 아니라, 퍼블릭, 프라이빗, 및 보호 필드에 직접 액세스 할 수 있습니다. 선택은 사용자에게 달렸습니다.또, 애플리케이션 설계에 맞추어 선택할 수 있습니다.
Equals/hash Code
- 엔티티를 유지할 때만 이 ID가 설정된 경우 생성된 ID를 사용하지 마십시오.
- 기본 설정: 불변의 값을 사용하여 고유한 비즈니스 키를 구성하고 이를 사용하여 동등성을 테스트합니다.
- 고유한 비즈니스 키를 사용할 수 없는 경우 엔티티 초기화 시 생성되는 비영구 UUID를 사용합니다.자세한 내용은 이 문서를 참조하십시오.
- 관련 엔티티(ManyToOne)를 참조하지 마십시오.이 엔티티(상위 엔티티와 같은)가 비즈니스 키의 일부여야 하는 경우 ID만 비교하십시오.프록시에서 getId()를 호출해도 속성 액세스 유형을 사용하는 한 엔티티의 로드는 트리거되지 않습니다.
엔티티 예시
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
이 목록에 추가할 다른 제안들은 환영할 만한 것이 아닙니다.
갱신하다
이 기사를 읽은 이후 저는 eq/hC를 구현하는 방법을 수정했습니다.
- 불변의 심플한 비즈니스 키를 사용할 수 있는 경우:
- 기타 모든 경우: UUID 사용
JPA 2.0 사양에는 다음과 같이 기술되어 있습니다.
- 엔티티 클래스에는 no-arg 생성자가 있어야 합니다.다른 컨스트럭터도 있을 수 있습니다.no-arg 생성자는 공개 또는 보호되어야 합니다.
- 엔티티 클래스는 최상위 클래스여야 합니다.열거형 또는 인터페이스를 엔티티로 지정할 수 없습니다.
- 엔티티 클래스는 최종일 수 없습니다.엔티티 클래스의 메서드 또는 영구 인스턴스 변수는 최종값이 될 수 없습니다.
- 엔티티 인스턴스가 (리모트인터페이스를 경유하는 등) 분리된 오브젝트로 값에 의해 전달되는 경우 엔티티 클래스는 Serializable 인터페이스를 구현해야 합니다.
- 추상 클래스와 구체적 클래스는 모두 엔티티일 수 있습니다.기업은 기업 클래스뿐만 아니라 비실체 클래스도 확장할 수 있으며, 비실체 클래스도 기업 클래스를 확장할 수 있습니다.
이 사양에는 엔티티의 equals 및 hashCode 메서드의 구현에 관한 요건은 포함되어 있지 않으며, 프라이머리 키클래스와 맵키에 대해서만 기재되어 있습니다.
몇 가지 중요한 점에 대해 설명하겠습니다.이것은 주요 어플리케이션을 포함한 긴 휴지 상태/지속 경험에서 나온 것입니다.
엔티티 클래스: 시리얼 가능 구현
키는 Serializable을 구현해야 합니다.Http Session에 들어가거나 RPC/Java EE에 의해 유선으로 전송되는 것은 Serializable을 구현해야 합니다.그 외: 많지는 않습니다.무엇이 중요한지 시간을 들여라.
생성자: 엔티티의 모든 필수 필드를 사용하여 생성자를 생성하시겠습니까?
응용 프로그램 로직 생성자에는 엔티티를 생성할 때 항상 알 수 있는 몇 개의 중요한 "외부 키" 또는 "유형/종류" 필드만 있어야 합니다.나머지는 셋터 메서드를 호출하여 설정해야 합니다.그것이 목적입니다.
생성자에 필드를 너무 많이 넣지 마십시오.컨스트럭터는 편리해야 하며 오브젝트에 기본적인 온전성을 부여해야 합니다.이름, 유형 및/또는 부모 모두 일반적으로 유용합니다.
OTOH (오늘날) 어플리케이션 규칙에 따라 고객님의 주소 지정이 필요한 경우 설정자에게 맡겨 주십시오.그것은 "약한 규칙"의 한 예이다.다음 주에 Enter Details 화면으로 이동하기 전에 Customer 객체를 생성하시겠습니까?알 수 없는 데이터, 불완전 데이터 또는 "부분적으로 입력된" 데이터의 가능성을 남겨두지 마십시오.
생성자: 또한 개인 기본 생성자를 패키지하시겠습니까?
예, 하지만 패키지 전용이 아닌 '보호'를 사용하십시오.필요한 내부를 볼 수 없을 때 하위 분류는 정말 골칫거리입니다.
필드/속성
휴지 상태 및 인스턴스 외부에서 'property' 필드 액세스를 사용합니다.인스턴스 내에서 필드를 직접 사용합니다.이유: 하이버네이트의 가장 심플하고 기본적인 방법인 표준 리플렉션을 사용할 수 있습니다.
어플리케이션의 「불변」필드에 대해서는, 하이버네이트는 이러한 필드를 로드할 수 있을 필요가 있습니다.이러한 메서드를 '비공개'로 하거나 주석을 달아 응용 프로그램 코드가 원치 않는 액세스를 만들지 않도록 할 수 있습니다.
주의: equals() 함수를 쓸 때는 'other' 인스턴스의 값에 getters를 사용하십시오.그렇지 않으면 프록시 인스턴스에서 초기화되지 않았거나 비어 있는 필드가 표시됩니다.
보호기능이 (하이버네이트) 퍼포먼스에 더 적합합니까?
그럴 것 같지 않다.
동일/해시 코드?
이것은 엔티티가 구원되기 전에 엔티티와 협력하는 것과 관련이 있습니다.이것은 어려운 문제입니다.불변의 값을 해시/비교하고 있습니까?대부분의 비즈니스 애플리케이션에는 아무것도 없습니다.
고객은 주소 변경, 회사명 변경 등을 할 수 있습니다.흔한 일은 아니지만, 그렇게 됩니다.데이터가 올바르게 입력되지 않은 경우에도 수정이 가능해야 합니다.
일반적으로 불변의 상태로 유지되는 것은 육아나 타입/킨드 등입니다.보통 사용자는 레코드를 변경하지 않고 재생성합니다.하지만 이것들은 실체를 고유하게 식별하지 않습니다!
즉, "불변의" 데이터라고 하는 것은 간단명료하게는 것이 아닙니다.Primary Key/ID 필드는 이러한 안정성과 불변성을 보장하기 위해 정확한 목적으로 생성됩니다.
A) UI에서 "변경/바인드 데이터"로 작업하는 경우, A) "불필요하게 변경된 필드"로 작업하는 경우, B) ID로 비교/해시하는 경우, 비교 및 해싱 및 요청 처리 단계를 계획하고 고려해야 합니다.
Equals/HashCode - 고유한 비즈니스 키를 사용할 수 없는 경우 엔티티를 초기화할 때 생성되는 비영구 UUID를 사용합니다.
네, 이것은 필요할 때 좋은 전략입니다.그러나 UUID는 퍼포먼스 면에서 무료가 아니며 클러스터링은 문제를 복잡하게 만듭니다.
Equals/HashCode -- 관련 엔티티를 참조하지 않음
관련 엔티티(상위 엔티티 등)가 비즈니스 키의 일부여야 하는 경우 삽입할 수 없고 갱신할 수 없는 필드를 추가하여 부모 ID(ManytoOne Join Column과 동일한 이름의)를 저장하고 이 ID를 동등성 검사에서 사용하십시오.
좋은 조언처럼 들리네요.
이게 도움이 됐으면 좋겠네요!
2센트의 추가 답변은 다음과 같습니다.
Field 또는 Property 액세스와 관련하여(퍼포먼스 고려사항에서 벗어나) getter와 setter를 통해 합법적으로 액세스되므로 모델 로직에서 동일한 방법으로 설정/취득할 수 있습니다.그 차이는 지속성 런타임 프로바이더(Hibernate, EclipseLink 또는 기타)가 표 B의 일부 열을 참조하는 외부 키를 가진 표 A의 레코드를 유지/설정할 필요가 있을 때 발생합니다.속성 액세스 유형의 경우 지속성 런타임 시스템은 코드화된 내 setter 메서드를 사용하여 표 B 열의 셀에 새로운 값을 할당합니다.필드 액세스 타입의 경우 지속성 런타임시스템은 셀을 테이블B 컬럼으로 직접 설정합니다.이 차이는 단방향 관계에서는 중요하지 않습니다만, 쌍방향 관계에서는 반드시 자신의 코드화된 세터 방식(프로퍼티 액세스 타입)을 사용해야 합니다.단방향 관계에서는 세터 방식이 정합성을 고려하도록 설계되어 있습니다.쌍방향 관계에서는 일관성이 중요한 문제입니다.잘 설계된 세터의 간단한 예에 대해서는 이 링크를 참조해 주세요.
Equals/hashCode 관련:양방향 관계에 참여하는 엔티티에 대해 Eclipse 자동 생성 Equals/hashCode 메서드를 사용할 수 없습니다. 그렇지 않으면 스택 오버플로 예외가 발생하는 순환 참조가 발생합니다.쌍방향 관계(OneToOne 등)와 자동 생성 Equals() 또는 hashCode() 또는 toString()을 시도하면 이 stackoverflow 예외가 발생합니다.
엔티티 인터페이스
public interface Entity<I> extends Serializable {
/**
* @return entity identity
*/
I getId();
/**
* @return HashCode of entity identity
*/
int identityHashCode();
/**
* @param other
* Other entity
* @return true if identities of entities are equal
*/
boolean identityEquals(Entity<?> other);
}
모든 엔티티에 대한 기본 구현으로 Equals/Hashcode 구현을 단순화합니다.
public abstract class AbstractEntity<I> implements Entity<I> {
@Override
public final boolean identityEquals(Entity<?> other) {
if (getId() == null) {
return false;
}
return getId().equals(other.getId());
}
@Override
public final int identityHashCode() {
return new HashCodeBuilder().append(this.getId()).toHashCode();
}
@Override
public final int hashCode() {
return identityHashCode();
}
@Override
public final boolean equals(final Object o) {
if (this == o) {
return true;
}
if ((o == null) || (getClass() != o.getClass())) {
return false;
}
return identityEquals((Entity<?>) o);
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + identity();
// OR
// return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}
회의실 엔티티의 의미:
@Entity
@Table(name = "ROOM")
public class Room extends AbstractEntity<Integer> {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
public Integer getId(){
return id;
}
public Building getBuilding() {
return building;
}
public String getNumber() {
return number;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
JPA 사업체의 모든 경우 사업 분야에 따라 사업체의 평등을 비교하는 것은 의미가 없다고 생각합니다.이러한 JPA 엔티티가 도메인 기반 엔티티(이 코드 예제의 대상)가 아닌 도메인 기반 Value Objects로 간주되는 경우일 수 있습니다.
언급URL : https://stackoverflow.com/questions/6033905/create-the-perfect-jpa-entity
'programing' 카테고리의 다른 글
Android Studio Google JAR 파일에서 GC 오버헤드 제한을 초과했습니다. (0) | 2022.08.01 |
---|---|
Vuex 속성 값을 변환이 아닌 액션 내부에서 설정하는 것이 잘못되었습니까? (0) | 2022.08.01 |
비트 필드를 사용하는 것은 어떤 경우에 가치가 있습니까? (0) | 2022.08.01 |
부호 없는 데이터 유형은 무엇입니까? (0) | 2022.08.01 |
VueJs 라디오 버튼을 통한 심플한 토굴링 (0) | 2022.08.01 |