JPA를 사용하다 보면 @Column, @ManyToOne 같은 매핑 어노테이션을 필드 위에 붙이는 경우도 있고, 게터(getter) 메서드에 붙이는 경우도 있다. 그렇다면 JPA는 엔티티와 데이터베이스 간에 데이터를 주고받을 때, 필드에 직접 접근할지 게터/세터를 사용할지 어떻게 판단할까?
바로 이 접근 방식을 제어하는 게 @Access 어노테이션이다.
@Access는 jakarta.persistence 패키지에 포함되어 있으며,
AccessType이라는 열거형(enum)을 통해 두 가지 접근 방식을 제공한다.
| 접근 방식 | 설명 |
|---|---|
| FIELD | JPA가 필드 자체에 직접 접근 (리플렉션 사용) |
| PROPERTY | JPA가 게터/세터 메서드를 통해 접근 |
즉, JPA가 엔티티의 값을 어떤 방식으로 읽고 쓸지를 결정하는 역할을 한다.
JPA는 @Id(또는 @EmbeddedId)의 위치를 보고 자동으로 기본 접근 방식을 판단한다.
@Id가 필드에 붙어있으면 → AccessType.FIELD@Id가 게터에 붙어있으면 → AccessType.PROPERTY대부분의 경우 이 기본 규칙으로 충분하지만, 특정 상황에서는 명시적으로 @Access를 지정해야 할 때가 있다.
@Entity
@Access(AccessType.FIELD)
public class Member {
@Id
private Long id;
private String name;
public String getName() {
System.out.println("getName() 호출됨");
return name;
}
public void setName(String name) {
System.out.println("setName() 호출됨");
this.name = name;
}
}
FIELD 방식을 사용하면, JPA는 getName()이나 setName()을 호출하지 않는다. 대신 리플렉션으로 name필드에 직접 접근한다. 즉, 게터나 세터에 작성한 로직이 전혀 실행되지 않는다.
다음과 같은 상황에서 유용하다.
@Getter, @Setter를 사용하되 세터 접근을 제한하고 싶을 때@Entity
@Access(AccessType.PROPERTY)
public class User {
private Long id;
private String email;
@Id
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getEmail() {
System.out.println("getEmail() 호출됨");
return email;
}
public void setEmail(String email) {
System.out.println("setEmail() 호출됨");
this.email = email;
}
}
PROPERTY 방식에서는 JPA가 게터와 세터를 통해 값을 읽고 쓴다. 따라서 setEmail()에 유효성 검사나 로깅 같은 부수 로직이 있다면, 엔티티 조회나 저장 시에도 그 로직이 실행된다.
다음과 같은 상황에서 유용하다.
| 상황 | 권장 접근 방식 |
|---|---|
| 세터 없이 불변처럼 설계하고 싶을 때 | FIELD |
| 세터에 검증, 로깅 등 부수 로직이 있을 때 | PROPERTY |
| 롬복(@Getter, @Setter)을 쓰되 세터를 제한하고 싶을 때 | FIELD |
| 단순 CRUD, DTO 매핑 중심 구조 | 기본값(FIELD)으로 충분 |
대부분의 엔티티는 필드에 @Id를 붙이기 때문에 기본값인 FIELD 접근으로 동작한다. 따로 @Access를 명시하지 않아도 되지만, 설계 의도를 명확히 하고 싶다면 명시적으로 지정하는 것도 좋다.
@Access는 JPA가 엔티티 필드에 접근하는 방식을 지정한다.