Spring Data JPA 和 NamedEntityGraphs

59

目前我正在努力获取我所需的数据。 findAll()方法需要根据其被调用的位置来获取数据。 我不想为每个实体图编写不同的方法。 另外,我希望避免调用entitymanagers并自己形成(重复的)查询。 基本上,我想使用内置的findAll方法,但是带有我喜欢的实体图。 有任何机会吗?

@Entity
@Table(name="complaints")
@NamedEntityGraphs({
    @NamedEntityGraph(name="allJoinsButMessages", attributeNodes = {
            @NamedAttributeNode("customer"),
            @NamedAttributeNode("handling_employee"),
            @NamedAttributeNode("genre")
    }),
    @NamedEntityGraph(name="allJoins", attributeNodes = {
            @NamedAttributeNode("customer"),
            @NamedAttributeNode("handling_employee"),
            @NamedAttributeNode("genre"),
            @NamedAttributeNode("complaintMessages")
    }),
    @NamedEntityGraph(name="noJoins", attributeNodes = {

    })
})
public class Complaint implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private long id;

    private Timestamp date;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer")
    private User customer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "handling_employee")
    private User handling_employee;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="genre")
    private Genre genre;

    private boolean closed;

    @OneToMany(mappedBy = "complaint", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<ComplaintMessage> complaintMessages = new ArrayList<ComplaintMessage>();

//getters and setters
}

还有我的JPARepository

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

    List<Complaint> findByClosed(boolean closed);

    @EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
    @Override
    List<Complaint> findAll(Sort sort);
}

2
我认为使用不同命名的方法解决方案会更加合适。 - K.Nicholas
6个回答

59
We ran into a similar problem and came up with some potential solutions, but it seems that there is no elegant solution for what appears to be a common problem.
1) 前缀。Data JPA提供了几个前缀(find,get等)用于方法名称。一种可能性是在不同的命名图中使用不同的前缀。这是最少的工作量,但会隐藏开发人员对方法的理解,有很大的潜力导致一些非明显的问题,如错误的实体加载。
@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);

    @EntityGraph(value = "User.membershipYears", type = EntityGraphType.LOAD)
    User readByUserId(int id);
}

2)CustomRepository。另一个可能的解决方案是创建自定义查询方法并注入EntityManager。这种解决方案为您的存储库提供了最清晰的界面,因为您可以为方法命名有意义的名称,但是它会给您的代码添加相当大的复杂性来提供解决方案,并且您正在手动获取实体管理器而不是使用Spring magic。

interface UserRepositoryCustom {
    public User findUserWithMembershipYearsById(int id);
}

class UserRepositoryImpl implements UserRepositoryCustom {
    @PersistenceContext
    private EntityManager em;
    @Override
    public User findUserWithMembershipYearsById(int id) {
        User result = null;
        List<User> users = em.createQuery("SELECT u FROM users AS u WHERE u.id = :id", User.class)
                .setParameter("id", id)
                .setHint("javax.persistence.fetchgraph", em.getEntityGraph("User.membershipYears"))
                .getResultList();
        if(users.size() >= 0) {
            result = users.get(0);
        }
        return result;
    }
}

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);
}

3) JPQL。本质上,这只是放弃命名实体图并使用JPQL来处理联接。在我看来不太理想。

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);

    @Query("SELECT u FROM users WHERE u.id=:id JOIN??????????????????????????")
    User findUserWithTags(@Param("id") final int id);
}

我们选择了选项1,因为它在实现上最简单,但这意味着当我们使用存储库时,我们必须查看提取方法,以确保我们正在使用具有正确实体图的方法。祝你好运。

来源:

很抱歉,我没有足够的声望来发布所有来源。


13
可以在动词和 "by" 之间添加任意字符串,比如 findEagerByIdfindLessEagerById,而不改变方法的动词。 - Jens Schauder
1
@JensSchauder 是否有类似的解决方案适用于findAll吗? - Jordan Mackie
4
结果会得到一些奇怪的名称,但您可以在 By 后结束名称,这将使其成为 findAll 的变体:findAllByfindEverythingBy ... - Jens Schauder
对于第三个问题,你需要进行连接吗?我认为这就是图表的作用。 - Matt Broekhuis
1
@JordanMackie,是的,你可以直接编写findAllEagerBygetAllWithAssociationsBy等等。 如果你只是在你的名称后面加上By(比如下一个模式find\get\read... + All + ... + By),它将被视为默认方法findAll的相同处理方式。 - Max
显示剩余2条评论

23

我们遇到了同样的问题,并构建了一个Spring Data JPA扩展来解决它:

https://github.com/Cosium/spring-data-jpa-entity-graph

该扩展允许将命名的或动态构建的EntityGraph作为任何存储库方法的参数传递。

有了这个扩展,您会立即获得此方法:

List<Complaint> findAll(Sort sort, EntityGraph entityGraph);

并且能够在运行时使用选择的EntityGraph调用它。


