hibernate 。实体变更\修订历史

标签 hibernate audit-logging

框架:Spring 3 和 Hibernate 3。

数据库:Oracle 11

要求:我们的应用程序将具有事件对象。这些事件对象中的每一个都是一组实体属性(表列)的封装。因此,每当我们系统中更新任何实体时,我们都需要确保对实体所做的更改是否是任何事件对象的一部分,如果是,我们需要将此信息以及记录该事件的记录一起存储在数据库中实际变化。

解决方案(我所知道的):

  1. 使用 Hibernate Envers 等完整的审核框架,并围绕审核表查询功能编写一个小包装器,以完成我的需要。一个问题是,hibernate Envers 是否有一种简单的方法来提供两个修订版之间的更改。

  2. 使用自定义注释标记属于事件的属性,并使用 AOP 监视这些属性的更改作为保存操作的一部分并调用自定义写入操作。

我更喜欢第二个想法。

请分享您对完成此任务的最佳方式的意见\想法。对于此类问题是否有现有的解决方案?

最佳答案

我在项目中有类似的要求,我应该在保存之前拍摄复杂对象图的快照。

我应用的解决方案是 1)开发自定义注释@Archivable,具有某些属性,例如nullify,ignore,orignal,setArchiveFlag

2) 编写的 hiberante 深度克隆实用程序,它创建对象的副本并插入到同一个表中。深度克隆器使用简单的技巧进行序列化,然后对对象进行去序列化,这将创建新实例,然后将 id 和版本设置为 null。

3) 在实体拦截器中使用克隆器实用程序来决定是否存档。

下面是一些代码。

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

关于 hibernate 。实体变更\修订历史,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11529237/

相关文章:

linux - 使用 Linux 审计系统追踪 Web shell 攻击

java - Hibernate 一对多到 JSON 给出 : 500 internal server error

SQL Server审计同表数据

spring - 如何使用 Jhipster 生成的代码为注销添加审核?

grails - Grails审核日志记录插件onSave in UserRole

grails - 为用户登录插入审核日志记录条目

java - 使用实体管理器查询日期

java - 每月 hibernate 表

java - 在 Hibernate 中使用@MappedSuperClass 进行软删除

java - HQL select 子句中的多个选择