2010-06-16 22 views
6

Nếu tôi có một mối quan hệ @OneToMany với @Cascade (CascadeType.SAVE_UPDATE) như sauLàm thế nào để kích hoạt chế độ Hibernate Interceptor khi tôi có giao dịch Hibernate được quản lý bởi Spring?

public class One { 

    private Integer id; 

    private List<Many> manyList = new ArrayList<Many>(); 

    @Id 
    @GeneratedValue 
    public Integer getId() { 
     return this.id; 
    } 

    @OneToMany 
    @JoinColumn(name="ONE_ID", updateable=false, nullable=false) 
    @Cascade(CascadeType.SAVE_UPDATE) 
    public List<Many> getManyList() { 
     return this.manyList; 
    }   

} 

Và Nhiều lớp

public class Many { 

    private Integer id; 

    /** 
     * required no-arg constructor 
     */ 
    public Many() {} 

    public Many(Integer uniqueId) { 
     this.id = uniqueId 
    } 

    /** 
     * Without @GeneratedValue annotation 
     * Hibernate will use assigned Strategy 
     */ 
    @Id 
    public Integer getId() { 
     return this.id; 
    } 

} 

Nếu tôi có Kịch bản sau

One one = new One(); 

/** 
    * generateUniqueId method will Take care of assigning unique id for each Many instance 
    */ 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 

Và tôi gọi

sessionFactory.getCurrentSession().save(one); 

Trước khi đi trên

Theo Transitive persistence tài liệu tham khảo Hibernate, bạn có thể nhìn thấy

Nếu phụ huynh được chuyển cho save(), cập nhật() hoặc saveOrUpdate(), tất cả trẻ em được chuyển đến saveOrUpdate()

ok. Bây giờ Chúng ta hãy xem những gì Java Persistence Với Hibernate cuốn sách Nói về phương pháp saveOrUpdate

Hibernate truy vấn bảng NHIỀU cho id cụ thểnếu nó được tìm thấy, Hibernate cập nhật hàng. Nếu nó không được tìm thấy, chèn một hàng mới là bắt buộc và thực hiện.

Mà có thể được dịch theo

INSERT INTO ONE (ID) VALUES (?) 

/** 
    * I have four Many instances added To One instance 
    * So four select-before-saving 
    * 
    * I DO NOT NEED select-before-saving 
    * Because i know i have a Fresh Transient instance 
    */ 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 

INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 

Bất kỳ workaround Để tránh select-trước-tiết kiệm ??? Vâng, Bạn có thể

  • Thêm một cột @version (Không áp dụng)
  • thực hiện phương pháp isTransient cung cấp bởi Hibernate đánh chặn (Tùy chọn tôi có)

Vì vậy, như một cách để tránh chọn trước khi lưu hành vi mặc định khi sử dụng loại tầng này, tôi đã cải thiện mã của mình bằng cách gán một Hibernate Interceptor cho một phiên Hibernate có Giao dịch được quản lý bởi Spring.

Ở đây đi kho của tôi

Trước (Nếu không có bất kỳ Hibernate Interceptor): Nó hoạt động tốt!

@Repository 
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> { 

    @Autowired 
    private SessionFactory sessionFactory; 

    @Override 
    public void add(SomeEntity instance) { 
     sessionFactory.getCurrentSession().save(instance); 
    } 

} 

Sau (Với Hibernate Inteceptor): họ gặp khó khăn (Không có truy vấn SQL được thực hiện - Cả INSERT Cũng CHỌN-TRƯỚC-TIẾT KIỆM)

@Repository 
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> { 

    @Autowired 
    private SessionFactory sessionFactory; 

    @Override 
    public void add(SomeEntity instance) { 
     sessionFactory.openSession(new EmptyInterceptor() { 
      /** 
       * To avoid select-before-saving 
       */ 
      @Override 
      public Boolean isTransient(Object o) { 
       return true; 
      } 
     }).save(instance); 
    } 

} 

Câu hỏi của tôi là: Tại sao mùa xuân không kiên trì thực thể của tôi và các mối quan hệ của nó khi sử dụng Hibernate Interceptor và những gì tôi nên làm như workaround để làm việc tốt ???

