์ฝ๋
์ ์ฒด ์ฝ๋๋ Github์ ์์ต๋๋ค :)
- TeamServiceTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TeamServiceTest {
@Autowired
private TeamService teamService;
@Before
public void setup() {
Team team = new Team(null, "team", new ArrayList<>());
for (int i = 1; i <= 3; i++) {
Member member = new Member(null, "member" + i, null);
member.updateTeam(team);
}
teamService.save(team);
}
@Test
public void lazy_exception() {
List<Team> teams = teamService.findAll();
}
}
- TeamService
@Service
public class TeamService {
@Autowired
private TeamRepository teamRepository;
public Team save(Team team) {
return teamRepository.save(team);
}
public List<Team> findAll() {
List<Team> teams = teamRepository.findAll();
extractMembers(teams); // Exception
return teams;
}
private void extractMembers(List<Team> teams) {
// LazyInitializationException!
teams.forEach(t -> t.getMembers().get(0).getName());
}
}
- TeamRepository
@Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
์ํฉ
- Serivce๋จ์์ Team์ ์กฐํํ๋๋ฐ Team ๋ด๋ถ์ MemberList๋ ์ง์ฐ๋ก๋ฉ์ผ๋ก ์กฐํ๋ฅผ ํ ์ํ์ด๋ค.
- Team ๋ด๋ถ์ ์๋ Member์ ์ ๊ทผ์ ํ๋ คํ๋
LazyInitialiazationException
์ด ๋ฐ์ํ๋ฉด์, ์ง์ฐ๋ก๋ฉ์ ํ ์ ์๋ค๊ณ ์๋ฌ๊ฐ ๋จ๋ ๊ฒ์ด๋ค.
์ฐฉ๊ฐ
์ด์ ์๋ Service์์ Repository.findAll()
์ ํธ์ถํ๋ Service์์๋ ์์์ฑ ์ปจํ
์คํธ๊ฐ ์ด์์์ ๊ฒ์ด๋ผ๊ณ ์ฐฉ๊ฐํ์๋ค.
Team์ ๋ฉค๋ฒ ๋ณ์๋ค์ ์ ๊ทผ์ด ๊ฐ๋ฅํ์ง๋ง, Member์ ์ ๊ทผํ๋ คํ๋ Proxy๋ก ๊ฐ์ ธ์จ Member๋ฅผ ์ด๊ธฐํ ํ ์ ์๋ค๋ ๊ฒ์ด์๋ค.
์๊ทธ๋ด๊น?
์ ๋ต์ ์์์ฑ ์ปจํ ์คํธ์ ๋ฒ์์ ์๋ค. Spring์์๋ ์์์ฑ ์ปจํ ์คํธ์ ๋ผ์ดํ์ฌ์ดํด์ด ํธ๋์ญ์ ๊ณผ ๋์ผํ๊ฒ ์ ์ง๋๋๋ฐ, ํ์ฌ๋ findAll() ์์ ํธ๋์ญ์ ์ด ์์๋์๋ค๊ฐ ๋ฐํ์ด ๋๋ฉด์ ์ข ๋ฃ๊ฐ ๋์๊ธฐ ๋๋ฌธ์ ์กฐํํด์จ Team์ด ์ค์์(Detached) ์ํ๊ฐ ๋๋ฒ๋ฆฐ ๊ฒ์ด๋ค.
์ฌ๊ธฐ์ ๋ค์๋ ์๋ฌธ์ ํธ๋์ญ์ ์ด ์ด๋์ ์๊ฒจ์ ์ง ํผ์ ๋ซํ๋ ๊ฒ์ธ๊ฐ.. ์ด๋ค.
TeamRepository๋ฅผ ๋ง๋ค ๋ ์์ํ ์ธํฐํ์ด์ค๋ JpaRepository๋ฅผ ์ฌ์ฉํ๋ค.
JpaRepository์ ๊ธฐ๋ณธ ๊ตฌํ์ฒด์ธ SimpleRepository๋ฅผ ์ดํด๋ณด์.
SimpleJpaRepository (Spring Data JPA 2.6.3 API)
@Repository
@Transactional(readOnly=true)
public class SimpleRepository<T, ID>
ํด๋์ค ๋ ๋ฒจ์ @Transactional
์ด ๋ถ์ด์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
@Transactional์ ๋ํดํธ ์ต์
์ Propagation.REQUIRED
์ด๋ค.
์ด์ ์ ์์ฑ๋ ํธ๋์ญ์
์ด ์๋ค๋ฉด ์ฐธ์ฌํ๊ณ , ์๋ค๋ฉด ์๋ก ํธ๋์ญ์
์ ์์ํ๊ฒ ๋๋ค.
SimpleJpaRepository.findAll()
๋ฉ์๋ ์ด๋ฆ์ผ๋ก ํธ๋์ญ์
์ด ์์ฑ์ด ๋๊ณ , TeamList๋ฅผ ๋ฐํํ๊ณ ํธ๋์ญ์
์ ์๋ฃ์ฒ๋ฆฌํ๋ ๊ฒ์ด๋ค.
Team์ ์์์ฑ ์ปจํ
์คํธ์ ์ํด ๊ด๋ฆฌ๋ฅผ ๋ฐ์ง ์์ผ๋, ์ค์์ ์ํ๊ฐ ๋๋ฒ๋ฆฌ๊ณ Service๋จ์์ Team.memberList
์ ์ ๊ทผํ๋ คํ๋ LazyInitializationException
์ด ๋ฐ์ํ๋ ๊ฒ์ด๋ค.
์๋์์ ์ค์ ๋ก๊ทธ๋ฅผ ์ดํด๋ณด๋๋ก ํ์.
๋ก๊ทธ๋ฅผ ์ดํด๋ณด์
๊ด๋ จ๋ ๋ก๊ทธ๋ง ๊ฐ์ถ๋ ค๋ดค์ต๋๋ค.
o.s.orm.jpa.JpaTransactionManager <1>: Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
o.s.orm.jpa.JpaTransactionManager : Opened new. EntityManager [SessionImpl(220766773<open>)] for JPA transaction
o.h.e.t.internal.TransactionImpl : begin
o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
... <2> select ์กฐํ ...
o.s.t.i.TransactionInterceptor <3>: Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
o.s.orm.jpa.JpaTransactionManager <4>: Closing JPA EntityManager [SessionImpl(220766773<open>)] after transaction
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: me.iseunghan.testjpa.domain.Team.members, could not initialize proxy - no Session
- <1> :
SimpleJpaRepository.findAll
๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์๋ก์ด ํธ๋์ญ์ ์ ์์ฑ - <2> : select ์กฐํ๋ฅผ ํฉ๋๋ค.
- <3> :
SimpleJpaRepository.findAll
ํธ๋์ญ์ ์ ์๋ฃ์ฒ๋ฆฌ ํฉ๋๋ค. - <4> : ์์์ฑ ์ปจํ ์คํธ๊ฐ ๋ซํ๊ฒ ๋ฉ๋๋ค.
์์์ฑ ์ปจํ ์คํธ์ ๋ผ์ดํ ์ฌ์ดํด์ ํธ๋์ญ์ ์ ๋ผ์ดํ ์ฌ์ดํด๊ณผ ๋์ผํ๋ค๊ณ ํ์ต๋๋ค.
์ง์ฐ ๋ก๋ฉ์ ํ๊ธฐ ์ํด์๋ ์์์ฑ ์ปจํ ์คํธ์ ์ํด ๊ด๋ฆฌ๊ฐ ๋๊ณ ์์ด์ผ ํฉ๋๋ค. ํ์ง๋ง <3> ํธ๋์ญ์ ์ด ์๋ฃ ์ฒ๋ฆฌ๊ฐ ๋๋ฉด์ Team์ ์ค์์ ์ํ๊ฐ ๋๋ฉด์ Member๋ฅผ ์ด๊ธฐํ ํ๋ ค๋ค ์คํจํ๊ฒ ๋ ๊ฒ์ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
- 1 TeamSerivce.findAll @Transactional ๋ถ์ด๊ธฐ
TeamService.findAll()
์์๋ถํฐ ํธ๋์ญ์ ์ ์์ฑํ์ฌ ์ ์ด(์ ํ)๋๋๋ก ํ๋ ๊ฒ์ด๋ค.- ๊ทธ๋ ๋ค๋ฉด,
SimpleRepository
์์@Transactonal
์ ๊ธฐ๋ณธ ์์ฑ์ผ๋ก ์ธํด ํธ๋์ญ์ ์ ์ฐธ์ฌํ๊ฒ ๋๋ค.
- 2 Join Fetch ์ฌ์ฉ
@Query(value = "select t from Team t join fetch t.members")
List<Team> findAllJoinFetch();
@EntityGraph(attributePaths = "members")
@Query("select t from Team t")
List<Team> findAllEntityGraph();
4. ๊ธ๋ก๋ฒ ํ์น ์ ๋ต์ EAGER๋ก ๋ณ๊ฒฝ(์ฌ๋งํ๋ฉด ์ฌ์ฉํ์ง ๋ง์)
๋ ์์๋ณด๋ฉด ์ข์ ๋ด์ฉ๋ค
- FACADE ํจํด
- OSIV(Open Session In View)
- @Transactional ์์ฑ, ์ค์ ๋ค
REFERENCES
LazyInitializationException - What it is and the best way to fix it
์? ์ด๊ฒ ์ ๋กค๋ฐฑ๋๋๊ฑฐ์ง? | ์ฐ์ํํ์ ๋ค ๊ธฐ์ ๋ธ๋ก๊ทธ
https://www.inflearn.com/questions/227574
์ฝ๋
์ ์ฒด ์ฝ๋๋ Github์ ์์ต๋๋ค :)
- TeamServiceTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class TeamServiceTest {
@Autowired
private TeamService teamService;
@Before
public void setup() {
Team team = new Team(null, "team", new ArrayList<>());
for (int i = 1; i <= 3; i++) {
Member member = new Member(null, "member" + i, null);
member.updateTeam(team);
}
teamService.save(team);
}
@Test
public void lazy_exception() {
List<Team> teams = teamService.findAll();
}
}
- TeamService
@Service
public class TeamService {
@Autowired
private TeamRepository teamRepository;
public Team save(Team team) {
return teamRepository.save(team);
}
public List<Team> findAll() {
List<Team> teams = teamRepository.findAll();
extractMembers(teams); // Exception
return teams;
}
private void extractMembers(List<Team> teams) {
// LazyInitializationException!
teams.forEach(t -> t.getMembers().get(0).getName());
}
}
- TeamRepository
@Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
์ํฉ
- Serivce๋จ์์ Team์ ์กฐํํ๋๋ฐ Team ๋ด๋ถ์ MemberList๋ ์ง์ฐ๋ก๋ฉ์ผ๋ก ์กฐํ๋ฅผ ํ ์ํ์ด๋ค.
- Team ๋ด๋ถ์ ์๋ Member์ ์ ๊ทผ์ ํ๋ คํ๋
LazyInitialiazationException
์ด ๋ฐ์ํ๋ฉด์, ์ง์ฐ๋ก๋ฉ์ ํ ์ ์๋ค๊ณ ์๋ฌ๊ฐ ๋จ๋ ๊ฒ์ด๋ค.
์ฐฉ๊ฐ
์ด์ ์๋ Service์์ Repository.findAll()
์ ํธ์ถํ๋ Service์์๋ ์์์ฑ ์ปจํ
์คํธ๊ฐ ์ด์์์ ๊ฒ์ด๋ผ๊ณ ์ฐฉ๊ฐํ์๋ค.
Team์ ๋ฉค๋ฒ ๋ณ์๋ค์ ์ ๊ทผ์ด ๊ฐ๋ฅํ์ง๋ง, Member์ ์ ๊ทผํ๋ คํ๋ Proxy๋ก ๊ฐ์ ธ์จ Member๋ฅผ ์ด๊ธฐํ ํ ์ ์๋ค๋ ๊ฒ์ด์๋ค.
์๊ทธ๋ด๊น?
์ ๋ต์ ์์์ฑ ์ปจํ ์คํธ์ ๋ฒ์์ ์๋ค. Spring์์๋ ์์์ฑ ์ปจํ ์คํธ์ ๋ผ์ดํ์ฌ์ดํด์ด ํธ๋์ญ์ ๊ณผ ๋์ผํ๊ฒ ์ ์ง๋๋๋ฐ, ํ์ฌ๋ findAll() ์์ ํธ๋์ญ์ ์ด ์์๋์๋ค๊ฐ ๋ฐํ์ด ๋๋ฉด์ ์ข ๋ฃ๊ฐ ๋์๊ธฐ ๋๋ฌธ์ ์กฐํํด์จ Team์ด ์ค์์(Detached) ์ํ๊ฐ ๋๋ฒ๋ฆฐ ๊ฒ์ด๋ค.
์ฌ๊ธฐ์ ๋ค์๋ ์๋ฌธ์ ํธ๋์ญ์ ์ด ์ด๋์ ์๊ฒจ์ ์ง ํผ์ ๋ซํ๋ ๊ฒ์ธ๊ฐ.. ์ด๋ค.
TeamRepository๋ฅผ ๋ง๋ค ๋ ์์ํ ์ธํฐํ์ด์ค๋ JpaRepository๋ฅผ ์ฌ์ฉํ๋ค.
JpaRepository์ ๊ธฐ๋ณธ ๊ตฌํ์ฒด์ธ SimpleRepository๋ฅผ ์ดํด๋ณด์.
SimpleJpaRepository (Spring Data JPA 2.6.3 API)
@Repository
@Transactional(readOnly=true)
public class SimpleRepository<T, ID>
ํด๋์ค ๋ ๋ฒจ์ @Transactional
์ด ๋ถ์ด์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
@Transactional์ ๋ํดํธ ์ต์
์ Propagation.REQUIRED
์ด๋ค.
์ด์ ์ ์์ฑ๋ ํธ๋์ญ์
์ด ์๋ค๋ฉด ์ฐธ์ฌํ๊ณ , ์๋ค๋ฉด ์๋ก ํธ๋์ญ์
์ ์์ํ๊ฒ ๋๋ค.
SimpleJpaRepository.findAll()
๋ฉ์๋ ์ด๋ฆ์ผ๋ก ํธ๋์ญ์
์ด ์์ฑ์ด ๋๊ณ , TeamList๋ฅผ ๋ฐํํ๊ณ ํธ๋์ญ์
์ ์๋ฃ์ฒ๋ฆฌํ๋ ๊ฒ์ด๋ค.
Team์ ์์์ฑ ์ปจํ
์คํธ์ ์ํด ๊ด๋ฆฌ๋ฅผ ๋ฐ์ง ์์ผ๋, ์ค์์ ์ํ๊ฐ ๋๋ฒ๋ฆฌ๊ณ Service๋จ์์ Team.memberList
์ ์ ๊ทผํ๋ คํ๋ LazyInitializationException
์ด ๋ฐ์ํ๋ ๊ฒ์ด๋ค.
์๋์์ ์ค์ ๋ก๊ทธ๋ฅผ ์ดํด๋ณด๋๋ก ํ์.
๋ก๊ทธ๋ฅผ ์ดํด๋ณด์
๊ด๋ จ๋ ๋ก๊ทธ๋ง ๊ฐ์ถ๋ ค๋ดค์ต๋๋ค.
o.s.orm.jpa.JpaTransactionManager <1>: Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
o.s.orm.jpa.JpaTransactionManager : Opened new. EntityManager [SessionImpl(220766773<open>)] for JPA transaction
o.h.e.t.internal.TransactionImpl : begin
o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
... <2> select ์กฐํ ...
o.s.t.i.TransactionInterceptor <3>: Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
o.s.orm.jpa.JpaTransactionManager <4>: Closing JPA EntityManager [SessionImpl(220766773<open>)] after transaction
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: me.iseunghan.testjpa.domain.Team.members, could not initialize proxy - no Session
- <1> :
SimpleJpaRepository.findAll
๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์๋ก์ด ํธ๋์ญ์ ์ ์์ฑ - <2> : select ์กฐํ๋ฅผ ํฉ๋๋ค.
- <3> :
SimpleJpaRepository.findAll
ํธ๋์ญ์ ์ ์๋ฃ์ฒ๋ฆฌ ํฉ๋๋ค. - <4> : ์์์ฑ ์ปจํ ์คํธ๊ฐ ๋ซํ๊ฒ ๋ฉ๋๋ค.
์์์ฑ ์ปจํ ์คํธ์ ๋ผ์ดํ ์ฌ์ดํด์ ํธ๋์ญ์ ์ ๋ผ์ดํ ์ฌ์ดํด๊ณผ ๋์ผํ๋ค๊ณ ํ์ต๋๋ค.
์ง์ฐ ๋ก๋ฉ์ ํ๊ธฐ ์ํด์๋ ์์์ฑ ์ปจํ ์คํธ์ ์ํด ๊ด๋ฆฌ๊ฐ ๋๊ณ ์์ด์ผ ํฉ๋๋ค. ํ์ง๋ง <3> ํธ๋์ญ์ ์ด ์๋ฃ ์ฒ๋ฆฌ๊ฐ ๋๋ฉด์ Team์ ์ค์์ ์ํ๊ฐ ๋๋ฉด์ Member๋ฅผ ์ด๊ธฐํ ํ๋ ค๋ค ์คํจํ๊ฒ ๋ ๊ฒ์ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
- 1 TeamSerivce.findAll @Transactional ๋ถ์ด๊ธฐ
TeamService.findAll()
์์๋ถํฐ ํธ๋์ญ์ ์ ์์ฑํ์ฌ ์ ์ด(์ ํ)๋๋๋ก ํ๋ ๊ฒ์ด๋ค.- ๊ทธ๋ ๋ค๋ฉด,
SimpleRepository
์์@Transactonal
์ ๊ธฐ๋ณธ ์์ฑ์ผ๋ก ์ธํด ํธ๋์ญ์ ์ ์ฐธ์ฌํ๊ฒ ๋๋ค.
- 2 Join Fetch ์ฌ์ฉ
@Query(value = "select t from Team t join fetch t.members")
List<Team> findAllJoinFetch();
@EntityGraph(attributePaths = "members")
@Query("select t from Team t")
List<Team> findAllEntityGraph();
4. ๊ธ๋ก๋ฒ ํ์น ์ ๋ต์ EAGER๋ก ๋ณ๊ฒฝ(์ฌ๋งํ๋ฉด ์ฌ์ฉํ์ง ๋ง์)
๋ ์์๋ณด๋ฉด ์ข์ ๋ด์ฉ๋ค
- FACADE ํจํด
- OSIV(Open Session In View)
- @Transactional ์์ฑ, ์ค์ ๋ค
REFERENCES
LazyInitializationException - What it is and the best way to fix it
์? ์ด๊ฒ ์ ๋กค๋ฐฑ๋๋๊ฑฐ์ง? | ์ฐ์ํํ์ ๋ค ๊ธฐ์ ๋ธ๋ก๊ทธ
https://www.inflearn.com/questions/227574