【问题标题】:Spring Data JPA And NamedEntityGraphsSpring Data JPA 和 NamedEntityGraphs
【发布时间】:2015-11-03 19:25:52
【问题描述】:

目前我正在努力只获取我需要的数据。 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);
}

【问题讨论】:

  • 我认为不同命名的方法解决方案会更合适。

标签: jpa fetch spring-data-jpa entitygraph


【解决方案1】:

我们遇到了一个类似的问题,并设计了几个预期的解决方案,但似乎没有一个优雅的解决方案来解决看似常见的问题。

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) 自定义存储库。另一种可能的解决方案是创建自定义查询方法并注入 EntityManager。该解决方案为您的存储库提供了最干净的接口,因为您可以为您的方法命名一些有意义的东西,但是添加到您的代码中以提供解决方案是非常复杂的,并且您手动获取实体管理器而不是使用 Spring 魔术。

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,因为它是最简单的实现,但这确实意味着当我们使用我们的存储库时,我们必须查看 fetch 方法以确保我们使用的是具有正确实体图的方法。祝你好运。

来源:

我没有足够的声誉来发布我的所有来源。对不起:(

【讨论】:

  • 除了更改方法的动词之外,还可以在动词和“by”之间添加任意字符串,例如findEagerByIdfindLessEagerById
  • @JensSchauder findAll 有类似的解决方案吗?
  • @JordanMackie 这会产生一些奇怪的名称,但您可以在 By 之后结束名称,这使其成为 findAll 的变体:findAllByfindEverythingBy ...
  • 对于#3,你必须做连接吗?我认为这就是图表的用途
  • @JordanMackie,是的,你可以写findAllEagerBygetAllWithAssociationsBy 等。如果你只是用By 完成你的名字(就像下一个模式find\get\read... + All + ... + By),它将被视为与默认方法findAll相同。
【解决方案2】:

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

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

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

有了这个扩展,你可以立即使用这个方法:

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

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

【讨论】:

  • 我的回答更准确了。
  • 这非常有用。关于这个问题,我有一张出色的 Spring Data 票 jira.spring.io/browse/DATAJPA-645?filter=-2。您是否考虑过加入 Spring Data 项目?它是否适用于 QueryDsl 谓词方法?
  • 你好 Alan,在创建 github.com/Cosium/spring-data-jpa-entity-graph 之前,我已经要求在票证 jira.spring.io/browse/DATAJPA-749 中包含动态实体图。但被拒绝了。包括 QueryDsl 支持。有一些测试用例。但我不在我的项目中使用 QueryDsl。如果您发现错误,请毫不犹豫地在跟踪器上报告它们;)
  • 太棒了。谢谢。有趣的是阅读您票上的讨论!
  • 嘿 Réda,谢谢你的解决方案,看起来很有希望,但是它与 Java 12 版本不兼容
【解决方案3】:

@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();

   ...

}

【讨论】:

  • 我喜欢你的解决方案。但是我在使用 NamedEntityGraphs 时遇到了问题 - 我不能像这样返回自定义对象 @Query("SELECT new DTO(f) from Entity f")
  • @ChrisSpencer 的回答似乎暗示将 jpql 和实体图结合起来不起作用,您能确认这是否有效吗?
  • @JordanMackie 我在我的项目中使用过它:github.com/gkislin/topjava/blob/master/src/main/java/ru/…
  • 已经很长时间了,但我一直看到@EntityGraph 不能与@Query 一起工作,而当我测试它时它确实工作正常。不确定这个假设来自哪里......可能是旧版本?我正在使用spring-data-jpa 2.5.5
【解决方案4】:

正如我从This article 中发现的那样,可以在派生查询上使用@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()。更简洁一些,但阅读起来可能更令人困惑。在 Spring 中使用仅以 By 结尾且没有任何条件的方法名称可能有在 Spring 的未来版本中中断的危险,因为它似乎不是派生查询名称的预期用途。

看起来在 Spring 中解析派生查询名称的代码是 here on github。如果您对派生查询存储库方法名称的可能性感到好奇,可以查看那里。

These 是派生查询的 spring 文档。

这是用 spring-data-commons-2.2.3.RELEASE 测试的

【讨论】:

    【解决方案5】:

    编辑:这实际上不起作用。最终不得不选择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);
        }
    }
    

    您不必做任何重新实现,并且可以使用现有接口自定义实体图。

    【讨论】:

      【解决方案6】:

      您能否尝试使用您将请求的子项创建 EntiyGraph 名称,并为 find all 方法提供相同的名称。 例如:

      @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 方法。

      【讨论】:

        猜你喜欢
        • 2017-11-29
        • 1970-01-01
        • 2012-11-09
        • 2019-07-20
        • 2016-02-12
        • 2022-01-01
        • 2018-06-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多