segunda-feira, 10 de junho de 2013

Detached Object no Hibernate ou desvincular objeto da sessão

Fala pessoal(como se fosse muita gente),

Gostaria de compartilhar um código com a comunidade, que consiste em retirar totalmente um objeto do hibernate, ou seja desvincular o objeto da sessao e falar "Olha cara, agora é um objeto normal sem proxy sem bags sem NADAA!".

Encontrei alguns problemas quando utilizei RMI ou EJB3 ou xml, sendo que quando uma entidade traz uma lista de objetos e ele é do tipo lazy ou mesmo a lista em si vem como "Bag", no momento que o sistema tenta de-serializar ou transformar o objeto em xml, ele vai como bag e não como list ou collection, 0o ... WTF ??

Pois é, e quando do outro lado vc tem um hibernate3.jar ainda passa, mas e quando não tem ?

Ou quando vc não quer que o objeto não acesse os dados do tipo Lazy ??

Ou quando as versoes do hibernate são diferentes e ele não consegue de-serializar o objeto, da aquele errão e vem na cabeça : E AGORA JOSÉ?

Pois é[2], e a primeira solução que vem na cabeça é : vou copiar as informações para um objeto novo e passar esse objeto, pois ele perderá as referencias com o proxy do hibernate -_- funciona ... mas da um trabalho cabuloso!

