@Id 애노테이션
@Id 애노테이션은 JPA 엔티티 객체의 식별자로 사용할 필드에 적용하며, 유니크한 DB의 컬럼과 맵핑하는것이 보통입니다.
@Id 애노테이션 적용 위치
@Id 애노테이션은 다음과 같이 필드에도 적용할 수 있고 자바빈 규약에 맞게 작성된 getter 메서드에도 적용할 수 있습니다.
필드에 적용하는 경우
1
2
3
4
5
6
7
8 |
@Entity
@Table(name = "user")
public class User {
@Id
private String id;
@Column(name = "user_name")
private String name; |
cs |
getter 메서드에 적용하는 경우
1
2
3
4 |
@Id
public String getId() {
return id;
} |
cs |
적용 위치에 따른 JPA 동작 방식 차이
JPA는 DB로부터 데이터를 읽어오거나, 삽입, 변경, 삭제할 때 엔티티 클래스의 맵핑 정보를 보고 직접 쿼리를 만들어 실행합니다. 우리는 엔티티 클래스를 작성할 때 DB의 컬럼과 엔티티 클래스의 필드를 맵핑하기 위해 @Column, @Id, @Basic 등의 다양한 맵핑관련 애노테이션 설정을 작성하고, JPA는 이 정보를 바탕으로 쿼리 생성시에 맵핑정보로 활용합니다.
JPA는 @Id 애노테이션이 필드에 걸려있는지, getter 메서드에 걸려있는지에 따라 쿼리 생성시 필드를 이용할지 getter 메서드를 이용할지 판단하게 됩니다.
필드에 @Id 애노테이션이 걸린 경우
user 테이블과 맵핑된 User 엔티티 클래스입니다. @Id 애노테이션이 필드에 걸려있으므로 JPA는 해당 엔티티에 관련된 쿼리를 작성할때 필드를 이용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
@Entity
@Table(name = "user")
public class User {
@Id
private String id;
@Column(name = "user_name")
private String name;
protected User() {}
public User(String id, String name) {
super();
this.id = id;
this.name = name;
}
} |
cs |
예를 들어 새로운 유저 정보를 INSERT 하는 경우입니다.
1
2 |
User user = new User("toda", "lee");
entityManager.persist(user); |
cs |
JPA는 맵핑 정보를 토대로 insert 쿼리를 작성합니다. @Id가 필드에 걸려 있으므로 테이블의 모든 컬럼에 대한 정보는 다른 필드를 참고합니다. 예를 들어 name 필드에는 @Column 애노테이션이 있고 user_name 컬럼과 맵핑되어 있다는 사실을 알아낼 것입니다.
그리고 쿼리 실행시 values(?, ?) 에 해당하는 값을 엔티티객체에서 빼내어 할당해 줍니다.
1 |
insert into user (id, user_name) values (?, ?) |
cs |
그런데 자세히 보면 엔티티 클래스는 필드에 private 접근제한자가 걸려 캡슐화 되어 있습니다. 그러나 JPA는 리플렉션을 활용하여, getter 메서드가 없더라도 필드에 접근해 값을 가져다가 쿼리 생성시에 할당하여 쿼리를 실행합니다.
1
2
3
4
5 |
@Id
private String id;
@Column(name = "user_name")
private String name; |
cs |
getter 메서드에 @Id 애노테이션이 걸린 경우
@Id 애노테이션이 getter 메서드에 걸린 경우에는 JPA가 맵핑 정보를 판단하는데 있어서 모든 필드에 대한 getter 메서드를 기준으로 참고합니다. 즉 @Id 애노테이션이 getId() 에 걸려 있기 때문에 name 필드에 대한 맵핑을 판단할 때 getName()메서드에 걸린 맵핑 정보들을 활용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 |
@Entity
@Table(name = "user")
public class User {
private String id;
private String name;
protected User() {}
public User(String id, String name) {
super();
this.id = id;
this.name = name;
}
@Id
public String getId() { return id; }
@Column(name = "user_name")
public String getName() { return name; }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
} |
cs |
만약 @Id 애노테이션은 getter 메서드에 걸어두고 @Column 애노테이션은 필드에 걸어두는 경우에는 필드에 걸려진 @Column 애노테이션은 무시됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 |
@Entity
@Table(name = "user")
public class User {
private String id;
@Column(name = "user_name")
private String name;
protected User() {}
public User(String id, String name) {
super();
this.id = id;
this.name = name;
}
@Id
public String getId() { return id; }
public String getName() { return name; }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
} |
cs |
이 경우 user 테이블의 user_name 컬럼명과 name 필드를 맵핑하려는 의도가 있었는데, 필드에 걸려진 설정은 무시되기 때문에 만약 새로운 유저 정보를 insert 하는 경우라면 다음과 같은 잘못된 쿼리가 생성됩니다. 즉 name에 필드에 사용된 맵핑이 무시되어 쿼리에는 user_name이 아닌 name이 그대로 쿼리에 사용된 것입니다.
1 |
insert into user (id, name) values (?, ?) |
cs |
동작방식의 차이점을 가장 쉽게 이해할 수 있는 방법은 다음과 같이 각 메서드에 출력문을 찍어두는 것입니다. @Id가 필드에 걸려있고 나머지 맵핑정보들도 필드에 걸어놓은 경우에는 JPA가 쿼리를 자동으로 생성하는데에 있어 해당 메서드를 이용하지 않고 필드를 다이렉트로 이용합니다.
반대로 아래 소스와 같이 메서드에 걸어둔 경우는 해당 출력문들이 출력되는것을 알 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 |
@Entity
@Table(name = "user")
public class User {
private String id;
private String name;
protected User() {
}
public User(String id, String name) {
super();
this.id = id;
this.name = name;
}
@Id
public String getId() {
System.out.println("getId - " + id);
return id;
}
@Column(name = "user_name")
public String getName() {
System.out.println("getName - " + name);
return name;
}
public void setId(String id) {
System.out.println("setId - " + id);
this.id = id;
}
public void setName(String name) {
System.out.println("setName - " + name);
this.name = name;
}
} |
cs |
맵핑정보를 필드와 메서드에 둘 다 이용하여 설정하는 경우
만약 @Id를 getter 메서드에 설정하고 일부 맵핑정보는 필드에 설정하거나, 반대로 @Id 애노테이션을 필드에 설정하고 일부 맵핑정보만 메서드에 설정하고 싶은 경우가 있을 수 있는데, 이때는 javax.persistence.Access 애노테이션(@Access) 를 사용하면 됩니다.
@Id 설정 방식에 반대되는 필드 또는 메서드에 @Access 애노테이션을 걸고 값으로 원하는 설정 방식을 지정하면 됩니다.
@Id를 메서드에 걸어서 기본 맵핑을 메서드로 참조하게 하고 user_name 컬럼에 대해서만 맵핑시에 name 필드로 참조하게 하는 경우에는 다음과 같이 @Access 애노테이션의 값으로 AccessType.FIELD를 할당하면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 |
@Entity
@Table(name = "user")
public class User {
private String id;
@Column(name = "user_name")
@Access(AccessType.FIELD)
private String name;
...생략...
@Id
public String getId() {
return id;
}
public String getName() {
return name;
}
...생략...
} |
cs |
반대로 기본 설정을 필드로, 특정 대상만 메서드로 참조하는 경우에는 @Id 애노테이션을 필드에 걸어두고 특정 대상의 getter 메서드에 @Access 애노테이션의 값으로 AccessType.PROPERTY를 할당합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 |
@Entity
@Table(name = "user")
public class User {
@Id
private String id;
private String name;
...생략...
public String getId() {
return id;
}
@Column(name = "user_name")
@Access(AccessType.PROPERTY)
public String getName() {
return name;
}
...생략...
} |
cs |
@Access 애노테이션을 클래스에 걸어두는 경우
@Access를 클래스 단위에도 걸어둘 수 있는데, @Access에 할당된 값이 필드인 경우에는 @Id 애노테이션 또한 필드에 걸어주어야 합니다. 만약 클래스에 @Access(AccessType.PROPERTY)를 설정한 경우에 @Id를 getter 메서드에 걸지 않는 경우 예외가 발생합니다.
특정 대상에 대해서반 클래스에 설정된 @Access를 따르지 않는경우 이전과 마찬가지로 원하는 대상의 필드나 메서드에 @Access를 사용합니다.
예를들어 엔티티 클래스에는 @Access(AccessType.FIELD)를 사용한 경우 당연히 @Id 는 필드에 걸어야 합니다. 그런데 name에 대해서는 메서드를 맵핑에 사용하고 싶다면 getName() 메서드에 @Access(AccessType.PROPERTY)를 걸어주면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
@Entity
@Table(name = "user")
@Access(AccessType.FIELD)
public class User {
@Id
private String id;
private String name;
...생략...
public String getId() {
return id;
}
@Column(name = "user_name")
@Access(AccessType.PROPERTY)
public String getName() {
return name;
}
...생략...
} |
cs |
관련글
'JPA - Hibernate' 카테고리의 다른 글
[JPA] xxx.Hobbies is not mapped as an embeddable 오류 발생 원인 및 해결 방법 (0) | 2019.03.12 |
---|---|
[JPA] 식별자 할당 SEQUENCE(시퀀스) 사용 전략 (2) | 2018.07.28 |
[JPA - Hibernate] Dialect(방언)이란? 하이버네이트 Dialect 종류 (0) | 2018.07.22 |
[JPA] 엔티티 식별자 DB에서 할당받기. @GeneratedValue와 GenerationType.IDENTITY 전략. (0) | 2018.07.21 |
[JPA] 엔티티 식별자 직접 할당하여 저장하기 (0) | 2018.07.20 |