JPA Tips (Lombok, ...) - redutan/redutan.github.io GitHub Wiki

Basic

  • ํ…Œ์ด๋ธ”๋ช…์€ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. : @Table(name = "COMPANY")
  • EAGER ์ „๋žต์„ ์œ„ํ•œ @NamedEntityGraph๋Š” Entity์— ์ •์˜ํ•ด์„œ ์žฌ์‚ฌ์šฉํ•˜์ž.
  • ์˜คํ”„๋ผ์ธ ๋‚™๊ด€์  ์ž ๊ธˆ์ด ํ•„์š”ํ•˜๋ฉด @Version ์„ ์‚ฌ์šฉํ•œ๋‹ค.
  • enum ์˜ ๊ฒฝ์šฐ String์œผ๋กœ ํ•˜๋˜ ์—ฌ์œ ๊ฐ€ ๋˜๋ฉด @Convert๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚ซ๋‹ค.
  • ๋“ฑ๋ก ์ˆ˜์ • ์ •๋ณด๋Š” ์ƒ์†์„ ์ด์šฉํ•˜๊ธฐ ๋ณด๋‹ค๋Š” ๋“ฑ๋ก & ์ˆ˜์ • ๊ฐ’๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์€ ๊ฒƒ ๊ฐ™๋‹ค.
  • ๋„๋ฉ”์ธ ํ‘œํ˜„๋ ฅ์„ ๊ฐ€์ง€๋Š” ์ •์  ์ƒ์„ฑ์ž ๋ฉ”์†Œ๋“œ๊ฐ€ ์ข‹์€ ๊ฒƒ ๊ฐ™๋‹ค. ์ƒ์„ฑ์ž๊ฐ€ ํ•œ ๊ฐœ ์ด์ƒ์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

Lombok

  • @Data ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • ๋ฌผ๋ก  ํด๋ž˜์Šค ๋ ˆ๋ฒจ๋กœ @Setter๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • @Getter ๋งŒ ์„ ์–ธํ•˜๊ณ  ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ํ•ด๋‹นํ•˜๋Š” ๋ณ€๊ฒฝ๋ฉ”์†Œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
  • @EqualsAndHashCode ์—ฐ๊ด€๊ด€๊ณ„ ํ•„๋“œ๋Š” ๋ฌด์กฐ๊ฑด exclude์— ํฌํ•จ
    • @EqualsAndHashCode(exclude = {"partner", "ads"})
  • @ToString ์—ฐ๊ด€๊ด€๊ณ„ ํ•„๋“œ๋Š” ๋ฌด์กฐ๊ฑด exclude์— ํฌํ•จ
    • @ToString(exclude = {"partner", "ads"})
  • ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋Š” ์กด์žฌ(JPA ์ŠคํŒฉ)ํ•˜๋˜ ๊ฐ€๋Šฅํ•œ ๋…ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค
    • @NoArgsConstructor(access = AccessLevel.PROTECTED)

Sample source

/**
 * ์—…์ฒด 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;
    }
}