2014-10-22 28 views
6

Tôi phải học các lớp có quan hệ một-nhiều. Khi tôi cố gắng truy cập bộ sưu tập được tải nhẹ nhàng, tôi nhận được LazyInitializationException. Tôi tìm kiếm trên web một thời gian và bây giờ tôi biết rằng tôi nhận được ngoại lệ vì phiên được sử dụng để tải lớp chứa bộ sưu tập bị đóng. Tuy nhiên tôi không tìm thấy một giải pháp (hoặc ít nhất là tôi không hiểu chúng). Về cơ bản tôi có những lớp học:Làm thế nào để giải quyết LazyInitializationException trong JPA dữ liệu mùa xuân?

tài

@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(); 
    } 

} 

Job

@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; 
       } 
      } 
     } 
    } 

} 

App

@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()); 
    } 

} 

Tôi thường đọc rằng phiên phải bị ràng buộc với chuỗi hiện tại, nhưng tôi không biết cách thực hiện điều này với cấu hình dựa trên chú thích của Spring. Someon có thể chỉ cho tôi cách làm điều đó không?

P.S. Tôi muốn sử dụng tải chậm, do đó tải háo hức là không có tùy chọn.

+0

Bạn có thể dán mã cho các dịch vụ tải thực thể không? – Smutje

Trả lời

-1

Thay đổi

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

để

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

Hoặc sử dụng Hibernate.initialize bên trong dịch vụ của bạn, trong đó có tác dụng tương tự.

+0

Tôi nghĩ rằng downvotes cho câu trả lời này là không công bằng. Đó là thiếu trong một số bối cảnh, nhưng nó là nếu không hoàn toàn chính xác. Điều này là cần thiết vì 'FetchType' mặc định cho' @ OneToMany' là Lười biếng. Việc thay đổi 'FetchType' thành háo hức làm cho đối tượng con được đưa vào biểu đồ đối tượng trước khi phiên được đóng lại, do đó việc chữa trị vấn đề (hiệu suất đạt được xác nhận). – 8bitjunkie

2

Bạn có 2 tùy chọn.

Lựa chọn 1: Như đã đề cập bởi BetaRide, sử dụng chiến lược EAGER lấy

Phương án 2: Sau khi nhận được user từ cơ sở dữ liệu sử dụng chế độ ngủ đông, hãy thêm dòng dưới đây trong mã để tải các yếu tố bộ sưu tập:

Hibernate.initialize(user.getCreatedJobs()) 

này cho ngủ đông để khởi tạo các yếu tố thu

3

Về cơ bản, bạn cần phải lấy dữ liệu lười khi bạn đang ở trong một giao dịch. Nếu các lớp dịch vụ của bạn là @Transactional, thì mọi thứ sẽ ổn khi bạn ở trong đó. Khi bạn ra khỏi lớp dịch vụ, nếu bạn cố gắng get bộ sưu tập lười biếng, bạn sẽ nhận được ngoại lệ đó, nằm trong phương thức main() của bạn, dòng System.out.println(random.getCreatedJobs());.

Bây giờ, nó đi xuống đến những gì các phương pháp dịch vụ của bạn cần phải trả lại. Nếu userService.getRandomUser() dự kiến ​​sẽ trả lại người dùng có công việc đã được khởi tạo để bạn có thể điều khiển chúng, thì đó là trách nhiệm của phương pháp để tìm nạp nó. Cách đơn giản nhất để thực hiện với Hibernate là gọi số Hibernate.initialize(user.getCreatedJobs()).

+0

Vì vậy, mọi tương tác với databse phải được thực hiện trong một bối cảnh giao dịch và bối cảnh giao dịch đảm bảo rằng một phiên có sẵn? Có thể liên kết phiên với chuỗi hiện tại để phiên luôn có sẵn không? – stevecross

+0

@feuerball, bạn có ý gì khi 'liên kết phiên với chuỗi hiện tại'? Nội bộ hibernate sử dụng các đối tượng phiên để có được kết nối cơ sở dữ liệu và một khi giao dịch được hoàn thành sau đó phiên cũng đóng cửa cùng với kết nối DB. Vì vậy, ngay cả khi bạn cố gắng giữ phiên bên ngoài bối cảnh giao dịch thì nó không được sử dụng. – Chaitanya

+0

@feuerball Bạn có thể đặt '@ Transactional' ở các cấp độ khác, thay vì các lớp dịch vụ, nhưng tầng dịch vụ là nơi tự nhiên cho chúng. Các phương thức dịch vụ bao gồm các hoạt động nghiệp vụ, cần được cam kết với toàn bộ cơ sở dữ liệu hoặc được khôi phục toàn bộ. Bất kỳ "phiên kết hợp khác với luồng hiện tại", IMHO, trong khi có thể có thể, là sử dụng sai khung này và có thể dẫn đến phá vỡ ranh giới giữa các lớp ứng dụng. –

2

Xem xét sử dụng JPA 2.1, với đồ thị Entity:

Lazy tải là thường là một vấn đề với JPA 2.0. Bạn phải xác định tại thực thể FetchType.LAZY hoặc FetchType.EAGER và đảm bảo mối quan hệ được khởi tạo trong giao dịch.

này có thể được thực hiện bằng cách:

  • sử dụng một truy vấn cụ thể mà đọc các thực thể
  • hoặc bằng cách truy cập mối quan hệ trong mã số kinh doanh (bổ sung truy vấn đối với từng mối quan hệ).

Cả hai phương pháp còn xa mới hoàn hảo, JPA 2.1 biểu đồ thực thể là một giải pháp tốt hơn cho nó:

0

Đối với những người không có khả năng sử dụng JPA 2.1 nhưng muốn giữ khả năng trả về một thực thể trong bộ điều khiển của chúng (và không phải là một String/JsonNode/byte []/void với wri te in response):

vẫn có khả năng tạo DTO trong giao dịch, bộ điều khiển sẽ trả về.

@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); 
    } 
} 
+0

'readOnly' không phải là thuộc tính hợp lệ cho' javax.transaction.Transactional'. Khi tôi cố gắng sử dụng nó IDE của tôi nhấn mạnh nó trong đọc và nói với tôi rằng điều này là không hợp lệ. – 8bitjunkie

Các vấn đề liên quan