我有以下两个实体。
@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查询 给我提供作者和他们所有的评论。我的预测是
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查询(底层DB是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)
我的测试数据包括一个作者和两个评论。不幸的是,当我运行这个查询时,我总是得到两个作者(实际上是同一个作者),每个作者有一个评论。当然,这也是DB返回每条记录的方式(原始SQL看起来很好,应该是这样)。Hibernate或Spring Data JPA(或任何负责的)是否可以将所有注释合并到列表中,或者我是否需要编写Java代码,根据作者id合并行?
如果可能的话,我希望DB已经完成了所有的数据转换,并且有一个结构良好的Projection作为查询的输出。
我的演示测试是
@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中能实现吗?
使用关系在 Author
实体来获取作者的评论。
@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();
}
你只需要查询作者就可以得到所有的评论。你需要在Author类中添加额外的List,然后,你可以使用JAP Reposity。
@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。