๐ŸŒป JAVA/์ž๋ฐ” ORM ํ‘œ์ค€ JPA ํ”„๋กœ๊ทธ๋ž˜๋ฐ

LazyInitializationException์ด ๋‚˜๋Š” ์ด์œ  (feat. ํŠธ๋žœ์žญ์…˜)

iseunghan 2022. 3. 30. 15:02
๋ฐ˜์‘ํ˜•

 

์ฝ”๋“œ

์ „์ฒด ์ฝ”๋“œ๋Š” 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

๋ฐ˜์‘ํ˜•