Hibernate: phương pháp hay nhất để kéo tất cả các bộ sưu tập lười biếng


92

Tôi có gì:

@Entity
public class MyEntity {
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Address> addreses;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Person> persons;

  //....
}

public void handle() {

   Session session = createNewSession();
   MyEntity entity = (MyEntity) session.get(MyEntity.class, entityId);
   proceed(session); // FLUSH, COMMIT, CLOSE session!

   Utils.objectToJson(entity); //TROUBLES, because it can't convert to json lazy collections
}

Thật là một vấn đề:

Vấn đề là tôi không thể kéo bộ sưu tập lười biếng sau khi phiên đã được đóng. Nhưng tôi cũng không thể không đóng một phiên trong phương pháp tiếp tục .

Thật là một giải pháp (giải pháp thô):

a) Trước khi phiên đóng, buộc ngủ đông để kéo các bộ sưu tập lười biếng

entity.getAddresses().size();
entity.getPersons().size();

....

b) Có thể một cách khó hiểu hơn là sử dụng @Fetch(FetchMode.SUBSELECT)chú thích

Câu hỏi:

Cách thực hành tốt nhất / cách phổ biến / cách nhẹ nhàng hơn để làm điều đó là gì? Có nghĩa là chuyển đổi đối tượng của tôi thành JSON.

Câu trả lời:


102

Sử dụng Hibernate.initialize()bên trong @Transactionalđể khởi tạo các đối tượng lười biếng.

 start Transaction 
      Hibernate.initialize(entity.getAddresses());
      Hibernate.initialize(entity.getPersons());
 end Transaction 

Bây giờ ngoài Giao dịch, bạn có thể nhận được các đối tượng lười biếng.

entity.getAddresses().size();
entity.getPersons().size();

1
Nó trông hấp dẫn). Theo tôi hiểu nếu tôi sẽ sử dụng @Fetch (FetchMode.SUBSELECT), tôi có thể gọi Hibernate.initialize chỉ một lần để kéo tất cả các bộ sưu tập. Tôi nói đúng chứ?
VB_

4
Và làm thế nào để bạn quản lý khi bạn truy xuất một bộ sưu tập MyEntity?
Alexis Dufrenoy

1
Nếu bạn gọi bất kỳ phương thức nào như "size ()" trên một tập hợp trong một giao dịch, nó cũng sẽ khởi tạo nó, vì vậy ví dụ của bạn sau khi khởi tạo không phải là tốt nhất. Điều này cho biết, "Hibernate.initialize (...)" tốt hơn về mặt ngữ nghĩa sau đó là collection.size (), vì vậy bạn có lời khuyên tốt nhất.
Tristan

7

Bạn có thể duyệt qua Getters của đối tượng Hibernate trong cùng một giao dịch để đảm bảo rằng tất cả các đối tượng con lười biếng đều được tìm nạp một cách háo hức với lớp trợ giúp chung sau :

HibernateUtil.initializeObject (myObject, "my.app.model");

package my.app.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.aspectj.org.eclipse.jdt.core.dom.Modifier;
import org.hibernate.Hibernate;

public class HibernateUtil {

public static byte[] hibernateCollectionPackage = "org.hibernate.collection".getBytes();

public static void initializeObject( Object o, String insidePackageName ) {
    Set<Object> seenObjects = new HashSet<Object>();
    initializeObject( o, seenObjects, insidePackageName.getBytes() );
    seenObjects = null;
}

private static void initializeObject( Object o, Set<Object> seenObjects, byte[] insidePackageName ) {

    seenObjects.add( o );

    Method[] methods = o.getClass().getMethods();
    for ( Method method : methods ) {

        String methodName = method.getName();

        // check Getters exclusively
        if ( methodName.length() < 3 || !"get".equals( methodName.substring( 0, 3 ) ) )
            continue;

        // Getters without parameters
        if ( method.getParameterTypes().length > 0 )
            continue;

        int modifiers = method.getModifiers();

        // Getters that are public
        if ( !Modifier.isPublic( modifiers ) )
            continue;

        // but not static
        if ( Modifier.isStatic( modifiers ) )
            continue;

        try {

            // Check result of the Getter
            Object r = method.invoke( o );

            if ( r == null )
                continue;

            // prevent cycles
            if ( seenObjects.contains( r ) )
                continue;

            // ignore simple types, arrays und anonymous classes
            if ( !isIgnoredType( r.getClass() ) && !r.getClass().isPrimitive() && !r.getClass().isArray() && !r.getClass().isAnonymousClass() ) {

                // ignore classes out of the given package and out of the hibernate collection
                // package
                if ( !isClassInPackage( r.getClass(), insidePackageName ) && !isClassInPackage( r.getClass(), hibernateCollectionPackage ) ) {
                    continue;
                }

                // initialize child object
                Hibernate.initialize( r );

                // traverse over the child object
                initializeObject( r, seenObjects, insidePackageName );
            }

        } catch ( InvocationTargetException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalArgumentException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalAccessException e ) {
            e.printStackTrace();
            return;
        }
    }

}

private static final Set<Class<?>> IGNORED_TYPES = getIgnoredTypes();

private static boolean isIgnoredType( Class<?> clazz ) {
    return IGNORED_TYPES.contains( clazz );
}

private static Set<Class<?>> getIgnoredTypes() {
    Set<Class<?>> ret = new HashSet<Class<?>>();
    ret.add( Boolean.class );
    ret.add( Character.class );
    ret.add( Byte.class );
    ret.add( Short.class );
    ret.add( Integer.class );
    ret.add( Long.class );
    ret.add( Float.class );
    ret.add( Double.class );
    ret.add( Void.class );
    ret.add( String.class );
    ret.add( Class.class );
    ret.add( Package.class );
    return ret;
}

private static Boolean isClassInPackage( Class<?> clazz, byte[] insidePackageName ) {

    Package p = clazz.getPackage();
    if ( p == null )
        return null;

    byte[] packageName = p.getName().getBytes();

    int lenP = packageName.length;
    int lenI = insidePackageName.length;

    if ( lenP < lenI )
        return false;

    for ( int i = 0; i < lenI; i++ ) {
        if ( packageName[i] != insidePackageName[i] )
            return false;
    }

    return true;
}
}

