@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


관련글
블로그 이미지

도로락

IT, 프로그래밍, 컴퓨터 활용 정보 등을 위한 블로그

,