【问题标题】:JPA Criteria API Specification for Many to Many多对多的 JPA 标准 API 规范
【发布时间】:2018-09-06 00:22:00
【问题描述】:

我有如下所述的三个课程。我正在尝试创建一个规范来过滤链接表中匹配的数据。

public class Album {
    private Long id;
    private List<AlbumTag> albumTags;
}

public class Tag {
    private Long id;
    private String category;
}

public class AlbumTag{
    private Long id;
    private Album album;
    private Tag tag;
}

在上面给出的架构中,我试图找到的是专辑表中所有专辑的列表,其中包含 AlbumTag 中的链接。我要实现的SQL,不必相同,如下

select *
from Album A 
where (A.Id in (select [AT].AlbumId 
from AlbumTag [AT]))

到目前为止我尝试过但不起作用的方法当然如下

public class AlbumWithTagSpecification implements Specification<Album> {

    @Override
    public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

         final Subquery<Long> personQuery = cq.subquery(Long.class); 
         final Root<Album> album = personQuery.from(Album.class); 
         final Join<Album, AlbumTag> albumTags = album.join("albumTags");
         personQuery.select((albumTags.get("album")).get("id"));
         personQuery.where(cb.equal(album.get("id"), (albumTags.get("album")).get("id"))); 
         return cb.in(root.get("id")).value(personQuery);

    }
}

