[스프링 부트 쇼핑몰 프로젝트 with JPA] 2장 정리
엔티티 매니저를 통해 영속성 컨텍스트에 접근하여 데이터베이스 작업을 수행하며, 이는 1차 캐시, 동일성 보장, 쓰기 지연, 변경 감지 등의 이점을 제공한다. Spring Data JPA를 사용하면 Repository 인터페이스를 통해 엔티티를 관리할 수 있으며, 복잡한 쿼리는 @Query 어노테이션과 JPQL을 사용하거나 Querydsl을 사용하여 처리할 수 있다. 또한, 쇼핑몰 프로젝트를 생성하고 상품 엔티티를 설계하는 방법, 데이터베이스 초기화 전략, 엔티티 매칭 관련 어노테이션, 기본키 생성 전략 등에 대해 설명하였다.
Mar 29, 2024
🌼JPA
💡핵심 키워드
- JPA(
Java Persistence API
)는 자바 ORM 기술에 대한 API 표준이다. ORM이란Object Relational Mapping
의 약자로 객체와 관계형 데이터베이스를 매핑해주는 것을 말한다.
JPA란?
- 데이터를 관계형 데이터베이스에서 관리하기 위해 우리는 SQL문을 사용한다.
- SQL 중심 개발의 문제점은 개발자가 CRUD문을 작성해서 객체를 관계형 데이터베이스에 넣어주고 가져오는 작업을 하는 것이다.
- 즉 자바 객체를 SQL을 통해 데이터베이스에 관리하게 하고 데이터베이스에 저장된 데이터를 자바 어플리케이션에서 사용하려면 SQL을 통해 다시 자바 객체로 변환하는 반복적인 작업을 해야 한다.
- 또한 객체와 관계형 데이터베이스의 패러다임의 불일치가 발생한다.
- JPA 사용시 장점
- 특정 데이터베이스에 종속되지 않는다.
- 객체지향적 프로그래밍이 가능하다.
- 생산성을 향상시킬 수 있다.
- JPA 사용시 단점
- 복잡한 쿼리를 사용할 경우 SQL문을 사용하는 게 나을 수 있다.
- 성능 저하의 위험이 존재한다.
- 학습 시간이 오래 걸린다.
JPA 동작 방식
- 엔티티란 데이터베이스의 테이블에 대응하는 클래스다.
- @Entity가 붙은 클래스는 JPA에서 관리하는 엔티티가 된다.
- 엔티티 매니저 팩토리는 엔티티 매니저 인스턴스를 관리하는 주체이다.
- 애플리케이션 실행 시 한 개만 만들어지며 사용자로부터 요청이 오면 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성한다.
- 엔티티 매니저는 영속성 컨텍스트에 접근하여 엔티티에 대한 데이터베이스 작업을 제공한다.
- 내부적으로 데이터베이스 커넥션을 사용해서 데이터베이스에 접근한다.
find()
: 영속성 컨텍스트에서 엔티티를 검색하고 영속성 컨텍스트에 없을 경우 데이터베이스에서 데이터를 찾아 영속성 컨텍스트에 저장한다.persist()
: 엔티티를 영속성 컨텍스트에 저장한다.remove()
: 엔티티 클래스를 영속성 컨텍스트에서 삭제한다.flush()
:영속성 컨텍스트에 저장된 내용을 데이터베이스에 반영한다.
- 영속성 컨텍스트는 엔티티를 영구 저장하는 환경으로 엔티티 매니저를 통해 접근한다.
생명주기 | 내용 |
비영속(new) | new 키워드를 통해 생성된 상태로 영속성 컨텍스트와 관련이 없는 상태 |
영속(managed) | -엔티티가 영속성 컨텍스트에 저장된 상태로 영속성 컨텍스트에 의해 관리되는 상태
-영속 상태에서 데이터베이스에 저장되지 않으며, 트랜잭션 커밋 시점에 데이터베이스에 반영 |
준영속 상태(detached) | 영속성 컨텍스트에 엔티티가 저장되었다가 분리된 상태 |
삭제 상태(removed) | 영속성 컨텍스트와 데이터베이스에서 삭제된 상태 |
- 영속성 컨텍스트 사용시 이점은 다음과 같다.
- 1차 캐시
- 영속성 컨텍스트에는 1차 캐시가 존재하며
Map<KEY, VALUE>
로 저장된다. entityManager.find()
메소드 호출시 영속성 컨텍스트의 1차 캐시를 조회해서 엔티티가 존재할 경우 반환하고, 없으면 데이터베이스에서 조회 후 1차 캐시에 저장 및 리턴한다.- 동일성 보장
- 하나의 트랜잭션에서 같은 키값으로 영속성 컨텍스트에 저장된 엔티티 조회 시 1차 캐시에 저장된 엔티티를 조회하므로 같은 엔티티 조회를 보장한다.
- 트랜잭션을 지원하는 쓰기 지연
- 영속성 컨텍스트에는 쓰기 지연 SQL 저장소가 존재한다.
entityManager.persist()
를 호출하면 1차 캐시에 저장되는 것과 동시에 쓰기 지연 SQL 저장소에 SQL문이 저장된다.- 이렇게 SQL문을 쌓아두고 트랜잭션을 커밋하는 시점에 저장된 SQL문들이
flush
되면서 데이터베이스에 반영된다. 따라서 성능에서 이점을 볼 수 있다. - 변경 감지
- JPA는 1차 캐시에 데이터베이스에서 처음 불러온 엔티티의 스냅샷 값을 갖고 있다.
- 그리고 1차 캐시에 저장된 엔티티와 스냅샷을 비교 후 변경 내용이 있다면 UPDATE SQL문을 쓰기 지연 SQL 저장소에 담아둔다.
- 그리고 데이터베이스에 커밋 시점에 변경 내용을 자동으로 반영한다. 즉, 따로 update문을 호출할 필요가 없다.
🌼쇼핑몰 스포젝트 생성하기
💡핵심 키워드
프로젝트 생성하기
Thymeleaf
: 서버에서 가공한 데이터를 뷰에 보여주기 위한 템플릿 엔진이다.
Spring Data JPA
: JPA를 쉽세 구현할 수 있도록 도와주는 모듈이다.
MySQL Driver
: MySQL 데이터베이스를 사용하기 위한 의존성이다.
H2 Dataabase
: 자바 기반의 관계형 데이터베이스로 매우 가볍고 빠른 데이터베이스다. 메모리 내에 데이터를 저장하는 메모리 데이터베이스 기능을 지원한다.
데이터베이스 초기화 전략
application.properties
에 추가한 jpa 옵션 중 주의 깊게 봐야 할 설정은DDL AUTO
옵션이다.spring.jpa.hibernate.ddl-auto
옵션을 통해 애플리케이션 구동 시 JPA의 데이터베이스 초기화 전략을 설정할 수 있다.none
: 사용하지 않음create
: 기존 테이블 삭제 후 테이블 생성create-drop
: 기존 테이블 삭제 후 테이블 생성, 종료 시점에 테이블 삭제update
: 변경된 스키마 적용validate
: 엔티티와 테이블 정상 매핑 확인- 스테이징, 운영환경에서는 절대로
create
,create-drop
,update
를 사용하면 안된다.
🌼상품 엔티티 설계하기
💡핵심 키워드
- 쇼핑몰을 만들기 위해서는 상품 등록 및 조회, 수정, 삭제가 가능해야 한다.
상품 엔티티 설계하기
- 엔티티란 데이터베이스의 테이블에 대응하는 클래스다.
@Entity
가 붙은 클래스는 JPA에서 관리하며 엔티티라고 한다.
- Lombok의 어노테이션을 이용한다면 getter, setter, toString등을 자동으로 만들어주기 때문에 코드를 깔끔하게 짤 수 있다.
- 엔티티 매칭 관련 어노테이션
어노테이션 | 설명 |
@Entity | 클래스를 엔티티로 선언 |
@Table | 엔티티와 매핑할 테이블을 지정 |
@id | 테이블의 기본키에 사용할 속성을 지정 |
@GeneratedValue | 키 값을 생성하는 전략 명시 |
@Column | 필드와 컬럼 매핑 |
@Lob | BLOB, CLOB 타입 매핑 |
@CreationTimestamp | insert 시 시간 자동 저장 |
@UpdateTimeStamp | update 시 시간 자동 저장 |
@Enumeratierd | enum 타입 매핑 |
@Transient | 해당 필드 데이터베이스 매핑 무시 |
@Temporal | 날짜 타입 매핑 |
@CreateDate | 엔티티가 생성되어 저장될 때 시간 자동 저장 |
@LastModifiedDate | 조회한 엔티티의 값을 변경할 떄 시간 자동 저장 |
@Column
어노테이션 추가 속성
속성 | 설명 | 기본값 |
name | 필드와 매핑할 컬럼의 이름 설정 | 객체의 필드 이름 |
unique(DDL) | 유니크 제약 조건 | ㅤ |
insertable | insert 가능 여부 | true |
updatable | update 가능 여부 | true |
length | String 타입의 문자 길이 제약조건 설정 | 255 |
nullable(DDL) | null 값의 허용 어부 설정, false 설정 시 DDL 생성 시에 not null 제약조건 추가 | ㅤ |
columnDefinition | 데이터베이스 컬럼 정보 직접 기술
예) @Column(columnDefinition = “varchar(5) default’10’ not null”) | ㅤ |
precision, scale(DDL) | BigDecimal 타입에서 사용(BigInteger 가능) precision은 소수점을 포함한 전체 자리수이고, scale은 소수점 자리수.
Double 타입과 float 타입에는 적용되지 않음 | ㅤ |
@GeneratedValue
어노테이션을 통한 기본키를 생성하는 전략
생성 전략 | 설명 |
GenerationType.AUTO(default) | JPA 구현체가 자동으로 생성 전략 결정 |
GenerationType.IDENTITY | 기본키 생성을 데이터베이스에 위임
예) MySql 데이터베이스의 경우 AUTO_INCREMENT를 사용하여 기본키 생성 |
GenerationType.SEQUENCE | 데이터베이스 시퀀스 오브젝트를 이용한 기본키 생성
@SequenceGenerator 를 사용하여 시퀀스 등록 필요 |
GenerationType.TABLE | 키 생성용 테이블 사용, @TableGenerator 필요 |
🌼Repository 설계하기
💡핵심 키워드
- Spring Data JPA에서는 엔티티 매니저를 직접 이용해 코드를 작성하지 않아도 된다.
- 그 대신 Data Access Object의 역할을 하는 Repository 인터페이스를 설계한 후 사용하는 것만으로 충분하다.
- JpaRepository에서 지원하는 메소드 예시
메소드 | 기능 |
<S extends T> save(S entity) | 엔티티 저장 및 수정 |
boid delete(T entity) | 엔티티 삭제 |
count() | 엔티티 총 개수 반환 |
Iterable<T> findAll() | 모든 엔티티 조회 |
- 테스트 환경의 경우 h2 데이터베이스를 사용하도록 resources 아래에 테스트 환경을 위한 별도의 application-test.properties 파일을 만든다.
🌼쿼리 메소드
💡핵심 키워드
- 쿼리 메소드는 스프링 데이터 JPA에서 제공하는 핵심 기능 중 하나로 Repository 인터페이스에 간단한 네이밍 룰을 이용하여 메소드를 작성하면 원하는 쿼리를 실행할 수 있다.
- 쿼리 메소드를 이용할 떄 가장 많이 사용하는 문법으로 find를 사용한다. 엔티티의 이름은 생략이 가능하며, By 뒤에는 검색할 때 사용할 변수의 이름을 적어준다.
find + (엔티티 이름) + By + 변수이름
- 쿼리 메소드 Sample 및 JPQL snippet
Keyword | Sample | JPQL snippet |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = 2? |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = 2? |
Is, Equals | findByFirstname
findByFirstnameIS
findByFirstnameEquals | … where x.lastname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GraterThan | findByAgeGraterThan | … where x.age > ?1 |
GraterThanEqual | findByAgeGraterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull, Null
IsNotNull | findByAge(Is)Null | … where x.age is null |
NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | FindByFirstnameLike | … where x.firstname like ?1 |
NotLike | FindByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | FindByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | FindByFirstnameEndingWIth | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | FindByFirstnameContaining | … where x.firstname like ?1 (parameter bound with wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
🌼Repository 설계하기
💡핵심 키워드
- Spring Data JPA에서는 엔티티 매니저를 직접 이용해 코드를 작성하지 않아도 된다.
- 그 대신 Data Access Object의 역할을 하는 Repository 인터페이스를 설계한 후 사용하는 것만으로 충분하다.
- JpaRepository에서 지원하는 메소드 예시
메소드 | 기능 |
<S extends T> save(S entity) | 엔티티 저장 및 수정 |
boid delete(T entity) | 엔티티 삭제 |
count() | 엔티티 총 개수 반환 |
Iterable<T> findAll() | 모든 엔티티 조회 |
- 테스트 환경의 경우 h2 데이터베이스를 사용하도록 resources 아래에 테스트 환경을 위한 별도의 application-test.properties 파일을 만든다.
🌼Spring DATA JPA @Query 어노테이션
💡핵심 키워드
- 조건이 많아질 때 쿼리 메소드를 선언하면 이름이 너무 길어진다. 따라서 쿼리 메소드는 복잡한 쿼리를 다루기에 적합하지 않다.
- Spring Data JPA에서 제공하는
@Query
어노테이션을 이용하면 SQL과 유사한 JPQL이라는 객체지향 쿼리 언어를 통해 복잡한 쿼리도 처리가 가능하다. - JPQL은 SQL을 추상화해서 사용하기 떄문에 특정 데이터베이스 SQL에 의존하지 않는다.
- @Param 어노테이션을 이용하여 변수를 JPQL에 전달하는 대신 파라미터의 순서를 이용해 전달해줄 수 있다.
- 이 경우
:파라미터 이름
대신 철 번째 파라미터를 전달하겠다는?1
이라는 표현을 사용하면 된다.
🌼Spring DATA JPA Querydsl
💡핵심 키워드
@Query
어노테이션 안에 JPQL 문법으로 문자열을 입력할 때 잘못 입력하면 컴파일 시점에 에러를 발견할 수 없다.- 이를 보완할 수 있는 방법으로
Querydsl
이 있다.
Querydsl
장점- 고정된 SQL문이 아닌 조건에 맞게 동적으로 쿼리를 생성할 수 있다.
- 비슷한 쿼리를 재사용할 수 있으며 제약 조건 조립 및 가독성을 향상시킬 수 있다.
- 문자열이 아닌 자바 소스코드로 작성하기 때문에 컴파일 시점에 오류를 발견할 수 있다.
- IDE의 도움을 받아서 자동 완성 기능을 이용할 수 있기 때문에 생산성을 향상시킬 수 있다.
Querydsl
을 사용하기 위해서는pom.xml
파일의<dependencies> </dependencies>
사이에querydsl-jpa
의존성과querydsl-apt
의존성을 추가해야 한다. 이 경우 version은 5.0.0 이상이어야 한다.
- 추가한 의존성을 받아오기 위해 인텔리제이의 오른쪽에 있는
Maven
에서Reload All Maven Projects
버튼을 클릭한다. - 이후
maven compile
을 통해 QItem 클래스를 생성할 수 있다.
- JPAQuery 데이터 반환 메소드
메소드 | 기능 |
List<T> fetch() | 조회 결과 리스트 반환 |
T fetchOne | 조회 대상이 1건인 경우 제네릭으로 지정한 타입 반환 |
T fetchFirst() | 조회 대상 중 1건만 반환 |
Long fetchCount() | 조회 대상 개수 반환 |
QueryResult<T> fetchResults() | 조회한 리스트와 전체 개수를 포함한 QueryResults 반환 |
- QueryDslPredicateExecutor 인터페이스 정의 메소드
메소드 | 기능 |
long count(Predicate) | 조건에 맞는 데이터의 총 개수 반환 |
boolean exists(Predicate) | 조건에 맞는 데이터 존재 여부 반환 |
Iterable findAll(Predicate) | 조건에 맞는 모든 데이터 반환 |
Page<T> findAll(Predicate, Pageable) | 조건에 맞는 페이지 데이터 반환 |
Iterable findAll(Predicate, Sort) | 조건에 맞는 정렬된 데이터 반환 |
T findOne(Predicate) | 조건에 맞는 데이터 1개 반환 |
🏁결론
해당 내용을 정리하면서 JPA의 등장 배경 및 특징, JPA의 구조와 동작 방식, SpringData JPA를 이용하여 데이터를 처리하는 방법을 이해할 수 있었다.
Share article