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

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

Spring Data JPA Specification search for property in nested collection

问题

以下是您要翻译的内容:

  1. Say I have at least two entities.
  2. @Entity
  3. public class Process {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. private Long id;
  7. @Column(unique = true)
  8. private String name;
  9. @ManyToAny(
  10. metaColumn = @Column(name = "node_type"),
  11. fetch = FetchType.LAZY
  12. )
  13. @AnyMetaDef(
  14. idType = "long", metaType = "string",
  15. metaValues = {
  16. @MetaValue(targetEntity = Milestone.class, value = MILESTONE_DISC),
  17. @MetaValue(targetEntity = Phase.class, value = PHASE_DISC)
  18. }
  19. )
  20. @Cascade({org.hibernate.annotations.CascadeType.ALL})
  21. @JoinTable(
  22. name = "process_nodes",
  23. joinColumns = @JoinColumn(name = "process_id", nullable = false),
  24. inverseJoinColumns = @JoinColumn(name = "node_id", nullable = false)
  25. )
  26. private Collection<ProcessNode> nodes = new ArrayList<>();
  27. ...
  28. }
  29. -------
  30. @Entity
  31. @ToString
  32. @DiscriminatorValue(MILESTONE_DISC)
  33. public class Milestone implements ProcessNode {
  34. @Id
  35. @GeneratedValue(strategy = GenerationType.IDENTITY)
  36. private Long id;
  37. private String name;
  38. @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
  39. private Collection<ResultDefinition> results;
  40. @ToString.Exclude
  41. @ManyToOne(fetch = FetchType.LAZY)
  42. @Transient
  43. private Process process;
  44. ...
  45. }
  46. Now I want to use spring data jpa specification to find (all) processes which have a milestone with name "S5".
  47. 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.
  48. I tried to write something like this:
  49. public static Specification<Process> hasMilestoneWithName(final String milestoneName) {
  50. return (Specification<Process>) (root, query, criteriaBuilder) -> {
  51. Path<?> namePath = root.join("nodes").get("name");
  52. return criteriaBuilder.equal(namePath, milestoneName);
  53. };
  54. }
  55. This does not work, but throws:
  56. > 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]
  57. 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.
  58. Also if you could help me write the hql it would be awesome.
  59. Thanks!
英文:

Say I have at least two entities.

  1. @Entity
  2. public class Process {
  3. @Id
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
  5. private Long id;
  6. @Column(unique = true)
  7. private String name;
  8. @ManyToAny(
  9. metaColumn = @Column(name = &quot;node_type&quot;),
  10. fetch = FetchType.LAZY
  11. )
  12. @AnyMetaDef(
  13. idType = &quot;long&quot;, metaType = &quot;string&quot;,
  14. metaValues = {
  15. @MetaValue(targetEntity = Milestone.class, value = MILESTONE_DISC),
  16. @MetaValue(targetEntity = Phase.class, value = PHASE_DISC)
  17. }
  18. )
  19. @Cascade({org.hibernate.annotations.CascadeType.ALL})
  20. @JoinTable(
  21. name = &quot;process_nodes&quot;,
  22. joinColumns = @JoinColumn(name = &quot;process_id&quot;, nullable = false),
  23. inverseJoinColumns = @JoinColumn(name = &quot;node_id&quot;, nullable = false)
  24. )
  25. private Collection&lt;ProcessNode&gt; nodes = new ArrayList&lt;&gt;();
  26. ...
  27. }

  1. @Entity
  2. @ToString
  3. @DiscriminatorValue(MILESTONE_DISC)
  4. public class Milestone implements ProcessNode {
  5. @Id
  6. @GeneratedValue(strategy = GenerationType.IDENTITY)
  7. private Long id;
  8. private String name;
  9. @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
  10. private Collection&lt;ResultDefinition&gt; results;
  11. @ToString.Exclude
  12. @ManyToOne(fetch = FetchType.LAZY)
  13. @Transient
  14. private Process process;
  15. ...
  16. }

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:

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

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等效代码为

  1. select *
  2. from process
  3. where id in (
  4. select process_id
  5. from milestone
  6. where name = &#39;S5&#39;
  7. )

不管我的解决方案如何,您的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

  1. select *
  2. from process
  3. where id in (
  4. select process_id
  5. from milestone
  6. where name = &#39;S5&#39;
  7. )

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:

确定