Cảm ơn bạn vì câu trả lời này. Tôi biết đã lâu rồi nhưng tôi đã cố gắng giải quyết vấn đề này và nó diễn ra rất chậm cho đến khi tôi đọc mã của bạn ở đây. Tôi cũng đã thêm ifs vào đầu phương thức thứ hai khởi tạo (object, sawObjects, insidePackageName): if (object instanceof List) { for(Object item : (List<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } else if (object instanceof Set) { for(Object item : (Set<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } Lặp lại danh sách nếu không thì bị bỏ qua.
Chip

Điều gì sẽ xảy ra nếu SecurityException được ném vào o.getClass (). GetMethods () ;?
Oleksii Kyslytsyn

6

Không phải là giải pháp tốt nhất, nhưng đây là những gì tôi nhận được:

1) Chú thích getter mà bạn muốn khởi tạo với chú thích này:

@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {

}

2) Sử dụng phương thức này (có thể được đặt trong một lớp chung hoặc bạn có thể thay đổi T bằng lớp Đối tượng) trên một đối tượng sau khi bạn đọc nó từ cơ sở dữ liệu:

    public <T> void forceLoadLazyCollections(T entity) {

    Session session = getSession().openSession();
    Transaction tx = null;
    try {

        tx = session.beginTransaction();
        session.refresh(entity);
        if (entity == null) {
            throw new RuntimeException("Entity is null!");
        }
        for (Method m : entityClass.getMethods()) {

            Lazy annotation = m.getAnnotation(Lazy.class);
            if (annotation != null) {
                m.setAccessible(true);
                logger.debug(" method.invoke(obj, arg1, arg2,...); {} field", m.getName());
                try {
                    Hibernate.initialize(m.invoke(entity));
                }
                catch (Exception e) {
                    logger.warn("initialization exception", e);
                }
            }
        }

    }
    finally {
        session.close();
    }
}

Tôi sử dụng session.refresh trong một lần lặp để tải lazyCollections. và mỗi khi tôi chạy chương trình của mình chỉ cho một thực thể của mình, tôi đã tải LazyInitializationException và các tập hợp khác sau khi gọi session.refresh. Làm thế nào điều này có thể xảy ra
saba safavi

5

Đặt Utils.objectToJson (thực thể); gọi trước khi đóng phiên.

Hoặc bạn có thể thử đặt chế độ tìm nạp và chơi với mã như thế này

Session s = ...
DetachedCriteria dc = DetachedCriteria.forClass(MyEntity.class).add(Expression.idEq(id));
dc.setFetchMode("innerTable", FetchMode.EAGER);
Criteria c = dc.getExecutableCriteria(s);
MyEntity a = (MyEntity)c.uniqueResult();

FetchMode.EAGER không được dùng nữa. Javadoc khuyên bạn nên sử dụng FetchMode.JOIN ngay bây giờ.
Alexis Dufrenoy

4

Với Hibernate 4.1.6, một tính năng mới được giới thiệu để xử lý các vấn đề liên kết lười biếng đó. Khi bạn bật thuộc tính hibernate.enable_lazy_load_no_trans trong hibernate.properties hoặc trong hibernate.cfg.xml, bạn sẽ không có LazyInitializationException nữa.

Tham khảo thêm: https://stackoverflow.com/a/11913404/286588


3
Đây thực sự là một mô hình chống. Để biết thêm thông tin: vladmihalcea.com/…
Ph03n1x

3

Khi phải tìm nạp nhiều bộ sưu tập, bạn cần:

  1. THAM GIA TÌM KIẾM một bộ sưu tập
  2. Sử dụng Hibernate.initializecho các bộ sưu tập còn lại.

Vì vậy, trong trường hợp của bạn, bạn cần một truy vấn JPQL đầu tiên như sau:

MyEntity entity = session.createQuery("select e from MyEntity e join fetch e.addreses where e.id 
= :id", MyEntity.class)
.setParameter("id", entityId)
.getSingleResult();

Hibernate.initialize(entity.persons);

Bằng cách này, bạn có thể đạt được mục tiêu của mình với 2 truy vấn SQL và tránh một Sản phẩm Descartes.


Xin chào Vlad, nó có hoạt động không nếu tôi gọi Hibernate#initialize(entity.getSubSet())nếu getSubSet trả về Collections.unmodifyableSet(this.subSet). Tôi đã thử và nó không. Bộ sưu tập bên dưới là 'PersentlySet'. Câu chuyện tương tự với việc gọi điện#size()
Vadim Kirilchuk

Nhưng có thể vấn đề là sau đó tôi gọi hàm chứa và các mục bằng của tôi sử dụng quyền truy cập trường trực tiếp chứ không phải getters ..
Vadim Kirilchuk

Nó hoạt động nếu bạn làm theo các bước được cung cấp trong câu trả lời của tôi.
Vlad Mihalcea

2

Có lẽ không phải nơi nào cũng tiếp cận được phương pháp hay nhất, nhưng tôi thường gọi một SIZEbộ sưu tập để tải các phần tử con trong cùng một giao dịch, như bạn đã đề xuất. Nó sạch sẽ, miễn nhiễm với bất kỳ thay đổi nào trong cấu trúc của các phần tử con và tạo ra SQL với chi phí thấp.


0

Hãy thử sử dụng Gsonthư viện để chuyển đổi các đối tượng sang Json

Ví dụ với các servlet:

  List<Party> parties = bean.getPartiesByIncidentId(incidentId);
        String json = "";
        try {
            json = new Gson().toJson(parties);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(json);

0

nếu bạn sử dụng kho lưu trữ jpa, hãy đặt property.put ("hibernate.enable_lazy_load_no_trans", true); sang jpaPropertymap


0

Bạn có thể sử dụng @NamedEntityGraphchú thích cho thực thể của mình để tạo một truy vấn có thể tải được đặt bộ sưu tập bạn muốn tải trên truy vấn của mình.

Ưu điểm chính của lựa chọn này là ngủ đông thực hiện một truy vấn duy nhất để truy xuất thực thể và các bộ sưu tập của nó và chỉ khi bạn chọn sử dụng biểu đồ này, như sau:

Cấu hình thực thể

@Entity
@NamedEntityGraph(name = "graph.myEntity.addresesAndPersons", 
attributeNodes = {
    @NamedAttributeNode(value = "addreses"),
    @NamedAttributeNode(value = "persons"
})

Sử dụng

public MyEntity findNamedGraph(Object id, String namedGraph) {
        EntityGraph<MyEntity> graph = em.getEntityGraph(namedGraph);

        Map<String, Object> properties = new HashMap<>();
        properties.put("javax.persistence.loadgraph", graph);

        return em.find(MyEntity.class, id, properties);
    }

0

Có một số hiểu lầm về bộ sưu tập lười biếng trong JPA-Hibernate. Trước hết, hãy làm rõ rằng tại sao cố gắng đọc một bộ sưu tập lười biếng lại ném ra các ngoại lệ và không chỉ trả về NULL cho các trường hợp chuyển đổi hoặc sử dụng khác?.

Đó là bởi vì các trường Null trong Cơ sở dữ liệu, đặc biệt là trong các cột được nối có ý nghĩa và không đơn giản là trạng thái không được trình bày, như ngôn ngữ lập trình. khi bạn đang cố gắng diễn giải một tập hợp lười biếng thành giá trị Null, điều đó có nghĩa là (ở phía Kho dữ liệu) không có mối quan hệ nào giữa các thực thể này và điều đó không đúng. vì vậy, ném ngoại lệ là một số loại phương pháp hay nhất và bạn phải đối phó với điều đó không phải là Hibernate.

Vì vậy, như đã đề cập ở trên, tôi khuyên bạn nên:

  1. Tách đối tượng mong muốn trước khi sửa đổi nó hoặc sử dụng phiên không trạng thái để truy vấn
  2. Thao tác các trường lười thành các giá trị mong muốn (0, null, v.v.)

cũng như được mô tả trong các câu trả lời khác, có rất nhiều cách tiếp cận (tìm nạp háo hức, tham gia, v.v.) hoặc thư viện và phương pháp để làm điều đó, nhưng bạn phải thiết lập quan điểm của mình về những gì đang xảy ra trước khi xử lý vấn đề và giải quyết nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.