학교 내 커뮤니티에서 스프링 스터디 겸 멘토링이 있어, 관련된 자료를 작성하게 되었다.
Entity의 값은 철저하게 통제된 환경 내에서 변동되어야 무결성을 유지할 수 있기 때문에 돌발적인 속성 변동을 일으킬 수 있는 @Setter는 지양하는 게 바람직하다. 필요하다면 직접 만들어 쓰도록 하자.
**@Entity**
public class User {
**@Id @GeneratedValue(strategy = GenerationType.IDENTITY)**
**@Column(name = "user_id")**
private Long id;
private String name; // 닉네임
private int height; // 키
private int weight; // 몸무게
private int gender; // 성별 (0, 1)
private int age;
@OneToMany(mappedBy = "user", cascade = {CascadeType.REMOVE}, orphanRemoval: false)
private List<Article> articles = new ArrayList<>();
public User(String name, int height, int weight, int gender, int age) {
this.name = name;
this.height = height;
this.weight = weight;
this.gender = gender;
this.age = age;
}
}
정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?
public static User createUser(String name, String keyCode, String profileImage) {
User user = new User();
user.name = name;
user.keyCode = keyCode;
user.profileImage = profileImage;
return user;
}
@Getter
**@AllArgsConstructor(access = AccessLevel.PRIVATE)** // 생성자 PRIVATE로 한정
public class ResponseUserDto {
private String name;
private int height;
private int weight;
private int age;
public static ResponseUserDto **from(User user)** {
return new ResponseUserDto(user.getName(), user.getHeight(), user.getWeight(), user.getAge());
}
public static ResponseUserDto **of(String name, int height, int weight, int age)** {
return new ResponseUserDto(name, height, weight, age);
}
}
User user = User.builder()
.name("John")
.height(180)
.weight(75)
.gender(1)
.age(30)
.build();
@OneToMany(mappedBy = "user", cascade = {CascadeType.REMOVE}, orphanRemoval: false)
private List<Article> articles = new ArrayList<>();
@OneToMany: 현재 Entity와 해당 Entity의 관계를 일대다로 설정
왜 ArrayList가 아니라 List로 굳이 선언한 뒤에 초기화하나요? 그냥 선언하면 안 되나요?
mappedBy: 어떤 필드가 주인 엔티티의 관계를 관리하는지를 나타냄
cascade: 연관관계의 트리거를 관리
부모 Entity의 상태 변화가 자식 Entity에도 전이되는 기능
다시말해 부모 Entity의 상태 변경(추가, 수정, 삭제)이 자식 Entity에 자동으로 적용
주요 CascadeType은 다음과 같다.
CascadeType.PERSIST: 부모 Entity가 영속성 컨텍스트에 진입할 때 자식 Entity도 함께 진입한다. 즉 부모 Entity가 저장/수정되면 자식 Entity도 함께 수정된다. 예를 들어 user가 getArticles()를 통해 게시물 목록을 받아와 특정 게시물을 삭제하거나 새 게시물을 추가할 때, 이 변동사항을 DB에 반영되도록 한다.
CascadeType.REMOVE: 부모 Entity가 데이터베이스에서 소멸하면 연관된 자식 Entity도 함께 소멸한다.
그 외 MERGE, DETACH, REFRESH 등이 있다.
CascadeType.ALL: 위 5개 Cascade를 적용한다. ALL을 남용하면 중요한 순간 의도치 않은 DB 제어로 인해 치명적인 결과를 발생시킬 수 있으므로 사용을 되도록이면 자제해야 한다.
→ 그러므로 Cascade의 구체적인 타입은 명시적으로, 한정적으로 결정해 주는 것이 바람직하다. 예를 들어 부모가 삭제되면 자식이 삭제되는 기능만 활성화되는 것을 원한다면, 예시와 같이 지정할 수 있을 것이다.
@Entity
public class Article {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "article_id")
private Long id;
private String title;
**@ManyToOne(fetch = FetchType.LAZY)**
**@JoinColumn(name = "user_id")**
private User user;
지연 로딩(fetch = FetchType.LAZY) (vs EAGER)
JoinColumn(name = “user_id”): DB 테이블 상에는 주인의 PK가 이 필드에 저장되며, user 테이블의 PK의 별명인 user_id를 자신의 주인으로 인식하게 된다.
@Entity
public class Likes {
@Id
@Column(name = "likes_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@CreatedDate
private LocalDateTime createdAt;
public interface UserRepository extends JpaRepository<User, Long> {
}
public interface FoodRepository extends JpaRepository<Food, Long> {
boolean existsByIdAndUserId(Long id, Long userId); // 유저가 먹은 음식인지 확인
List<Food> findAllByUserIdAndDate(Long userId, LocalDate date); //유저가 특정 날짜에 먹은 음식 반환
List<Food> findAllByUserIdAndDateBetween(Long userId, LocalDate startDate, LocalDate endDate); // 유저가 특정 기간 내에 먹은 음식 반환
}
public interface FollowRepository extends JpaRepository<Follow, Follow.PK> {
@Query(value = "select u from Follow f INNER JOIN User u ON f.toUser = u.id where f.fromUser = :userId") // 팔로잉 목록 조회
List<User> findAllByFromUser(@Param("userId") Long userId);
}