-
Notifications
You must be signed in to change notification settings - Fork 0
JPA Tips (Lombok, ...)
정명주(myeongju.jung) edited this page Oct 8, 2017
·
3 revisions
- 테이블명은 명시적으로 선언하는 것이 좋다. :
@Table(name = "COMPANY")
- EAGER 전략을 위한
@NamedEntityGraph
는 Entity에 정의해서 재사용하자. - 오프라인 낙관적 잠금이 필요하면
@Version
을 사용한다. -
enum
의 경우 String으로 하되 여유가 되면 @Convert를 사용하는 것이 더 낫다. - 등록 수정 정보는 상속을 이용하기 보다는 등록 & 수정 값객체를 이용하는 것이 더 나은 것 같다.
- 도메인 표현력을 가지는 정적 생성자 메소드가 좋은 것 같다. 생성자가 한 개 이상인 경우가 많다.
-
@Data
는 사용하지 않는다.- 물론 클래스 레벨로
@Setter
도 사용하지 않는다. -
@Getter
만 선언하고 변경이 필요한 경우에 해당하는 변경메소드를 제공한다.
- 물론 클래스 레벨로
-
@EqualsAndHashCode
연관관계 필드는 무조건exclude
에 포함@EqualsAndHashCode(exclude = {"partner", "ads"})
-
@ToString
연관관계 필드는 무조건exclude
에 포함@ToString(exclude = {"partner", "ads"})
- 기본 생성자는 존재(JPA 스팩)하되 가능한 노출하지 않는다
@NoArgsConstructor(access = AccessLevel.PROTECTED)
/**
* 업체 Entity
*/
@Entity
@Table(name = "COMPANY") // **테이블명은 명시적으로 선언하는 것이 좋다.**
@SequenceGenerator(name = "COMPANY_SEQ", sequenceName = "COMPANY_SEQ", allocationSize = 1) // sequence
@NamedEntityGraphs({ // **EAGER 전략을 위한 NamedEntityGraph는 Entity에 정의해서 재사용하자.**
@NamedEntityGraph(name = "Company.withPartnerAndAds", attributeNodes = {
@NamedAttributeNode("partner"),
@NamedAttributeNode("ads")
})
})
@Getter
@EqualsAndHashCode(exclude = {"partner", "ads"}) // **@EqualsAndHashCode 연관관계 필드는 무조건 exclude에 포함**
@ToString(exclude = {"partner", "ads"}) // **@ToString 연관관계 필드는 무조건 exclude에 포함**
@NoArgsConstructor(access = AccessLevel.PROTECTED) // **기본 생성자는 존재하되 가능한 노출하지 않는다.**
@Slf4j
public class Company implements Serializable {
// **직렬화 마커 인터페이스를 구현하고 serialVersionUID 꼭 선언한다.**
private static final long serialVersionUID = 2415772833217876197L;
/**
* 업체 번호 (PK)
*/
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COMPANY_SEQ")
@Column(name = "COMPANY_NO")
private Long companyNo;
/**
* 버전 for LOCK **필요하면 Version 속성을 사용한다**
*/
@Version
@Column(name = "version", nullable = false)
private long version;
/**
* 담당 협력사 (fk)
*/
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "PARTNER_NO", nullable = false, foreignKey = @ForeignKey(name = "FK_COMPANY_PARTNER_NO"))
private Partner partner;
/**
* 업체에 등록된 광고들 (fk)
*/
@OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<Ad> ads;
/**
* 카테고리
*/
@Enumerated(EnumType.STRING) // **String으로 하되 여유가 되면 @Convert를 사용하는 것이 더 낫다.**
@Column(name = "CATEGORY", length = 50, nullable = false)
private CompanyCategory category;
/**
* 업체명
*/
@Column(name = "COMPANY_NAME", length = 100, nullable = false)
private String companyName;
/**
* 전화번호
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phone", column = @Column(name = "PHONE", length = 20, nullable = false))
})
private Phone phone;
/**
* 주소
*/
@AttributeOverrides({
@AttributeOverride(name = "zipCode", column = @Column(name = "ZIP_CODE", length = 5)),
@AttributeOverride(name = "address", column = @Column(name = "ADDRESS", length = 200, nullable = false)),
@AttributeOverride(name = "detailAddress", column = @Column(name = "DETAIL_ADDRESS", length = 200))
})
private Address address;
/**
* 영업 시작 시간
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "hour", column = @Column(name = "SALES_START_TIME_HOUR", length = 2, nullable = false)),
@AttributeOverride(name = "minute", column = @Column(name = "SALES_START_TIME_MINUTE", length = 2, nullable = false)),
})
private Time salesStartTime;
/**
* 영업 마감 시간
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "hour", column = @Column(name = "SALES_END_TIME_HOUR", length = 2, nullable = false)),
@AttributeOverride(name = "minute", column = @Column(name = "SALES_END_TIME_MINUTE", length = 2, nullable = false)),
})
private Time salesEndTime;
/**
* 최소주문금액
*/
@Column(name = "MINIMUM_ORDER_AMOUNT", nullable = false)
private long minimumOrderAmount; // **숫자형 NOT NULL 타입이면서 연산이 요구되면 primitive type으로 선언하는 것이 좋다.**
/**
* 계약서 이미지
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "originName", column = @Column(name = "CONTRACT_IMAGE_ORIGIN", nullable = false)),
@AttributeOverride(name = "storageName", column = @Column(name = "CONTRACT_IMAGE_STORAGE", nullable = false))})
/**
* 메뉴1 이미지
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "originName", column = @Column(name = "MENU1_IMAGE_ORIGIN")),
@AttributeOverride(name = "storageName", column = @Column(name = "MENU1_IMAGE_STORAGE"))})
private SaveFile menu1Image;
/**
* 차단여부
*/
@ColumnDefault("'N'") // **hibernate의 @ColumnDefault는 가독성 향상에 도움**
@Column(name = "BLOCK_YN", length = 1, nullable = false, insertable = false)
@Convert(converter = YnAttributeConverter.class)
private boolean block;
/**
* 등록 & 수정 Value
*/
@Embedded
private CreateAndUpdate cau;
/**
* 등록을 위한 정적 생성자 **꼭 필요한 파라미터를 받는 생성자를 제공한다. 도메인 표현력을 위해서 정적 생성자를 추천한다.**
*/
public static Company create(@NonNull Partner partner,
@NonNull CompanyCategory category,
@NonNull String companyName,
@NonNull Phone phone,
@NonNull Address address,
@NonNull Time salesStartTime,
@NonNull Time salesEndTime,
@NonNull Long minimumOrderAmount,
@NonNull SaveFile contractImage,
Business business,
SaveFile menu1Image,
@NonNull Long createMemberNo) {
// ...
result.cau = CreateAndUpdate.create(createMemberNo);
return result;
}
// 등록 시 유효성 체크
@PrePersist
protected void onPrePersist() {
cau.checkPreInsert();
}
// 수정 시 유효성 체크
@PreUpdate
protected void onPreUpdate() {
cau.checkPreUpdate();
}
/**
* 배달업체 수정 **모든 항목에 대한 setter를 제공하지 않고 변경을 하는 도메인 표현력을 가지는 변경 메소드를 제공한다.**
*
* @param update 수정 파라미터
*/
public void update(CompanyUpdate update) {
if (companyNo == null) {
throw new IllegalStateException("'companyNo' must not be null");
}
// Check version for optimistic lock
if (version != update.getVersion()) {
throw new OptimisticLockException("Conflict version");
}
this.version = update.getVersion();
this.partner = update.getPartner();
this.category = update.getCategory();
this.companyName = update.getCompanyName();
// 관리자인 경우에만 전화번호를 수정할 수 있음
if (update.getUser().isAdmin()) {
this.phone = update.getPhone();
}
// ...
if (update.getMenu1Image() != null) {
this.menu1Image = update.getMenu1Image();
}
this.cau.update(update.getUser().getUserNo());
}
/**
* 관리 협력사 변경
*
* @param newPartner 변경할 협력사
*/
public void changePartner(Partner newPartner, Long updateUserNo) {
if (this.partner == newPartner) {
return;
}
this.partner = newPartner;
this.cau.update(updateUserNo);
}
public boolean hasContractImage() {
return contractImage != null && contractImage.isValid();
}
public boolean hasBusinessImage() {
return business != null && business.getImage() != null && business.getImage().isValid();
}
public boolean hasMenu1Image() {
return menu1Image != null && menu1Image.isValid();
}
public boolean hasMenu2Image() {
return menu2Image != null && menu2Image.isValid();
}
public void block(Partner user) {
if (cau == null) {
throw new IllegalStateException("cau must not be null");
}
cau.update(user.getUserNo());
block = true;
}
public boolean hasPerfectBusiness() {
return business != null && business.isPerfect();
}
/**
* 완벽한 사업자 정보를 가지고 있는지 체크
* @throws IllegalStateException 사업자 정보가 완벽하지 않으면 발생
*/
public void checkPerfectBusiness() {
if (!hasPerfectBusiness()) {
throw new IllegalStateException("Company is invalid : " + companyNo);
}
}
/**
* 광고 중인 광고가 존재하는가?
*/
public boolean hasOpenAd() {
for (Ad ad : ads) {
if (ad.getAdDisplayStatus() == AdDisplayStatus.OPEN) {
return true;
}
}
return false;
}
}
JAVA
JPA
- JPA-Create-And-Update
- Optional-Eager
- QueryDsl-Configuration
- QueryDsl-More-Type-safety
- QueryDsl-SubQuery
DDD
Install
Spring
Spring-Boot
- Swagger2-Configuration
- Spring-Restdocs-Configuration
- Spring-Page-Jackson
- JSR310-Guide
- logback-spring.xml
- WebMvcUtils.java
- Spring-Boot-Properties
- Spring-Boot-Hidden-Gems
- Spring-Boot-Config
Spring-Cloud
- Spring-Cloud-Zuul
- Spring-Cloud-Feign
- Spring-Cloud-Hystrix
- Spring-Cloud-Consul
- Spring-Cloud-Ribbon
- Spring-Cloud-Circuit-Breaker
JavaScript
Gradle
Test
Linux
Etc
TODO http://zoltanaltfatter.com/2017/06/09/publishing-domain-events-from-aggregate-roots/