Saturday, June 25, 2005
Why use beanlib with Hibernate ?
(This post is about Beanlib 2.x. Please go to the JavaBean Library home page for the latest and greatest.)
Put simply, in cloning a Hibernate entity bean, we want to specify how deep we want to go in the object graph. Is it a 100% deep clone, a skin deep shallow clone, or some other combinations ? How about beans that involve a one-to-many collection ? The answer to this question has a significant impact on both the performance and memory footprint of the cloning process.
The JavaBean Library provides the following options
Put simply, in cloning a Hibernate entity bean, we want to specify how deep we want to go in the object graph. Is it a 100% deep clone, a skin deep shallow clone, or some other combinations ? How about beans that involve a one-to-many collection ? The answer to this question has a significant impact on both the performance and memory footprint of the cloning process.
The JavaBean Library provides the following options
- 100% deep clone which will result in eagerly fetching all related entites and populating the values into pure POJO's which therefore will get rid of all CGLIB enhanced instances;
- Shallow clone which will result in cloning the top level bean and the immediately contained member fields of Java primitive types, String, Date, etc., but will exclude instances of Collection and application specific types. The excluded member fields will be set to null;
- Partial deep clone that extends only to a specified set of classes and a specified set of "collection properties". A collection property can be specified via a class named CollectionPropertyName. This allows an arbitrary portion of the object graph to be deeply cloned;
- Provide your own customized "Vetoer" by implementing the BeanPopulatable interface to control the population decision of each bean property;
- Provide your own customized BeanPopulatable and/or DetailedBeanPopulatable to completely override the population decision of each bean property.
- A new enhancement can be found here.
Comments:
<< Home
First, let me say thanks for creating a very nice utility. I was working on implementing my own clone functionality when I came accross yours in a posting on the hibernate forum. You save me a lot of work!
Here is my question: is it possible to not clone specific properties besides classes and collections? The problem I am having is, is all of the associated objects of my cloned objects also retain their identifier (id), so, when I try to persist my cloned object I am getting DuplicateKey exceptions.
Here is my question: is it possible to not clone specific properties besides classes and collections? The problem I am having is, is all of the associated objects of my cloned objects also retain their identifier (id), so, when I try to persist my cloned object I am getting DuplicateKey exceptions.
You can provide your own customized "Vetoer" by implementing the BeanPopulatable interface to control the population of each bean property. For example:
BeanPopulatable vetoer = new MyVetoer(session);
// Deeply replicate the "fromBean" but not the ID's,
// eagerly fetching as necessary
Object toBean = HibernateBeanReplicator.dupEntityBean(fromBean, vetoer);
/** Not to populate ID's. */
public class MyVetoer implements BeanPopulatable {
private SessionFactory sessionFactory;
public MyVetoer(Session session) {
this.sessionFactory = session.getSessionFactory();
}
public boolean shouldPopulate(String propertyName, Method readerMethod)
{
if (Collection.class.isAssignableFrom(readerMethod.getReturnType()))
return true;
// Skip populating if it is an identifier property.
Class c = readerMethod.getDeclaringClass();
if (Enhancer.isEnhanced(c)) {
// figure out the pre-enhanced class
c = c.getSuperclass();
}
ClassMetadata classMetaData = sessionFactory.getClassMetadata(c);
return !classMetaData.getIdentifierPropertyName().equals(propertyName);
}
}
(You need beanlib-1.2.8+ for this to work.)
BeanPopulatable vetoer = new MyVetoer(session);
// Deeply replicate the "fromBean" but not the ID's,
// eagerly fetching as necessary
Object toBean = HibernateBeanReplicator.dupEntityBean(fromBean, vetoer);
/** Not to populate ID's. */
public class MyVetoer implements BeanPopulatable {
private SessionFactory sessionFactory;
public MyVetoer(Session session) {
this.sessionFactory = session.getSessionFactory();
}
public boolean shouldPopulate(String propertyName, Method readerMethod)
{
if (Collection.class.isAssignableFrom(readerMethod.getReturnType()))
return true;
// Skip populating if it is an identifier property.
Class c = readerMethod.getDeclaringClass();
if (Enhancer.isEnhanced(c)) {
// figure out the pre-enhanced class
c = c.getSuperclass();
}
ClassMetadata classMetaData = sessionFactory.getClassMetadata(c);
return !classMetaData.getIdentifierPropertyName().equals(propertyName);
}
}
(You need beanlib-1.2.8+ for this to work.)
Hi, I am having problems with the sample code you provided:
Class c = readerMethod.getDeclaringClass();
if (Enhancer.isEnhanced(c)) {
// figure out the pre-enhanced class
c = c.getSuperclass();
}
ClassMetadata classMetaData = sessionFactory.getClassMetadata(c);
My super class does not have a hibernate mapping (it is just a base class, no corresponding table in my DB), thus classMetaData will always return null. However, If I insert the correct class everything seems to work fine.
Perhaps, the hibernate forum is a better place to post these questions?
Class c = readerMethod.getDeclaringClass();
if (Enhancer.isEnhanced(c)) {
// figure out the pre-enhanced class
c = c.getSuperclass();
}
ClassMetadata classMetaData = sessionFactory.getClassMetadata(c);
My super class does not have a hibernate mapping (it is just a base class, no corresponding table in my DB), thus classMetaData will always return null. However, If I insert the correct class everything seems to work fine.
Perhaps, the hibernate forum is a better place to post these questions?
Your problem can be easily solved by changing:
return classMetaData
.getIdentifierPropertyName()
.equals(propertyName);
To:
return classMetaData == null
|| classMetaData
.getIdentifierPropertyName()
.equals(propertyName);
return classMetaData
.getIdentifierPropertyName()
.equals(propertyName);
To:
return classMetaData == null
|| classMetaData
.getIdentifierPropertyName()
.equals(propertyName);
Oops, I meant change to:
return classMetaData == null
|| !classMetaData
.getIdentifierPropertyName()
.equals(propertyName);
(Note the negate sign.)
return classMetaData == null
|| !classMetaData
.getIdentifierPropertyName()
.equals(propertyName);
(Note the negate sign.)
The questions and answers are actually already posted in the Hibernate forum:
http://forum.hibernate.org/viewtopic.php?p=2248802#2248802
http://forum.hibernate.org/viewtopic.php?p=2248802#2248802
This is great stuff and very useful! However, I have a problem: My Hibernated domain model contains certain required values (i.e. data that is not nullable in the database) that are set via package or protected scoped setter methods. For example,
public class A {
public String getValue1() {...}
protected void setValue1(String v) {...}
}
After looking at your code, I noticed that the BeanPopulator's populate method does a check to see if a method is public. If it's not public, you ignore the method. This seems like a problem to me (not only for my case) because Hibernate suggests that you always make the methods that set your identifier private. Hibernate assumes you don't have a security manager installed, and by doing that, they can call the setter method using reflection. You could do the same thing. Would it be possible to remove the public check on the method? Otherwise, I'll have to fork off of your code and do it myself.
Thanks!
public class A {
public String getValue1() {...}
protected void setValue1(String v) {...}
}
After looking at your code, I noticed that the BeanPopulator's populate method does a check to see if a method is public. If it's not public, you ignore the method. This seems like a problem to me (not only for my case) because Hibernate suggests that you always make the methods that set your identifier private. Hibernate assumes you don't have a security manager installed, and by doing that, they can call the setter method using reflection. You could do the same thing. Would it be possible to remove the public check on the method? Otherwise, I'll have to fork off of your code and do it myself.
Thanks!
Beanlib-2.0+ should solve your problem. The default behavior of HibernateBeanReplicator now permits protected setter methods.
Is there a particular reason you chose to only support Sets? I make wide use of Lists and Maps as well. Is it common for people to only use Set mappings?
No good reason except I've only used Set (probably due to the generated code via Middlegen?). I will add support for other collection types when needed. Maybe this is the time to start.
Hi,
Cool stuff!
I have a question about using Enums. With Hibernate 3 I use enums for various status fields. These are mapped to int columns.
Now, replicating this beans is problematic, Enums do not have public constructors.
Can I do a deep-copy on a bean, but specify to shallow-copy the enum-types?
For now, I implemented a vetoer that checks the type of the property and returns false for enum types. But then, I get 'null' for those fields.
Cheers,
Edwin van der Elst
Cool stuff!
I have a question about using Enums. With Hibernate 3 I use enums for various status fields. These are mapped to int columns.
Now, replicating this beans is problematic, Enums do not have public constructors.
Can I do a deep-copy on a bean, but specify to shallow-copy the enum-types?
For now, I implemented a vetoer that checks the type of the property and returns false for enum types. But then, I get 'null' for those fields.
Cheers,
Edwin van der Elst
I found a solution...
I created a detailedBeanPopulatable:
if (readerMethod.getReturnType().isEnum()) {
try {
setterMethod.invoke(toBean, new Object[] {readerMethod.invoke(fromBean)});
}
catch (IllegalArgumentException e) {
}
etc.
But now it is a side-effect of the 'shouldPopulate'. That might not be a clean solution, but it works :-)
I created a detailedBeanPopulatable:
if (readerMethod.getReturnType().isEnum()) {
try {
setterMethod.invoke(toBean, new Object[] {readerMethod.invoke(fromBean)});
}
catch (IllegalArgumentException e) {
}
etc.
But now it is a side-effect of the 'shouldPopulate'. That might not be a clean solution, but it works :-)
Clever hack, Edwin :)
Check out Beanlib 2.4.1 and my latest blog. This Enum problem could be solved via the general CustomHibernateBeanTransformable interface:
http://hansonchar.blogspot.com/2005/08/beanlib-241.html
Hope it makes sense.
Check out Beanlib 2.4.1 and my latest blog. This Enum problem could be solved via the general CustomHibernateBeanTransformable interface:
http://hansonchar.blogspot.com/2005/08/beanlib-241.html
Hope it makes sense.
I will have a look. Looks promising.
Another problem at the moment is that I have a class hierarchy with abstract classes.
Like this:
abstract class A {}
class B extends A {}
class C extends A {}
class D {
List<A> list;
}
Now, hibernate works ok with this. But when I try to replicate an instance of D where the list contains instances of B and C, the beanlib tries to instantiate 'A' (which fails, because it is abstract).
Edwin
Another problem at the moment is that I have a class hierarchy with abstract classes.
Like this:
abstract class A {}
class B extends A {}
class C extends A {}
class D {
List<A> list;
}
Now, hibernate works ok with this. But when I try to replicate an instance of D where the list contains instances of B and C, the beanlib tries to instantiate 'A' (which fails, because it is abstract).
Edwin
Which version of beanlib are you using ? I just ran the following junit test on cloning a list of subtypes of an abstract type, and it works:
public class SubTypeTest extends TestCase {
Log log = LogFactory.getLog(this.getClass());
public static abstract class A{};
public static class B extends A{};
public static class C extends A{};
public static class D {
private List<A>>list = new ArrayList<A>();
public List<A>getList() {
return list;
}
public void setList(List<A> list) {
this.list = list;
}
public void addToList(A a) {
list.add(a);
}
}
public void testCopy() {
D d = new D();
d.addToList(new B());
d.addToList(new C());
HibernateBeanReplicator replicator = new Hibernate3BeanReplicator();
D d2 = (D)replicator.deepCopy(d);
for (A a : d2.getList()) {
Class type = a.getClass();
log.info(type);
assertFalse(type == A.class);
assertTrue(type == B.class || type == C.class);
}
}
}
public class SubTypeTest extends TestCase {
Log log = LogFactory.getLog(this.getClass());
public static abstract class A{};
public static class B extends A{};
public static class C extends A{};
public static class D {
private List<A>>list = new ArrayList<A>();
public List<A>getList() {
return list;
}
public void setList(List<A> list) {
this.list = list;
}
public void addToList(A a) {
list.add(a);
}
}
public void testCopy() {
D d = new D();
d.addToList(new B());
d.addToList(new C());
HibernateBeanReplicator replicator = new Hibernate3BeanReplicator();
D d2 = (D)replicator.deepCopy(d);
for (A a : d2.getList()) {
Class type = a.getClass();
log.info(type);
assertFalse(type == A.class);
assertTrue(type == B.class || type == C.class);
}
}
}
Here is a junit test to demo how Enum can be copied via a Custom Transformer:
public class EnumTest extends TestCase {
public static enum Status {
BEGIN, WIP, END;
}
public static class C {
private Status status;
private String testString;
public Status getStatus() {return status;}
public void setStatus(Status status) {this.status = status;}
public String getTestString() {return testString;}
public void setTestString(String testString) {this.testString = testString;}
}
public void testCopy() {
C c = new C();
c.setStatus(Status.BEGIN);
c.setTestString("testStr");
HibernateBeanReplicator replicator = new Hibernate3BeanReplicator().initCustomTransformer(
new CustomHibernateBeanTransformable() {
public boolean isTransformable(Object from, Class toClass, HibernateBeanTransformableSpi hibernateBeanTransformer) {
return toClass.isEnum();
}
public Object transform(Object in, Class toClass) {return in;}
});
C c2 = (C)replicator.deepCopy(c);
assertFalse (c2 == c);
assertTrue(c2.getStatus() == c.getStatus());
assertEquals(c.getTestString(), c2.getTestString());
}
}
public class EnumTest extends TestCase {
public static enum Status {
BEGIN, WIP, END;
}
public static class C {
private Status status;
private String testString;
public Status getStatus() {return status;}
public void setStatus(Status status) {this.status = status;}
public String getTestString() {return testString;}
public void setTestString(String testString) {this.testString = testString;}
}
public void testCopy() {
C c = new C();
c.setStatus(Status.BEGIN);
c.setTestString("testStr");
HibernateBeanReplicator replicator = new Hibernate3BeanReplicator().initCustomTransformer(
new CustomHibernateBeanTransformable() {
public boolean isTransformable(Object from, Class toClass, HibernateBeanTransformableSpi hibernateBeanTransformer) {
return toClass.isEnum();
}
public Object transform(Object in, Class toClass) {return in;}
});
C c2 = (C)replicator.deepCopy(c);
assertFalse (c2 == c);
assertTrue(c2.getStatus() == c.getStatus());
assertEquals(c.getTestString(), c2.getTestString());
}
}
Back to the abstract class issue...
It works in the test-case, but not with hibernate. I debugged the Hibernate3BeanReplicator.copy method.
fromClass=LayoutElement$$EnhancerByCGLIB
toClass=Class<T>(.....LayoutElement$$EnhancerByCGLIB)
When I inspect the object I try to copy, I see the following in the debugger:
LayoutElement$$EnhancerByCGLIB
- CGLIB$CALLBACK_0=CGLIBLazyInitalizer
-- entityName = nl.......LayoutElement
-- target = ContentItem
Now, target (ContentItem) points to the actual class I persisted!
I hope this makes sense......
Cheers,
Edwin van der Elst
It works in the test-case, but not with hibernate. I debugged the Hibernate3BeanReplicator.copy method.
fromClass=LayoutElement$$EnhancerByCGLIB
toClass=Class<T>(.....LayoutElement$$EnhancerByCGLIB)
When I inspect the object I try to copy, I see the following in the debugger:
LayoutElement$$EnhancerByCGLIB
- CGLIB$CALLBACK_0=CGLIBLazyInitalizer
-- entityName = nl.......LayoutElement
-- target = ContentItem
Now, target (ContentItem) points to the actual class I persisted!
I hope this makes sense......
Cheers,
Edwin van der Elst
LayoutElement is not a Collection class, right ? This sounds similar to a Hibernate bug which I encountered before:
http://hansonchar.blogspot.com/2005/06/hibernate-305-returns-corrupted-object.html
The good news is eager fetching should fix it.
http://hansonchar.blogspot.com/2005/06/hibernate-305-returns-corrupted-object.html
The good news is eager fetching should fix it.
I suggest future posting should be made to the Sourceforge beanlib public forum at http://sourceforge.net/forum/?group_id=14 so others can more easily share the information.
Hi,
I am starting to evaluate beanlib 2.4.1. It seems to be very good!
My problem is, that in my application the hibernate classes (e.g. PersistentList) must be loaded dynamically.
So the "getClass().getPackage()" will evaluate to "null", if a PersistentList is handled.
Therefore I get a nullpointer exception in class "HibernateBeanTransformer" for methods "createToCollection" and "createToMap".
Could You perhaps change these methods to ClassUtils.getPackageName(fromClass)?
Thanx
I am starting to evaluate beanlib 2.4.1. It seems to be very good!
My problem is, that in my application the hibernate classes (e.g. PersistentList) must be loaded dynamically.
So the "getClass().getPackage()" will evaluate to "null", if a PersistentList is handled.
Therefore I get a nullpointer exception in class "HibernateBeanTransformer" for methods "createToCollection" and "createToMap".
Could You perhaps change these methods to ClassUtils.getPackageName(fromClass)?
Thanx
Hi,
I find your beanlib (2.4.2 coupled with Hibernate 3.0.5) nice and I think it feet exactly what I need. (I didn't not use beanlib 3.0.4 because I got a problem with class version)
However, I am facing a problem concerning a partial deep clone although I dig trough the source code.
I tried to use HibernateBeanReplicator.copy()" method with the following initializers "initCollectionPropertyNameSet" and/or "initEntityBeanClassSet" to specify which objects and properties from my domain I would like to replicate. But I still get the same behaviour: the same as deepCopy, a huge replication.
Thanks
I find your beanlib (2.4.2 coupled with Hibernate 3.0.5) nice and I think it feet exactly what I need. (I didn't not use beanlib 3.0.4 because I got a problem with class version)
However, I am facing a problem concerning a partial deep clone although I dig trough the source code.
I tried to use HibernateBeanReplicator.copy()" method with the following initializers "initCollectionPropertyNameSet" and/or "initEntityBeanClassSet" to specify which objects and properties from my domain I would like to replicate. But I still get the same behaviour: the same as deepCopy, a huge replication.
Thanks
Can you post your class, and the code related to the initiCollectionPropertyNameSet to the beanlib's Sourceforge Help forum, so I can have a look at the problem ?
Thanks for this great utility... is there also a version available that works with jdk 1.4.2?
I can't use Java 5 in my current projects :(
I can't use Java 5 in my current projects :(
is anyone using beanlib 5.0.2. I keep getting this error for 3 different methods used in the HibernateBeanReplicator simple examples.
bad class file: C:\dev\workspace\camod\software\camod\WebRoot\WEB-INF\lib\beanlib-hibernate-5.0.2beta.jar(net/sf/beanlib/hibernate/HibernateBeanReplicator.class)
[javac] class file has wrong version 50.0, should be 49.0
bad class file: C:\dev\workspace\camod\software\camod\WebRoot\WEB-INF\lib\beanlib-hibernate-5.0.2beta.jar(net/sf/beanlib/hibernate/HibernateBeanReplicator.class)
[javac] class file has wrong version 50.0, should be 49.0
found the answer, but upgrading to use jdk 6 may not be an option in my project. Does anyone have working code to use 3.x while excluding copy of the object ids?
I would love working code for versions above 2.x since there were many changes.
I would love working code for versions above 2.x since there were many changes.
Just replaced the latest download bundles at sourceforge with classes compiled with jdk5. Can you download and retry to see if it works for you now ?
Hi,
Is this library is in development, I can't see any updates since 2011. Can I found any alternatives to this, if this is not in development. Many thanks.
Is this library is in development, I can't see any updates since 2011. Can I found any alternatives to this, if this is not in development. Many thanks.
No, this library is no longer in active development. However, the advantage of opensource is that you always have access to the source code and therefore you can make any bug fixes or enhancement if necessary.
Post a Comment
<< Home