Как создать универсальный класс модели сущностей, поддерживающий универсальный идентификатор, включая автоматически генерируемые идентификаторы?
У меня есть три вида первичных ключей для таблиц:
-
INT
автоматически генерируемый первичный ключ, который используетAUTO_INCREMENT
емкость от поставщика базы данных (MySQL) -
CHAR(X)
первичный ключ для хранения читаемого пользователем значения в виде ключа (где X-число, а 50 - сложные первичные ключи, состоящие из 2 или 3 полей таблицы.
Также существует некоторая группа полей, которые могут присутствовать (или не присутствовать):
- версия,
INT
поле. - createdBy,
VARCHAR(60)
field и lastUpdatedBy,VARCHAR(60)
field (есть еще поля, но они охватывают базовый пример).
Некоторые примеры выше:
- Таблица 1
- id int первичный ключ auto_increment
- version int
- значение char (10)
- created by varchar (60)
- lastUpdatedBy varchar (60)
- Таблица 2
- id char (60) primary key
- краткое описание varchar (20)
- длинное описание varchar (100)
- Таблица 3
- field1 int первичный ключ
- field2 int первичный ключ
- сумма десятичная (10, 5)
- version int
Учитывая все это, мне нужно создать универсальный набор классов, который поддерживает эти требования и позволяет выполнять операции CRUD с использованием Hibernate 4.3 и JPA 2.1.
Вот моя текущая модель (геттеры / сеттеры избегали сокращать код Образец):
@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
и получил то же самое сообщение об ошибке.
Как я могу реконструировать эти классы, чтобы поддержать эти требования? Или еще лучше, как я могу обеспечить стратегию генерации, которая зависит от ключа сохраняемой мной сущности, который может быть автоматически сгенерирован или предоставлен мной?
Задействованные технологии:
- Java SDK 8
- Hibernate 4.3.6
- JPA 2.1
- базы данных MySQL и Postgres
- ОС: Windows 7 Professional
Примечание: вышеизложенное может (и, вероятно, будет) изменяться, чтобы быть поддержанным для других реализаций JPA 2.1, таких как EclipseLink.
3 ответа:
Вы можете "обойти" этот принудительный производный класс для реализации метода, который обеспечит присвоение идентификатора и аннотацию этого метода с помощью @PrePersist. Вы можете предоставить реализацию по умолчанию для классов, для которых идентификатор будет автоматически сгенерирован.
Что-то вроде:
@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"; } }
Не пробовал этого, но в соответствии с api Hibernate это не должно усложняться созданием пользовательской реализацииIdentityGenerator .
Это метод generate получает и объект, для которого вы генерируете значение, чтобы вы могли проверить тип поля 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-совместимого " поставщика. В соответствии с api JPA в аннотации GeneratedValue вы можете предоставить свой пользовательский генератор. Это означает, что вы можете указать имя вашего пользовательского генератора, и вы должны реализовать этот генератор для каждого поставщика jpa.
Это означает, что вам нужно аннотировать BaseEntity следующей аннотацией
@MappedSuperclass public abstract class BaseEntity<T> implements Serializable { @Id @GeneratedValue(generator="my-custom-generator") protected T id; }
Теперь вам нужно зарегистрировать пользовательский генератор с именем "my-custom-generator" для каждого поставщика jpa, которого вы хотите использовать.
Для 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); } }
Каждый поставщик должен уступить дорогу зарегистрируйте генератор идентификаторов, поэтому вам нужно будет реализовать и зарегистрировать пользовательскую стратегию генерации для каждого поставщика, если вы хотите поддерживать их все.
Иерархии наследования борются с ORM. Так что будьте проще и немного ближе к реализации базы данных. Не сопоставить иерархию абстрактных суперклассов, но добавьте аннотированный POJO-объект на куски общие столбцы. Они вполне могут оказаться удобными для работы с и в остальной части вашего кода.
Создайте классы
@Embeddable
для общих полей, а также создайте класс для вашего составного идентификатора@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; } }
Для идентификатора строки нет автоматической генерации:
@Entity public class Table2 { @Id private String id; @Column private String shortDescription; @Column private String longDescription; ... }
И составной идентификатор в виде
@EmbeddedId
:@Entity public class Table3 { @EmbeddedId private CompositeId id; @Version private String version; @Column private int amount; ... }
В качестве дополнительного преимущества вы можете смешивать и сопоставлять, добавляя больше этих признаков, если хотите, так как вы больше не ограничены деревом наследования.
(но вы можете сохранить иерархию, содержащую геттеры, сеттеры и делегаты по умолчанию, если существующий код полагается на нее и/или извлекает из нее выгоду.)