Hibernate. Entity change\revision history

落花浮王杯 提交于 2019-12-24 00:59:18

问题


Framework: Spring 3 with Hibernate 3.

Database: Oracle 11

Requirement: Our application will have Event objects. Each of these Event objects is a encapsulation of a set of Entity properties (table columns). So whenever any Entity is updated in our system, we need to make sure if the changes made to the Entity, is part of any of the Event objects and if yes, we need to store this information in the database along with a transcript recording the actual change.

Solutions (That I know of):

  1. Use a full blown auditing framework like Hibernate Envers and write a small wrapper around the audit table query functionality, to accomplish what I need. One question is, does hibernate Envers have an easy way to provide what has changed between two revisions.

  2. Use Custom Annotations to mark attributes to belong to an Event and Use AOP to monitor changes to these attributes as part of the save operations and invoke a custom write operations.

I like the second idea better.

Please share your opinions\thoughts on the best possible way to get this done. Is there an existing solution for a problem of this sort?


回答1:


i had similiar requirement in project where i am suppose to take snapshot of complex object graph before saving.

solution i have applied is 1) developed custom annotation @Archivable with certain attribute like nullify,ignore, orignal, setArchiveFlag

2) written hiberante deep cloner utility which create replica of object and insert into same table. deep cloner works on simple trick searlize and then desearlize object this will create new instances and then set id and version to null.

3) used cloner utility in entity interceptor to take decision weather to archive or not.

below is some of that code.

@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.TYPE })
public @interface Archivable {

    /** This will mark property as null in clone */
    public String[] nullify() default {};

    /**
     * If property is archivable but not from enclosing entity then specify as
     * ignore.
     */
    public String[] ignore() default {};

    /**
     * sets original reference to clone for back refer data. This annotation is
     * applicable to only root entity from where archiving started.
     * 
     * @return
     */
    public String original() default "";

    /**
     * if marks cloned entity to archived, assumes flag to be "isArchived". 
     * @return
     */
    public boolean setArchiveFlag() default false;
}


@Component
public class ClonerUtils {

    private static final String IS_ARCHIVED = "isArchived";
    @Autowired
    private SessionFactory sessionFactory;

    public Object copyAndSave(Serializable obj) throws Exception {
        List<BaseEntity> entities = new ArrayList<BaseEntity>();
        Object clone=this.copy(obj,entities);
        this.save(clone, entities);
        return clone;
    }

    public Object copy(Serializable obj,List<BaseEntity> entities) throws Exception{
        recursiveInitliaze(obj);
        Object clone = SerializationHelper.clone(obj);
        prepareHibernateObject(clone, entities);
        if(!getOriginal(obj).equals("")){
            PropertyUtils.setSimpleProperty(clone, getOriginal(obj), obj);
        }
        return clone;
    }


    private void save(Object obj,List<BaseEntity> entities){
        for (BaseEntity baseEntity : entities) {
            sessionFactory.getCurrentSession().save(baseEntity);
        }
    }

    @SuppressWarnings("unchecked")
    public void recursiveInitliaze(Object obj) throws Exception {
        if (!isArchivable(obj)) {
            return;
        }
        if(!Hibernate.isInitialized(obj))
            Hibernate.initialize(obj);
        PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(obj);
        for (PropertyDescriptor propertyDescriptor : properties) {
            Object origProp = PropertyUtils.getProperty(obj, propertyDescriptor.getName());
            if (origProp != null && isArchivable(origProp) && !isIgnore(propertyDescriptor, obj)) {
                this.recursiveInitliaze(origProp);
            }
            if (origProp instanceof Collection && origProp != null) {               
                for (Object item : (Collection) origProp) {
                    this.recursiveInitliaze(item);
                }
            }
        }
    }


