반응형
    
    
    
  Spring/JPA 데이터베이스 관련 어노테이션 완벽 가이드
목차
1. 트랜잭션 관련 어노테이션
@Transactional
            
                목적: 메서드나 클래스에 트랜잭션 경계를 설정하여 데이터베이스 작업의 원자성을 보장합니다.
            
            
@Transactional(
    isolation = Isolation.READ_COMMITTED,
    propagation = Propagation.REQUIRED,
    rollbackFor = Exception.class,
    noRollbackFor = IllegalArgumentException.class,
    timeout = 30,
    readOnly = false
)
public void updateUser(User user) {
    userRepository.save(user);
}
            
                주요 속성:
                
        isolation: 트랜잭션 격리 수준 (DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE)
                propagation: 트랜잭션 전파 방식 (REQUIRED, REQUIRES_NEW, NESTED, SUPPORTS 등)
                rollbackFor: 롤백을 수행할 예외 클래스 지정
                timeout: 트랜잭션 타임아웃 시간 (초)
                readOnly: 읽기 전용 트랜잭션 여부
            @EnableTransactionManagement
            
                목적: Spring의 트랜잭션 관리 기능을 활성화합니다.
            
            
@Configuration
@EnableTransactionManagement
public class DatabaseConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}
            
                Configuration 클래스에 추가하여 @Transactional 어노테이션이 동작하도록 합니다.
            
        2. JPA 엔티티 관련 어노테이션
기본 엔티티 어노테이션
@Entity
            
                목적: 클래스를 JPA 엔티티로 지정하여 데이터베이스 테이블과 매핑합니다.
            
            
@Entity
@Table(name = "users")
public class User {
    // 필드와 메서드
}
            
                주요 속성:
                
        name: 엔티티 이름 (기본값: 클래스명)
            @Table
            
                목적: 엔티티와 매핑될 테이블의 상세 정보를 지정합니다.
            
            
@Table(
    name = "users",
    schema = "public",
    indexes = {
        @Index(name = "idx_email", columnList = "email"),
        @Index(name = "idx_name_status", columnList = "name, status")
    },
    uniqueConstraints = @UniqueConstraint(columnNames = {"email"})
)
public class User { ... }
            
                주요 속성:
                
        name: 테이블 이름
                schema: 데이터베이스 스키마
                indexes: 인덱스 정의
                uniqueConstraints: 유니크 제약 조건
            @Id
            
                목적: 엔티티의 기본 키(Primary Key)를 지정합니다.
            
            
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
            
                모든 엔티티는 반드시 @Id 어노테이션이 지정된 필드가 하나 이상 있어야 합니다.
            
        @GeneratedValue
            
                목적: 기본 키 값의 생성 전략을 지정합니다.
            
            
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1)
private Long id;
            
                주요 전략:
                
        AUTO: JPA 구현체가 자동으로 선택
                IDENTITY: 데이터베이스의 AUTO_INCREMENT 사용
                SEQUENCE: 데이터베이스 시퀀스 사용
                TABLE: 별도 테이블을 사용한 키 생성
            @Column
            
                목적: 엔티티 필드와 데이터베이스 컬럼의 매핑 정보를 지정합니다.
            
            
@Column(
    name = "email",
    nullable = false,
    unique = true,
    length = 100,
    columnDefinition = "VARCHAR(100) NOT NULL"
)
private String email;
            
                주요 속성:
                
        name: 컬럼 이름
                nullable: NULL 허용 여부
                unique: 유니크 제약 조건
                length: 문자열 길이
                columnDefinition: 컬럼 정의 SQL
            @Temporal
            
                목적: 날짜/시간 타입 필드의 데이터베이스 매핑 방식을 지정합니다.
            
            
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
@Temporal(TemporalType.DATE)
private Date birthDate;
            
                타입:
                
        DATE: 날짜만 (YYYY-MM-DD)
                TIME: 시간만 (HH:MM:SS)
                TIMESTAMP: 날짜와 시간 (YYYY-MM-DD HH:MM:SS)
            @Enumerated
            
                목적: Enum 타입 필드의 데이터베이스 저장 방식을 지정합니다.
            
            
