What is the correct way to initialize collection of an entity (POJO) in Spring-Hibernate project?
I have a POJO class, say Foo, which has a Set of other entity instances, say bars. Also there are standart misc classes for such project: service and dao for both Foo and Bar.
I want BarService to get the Set of Bar instances associated with some Foo. Now I have the following code, wich I believe is conceptually bad.
public class Foo {
Set<Bar> bars;
public Set<Bar> getBars() {
if (bars == null)
return ( bars = new HashSet() );
return bars;
}
}
public class BarServiceImpl {
public List<Bar> getListOfBars(Foo foo) {
return new ArrayList(foo.getBars());
}
}
3 questions: Where it is better to initialize Foo's Set? What specific Sets and Lists are better for such purposes? What conceptual issues has my current implementation, and how to do better?
Than开发者_如何学Goks in advance.
Where it is better to initialize Foo's Set?
Most of time, I initialize a collections when declaring it, which is what Hibernate recommends. Quoting the documentation:
6.1. Persistent collections
Hibernate requires that persistent collection-valued fields be declared as an interface type. For example:
public class Product { private String serialNumber; private Set parts = new HashSet(); public Set getParts() { return parts; } void setParts(Set parts) { this.parts = parts; } public String getSerialNumber() { return serialNumber; } void setSerialNumber(String sn) { serialNumber = sn; } }
The actual interface might be
java.util.Set
,java.util.Collection
,java.util.List
,java.util.Map
,java.util.SortedSet
,java.util.SortedMap
or anything you like ("anything you like" means you will have to write an implementation oforg.hibernate.usertype.UserCollectionType
.)Notice how the instance variable was initialized with an instance of
HashSet
. This is the best way to initialize collection valued properties of newly instantiated (non-persistent) instances. When you make the instance persistent, by callingpersist()
for example, Hibernate will actually replace theHashSet
with an instance of Hibernate's own implementation ofSet
.
If leaving it null
is part of your business, my suggestion would be to initialize it in a (common) link management methods:
public class Foo {
...
private Set<Bar> bars;
...
public void addBar(Bar bar) {
if (this.bars == null) {
this.bars = new HashSet<Bar>();
}
this.bars.add(bar);
}
}
What specific Sets and Lists are better for such purposes?
It all depends on the semantics you need. A Set
doesn't allow duplicates, a List
allows duplicates and introduces positional indexing.
What conceptual issues has my current implementation, and how to do better?
- I wouldn't perform an assignment in the getter.
- If a collection is supposed to be
null
at that point, let it benull
.
- If a collection is supposed to be
- I don't see the added value of your service
- why not just calling
foo.getBars()
? - why converting the collection?
- why not just calling
Your entity could be simple set of fields with getters and setters. What you need to take care of is how you relate your objects and approach you take to populate objects.
ORM APIs gives liberty of using Objects rather than SQL. You should be careful as to when you should choose to initialize the fields of an Object. For example if you have an object person, which comprises of name, age, collection of contacts and cities visited. In a situation where you are interested in name and age of the person and not contact and cities, you should load name and age only. Which implies that contacts and cities should be lazy loaded.
When interested in contact you load only contacts and not the entire person object or through person object. You would want to load Set of contacts only using Dao/Service and explicitly defining methods to load specific aspect of the object (use of reverse association).
Some best hibernate practices can be found at Best Practices.
Updated:
1) Entity does not populate on its own. One of the popular approach is to have DAO to do this Job. Your Entity could simple be
public class Foo {
private Set<Bar> bar=new HashSet<Bar>();
public Set<Bar> getBar {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
}
2) You can have transaction managed in another layer also referred as Service layer.
i tend to initialize the collections in the service layer where i keep the transaction handling as well. So i can have a method in my BaseDAO which lets me initialize any collection of any Entity in my projects using reflection, by passing the collection names into the method which are to be fetched eagerly (initialized):
public <T extends Object> T getEntity(Class<T> clazz,long id,String[] collectionsToBeInitialized){
T entity=(T) this.getCurrentSession().createCriteria(clazz).add(Restrictions.idEq(id)).setFetchMode(collectionsToBeInitialized[0], FetchMode.JOIN).uniqueResult();
int length=collectionsToBeInitialized.length;
for (int idx=1;idx<length;idx++){
String collectionName=collectionsToBeInitialized[idx];
try {
Method m = clazz.getMethod("get" + collectionName.substring(0, 1).toUpperCase() + collectionName.substring(1),(Class<T>) null);
Hibernate.initialize(m.invoke(entity,(Object[]) null));
} catch (NoSuchMethodException e) {
LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
} catch (InvocationTargetException e) {
LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
} catch (IllegalAccessException e) {
LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
}
}
return entity;
}
then you can initialize any collection from the service layer using this method:
MyEntity ent=getEntity(MyEntity.class,id,new String[]{"collection1","collection2"});
A more detailed example: http://objecthunter.congrace.de/tinybo/blog/articles/69
精彩评论