Критерии гибернации. Построить с наследованием - PullRequest
0 голосов
/ 09 июля 2020

У меня есть суперкласс AuditEntity.

@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditEntityListener.class)
public abstract class AuditEntity {
    public static final String ID = "id";
    public static final String ORGANIZATION = "organization";
    public static final String CREATED_BY = "createdBy";
    public static final String MODIFIED_BY = "modifiedBy";
    public static final String CREATED_DATETIME = "createdDatetime";
    public static final String MODIFIED_DATETIME = "modifiedDatetime";
    public static final String STATE = "state";

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "organization_id")
    protected Organization organization;

    @Column(name = "created_datetime")
    protected Instant createdDatetime;

    @Column(name = "modified_datetime")
    protected Instant modifiedDatetime;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "created_by")
    protected User createdBy;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "modified_by")
    protected User modifiedBy;

    @Enumerated(EnumType.STRING)
    @Column(name = "state")
    protected State state;

}

И объект расширяет суперкласс.

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(of = {"id"}, callSuper = false)
@Entity
@Table(name = "inventory_request")
public class InventoryRequest extends AuditEntity {

    public static final String NAME = "name";
    public static final String REQUESTER = "requester";
    public static final String SOURCE = "source";
    public static final String EVENT = "event";
    public static final String TRANSFER = "transfers";
    public static final String ASSIGNMENT = "assignment";
    public static final String INVENTORY_REQUEST_STATUS = "status";
    public static final String NOTES = "notes";

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "inventory_request_id")
    private UUID id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "email", nullable = false)
    private String email;

    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
    @JoinColumn(name = "requester_id")
    private User requester;

    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
    @JoinColumn(name = "source_id")
    @ApiModelProperty(hidden = true)
    private User source;

    @Enumerated(EnumType.STRING)
    @Column(name = "status", nullable = false, length = 24)
    private InventoryRequestStatus status;

    @Column(name = "carrier")
    private String carrier;

    @Column(name = "tracking_number")
    private String trackingNumber;

    @Column(name = "note", length = 1024)
    private String note;

    @JsonManagedReference
    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
    @JoinColumn(name = "event_id")
    private Event event;

    @Column(name = "number", nullable = false)
    private Long number;

    @Column(name = "tracking_enabled", nullable = false)
    private Boolean trackingEnabled;

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = Transfer.INVENTORY_REQUEST)
    private Set<Transfer> transfers;

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = InventoryRequestAssignment.INVENTORY_REQUEST)
    private Set<InventoryRequestAssignment> assignment;

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = InventoryRequestNote.INVENTORY_REQUEST)
    private Set<InventoryRequestNote> notes;
}

Это класс, который я хочу выбрать с критериями api.

@NoArgsConstructor
@Getter
@Setter
public class InventoryRequestDTO extends InventoryRequest {

    public InventoryRequestDTO(InventoryRequest inventoryRequest,
                               Long completeSetsQuantity,
                               Long specificProductQuantity) {
        super(inventoryRequest.getId(),
                inventoryRequest.getName(),
                inventoryRequest.getEmail(),
                inventoryRequest.getRequester(),
                inventoryRequest.getSource(),
                inventoryRequest.getStatus(),
                inventoryRequest.getCarrier(),
                inventoryRequest.getTrackingNumber(),
                inventoryRequest.getNote(),
                inventoryRequest.getEvent(),
                inventoryRequest.getNumber(),
                inventoryRequest.getTrackingEnabled(),
                inventoryRequest.getTransfers(),
                inventoryRequest.getAssignment(),
                inventoryRequest.getNotes());
        this.completeSetsQuantity = completeSetsQuantity;
        this.specificProductQuantity = specificProductQuantity;
    }

    private Long completeSetsQuantity;
    private Long specificProductQuantity;
}

