我有三种种表的主键:
INT
自动生成的主键,使用来自数据库供应商(MySQL)的AUTO_INCREMENT
容量CHAR(X)
将用户可读取的值存储为键的主键(其中X是数字且50 <= X <= 60)此外,还有一些字段可能存在(或不存在):
INT
字段。VARCHAR(60)
字段和lastUpdatedBy,VARCHAR(60)
字段(还有更多字段,但这些字段涵盖了一个基本示例)。以上的一些例子:
考虑到所有这些,我需要创建一组支持这些要求的通用类,并允许使用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;
}
我目前正在测试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();
尝试保存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
并收到相同的错误消息。
如何重新构建这些类以支持这些要求?或者甚至更好,我怎样才能提供依赖于我正在保存的实体的密钥的生成策略,可以由我自动生成或提供?
涉及的技术:
注意:以上内容可能(并且可能会)发生变化,以支持JPA 2.1的其他实现,如EclipseLink。
答案 0 :(得分: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;
}
}
}
将此简单地用作您的生成策略:
@MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
@Id
@GenericGenerator(name="seq_id", strategy="my.package.DynamicGenerator")
protected T id;
}
对于Hibernate 4,您应该实现IdentifierGenerator
接口。
如上所述,Hibernate接受它仍然可以为任何“jpa兼容”提供者以更通用的方式创建它。根据{{3}}注释中的JPA api,您可以提供自定义生成器。这意味着您可以提供自定义生成器的名称,并且应该为每个jpa提供程序实现此生成器。
这意味着您需要使用以下注释来注释BaseEntity
@MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
@Id
@GeneratedValue(generator="my-custom-generator")
protected T id;
}
现在,您需要为要使用的每个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);
}
}
每个提供商必须提供一种注册ID生成器的方法,因此如果您想支持所有提供者,则需要为每个提供者实现并注册自定义生成策略。
答案 1 :(得分:1)
继承层次结构与ORM对抗。 所以保持简单,并保持更接近数据库实现。不要为此映射抽象超类的层次结构,而是为共享列的块嵌入带注释的POJO。他们的代码中的其他代码也可能turn out to be handy to work with。
为共享字段创建@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;
...
}
作为额外的好处,如果您愿意,可以混合和匹配添加更多这些特征,因为您不再受继承树的约束。
(但是,如果现有代码依赖于它和/或从中受益,您可以保留包含getter和setter以及默认委托的层次结构。)
答案 2 :(得分:1)
你可以&#34;解决方法&#34;这个强制派生类实现的方法将确保分配Id并使用@PrePersist注释此方法。您可以为将自动生成Id的类提供默认实现。
Somethig喜欢:
@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";
}
}