使用Hibernate更新持久对象和瞬态对象的方法

7

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

  1. 我创建了一个新的(瞬态)POJO实例,并使用JPA注释在Hibernate中进行了映射。
  2. 我将来自Web服务的数据填充到瞬态实例中。
  3. 我从数据库中加载要使用瞬态实例更新的持久化对象。
  4. 我以某种方式合并这个瞬态实例和持久化实例。如果持久化对象在其字段上有非空值,则不会被潜在的瞬态对象上的可能为空的值覆盖。基本上,我不想丢失任何数据,只是在发生更改时进行更新。

我知道我可以遍历POJO上的所有字段,并检查null值,然后在适当的地方进行更新,但如果可能的话,我更喜欢让Hibernate执行此操作,因为这将减少我添加字段并忘记将其添加到此手动合并过程的机会。

Hibernate能执行上述第4步吗?


如果您的瞬态POJO对象将包含所有最新数据,则只需将相关持久性对象的PK附加到瞬态对象上。然后使用session更新它。无需从对象中获取和设置数据。 - Nikunj
2个回答

11
No,Hibernate(或JPA)不会直接提供该功能,但使用JavaBeans机制(或许多提供对其进行抽象的库之一)很容易实现。

使用普通JavaBean内省

这是一种方法,如果beanB中的属性为null,则使用标准的JavaBeans Introspector机制从beanA复制所有属性到beanB

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

我不知道有多少POJO需要那个功能。如果只有一个,那么这可能听起来有些过度了。但事实上,反射是一个很好的解决方案。 - Adeel Ansari
@Sean,我使用了你的代码将beanA的属性值复制到beanB的属性值上。但是你的方法抛出了“org.springframework.beans.NotReadablePropertyException”异常。引发此异常的属性是一个名为“available”的布尔值。当从数据库中检索beanA的布尔值时,它的值为1,然后我将beanA保存在模型中,以便稍后在另一个方法中将其值复制到beanB中。但是,当我从模型属性中检索beanA时,它的值为null。我认为这就是为什么会抛出错误的原因。你能解释一下为什么它会自动将布尔值设置为null吗? - h-rai
如果值可能为空,你必须使用Boolean,而不是boolean。 - Sean Patrick Floyd
@SeanPatrickFloyd我已确保所有值都分配给了包装类变量,例如Boolean、Integer等。我认为问题在于我试图将单个对象分配给两个不同的变量。因此,我尝试在编辑表单提交后检索一个新对象。现在我得到了一个“java.lang.NoSuchMethodException: java.lang.Integer.<init>()”错误。我只是试图将属性值复制到具有空值的属性。 - h-rai

0
Hibernate 不会为您完成这项任务。您可以更改您的 setter 方法以实现此功能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接