И это метод, который я пытался сделать.

   @Transactional
    public Page<? extends InventoryRequest> getInventoryRequestPage(InventoryRequestSearchParams searchParams, Pageable pageable) {
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<InventoryRequest> query = builder.createQuery(InventoryRequest.class);
        Root<InventoryRequest> root = query.from(InventoryRequest.class);
        Join<InventoryRequest, InventoryRequestAssignment> assignmentJoin = root.join(InventoryRequest.ASSIGNMENT, JoinType.LEFT);
        Expression<Long> specificProductQuantity = builder.count(builder.selectCase()
                .when(assignmentJoin.get(InventoryRequestAssignment.CATALOG).isNotNull(), 1)
                .otherwise(0));
        Expression<Long> completeSetsQuantity = builder.count(builder.selectCase()
                .when(assignmentJoin.get(InventoryRequestAssignment.CATALOG).isNull(), 1)
                .otherwise(0));

        Predicate predicate = InventoryRequestSpecificationComposer.builder()
                .searchParams(searchParams)
                .build()
                .compose()
                .toPredicate(root, query, builder);

        query.select(
                builder.construct(InventoryRequestDTO.class,
                        root,
                        completeSetsQuantity,
                        specificProductQuantity))
                .where(predicate);

        Query q = entityManager.createQuery(query);

        int totalRows = q.getResultList().size();
        q.setFirstResult(pageable.getPageNumber() * pageable.getPageSize());
        q.setMaxResults(pageable.getPageSize());
        return new PageImpl<>(q.getResultList(), pageable, totalRows);
    }

Но я получаю это исключение.

org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias2,role=com.connectsx.core.model.entity.InventoryRequest.createdBy,tableName=users,tableAlias=user2_,origin=inventory_request inventoryr0_,columns={inventoryr0_.created_by ,className=com.connectsx.core.model.entity.User}}] 

Также у меня есть построитель спецификаций для объекта аудита , который извлекает свойства organization, created_by, modified_by. Но если я выберу InventoryRequest.class, он работает нормально, а с DTO не работает.

1 Ответ

0 голосов
/ 10 июля 2020

Нет смысла использовать выражение конструктора, если вы передаете всю сущность. С таким же успехом можно получить сущность и вызвать конструктор для объектов списка результатов. Однако, если вы действительно хотите повысить производительность, это идеальный вариант использования Blaze-Persistence Entity Views .

Я создал библиотеку, чтобы обеспечить простое сопоставление между моделями JPA и настраиваемым интерфейсом или модели, определенные абстрактным классом, что-то вроде Spring Data Projection на стероидах. Идея состоит в том, что вы определяете свою целевую структуру (модель домена) так, как вам нравится, и сопоставляете атрибуты (геттеры) через выражения JPQL с моделью сущности. Поскольку имя атрибута используется в качестве сопоставления по умолчанию, в большинстве случаев явное сопоставление не требуется, поскольку в 80% случаев использования DTO являются подмножеством модели сущности.

Пример модели для вашего использования case может выглядеть следующим образом:

@EntityView(InventoryRequest.class)
public interface InventoryRequestDTO {
    @IdMapping
    UUID getId();
    String getName();
    String getEmail();
    UserDTO getRequester();
    UserDTO getSource();
    InventoryRequestStatus getStatus();
    String getCarrier();
    String getTrackingNumber();
    String getNote();
    EventIdView getEvent();
    Long getNumber();
    Boolean getTrackingEnabled();
    @Mapping(fetch = FetchStrategy.MULTISET)
    Set<TransferDTO> getTransfers();
    @Mapping(fetch = FetchStrategy.MULTISET)
    Set<InventoryRequestAssignmentDTO> getAssignment();
    @Mapping(fetch = FetchStrategy.MULTISET)
    Set<InventoryRequestNoteDTO> getNotes();
    @Mapping("COUNT(*) FILTER (WHERE assignment.catalog IS NULL)")
    Long getCompleteSetsQuantity();
    @Mapping("COUNT(*) FILTER (WHERE assignment.catalog IS NOT NULL)")
    Long getSpecificProductQuantity();
}

@EntityView(User.class)
public interface UserDTO {
    @IdMapping
    UUID getId();
    // Other fields you need
}

@EntityView(Transfer.class)
public interface TransferDTO {
    @IdMapping
    UUID getId();
    // Other fields you need
}

@EntityView(InventoryRequestAssignment.class)
public interface InventoryRequestAssignmentDTO {
    @IdMapping
    UUID getId();
    // Other fields you need
}

@EntityView(InventoryRequestNote.class)
public interface InventoryRequestNoteDTO {
    @IdMapping
    UUID getId();
    // Other fields you need
}

Запрос - это вопрос применения представления сущности к запросу, самый простой из которых - это просто запрос по идентификатору.

InventoryRequestDTO dto = entityViewManager.find(entityManager, InventoryRequestDTO.class, id);

Интеграция Spring Data позволяет вам использовать его почти как Spring Data Projection: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring -data-features т.е. иметь репозиторий, подобный следующему

@Repository
public interface InventoryRepository {
    Page<InventoryRequestDTO> findAll(Specification specification, Pageable pageable);
}

Он будет бери только то, что говоришь. Наслаждайтесь!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...