[Spring] Spring boot와 JPA 활용 - 프로젝트 환경설정
Spring 프로젝트 생성을 하려면 GroupId, ArtifactId 를 적어야 한다.
GroupId
- 모든 프로젝트 중에서 나의 프로젝트를 식별하게 해주는 식별자이다.
- Java의 패키지 네이밍 룰을 따른다.
eg. naver.com -> GroupId: com.naver
- 일반적으로 작성하는 회사의 도메인 명을 거꾸로 쓴다.
ArtifactId
- 프로젝트 진행 시 해당 프로젝트의 이름을 사용한다.
- 이 이름으로 버전을 제외한 컴파일된 Jar 파일이 생성된다.
- 소문자로만 작성 / 특수문자 사용 금지
eg. 아톰 프로젝트
ArtifactId: atom
스프링 부트 라이브러리 살펴보기
- spring-boot-starter-web
- spring-boot-starter-tomcat: 톰캣(웹서버)
- spring-webmvc: 스프링 웹 MVC
- spring-boot-starter-thymeleaf: 타임리프 템플릿 엔진(view)
- spring-boot-starter-data-jpa
- spring-boot-starter-aop
- spring-boot-starter-jdbc
- HikariCP 커넥션 풀(부트 2.0 기본)
- hibernate + JPA
- spring-data-jpa: 스프링 데이터 JPA
- spring-boot-starter(공통): 스프링 부트 + 스프링 코어 + 로깅
- spring-boot
- spring-core
- spring-boot-starter-logging
- logback, slf4j
테스트 라이브러리
- spring-boot-starter-test
- junit: 테스트 프레임워크
- mockito: 목 라이브러리
- assertj: 테스트 코드를 좀 더 편리하게 작성하게 도와줌
- spring-test: 스프링 통합 테스트 지원
핵심 라이브러리
- 스프링 MVC
- 스프링 ORM
- JPA, 하이버네이트
- 스프링 데이터 JPA
기타 라이브러리
- H2
- 커넥션 풀: 부트 기본은 HikariCP
- WEB(thymeleaf)
- 로깅 SLF4J, LogBack
- 테스트
JPA와 DB 설정, 동작확인
application.yml 파일을 main/resources/ 경로에 만들어준다.
- spring.jpa.hibernate.ddl-auto: create
: 이 옵션은 애플리케이션 실행 시점에 테이블을 drop 하고, 다시 생성한다.
모든 로그 출력은 가급적 로거를 통해 남겨야 한다.
show_sql 옵션은 System.out에,
org.hibernate.SQL 옵션은 logger를 통해 hibernate 실행 SQL를 남긴다.
yml 파일은 띄어쓰기에 주의해서 작성해야 한다.
@Id, @GeneratedValue
@Id
해당 프로퍼티가 테이블의 PK를 지정한다.
@GeneratedValue
생성 전략을 정의하기 위해 사용한다.
@GeneratedValue 없이 @Id만 사용하면 직접 할당이 된다.
@GeneratedValue는 엔티티의 기본키 생성 전략으로 4가지 타입이 있다.
- TABLE : 데이터베이스에 키 생성 전용 테이블을 하나 만들고 이를 사용하여 기본키를 생성한다.
- SEQUENCE : 데이터베이스에 특별한 오브젝트 시퀀스를 사용하여 기본키를 생성한다.
- IDENTITY : 기본키 생성을 데이터베이스에 위임한다.
- AUTO : JPA 구현체가 자동으로 생성 전략을 결정한다. (default)
Entity
@Entity
클래스 위에 선언하여 이 클래스가 엔티티임을 알려준다. JPA에서 정의된 필드들을 바탕으로 데이터베이스에 테이블을 만들어준다.
@AllArgsConstructor
선언된 모든 필드를 파라미터로 갖는 생성자를 자동으로 만들어준다.
@NoArgsConstructor
파라미터가 아예 없는 기본 생성자를 자동으로 만들어준다.
@ToString
해당 클래스에 선언된 필드들을 모두 출력할 수 있는 toString 메소드를 자동으로 생성할 수 있게 해준다.
@Id, @GeneratedValue
@Id는 해당 엔티티의 주요 키(PK)가 될 값을 지정해주는 것
@GeneratedValue는 이 PK가 자동으로 1씩 증가하는 형태로 생성될지 등을 결정해주는 애노테이션
Repository
Entity에 의해 생성된 DB에 접근하는 findAll() 과 같은 메서드들을 사용하기 위한 인터페이스이다. 엔티티를 선언함으로써 데이터베이스 구조를 만들었다면, 여기에 어떤 값을 넣거나, 넣어진 값을 조회하는 등의 CRUD를 해야 하는데, 이것을 어떻게 해야할 것인지 정의해주는 계층이다.
JPA의 영속성 컨텍스트
클라이언트의 요청이 올 때마다 (즉, thread가 하나씩 생성될 때마다) EntityManager를 생성한다.
EntityManager는 내부적으로 DB 커넥션 풀을 사용해서 DB에 접근한다.
EntityManager는 실제 트랜잭션 단위가 수행될 때마다 생성된다.
클라이언트의 요청이 들어올 때 생성했다가 요청이 끝나면 닫는다.
thread간 공유가 되어서는 안되고 트랜잭션이 수행된 후에는 반드시 닫고 DB 커넥션을 반환한다.
데이터를 변경하는 모든 작업은 반드시 트랜잭션 안에서 이루어져야 한다.
(조회는 상관 없다)
영속성 컨텍스트(Persistence Context)
Entity를 영구 저장하는 환경을 뜻하며, EntityManager로 Entity를 저장하거나 조회하면 영속성 컨텍스트에 Entity를 보관하고 관리한다.
em.persist(entity);
위 코드는 실제로 DB에 저장한다는 것이 아니라 영속성 컨텍스트를 통해 Entity를 영속화하겠다는 의미로, Entity를 영속성 컨텍스트에 저장하는 것이다.
EntityManager를 통해 영속성 컨텍스트에 접근하게 된다.
EntityManager를 생성하게 되면 1:1로 영속성 컨텍스트가 생성되지만,
컨테이너 환경의 JPA 에서는 여러 EntityManager가 하나의 영속성 컨텍스트를 공유하게 된다.
Spring과 같이 컨테이너를 사용하는 환경에서는 EntityManager를 직접 생성하지 않고 컨테이너에 위임한다.
일반적으로 스프링은 싱글톤 기반으로 동작하므로 속성값은 모든 thread가 공유하게 된다. 그래서 여러 thread가 동시에 접근하면 동시성 문제가 발생할 수도 있다.
그렇다면 스프링이 관리하는 EntityManager의 Thread-safe를 어떻게 보장할까? EntityManager의 Proxy 객체를 통해 감싸서 EntityManager 메소드 호출할 때 마다 Proxy를 통해서 EntityManager를 생성한다.
EntityManager를 직접 사용하는 경우에는 @PersistenceContext를 사용한다.
스프링 컨테이너가 초기화 되면서 @PersistenceContext 어노테이션으로 주입 받은 EntityManager를 Proxy로 감싼다.
스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트을 기본으로 사용한다. 트랜잭션이 시작될 때 영속성 컨텍스트를 생성하고, 트랜잭션이 끝날 때 영속성 컨텍스트를 끝낸다.
즉, 같은 트랜잭션이면 같은 영속성 컨텍스트를 사용하는 것이다.
여러 Entity Manager를 사용해도 한 트랜잭션으로 묶이면 영속성 컨텍스트를 공유한다.
같은 EntityManager를 사용해도 트랜잭션에 따라 접근하는 영속성 컨텍스트가 다르다.
따라서 같은 EntityManager를 호출해도 접근하는 영속성 컨텍스트가 다르므로 멀티스레드에 안전하다.
[ref]