【问题标题】:Spring Data JPA projection with ManyToOne使用 ManyToOne 进行 Spring Data JPA 投影
【发布时间】:2020-04-30 13:12:36
【问题描述】:

我有以下两个实体:

@Entity
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String userName;
    // Getters/setters
}

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "author_id")
    private Author author;

    private String comment;
    // Getters/setters
}

我想在 Spring Data JPA 存储库中生成一个 JPQL 查询,它为我提供了作者所有的 cmets。我的投影是

public interface AuthorProjection {
    String getUserName();
    List<String> getComments();
}

我的 JPQL 查询是

public interface AuthorRepository extends JpaRepository<Author, Long> {

    @Query(value= "SELECT a.id as id, \n" +
            "    a.userName as userName,\n" +
            "    c.comment as comments\n" +
            "from Author a \n" +
            "left join Comment c on c.author = a")
    List<AuthorProjection> authorsWithComments();
}

public interface CommentRepository extends JpaRepository<Comment, Long> { 
}

这会通过 Hibernate 生成以下原始 SQL 查询(底层数据库是 PostgreSQL):

select author0_.id as col_0_0_, author0_.user_name as col_1_0_,comment1_.comment as col_2_0_ from author author0_ left outer join comment comment1_ on (comment1_.author_id=author0_.id)

我的测试数据由一个作者和两个评论组成。不幸的是,当我运行它时,我总是得到两个作者(实际上是同一个作者),每个作者都有一个评论。这当然是数据库返回每一行的方式(原始 SQL 看起来不错,应该是这样)。 Hibernate 或 Spring Data JPA(或任何负责人)是否可以将所有 cmets 合并到列表中,还是我需要编写 Java 代码来根据作者 ID 合并行?

如果可能,我希望数据库已经执行所有数据转换,并有一个结构良好的投影作为查询的输出。

我的演示测试是:

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class JpatestApplicationTests {

    @Autowired
    private AuthorRepository authorRepository;

    @Autowired
    private CommentRepository commentRepository;

    @Test
    void contextLoads() {
        var author = new Author();
        author.setUserName("John Doe");
        var savedAuthor = authorRepository.save(author);

        var comment1 = new Comment();
        comment1.setComment("First comment").setAuthor(savedAuthor);
        commentRepository.save(comment1);

        var comment2 = new Comment();
        comment2.setComment("Second comment").setAuthor(savedAuthor);
        commentRepository.save(comment2);

        authorRepository.authorsWithComments().forEach(
                projection -> System.out.printf("%s, %s\n", projection.getUserName(), projection.getComments())
        );
    }
}

给出以下输出:

John Doe, [First comment]
John Doe, [Second comment]

并直接在 PostgreSQL 中运行,它给出:

 col_0_0_ | col_1_0_ |    col_2_0_
----------+----------+----------------
        1 | John Doe | First comment
        1 | John Doe | Second comment
(2 rows)

我预期的 Java 输出是:

John Doe, [First comment, Second comment]

JPA 甚至可以做到这一点吗?

【问题讨论】:

  • 你能添加你的原始 sql 查询吗?
  • 我已经用一个简单的工作示例对其进行了更新。

标签: java spring-data-jpa jpql


【解决方案1】:

使用Author实体中的关系来获取作者的cmets。

@OneToMany(mappedBy = "author")
private List<Comment> comments = new ArrayList<>();

那就这样查询

@Query(value= "SELECT a.userName as userName, a.comments as comments from Author a")
List<AuthorProjection> authorsWithComments();

和投影一样

public interface AuthorProjection {
    String getUserName();
    List<Comment> getComments();
}

【讨论】:

  • 不改变实体也可以吗?我的实际查询比这样复杂得多,我不想更改实体。但我想让数据库做尽可能多的工作。
  • 如果不使用关系那么JPA如何为作者整合所有cmet。您的查询在加入后返回笛卡尔积,这就是您只获得一个 cmets 的原因。或者您必须为作者手动集成所有 cmets。
  • 你试过这个解决方案了吗?
  • 我不能,因为没有 OneToMany 关系,我无法说服我的同事让我添加它:-(。最后我创建了一个事务性 @Service 方法,我在其中调用了多个存储库并且只是在Java中汇总了结果。我确实想在JPQL中完全尝试,但最后我只需要结果。不过感谢您的帮助!
【解决方案2】:

您只需查询作者即可获得所有 cmets。您需要在 Author 类中添加额外的 List。

@OneToMany(mappedBy = "author", fetch=FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();

然后,您可以使用 JAP Reposity

@Repository
public interface AuthorRepository  extends CrudRepository<Author, Long> {

}

CrudRepository已经实现了很多有用的API,比如findAll()findById(),你不需要写任何sql。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-01
    • 2018-09-04
    • 1970-01-01
    • 1970-01-01
    • 2018-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多