【问题讨论】:

    标签: java spring spring-boot spring-data-jpa jpa-2.0


    【解决方案1】:

    JPA 中的子查询仅适用于 CriteriaBuilder.exists(),所以我会尝试:

    public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
    
         final Subquery<Long> subQuery = cq.subquery(Long.class); 
         final Root<AlbumTag> albumTag = subQuery.from(AlbumTag.class); 
         // it doesn't really matter what we select
         subQuery.select(cb.literal(1));
         subQuery.where(cb.equal(root.get("id"), (albumTag.get("album")).get("id"))); 
    
         return cb.exists(subQuery);
    
    }
    

    相当于

    select *
    from Album A 
    where exists(
        select 1 from AlbumTag AT 
        where AT.AlbumId = A.Id
    )
    

    【讨论】:

      【解决方案2】:
      creteria query for join tables
      
      CriteriaQuery<Album> query = cb.createQuery(Album.class);
      Root<Album> album = query.from(Teacher.class);
      Join<Album, AlbumTag> tag = teacher.join("id");
      query.select(tag).where(cb.equal(album.get("album")));
      
      List<Album> results = em.createQuery(query).getResultList();
      for (Album al : results) {
          System.out.println("album-->+al.get(name));
      }
      

      【讨论】:

        【解决方案3】:

        好吧,在这种情况下,我不会使用 in 操作 - 它只会使查询和规范复杂化。您描述的问题实际上是将来自Table A 的记录与来自Table B 的相关记录连接起来,因此您的查询将类似于:

        SELECT a from Album a join AlbumTag at on a.id = at.albumId - 根据您的需要,它将返回所有具有专辑标签的专辑。 Inner join explained

        所以在你的情况下,我会创建这个“工厂”方法,它会为你创建这个规范。

        public static Specification<Album> withTags() {
            return new Specification<Album>() {
                @Override
                public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                    return root.join("albumTags").getOn();
            }
        };
        

        }

        另外,我建议您查看 hibernate 中的 static metamodel 库 - link to introduction。它从您的实体类为您生成静态模型,帮助您避免使用硬编码字符串创建查询/规范。

        【讨论】:

          【解决方案4】:

          使用spring boot和spring data JPA,可以优先选择实体关系来获取数据。

          1.用下面给出的实体关系注释领域类:

          @Entity
          @Table(name="Album")
          public class Album {
              @Id
              @Column(name="id")
              private Long id;
              @OneToMany(targetEntity = AlbumTag.class, mappedBy = "album")
              private List<AlbumTag> albumTags;
          
              //getter and setter
          }
          
          @Entity
          @Table(name="Tag")
          public class Tag {
              @Id
              @Column(name="id")
              private Long id;
              @Column(name="category")
              private String category;
          
              //getter and setter
          }
          
          @Entity
          @Table(name="AlbumTag")
          public class AlbumTag{
              @Id
              @Column(name="id")
              private Long id;
              @ManyToOne(optional = false, targetEntity = Album.class)
              @JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)
              private Album album;
              @ManyToOne(optional = false, targetEntity = Tag.class)
              @JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)
              private Tag tag;
          
              //getter and setter
          }
          

          2.使用 spring 数据获取详细信息,如下所示:

          Album album = ablumRepository.findOne(1); // get the complete details about individual album.
          List<AlbumTag> albumTags = ablum.getAlbumTags(); // get the all related albumTags details for particular album.
          

          我希望这能帮助你解决它。

          【讨论】:

            【解决方案5】:

            这看起来像是一个经典的多对多示例。您拥有的三个类直接映射到您期望在数据库中的表。 JPA 是一个对象关系映射 (ORM) 库,这意味着我们可以以更 OO 的风格构造类并映射到底层关系数据库。

            AlbumTag 类可以省略,@ManyToMany 关系添加到AlbumTag

            public class Album {
                private Long id;
            
                @ManyToMany
                @JoinTable(name="AlbumTag",
                    joinColumns=
                        @JoinColumn(name="album", referencedColumnName="id"),
                    inverseJoinColumns=
                        @JoinColumn(name="tag", referencedColumnName="id"))
                private List<Tag> tags;
            }
            
            public class Tag {
                private Long id;
                private String category;
            
                @ManyToMany(mappedBy="tags")
                private List<Album> albums;
            }
            

            要查找Tag 的专辑,您首先需要使用findById(1l);findByCategory("Rock"); 从存储库中检索Tag,然后只需在Tag 对象上调用getAlbums()

            注意:这里的一个细微差别是 AlbumTag 表只有两列(专辑和标签)。 AlbumTag 上的额外 id 列是不必要的,因为专辑和标签的组合将是一个唯一的 id,而且您永远不需要在此表中按 id 查找。

            【讨论】:

            • id 列仅用于示例,但还有其他列存储信息,例如何时使用日期列创建此关联,创建此关联的人以及位置名称在哪里。
            【解决方案6】:

            由于您使用的是 spring-data-jpa,因此您应该真正利用它提供的功能。

            我的第一个问题与您的实体类有关。我不明白为什么需要在专辑类中存储专辑标签列表。由于您有一个连接表,因此这些信息是多余的。

            其次,您应该注释您的实体类:

            @Entity
            public class Album {
            @Id
            @Column
            private Long id;
            }
            
            @Entity
            public class Tag {
              @Id
              @Column
              private Long id;
              @Column
              private String category;
            }
            
            @Entity
            @Table
            public class AlbumTag{
              @Id
              @Column
              private Long id;
              @ManyToOne
              @JoinColumn
              private Album album;
              @ManyToOne
              @JoinColumn
              private Tag tag;
            }
            

            接下来,您应该为实体类创建存储库。

            interface AlbumRepository extends JpaRepository<Album, Long>{
            
               @Query
               ("select DISTINCT(a) from AlbumTag at "+
                "join at.album a "
                "where at.tag is not null")
               List<Album> findAlbumWithTag();
            }
            

            然后只需调用存储库函数,该函数将返回一个包含至少一个标签的专辑列表。

            【讨论】:

            • 通过编写 SQL,我将失去我不想做的编译时帮助,因此我要求提供特定于规范的解决方案
            • 我不认为这是一个争论,但这是你的决定。编写 jpql 很容易,您可以指示 hibernate 在运行时打印本机查询。启动应用程序时也会验证查询。
            • 这不回答问题,完全无关。
            猜你喜欢
            • 1970-01-01
            • 2019-12-07
            • 1970-01-01
            • 2017-10-28
            • 1970-01-01
            • 2011-12-29
            • 2012-12-06
            • 2020-11-06
            • 2015-10-28
            相关资源
            最近更新 更多