Spring Data JPA Specification对@OneToMany Collection的谓词不起作用

时间:2014-09-16 21:58:12

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

背景:

我构建了一个模块,用于捕获资产在其生命周期中发生的历史事件列表,并使用带有hibernate的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;
}

这是我的Part Catalog 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为false,它会很快返回,但它会返回错误的关联列表,并且永远不会从资产直接绑定的事件中返回任何结果,就像includeAssociations为false一样。这是我需要帮助的地方,如何最好地编写标准构建器查询以正确提取数据。

这些是我尝试使用Criteria API组合到Predicate中的两个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?

1 个答案:

答案 0 :(得分:1)

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

之后你必须分析你的查询并用不同的组合进行一些测试来检查你的jpa规范是否正在下降。

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

好看的