I have three kinds of primary keys for tables:
INT auto generated primary key which use AUTO_INCREMENT capacity from database vendor (MySQL)
CHAR(X) primary key to store a user readable value as key (where X is a number and 50 <= X <= 60)
Complex primary keys, composed by 2 or 3 fields of the table.
Also, there are some group of fields that may be present (or not):
version, INT field.
createdBy, VARCHAR(60) field, and lastUpdatedBy, VARCHAR(60) field (there are more fields but these covers a basic example).
Some examples of above:
Table1
id int primary key auto_increment
version int
value char(10)
createdBy varchar(60)
lastUpdatedBy varchar(60)
Table2
id char(60) primary key
shortDescription varchar(20)
longDescription varchar(100)
Table3
field1 int primary key
field2 int primary key
amount decimal(10, 5)
version int
With all this in mind, I need to create a generic set of classes that supports these requirements and allows CRUD operations using Hibernate 4.3 and JPA 2.1.
Here's my current model (getters/setters avoided to shorten the code sample):
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
protected T id;
}
@MappedSuperclass
public abstract class VersionedEntity extends BaseEntity {
@Version
protected int version;
}
@MappedSuperclass
public abstract class MaintainedEntity extends VersionedEntity {
@Column
protected String createdBy;
@Column
protected String lastUpdatedBy;
}
@Entity
public class Table1 extends MaintainedEntity {
@Column
private String value;
}
@Entity
public class Table2 extends BaseEntity {
@Column
private String shortDescription;
@Column
private String longDescription;
}
I'm currently testing save instances of Table1 and Table2. I have the following code:
SessionFactory sf = HibernateUtils.getSessionFactory();
Session session = sf.getCurrentSession();
session.beginTransaction();
Table1 newTable1 = new Table1();
newTable1.setValue("foo");
session.save(newTable1); //works
Table2 newTable2 = new Table2();
//here I want to set the ID manually
newTable2.setId("foo_id");
newTable2.setShortDescription("short desc");
newTable2.setLongDescription("long description");
session.save(newTable2); //fails
session.getTransaction().commit();
sf.close();
It fails when trying to save Table2 and I get the following error:
Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:996)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3887)
The error message is obvious because a CHAR(X) field doesn't have a default value and won't have it (AFAIK). I tried changing the generation strategy to GenerationType.AUTO and got the same error message.
How can I remodel these classes in order to support these requirements? Or even better, how could I provide a generation strategy that depends on the key of the entity I'm saving, which could be auto generated or provided by me?
Involved technologies:
Java SDK 8
Hibernate 4.3.6
JPA 2.1
MySQL and Postgres databases
OS: Windows 7 Professional
Note: the above may (and probably will) change in order to be supported for other implementations of JPA 2.1 like EclipseLink.
解决方案
You can "workaround" this forcing derived class to implement method which will ensure the Id is assigned and annotate this method with @PrePersist. You can provide default implementation for classes for which the Id will be auto generated.
Somethig like:
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
protected T id;
@PrePersist
public void ensureIdAssigned() {
ensureIdAssignedInternal();
}
public abstract void ensureIdAssignedInternal();
}
@MappedSuperclass
public abstract class AutoIdMaintaintedEntity extends MaintainedEntity { // provide default implementation for Entities with Id generated by @GeneratedValue(strategy=GenerationType.IDENTITY) on BaseEntity superclass
public void ensureIdAssignedInternal() {
// nothing here since the Id will be automatically assigned
}
}
@Entity
public class Table1 extends AutoIdMaintaintedEntity {
@Column
private String value;
}
@Entity
public class Table2 extends BaseEntity {
@Column
private String shortDescription;
@Column
private String longDescription;
public void ensureIdAssignedInternal() {
this.id = generateMyTextId();
}
private String generateMyTextId() {
return "text id";
}
}