Hi,
I have to model a composite pattern and I would like to benefig of the JPA Annotations and Hibernate
The classes are shown below, let's say in the meanwhile they are the canonical Composite Design Pattern entities, where:
* Component is the (pretending to be) abstract top class
* Leaf is a concrete class which extends Component and has no children
* Composite is a concrete class which extends Component and has a Set<Componet> children
My needs would be that the root class should be abstract, and I want to be able to impose unique contrainsts on the 2 subclasses so that no "equal" entity can be under the same node in the hierarchy (ie: no two Leaves with same content under the same parent node). I would like also to use the JOINED strategy
Hibernate version:
Latest from Maven2 ibiblio repo:
* hibernate-core: 3.1.2
* hibernate-annotations: 3.1beta8
Classes:
These are the 3 classes, with some comments useful to understand why I made some design choices.
/**
* XXX: This class can't be abstract because otherwise I get:
* org.hibernate.InstantiationException: Cannot instantiate abstract class
* or interface my.package.Component
* There is no hibernate sql weird log anway
*/
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Component {
protected Long id;
protected Composite parent;
public Component() {
super();
this.parent = null;
}
@Id @GeneratedValue(strategy=GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
/**
* The 1:N association is bidirectional, and the <code>ManyToOne</code> side thus
* must declare <code>mappedBy="parent"</code>,
* and so the <code>parent</code> is actually needed on the super class because otherwise it will say
* <em>""Colum Component.parent not found"</em> or something like that
*
* @return
*/
@ManyToOne(fetch=FetchType.EAGER, cascade={CascadeType.MERGE, CascadeType.PERSIST}, optional=true)
public Composite getParent(){
return this.parent;
}
public void setParent(Composite parent){
this.parent = parent;
}
}
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"parent_id", "name"})})
public class Composite extends Component {
private String name;
private Set<Component> children;
public Composite(){
super();
this.children = new HashSet<Component>();
}
@Column(nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* The <code>parent</code> must be overrided in order to be able to declare
* <code>uniqueConstraints</code> on the class (see this class definition), otherwise
* it will say "Unable to find column with logical name <code>Composite.parent_id</code>"
*/
@Override
@ManyToOne(fetch=FetchType.EAGER, cascade={CascadeType.MERGE, CascadeType.PERSIST}, optional=true)
public Composite getParent(){
return this.parent;
}
public void setParent(Composite parent){
this.parent = parent;
}
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade=CascadeType.ALL)
public Set<Component> getChildren() {
return children;
}
public void setChildren(Set<Component> children) {
this.children = children;
}
/* ==================== */
@Override
public boolean equals(Object obj) {
if(obj == this){
return true;
}
else if(!(obj instanceof Composite)) {
return false;
}else{
Composite composite = (Composite) obj;
return new EqualsBuilder()
.append(this.parent, composite.getParent())
.append(this.name, composite.getName())
.isEquals();
}
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(this.parent)
.append(this.name)
.toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", this.id)
.append("parent", this.parent.getName())
.append("name", this.name)
.toString();
}
}
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"parent_id", "content"})})
public class Leaf extends Component {
private String content;
public Leaf() {
super();
}
@Column(nullable=false)
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
/**
* The <code>parent</code> must be overrided in order to be able to declare
* <code>uniqueConstraints</code> on the class (see this class definition), otherwise
* it will say "Unable to find column with logical name <code>Leaf.parent_id</code>"
*/
@Override
@ManyToOne(fetch=FetchType.EAGER, cascade={CascadeType.MERGE, CascadeType.PERSIST}, optional=true)
public Composite getParent(){
return this.parent;
}
public void setParent(Composite parent){
this.parent = parent;
}
/* ==================== */
@Override
public boolean equals(Object obj) {
if(obj == this){
return true;
}
else if(!(obj instanceof Composite)) {
return false;
}else{
Composite composite = (Composite) obj;
return new EqualsBuilder()
.append(this.parent, composite.getParent())
.append(this.content, composite.getName())
.isEquals();
}
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(this.parent)
.append(this.content)
.toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", this.id)
.append("parent", this.parent.getName())
.append("name", this.content)
.toString();
}
}
Unit test code (DAOs are created with Spring HibernateTemplate that is used throughout the project for other DAOs)
public void testIt() throws Exception {
// DAOs with Spring HibernateTemplate
this.compositeDao.removeAll(); // ok
this.leafDao.removeAll(); // ok
this.root = new Composite();
this.root.setName("root name");
this.root.setParent(null);
this.compositeDao.saveOrUpdate(this.root);
assertEquals(1, this.compositeDao.findAll().size());
this.leafA = new Leaf();
this.leafA.setContent("leaf A content");
this.leafA.setParent(this.root);
this.leafDao.saveOrUpdate(this.leafA);
assertEquals(1, this.leafDao.findAll().size());
Leaf leafB = new Leaf();
leafB.setContent(this.leafA.getContent());
leafB.setParent(this.leafA.getParent());
try{
this.leafDao.saveOrUpdate(leafB);
fail("Must violate unique constraints");
}catch(Exception e){
assertTrue("Ok, unique constraints annotation works", true);
}
/* if the Component top class is abstract here i get exception:
* org.hibernate.InstantiationException: Cannot instantiate abstract class
* or interface my.package.Component
* (There is no hibernate sql log anway)
*
* if Component is not declared abstract (is a concrete class),
* it all works!!
*/
this.leafDao.removeAll(); // throws Exception (see the comment)
this.compositeDao.removeAll(); // does not even reach this point
}
Code between sessionFactory.openSession() and session.close():
Managed by Spring 1.2.6 HibernateTemplate, no problem in this
Full stack trace of any exception that occurs:
(see comments in the code because they are thrown depending on which choices I make in the code: abstract/non abstact Component class, @Override of getParent(), ...)
Name and version of the database you are using:
HsqlDB 1.8.0.1 running in RAM
As you can read in the comments, and in the unit test, the problem is with the Component class that I am "forced" to declare as a concrete class, otherwise I get "org.hibernate.InstantiationException: Cannot instantiate abstract class or interface my.package.Component"
Any help is appreciated, if somebody needs more info just let me know.
Thanks a lot,
Alessio Pace.
|