DevLog ๐ถ
[Spring] @SpringBootTest์์ ์ง์ฐ ๋ก๋ฉ ์ฌ์ฉํ๊ธฐ - no Session ๋ฐฉ์งํ๊ธฐ ๋ณธ๋ฌธ
[Spring] @SpringBootTest์์ ์ง์ฐ ๋ก๋ฉ ์ฌ์ฉํ๊ธฐ - no Session ๋ฐฉ์งํ๊ธฐ
dolmeng2 2023. 7. 30. 14:23๐ฑ ๋ค์ด๊ฐ๊ธฐ ์
์ฐ๋ฆฌ ํ์ ๊ฒฝ์ฐ E2E ํ ์คํธ ํ๊ฒฝ์ ๊ตฌ์ถํ๊ธฐ ์ํด์ @SpringBootTest๋ฅผ ํตํด ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์๋๋ฐ, ์ฐ๋ฆฌ ํ์ ํ์๋ถ์ด ์๋์ ๊ฐ์ ์ค๋ฅ๋ฅผ ๋ง๋๊ฒ ๋์๋ค.
failed to lazily initialize a collection of role: co.kirikiri.domain.roadmap.RoadmapContent.nodes.values: could not initialize proxy - no Session
์ํฉ์ ์๋์ ๊ฐ๋ค. (์ถํ ์ฝ๋๋ก ๋ ์ ์ดํด๋ณผ ์์ ์ด๋ค.)
A๋ผ๋ ์์ฑ API์ B๋ผ๋ ์กฐํ API๊ฐ ์์ ๋, ํ ๋ด์์ ๊ธฐ๋ฅ์ ์ธ๋ถํํ ๋ค์ ๊ฐ์ ๊ฐ๋ฐ์ ์งํํ๋ค ๋ณด๋ B๋ฅผ ๊ฐ๋ฐํ๋ ์์ ์ A๋ผ๋ API๊ฐ ์์ด, ํตํฉ ํ ์คํธ ๋ repository๋ฅผ ์์กดํ์ฌ ์ง์ save๋ฅผ ํ๊ฒ ๋์๋ค.
ํ์ง๋ง, save ํ ๋ฐํ๋ ์ํฐํฐ์ ๊ฐ์ฒด๋ฅผ ์กฐํํ ๋ ์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํ๋ค ๋ณด๋ ํธ๋์ญ์ ์ด ํ์ํ๊ฒ ๋์๋๋ฐ, ํ ์คํธ ๋ฉ์๋์์๋ repository๋ฅผ ํธ์ถํ๋ ์์ ์์๋ง ํธ๋์ญ์ ์ด ๊ฑธ๋ฆฌ๊ณ , ํด๋น ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ์์ ์๋ ํธ๋์ญ์ ์ด ์์ด ์์์ฑ ์ปจํ ์คํธ๊ฐ ์๋ ์ํ๊ฐ ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๋ค ๋ณด๋ ์ธ์ ์ ๋ณด๊ฐ ์์ด ์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํ ์ ์๋ค๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฒ ๋์๋ค.
๋น์ ์ด ์ค๋ฅ๋ฅผ ์ฒ์ ์ ํ์ ๋๋ ํ ์คํธ ๋ฉ์๋์ ํธ๋์ญ์ ์ด ์์ด์ ๋ฐ์ํ ๋ฌธ์ ๋๊น, A api๊ฐ merge ๋ ์ดํ์ ์์ ํ์ ๋ ์ถฉ๋ถํ์ค ๊ฒ ๊ฐ์์! ๊ฐ์ ๋ต์ ๋๋ ธ์ง๋ง, ๊ณฐ๊ณฐ์ด ์๊ฐํด๋ณด๋ ์ด๊ฒ ๊ณผ์ฐ ์ณ์ ํด๋ต์ธ๊ฐ? ๋ผ๋ ์๊ฐ์ด ๋ค์๋ค. (๋ฌผ๋ก ์ง๊ธ ์ฐ๋ฆฌ ํ ์ํฉ์์๋ ๋น ๋ฅด๊ฒ API ๊ฐ๋ฐ์ ๋๋ด์ผ ํ๋๊น, ์ง๊ธ ์์ฑํด๋ดค์ ์ด์ฐจํผ ๋ฆฌํฉํฐ๋ง์ ํด์ผ ํ๋ ์ด๋ ๊ฒ ๋์ด๊ฐ ๊ฒ๋ ์๋ค ๐ฅฒ)
์์ผ๋ก๋ ์ด๋ฐ ํ ์คํธ ์ํฉ์ด ์๊ธธ ํ ๋ฐ, ๊ทธ๋ด ๋๋ง๋ค ์ด๋ด ์๋ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฒ์ ๊ณต๋ถํ๋ฉด์ ์๋ก์ด ๋ฐฉ๋ฒ์ ์ ์ฉํ๊ฒ ๋์๋ค.
๐ฑ ์ํฐํฐ ์๊ฐ
ํ ๋ฌธ์ ์ํฉ์์ ํ์ํ ๊ฐ๋จํ ์ํฐํฐ ๋ค์ด์ด๊ทธ๋จ์ด๋ค.
๋ก๋๋งต (Roadmap)์ ๋ก๋๋งต ๋ด์ฉ๋ค(RoadmapContents)์ ๋ํด์ 1:N ๊ด๊ณ๋ฅผ ๋งบ๊ณ ์์ผ๋ฉฐ, ๊ฐ ๋ก๋๋งต ๋ด์ฉ(RoadmapContent)์ ๋ก๋๋งต ๋ ธ๋๋ค(RoadmapNodes)์ ๋ํด์ ๋ ๋ค์ 1:N ๊ด๊ณ๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
์ด๋ฅผ ์ฝ๋๋ก ๋ดค์ ๋๋ ๋๋ต์ ์ผ๋ก ์๋์ ๊ฐ์ด ๊ตฌ์ฑ๋๋ค.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Roadmap extends BaseEntity {
...
@Embedded
private RoadmapContents contents = new RoadmapContents();
...
}
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class RoadmapContent extends BaseUpdatedTimeEntity {
...
@Embedded
private final RoadmapNodes nodes = new RoadmapNodes();
...
public RoadmapNodes getNodes() {
return nodes;
}
}
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RoadmapNodes {
@OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "roadmapContent")
private final List<RoadmapNode> values = new ArrayList<>();
...
public List<RoadmapNode> getValues() {
return new ArrayList<>(values);
}
}
์ฌ๊ธฐ์ ๋ด์ผํ๋ ์ ์, ๋ก๋๋งต(Roadmap) - ๋ก๋๋งต ๋ณธ๋ฌธ(RoadmapContent)๊ณผ ๋ก๋๋งต ๋ณธ๋ฌธ(RoadmapContent)๊ณผ ๋ก๋๋งต ๋ ธ๋(RoadmapNode) ๋ชจ๋๊ฐ LAZY ์ ๋ต์ ์ฌ์ฉํ๊ณ ์๋ค๋ ๊ฒ์ด๋ค.
๐ฑ ๋ฌธ์ ์ํฉ
์๋์ ํ ์คํธ ์ฝ๋๋ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)์ ํตํด์ ํตํฉ ํ ์คํธ ํ๊ฒฝ์ ๊ตฌ์ถํ ์ํ์์ ์งํ๋๋ค.
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues(); // Here!
...
}
private RoadmapContent ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(final Long ๋ก๋๋งต_์์ด๋) {
final Roadmap ๋ก๋๋งต = roadmapRepository.findById(๋ก๋๋งต_์์ด๋).get();
return roadmapContentRepository.findFirstByRoadmapOrderByCreatedAtDesc(๋ก๋๋งต).get();
}
์ฝ๋์ ์ฒซ ๋ผ์ธ์ ๋ณด๋ฉด ๋ก๋๋งต์_์์ฑํ๋ค(); ๋ฉ์๋๋ฅผ ํตํด ๋ก๋๋งต ์์ด๋๋ฅผ ๋ฐํ๋ฐ๊ณ ์๋ค. ์ฌ๊ธฐ์๋ ์ค์ API call์ ์งํํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์ค์ ๋ก ์์ฑ๋ ๋ก๋๋งต์ ์์ด๋๋ง์ ๋ฐํ๋ฐ๋๋ค. ๋ฐํ๋ฐ์ ์์ด๋๋ฅผ ๋ฐํ์ผ๋ก ๋ก๋๋งต ์ํฐํฐ๋ฅผ ์ป์ด์ค๊ธฐ ์ํด findById()๋ฅผ ํตํด ๋ก๋๋งต ์ํฐํฐ๋ฅผ ์กฐํํด์ค๋ฉฐ, ์ฌ๊ธฐ์ ๊ฐ์ฅ ์ต๊ทผ์ ์์ฑ๋ ๋ก๋๋งต ๋ณธ๋ฌธ ์ํฐํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด ํ ๋ฒ ๋ ์กฐํ๋ฅผ ์งํํ๋ค.
๐ก ๋ฌผ๋ก , ๋ก๋๋งต ์ํฐํฐ๋ก๋ถํฐ ๊ฐ์ฅ ์ต๊ทผ์ ๋ก๋๋งต ๋ณธ๋ฌธ์ ๋ฐ์์ค๋ ๋ฐฉ๋ฒ๋ ์๊ฒ ์ง๋ง, ํ ํ๋ก๋์ ์ฝ๋์์๋ ํด๋น ๋ถ๋ถ์ด ํ์ํ์ง ์์๊ณ , ๋ง์ฝ ๊ทธ๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ค๋ฉด ๋ณธ๋ฌธ์ ๊ฐ์ ธ์ค๋ ๊ณผ์ ์์๋ถํฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ ๊ฒ์ด๋ค.
๋ํ, ํ์ฌ ์์ฑํ ๋ถ๋ถ์ ์์ฑ API๊ฐ ์์ง ๋ง๋ค์ด์ง์ง ์์ ์ํ๋ก ์กฐํ API๋ฅผ ๋ง๋ค๋ค ๋ณด๋ ์์ฑ์ ์ํ ์ํฐํฐ๋ฅผ ๋ง๋๋ ค๊ณ '๋ก๋๋งต ๋ ธ๋๋ค' ์ํฐํฐ๋ฅผ ์กฐํํด์จ ์ฝ๋์ฌ์ ์ถํ ์์ฑ API๊ฐ ๋ง๋ค์ด์ง๋ค๋ฉด ์ ๊ฑฐ๋ ๋ถ๋ถ์ด๊ธด ํ๋ค!
๐ฌ cf. findFirst~() ๋ฉ์๋์์๋ ํธ๋์ญ์ ์ด ์ด๋จ์๊น?
public interface RoadmapContentRepository extends JpaRepository<RoadmapContent, Long> {
Optional<RoadmapContent> findFirstByRoadmapOrderByCreatedAtDesc(final Roadmap roadmap);
}
roadmapContentRepository์ ๊ฒฝ์ฐ ์์ ๊ฐ์ด JpaRepository๋ฅผ ์์๋ฐ๊ณ ์๋ค.
์ด๋, JpaRepository ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ธ SimpleJpaRepository ํด๋์ค๋ฅผ ๊ฐ๋ฉด ์์ ๊ฐ์ด @Transactional์ด ๋ถ์ด ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฉ์๋๊ฐ ํธ์ถํ๋ ์์ ์ ๋ํด์๋ง ํธ๋์ญ์ ์ด ๊ฑธ๋ ค์๊ฒ ๋๋ ๊ฒ์ด๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์คํ๋ง์์ ์์์ฑ ์ปจํ ์คํธ๋ ํธ๋์ญ์ ๊ณผ ์๋ช ์ฃผ๊ธฐ๊ฐ ๋์ผํ๊ธฐ ๋๋ฌธ์, roadmapContentRepository๋ก๋ถํฐ ์กฐํํด์จ RoadmapContent๋ ํธ๋์ญ์ ์ด ์ข ๋ฃ๋๋ฉด์ ์์์ฑ ์ปจํ ์คํธ์ ๊ด๋ฆฌ ๋ฒ์์์๋ ํจ๊ป ๋ฒ์ด๋๊ฒ ๋๋ค. ํ์ฌ ๋ก๋๋งต ๋ณธ๋ฌธ ์ํฐํฐ๋ ์ค์์ ์ํ๊ฐ ๋์์์ ๊ธฐ์ตํ์.
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues(); // Here!
๋ค์ ๋์์์, here! ์ด๋ผ๊ณ ํน์ ๋ ๋ถ๋ถ์ ๋ณด์. ํด๋น ๋ถ๋ถ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ์ง์ ์ด๋ค.
๋ก๋๋งต ๋ณธ๋ฌธ์ผ๋ก๋ถํฐ ๋ ธ๋์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ค๊ณ ํ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
public List<RoadmapNode> getValues() {
return new ArrayList<>(values);
}
๋ ธ๋์์ getValues() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด values()์ ๋ํ ์ ๊ทผ์ด ์ผ์ด๋๊ฒ ๋๋๋ฐ, ์ด๋ ์๋์ ๊ฐ์ ์ผ๋ค์ด ๋ฐ์ํ๋ค.
read() ๋ผ๋ ๋ฉ์๋๋ฅผ ๋ฐ๋ผ ๋ค์ด๊ฐ๋ค ๋ณด๋ฉด, ์ด๋ ํ ์ด๊ธฐํ ์์ ์ด ์ฒ์์ ๋ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ , ์ง์ฐ ๋ก๋ฉ ์์ ์ ์ธ์ ์ผ๋ก๋ถํฐ ์ปฌ๋ ์ ์ ๋ณด๋ฅผ ์ด๊ธฐํํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์์์ ์ฃผ์์ ์ ์ฝ์ด๋ณด๋ฉด, ์ด๊ธฐํ๊ฐ ๋ถ๊ฐ๋ฅํ ๋ LazyInitializationException์ด ๋ฐ์ํจ์ ๋ณผ ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ , ํด๋น ๋ฉ์๋๋ฅผ ํ๊ณ ๋ค์ด๊ฐ๋ฉด session ์ ๋ณด๊ฐ ์์ ๋ ์ธ๋ถ์ ํธ๋์ญ์ ์ ์ฌ์ฉํ ์ ์๋์ง ํ๋จํ๋๋ฐ, ์ฌ์ฉ ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ (allowLoadOutsideTransaction=false) ํ๋จ์ else ๊ตฌ๋ฌธ์ผ๋ก ์ ์ด๊ฐ ๋ด๋ ค๊ฐ๊ฒ ๋์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค.
์ฐธ๊ณ ๋ก, allowLoadOutsideTransaction์ ๊ฒฝ์ฐ ์ ์ผ ์ฒ์ ์ธ์ ์ ๋ณด๋ฅผ ์ธํ ํด์ค ๋ ์์ ๋ฉ์๋์์ ์ ์ฉ๋๋ค.
์ธ์ ์ ๋ณด๋ก๋ถํฐ ์ง์ฐ ๋ก๋ฉ์์๋ ํธ๋์ญ์ ์ ์ฌ์ฉํ ์ ์๋์ง ์ ๋ณด๋ฅผ ๋ฐ์์ค๋ ๊ฒ ๊ฐ๋ค.
๊ทธ๋ ๋ค๋ฉด ํธ๋์ญ์ ์ ๋ณด๊ฐ ์์ผ๋ฉด ๋๋ ๊ฒ ์๋๊น?
ํ ๋ฒ ์ฒ์ฒํ ํด๊ฒฐํด๋๊ฐ๋ณด์.
๐ฑ ๊ทธ๋ฅ @Transactional์ ๋ถ์ด๋ฉด ๋์ง ์๋?
@Test
@Transactional // ํธ๋์ญ์
!
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
...
}
private RoadmapCategory ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค(final String ์นดํ
๊ณ ๋ฆฌ_์ด๋ฆ) {
final RoadmapCategory ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ = new RoadmapCategory(์นดํ
๊ณ ๋ฆฌ_์ด๋ฆ);
return roadmapCategoryRepository.save(๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ);
}
ํธ๋์ญ์ ์ด ์์ด์ ์๊ธด ๋ฌธ์ ๋ผ๋ฉด, @Transactional์ ๋ถ์ด๋ฉด ๋๋ค๊ณ ์๊ฐํ ์ ์๋ค.
์ฌ๊ธฐ์ ์๊น ์์ ์ ๋ค๋ฅด๊ฒ ๋ก๋๋งต์ ๋ํ ์นดํ ๊ณ ๋ฆฌ ์์ฑ ๋ฉ์๋๊ฐ ์ถ๊ฐ๋ ๊ฒ์ ๋ณผ ์ ์๋๋ฐ, ์ค๋ฅ๊ฐ ๋ฐ์ํ ํฌ์ธํธ์ด๊ธฐ ๋๋ฌธ์ ๋ฃ์ด๋์๋ค. ์นดํ ๊ณ ๋ฆฌ์ ๊ฒฝ์ฐ ์์ฑํ๋ API๊ฐ ์๊ธฐ ๋๋ฌธ์ (๋์ค์ admin ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ฉด ๋ฃ์ ์์ ์ด์๋ค.) ์์ ๊ฐ์ด ์ง์ save๋ฅผ ํด์ค๋ค.
ํ์ง๋ง, ์ ๋ง ๋ฌ๊ธ์์ด NPE๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ด๋, ์๋ต๊ฐ์ด ์ ๋๋ก ๋ฐํ๋์ง ์์ผ๋ฉด์ response header ๊ฐ์ด ์ ๋๋ก ๋ด๋ ค์ค์ง ์์๊ธฐ ๋๋ฌธ์ด๋ค.
์ค์ ๋ก ํด๋น ๋ก๊ทธ์ ์๋จ์ผ๋ก ์ฌ๋ผ๊ฐ๋ณด๋ฉด, ์์ ๊ฐ์ด ์นดํ ๊ณ ๋ฆฌ์ ๋ํ Exception์ด ๋ฐ์ํ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ด๋, ๋ก๋๋งต ์์ฑ API๊ฐ ํธ์ถ๋๋ฉด์ create ํ๋ ๊ณผ์ ์ request body ๊ฐ์ผ๋ก ๋ฐ์ ๋ก๋๋งต ์นดํ ๊ณ ๋ฆฌ ์์ด๋์ ๋ํ ์ ํจ์ฑ์ ๊ฒ์ฆํ๊ฒ ๋๊ณ , ํด๋น ๋ก์ง์์ ์นดํ ๊ณ ๋ฆฌ ์ ๋ณด๊ฐ ์กด์ฌํ์ง ์์ ๋ฐ์ํ ์ค๋ฅ์ด๋ค.
๋ถ๋ช ์์์ ๋ก๋๋งต_์นดํ ๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค() ๋ฉ์๋๋ฅผ ํตํด ์ ์ฅ์ ํ๋๋ฐ, ์ด๊ฒ ์ด๋ป๊ฒ ๋ ์ผ์ผ๊น?
์ด๋, @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) ๋๋ฌธ์ด๋ค.
์ด์ ์ ํ ์คํธ ์ปจํ ์คํธ์ ๋ํ ๊ฒ์๊ธ์ ์์ฑํ ์ ์ด ์๋๋ฐ, ๊ทธ๋ random_port ์ต์ ์ ๋ํด ์ด์ ๊ฐ์ด ์ปค๋ฉํธ๋ฅผ ๋จ๊ฒผ๋ค.
random_port ์ต์ ์ ์ง์ ํ๊ฒ ๋๋ฉด ๋ณ๊ฐ์ ์ค๋ ๋์์ ์ปจํ ์ด๋๊ฐ ์คํ๋๋ค๋ ๊ฒ์ด๋ค.
= ์ฆ, ํ๋ก๋์ ์ฝ๋์์ ์์ฑํ ๋ฉ์๋์ ์ค๋ ๋์ @SpringBootTest์ ๋ฉ์๋์ ์ค๋ ๋๊ฐ ๋ค๋ฅด๋ค๋ ์๋ฏธ์ด๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก @Transactional์ด ๋ถ๊ฒ ๋๋ฉด ์์ ์ค๋ ๋๋ ์ปค๋ฅ์ ํ์์ Connection ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์์ ์ฌ์ฉํ๊ฒ ๋๋๋ฐ, ์ค๋ ๋๊ฐ ๋ฌ๋ผ์ง๊ฒ ๋๋ฉด ์ฌ์ฉํ๊ฒ ๋๋ Connection์ด ๋ฌ๋ผ์ง๊ฒ ๋๋ค. ์ฆ, ํธ๋์ญ์ ์ด ์์ ๋ฌ๋ผ์ง๊ฒ ๋๋ ๊ฒ์ด๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํ๋ก๋์ ์ฝ๋๊ฐ ์คํ๋๋ ๋ก๋๋งต ์์ฑ API์ ์ค๋ ๋ ์ ์ฅ์ผ๋ก์๋ ํ ์คํธ ๋ฉ์๋์ธ ๋ก๋๋งต_์นดํ ๊ณ ๋ฆฌ๋ฅผ_์์ฑํ๋ค() ์ค๋ ๋๊ฐ ํ๋ ์ผ์ ์ธ์ํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ์นดํ ๊ณ ๋ฆฌ์ ๋ํ ์ ๋ณด๋ฅผ ๋ฐ์์ฌ ์ ์๋ ๊ฒ์ด๋ค.
์ฌ๊ธฐ์ ํ ๊ฐ์ง ์ฝ์ง์ ํ๋๋ฐ, ์ฝ์์์ผ๋ก๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ธธ๋ ๊ณ์ insert๊ฐ ๋๋ค๊ณ ์๊ฐํ์๋ค. (IDENTITY ์ ๋ต์ผ๋ก ์ธํด)
ํ์ง๋ง, ์ค์ ๋ก DB์ ๊ฐ์ ํ์ธํด๋ณด๋ ๊ฒฐ๊ณผ๊ฐ ์ ์ฅ๋์ง ์์์๊ณ , ์๋ง ์ฟผ๋ฆฌ๋ง ๋ฐ์ํ ๊ฒ ๊ฐ๋ค๊ณ ์ถ์ธก๋๋ค.
(ํธ๋์ญ์ ์์ ํ ์ปค๋ฐ์ด ๋์ง ์์ ์ํ๋ผ๊ณ ๋ณด๋ ๊ฒ ๋ ์ ํํ ๊ฒ ๊ฐ๋ค.)
@Transactional์ ์ ๊ฑฐํ๋ฉด ์์ ๊ฐ์ด ๋ ์ฝ๋๊ฐ ์ ์ฅ๋๋ค. (ํธ๋์ญ์ ์ด ์์ผ๋ ๋ฐ๋ก DB์ ์ ์ฅ)
๊ทธ๋ฌ๋ฉด ์ด ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํด์ผ ํ ๊น?
RANDOM_PORT๋ฅผ ์ฌ์ฉํ์ง ์๊ฒ ๋๋ฉด E2E ํ ์คํธ์ ์๋ฏธ๊ฐ ์๊ณ , API call๋ก ๋์ฒดํ ์ ์๋ ์ํฉ์ด๋ค.
๐ฑ ์ง์ฐ ๋ก๋ฉ์ด ํ์ํ ์์ ์์๋ง ํธ๋์ญ์ ์ ์์ฑํ๊ธฐ - ๋ฉ์๋ ๋ถ๋ฆฌํ๊ธฐ
์ฐ๋ฆฌ๊ฐ ๋ง๋ฌ๋ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ๋, ์ง์ฐ ๋ก๋ฉ์ ํ๋ ์์ ์ ํธ๋์ญ์ ์ด ์กด์ฌํ์ง ์์ ์กฐํ๋ฅผ ํด์ฌ ์ ์์๋ ๊ฒ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด ์กฐํํ๋ ์์ ์ ์๋ก์ด ํธ๋์ญ์ ์ ์์ฑํด์ฃผ๋ ๊ฑด์ ์ด๋จ๊น?
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = ๋ก๋๋งต_๋
ธ๋๋ค์_๋ฐํํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ, ๋
ธ๋1, ๋
ธ๋2);
...
}
@Transactional // ํธ๋์ญ์
!
public List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค์_๋ฐํํ๋ค(final String ์ก์ธ์ค_ํ ํฐ, final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ, final RoadmapNodeSaveRequest ๋
ธ๋1,
final RoadmapNodeSaveRequest ๋
ธ๋2) {
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
return ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
}
ํ์ง๋ง, ์ ์ฝ๋๋ ๋์ํ์ง ์๋๋ค. ์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก @Transactional์ Spring AOP๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ๋๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๋ ํฌ๊ฒ 2๊ฐ์ง์ ํน์ง์ด ์กด์ฌํ๋ค.
1. ํ๊ฒ ํด๋์ค๋ฅผ ์์ํ์ฌ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ์์ ์์ฒด๊ฐ ๋ถ๊ฐ๋ฅํ private ๋ฉ์๋์ ๋ํด์๋ ์ ์ฉ ๋ถ๊ฐ
2. ๋์ผํ ํด๋์ค์ ๋ด๋ถ ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉด ํ๋ก์๋ฅผ ํธ์ถํ์ง ์๊ณ ๋์ ๊ฐ์ฒด๋ฅผ ์ง์ ํธ์ถํ๊ฒ ๋์ด ์ ์ฉ ๋ถ๊ฐ
์ ์ฝ๋์์๋ 2๋ฒ์ ๊ฒฝ์ฐ, ๋ด๋ถ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํธ๋์ญ์ ์ ์ ์ฉํ ์ ์๋ ๊ฒ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด, ๋ด๋ถ ๋ฉ์๋ ๋์ ์ ์ธ๋ถ ํด๋์ค๋ฅผ ํ์ฉํ๋ฉด ๋์ง ์์๊น?
๐ฑ ์ง์ฐ ๋ก๋ฉ์ด ํ์ํ ์์ ์์๋ง ํธ๋์ญ์ ์ ์์ฑํ๊ธฐ - ํด๋์ค ๋ถ๋ฆฌํ๊ธฐ
@Component
@TestConstructor(autowireMode = AutowireMode.ALL)
public class RoadmapTestHelper {
private final RoadmapRepository roadmapRepository;
private final RoadmapContentRepository roadmapContentRepository;
public RoadmapTestHelper(final RoadmapRepository roadmapRepository,
final RoadmapContentRepository roadmapContentRepository) {
this.roadmapRepository = roadmapRepository;
this.roadmapContentRepository = roadmapContentRepository;
}
@Transactional(readOnly = true)
public List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค์_์กฐํํ๋ค(final Long ๋ก๋๋งต_์์ด๋) {
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
return ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
}
private RoadmapContent ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(final Long ๋ก๋๋งต_์์ด๋) {
final Roadmap ๋ก๋๋งต = roadmapRepository.findById(๋ก๋๋งต_์์ด๋).get();
return roadmapContentRepository.findFirstByRoadmapOrderByCreatedAtDesc(๋ก๋๋งต).get();
}
}
๋ก๋๋งต ๋ ธ๋๋ฅผ ์กฐํํ๊ธฐ ์ํ ๋ณ๋์ Helper ํด๋์ค๋ฅผ ์์ฑํด์ค๋ค.
๋ก๋๋งต_๋ณธ๋ฌธ ์ํฐํฐ๊ฐ detached ์ํ๊ฐ ๋์ง ์๋๋ก ์กฐํ๋ฅผ ํด์ค๋ ์์ ์ ๋ฉ์๋๋ถํฐ helper ํด๋์ค์ ๋์์ผ๋ฉฐ, ๋ณธ๋ฌธ์ผ๋ก๋ถํฐ ๋ ธ๋ ์ ๋ณด๋ฅผ ์ง์ฐ๋ก๋ฉ์ ํตํด ๊ฐ์ ธ์์ ๋ฐํํ๋๋ก ๋ง๋ค์๋ค.
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
// Here!
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = roadmapTestHelper.๋ก๋๋งต_๋
ธ๋๋ค์_์กฐํํ๋ค(๋ก๋๋งต_์์ด๋);
...
}
๊ทธ๋ฆฌ๊ณ ๊ธฐ์กด์ ํ ์คํธ ์ฝ๋์์ helper ํด๋์ค๋ฅผ ํตํด์ ๋ ธ๋๋ฅผ ์กฐํํด์๋ค.
๊ทธ๋ฌ๋ฉด ์์ ๊ฐ์ด ํ ์คํธ ์ฝ๋๊ฐ ์ฑ๊ณตํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค!
ํ์ง๋ง, ์ ๋ฐฉ๋ฒ์ ๋งค์ฐ ์ฐ์ฐํ๋ค. ์ง์ฐ๋ก๋ฉ์ด ํ์ํ ์ฝ๋๋ง๋ค ์ด๋ ๊ฒ ํ ์คํธ ํด๋์ค๋ก ๋ถ๋ฆฌํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ง์ฝ ํ ์คํธํ๋ ์ผ์ด์ค๊ฐ ์ ๋ง ๋ง์์ง๋ค๋ฉด ๊ทธ๋ด ๋๋ง๋ค helper ํด๋์ค์ ๋ฉ์ค๋๊ฐ ์์ฒญ๋๊ฒ ๋์ด๋๊ฒ ๋ ๊ฒ์ด๋ค.
์ด๋ ์ ํ ๊ฐ๋ฐ์๋ต์ง ๋ชปํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๋ชจ์ํ ํ์๊ฐ ์๋ค.
๐ฑ ์ง์ฐ ๋ก๋ฉ์ด ํ์ํ ์์ ์์๋ง ํธ๋์ญ์ ์ ์์ฑํ๊ธฐ - ํจ์ํ ์ธํฐํ์ด์ค ํ์ฉํ๊ธฐ
ํ์ํ ๋๋ง๋ค helper ํด๋์ค์ ๋ฉ์๋๋ก ๋ถ๋ฆฌํ๋ ๊ฒ ์๋๋ผ, ํธ๋์ญ์ ์ด ํ์ํ ๋ก์ง์ ๋ํด์๋ง ์ธ๋ถ ํด๋์ค์์ ์คํ๋๋๋ก ๋ง๋ค ์๋ ์์๊น? ์ฆ, ๋ฉ์๋๋ฅผ ์ธ์๋ก ๋๊ฒจ์ ์ด๋ ํ ๊ณณ์์ ์ฒ๋ฆฌํ๊ณ ์ถ์ ๊ฒ์ด๋ค.
์๋ฐ์์๋ ๋ฉ์๋๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๊ธฐ ์ํด์ ๋๋ค์์ ํ์ฉํ ์ ์๋ค.
์ฐ๋ฆฌ๋ ์ธ์๋ก ๋๊ธด ๊ฐ์ผ๋ก List<RoadmapNode>๋ฅผ ๋ฐ์์ผ ํ๊ธฐ ๋๋ฌธ์ T๋ฅผ ๋ฐํํ๋ ์๊ทธ๋์ฒ๋ฅผ ๊ฐ์ง ํจ์ํ ์ธํฐํ์ด์ค๋ฅผ ํ๋ ์ค์ ํ ์ ์๋ค.
@FunctionalInterface
public interface TransactionalTask<T> {
T execute();
}
๐ก ํจ์ํ ์ธํฐํ์ด์ค๋?
์ค์ง 1๊ฐ์ ์ถ์ ๋ฉ์๋๋ฅผ ๊ฐ์ง๋ ์ธํฐํ์ด์ค.
์ฌ๊ธฐ์ ์ถ์ ๋ฉ์๋๋, ์์ ํด๋์ค์์ ๋ฐ๋์ ์ค๋ฒ๋ผ์ด๋ฉ ํด์ผ ์ฌ์ฉํ ์ ์๋ ๋ฉ์๋์ด๋ค.
๊ทธ๋ฆฌ๊ณ , ํธ๋์ญ์ ์ด ํ์ํ ์์ ์ ๋์์ค๋ค๋ ์๋ฏธ๋ก ํ๋์ ํฌํผ ํด๋์ค๋ฅผ ์์ฑํ๋๋ก ํ์.
@Component
public class TransactionHelper {
@Transactional(readOnly = true)
public <T> T getResult(final TransactionalTask<T> task) {
return task.execute();
}
}
ํฌํผ ํด๋์ค์์๋ TransactionTask ํ์ ์ ์ด๋ ํ ๋๋ค์์ ๋ฐ์์, ํด๋น ๋๋ค์์ @Transactional์ด ๊ฑธ๋ฆฐ ์ํ๋ก ์คํํด์ฃผ๋ ์ญํ ์ ์งํํ๋ค. ์ด์ ์ด ํฌํผ ํด๋์ค๋ฅผ ํ์ฉํ๊ฒ ๋๋ฉด ์๋์ ๊ฐ์ด ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = transactionHelper.getResult(new TransactionalTask<List<RoadmapNode>>() {
@Override
public List<RoadmapNode> execute() {
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
return ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
}
});
...
}
transactionHelper ํด๋์ค์ getResult() ๋ฉ์๋์ ์ธ์๋ก ํจ์ํ ์ธํฐํ์ด์ค์ ์ถ์ ๋ฉ์๋์ธ execute()์ ์ต๋ช ํด๋์ค๊ฐ ๋ค์ด๊ฐ๋ค.
๊ทธ๋ฆฌ๊ณ , ํด๋น ๊ตฌํ์ฒด์ ๋ก๋๋งต ๋ณธ๋ฌธ์ ์กฐํํ์ฌ ๋ก๋๋งต ๋ ธ๋๋ฅผ ๋ฐํํ๋ ๋ก์ง์ ๋ฃ์๋ค.
๐ก ์ต๋ช ํด๋์ค
ํด๋์ค์ ์ ์ธ๊ณผ ์ธ์คํด์คํ๋ฅผ ๋์์ ์งํํ ์ ์๋ ํด๋์ค๋ก, ์ด๋ฆ์ด ์๋ ํด๋์ค๋ผ ์ต๋ช ํด๋์ค๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ด๋, ์ต๋ช ํด๋์ค์ ๊ฒฝ์ฐ ๋๋ค๋ก ์ถ์ฝํ ์ ์๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ๋ ๊ฐ๊ฒฐํ๊ฒ ๋ฆฌํฉํฐ๋ง์ ์งํํ ์ ์๋ค.
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = transactionHelper.getResult(() -> {
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
return ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
});
...
}
์ต์ข ์ ์ผ๋ก ์คํํด๋ณด๋ฉด ์์ ๊ฐ์ด ํ ์คํธ ์ฝ๋๋ ์ ์คํ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค! ๐
์ด์ ํ ์คํธ ์ฝ๋์์ ์ง์ฐ๋ก๋ฉ์ด ํ์ํ ๋ถ๋ถ์ ๋ํด (then์ ๊ฐ์ด ์๋ต๊ฐ์ ๊บผ๋ด์ด ๊ฒ์ฆํ ๋) ์ด๋ฅผ ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
์ค๋๋ง์ ํจ์ํ ์ธํฐํ์ด์ค ๊ฐ์ ๊ฐ๋ ์ ๋ณด๋ค ๋ณด๋๊น ํท๊ฐ๋ ธ๋๋ฐ, ์๋ฌด์ชผ๋ก ์ ํด๊ฒฐํ ์ ์์ด์ ๋คํ์ด๋ค.
๊ฝค ์๋ฏธ์๋ ํธ๋ฌ๋ธ ์ํ ์ ํ ๊ฒ ๊ฐ์์ ์ฌ๋ฐ์๋ค ๐
+ ์ถ๊ฐ
๊ฐ๋ง๋ ์ ์๋๊ป์ ๋ ์ข์ ๋ฐฉ๋ฒ์ ์ ์ํด์ฃผ์ ์ ๊ณต์ !
@Autowird
private TransactionTemplate transactionTemplate;
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = transactionTemplate.execute(new TransactionCallback<List<RoadmapNode>>() {
@Override
public List<RoadmapNode> doInTransaction(final TransactionStatus status) {
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
return ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
}
});
...
}
์คํ๋ง์์๋ ์ด๋ฏธ TransactionTemplate ์ด๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ํตํด์ ์ ๊ณต์ ํด์ฃผ๊ณ ์์๋ค... ใ ใ (๊ทธ๋ฅ ํผ์ ๊ตฌํํ ์ฌ๋ ๋จ)
์์ ๊ฐ์ด ๋ฐํ๊ฐ์ด ํ์ํ ๊ฒฝ์ฐ๋ผ๋ฉด TransactionCallback์ ์ธ์๋ก ๋ฐ์ผ๋ฉด ๋๋ค.
๋๋ค๋ฅผ ํ์ฉํ๋ฉด ๋ ์ถ์ฝ์ด ๊ฐ๋ฅํ๋ค.
@Autowird
private TransactionTemplate transactionTemplate;
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
final List<RoadmapNode> ๋ก๋๋งต_๋
ธ๋๋ค = transactionTemplate.execute(status -> {
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
return ๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
});
...
}
์ง๊ธ ๋ณด๋ฉด ์ง์ ๋ง๋ transactionHelper์ ์์ ๋์ผํ๋ค๊ณ ๋ด๋ ๋ฌด๋ฐฉํ๋ค. ๊ทธ๋ฅ ์๋ ๊ฒ ์ฌ์ฉํ๋ ๊ฒ ๋ ์ข์ ๊ฒ ๊ฐ๋ค.
๋ง์ฝ, ๋ฐํ๊ฐ์ด ํ์์๋ค๋ฉด TransactionCallbackWithoutResult๋ฅผ ์ธ์๋ก ๋ฐ์ผ๋ฉด ๋๋ค.
@Autowird
private TransactionTemplate transactionTemplate;
@Test
void ํ
์คํธ() throws JsonProcessingException {
...
final RoadmapCategory ์นดํ
๊ณ ๋ฆฌ = ๋ก๋๋งต_์นดํ
๊ณ ๋ฆฌ๋ฅผ_์ ์ฅํ๋ค("์ฌํ");
final Long ๋ก๋๋งต_์์ด๋ = ๋ก๋๋งต์_์์ฑํ๋ค(์ก์ธ์ค_ํ ํฐ, ์นดํ
๊ณ ๋ฆฌ.getId(), "๋ก๋๋งต ์ ๋ชฉ", "๋ก๋๋งต ์๊ฐ๊ธ", "๋ก๋๋งต ๋ณธ๋ฌธ",
RoadmapDifficultyType.DIFFICULT, 30, List.of(๋
ธ๋1, ๋
ธ๋2));
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(final TransactionStatus status) {
final RoadmapContent ๋ก๋๋งต_๋ณธ๋ฌธ = ๋ก๋๋งต์ผ๋ก๋ถํฐ_๋ณธ๋ฌธ์_๊ฐ์ ธ์จ๋ค(๋ก๋๋งต_์์ด๋);
๋ก๋๋งต_๋ณธ๋ฌธ.getNodes().getValues();
}
});
...
}
์ข์ ๋ฐฉ๋ฒ ๊ณต์ ํด์ค ๋ง๋ ์ ์๋ ๊ณ ๋ง์์ ๐โ๏ธ