Spring Data JPA 和 NamedEntityGraphs

问题描述 投票:0回答:6

目前我正在努力解决如何仅获取我需要的数据的问题。 findAll() 方法需要根据调用位置来获取数据。 我不想最终为每个实体图编写不同的方法。 另外,我会避免调用实体管理器并自己形成(重复的)查询。 基本上我想使用 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
6个回答
60
投票

我们遇到了类似的问题,并设计了几种前瞻性的解决方案,但对于似乎是一个常见问题,似乎没有一个优雅的解决方案。

  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 方法,以确保我们使用的是具有正确实体图的方法。祝你好运。

来源:

我没有足够的声誉来发布我的所有来源。抱歉:(


23
投票

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

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

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

通过此扩展,您将立即可以使用此方法:

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

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


15
投票

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

   ...

}


5
投票

在派生查询上使用

@EntityGraph
注释是可能的,正如我从 这篇文章 中发现的那样。文章有例子:

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

但我不认为“with”有什么特别之处,实际上你可以拥有

find
By
之间的任何东西。我尝试了这些并且它们起作用了(这段代码是 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
投票

您可以尝试使用您将请求的子项创建 EntiyGraph 名称,并为查找所有方法指定相同的名称吗? 例如:

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

© www.soinside.com 2019 - 2024. All rights reserved.