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

Lui*_*oza 8 java mysql hibernate jpa hibernate-4.x

我有三个种类的对表的主键:

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

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

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

上面的一些例子:

  • 表格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<T> implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    protected T id;
}

@MappedSuperclass
public abstract class VersionedEntity<T> extends BaseEntity<T> {
    @Version
    protected int version;
}

@MappedSuperclass
public abstract class MaintainedEntity<T> extends VersionedEntity<T> {
    @Column
    protected String createdBy;
    @Column
    protected String lastUpdatedBy;
}

@Entity
public class Table1 extends MaintainedEntity<Long> {
    @Column
    private String value;
}

@Entity
public class Table2 extends BaseEntity<String> {
    @Column
    private String shortDescription;
    @Column
    private String longDescription;
}
Run Code Online (Sandbox Code Playgroud)

我正在测试Table1和的实例Table2.我有以下代码:

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();
Run Code Online (Sandbox Code Playgroud)

尝试保存时失败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)
Run Code Online (Sandbox Code Playgroud)

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

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

涉及的技术:

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

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

wal*_*ros 5

没试过这个,但根据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;

            }

        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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


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

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

@MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
    @Id
    @GeneratedValue(generator="my-custom-generator")
    protected T id;
}
Run Code Online (Sandbox Code Playgroud)

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

对于Hibernate作为(添加前所示,这是通过切切实实注释@GenericGenerator完成 @GenericGenerator(name="my-custom-generator", strategy="my.package.DynamicGenerator"BaseEntity类在任id字段或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);
    }

}    
Run Code Online (Sandbox Code Playgroud)

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


wal*_*ros 1

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

像这样的东西:

@MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    protected T id;

    @PrePersist
    public void ensureIdAssigned() {
          ensureIdAssignedInternal();  
    }


    public abstract void ensureIdAssignedInternal();  
}


@MappedSuperclass
public abstract class AutoIdMaintaintedEntity<T> extends MaintainedEntity<T> { // 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<Long> {
    @Column
    private String value;
}

@Entity
public class Table2 extends BaseEntity<String> {
    @Column
    private String shortDescription;
    @Column
    private String longDescription;

    public void ensureIdAssignedInternal() {
         this.id = generateMyTextId();

    }

     private String generateMyTextId() {
         return "text id";
     }


}
Run Code Online (Sandbox Code Playgroud)