

package com.springboot.shootformoney.common;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter @Setter
@ToString
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(insertable = false)
private LocalDateTime updatedAt;
}
BaseEntity로 모든 엔티티에서 공통적인 부분을 모아놓아 만들었다. lombok을 적용해 @Getter, @Setter, @ToString을 간편하게 어노테이션으로 사용하였고 @MappedSuperclass를 사용하였다. 이는 객체의 입장에서 공통 매핑 정보가 필요할 때 사용하며 매핑정보만 상속받는 Superclass라는 의미의 @MappedSuperclass 어노테이션을 선언한다. @EntityListeners(AuditingEntityListener.class)로 JPA Auditing 기능을 활성화한다. 이 방식으로 트랜잭션 커밋 시점에서 플러시가 호출할 때, 하이버네이트가 자동으로 시간값을 채워준다.
package com.springboot.shootformoney.admin.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import lombok.Data;
@Entity
@Data
public class ConfigEntity {
@Id
@Column(name = "code_", length = 45)
private String code; // PK
@Lob
@Column(name = "value_")
private String value; // json 형태의 데이터
}
@Entity로 이 클래스가 엔티티임을 지정해주고 @Data 어노테이션으로 getter/setter, 생성자, toString() 등을 간편하게 생성한다. @Lob은 대용량 데이터를 처리할 때 사용한다.
package com.springboot.shootformoney.bet.entity;
import com.springboot.shootformoney.game.dto.data.Result;
import com.springboot.shootformoney.game.entity.Game;
import com.springboot.shootformoney.member.entity.Member;
import jakarta.persistence.*;
import jakarta.validation.constraints.Min;
import lombok.*;
import java.time.LocalDateTime;
//유저들의 베팅 목록을 담은 엔터티.
@Entity
@Table(name = "bet")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class Bet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "bt_no")
private Long btNo;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "g_no")
private Game game;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="m_no")
private Member member;
@Column(name = "bt_money", nullable = false)
private Integer btMoney;
//회원의 예상 결과를 담는 칼럼.
@Column(name = "bt_expect", nullable = false)
@Enumerated(EnumType.STRING)
private Result expect;
@Column(name = "bt_time")
private LocalDateTime btTime;
//최종 배당률 칼럼은 일단 모두 만들어 보고 넣을지 말지 생각.
@Column(name = "bt_ratio")
private Double btRatio = 1.0; //기본값 설정(경기 결과 나오면 update)
@Column(name = "bt_end_paid")
private Byte endPaid = 0; //0은 false(아직 정산 후 지급 안 됨.), 1은 true(정산 후 지급됨)를 뜻한다.
//배팅 적중 여부 표시. 1은 적중, 0은 적중 실패, 기본값 0
@Column(name = "bt_correct")
private Byte correct = 0;
}
@Entity로 엔티티임을 지정후, bet라는 이름의 테이블과 매핑해준다. getter/setter, 생성자, toString을 만들어주고 @Id로 기본키를 지정해준다. @GeneratedValue로 기본키 값을 1씩 증가시켜준다. 배팅과 게임, 배팅과 회원은 다대일 관계이므로 @ManyToOne으로 관계를 맺어주고 @JoinColumn으로 name의 이름을 가진 컬럼과 매핑한다. 이로써 배팅과 게임, 멤버와 관계가 맺어졌다.
package com.springboot.shootformoney.bet.entity;
import com.springboot.shootformoney.game.entity.Game;
import jakarta.persistence.*;
import lombok.*;
/*
* 각 경기별 걸린 유로 합을 실시간으로 update하여 저장하는 엔터티.
* Author: Hyedokal(https://www.github.com/Hyedokal)
*/
@Entity
@Table(name = "euroPool")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EuroPool {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ep_no")
private Long epNo;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "g_no")
private Game game;
@Column(name = "win_euro", nullable = false)
private Integer winEuro = 1;
@Column(name = "draw_euro", nullable = false)
private Integer drawEuro = 1;
@Column(name = "lose_euro", nullable = false)
private Integer loseEuro = 1;
}
@Builder를 통해 매개변수를 많이 받는 생성자도 간편하게 생성되도록 하였다.
package com.springboot.shootformoney.board.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.springboot.shootformoney.post.Post;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
public class Board {
@Id
@GeneratedValue
@Column(name = "b_no")
private Long bNo;
@Column(name = "b_name")
private String bName; // 게시판 명(카테고리 명)
@Column(name = "b_pageNo")
@ColumnDefault("10")
private int bPageNo; // 페이지 수(하단)
@Column(name = "b_unitNo")
@ColumnDefault("15")
private int bUnitNo; // 리스트별 게시글 개수
// @OneToMany(mappedBy = "board", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL)
// @JsonIgnore // 클라이언트로부터 게시판 정보만 받고 게시물 정보는 따로 처리하려는 경우에 유용
// @OneToMany(mappedBy = "parent")
private List<Post> posts = new ArrayList<>();
}
보드 엔티티도 다른 엔티티와 마찬가지지만 board와 post와 일대다관계를 맺어주고 cascade설정을 해준다. 이는 보드가 삭제되면 포스트도 삭제되어야하므로 cascade를 설정해주지 않으면 board가 삭제되어도 post는 데이터베이스에 고아객체로 남아있을 수 있기 때문에 설정해주는 것이다.
package com.springboot.shootformoney.comment.entity;
import com.springboot.shootformoney.common.BaseEntity;
import com.springboot.shootformoney.member.entity.Member;
import com.springboot.shootformoney.post.Post;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Comment extends BaseEntity{
@Id
@GeneratedValue
@Column(name = "c_no")
private Long cNo;
@Column(name = "c_content")
private String cContent; // 댓글 본문
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "mNo")
private Member member;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
@JoinColumn(name = "p_no")
private Post post;
}
위 엔티티에서는 @ManyToOne으로 댓글과 회원, 댓글과 포스팅 간에 다대일 연관관계가 맺어졌다. 여기서 fetch = FetchType.LAZY는 지연로딩으로 생략하면 fetch = FetchType.EAGER로 즉시로딩이 된다. 즉시로딩을 사용하지 않는 이유는 즉시로딩은 comment를 조회하는 시점에 연관되어 있는 member와 post도 함께 조회된다. 연관된 member나 post가 많으면 많을 수록 comment를 조회하는 쿼리가 추가로 나가게 되기 때문에 지연로딩을 통해서 실제 member나 post를 사용하는 시점에 쿼리가 나가도록 하는게 좋다.
package com.springboot.shootformoney.game.entity;
import com.springboot.shootformoney.game.dto.data.Result;
import jakarta.persistence.*;
import lombok.*;
/*경기 데이터 엔터티
* Author: Hyedokal(https://www.github.com/Hyedokal)
*/
@Entity
@Table(name = "game")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EqualsAndHashCode
@Getter
@Setter
public class Game {
//PK 설정
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long gNo;
//API에서 제공하는 경기마다의 고유한 ID. (table update를 위해 추가)
@Column(name = "match_id", nullable = false, unique = true)
private Integer matchId;
@Column(name = "g_league", nullable = false)
private String gLeague;
@Column(name="g_home_team", nullable = false)
private String gHomeTeam;
@Column(name="g_away_team", nullable = false)
private String gAwayTeam;
@Column(name="g_start_time", nullable = false)
private String gStartTime;
//end time은 경기마다 다르므로 굳이 저장하지 않을 예정
@Enumerated(EnumType.STRING)
@Column(name = "g_Result")
private Result result = Result.NN; //필드 선언 시 디폴트 값으로 초기화.
/*
* Entity가 저장되거나 업데이트될 때마다 호출되어
* gHomeScore와 gAwayScore를 비교하고 그 결과에 따라 result 값을 설정한다.
*/
@PrePersist
@PreUpdate
public void calcResult() {
if(gHomeScore == null || gAwayScore == null){
this.result = Result.NN;
} else if(gHomeScore > gAwayScore){
this.result = Result.WIN;
} else if(gHomeScore.equals(gAwayScore)){
this.result = Result.DRAW;
} else{
this.result = Result.LOSE;
}
}
@Column(name="g_home_score")
private Byte gHomeScore;
@Column(name="g_away_score")
private Byte gAwayScore;
}
@PrePersist와 @PreUpdate로 해당 엔티티가 언제 생성되고 수정되었는지 알 수 있다.
package com.springboot.shootformoney.member.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.Getter;
import java.io.Serializable;
@Entity @Data
@Getter
public class Euro implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long eNo;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="mNo")
private Member member;
@Column(nullable = false)
private Integer value = 100 * 10000;
}
package com.springboot.shootformoney.member.entity;
import jakarta.persistence.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Entity @Data
public class LoginData implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="l_no")
private Long lNo;
// @OneToOne(fetch=FetchType.LAZY)
// @JoinColumn
// private Member member;
private LocalDateTime loginDate;
private LocalDateTime lastLoginDate;
}
package com.springboot.shootformoney.member.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.springboot.shootformoney.common.BaseEntity;
import com.springboot.shootformoney.member.enum_.Grade;
import com.springboot.shootformoney.member.enum_.Role;
import com.springboot.shootformoney.post.Post;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Entity
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
@NoArgsConstructor @AllArgsConstructor
@Table(indexes={
@Index(name="idx_member_name", columnList = "m_name"),
@Index(name="idx_member_email", columnList = "m_email"),
@Index(name="idx_member_nickName", columnList= "m_nickName")
})
public class Member extends BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mNo;
@Column(name = "m_id",length = 40, nullable = false, unique = true)
private String mId;
@Column(name = "m_password", length = 65, nullable = false)
private String mPassword;
@Column(name = "m_name",length = 40, nullable = false)
private String mName;
@Column(name = "m_phone",length=11, nullable = false)
private String mPhone;
@Column(name = "m_email",length = 100, nullable = false)
private String mEmail;
@Column(name = "m_nickName",length = 40, nullable = false)
private String mNickName;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role = Role.MEMBER;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Grade grade = Grade.CONFERENCE;
@Column(name = "m_level",nullable = false)
private Integer mLevel = 1;
@Column(name = "m_stack")
private Long mStack = 0L;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="l_no", referencedColumnName = "l_No")
private LoginData loginData;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "eNo", referencedColumnName = "eNo")
private Euro euro;
public void createEuro() {
this.euro = new Euro();
euro.setMember(this); // 만약 양방향 매핑시, 해당 코드 필요
}
}
package com.springboot.shootformoney.post;
import com.springboot.shootformoney.board.entity.Board;
import com.springboot.shootformoney.comment.entity.Comment;
import com.springboot.shootformoney.common.BaseEntity;
import com.springboot.shootformoney.member.entity.Member;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Getter
@Setter
public class Post extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "p_no")
private Long pNo; // 게시글 번호
@Column(name = "p_title")
private String pTitle; // 게시즐 제목
@Column(name = "p_content")
private String pContent; // 게시글 내용
private Long view = 0L; // 조회수
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "bNo")
private Board board;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "mNo")
private Member member;
@Builder
public Post(String title, String content) {
this.pTitle = title;
this.pContent= content;
}
@OneToMany(mappedBy = "post", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
@OrderBy("createdAt desc")//댓글 관련(정렬)
private List<Comment> comments;
public Post() {
}
public void update(String title, String content) {
this.pTitle = title;
this.pContent = content;
this.setUpdatedAt(LocalDateTime.now());
}
public void incrementViewCount() {
this.view += 1;
}
public void decreaseViewCount() {
this.view -= 1;
}
public void setBoard(Board board) {
this.board = board;
if (!board.getPosts().contains(this)) {
board.getPosts().add(this);
}
}
public Long getBNo() {
return this.board == null ? null : this.board.getBNo();
}
public String getBName() {
return this.board == null ? null : this.board.getBName();
}
}
이와 같이 이전 포스팅에서 설계했던 관계를 바탕으로 연관관계를 맺어 엔티티를 작성해 보았다. 다음 포스팅에서는 설계한 엔티티들을 가지고 repository를 작성해보자