복붙노트

[SPRING] 스프링 데이터 JPA에서 LazyInitializationException을 해결하는 방법?

SPRING

스프링 데이터 JPA에서 LazyInitializationException을 해결하는 방법?

나는 일대 다 (one-to-many-relation) 클래스를 가져야한다. 느슨하게로드 된 컬렉션에 액세스하려고하면 LazyInitializationException이 발생합니다. 나는 잠시 동안 웹 검색을하고 이제 컬렉션을 보유하는 클래스를로드하는 데 사용 된 세션이 닫혀 있기 때문에 예외가 발생한다는 것을 알고 있습니다. 그러나 나는 해결책을 찾지 못했다. (또는 나는 적어도 그것을 이해하지 못했다.) 기본적으로 나는 그 수업을 가지고 :

사용자

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private long id;

    @OneToMany(mappedBy = "creator")
    private Set<Job> createdJobs = new HashSet<>();

    public long getId() {
        return id;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public Set<Job> getCreatedJobs() {
        return createdJobs;
    }

    public void setCreatedJobs(final Set<Job> createdJobs) {
        this.createdJobs = createdJobs;
    }

}

UserRepository

public interface UserRepository extends JpaRepository<User, Long> {}

UserService

@Service
@Transactional
public class UserService {

    @Autowired
    private UserRepository repository;

    boolean usersAvailable = false;

    public void addSomeUsers() {
        for (int i = 1; i < 101; i++) {
            final User user = new User();

            repository.save(user);
        }

        usersAvailable = true;
    }

    public User getRandomUser() {
        final Random rand = new Random();

        if (!usersAvailable) {
            addSomeUsers();
        }

        return repository.findOne(rand.nextInt(100) + 1L);
    }

    public List<User> getAllUsers() {
        return repository.findAll();
    }

}

