【问题标题】:Spring Data JPA Specification Predicate for a @OneToMany Collection not working@OneToMany 集合的 Spring Data JPA 规范谓词不起作用
【发布时间】:2014-09-16 21:58:12
【问题描述】:

背景:

我构建了一个模块,该模块捕获资产在其生命周期内发生的历史事件列表,并使用带有休眠的 spring-data-jpa 使用 JPA 规范来使用 JPA SpecificationExecutor 接口运行动态查询。我有以下历史事件 JPA 对象,该对象具有该历史事件直接针对的多对一资产,以及该历史事件也与以多对多关系定义的其他关联资产。我正在尝试编写一个 JPA 规范谓词,该谓词通过使用谓词中的 includeAssociations 标志来提取该资产直接针对或关联的给定资产的所有历史事件。当我尝试执行谓词时,当我将 includeAssociations 标志设置为 true 时,我没有得到正确的结果。我希望默认情况下,它至少会返回它们直接存在的所有历史事件,就好像 includeAssociations 是错误的一样,再加上它们间接关联的任何事件。我需要帮助弄清楚为什么这个谓词没有返回我所期望的。非常感谢任何帮助!

这是我的历史事件 JPA 对象:

   @Entity
   @Table(name = "LC_HIST_EVENT_TAB")
   public class HistoricalEvent extends BaseEntity implements Comparable<HistoricalEvent>, Serializable 
{
private static final long serialVersionUID = 1L;

@ManyToOne(targetEntity = Asset.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(nullable = false, name = "ASSET_ID")
private Asset asset;

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = Asset.class)
@JoinTable(name = "LC_HIST_EVENT_ASSETS", joinColumns =
  {
    @JoinColumn(name = "HIST_EVENT_ID", referencedColumnName = "id")
  }, inverseJoinColumns =
  {
    @JoinColumn(name = "ASSET_ID", referencedColumnName = "id")
  }, uniqueConstraints =
  {
    @UniqueConstraint(columnNames =
    {
        "HIST_EVENT_ID", "ASSET_ID"
    })
})
@BatchSize(size=10)
@OrderBy("partCatalogItem.partID, serialNumber ASC")
private Set<Asset> associatedAssets;

@Column(name = "START_DATE", nullable = true)
@Temporal(value = TemporalType.TIMESTAMP)
private Calendar startDate;

@Column(name = "END_DATE", nullable = true)
@Temporal(value = TemporalType.TIMESTAMP)
private Calendar endDate;
}

历史事件的 JPA 元模型:

@StaticMetamodel(HistoricalEvent.class)
public class HistoricalEvent_ extends BaseEntity_
{
    public static volatile SingularAttribute<HistoricalEvent, Asset> asset;
    public static volatile SetAttribute<HistoricalEvent, Asset> associatedAssets;
    public static volatile SingularAttribute<HistoricalEvent, Calendar> startDate;
    public static volatile SingularAttribute<HistoricalEvent, Calendar> endDate;
    public static volatile SingularAttribute<HistoricalEvent, String> type;
    public static volatile SingularAttribute<HistoricalEvent, String> description;
    public static volatile SingularAttribute<HistoricalEvent, HistoricalEvent> triggeringEvent;
    public static volatile SetAttribute<HistoricalEvent, HistoricalEvent> associatedEvents;
    public static volatile MapAttribute<HistoricalEvent, String, HistoricalEventMap> data;
}

这是我的资产 JPA 对象:

@Entity
@Table(name = "LC_ASSET_TAB")
public class Asset extends BaseEntity implements Comparable<Asset>, Serializable
{
    private static final long serialVersionUID = 1L;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = PartCatalog.class)
    @JoinColumn(name = "PART_CATALOG_ID", nullable = false)
    private PartCatalog partCatalogItem;

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

    @Column(name = "DATE_INTO_SERVICE", nullable = false)
    @Temporal(value = TemporalType.TIMESTAMP)
    private Calendar dateIntoService;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "asset", targetEntity = AssetMap.class)
    @MapKey(name = "fieldName")
    @BatchSize(size=25)
    private Map<String, AssetMap> data;
}

资产元模型:

@StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
    public static volatile SingularAttribute<PartCatalog, String> partID;
    public static volatile SingularAttribute<PartCatalog, String> nsn;
    public static volatile SingularAttribute<PartCatalog, String> description;
    public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}

这是我的零件目录 JPA 对象:

@Entity
@Table(name = "LC_PART_CATALOG_TAB")
    public class PartCatalog extends BaseEntity implements Comparable<PartCatalog>, Serializable
{

private static final long serialVersionUID = 1L;

@Column(name = "PART_ID", length=100, nullable = false)
private String partID;

@Column(name = "NSN", length=100, nullable = true)
private String nsn;

@Column(name = "DESCRIPTION", length=250, nullable = false)
private String description;

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "partCatalogItem", targetEntity = PartCatalogMap.class)
@MapKey(name = "fieldName")
private Map<String, PartCatalogMap> data;

}

零件目录元模型:

@StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
    public static volatile SingularAttribute<PartCatalog, String> partID;
    public static volatile SingularAttribute<PartCatalog, String> nsn;
    public static volatile SingularAttribute<PartCatalog, String> description;
    public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}

通过给定的零件号和序列号返回历史事件的规范谓词:

问题:如果 includeAssociations 为假,它会返回正常,但是一旦它为真,它会返回错误的关联列表,并且永远不会从资产直接绑定的事件中返回任何结果,就像 includeAssociations 为假时一样。这是我需要帮助如何最好地编写标准构建器查询以正确提取数据的地方。