@Enumerated(EnumType.STRING)
private UserStatus status;
@Enumerated(EnumType.ORDINAL)
private Priority priority;
            
                타입:
                
            STRING: Enum 이름을 문자열로 저장 (권장)
                ORDINAL: Enum 순서를 숫자로 저장
            
                ORDINAL 사용 시 Enum 순서가 변경되면 데이터 무결성 문제가 발생할 수 있습니다.
            
        @Lob
            
                목적: 대용량 데이터(Large Object)를 저장하는 필드를 지정합니다.
            
            
@Lob
private String content; // CLOB
@Lob
private byte[] image; // BLOB
            
                문자열 타입은 CLOB, 바이트 배열은 BLOB으로 매핑됩니다.
            
        연관관계 어노테이션
@OneToOne
            
                목적: 일대일 관계를 매핑합니다.
            
            
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "profile_id")
private UserProfile profile;
            
                주요 속성:
                
        fetch: 로딩 전략 (LAZY, EAGER)
                cascade: 영속성 전이 설정
                mappedBy: 연관관계의 주인이 아닌 쪽에서 사용
            @OneToMany
            
                목적: 일대다 관계를 매핑합니다.
            
            
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
            
                주요 속성:
                
        mappedBy: 연관관계의 주인 지정
                orphanRemoval: 고아 객체 제거 여부
            @ManyToOne
            
                목적: 다대일 관계를 매핑합니다.
            
            
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
            
                다대일 관계에서는 '다' 쪽이 연관관계의 주인이 됩니다.
            
        @ManyToMany
            
                목적: 다대다 관계를 매핑합니다.
            
            
@ManyToMany
@JoinTable(
    name = "user_roles",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
            
                다대다 관계는 중간 테이블을 생성하며, 복잡한 비즈니스 로직이 필요한 경우 중간 엔티티를 직접 생성하는 것이 좋습니다.
            
        @JoinColumn
            
                목적: 연관관계 매핑 시 외래 키 컬럼을 지정합니다.
            
            
@JoinColumn(
    name = "user_id",
    nullable = false,
    foreignKey = @ForeignKey(name = "FK_order_user")
)
private User user;
        @JoinTable
            
                목적: 다대다 관계에서 중간 테이블을 지정합니다.
            
            
@JoinTable(
    name = "user_roles",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "role_id")
)
        3. Repository 관련 어노테이션
@Repository
            
                목적: 데이터 접근 계층을 나타내는 스테레오타입 어노테이션입니다.
            
            
@Repository
public class UserRepositoryImpl {
    
    @PersistenceContext
    private EntityManager entityManager;
}
            
                Spring의 예외 변환 기능을 제공하여 데이터베이스 예외를 Spring의 DataAccessException으로 변환합니다.
            
        @Query
            
                목적: Repository 메서드에 JPQL 또는 네이티브 SQL 쿼리를 지정합니다.
            
            
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
@Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
List<User> findUsersCreatedAfter(@Param("date") Date date);
            
                주요 속성:
                
        value: JPQL 또는 SQL 쿼리
                nativeQuery: 네이티브 SQL 사용 여부
            @Modifying
            
                목적: 데이터 변경 쿼리(UPDATE, DELETE)임을 나타냅니다.
            
            
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateUserStatus(@Param("id") Long id, @Param("status") UserStatus status);
            
                @Modifying 쿼리 실행 후에는 영속성 컨텍스트를 수동으로 clear해야 할 수 있습니다.
            
        @Param
            
                목적: 쿼리 파라미터의 이름을 지정합니다.
            
            
@Query("SELECT u FROM User u WHERE u.name = :name AND u.age > :age")
List<User> findByNameAndAge(@Param("name") String name, @Param("age") int age);
        @PersistenceContext
            
                목적: JPA EntityManager를 주입합니다.
            
            
