Суперкласс сопоставления сохраняемости 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{...

Плюсы этого решения:

  1. Простым и понятным способом используется ООП без необходимости встраивания дублирующего кода, реализующего интерфейсы в каждом подклассе. (Каждый класс также интерфейс)

  2. Оптимистическая блокировка версии столбца - наиболее часто используемый подход. И должен быть частью базового класса. За исключением объектов только для чтения, таких как кодовые таблицы.

Использование:

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 аннотация, когда у вас есть объекты с отметками времени. Нравится ли вам такой подход или нет, решать только вам.

Другие вопросы по тегам