[Spring JPA #20] 스프링 부트 Spring JPA 엔티티 저장 메커니즘
| 스프링 데이터 JPA 엔티티 저장 상태 전이
- 스프링 데이터 JPA에서는 기본적으로 JpaRepository를 통해 DB와 상호작용하게 됩니다.
- JpaRepository는 save 메서드들 통해 DB에 엔티티 정보를 저장하게 됩니다.
- save 메서드는 단순히 새 엔티티를 DB에 추가하는 것이 아니고 엔티티의 상태에 따라 다른 동작방식을 보입니다. 아래의 조건에 따라 엔티티를 관리해주는 EntitiyManager는 엔티티의 상태를 추적해서 DB에 반영할지 아니면 업데이트를 할 지 결정하게 됩니다.
엔티티의 @Id를 기준으로 해당 프로퍼티가 null이면 Transient 상태로 판단하고 id가 null이 아니면 Detached 상태로 판단합니다.
만일 @Id가 null일 경우 save 메서드로 엔티티를 DB에 반영하려고 할 경우 EntitiyManager에서 이 엔티티를 관리하며 Lazy Loading 방식을 통해 꼭 반영해야 할 때만 엔티티를 반영하게 됩니다. 이때 EntitiyManager에서는 psersist() 메서드를 통해 엔티티를 Persistent 상태로 만듭니다.
@Id가 null이 아닐 경우 Detached 상태로 판단하여 이에 따른 다른 동작방식을 행하게 됩니다. EntityManager에서는 merge() 메서드를 통해 Detached 상태의 엔티티를 Persistent 상태로 되돌립니다.
| 스프링 데이터 JPA 엔티티 저장 예제
프로젝트 구조
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── tutorial
│ │ │ └── springevent
│ │ │ ├── Application.java
│ │ │ ├── PostController.java
│ │ │ ├── Post.java
│ │ │ └── PostRepository.java
│ │ └── resources
│ │ └── application.properties
│ └── test
│ └── java
│ └── com
│ └── tutorial
│ └── springevent
│ └── PostControllerTest.java
의존성 관리
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
소스 코드
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Entity
@Data
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
@Temporal(TemporalType.TIMESTAMP)
private Date created;
}
@RestController
public class PostController {
@Autowired
PostRepository postRepository;
@GetMapping("/posts")
public PagedResources<Resource<Post>> getPosts(Pageable pageable, PagedResourcesAssembler<Post> assembler) {
return assembler.toResource(postRepository.findAll(pageable));
}
}
public interface PostRepository extends JpaRepository<Post, Long> {
}
테스트 코드
@RunWith(SpringRunner.class)
@DataJpaTest
public class PostControllerTest {
@Autowired
PostRepository postRepositry;
@PersistenceContext
private EntityManager entityManager;
// Return 값을 받은 것을 확인하면서 코딩하는 것이 BEST PRACTICE
// Return 값을 받아서 값을 변경하는 것이 가장 좋다
@Test
public void save() {
Post post = new Post();
post.setTitle("jpa");
Post savedPost = postRepositry.save(post); // persist
assertThat(entityManager.contains(post)).isTrue();
assertThat(entityManager.contains(savedPost)).isTrue();
assertThat(savedPost == post);
Post postUpdate = new Post();
postUpdate.setId(post.getId());
postUpdate.setTitle("hibernate");
Post updatedPost = postRepositry.save(postUpdate); // merge
assertThat(entityManager.contains(updatedPost)).isTrue();
assertThat(entityManager.contains(postUpdate)).isFalse();
assertThat(updatedPost == postUpdate);
List<Post> all = postRepositry.findAll();
assertThat(all.size()).isEqualTo(1);
}
}
- 처음 Post 엔티티를 저장할 때 이 엔티티는 Transient 상태에서 Persistent 상태로 전이됩니다. 왜냐하면 엔티티에 @Id 값이 없기 때문에 EntityManager에서는 이 상태를 Transient 상태로 보기때문입니다.
- 새로운 Post 엔티티를 생성하고 여기서 Id 값을 부여했을 경우 EntityManager는 이 상태를 Detached 된 상태라고 보고 save 메서드를 실행할 시 Persistent로 다시 전이시킵니다.
'Spring Data > Spring Data JPA' 카테고리의 다른 글
[Spring JPA #24] Spring JPA Projection (0) | 2021.03.26 |
---|---|
[Spring JPA #23] Spring JPA EntityGraph (0) | 2021.03.26 |
[Spring JPA #22] Spring JPA Named Parameter, SpEL (0) | 2021.03.26 |
[Spring JPA #21] Spring JPA 쿼리 메서드 및 정렬 (0) | 2021.03.26 |
[Spring JPA #19] 스프링 데이터 HATEOAS (0) | 2021.03.26 |
[Spring JPA #18] 스프링 데이터 Pageable과 Sort (0) | 2021.03.26 |
[Spring JPA #17] 스프링 데이터 DomainClassConverter (0) | 2021.03.26 |
[Spring JPA #16] 스프링 데이터 QueryDsl (0) | 2021.03.25 |