    @SuppressWarnings("unchecked")
    private void prepareHibernateObject(Object obj, List entities) throws Exception {
        if (!isArchivable(obj)) {
            return;
        }
        if (obj instanceof BaseEntity) {
            ((BaseEntity) obj).setId(null);
            ((BaseEntity) obj).setVersion(null);
            if(hasArchiveFlag(obj)){
                PropertyUtils.setSimpleProperty(obj, IS_ARCHIVED, true);
            }
            entities.add(obj);
        }
        String[] nullifyList = getNullifyList(obj);
        for (String prop : nullifyList) {
            PropertyUtils.setProperty(obj, prop, null);
        }
        PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(obj);
        for (PropertyDescriptor propertyDescriptor : properties) {
            if (isIgnore(propertyDescriptor, obj)) {
                continue;
            }
            Object origProp = PropertyUtils.getProperty(obj, propertyDescriptor.getName());         
            if (origProp != null && isArchivable(origProp)) {
                this.prepareHibernateObject(origProp, entities);
            }   
            /**  This code is for element collection */
            if(origProp instanceof PersistentBag){
                Collection elemColl=createNewCollection(origProp);
                PersistentBag pColl=(PersistentBag) origProp;
                elemColl.addAll(pColl.subList(0, pColl.size()));
                PropertyUtils.setSimpleProperty(obj, propertyDescriptor.getName(), elemColl);
                continue;
            }
            if (origProp instanceof Collection && origProp != null) {
                Collection newCollection  = createNewCollection(origProp);
                PropertyUtils.setSimpleProperty(obj, propertyDescriptor.getName(), newCollection);
                for (Object item : (Collection) origProp) {
                    this.prepareHibernateObject(item, entities);
                }
            }
        }
    }



    @SuppressWarnings("unchecked")
    private Collection createNewCollection(Object origProp) {
        try {
            if(List.class.isAssignableFrom(origProp.getClass()))
                return new ArrayList((Collection)origProp);
            else if(Set.class.isAssignableFrom(origProp.getClass()))
                    return new HashSet((Collection)origProp);
            else{
                Collection tempColl=(Collection) BeanUtils.cloneBean(origProp);
                tempColl.clear();
                return tempColl;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ArrayList();     
    }

    private boolean isIgnore(PropertyDescriptor propertyDescriptor,Object obj){
        String propertyName=propertyDescriptor.getName();
        String[] ignores=getIgnoreValue(obj);
        return ArrayUtils.contains(ignores, propertyName);
    }

    private String[] getIgnoreValue(Object obj) {
        String[] ignore=obj.getClass().getAnnotation(Archivable.class).ignore();
        return ignore==null?new String[]{}:ignore;
    }

    private String[] getNullifyList(Object obj) {
        String[] nullify=obj.getClass().getAnnotation(Archivable.class).nullify();
        return nullify==null?new String[]{}:nullify;
    }

    public boolean isArchivable(Object obj) {
        return obj.getClass().isAnnotationPresent(Archivable.class);
    }


    private String getOriginal(Object obj) {
        String original=obj.getClass().getAnnotation(Archivable.class).original();
        return original==null?"":original;
    }

    private boolean hasArchiveFlag(Object obj) {        
        return obj.getClass().getAnnotation(Archivable.class).setArchiveFlag();
    }

    @SuppressWarnings({ "unchecked", "unused" })
    private Collection getElemColl(Object obj, Object origProp) {
        Collection elemColl=createNewCollection(origProp);
        for (Object object : (Collection)origProp) {
            elemColl.add(object);
        }
        return elemColl;
    }

    @SuppressWarnings("unused")
    private boolean isElementCollection(Object obj, String name) {
        try {
            Annotation[] annotations=obj.getClass().getDeclaredField(name).getAnnotations();
            for (Annotation annotation : annotations) {
                if(annotation.annotationType() == ElementCollection.class)
                    return true;
            }
        } catch (Exception e) {
            e.printStackTrace();            
        }
        return false;
    }
    }


来源:https://stackoverflow.com/questions/11529237/hibernate-entity-change-revision-history

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!