@PersistenceContext
private EntityManager entityManager;
        @PersistenceUnit
            
                목적: JPA EntityManagerFactory를 주입합니다.
            
            
@PersistenceUnit
private EntityManagerFactory entityManagerFactory;
        4. 검증 관련 어노테이션
@NotNull
            
                목적: 필드가 null이 아님을 검증합니다.
            
            
@NotNull(message = "이메일은 필수입니다")
private String email;
        @NotEmpty
            
                목적: 컬렉션이나 문자열이 null이 아니고 비어있지 않음을 검증합니다.
            
            
@NotEmpty(message = "이름은 필수입니다")
private String name;
        @NotBlank
            
                목적: 문자열이 null이 아니고 공백이 아님을 검증합니다.
            
            
@NotBlank(message = "제목은 필수입니다")
private String title;
        @Size
            
                목적: 문자열, 컬렉션, 배열의 크기를 검증합니다.
            
            
@Size(min = 2, max = 50, message = "이름은 2-50자 사이여야 합니다")
private String name;
        @Min / @Max
            
                목적: 숫자 값의 최소/최대 값을 검증합니다.
            
            
@Min(value = 0, message = "나이는 0 이상이어야 합니다")
@Max(value = 150, message = "나이는 150 이하여야 합니다")
private Integer age;
        @Email
            
                목적: 이메일 형식을 검증합니다.
            
            
@Email(message = "올바른 이메일 형식이 아닙니다")
private String email;
        @Pattern
            
                목적: 정규표현식을 이용한 패턴 검증을 수행합니다.
            
            
@Pattern(regexp = "^[0-9]{10,11}$", message = "올바른 전화번호 형식이 아닙니다")
private String phoneNumber;
        5. 캐싱 관련 어노테이션
@Cacheable
            
                목적: 메서드 실행 결과를 캐시에 저장합니다.
            
            
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
    return userRepository.findById(id).orElse(null);
}
            
                주요 속성:
                
        value: 캐시 이름
                key: 캐시 키 (SpEL 표현식)
                condition: 캐시 조건
            @CacheEvict
            
                목적: 캐시에서 데이터를 제거합니다.
            
            
@CacheEvict(value = "users", key = "#user.id")
public User updateUser(User user) {
    return userRepository.save(user);
}
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
    // 모든 사용자 캐시 삭제
}
            
                주요 속성:
                
        allEntries: 모든 캐시 엔트리 삭제 여부
                beforeInvocation: 메서드 실행 전 캐시 삭제 여부
            @CachePut
            
                목적: 메서드를 항상 실행하고 결과를 캐시에 저장합니다.
            
            
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    return userRepository.save(user);
}
            
                @Cacheable과 달리 메서드를 항상 실행하고 캐시를 업데이트합니다.
            
        @EnableCaching
            
                목적: Spring의 캐시 기능을 활성화합니다.
            
            
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "orders");
    }
}
        6. 성능 최적화 어노테이션
@NamedQuery
            
                목적: 엔티티에 명명된 쿼리를 정의하여 재사용합니다.
            
            
@Entity
@NamedQuery(
    name = "User.findByEmailAndStatus",
    query = "SELECT u FROM User u WHERE u.email = :email AND u.status = :status"
)
public class User { ... }
            
                애플리케이션 시작 시 쿼리 검증이 이루어져 런타임 오류를 방지할 수 있습니다.
            
        @NamedEntityGraph
            
                목적: 엔티티 그래프를 정의하여 N+1 문제를 해결합니다.
            
            
