Суперкласс сопоставления сохраняемости Java с дополнительными свойствами
Я использую javax.persistence
пакет для сопоставления моих классов Java.
У меня есть такие объекты:
public class UserEntity extends IdEntity {
}
который расширяет сопоставленный суперкласс с именем IdEntity
:
@MappedSuperclass
public class IdEntity extends VersionEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// Getters and setters below...
}
IdEntity
суперкласс расширяет еще один сопоставленный суперкласс с именем VersionEntity
чтобы все сущности наследовали свойства версии:
@MappedSuperclass
public abstract class VersionEntity {
@Version
private Integer version;
// Getters and setters below...
}
Зачем?
Потому что теперь я могу делать общие запросы к классу IdEntity для всех сущностей, и это будет выглядеть так: (пример)
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Теперь к проблеме.
Некоторые из моих сущностей будут иметь временные метки, такие как created_at
а также deleted_at
, Но не все сущности.
Я мог бы предоставить эти свойства в моих классах сущностей следующим образом:
public class UserEntity extends IdEntity {
@Basic(optional = false)
@Column(name = "updated_at")
@Temporal(TemporalType.TIMESTAMP)
private Date updatedAt;
}
Но так как у меня много сущностей, это заставит меня поместить много избыточного кода во все сущности, которые должны иметь временные метки. Хотелось бы, чтобы я мог каким-то образом заставить соответствующие классы наследовать эти поля.
Одним из возможных решений является создание параллеля IdEntity
суперкласс, может быть назван IdAndTimeStampEntity
и сделать так, чтобы те сущности, которые должны иметь временные метки, наследовали от этого нового суперкласса, но эй, это несправедливо по отношению к моим коллегам-разработчикам, потому что теперь они должны знать, какой суперкласс выбрать при написании общих запросов:
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<???> criteria = builder.createQuery(???); // Hmm which entity should I choose IdEntity or IdAndTimeStampEntity ?? *Annoyed*
И запросы общих сущностей становятся не такими общими.
Мой вопрос: как я могу сделать
all
мои лица наследуютid
а такжеversion
поля, но только часть всех сущностей наследуют поля меток времени, но хранят ли мои запросы к одному типу сущностей?
Обновление № 1
Вопрос от Больцано: "Можете ли вы добавить код, который вы указываете путь (содержит информацию таблицы) для сущностей?"
Вот рабочий пример запроса UserEntity
который является IdEntity
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Root<IdEntity> from = criteria.from(IdEntity.class);
criteria.select(from);
Path<Integer> idPath = from.get(UserEntity_.id); //generated meta model
criteria.where(builder.in(idPath).value(id));
TypedQuery<IdEntity> query = JPA.em().createQuery(criteria);
return query.getSingleResult();
3 ответа
Я бы выбрал решение, которое не применяет объектную модель на основе классов, как вы обрисовали. Что происходит, когда вам не нужна оптимистическая проверка параллелизма и нет временных отметок, или временных отметок, но нет OCC или следующего полураспространенного элемента функциональности, который вы хотите добавить? Перестановки станут неуправляемыми.
Я бы добавил эти обычные взаимодействия в качестве интерфейсов и улучшил бы вашу повторно используемую находку по идентификатору с помощью шаблонов, чтобы возвращать фактический класс, который вас интересует, вызывающему, а не базовому суперклассу.
Примечание: я написал этот код в переполнении стека. Может потребоваться некоторая настройка для компиляции.
@MappedSuperclass
public abstract class Persistable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// getter/setter
}
public interface Versioned {
Integer getVersion();
}
public interface Timestamped {
Date getCreated();
Date getLastUpdated();
}
@Embeddable
public class TimestampedEntity {
@Column(name = "create_date")
@Temporal
private Date created;
@Column
@Temporal
private Date lastUpdated;
// getters/setters
}
@Entity
public class UserEntity extends Persistable implements Versioned, Timestamped {
@Version
private Integer version;
@Embedded
private TimestampedEntity timestamps;
/*
* interface-defined getters. getTimestamps() doesn't need to
* be exposed separately.
*/
}
public class <CriteriaHelperUtil> {
public <T extends Persistable> T getEntity(Class<T> clazz, Integer id, SingularAttribute idField) {
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(clazz);
Root<T> from = criteria.from(clazz);
criteria.select(from);
Path<Integer> idPath = from.get(idField);
criteria.where(builder.in(idPath).value(id));
TypedQuery<T> query = JPA.em().createQuery(criteria);
return query.getSingleResult();
}
}
Основное использование:
private UserEntity ue = CriteriaHelperUtil.getEntity(UserEntity.class, 1, UserEntity_.id);
ue.getId();
ue.getVersion();
ue.getCreated();
// FooEntity implements Persistable, Timestamped
private FooEntity fe = CriteriaHelperUtil.getEntity(FooEntity.class, 10, FooEntity_.id);
fe.getId();
fe.getCreated();
fe.getVersion(); // Compile Error!
@MappedSuperclass
public class IdEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Version
private Integer version;
}
@MappedSuperclass
public class IdAndTimeStampEntity extends IdEntity{
Date created;
}
@Entity
public class UserEntity extends IdAndTimeStampEntity{
String name;
}
@Entity
public class FooEntity extends IdEntity{...
Плюсы этого решения:
Простым и понятным способом используется ООП без необходимости встраивания дублирующего кода, реализующего интерфейсы в каждом подклассе. (Каждый класс также интерфейс)
Оптимистическая блокировка версии столбца - наиболее часто используемый подход. И должен быть частью базового класса. За исключением объектов только для чтения, таких как кодовые таблицы.
Использование:
public <T extends IdEntity> T persist(T entity) {
if (entity instanceof IdAndTimeStampEntity) {
((IdAndTimeStampEntity) entity).setCreated(new Date());
}
if (!em.contains(entity) && entity.getId() != null) {
return em.merge(entity);
} else {
em.persist(entity);
return entity;
}
}
Хотелось бы, чтобы я мог каким-то образом заставить соответствующие классы наследовать эти поля.
Вы можете сделать пользовательскую аннотацию @Timed
и использовать процессор аннотаций для добавления поля меток времени и аннотаций либо с помощью инфраструктуры манипулирования байт-кодом, либо путем создания подкласса делегирования. Или, например, если вы используете Lombok, создайте аннотацию Lombok.
Таким образом, члены вашей команды должны только помнить, чтобы использовать @Timed
аннотация, когда у вас есть объекты с отметками времени. Нравится ли вам такой подход или нет, решать только вам.