@Entity
@Table(name = "job")
@Inheritance
@DiscriminatorColumn(name = "job_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Job {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private long id;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User creator;

    public long getId() {
        return id;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public User getCreator() {
        return creator;
    }

    public void setCreator(final User creator) {
        this.creator = creator;
    }

}

JobRepository

public interface JobRepository extends JpaRepository<Job, Long> {}

Jobservice

@Service
@Transactional
public class JobService {

    @Autowired
    private JobRepository repository;

    public void addJob(final Job job) {
        repository.save(job);
    }

    public List<Job> getJobs() {
        return repository.findAll();
    }

    public void addJobsForUsers(final List<User> users) {
        final Random rand = new Random();

        for (final User user : users) {
            for (int i = 0; i < 20; i++) {
                switch (rand.nextInt(2)) {
                case 0:
                    addJob(new HelloWorldJob(user));
                    break;
                default:
                    addJob(new GoodbyeWorldJob(user));
                    break;
                }
            }
        }
    }

}

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class App {

    public static void main(final String[] args) {
        final ConfigurableApplicationContext context = SpringApplication.run(App.class);
        final UserService userService = context.getBean(UserService.class);
        final JobService jobService = context.getBean(JobService.class);

        userService.addSomeUsers();                                 // Generates some users and stores them in the db
        jobService.addJobsForUsers(userService.getAllUsers());      // Generates some jobs for the users

        final User random = userService.getRandomUser();            // Picks a random user

        System.out.println(random.getCreatedJobs());
    }

}

세션이 현재 스레드에 바인드되어야한다는 것을 자주 읽었지 만 Spring의 주석 기반 구성으로이를 수행하는 방법을 알지 못합니다. 누군가가 저에게 어떻게 할 수 있다고 지적 할 수 있습니까?

추신 게으른 로딩을 사용하고 싶기 때문에 열망하는 로딩 옵션이 없습니다.

해결법

  1. ==============================

    1.기본적으로 트랜잭션 내부에있는 동안 게으른 데이터를 가져와야합니다. 서비스 클래스가 @Transactional 인 경우에는 모든 것이 정상적으로 처리되어야합니다. 서비스 클래스를 벗어나 게으른 콜렉션을 얻으려면 main () 메소드 인 System.out.println (random.getCreatedJobs ());에있는 해당 예외가 발생합니다.

    기본적으로 트랜잭션 내부에있는 동안 게으른 데이터를 가져와야합니다. 서비스 클래스가 @Transactional 인 경우에는 모든 것이 정상적으로 처리되어야합니다. 서비스 클래스를 벗어나 게으른 콜렉션을 얻으려면 main () 메소드 인 System.out.println (random.getCreatedJobs ());에있는 해당 예외가 발생합니다.

    이제, 귀하의 서비스 방법이 무엇을 반환해야하는지에 관해서 설명합니다. userService.getRandomUser ()가 조작 할 수 있도록 초기화 된 작업을 가진 사용자를 반환 할 것으로 예상되는 경우 가져 오는 것은 해당 메서드의 책임입니다. Hibernate로 그것을하는 가장 간단한 방법은 Hibernate.initialize (user.getCreatedJobs ())를 호출하는 것이다.

  2. ==============================

    2.엔티티 그래프와 함께 JPA 2.1 사용을 고려하십시오.

    엔티티 그래프와 함께 JPA 2.1 사용을 고려하십시오.

    JPA 2.0에서는 종종 지연로드가 문제가되었습니다. 엔티티 FetchType.LAZY 또는 FetchType.EAGER에서 정의해야하며 관계가 트랜잭션 내에서 초기화되는지 확인해야합니다.

    이 작업은 다음과 같이 수행 할 수 있습니다.

    두 방법 모두 완벽하지 못합니다. JPA 2.1 엔티티 그래프가 더 나은 솔루션입니다.

  3. ==============================

    3.2 가지 옵션이 있습니다.

    2 가지 옵션이 있습니다.

    옵션 1 : BetaRide에서 언급했듯이 EAGER 가져 오기 전략을 사용합니다.

    옵션 2 : hibernate를 사용하여 데이터베이스에서 사용자를 얻은 후에 콜렉션 요소를로드하기 위해 아래 코드를 코드에 추가하십시오 :

    Hibernate.initialize(user.getCreatedJobs())
    

    이것은 hibernate에게 collection 요소를 초기화하도록 지시한다.

  4. ==============================

    4.변화

    변화

    @OneToMany(mappedBy = "creator")
    private Set<Job> createdJobs = new HashSet<>();
    

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "creator")
    private Set<Job> createdJobs = new HashSet<>();
    

    또는 서비스 내에서 Hibernate.initialize를 사용하면 동일한 효과를 얻을 수 있습니다.

  5. ==============================

    5.JPA 2.1을 사용할 가능성이 없지만 컨트롤러에서 엔티티를 반환 할 가능성을 유지하려는 사람 (응답으로 쓰기가 가능한 String / JsonNode / byte [] / void가 아님)의 경우 :

    JPA 2.1을 사용할 가능성이 없지만 컨트롤러에서 엔티티를 반환 할 가능성을 유지하려는 사람 (응답으로 쓰기가 가능한 String / JsonNode / byte [] / void가 아님)의 경우 :

    트랜잭션에 DTO를 구축 할 가능성이 여전히 있으며, 이는 컨트롤러가 리턴합니다.

    @RestController
    @RequestMapping(value = FooController.API, produces = MediaType.APPLICATION_JSON_VALUE)
    class FooController{
    
        static final String API = "/api/foo";
    
        private final FooService fooService;
    
        @Autowired
        FooController(FooService fooService) {
            this.fooService= fooService;
        }
    
        @RequestMapping(method = GET)
        @Transactional(readOnly = true)
        public FooResponseDto getFoo() {
            Foo foo = fooService.get();
            return new FooResponseDto(foo);
        }
    }
    
  6. ==============================

    6.컨텍스트 구성 클래스에 @EnableTransactionManagement 주석을 추가하여 Spring 트랜잭션 관리자를 활성화해야한다.

    컨텍스트 구성 클래스에 @EnableTransactionManagement 주석을 추가하여 Spring 트랜잭션 관리자를 활성화해야한다.

    두 서비스 모두 @Transactional annotation과 그것의 default value 속성을 가지고 있기 때문에 TxType.Required이므로 트랜잭션 관리자가 켜져 있으면 현재 트랜잭션은 서비스간에 공유됩니다. 따라서 세션을 사용할 수 있어야하고 LazyInitializationException을 얻지 못할 것입니다.

  7. from https://stackoverflow.com/questions/26507446/how-to-resolve-lazyinitializationexception-in-spring-data-jpa by cc-by-sa and MIT license