我让我的答案更准确了。 - Réda Housni Alaoui
1
这非常有用。我在这个问题周围有一个突出的Spring Data票 https://jira.spring.io/browse/DATAJPA-645?filter=-2。您是否考虑将其纳入Spring Data项目?它是否与QueryDsl Predicate方法兼容? - Alan Hay
你好,Alan, 在创建https://github.com/Cosium/spring-data-jpa-entity-graph之前,我曾在票号https://jira.spring.io/browse/DATAJPA-749中提出了动态实体图包含的请求。 但是被拒绝了。QueryDsl支持已经包含在内。有一些测试用例。但是我在我的项目中不使用QueryDsl。如果您发现错误,请毫不犹豫地在跟踪器上报告它们;) - Réda Housni Alaoui
太好了。谢谢。很有趣阅读你的工单上的讨论! - Alan Hay
嗨,Réda,感谢您提供的解决方案,看起来非常有前途,但是它与Java 12版本不兼容。 - Oleg Baranenko
嗨@OlegBaranenko,请查看https://github.com/Cosium/spring-data-jpa-entity-graph/issues/23 - Réda Housni Alaoui

13

使用@EntityGraph@Query一起使用

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

   @EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
   @Query("SELECT c FROM Complaint ORDER BY ..")
   @Override
   List<Complaint> findAllJoinsButMessages();

   @EntityGraph(value = "allJoins" , type=EntityGraphType.FETCH)
   @Query("SELECT c FROM Complaint ORDER BY ..")
   @Override
   List<Complaint> findAllJoin();

   ...

}


1
我喜欢你的解决方案。但是我在使用NamedEntityGraphs时遇到了问题 - 我无法像这样返回自定义对象 @Query("SELECT new DTO(f) from Entity f") - Oleg Kuts
@ChrisSpencer的答案似乎表明组合jpql和实体图不起作用,你能确认这是否有效吗? - Jordan Mackie
3
@JordanMackie 我在我的项目中使用了它:https://github.com/gkislin/topjava/blob/master/src/main/java/ru/javawebinar/topjava/repository/datajpa/CrudUserRepository.java - Grigory Kislin
已经过了很长时间,但我仍然看到 @EntityGraph@Query 不兼容的说法,而当我测试时它确实可以正常工作。不确定这个假设是从哪里来的...也许是旧版本?我正在使用 spring-data-jpa 2.5.5 - Blockost

5

我从这篇文章中了解到,在派生查询中使用@EntityGraph注释是可行的。该文章提供了以下示例:

@Repository
public interface ArticleRepository extends JpaRepository<Article,Long> {
   @EntityGraph(attributePaths = "topics")
   Article findOneWithTopicsById(Long id);
}

但我认为“with”没有什么特别之处,实际上你可以在findBy之间使用任何内容。我尝试了以下代码,它们都有效(这段代码是Kotlin的,但思路是一样的):

interface UserRepository : PagingAndSortingRepository<UserModel, Long> {
    @EntityGraph(attributePaths = arrayOf("address"))
    fun findAnythingGoesHereById(id: Long): Optional<UserModel>

    @EntityGraph(attributePaths = arrayOf("address"))
    fun findAllAnythingGoesHereBy(pageable: Pageable): Page<UserModel>
}


本文提到了一个警告,即您不能创建类似于findAll的方法,该方法将在没有By条件的情况下查询所有记录,并以findAllWithTopicsByIdNotNull()为例。我发现只需在名称末尾单独包含By就足够了:findAllWithTopicsBy()。这样做更加简洁,但可能更难以理解。使用仅以By结尾而没有任何条件的方法名称可能会在Spring的未来版本中出现故障,因为它似乎不是衍生查询名称的预期用途。
看起来,在Spring中解析衍生查询名称的代码在这里的github上。如果您想了解衍生查询存储库方法名称的可能性,可以在那里查看。 这里是衍生查询的Spring文档。
此内容已在spring-data-commons-2.2.3.RELEASE中进行了测试。

2

编辑:这个方法实际上不起作用。最终我只能选择https://github.com/Cosium/spring-data-jpa-entity-graph。默认方法看上去是正确的,但无法成功覆盖注解。

使用JPA, 我发现可行的方法是使用一个带有不同EntityGraph注解的默认方法:

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

    List<Complaint> findByClosed(boolean closed);

    @EntityGraph(attributePaths = {"customer", "genre", "handling_employee" }, type=EntityGraphType.FETCH)
    @Override
    List<Complaint> findAll(Sort sort);

    @EntityGraph(attributePaths = {"customer", "genre", "handling_employee", "messages" }, type=EntityGraphType.FETCH)
    default List<Complaint> queryAll(Sort sort){
      return findAll(sort);
    }
}

您不需要重新实现任何内容,可以使用现有的接口自定义实体图。


1
你能否尝试创建一个包含子级的EntityGraph名称,然后将相同的名称用于查找所有方法。 例如:
@EntityGraph(value = "fetch.Profile.Address.record", type = EntityGraphType.LOAD)
 Employee getProfileAddressRecordById(long id);

针对您的情况:
@NamedEntityGraph(name="all.Customer.handling_employee.genre", attributeNodes = {
        @NamedAttributeNode("customer"),
        @NamedAttributeNode("handling_employee"),
        @NamedAttributeNode("genre")
})

仓库中的方法名

@EntityGraph(value = "all.Customer.handling_employee.genre" , type=EntityGraphType.FETCH)
 findAllCustomerHandlingEmployeeGenre

这样你就可以跟踪不同的findAll方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接