@Entity
@NamedEntityGraph(
    name = "User.withOrders",
    attributeNodes = {
        @NamedAttributeNode("orders"),
        @NamedAttributeNode(value = "profile", subgraph = "profile.details")
    },
    subgraphs = @NamedSubgraph(
        name = "profile.details",
        attributeNodes = @NamedAttributeNode("address")
    )
)
public class User { ... }
        @EntityGraph
            
                목적: Repository 메서드에서 엔티티 그래프를 사용합니다.
            
            
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @EntityGraph(value = "User.withOrders", type = EntityGraph.EntityGraphType.FETCH)
    List<User> findByStatus(UserStatus status);
    
    @EntityGraph(attributePaths = {"orders", "profile"})
    Optional<User> findById(Long id);
}
            
                주요 속성:
                
        value: @NamedEntityGraph 이름
                attributePaths: 직접 로딩할 속성 경로
                type: FETCH(즉시 로딩) 또는 LOAD(기본값 유지)
            @BatchSize
            
                목적: 컬렉션이나 프록시를 일괄 로딩할 때의 배치 크기를 지정합니다.
            
            
@OneToMany(mappedBy = "user")
@BatchSize(size = 10)
private List<Order> orders;
            
                N+1 문제를 완화하기 위해 지정된 크기만큼 한 번에 로딩합니다.
            
        @Fetch
            
                목적: Hibernate의 fetch 전략을 세밀하게 제어합니다.
            
            
@OneToMany(mappedBy = "user")
@Fetch(FetchMode.SUBSELECT)
private List<Order> orders;
            
                FetchMode:
                
        SELECT: 기본값, 별도 SELECT 쿼리
                JOIN: JOIN을 사용한 즉시 로딩
                SUBSELECT: 서브쿼리를 사용한 로딩
            7. 테스트 관련 어노테이션
@DataJpaTest
            
                목적: JPA Repository 계층만을 테스트하기 위한 슬라이스 테스트를 제공합니다.
            
            
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
}
            
                내장 데이터베이스를 사용하고 @Entity 클래스들과 Repository만 스캔합니다.
            
        @AutoConfigureTestDatabase
            
                목적: 테스트용 데이터베이스 설정을 제어합니다.
            
            
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class IntegrationTest { ... }
            
                Replace 옵션:
                
        ANY: 내장 데이터베이스로 교체 (기본값)
                AUTO_CONFIGURED: 자동 설정된 DataSource만 교체
                NONE: 교체하지 않음
            @Sql
            
                목적: 테스트 실행 전후에 SQL 스크립트를 실행합니다.
            
            
@Test
@Sql("/test-data.sql")
@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testFindByEmail() { ... }
            
                주요 속성:
                
        scripts: 실행할 SQL 파일 경로
                executionPhase: 실행 시점 (BEFORE_TEST_METHOD, AFTER_TEST_METHOD)
            @Rollback
            
                목적: 테스트 메서드 실행 후 트랜잭션 롤백 여부를 지정합니다.
            
            
@Test
@Rollback(false)
public void testCreateUser() {
    // 이 테스트는 롤백되지 않음
}
        @TestPropertySource
            
                목적: 테스트용 프로퍼티 소스를 지정합니다.
            
            
@TestPropertySource(
    locations = "classpath:application-test.properties",
    properties = {
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "logging.level.org.hibernate.SQL=DEBUG"
    }
)
public class DatabaseTest { ... }
        8. 감사(Auditing) 관련 어노테이션
@CreatedDate
            
                목적: 엔티티가 생성된 날짜를 자동으로 설정합니다.
            
            
@Entity
@EntityListeners(AuditingEntityListener.class)
public class User {
    
    @CreatedDate
    private LocalDateTime createdAt;
}
        @LastModifiedDate
            
                목적: 엔티티가 마지막으로 수정된 날짜를 자동으로 설정합니다.
            
            
@LastModifiedDate
private LocalDateTime updatedAt;
        @CreatedBy
            
                목적: 엔티티를 생성한 사용자를 자동으로 설정합니다.
            
            
@CreatedBy
private String createdBy;
        @LastModifiedBy
            
                목적: 엔티티를 마지막으로 수정한 사용자를 자동으로 설정합니다.
            
            
@LastModifiedBy
private String lastModifiedBy;
        @EnableJpaAuditing
            
                목적: JPA Auditing 기능을 활성화합니다.
            
            
