如何创建支持通用ID的通用实体模型类,包括自动生成的ID?

我有三种表的主键:

  • INT自动生成的主键,使用来自数据库供应商(MySQL)的AUTO_INCREMENT容量
  • CHAR(X)主键,用于将用户可读值存储为键(其中X是数字,50 <= X <= 60)
  • 复杂的主键,由表的2或3个字段组成。

此外,还有一些字段可能存在(或不存在):

  • 版本, INT字段。
  • createdBy, VARCHAR(60) field和lastUpdatedBy, VARCHAR(60)字段(还有更多字段,但这些字段涵盖了一个基本示例)。

上面的一些例子:

  • 表格1
    • id int主键auto_increment
    • 版本int
    • 值char(10)
    • createdBy varchar(60)
    • lastUpdatedBy varchar(60)
  • 表2
    • id char(60)主键
    • shortDescription varchar(20)
    • longDescription varchar(100)
  • 表3
    • field1 int主键
    • field2 int主键
    • 小数(10,5)
    • 版本int

考虑到所有这些,我需要创建一组支持这些要求的通用类,并允许使用Hibernate 4.3和JPA 2.1进行CRUD操作。

这是我当前的模型(避免使用getter / setter来缩短代码示例):

 @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; } 

我目前正在测试Table1Table2保存实例。 我有以下代码:

 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(); 

尝试保存Table2时失败,我收到以下错误:

 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) 

错误消息很明显,因为CHAR(X)字段没有默认值,也没有(AFAIK)。 我尝试将生成策略更改为GenerationType.AUTO并得到相同的错误消息。

我如何重新构建这些类以支持这些要求? 或者甚至更好,我怎样才能提供依赖于我正在保存的实体的密钥的生成策略,这可以由我自动生成或提供?

涉及的技术:

  • Java SDK 8
  • Hibernate 4.3.6
  • JPA 2.1
  • MySQL和Postgres数据库
  • 操作系统:Windows 7专业版

注意:以上可能(并且可能会)更改以支持JPA 2.1的其他实现,如EclipseLink。

您可以“解决”此强制派生类以实现确保已分配Id的方法,并使用@PrePersist注释此方法。 您可以为将自动生成Id的类提供默认实现。

Somethig喜欢:

 @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"; } } 

没试过这个,但根据Hibernate的api,创建IdentityGenerator的自定义实现不应该复杂化。

它是生成方法获取和生成值的对象,因此您可以检查id字段的类型并返回主键的适当值。

 public class DynamicGenerator implements IdentityGenerator public Serializable generate(SessionImplementor session, Object object) throws HibernateException { if (shouldUseAutoincrementStartegy(object)) { // basing on object detect if this should be autoincrement or not, for example inspect the type of id field by using reflection - if the type is Integer use IdentityGenerator, otherwise another generator return new IdentityGenerator().generate(seession, object) } else { // else if (shouldUseTextKey) String textKey = generateKey(session, object); // generate key for your object // you can of course connect to database here and execute statements if you need: // Connection connection = session.connection(); // PreparedStatement ps = connection.prepareStatement("SELECT nextkey from text_keys_table"); // (...) return textKey; } } } 

只需将它用作您的生成策略:

 @MappedSuperclass public abstract class BaseEntity implements Serializable { @Id @GenericGenerator(name="seq_id", strategy="my.package.DynamicGenerator") protected T id; } 

对于Hibernate 4,您应该实现IdentifierGenerator接口。


如上所述,对于任何“jpa兼容”提供程序,仍然可以以更通用的方式为Hibernate创建它。 根据GeneratedValue注释中的JPA api,您可以提供自定义生成器。 这意味着您可以提供自定义生成器的名称,并且应该为每个jpa提供程序实现此生成器。

这意味着您需要使用以下注释来注释BaseEntity

 @MappedSuperclass public abstract class BaseEntity implements Serializable { @Id @GeneratedValue(generator="my-custom-generator") protected T id; } 

现在,您需要为要使用的每个jpa提供程序注册名为“my-custom-generator”的自定义生成器。

对于Hibernate,这是由@GenericGenerator注释完成的,如前所示(在id字段或BaseEntity类级别@GenericGenerator(name="my-custom-generator", strategy="my.package.DynamicGenerator"BaseEntity类)足够)。

在EclipseLink中,我看到您可以通过GeneratedValue注释执行此操作并通过SessionCustomizer注册它:

  properties.put(PersistenceUnitProperties.SESSION_CUSTOMIZER, "my.custom.CustomIdGenerator"); public class CustomIdGenerator extends Sequence implements SessionCustomizer { @Override public Object getGeneratedValue(Accessor accessor, AbstractSession writeSession, String seqName) { return "Id"; // generate the id } @Override public Vector getGeneratedVector(Accessor accessor, AbstractSession writeSession, String seqName, int size) { return null; } @Override protected void onConnect() { } @Override protected void onDisconnect() { } @Override public boolean shouldAcquireValueAfterInsert() { return false; } @Override public boolean shouldOverrideExistingValue(String seqName, Object existingValue) { return ((String) existingValue).isEmpty(); } @Override public boolean shouldUseTransaction() { return false; } @Override public boolean shouldUsePreallocation() { return false; } public void customize(Session session) throws Exception { CustomIdGenerator sequence = new CustomIdGenerator ("my-custom-generator"); session.getLogin().addSequence(sequence); } } 

每个提供者必须提供一种注册id生成器的方法,因此如果要支持所有提供者,则需要为每个提供者实现和注册自定义生成策略。

inheritance层次结构与ORM对抗。 因此,保持简单,并保持更接近数据库实现。 不要为此映射抽象超类的层次结构,而是为共享列的块嵌入带注释的POJO。 在其余的代码中,它们很可能也很方便 。

为共享字段创建@Embeddable类,并为复合ID @Embeddable创建类。

 @Embeddable public class Maintained implements Serializable{ private String maintainedBy; private String updatedBy; // getters and setters } @Embeddable public class CompositeId implements Serializable{ @Column private int id1; @Column private int id2; ... } 

最简单的实现类版本如下所示:

 @Entity public class Table1 { @GeneratedValue(strategy=GenerationType.IDENTITY) @Id protected Long id; @Version private String version; @Embedded private Maintained maintained; ... public Maintained getMaintained(){ return maintained; } } 

对于String ID,没有自动生成:

 @Entity public class Table2 { @Id private String id; @Column private String shortDescription; @Column private String longDescription; ... } 

并且复合ID为@EmbeddedId

 @Entity public class Table3 { @EmbeddedId private CompositeId id; @Version private String version; @Column private int amount; ... } 

作为额外的好处,如果您愿意,可以混合和匹配添加更多这些特征,因为您不再受inheritance树的约束。

(但是,如果现有代码依赖于它和/或从中受益,您可以保持包含getter和setter以及默认委托的层次结构。)