Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update persistent object with transient object using Hibernate

On a daily basis, data is imported via a webservice.

  1. I create a new (transient) instance of a pojo which i have mapped in hibernate via JPA annotations.
  2. I populate the data from the webservice into the transient instance
  3. I load the persistient object from the database that I want to update with the data in my transient instance.
  4. I somehow merge this transient instance with the persistent one. If the persistent object has a non null value on one of its fields, it will not be overritten by a potentially null value on the transient object. Basically I don't want to lose any data, just update where things have changed.

I know I could go through all the fields on the pojos, and check for null values, and update where appropriate, but I would prefer to have hibernate do this if it able to, as it would decrease the chances of me adding a field and forgetting to add it to this manual merge process.

Can hibernate perform step #4 above?

like image 941
Matt Broekhuis Avatar asked Jan 24 '11 06:01

Matt Broekhuis


1 Answers

No, Hibernate (or JPA) does not provide that functionality out of the box, but it's not hard to achieve using the JavaBeans mechanism (or one of many libraries that provide an abstraction layer over that).

Using Plain Javabeans Introspection

Here's a method that copies all properties from beanA to beanB if they are null in beanB, using the standard JavaBeans Introspector mechanism:

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

Of course this is just a quick-and-dirty implementation, but it should get you started.

Using Apache Commons / BeanUtils

Here is a slightly more elegant version using Commons / BeanUtils. It hides the reflection from you and provides map-based property access:

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

Using Spring's BeanWrapper

And here's another version using Spring's BeanWrapper interface (it's the least verbose, because Spring provides an abstraction above all the reflection and does it's own Exception handling), but unfortunately the BeanWrapper technology is only available with the Spring IOC container (which is of course only unfortunate if you don't use the container):

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);
    }
}
like image 103
Sean Patrick Floyd Avatar answered Sep 27 '22 22:09

Sean Patrick Floyd