JpaRepository.save()
save() ๋ ๋จ์ํ ์ํฐํฐ๋ฅผ ์ ์ฅํด์ฃผ๋ ๊ธฐ๋ฅ์ ์ํํ๋ ๊ฒ์ด ์๋. ๊ฒฝ์ฐ์ ๋ฐ๋ผ persist ๋๋ merge ๋ก ๋์ํ๋ค.
Transient ์ํ์ ๊ฐ์ฒด๋ผ๋ฉด EntityManager.persist()
Detached ์ํ์ ๊ฐ์ฒด๋ผ๋ฉด EntityManager.merge()
persist
Persist() ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ๋๊ธด(save ๋์) ๊ทธ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ Persistent ์ํ๋ก ๋ณ๊ฒฝํ๋ค. save() ๊ฒฐ๊ณผ๋ก ๋ฐํ๋ฐ์ saved entity ๊ฐ ๊ณง ํ๋ผ๋ฏธํฐ๋ก ๋๊ธด ๊ทธ save ๋์๊ณผ ๊ฐ๋ค.
Copy @DataJpaTest
public class PostRepositoryTest {
@Autowired
PostRepository postRepository;
@PersistenceContext
EntityManager entityManager;
@Test
void saveTest() {
Post post = new Post();
post.setName("name_1");
post.setDescription("description_1");
Post savedPost = postRepository.save(post);
Assertions.assertThat(entityManager.contains(post)).isTrue();
Assertions.assertThat(entityManager.contains(savedPost)).isTrue();
Assertions.assertThat(post).isEqualTo(savedPost);
}
}
merge
์ฃผ์ ํ์ํ ๊ฒ๋ค์ ์ ๊ธฐ์ตํ์. update ์ ํ๋ผ๋ฏธํฐ๋ก ๋๊ธด entity๋ ์์ํ๋์ง ์๊ณ ๊ทธ๊ฒ์ ๋ณต์ฌ๋ณธ์ด ์์ํ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์์ํ๋ ๊ฐ์ฒด๊ฐ return ๋๋ค.
์ด ํฌ์ธํธ๊ฐ ์ฃผ๋ ์ค์ํ ์์ฌ์ ์ update ๋ก์ง ์ฒ๋ฆฌ ์ดํ ํ์ ์์
์ ํด์ผํ ๊ฒฝ์ฐ ๋ฐ๋์ return ๋ ๊ทธ ๊ฐ์ ์ฌ์ฉํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค. ์๋ํ๋ฉด ๊ทธ ๊ฐ์ฒด๊ฐ persistent ๋ ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฐ๋๋ก ๋งํ๋ฉด save() ์ ํ๋ผ๋ฏธํฐ๋ก ๋๊ธด ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด์๋ ์๋๋ค๋ ๊ฒ์ด๋ค.
์ ์ฌ์ฉํ๋ฉด ์๋๋? managed ๊ฐ์ฒด๊ฐ ์๋๊ธฐ ๋๋ฌธ์ด๋ค. ์ฆ, persistent ์ํ๊ฐ ์๋๊ธฐ ๋๋ฌธ์ ์ํ๊ฐ ์ถ์ ์ด ๋์ง ์์์ dirty check ๋ฑ JPA์ ์ด์ ์ ๋๋ฆฌ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ด๋ค.
Copy @Test
void updateTest() {
Post post = new Post();
post.setName("name_1");
post.setDescription("description_1");
postRepository.save(post);
Post newPost = new Post();
newPost.setId(1L);
newPost.setName("new_name_1");
newPost.setDescription("new_description_1");
Assertions.assertThat(entityManager.contains(newPost)).isFalse();
// merge ๋ฐ์. ์ด ๋ newPost์ ๋ณต์ฌ๋ณธ์ด ์์ํ๋๋ค.
Post updatedPost = postRepository.save(newPost);
// ํ๋ผ๋ฏธํฐ๋ก ๋๊ธด entity์ธ newPost ๋ detached
Assertions.assertThat(entityManager.contains(newPost)).isFalse();
// update ๊ฒฐ๊ณผ๋ก ๋ฐ์ entity์ธ updatedPost๋ persistent
Assertions.assertThat(entityManager.contains(updatedPost)).isTrue();
}
entity์ ์ผ๋ถ ์ปฌ๋ผ๋ง ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ธ๋ฐ ๋๋ ์ค๋ฌด์์ ์จ๋ณธ์ ์ด ์๊ฑฐ๋์ ์ป๋ ์ฑ๋ฅ ํจ์จ ๋๋น ์ฝ๋ ๋ณต์ก๋๋ง ๋ ์ปค์ง๋ ๋๋์ด๋ค. ์ผ๋จ์ ์ด๋ฐ๊ฒ ์๋ค ์ ๋๋ง ์ธ์งํด๋๋ค.
์๋๋ ๊ฐ์ ๋
ธํธ ๊ทธ๋๋ก ๋ฐ์ท.
์ธํฐํ์ด์ค ๊ธฐ๋ฐ ํ๋ก์ ์
Nested ํ๋ก์ ์
๊ฐ๋ฅ.
Closed ํ๋ก์ ์
์ฟผ๋ฆฌ๋ฅผ ์ต์ ํ ํ ์ ์๋ค. ๊ฐ์ ธ์ค๋ ค๋ ์ ํธ๋ฆฌ๋ทฐํธ๊ฐ ๋ญ์ง ์๊ณ ์์ผ๋๊น.
Java 8์ ๋ํดํธ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ์ฐ์ฐ์ ํ ์ ์๋ค.
Open ํ๋ก์ ์
@Value(SpEL)์ ์ฌ์ฉํด์ ์ฐ์ฐ์ ํ ์ ์๋ค. ์คํ๋ง ๋น์ ๋ฉ์๋๋ ํธ์ถ ๊ฐ๋ฅ. -์ฟผ๋ฆฌ ์ต์ ํ๋ฅผ ํ ์ ์๋ค. SpEL์ ์ํฐํฐ ๋์์ผ๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์.
ํด๋์ค ๊ธฐ๋ฐ ํ๋ก์ ์
๋กฌ๋ณต @Value๋ก ์ฝ๋ ์ค์ผ ์ ์์
Specification
JPA 2 introduces a criteria API that you can use to build queries programmatically. By writing a criteria, you define the where clause of a query for a domain class. Taking another step back, these criteria can be regarded as a predicate over the entity that is described by the JPA criteria API constraints.
์ฌ์ฉ ๋ฐฉ๋ฒ์ ๊ณต์๋ฌธ์์๋ ๋์์์ง๋ง ๊ฐ๋จํ๋ค. repository ์์ JpaSpecificationExecutor ๋ฅผ ์์ ๋ฐ๊ณ findAll() ์ ํ๋ผ๋ฏธํฐ๋ก Specification ์ ๋๊ฒจ์ค๋ค. pageable ๋ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ๋ฉด ํ์ด์ง ์ฒ๋ฆฌ๋ ๊ฐ๋ฅํ๋ค.
์ ์ฉํ์ง๋ง ํ์
์ธ์ดํํ์ง ์์์ ๊ทธ๋ฅ queryDsl ์ฐ๋๊ฒ ๋ง์ ํธํ๋ค.
Copy public interface PostRepository extends JpaRepository<Post, Long>, PostCustomRepository, QuerydslPredicateExecutor<Post>, JpaSpecificationExecutor<Post> {
}
Copy public static Specification<Post> getPostsByCustomSpecification(String name, String description) {
return ((root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>(); // javax.persistence.criteria.Predicate;
if (name != null) {
predicates.add(criteriaBuilder.equal(root.get("name"), name));
}
if (description != null) {
predicates.add(criteriaBuilder.equal(root.get("description"), description));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
});
}
Copy @Test
void specificationTest() {
Post post = new Post();
final String NAME = "specificationPostName";
final String DESCRIPTION = "specificationPostDescription";
post.setName(NAME);
post.setDescription(DESCRIPTION);
postRepository.save(post);
List<Post> targets = postRepository.findAll(Post.getPostsByCustomSpecification(NAME, DESCRIPTION));
Assertions.assertThat(targets.size()).isEqualTo(1);
Assertions.assertThat(targets.get(0).getName()).isEqualTo(NAME);
Assertions.assertThat(targets.get(0).getDescription()).isEqualTo(DESCRIPTION);
}
Copy select
post0_.id as id1_0_,
post0_.description as descript2_0_,
post0_.name as name3_0_
from
post post0_
where
post0_.name=?
and post0_.description=?
# binding parameter [1] as [VARCHAR] - [specificationPostName]
# binding parameter [2] as [VARCHAR] - [specificationPostDescription]
๋ง์ฝ ์ฌ๊ธฐ์ ์๋์ ๊ฐ์ด DESCRIPTION ์ null ์ ๋ฃ์ ๊ฒฝ์ฐ ์ฟผ๋ฆฌ๊ฐ ์๋ํ๋๋ก ๋ฌ๋ผ์ง๋ค.
Copy @Test
void specificationTest() {
Post post = new Post();
final String NAME = "specificationPostName";
final String DESCRIPTION = "specificationPostDescription";
post.setName(NAME);
post.setDescription(DESCRIPTION);
postRepository.save(post);
List<Post> targets = postRepository.findAll(Post.getPostsByCustomSpecification(NAME, null));
Assertions.assertThat(targets.size()).isEqualTo(1);
Assertions.assertThat(targets.get(0).getName()).isEqualTo(NAME);
Assertions.assertThat(targets.get(0).getDescription()).isEqualTo(DESCRIPTION);
}
Copy select
post0_.id as id1_0_,
post0_.description as descript2_0_,
post0_.name as name3_0_
from
post post0_
where
post0_.name=?
Auditing
ํน๋ณํ ๋ด์ฉ์ ์์ด์ ๊ฐ์์๋ฃ๋ง ์ฒจ๋ถํ๋ค.
๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์
์์ @EnableJpaAuditing ์ถ๊ฐ (์คํ๋ง๋ถํธ๊ฐ ์๋์ค์ ํด์ฃผ์ง ์๋๋ค)
์ํฐํฐ ํด๋์ค ์์ @EntityListeners(AuditingEntityListener.class) ์ถ๊ฐ
Copy @CreatedDate
private Date created;
@LastModifiedDate
private Date updated;
@CreatedBy
@ManyToOne
private Account createdBy;
@LastModifiedBy
@ManyToOne
private Account updatedBy;