1. ์ํฉ
๊ธฐ์กด์ view ์ฆ๊ฐ ์ฝ๋์์๋ dirty checking๋ฅผ ์ฌ์ฉํ์ฌ view๋ฅผ updateํ๋ ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ๋ฐ์ํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ค. ์ด ๊ฒฝ์ฐ view๊ฐ ์ฆ๊ฐํ๋ฉด @EnableAuditing์ผ๋ก ์ธํด updated๋ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ค๋ ๋ฌธ์ ๊ฐ ์๋ค.
ํด๋น ๋ฌธ์ ์ ํด๊ฒฐ์ ์ํด JPQL์ ํ์ฉํ์ฌ ์์์ฑ ์ปจํ ์คํธ(1์ฐจ ์บ์)๋ฅผ ๋ฌด์ํ๊ณ ๋ค์ด๋ ํธ๋ก DB์ ์์ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋๋ก ํ๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ฟผ๋ฆฌ๊ฐ ์คํ๋ ๋ JPA๋ ํด๋น ์ํฐํฐ์ ์ํ๋ฅผ ๊ฐ์งํ์ง ์์ผ๋ฏ๋ก, Auditing ๊ธฐ๋ฅ์ด ์๋ํ์ง ์๋๋ค.
ํ์ฌ ์ํฉ์ ๋จ๊ฑด ์์ ์ด์ง๋ง, ์ฌ๋ฌ๊ฐ์ ๋ ์ฝ๋๋ฅผ ์์ ๋ฐ ์ญ์ ํ ๋๋ ์ด์ ์ ์ฌํ๊ฒ JPQL, QueryDSL ๋ฑ์ ํ์ฉํ๊ณ ์ด๋ฅผ "๋ฒํฌ ์ฐ์ฐ"์ด๋ผ๊ณ ํ๋ค.
@Entity
@Getter
@Table(name = "inventories")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Inventory extends BaseEntity {
...
@Column(nullable = false)
private Integer view;
public void increaseView(){
this.view += 1;
}
}
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class BaseEntity {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
@Query์ ๋ฒํฌ ์ฐ์ฐ์ ์์ฑํ ๋ค, @Modifying์ ์์ฑํ์ง ์์ผ๋ฉด InvalidDataAccessApiUsageException์ด ๋ฐ์ํ๋ ๊ฒ์ ์์ง ๋ง์!
// ์์ ์ : dirty checking์ ํ์ฉํ์ฌ view๋ฅผ ์์ ํ๋ค.
public void increaseInventoryView(Long inventoryId) {
Inventory inventory = inventoryRepository.findById(inventoryId)
.orElseThrow(() -> new CustomException(InventoryErrorCode.INVENTORY_NOT_FOUND));
inventory.increaseView(); // dirty checking
}
// ์์ ํ : ์ง์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Query("update Inventory i set i.view = i.view+1 where i.id = :id")
void increaseView(@Param("id") Long id);
2. ์ฃผ์ ํด์ผ ํ ์
JPA์ ์์์ฑ ์ปจํ ์คํธ 1์ฐจ ์บ์๋ @Id ๊ฐ์ Key ๊ฐ์ผ๋ก ์ํฐํฐ๋ฅผ ๊ด๋ฆฌํ๋ค. ๊ทธ๋์ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ @Id ๊ฐ์ด 1์ฐจ ์บ์์ ์กด์ฌํ๋ฉด DB์ ์ ๊ทผํ์ง ์๊ณ , ์บ์ฑ๋ ์ํฐํฐ๋ฅผ ๋ฐํํ๋ค.
1์ฐจ ์บ์์ DB ์ํ๊ฐ ๋ค๋ฅผ ๋ ๋ฐ์ํ ์ ์๋ ์ํฉ์ ๋ค์๊ณผ ๊ฐ๋ค.
1. ํธ๋์ญ์ ์์
2. ๋์ด๊ฐ 40์ด์ธ ์ง์๋ค์ ์กฐํํ๋ค.
3. ๋์ด๊ฐ 40์ด์ธ ์ง์๋ค์ ์๊ธ์ 100๋ง์ ์ธ์ํ๊ณ , ๋ฒํฌ ์ฐ์ฐ์ ์คํํ๋ค.
4. findByAge(int age)๋ก ๋์ด๊ฐ 40์ด์ธ ์ง์๋ค์ ์กฐํํ๋ค. (์ธ์๋ ์๊ธ์ด ์๋, ๊ธฐ์กด ์๊ธ์ด ์กฐํ๋๋ค..!)
5. ํธ๋์ญ์ ์ข ๋ฃ
4๋ฒ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ์ด์ ๋ JPA์ 1์ฐจ ์บ์์ ํด๋น ์ํฐํฐ๊ฐ ์กด์ฌํ๋ฉด DB ๋ฐ์ดํฐ๊ฐ ์๋, ์บ์ฑ๋ ์ํฐํฐ๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ด๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ
- ์์ , ์ญ์ ๊ฐ ์๋ฃ๋๋ฉด ๋ฐ๋ก ํธ๋์ญ์ ์ ์ข ๋ฃํ๋ค. ์ฆ, ์์ &์ญ์ ํ ์ฝ๊ธฐ๋ฅผ ์์ ํ์ง ์๋๋ค.
- flushAutomatically = true, clearAutomatically = true
- flushAutomatically = true: JPQL ์คํ ์ด์ ์ flush (ํด๋น jpql ๊ณผ ๊ด๊ณ์๋ ์ํฐํฐ ๋ณ๊ฒฝ์ํฉ์ด clear ์ ์ํด ์ง์์ง ์ ์๊ธฐ ๋๋ฌธ์)
- flush : ์์์ฑ ์ปจํ ์คํธ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํ๋ค. ์์์ฑ ์ปจํ ์คํธ๊ฐ ์ด๊ธฐํ ๋๊ฒ์ ์๋๋ค. ๋ณ๊ฒฝ๋ ์ํฐํฐ๋ ์ฌ์ ํ ์์์ฑ ์ปจํ ์คํธ์ ๋จ์ ์์ผ๋ฉฐ, ์ดํ์๋ ๊ณ์ํด์ ์ฌ์ฉํ ์ ์๋ค.
- clearAutomatically = true: JPQL ์คํ ํ clear
- clear : ์์์ฑ ์ปจํ ์คํธ๋ฅผ ๋น์ด๋ค. clear๋ ์ํฐํฐ๋ ๋ฉ๋ชจ๋ฆฌ์์ ์ ๊ฑฐ๋๋ฉฐ, ๋ค์ ์ฌ์ฉํ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์๋ก ๋ก๋ํด์ผํ๋ค.
- flushAutomatically = true: JPQL ์คํ ์ด์ ์ flush (ํด๋น jpql ๊ณผ ๊ด๊ณ์๋ ์ํฐํฐ ๋ณ๊ฒฝ์ํฉ์ด clear ์ ์ํด ์ง์์ง ์ ์๊ธฐ ๋๋ฌธ์)
๋ฌผ๋ก ๋ณธ์ธ์ด ๋ฌธ์ ์ํฉ์ ์ ์์งํ๋ฉด ๋ฌธ์ ๋ ์์ง๋ง, ์ฌ๋ฌ ์ฌ๋์ด ๊ณต์ ํ๋ ์ฝ๋์ด๊ณ ํด๋จผ์๋ฌ๋ ์ธ์ ๋ ๋ฐ์ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฏธ๋ฆฌ ๋ฐฉ์งํ๋ ๊ฒ ์ข๋ค.
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Query("update Inventory i set i.view = i.view+1 where i.id = :id")
void increaseView(@Param("id") Long id);
+ ํ ์คํธ ์ฝ๋ ์์ฑ ์ ๋ฐ์ํ ๋ฌธ์
@Test
@DisplayName("updatedAt๋ ๋ณ๊ฒฝ ์์ด ์ด์ ๊ณผ ๋์ผํ๋ค.")
void it_remains_same_updatedAt() {
inventoryRepository.increaseView(inventory.getId());
em.flush();
em.clear();
Inventory updatedInventory = inventoryRepository.findById(inventory.getId())
.orElse(null);
assertThat(updatedInventory).isNotNull();
assertThat(updatedInventory.getUpdatedAt()).isEqualTo(inventory.getUpdatedAt());
}
์ฌ๊ณ ํํฉ์ ์กฐํ์๊ฐ ์ฆ๊ฐํด๋ updatedAt์ ๋ณํ์ง ์์์ ํ์ธํ๋ unit test๋ฅผ ์์ฑํ ํ, PR์ ์ฌ๋ ธ๋๋ฐ ๋น๋๊ฐ ๊นจ์ง๋ ์ผ์ด ๋ฐ์ํ๋ค. ./gradlew clean build๋ผ๋ ๋์ผํ ๋ช ๋ น์ด๋ก ์คํํ๋๋ฐ ์ intelliJ์์๋ ์ฑ๊ณตํ๊ณ , github actions์์๋ ์คํจํ ๊น?
๋ก๊ทธ๋ก ํ์ธํด ๋ณด๋ ๊ฒ์ด ๊ฐ์ฅ ์ ํํ๊ธฐ์ github actions์ ๋น๋๊ฐ ๊นจ์ง๋ฉด test-report๋ฅผ ์์ฑํ๋๋ก ํ๋ค.
// github actions๋ฅผ ๊ด๋ฆฌํ๋ yml ํ์ผ์ "Test Report ์ ์" task๋ฅผ ์ถ๊ฐํ๋ค.
- name: Gradle ๋น๋
run: ./gradlew clean build
- name: Test Report ์ ์
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-report
path: ./yellobook-domain/build/reports/tests/test/

๊ทธ ๊ฒฐ๊ณผ๋!

๊ทธ๋ ๋ค... ๋ฐ์ฌ๋ฆผ ๋๋ฌธ์ด์๋ค... intelliJ๋ ๋ฐ์ฌ๋ฆผ์ ํ์ง ์๊ณ ๊ฐ ๋น๊ต๋ฅผ ํด์ test๊ฐ ์ฑ๊ณตํ์ง๋ง, github actions์์๋ ์์์ ์๋ 6์๋ฆฌ๊น์ง ๋ฐ์ฌ๋ฆผ์ ํ๊ธฐ์ ๊ฐ์ด ๋ฌ๋ผ์ test๊ฐ ์คํจํ๋ค.
๋ฐ๋ผ์ ์๋์ ๊ฐ์ด ์ด ๋จ์๋ก ์๋ผ์ ๊ฐ์ ๋น๊ตํ๋๋ก ์์ ํ๋ค.
assertThat(updatedInventory.getUpdatedAt()
.truncatedTo(ChronoUnit.SECONDS))
.isEqualTo(oldUpdatedAt.truncatedTo(ChronoUnit.SECONDS));
์ฐธ๊ณ
https://frogand.tistory.com/174
https://velog.io/@sdsd0908/JPA-%EB%B2%8C%ED%81%AC-%EC%BF%BC%EB%A6%AC%EC%97%B0%EC%82%B0-%EC%82%AC%EC%9A%A9%EB%B2%95-em.flush
'๐ฟ Spring > JPA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
JpaRepository์ save(S entity) ๋์ ์๋ฆฌ (1) | 2024.12.17 |
---|