Trả lời

3

mùa xuân duy trì một mối liên hệ giữa các phiên hiện tại và các giao dịch vãng lai (xem SessionFactoryUtils.java.) Kể từ khi đã có một phiên đi kèm cho các cuộc gọi phương thức DAO hiện tại, bạn phải sử dụng phiên này, hoặc đi plunge của tham gia với các chi tiết tối tăm về việc kết hợp phiên mới với bối cảnh giao dịch trước đó. Nó có thể là có thể, nhưng với rủi ro đáng kể, và chắc chắn không được khuyến khích. Trong chế độ ngủ đông, nếu bạn có một phiên đã mở, thì nó sẽ được sử dụng.

Có nói rằng, bạn có thể có được mùa xuân để tạo một phiên mới cho bạn và kết hợp nó với ngữ cảnh giao dịch hiện tại. Sử dụng SessionFactoryUtils.getNewSession(SessionFactory, Interceptor). Nếu bạn sử dụng sessionFactory của hibernate thay vì hibernate, thì điều này sẽ giữ liên kết với giao dịch.

Ban đầu, bạn có thể mã hóa mã này trực tiếp trong DAO. Khi nó được thử và thử nghiệm và hy vọng tìm được cách làm việc, bạn có thể thực hiện các bước để di chuyển mã mùa xuân ra khỏi DAO của bạn, chẳng hạn như sử dụng AOP để thêm lời khuyên cho các phương thức add() tạo và làm sạch phiên mới.

Một giải pháp thay thế khác là sử dụng Trình chặn đánh chặn toàn cầu. Mặc dù nó là toàn cầu, bạn có thể cho nó hành vi có thể kiểm soát cục bộ. TransientInterceptor chứa threadLocal<Boolean>. Đây là cờ cho chuỗi hiện tại để cho biết liệu trình chặn có trả về true cho isTransient hay không. Bạn đặt nó thành true khi bắt đầu phương thức add() và xóa nó ở cuối. Ví dụ.

class TransientInterceptor extends EntityInterceptor { 
     ThreadLocal<Boolean> transientFlag = new ThreadLocal<Boolean)(); 
     public boolean isTransient() { 
     return transientFlag.get()==Boolean.TRUE; 
     } 
     static public setTransient(boolean b) { 
      transientFlag.set(b); 
     } 
    } 

Và sau đó trong DAO của bạn:

@Override 
public void add(SomeEntity instance) { 
    try { 
     TransientInterceptor.set(true); 
     sessionFactory.getCurrentSession().save(instance); 
    } 
    finally { 
     TransientInterceptor.set(false); 
    } 
} 

Bạn có thể sau đó thiết lập TransientInterceptor như một đánh chặn toàn cầu trên SessionFactory (. Ví dụ LocalSessionFactoryBean) Để làm điều này ít xâm lấn, bạn có thể tạo một AOP xung quanh lời khuyên để áp dụng hành vi này cho tất cả các phương pháp thêm DAO của bạn, nếu thích hợp.

0

Trong phương pháp 'sau' bạn đang tạo phiên mới và không xóa phiên đó, do đó không có cập nhật nào được gửi đến cơ sở dữ liệu. Điều này không liên quan gì đến Spring, nhưng là hành vi Hibernate thuần túy.

Điều bạn có thể muốn là thêm trình chặn chặn (thực thể) vào sessionFactory, có thể được định cấu hình bằng Spring. Sau đó bạn có thể chỉ giữ phương thức add() của kho lưu trữ của bạn như trước đây. Xem http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/LocalSessionFactoryBean.html#setEntityInterceptor%28org.hibernate.Interceptor%29

+0

Như đã nói: * Bạn có thể thêm Bộ chặn vào SessionFactory *. Nó xảy ra cho dù tôi thêm một Interceptor vào SessionFactory, tôi sẽ nhận được một hành vi ** toàn cục **. Tôi chỉ muốn thêm một Interceptor vào cá thể Session được sử dụng bởi phương thức add (SomeEntity instance). Bất kỳ cách giải quyết nào ??? –

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