On a daily basis, data is imported via a webservice.
- I create a new (transient) instance of a pojo which i have mapped in hibernate via JPA annotations.
- I populate the data from the webservice into the transient instance
- I load the persistient object from the database that I want to update with the data in my transient instance.
- 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?
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);
}
}
Hibernate wouldn't do this for you. You can alter your setter
s in order to have this functionality.
精彩评论