Por isso, em algumas discussões em foruns chegamos a essa solução :

 import org.apache.commons.logging.Log;  
 import org.apache.commons.logging.LogFactory;  
 import org.hibernate.Hibernate;  
 import org.hibernate.proxy.HibernateProxy;  
 import javax.xml.bind.annotation.XmlAccessType;  
 import javax.xml.bind.annotation.XmlAccessorType;  
 import javax.xml.bind.annotation.XmlTransient;  
 import java.beans.BeanInfo;  
 import java.beans.Introspector;  
 import java.beans.PropertyDescriptor;  
 import java.lang.reflect.Constructor;  
 import java.lang.reflect.Field;  
 import java.lang.reflect.Method;  
 import java.util.ArrayList;  
 import java.util.Collection;  
 import java.util.Collections;  
 import java.util.HashMap;  
 import java.util.HashSet;  
 import java.util.List;  
 import java.util.Map;  
 import java.util.Set;  
 public class HibernateDetachUtility {  
   private static final Log LOG = LogFactory.getLog(HibernateDetachUtility.class);  
   public static enum SerializationType {  
     SERIALIZATION, JAXB  
   }  
   public static void nullOutUninitializedFields(Object value, SerializationType serializationType) throws Exception {  
     long start = System.currentTimeMillis();  
     Set<Integer> checkedObjs = new HashSet<Integer>();  
     nullOutUninitializedFields(value, checkedObjs, 0, serializationType);  
     long duration = System.currentTimeMillis() - start;  
     if (duration > 1000) {  
       LOG.info("Detached [" + checkedObjs.size() + "] objects in [" + duration + "]ms");  
     } else {  
       LOG.debug("Detached [" + checkedObjs.size() + "] objects in [" + duration + "]ms");  
     }  
   }  
   private static void nullOutUninitializedFields(Object value, Set<Integer> nulledObjects, int depth,  
     SerializationType serializationType) throws Exception {  
     if (depth > 50) {  
       LOG.warn("Getting different object hierarchies back from calls: " + value.getClass().getName());  
       return;  
     }  
     if ((value == null) || nulledObjects.contains(System.identityHashCode(value))) {  
       return;  
     }  
     nulledObjects.add(System.identityHashCode(value));  
     if (value instanceof Object[]) {  
       Object[] objArray = (Object[]) value;  
       for(int i = 0; i < objArray.length; i++)  
       {  
         nullOutUninitializedFields(objArray[i], nulledObjects, depth +1, serializationType);  
       }  
     } else if (value instanceof Collection) {  
       // Null out any entries in initialized collections  
       for (Object val : (Collection) value) {  
         nullOutUninitializedFields(val, nulledObjects, depth + 1, serializationType);  
       }  
     } else if (value instanceof Map) {  
       for (Object key : ((Map)value).keySet()) {  
         nullOutUninitializedFields(((Map)value).get(key), nulledObjects, depth+1, serializationType);  
         nullOutUninitializedFields(key, nulledObjects, depth+1, serializationType);  
       }  
     }   
     if (serializationType == SerializationType.JAXB) {  
       XmlAccessorType at = value.getClass().getAnnotation(XmlAccessorType.class);  
       if (at != null && at.value() == XmlAccessType.FIELD) {  
         //System.out.println("----------XML--------- field access");  
         nullOutFieldsByFieldAccess(value, nulledObjects, depth, serializationType);  
       } else {  
         //System.out.println("----------XML--------- accessor access");  
         nullOutFieldsByAccessors(value, nulledObjects, depth, serializationType);  
       }  
     } else if (serializationType == SerializationType.SERIALIZATION) {  
       //        System.out.println("-----------JRMP-------- field access");  
       nullOutFieldsByFieldAccess(value, nulledObjects, depth, serializationType);  
     }  
   }  
   private static void nullOutFieldsByFieldAccess(Object object, Set<Integer> nulledObjects, int depth,  
     SerializationType serializationType) throws Exception {  
     Class tmpClass = object.getClass();  
     List<Field> fieldsToClean = new ArrayList<Field>();  
     while (tmpClass != null && tmpClass != Object.class) {  
       Collections.addAll(fieldsToClean, tmpClass.getDeclaredFields());  
       tmpClass = tmpClass.getSuperclass();  
     }  
     nullOutFieldsByFieldAccess(object, fieldsToClean, nulledObjects, depth, serializationType);  
   }  
   @SuppressWarnings("unchecked")  
   private static void nullOutFieldsByFieldAccess(Object object, List<Field> classFields, Set<Integer> nulledObjects, int depth,  
                           SerializationType serializationType) throws Exception {  
     boolean accessModifierFlag = false;  
     for (Field field : classFields) {  
       accessModifierFlag = false;  
       if (!field.isAccessible()) {  
         field.setAccessible(true);  
         accessModifierFlag = true;  
       }  
       Object fieldValue = field.get(object);  
       if (fieldValue instanceof HibernateProxy) {  
         Object replacement = null;  
         if (fieldValue.getClass().getName().contains("javassist")) {  
           Class assistClass = fieldValue.getClass();  
           try {  
             Method m = assistClass.getMethod("writeReplace");  
             replacement = m.invoke(fieldValue);  
             String className = fieldValue.getClass().getName();  
             className = className.substring(0, className.indexOf("_$$_"));  
             if (!replacement.getClass().getName().contains("hibernate")) {  
               nullOutUninitializedFields(replacement, nulledObjects, depth+1, serializationType);  
               field.set(object, replacement);  
             } else {  
               replacement = null;  
             }  
           } catch (Exception e) {  
             System.out.println("Unable to write replace object " + fieldValue.getClass());  
           }  
         }  
         if (replacement == null) {  
              field.set(object, replacement);  
           /*String className = ((HibernateProxy) fieldValue).getHibernateLazyInitializer().getEntityName();  
           Class clazz = Class.forName(className);  
           Class[] constArgs = {Integer.class};  
           Constructor construct = null;  
           try {  
             construct = clazz.getConstructor(constArgs);  
             replacement = construct.newInstance((Integer) ((HibernateProxy) fieldValue).getHibernateLazyInitializer().getIdentifier());  
             field.set(object, replacement);  
           } catch (NoSuchMethodException nsme) {  
             try {  
               Field idField = clazz.getDeclaredField("id");  
               Constructor ct = clazz.getDeclaredConstructor();  
               ct.setAccessible(true);  
               replacement = ct.newInstance();  
               if (!idField.isAccessible()) {  
                 idField.setAccessible(true);  
               }  
               idField.set(replacement, (Integer) ((HibernateProxy) fieldValue).getHibernateLazyInitializer().getIdentifier());  
             } catch (Exception e) {  
               e.printStackTrace();  
               System.out.println("No id constructor and unable to set field id for base bean " + className);  
             }  
             field.set(object, replacement);  
           }*/  
         }  
       } else {  
         if (fieldValue instanceof org.hibernate.collection.PersistentCollection) {  
           // Replace hibernate specific collection types  
           if (!((org.hibernate.collection.PersistentCollection) fieldValue).wasInitialized()) {  
             field.set(object, null);  
           } else {  
             Object replacement = null;  
             if (fieldValue instanceof Map) {  
               replacement = new HashMap((Map) fieldValue);  
             } else if (fieldValue instanceof List) {  
               replacement = new ArrayList((List) fieldValue);  
             } else if (fieldValue instanceof Set) {  
               replacement = new HashSet((Set) fieldValue);  
             } else if (fieldValue instanceof Collection) {  
               replacement = new ArrayList((Collection)fieldValue);  
             }  
             setField(object, field.getName(), replacement);  
             nullOutUninitializedFields(replacement, nulledObjects, depth+1, serializationType);  
           }  
         } else {  
           if (fieldValue != null &&  
               (fieldValue.getClass().getName().contains("org.rhq") ||  
                 fieldValue instanceof Collection ||  
                 fieldValue instanceof Object[] ||   
                 fieldValue instanceof Map))  
             nullOutUninitializedFields((fieldValue), nulledObjects, depth+1, serializationType);  
         }  
       }  
       if (accessModifierFlag) {  
         field.setAccessible(false);  
       }  
     }  
   }  
   private static void nullOutFieldsByAccessors(Object value, Set<Integer> nulledObjects, int depth,  
     SerializationType serializationType) throws Exception {  
     // Null out any collections that aren't loaded  
     BeanInfo bi = Introspector.getBeanInfo(value.getClass(), Object.class);  
     PropertyDescriptor[] pds = bi.getPropertyDescriptors();  
     for (PropertyDescriptor pd : pds) {  
       Object propertyValue = null;  
       try {  
         propertyValue = pd.getReadMethod().invoke(value);  
       } catch (Throwable lie) {  
         if (LOG.isDebugEnabled()) {  
           LOG.debug("Couldn't load: " + pd.getName() + " off of " + value.getClass().getSimpleName(), lie);  
         }  
       }  
       if (!Hibernate.isInitialized(propertyValue)) {  
         try {  
           if (LOG.isDebugEnabled()) {  
             LOG.debug("Nulling out: " + pd.getName() + " off of " + value.getClass().getSimpleName());  
           }  
           Method writeMethod = pd.getWriteMethod();  
           if ((writeMethod != null) && (writeMethod.getAnnotation(XmlTransient.class) == null)) {  
             pd.getWriteMethod().invoke(value, new Object[] { null });  
           } else {  
             nullOutField(value, pd.getName());  
           }  
         } catch (Exception lie) {  
           LOG.debug("Couldn't null out: " + pd.getName() + " off of " + value.getClass().getSimpleName()  
             + " trying field access", lie);  
           nullOutField(value, pd.getName());  
         }  
       } else {  
         if ((propertyValue instanceof Collection)  
           || ((propertyValue != null) && propertyValue.getClass().getName().startsWith("org.rhq.core.domain"))) {  
           nullOutUninitializedFields(propertyValue, nulledObjects, depth + 1, serializationType);  
         }  
       }  
     }  
   }  
   private static void setField(Object object, String fieldName, Object newValue) {  
     try {  
       Field f = object.getClass().getDeclaredField(fieldName);  
       if (f != null) {  
         // try to set the field this way  
         f.setAccessible(true);  
         f.set(object, newValue);  
       }  
     } catch (NoSuchFieldException e) {  
       // ignore this  
     } catch (IllegalAccessException e) {  
       // ignore this  
     }  
   }  
   private static void nullOutField(Object value, String fieldName) {  
     try {  
       Field f = value.getClass().getDeclaredField(fieldName);  
       if (f != null) {  
         // try to set the field this way  
         f.setAccessible(true);  
         f.set(value, null);  
       }  
     } catch (NoSuchFieldException e) {  
       // ignore this  
     } catch (IllegalAccessException e) {  
       // ignore this  
     }  
   }  
 }  

Detalhe que eu comentei a parte do ID, pois nao é todo mundo que usa o nome "ID" como chave, portanto caso necessário implementem !

Ele funciona sem a parte do id, pois objetos lazy seriam "buscados" nesse momento, caso não haja a necessidade, deixem comentado pois ele setará NULL nos objetos.

\m/(0.0)\m/

Espero que tenha ajudado pessoal !

Abraços !

Nenhum comentário:

Postar um comentário