一尘不染

使用Hibernate使用瞬态对象更新持久对象

hibernate

每天都会通过Web服务导入数据。

  1. 我创建了一个pojo 的 新(临时)实例 ,该 实例 已通过JPA注释在hibernate中进行了映射。
  2. 我将来自Web服务的数据填充到临时实例中
  3. 我从数据库中加载要使用瞬时实例中的数据更新的持久对象。
  4. 我以某种方式将此瞬态实例与持久实例合并。如果持久对象在其字段之一上具有非null值,则不会被瞬态对象上的潜在null值覆盖。基本上,我不想丢失任何数据,只需更新发生变化的地方即可。

我知道我可以遍历pojos上的所有字段,并检查null值,并在适当的地方进行更新,但是我更愿意让hibernate做到这一点,因为这样做会减少我添加字段和忘记将其添加到此手动合并过程中。

hibernate状态可以执行上面的步骤4吗?


阅读 212

收藏
2020-06-20

共1个答案

一尘不染

不,Hibernate(或JPA)没有提供现成的功能,但是使用JavaBeans机制(或在其之上提供抽象层的许多库之一)并不难实现。

使用纯Javabeans自省

这是一种方法,该方法使用标准JavaBeans 机制从中复制所有属性到beanAbeanB如果它们为null
beanBIntrospector

public static void copyBeanProperties(
    final Object beanA, final Object beanB){

    if(beanA.getClass() != beanB.getClass()){
        // actually, this may be a problem, because beanB may be a
        // cglib-created subclass
        throw new IllegalArgumentException();
    }
    try{
        for( final PropertyDescriptor pd :
            Introspector
              .getBeanInfo(beanB.getClass(), Object.class)
              .getPropertyDescriptors()){
            if(pd.getReadMethod().invoke(beanB)==null){
                pd.getWriteMethod().invoke(beanB,
                    pd.getReadMethod().invoke(beanA)
                );
            }
        }
    } catch(IntrospectionException e){
        throw new IllegalStateException(e);
    } catch(IllegalAccessException e){
        throw new IllegalStateException(e);
    } catch(InvocationTargetException e){
        throw new IllegalStateException(e);
    }
}

当然,这只是一个快速而肮脏的实现,但这应该可以帮助您入门。

使用Apache Commons / BeanUtils

这是一个使用Commons /
BeanUtils的
优雅版本。它隐藏了您的反射,并提供基于地图的属性访问:

public static void copyBeanProperties(final Object beanA, final Object beanB){
    try{
        @SuppressWarnings("unchecked") // this should be safe
        final Map<String, Object> beanAProps = PropertyUtils.describe(beanA);
        @SuppressWarnings("unchecked") // this should be safe
        final Map<String, Object> beanBProps = PropertyUtils.describe(beanB);

        if(!beanAProps.keySet().containsAll(beanBProps.keySet())){
            throw new IllegalArgumentException("Incompatible types: "
                + beanA + ", " + beanB);
        }
        for(final Entry<String, Object> entryA : beanAProps.entrySet()){
            if(beanBProps.get(entryA.getKey()) == null){
                PropertyUtils.setMappedProperty(beanB, entryA.getKey(),
                    entryA.getValue());
            }
        }
    } catch(final IllegalAccessException e){
        throw new IllegalStateException(e);
    } catch(final InvocationTargetException e){
        throw new IllegalStateException(e);
    } catch(final NoSuchMethodException e){
        throw new IllegalStateException(e);
    }
}

使用Spring的BeanWrapper

这是使用Spring
BeanWrapper接口
的另一个版本(最不冗长,因为Spring在所有反射之上都提供了抽象,并且它是自己的异常处理),但是不幸的BeanWrapper是,该技术仅适用于Spring
IOC容器(当然,如果您不使用容器):

public static void copyBeanProperties(final Object beanA, final Object beanB){

    final BeanWrapper wrapperA = new BeanWrapperImpl(beanA);
    final BeanWrapper wrapperB = new BeanWrapperImpl(beanB);

    try{
        for(final PropertyDescriptor descriptor : wrapperB
            .getPropertyDescriptors()){

            final String propertyName = descriptor.getName();
            if(wrapperB.getPropertyValue(propertyName) == null){
                wrapperB.setPropertyValue(propertyName,
                    wrapperA.getPropertyValue(propertyName));
            }
        }
    } catch(final /* unchecked */ InvalidPropertyException e){
        throw new IllegalArgumentException("Incompatible types: " + beanA
            + ", " + beanB, e);
    }
}
2020-06-20