Spring 입문주차 -4

dev_joo·2026년 2월 2일

SpringBoot의 JPA

JPA 프로젝트 세팅

SpringBoot 환경에서는
application.properties에 DB 정보를 전달해 주면 이를 토대로 EntityManagerFactory와 EntityManager를 자동으로 생성해준다.

@PersistenceContext

	@PersistenceContext
	EntityManager em;
	
@PersistenceConext 애너테이션을 사용하면 자동으로 생성된 EntityManager를 주입받아 사용할 수 있다.
# application.properties
spring.application.name=crud
spring.datasource.url=jdbc:mysql://localhost:3306/crud
spring.datasource.username=root
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

테이블 정책

기존 생성된 테이블을 삭제하고 새로 생성

spring.jpa.hibernate.ddl-auto=create

종료 시점에 테이블을 삭제

spring.jpa.hibernate.ddl-auto=create-drop

변경된 부분만 반영

spring.jpa.hibernate.ddl-auto=update

엔티티와 테이블 매핑이 정상인지 확인

spring.jpa.hibernate.ddl-auto=validate

-

spring.jpa.hibernate.ddl-auto=none

sql 관련 설정

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

트랜잭션

JPA를 사용하여 DB에 데이터를 저장, 수정, 삭제 하려면 트랜잭션 적용이 반드시 필요하다.

@Transactional

@Transactional 애너테이션을 클래스나 메서드에 추가하면 해당 클래스나 메서드 내에서 수행되는 모든 DB 연산 내용은 하나의 트랜잭션으로 묶여 수행된다.

@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
						...
			
		@Transactional
		@Override
		public <S extends T> S save(S entity) {
		
			Assert.notNull(entity, "Entity must not be null");
		
			if (entityInformation.isNew(entity)) {
				em.persist(entity);
				return entity;
			} else {
				return em.merge(entity);
			}
		}

						...
}

readOnly 옵션

@Transactional(readOnly = true)

트랜잭션에서 데이터를 읽기만 할 때, 읽기 작업에 대한 최적화를 수행한다.

해당 트랜잭션에서 데이터를 수정하려고 하면 예외가 발생한다.

영속성 컨텍스트와 트랜잭션의 생명주기

스프링 컨테이너 환경에서는 영속성 컨텍스트와 트랜잭션의 생명주기가 일치한다.
즉, 트랜잭션이 유지되는 동안은 영속성 컨텍스트도 계속 유지가 되기 때문에 영속성 컨텍스트의 기능을 사용할 수 있다.

트랜잭션 전파

Spring이 Service 부터 Repository 까지 Transaction을 유지하는 방법

propagation 옵션

@Transactional(propagation = Propagation...) 

REQUIRED

부모 메서드에 트랜잭션이 존재하면, 자식 메서드의 트랜잭션이 부모의 트랜잭션으로 합류하게 된다.

부모 메서드가 종료될 때 커밋이 된다.

MANDATORY

NESTED

NEVER

SUPPORTS

NOT_SUPPORTED

REQUIRES_NEW

Spring Data JPA

Spring Data JPA는 JPA를 더 쉽고 편리하게 사용할 수 있도록 도와주는
Spring Data의 하위 모듈로, 표준화된 Repository 계층을 JPA상에서 자동으로 구현해준다.

기존에 EntityManager를 직접 사용하거나 JdbcTemplate 기반으로 작성하던 Repository 계층 데이터 접근 로직 대신,

JpaRepository를 상속한 Repository 인터페이스만 작성 하면,

Spring Data JPA가 런타임에 기본 구현체인 SimpleJpaRepository를 기반으로 트랜잭션 및 예외 변환 등이 적용된 프록시 객체를 생성하고,
이를 Spring Bean으로 등록하여 사용 가능하게 한다.

Data JPA로 Repository 작성

Spring Data JPA에서는
JpaRepository<엔티티 클래스, @Id 타입>을 상속받는 인터페이스를 선언하여
Repository를 생성한다.

public interface MemoRepository extends JpaRepository<Memo, Long> {

}
런타임 시 Spring Data JPA가 자동으로 구현체를 생성하여 Bean으로 등록해준다.

1. 인터페이스에 정의된 CRUD 메서드를 사용하기

JpaRepository에는 기본적인 CRUD 메서드가 이미 정의되어 있어
별도의 구현 없이 바로 사용 가능하다.

CREATE

save(entity)

READ

findAll()

findById(id)

