Spring Data JPA规范在嵌套集合中搜索属性。

huangapple go评论83阅读模式
英文:

Spring Data JPA Specification search for property in nested collection

问题

以下是您要翻译的内容:

Say I have at least two entities. 

    @Entity
    public class Process {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(unique = true)
        private String name;
    
        @ManyToAny(
                metaColumn = @Column(name = "node_type"),
                fetch = FetchType.LAZY
        )
        @AnyMetaDef(
                idType = "long", metaType = "string",
                metaValues = {
                        @MetaValue(targetEntity = Milestone.class, value = MILESTONE_DISC),
                        @MetaValue(targetEntity = Phase.class, value = PHASE_DISC)
                }
        )
        @Cascade({org.hibernate.annotations.CascadeType.ALL})
        @JoinTable(
                name = "process_nodes",
                joinColumns = @JoinColumn(name = "process_id", nullable = false),
                inverseJoinColumns = @JoinColumn(name = "node_id", nullable = false)
        )
        private Collection<ProcessNode> nodes = new ArrayList<>();
    
        ...
    }
-------

    @Entity
    @ToString
    @DiscriminatorValue(MILESTONE_DISC)
    public class Milestone implements ProcessNode {
    
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
        private Collection<ResultDefinition> results;
    
        @ToString.Exclude
        @ManyToOne(fetch = FetchType.LAZY)
        @Transient
        private Process process;
    
    ...
    }

Now I want to use spring data jpa specification to find (all) processes which have a milestone with name "S5".

Note that Milestone is a ProcessNode and there is another Entity called Phase which is also a ProcessNode. These can be contained in the "nodes" collection of my Process Entity.

I tried to write something like this:

        public static Specification<Process> hasMilestoneWithName(final String milestoneName) {
        return (Specification<Process>) (root, query, criteriaBuilder) -> {
            Path<?> namePath = root.join("nodes").get("name");
            return criteriaBuilder.equal(namePath, milestoneName);
        };
    }

This does not work, but throws:

> Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [nodes] on this ManagedType [com.smatrics.dffs.processservice.model.entities.Process]

I don't really know how to use the API. Examples often refer to a meta-model that would be generated by the IDE or maven, but I really do not want to have any static generated resources. Please help me resolve this with Specification of spring-data-jpa without a generated meta-model.

Also if you could help me write the hql it would be awesome.

Thanks!
英文:

Say I have at least two entities.

@Entity
public class Process {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String name;
@ManyToAny(
metaColumn = @Column(name = &quot;node_type&quot;),
fetch = FetchType.LAZY
)
@AnyMetaDef(
idType = &quot;long&quot;, metaType = &quot;string&quot;,
metaValues = {
@MetaValue(targetEntity = Milestone.class, value = MILESTONE_DISC),
@MetaValue(targetEntity = Phase.class, value = PHASE_DISC)
}
)
@Cascade({org.hibernate.annotations.CascadeType.ALL})
@JoinTable(
name = &quot;process_nodes&quot;,
joinColumns = @JoinColumn(name = &quot;process_id&quot;, nullable = false),
inverseJoinColumns = @JoinColumn(name = &quot;node_id&quot;, nullable = false)
)
private Collection&lt;ProcessNode&gt; nodes = new ArrayList&lt;&gt;();
...
}

@Entity
@ToString
@DiscriminatorValue(MILESTONE_DISC)
public class Milestone implements ProcessNode {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection&lt;ResultDefinition&gt; results;
@ToString.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@Transient
private Process process;
...
}

Now I want to use spring data jpa specification to find (all) processes which have a milestone with name "S5".

Note that Milestone is a ProcessNode and there is another Entity called Phase which is also a ProcessNode. These can be contained in the "nodes" collection of my Process Entity.

Spring Data JPA规范在嵌套集合中搜索属性。

I tried to write something like this:

    public static Specification&lt;Process&gt; hasMilestoneWithName(final String milestoneName) {
return (Specification&lt;Process&gt;) (root, query, criteriaBuilder) -&gt; {
Path&lt;?&gt; namePath = root.join(&quot;nodes&quot;).get(&quot;name&quot;);
return criteriaBuilder.equal(namePath, milestoneName);
};
}

This does not work, but throws:

> Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [nodes] on this ManagedType [com.smatrics.dffs.processservice.model.entities.Process]

I don't really know how to use the API. Examples often refer to a meta-model that would be generated by the IDE or maven, but I really do not want to have any static generated resources. Please help me resolve this with Specification of spring-data-jpa without a generated meta-model.

Also if you could help me write the hql it would be awesome.

Thanks!

答案1

得分: 1

  • 建议一个更简单的替代方案,从底部开始:

    • 使用name=S5加载Milestone实体:findByName(&quot;S5&quot;)
    • 返回每个MilestoneProcess
    • 过滤掉重复项

或者,您甚至可以通过返回不是Milestone实体而是每个MilestoneProcess的ID来节省一些SQL查询,然后通过ID列表加载Process节点:

本机SQL等效代码为

select *
from process
where id in (
select process_id
from milestone
where name = &#39;S5&#39;
)

不管我的解决方案如何,您的join看起来对我来说并不完全正确,但我无法指出哪里出了问题 - 也许JPA元模型上有其他返回CollectionJoin的方法?不确定。可能是因为@ManyToAny不是JPA标准,所以JPA criteria API不会将nodes识别为有效的“可连接”字段。

英文:

I would suggest a simpler alternative, coming from bottom-up:

  • Load Milestone entities with name=S5: findByName(&quot;S5&quot;)
  • Return the Process for each Milestone
  • Filter out the duplicates

Or you could even save a few SQL queries by returning not the Milestone entity but only the ID of the Process for each Milestone and then load the Process nodes by a list of IDs:

The (native) SQL equivalent would be

select *
from process
where id in (
select process_id
from milestone
where name = &#39;S5&#39;
)

Regardless of my solution your join does not look completely correct to me but I can't point out what's wrong - maybe there are other methods on the JPA metamodel that return a CollectionJoin? Not sure. Probably it is because @ManyToAny is not JPA standard so the JPA criteria API does not recognize nodes as a valid "joinable" field.

huangapple
  • 本文由 发表于 2020年8月3日 21:13:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/63230128.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定