这是我尝试使用 Criteria API 将两个 JPQL 查询组合到谓词中:

正常:

@Query("SELECT he FROM HistoricalEvent he WHERE he.asset.partCatalogItem.partID =:partID AND he.asset.serialNumber =:serialNumber " +
            "AND he.startDate >:startDate AND he.endDate <:endDate")

协会:

@Query("SELECT he FROM HistoricalEvent he INNER JOIN he.associatedAssets associated WHERE associated.partCatalogItem.partID =:partID AND associated.serialNumber =:serialNumber " +
            "AND he.startDate >:startDate AND he.endDate <:endDate");



/**
 * Creates a specification used to find historical events by a given asset part number and serial
 * parameter.
 *
 * @param partID - part identifier
 * @Param serialNumber
 * @return Historical Event Specification
 */
public static Specification<HistoricalEvent> hasPartAndSerial(final String partID, final String serialNumber, final Boolean includeAssociations) 
{

    return new Specification<HistoricalEvent>() {
        @Override
        public Predicate toPredicate(Root<HistoricalEvent> historicalEventRoot,
                CriteriaQuery<?> query, CriteriaBuilder cb) {

            if (partID == null || partID == "")
            {
                return null;
            }

            if(serialNumber == null || serialNumber =="")
            {
                return null;
            }

            Path<Asset> assetOnEvent = historicalEventRoot.get(HistoricalEvent_.asset);
            Path<PartCatalog> partCatalogItem = assetOnEvent.get(Asset_.partCatalogItem);
            Expression<String> partIdToMatch = partCatalogItem.get(PartCatalog_.partID);
            Expression<String> serialToMatch = assetOnEvent.get(Asset_.serialNumber);

            if(includeAssociations)
            {
                SetJoin<HistoricalEvent, Asset> assetsAssociatedToEvent = historicalEventRoot.join(HistoricalEvent_.associatedAssets);
                Path<PartCatalog> partCatalogItemFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.partCatalogItem);
                Expression<String> partIdToMatchFromAssociatedAsset = partCatalogItemFromAssociatedAsset.get(PartCatalog_.partID);
                Expression<String> serialToMatchFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.serialNumber);


                return cb.or(cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase())), 
                             cb.and(cb.equal(cb.lower(partIdToMatchFromAssociatedAsset), partID.toLowerCase()), cb.equal(cb.lower(serialToMatchFromAssociatedAsset), serialNumber.toLowerCase())));
            }
            else
            {
                return cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase()));
            }
        }
    };
}

最后我调用这个来查找历史事件:

@Override
    public Page<HistoricalEvent> getByCriteria(String type, String partID,
            String serialNumber, Calendar startDate, Calendar endDate,
            Boolean includeAssociations, Integer pageIndex, Integer recordsPerPage) 
    {

        LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Searching historical event repository for type of " + type + " , part id of " + partID + 
                " , serial number of " + serialNumber + " , start date of " + startDate + " , end date of " + endDate + ", include associations flag of " + includeAssociations
                + " , pageIndex " + pageIndex + " and records per page of " + recordsPerPage);

        Page<HistoricalEvent> requestedPage = historicalEventRepository.findAll(Specifications
                .where(HistoricalEventSpecifications.hasType(type))
                .and(HistoricalEventSpecifications.greaterThanOrEqualToStartDate(startDate))
                .and(HistoricalEventSpecifications.lessThanOrEqualToEndDate(endDate))
                .and(HistoricalEventSpecifications.hasPartAndSerial(partID, serialNumber, includeAssociations)),  
                    DatabaseServicePagingUtil.getHistoricalEventPagingSpecification(pageIndex, recordsPerPage));

        LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Found " + requestedPage.getTotalElements() + " that will comprise " + requestedPage.getTotalPages() + " pages of content.");

        return requestedPage;
    }                                             UPDATE: i have been able to get the specification if the historical event was either directly or indirectly associated working however using the following Predicate 1 = cb.equals(cb.lower(partIDToMatch, partID.toLowercase());          Predicate2 = cb.equals(cb.lower(serialToMatch), serialNumber.toLowercase();              Predicate3 = cb.or(Predicate1, Predicate2 );         Predicate4 = cb.equals(cb.lower(partIDToMatchFromAssociatedAsset), partIDToMatch.toLowercase());    Predicate5 = cb.equals(cb.lower(serialNumberFromAssociatedAsset), serialNumberToMatch.toLowercase());                 Predicate6 = cb.and(Predicate4, Predicate5);                                                        Predicate7 = cb.or(Predicate3,Predicate6);            When i return Predicate I only get results matching Predicate6 not either one as i would expect. I want it to pull events where either predicate condition returns a record. Each predicate returns the right data but when i use the cb.or it doesnt combine results as i would expect. What am I missing?

【问题讨论】:

    标签: spring hibernate jpa spring-data-jpa criteria-api


    【解决方案1】:

    您必须开始打印 bean 生成的查询和参数值,只需启用此属性即可。

    之后,您必须分析您的查询并使用不同的组合进行一些测试,以检查您的 jpa 规范是否下降。

    没有什么神奇的方法可以做到这一点,而且很难而且很痛苦:(

    好看

    【讨论】:

      猜你喜欢
      • 2012-08-15
      • 2022-11-21
      • 2014-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-03
      • 2021-09-25
      • 1970-01-01
      相关资源
      最近更新 更多