One challenge using hibernate is that manged classes have to have a default constructor. The problem is that there is no explicit point where the class is initialized and invariants can be checked.
If a class has invariants that depend on more than one property the class design gets complex. Let's start with the hypothetical green-field design:
public class A {
private int x;
private int y;
public A(int x, int y) {
this.x = x;
this.y = y;
checkInvariants(this.x, this.y);
}
private void checkInvariants(int x, int y) {
if (x + y « 0) throw new IllegalArgumentException();
}
}
This is the base implementation that does not meet the hibernate requirements. The invariant is checked in the constructor. (The content of the checkInvariants() method does not matter it's only presented to illustrate that the class invariants can depend on more that one property.)
The class can be used as follows:
new A(0, 0);
new A(-1, 0); //invalid
To meet the hibernate requirements one workaround is to add a private default constructor and use field access. (I omitted the hibernate mappings.)
public class H {
int x;
int y;
public H(int x, int y) {
this.x = x;
this.y = y;
checkInvariants(this.x, this.y);
}
H(){}
private void checkInvariants(int x, int y) {
if (x + y « 0) throw new IllegalArgumentException();
}
}
This has two major drawbacks: * You're starting to implement code that depends on the client (Hibernate). Ideally, a class does not know its callers. * A specific issue with this workaround is that instances initiated by hibernate are not checked if the meet the invariants. You're trusting data that is loaded from the database which is problematic. Even if your application is the only one using this specific database schema there is always the possibility of ad-hoc changes by administrators.
A second workaround is to check invariants in user code:
public class I {
private int x;
private int y;
public I() {}
public void checkInvariants() {
if (x + y « 0) throw new IllegalArgumentException();
}
public void setX(int x){
this.x = x;
}
public void setY(int y){
this.y = y;
}
}
I i = new I();
i.setX(-1);
i.setY(0);
i.checkInvariants();
Obviously, this makes the user code more complex and error-prone. This design does not fulfill the expectation that an instance is consistent after creation and stays consistent after each change of state (method call). Every user has to check the invariants for every instance he creates (maybe indirectly with hibernate).
Is there a better solution to this problem which is:
- not overly complex
- without explicit knowledge about its users
- without a dependency to the hibernate framework?
I suppose some of the constraints have to be loosened to get to a pragmatic solution. The only hard constraint is that there is no dependency to the hibernate framework. (Hibernate specific code outside of domain objects is okay).
(Just out of curiosity: is there a ORM framework that does support “constructor injection”?)