【问题标题】:JPQL: How to join via @ElementCollection with @MapKeyJoinColumnJPQL:如何通过 @ElementCollection 和 @MapKeyJoinColumn 加入
【发布时间】:2023-03-13 09:04:02
【问题描述】:

我在创建正确的 JPQL 查询以通过下表加入时遇到问题:

虽然在 GROUPS 和 USERS 之间有一个常规的 @ManyToMany 映射表,但 DOCUMENTS_GROUPS 是导致问题的原因。正如您在以下实体中看到的那样,我希望将 DOCUMENTSGROUPS 之间的关系映射为包含 access_mode 的地图(有效很好,除了查询):

@Entity
@Table(name = "DOCUMENTS")
@NamedQueries({
    @NamedQuery(
        name = "Documents.findAccessibleByUser",
        query = "SELECT d FROM Document d INNER JOIN d.groups g INNER JOIN KEY(g).members m WHERE m.id = :userId"
    )
})
public class Document {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @ElementCollection
    @CollectionTable(name = "DOCUMENTS_GROUPS", joinColumns = {@JoinColumn(name = "document_id")})
    @MapKeyJoinColumn(name = "group_id")
    @Column(name = "access_mode")
    @Enumerated(EnumType.STRING)
    private Map<Group, AccessMode> groups = new HashMap<>();

    /* ... */

}

Group 比较正常:

@Entity
@Table(name = "GROUPS")
public class Group {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(length = 255)
    private String name;

    @ManyToMany
    @JoinTable(name = "USERS_GROUPS", //
            joinColumns = {@JoinColumn(name = "group_id")}, //
            inverseJoinColumns = {@JoinColumn(name = "user_id")} //
    )
    private Set<User> members = new HashSet<>();

    /* ... */

}

我现在的问题是:我需要如何修改 JPQL 查询中的第二个 JOIN?

SELECT d FROM Document d
  INNER JOIN d.groups g
  INNER JOIN KEY(g).members m
WHERE m.id = :userId

语法错误(在INNER JOIN 之后出现意外的KEY)。

当然,我已经尝试过普通的INNER JOIN g.members m,但由于我们处理的是Map&lt;Group, AccessMode&gt;,所以cannot dereference scalar collection element: members 会失败。

【问题讨论】:

    标签: jpa jpa-2.0 jpql


    【解决方案1】:

    我在使用简单的键值Map&lt;String, String&gt; 时遇到了同样的问题,例如:

    @EntityItem.java

    @ElementCollection
    @MapKeyColumn(name = "name")
    @Column(name = "value")
    @CollectionTable(indexes = @Index(columnList = "value"))
    private Map<String, String> attributes = new HashMap<>();
    

    加入属性是可能的:

    Query query = em.createQuery("SELECT i FROM Item i INNER JOIN i.attributes attr");
    

    查询字段:

    Query query = em.createQuery("SELECT i FROM Item i INNER JOIN i.attributes attr WHERE attr.value = 'something'");
    

    我调试了 Hibernate 内部,发现别名 attr 已经解析为值 (e.attributes.value),所以你唯一能做的就是:

    Query query = em.createQuery("SELECT i FROM Item i INNER JOIN i.attributes attr WHERE attr = 'something'");
    

    但我没有找到任何说明这一点的文档或 JPQL 示例。在我的情况下,这种行为是无用的,因为我想同时拥有键和值的条件。这就是我迁移到具有键映射和复合主键的外部实体集合的原因。它的方式更复杂,但可以按预期工作。

    防止单一主键的复合键实体

    @Embeddable
    public class ItemAttributeName implements Serializable {
    
        private String name;
    
        @ManyToOne
        @JoinColumn(nullable = false)
        private Item item;
    
        // Empty default constructor is important
        public ItemAttributeName() {
    
        }
    
        public ItemAttributeName(Item item, String name) {
            this.item = article;
            this.name = name;
        }
    }
    

    真正的属性实体

    @Entity
    public class ItemAttribute {
    
        @EmbeddedId
        private ItemAttributeName id;
    
        private String value;
    
        // Empty default constructor is important
        public ItemAttribute() {
    
        }
    
       
        public ItemAttribute(Item item, String name) {
            this.id = new ItemAttributeName (item, name);
        }
    
        public String getValue() {
            return value;
        }
    }
    

    @EntityItem.java

    @OneToMany(mappedBy = "id.item",cascade = CascadeType.PERSIST)
    @MapKeyColumn(name = "name")
    public Map<String, ItemAttribute> attributes = new HashMap<>();
    

    创建实体:

    Item item = new Item ();
    ItemAttribute fooAttribute = new ItemAttribute(item, "foo");
    fooAttribute.setValue("356");
    item.attributes.put("foo", fooAttribute);
    

    查询实体:

    Query query = em.createQuery("SELECT i FROM Item i JOIN i.attributes attr WHERE attr.id.name = 'foo' AND attr.value='bar'");
    List<Item> resultList = query.getResultList();
    System.out.println(resultList.get(0).attributes.get("foo").getValue());
    

    打印出来:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-12
      • 2013-12-08
      • 2014-03-22
      • 1970-01-01
      • 1970-01-01
      • 2011-12-23
      • 1970-01-01
      • 2017-12-20
      相关资源
      最近更新 更多