UPDATE

save(entity) - 영속 상태의 엔티티 값을 변경하면 UPDATE 쿼리 실행

DELETE

delete(entity)

deleteById(id)

2. Query Methods 사용하기

Query Methods는 메서드 이름을 분석하여 JPQL 쿼리를 자동 생성·실행하는 Spring Data JPA 기능이다.

정해진 네이밍 규칙에 맞게 메서드를 선언하면,
Spring Data JPA가 해당 메서드 이름을 분석하여 SimpleJpaRepository 구현체에 쿼리를 자동으로 생성한다.

public interface MemberRepository extends JpaRepository<Member, Long> {

    Member findByUsername(String username);
}

Spring Data JPA는 위 메서드를 내부적으로 다음과 같은 JPQL로 해석한다.
select m from Member m where m.username = :username

+ JPQL (Java Persistence Query Language)

@Query("select m from Member m where m.age >= :age")
List<Member> findAdult(@Param("age") int age);

DB의 테이블이 아닌 엔티티 기준으로 쿼리한다.
실행 시 DB에 맞는 SQL로 JPA가 변환하여 처리한다.
DB 고유 기능또는 DB 최적화가 필요하면 JPQL 보단 Native Query를 사용하도록 한다.

+ Native Query

@Query(
  value = "select * from member where age >= ?",
  nativeQuery = true
)
List<Member> findAdult(int age);

Query Methods의 한계

1. 조건이 많아질수록 메서드명이 가독성을 해친다.
findByUsernameAndAgeGreaterThanAndStatusOrderByCreatedAtDesc  <-???????
2. 복잡한 쿼리 표현이 어렵다.
* 서브쿼리
* 복잡한 JOIN
* CASE WHEN
* GROUP BY / HAVING
3. 동적 쿼리에 부적합하다.
4. 성능 튜닝에 한계가 있다.

@Query

@Query는 Repository 메서드에 직접 JPQL 또는 SQL을 작성할 수 있도록 제공되는 기능이다.

public interface MemberRepository extends JpaRepository<Member, Long> {

    @Query("select m from Member m where m.username = :username")
    Member findByUsername(@Param("username") String username);
}

JPA Auditing

Spring Data JPA에서 엔티티의 생성/수정 시간 같은 공통 필드를 자동으로 관리해 주는 기능

Auditing: 감사, 검증

Application 설정

@SpringBootApplication 이 있는 class에 @EnableJpaAuditing을 추가한다.

@EnableJpaAuditing
@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

Entity 설정

@EntityListeners(AuditingEntityListener.class)

해당 엔티티(또는 상위 클래스)에 Auditing 기능을 활성화

@CreatedDate

엔티티가 처음 생성되어 저장될 때의 시간이 자동으로 저장됩니다.
NSERT 시점에만 동작하므로,
엔티티 수정 시에는 값이 자동으로 변경되지 않는다.

  • @Column(updatable = false) → UPDATE 쿼리에서 해당 컬럼을 제외하여 (생성 시간이) 변경되지 않도록 보장

@LastModifiedDate

엔티티가 수정될 때마다 변경된 시간이 자동으로 저장

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 매핑
SQL 타입
DATE : ex) 2026-02-02
TIME : ex) 16:21:14
TIMESTAMP : ex) 2026-02-02 20:22:38.771000

@MappedSuperclass

createdAt, modifiedAt과 같이 공통 필드를 상속용으로 사용하는 부모 클래스를 만들 때 @MappedSuperclass 애너테이션을 사용한다.

이는 엔티티가 아님을 나타내며, 테이블은 생성되지 않고 자식 엔티티 클래스에 필드만 상속되도록 한다.

(abstract 키워드를 쓰지 않아도 동작은 하지만, 코드 관리상 써 주는게 좋다.)

해당 추상 클래스를 상속받은 JPA 엔티티 클래스들은
추상 클래스에 선언된 필드들을 자신의 컬럼으로 인식하게 된다.

JPA Auditing 사용 예

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {

   @CreatedDate
   @Column(updatable = false) // UPDATE 방지
   @Temporal(TemporalType.TIMESTAMP)
   private LocalDateTime createdAt;

   @LastModifiedDate
   @Column
   @Temporal(TemporalType.TIMESTAMP)
   private LocalDateTime modifiedAt;
}
profile
풀스택 연습생. 끈기있는 삽질로 무대에서 화려하게 데뷔할 예정 ❤️🔥

0개의 댓글