@Configuration
@EnableJpaAuditing
public class AuditConfig {
    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> {
            // 현재 사용자 정보 반환
            return Optional.of("system");
        };
    }
}
        @EntityListeners
            
                목적: 엔티티 이벤트 리스너를 지정합니다.
            
            
@Entity
@EntityListeners(AuditingEntityListener.class)
public class User { ... }
        9. 이벤트 관련 어노테이션
@PrePersist
            
                목적: 엔티티가 영속화되기 전에 실행됩니다.
            
            
@Entity
public class User {
    
    @PrePersist
    public void prePersist() {
        this.createdAt = new Date();
        this.updatedAt = new Date();
    }
}
        @PostPersist
            
                목적: 엔티티가 영속화된 후에 실행됩니다.
            
            
@PostPersist
public void postPersist() {
    log.info("새로운 사용자가 생성되었습니다: {}", this.id);
}
        @PreUpdate
            
                목적: 엔티티가 업데이트되기 전에 실행됩니다.
            
            
@PreUpdate
public void preUpdate() {
    this.updatedAt = new Date();
}
        @PostUpdate
            
                목적: 엔티티가 업데이트된 후에 실행됩니다.
            
            
@PostUpdate
public void postUpdate() {
    log.info("사용자 정보가 업데이트되었습니다: {}", this.id);
}
        @PreRemove
            
                목적: 엔티티가 삭제되기 전에 실행됩니다.
            
            
@PreRemove
public void preRemove() {
    log.warn("사용자가 삭제됩니다: {}", this.id);
}
        @PostRemove
            
                목적: 엔티티가 삭제된 후에 실행됩니다.
            
            
@PostRemove
public void postRemove() {
    log.info("사용자가 삭제되었습니다: {}", this.id);
}
        @PostLoad
            
                목적: 엔티티가 데이터베이스에서 로드된 후에 실행됩니다.
            
            
@PostLoad
public void postLoad() {
    // 엔티티 로드 후 추가 초기화 작업
    this.displayName = this.firstName + " " + this.lastName;
}
        10. 기타 유용한 어노테이션
@DynamicInsert / @DynamicUpdate
            
                목적: Hibernate가 동적으로 INSERT/UPDATE SQL을 생성하도록 합니다.
            
            
@Entity
@DynamicInsert
@DynamicUpdate
public class User {
    // null이 아닌 필드만 INSERT/UPDATE에 포함
}
            
                성능 최적화에 도움이 되지만 SQL 캐싱 효과가 감소할 수 있습니다.
            
        @Where
            
                목적: 엔티티 조회 시 추가 조건을 자동으로 적용합니다.
            
            
@Entity
@Where(clause = "deleted = false")
public class User {
    
    @Column(name = "deleted")
    private Boolean deleted = false;
}
        @Formula
            
                목적: 계산된 필드를 정의합니다.
            
            
@Formula("(SELECT COUNT(*) FROM orders o WHERE o.user_id = id)")
private int orderCount;
        @Version
            
                목적: 낙관적 락을 위한 버전 필드를 지정합니다.
            
            
@Version
private Long version;
            
                동시성 제어를 위해 사용되며, 엔티티 수정 시 자동으로 증가합니다.
            
        
            주의사항:
            
        - 어노테이션을 과도하게 사용하면 코드 복잡성이 증가할 수 있습니다
- 성능 관련 어노테이션은 실제 측정을 통해 효과를 검증해야 합니다
- 데이터베이스 독립성을 고려하여 어노테이션을 선택해야 합니다
- 테스트 환경과 운영 환경의 설정이 다를 수 있으므로 주의가 필요합니다
            결론: Spring과 JPA의 데이터베이스 관련 어노테이션들은 강력하고 편리한 기능을 제공합니다. 각 어노테이션의 특성과 용도를 정확히 이해하고 적절히 활용하면 효율적이고 안정적인 데이터 액세스 계층을 구축할 수 있습니다. 특히 성능 최적화와 관련된 어노테이션들은 실제 운영 환경에서의 성능 측정을 통해 효과를 검증하는 것이 중요합니다.
        
        
    반응형