[Project] User Entity(User) 개발
현재 글은 User Repository 개발 글의 뒷 내용을 이어갑니다.
👨💻 User CRUD 개발과정 순서
-
Repository :데이터베이스에 접근하여 데이터를 다루는 기능을 담당하는 Repository를 먼저 구현.
JpaRepository를 추가함으로써 USER CRUD 기능을 쉽게 사용하게 함.email로 user찾기 / username으로 user찾기 / id값에 해당하는 user찾기 메서드 추가.
- Entity :
데이터베이스의 테이블과 매핑되는 객체로, 테이블의 컬럼들과 매핑되는 필드를 가지고있음.
- User
- Reputation
- DTO :
데이터를 전송하기 위한 객체로, Entity와 비슷하지만 HTTP 요청과 응답에서 사용하는 데이터를 가지고 있음.
- Service :
비즈니스 로직을 처리하는 컴포넌트로, Repository를 사용하여 데이터를 조작하고, 비즈니스 로직에 맞게 데이터를 가공한다.
- Controller :
HTTP 요청을 처리하는 컴포넌트로, 클라이언트로부터 요청을 받아 Servcie에게 전달하고, Service가 처리한 결과를 다시 클라이언트에게 반환한다.
- Mapper :
Entity와 DTO 간의 변환을 담당하는 컴포넌트로, Entity를 DTO로 변환하거나, DTO를 Entity로 변환하는 등의 작업을 함
⭐ User Entity - User 코드
Entity 클래스에 들어갈 내용은 데이터베이스 테이블의 구조와 일치해야 합니다.
사용자(User) 테이블이 있다면 해당 테이블의 구조는 사용자의 아이디(id), 이름(name), 이메일(email), 비밀번호(password) 등으로 구성하게 될것입니다. 따라서 이 구조와 일치하는 Entity 클래스를 만들어보도록 하겠습니다.
@Entity //JPA엔티티임을 알려줌.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "USERS") // 테이블 이름을 user로 설정.
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId; //사용자의 고유 식별자
@Column
private String displayName; //보여지는 이름.
@Column(nullable = false, updatable = false, unique = true) // 프론트분들께서 이메일은 수정 불가로 요청.
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String profileImage = "default"; // 사용자가 이미지를 등록하지 않아도 default값을 보여주려고함.
@ElementCollection(fetch = FetchType.EAGER)
private List<String> roles = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "reputation_id")
private Reputation reputation; // 프론트에서 평판과 관련된 내용 advanced로 하실 예정이니 추가 해달라고함.
/*
추가해야할 항목
이메일 승인여부 / 이메일 인증 여부 /
작성한 엔티티
유저 이름 / 이메일 / 패스워드 / 프로필 이미지 / 사용자의 역할정보 / 사용자 평판
* */
}
-
🔑 @GeneratedValue 어노테이션?
@GeneratedValue 어노테이션은 JPA에서 Entity 클래스의 기본 키를 자동으로 생성하기 위해 사용됩니다. 이 어노테이션은 @Id 어노테이션과 함께 사용되며, strategy 속성을 통해 어떤 방식으로 기본 키를 생성할지 설정할 수 있습니다.
그러면 strategy 속성에는 어떤 값이 있을까? ↓ 대표적인 값 3가지
GenerationType.IDENTITY: 데이터베이스에서 기본 키 생성을 처리합니다. 예를 들어 MySQL에서는 AUTO_INCREMENT를 사용하여 기본 키를 생성합니다.GenerationType.SEQUENCE: 데이터베이스 시퀀스를 사용하여 기본 키를 생성합니다. 이 방식은 Oracle과 같은 몇몇 데이터베이스에서 사용할 수 있습니다.GenerationType.TABLE: 데이터베이스의 특별한 테이블에서 기본 키 값을 가져옵니다. 이 방식은 모든 데이터베이스에서 사용할 수 있습니다.
나는 MySQL을 사용할것이기에 IDENTITY 속성을 사용할 것이다.
GenerationType.IDENTITY는 매우 일반적인 전략으로, 대부분의 데이터베이스에서 지원됩니다. 다른 전략은 데이터베이스의 종류나 설정에 따라 지원되지 않을 수도 있습니다. 기본 키를 생성하는 방식은 JPA 구현체에서 지원하는 방식과 데이터베이스 종류 및 설정에 따라 달라질 수 있습니다.
예를 들어, 다음과 같이 Entity 클래스를 작성하면 GenerationType.IDENTITY를 사용하여 MySQL에서 자동으로 기본 키를 생성할 수 있습니다.
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
위 코드에서 @GeneratedValue(strategy = GenerationType.IDENTITY) 어노테이션을 사용하여 데이터베이스에서 자동으로 기본 키를 생성하도록 설정합니다. 이렇게 하면 데이터베이스가 자동으로 기본 키 값을 할당하므로 개발자가 직접 기본 키를 생성하거나 설정할 필요가 없습니다.
🔖 User - Entity 역할추가.
@ElementCollection(fetch = FetchType.EAGER)
private List<String> roles = new ArrayList<>();
🔑 @ElementCollection 이란?
JPA에서는 엔티티 클래스와 관련된 테이블을 자동으로 생성해주는데, 이때 필드와 컬럼을 매핑해주는 어노테이션을 사용합니다.
@ElementCollection 어노테이션은 엔티티에서 컬렉션을 매핑하는 데 사용됩니다. 예를 들어, 아래와 같이 User 엔티티 클래스에 List<String> 타입의 roles 필드가 있다고 가정해보겠습니다.
@Entity
public class User {
@Id
private Long id;
private String name;
@ElementCollection
private List<String> roles;
// ...
}
이 경우 roles 필드는 @ElementCollection 어노테이션을 사용하여 엔티티에서 컬렉션으로 매핑됩니다.
JPA는 User 테이블 외에도 User_roles와 같은 이름의 테이블을 자동으로 생성하여 User 엔티티와 roles 컬렉션을 매핑해줍니다. 이때 User_roles 테이블은 id와 value 두 개의 컬럼을 가지며, id 컬럼은 User 엔티티의 기본 키인 id와 매핑되고, value 컬럼은 roles 컬렉션의 각 원소와 매핑됩니다.
따라서, @ElementCollection 어노테이션은 엔티티에서 컬렉션을 매핑하여, 엔티티 클래스와 컬렉션을 매핑하는 중간 테이블을 자동으로 생성해줍니다. 이를 통해 객체지향 프로그래밍에서 사용하는 컬렉션을 데이터베이스에 저장할 수 있습니다.
결과적으로는 @ElementCollection 어노테이션을 사용하여 컬렉션을 매핑할 때, 해당 컬렉션의 원소들은 별도의 테이블에 저장됩니다. 이때 @ElementCollection 어노테이션을 사용한 필드가 속한 엔티티 클래스의 @Id 어노테이션이 적용된 필드와 같은 기본 키를 공유합니다. 따라서, 컬렉션의 원소들이 저장될 때 엔티티 클래스와 컬렉션의 원소들이 매핑되는 테이블에 @Id 어노테이션으로 지정한 필드값과 함께 저장되어, 엔티티 객체와 컬렉션 객체를 서로 연결할 수 있게 됩니다.
직관적인 예시 위 같은 코드가 있다면,
+-------------+
| user_roles |
+-------------+
| user_id (PK) |
| value |
+-------------+
User 엔티티와 roles 컬렉션을 매핑하는 중간 테이블인 user_roles 테이블은 다음과 같이 생성됩니다.
여기서 user_id 컬럼은 User 엔티티의 기본 키인 id와 매핑되며, value 컬럼은 roles 컬렉션의 각 원소와 매핑됩니다. 따라서, User 엔티티 객체의 id 필드와 roles 컬렉션 객체의 각 원소가 매핑되는 user_roles 테이블에는 다음과 같은 데이터가 저장됩니다.
+---------+-------+
| user_id | value |
+---------+-------+
| 1 | USER |
| 1 | ADMIN |
+---------+-------+
위의 예시에서 User 엔티티의 id 필드값이 1이라고 가정합니다. 이때 roles 컬렉션에는 “USER”와 “ADMIN”이라는 두 개의 원소가 포함되어 있습니다. @ElementCollection 어노테이션을 사용하여 roles 필드를 매핑하면, 이 컬렉션의 각 원소는 user_roles 테이블에 저장됩니다. 저장될 때 User 엔티티의 id 필드값과 함께 저장되어, User 엔티티 객체와 roles 컬렉션 객체를 서로 연결할 수 있게 됩니다.
그러면 여기에서 fetch는 뭘까?
@ElementCollection(fetch = FetchType.EAGER)
fetch 속성은 @ElementCollection 어노테이션과 함께 사용하여 컬렉션을 로드할 때 어떻게 로드할지를 설정합니다. fetch 속성은 다음과 같은 옵션을 가질 수 있습니다.
FetchType.EAGER: 즉시 로드합니다.User엔티티를 조회할 때roles컬렉션을 함께 조회합니다.FetchType.LAZY: 지연 로드합니다.User엔티티를 조회할 때roles컬렉션은 로드하지 않고, 실제로 사용될 때 로드됩니다.
따라서 @ElementCollection(fetch = FetchType.EAGER)는 User 엔티티를 조회할 때 roles 컬렉션도 함께 조회하도록 설정하는 것입니다. 이렇게 함으로써, User 엔티티 객체와 함께 roles 컬렉션 객체도 로드되므로, roles 컬렉션에 접근할 때 지연 로딩으로 인한 성능 문제를 피할 수 있습니다.
반면에 @ElementCollection(fetch = FetchType.LAZY)를 사용하면, User 엔티티를 조회할 때 roles 컬렉션은 로드되지 않습니다. 대신, roles 컬렉션에 실제로 접근할 때 로드됩니다. 이 방법은 roles 컬렉션이 큰 경우 성능을 향상시킬 수 있습니다.
사용자 평판과 관련된 Entity
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "reputation_id")
private Reputation reputation;
@OneToOne 어노테이션은 하나의 User 엔티티 객체가 하나의 Reputation 엔티티 객체와 연결됨을 나타냅니다.
cascade = CascadeType.ALL은 User 엔티티 객체가 저장, 수정, 삭제될 때, 연결된 Reputation 엔티티 객체도 같이 저장, 수정, 삭제되도록 설정합니다.
@JoinColumn(name = "reputation_id")은 User 엔티티 테이블과 Reputation 엔티티 테이블 사이의 연결을 위한 컬럼을 설정합니다. 이 컬럼은 reputation_id 라는 이름으로 User 엔티티 테이블에 생성됩니다.
따라서 User 엔티티 객체는 Reputation 엔티티 객체와 1:1로 연결되며, User 엔티티 객체가 저장, 수정, 삭제될 때 연결된 Reputation 엔티티 객체도 같이 저장, 수정, 삭제됩니다.
🔑 Cascade?
JPA에서 엔티티 객체의 상태를 변경할 때, 해당 객체와 연관된 다른 객체들의 상태도 함께 변경해야 하는 경우가 있습니다. 이런 경우에 CascadeType 속성을 사용하여 연관된 객체들의 상태도 함께 변경할 수 있습니다.
CascadeType은 다음과 같은 값들을 가질 수 있습니다.
CascadeType.PERSIST: 새로운 객체를 저장할 때 연관된 객체도 함께 저장합니다.CascadeType.MERGE: 객체를 수정할 때 연관된 객체도 함께 수정합니다.CascadeType.REMOVE: 객체를 삭제할 때 연관된 객체도 함께 삭제합니다.CascadeType.REFRESH: 객체를 새로고침할 때 연관된 객체도 함께 새로고침합니다.CascadeType.ALL: 위 모든 동작을 수행합니다.
CascadeType은 @OneToOne, @OneToMany, @ManyToOne, @ManyToMany 어노테이션과 함께 사용할 수 있으며, 필요에 따라 해당하는 옵션을 선택하여 사용할 수 있습니다.
예를 들어 User 엔티티 클래스에서 CascadeType.ALL을 설정한 경우, User 객체의 상태가 변경될 때 연관된 Reputation 객체의 상태도 함께 변경되며, User 객체가 삭제될 때 연관된 Reputation 객체도 함께 삭제됩니다.
예를들어
@OneToMany와 CascadeType.ALL을 사용해보면,
@Entity
public class User {
// ...
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
// ...
}
@Entity
public class Post {
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
// ...
}
위 예시에서 User 엔티티는 여러 개의 Post 엔티티를 가지며, Post 엔티티는 하나의 User 엔티티에 속합니다. User 엔티티에서 posts 필드는 Post 엔티티와 일대다 관계를 가지며, mappedBy 속성으로 Post 엔티티의 user 필드와 매핑됩니다. cascade 속성은 CascadeType.ALL로 설정되어 있으므로, User 엔티티의 상태가 변경될 때 연관된 Post 엔티티들의 상태도 함께 변경됩니다.
따라서 이 코드를 실행하게 되면,
User user = new User();
Post post1 = new Post();
Post post2 = new Post();
post1.setUser(user);
post2.setUser(user);
user.getPosts().add(post1);
user.getPosts().add(post2);
entityManager.persist(user);
User 엔티티와 연관된 Post 엔티티들도 함께 저장됩니다. 그리고 User 엔티티를 삭제하면 연관된 Post 엔티티들도 함께 삭제됩니다.
-
persist()메서드는 엔티티를 영속화(persistent)하는 메서드입니다. 즉, 새로운 엔티티를 데이터베이스에 저장하거나, 이미 있는 엔티티를 가져와서 영속성 컨텍스트(persistence context)에 추가하는 작업을 수행합니다. -
-
영속성 컨텍스트?
영속성 컨텍스트(persistence context)는 JPA가 엔티티를 관리하는 논리적인 영역입니다. 즉, 엔티티 매니저(entity manager)가 관리하는 엔티티 객체들이 영속성 컨텍스트 내부에서 관리됩니다.
영속성 컨텍스트는 엔티티를 데이터베이스에 저장하거나, 조회, 수정, 삭제 등의 작업을 수행할 때 사용됩니다. 영속성 컨텍스트는 엔티티 매니저를 통해 생성되고, 엔티티 매니저가 소멸될 때 함께 소멸됩니다.
영속성 컨텍스트는 다음과 같은 주요한 특징을 갖습니다.
- 엔티티의 생명주기를 관리합니다.
- 1차 캐시(First-Level Cache)를 제공합니다. 같은 엔티티가 여러 번 조회될 때 캐시에서 가져오기 때문에, 데이터베이스에 매번 접근하지 않아도 됩니다.
- Dirty Checking 기능을 제공합니다. 엔티티가 변경되었는지 여부를 판단하여 변경이 감지되면 자동으로 UPDATE 쿼리를 실행합니다.
- 트랜잭션을 지원합니다. 트랜잭션을 시작하면 영속성 컨텍스트가 시작되고, 트랜잭션을 커밋하거나 롤백하면 영속성 컨텍스트도 함께 종료됩니다.
-
🔑 User Entity에 @NoArgsConstructor @AllArgsConstructor 를 추가하는 이유?
- 기본 생성자: JPA에서 엔티티 객체를 생성할 때는 반드시 기본 생성자가 필요합니다. 따라서 User 클래스에는 기본 생성자가 반드시 있어야 합니다. @NoArgsConstructor 어노테이션을 사용하면, 기본 생성자를 간편하게 생성할 수 있습니다.
- 모든 필드를 인자로 받는 생성자: 객체를 생성할 때, 객체의 모든 필드에 대한 값을 한번에 입력받을 수 있습니다. 예를 들어, 회원가입 시 User 객체를 생성할 때, 아이디, 비밀번호, 이메일 등 모든 필드에 대한 값을 한번에 입력받을 수 있습니다. 이때 @AllArgsConstructor 어노테이션을 사용하면, 모든 필드를 인자로 받는 생성자를 간편하게 생성할 수 있습니다.
따라서, User 클래스에 @NoArgsConstructor와 @AllArgsConstructor 어노테이션을 사용하면, JPA에서 엔티티 객체를 생성할 때 기본 생성자를 사용할 수 있으며, 다양한 방법으로 객체를 생성할 수 있습니다. 예를 들어, 아이디와 비밀번호만 입력받아 객체를 생성할 수도 있고, 모든 필드에 대한 값을 입력받아 객체를 생성할 수도 있습니다.
그러면, 두 어노테이션의 차이가 뭘까?
@NoArgsConstructor와 @AllArgsConstructor의 차이는 생성자의 매개변수 유무에 있습니다.
@NoArgsConstructor는 매개변수가 없는 기본 생성자를 생성합니다. 이는 JPA에서 엔티티 클래스에서 매우 유용한데, JPA는 클래스가 기본 생성자를 가져야한다는 규칙을 갖고 있기 때문입니다.
@AllArgsConstructor는 모든 필드를 매개변수로 받는 생성자를 생성합니다. 이는 객체를 생성할 때 각 필드에 대한 값을 모두 지정해야하는 경우 유용합니다.
따라서, 이러한 어노테이션을 사용함으로써 개발자는 매우 적은 노력으로 클래스 생성자를 만들 수 있으며, 코드의 가독성과 유지보수성을 높일 수 있습니다.
🌟 Today I Learn
JPA에 관련된 심화학습 [@ElementCollection / Cascade? / @NoArgsConstructor @AllArgsConstructo] 을 할 수 있었고, USER의 ENTITY 클래스를 작성해 볼 수 있었음.
댓글남기기