Spring Data JPA Repository:如何有条件地获取子实体
Posted
技术标签:
【中文标题】Spring Data JPA Repository:如何有条件地获取子实体【英文标题】:Spring Data JPARepository: How to conditionally fetch children entites 【发布时间】:2016-01-20 21:37:43 【问题描述】:除非提供了某个执行参数,否则如何配置他们的 JPA 实体以不获取相关实体。
根据 Spring 的文档 4.3.9. Configuring Fetch- and LoadGraphs,您需要使用 @EntityGraph
注释来指定查询的获取策略,但这并不能让我在运行时决定是否要加载这些实体。
我可以在单独的查询中获取子实体,但为了做到这一点,我需要将我的存储库或实体配置为不检索任何子实体。不幸的是,我似乎找不到任何关于如何做到这一点的策略。 FetchPolicy
被忽略,EntityGraph
仅在指定我想要急切检索的实体时才有用。
例如,假设Account
是父级,Contact
是子级,并且一个帐户可以有多个联系人。
我希望能够做到这一点:
if(fetchPolicy.contains("contacts"))
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
问题是 spring-data 无论如何都急切地获取联系人。
Account Entity 类如下所示:
@Entity
@Table(name = "accounts")
public class Account
protected String accountId;
protected Collection<Contact> contacts;
@OneToMany
//@OneToMany(fetch=FetchType.LAZY) --> doesn't work, Spring Repositories ignore this
@JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
return contacts;
//getters & setters
AccountRepository 类如下所示:
public interface AccountRepository extends JpaRepository<Account, String>
//@EntityGraph ... <-- has type= LOAD or FETCH, but neither can help me prevent retrieval
Account findOne(String id);
【问题讨论】:
JPA 中的集合默认是惰性的,Spring Data JPA 对此没有任何改变。如果在您的代码中某处调用了getContacts
,那么所有内容都将被获取,因为这是默认设置。
【参考方案1】:
如果没有调用 getContacts() 产生的对象方法,则延迟获取应该正常工作。
如果您喜欢更多的手动工作,并且真的希望对此进行控制(可能更多的上下文取决于用例)。我建议您从帐户实体中删除联系人,然后将帐户映射到联系人中。告诉 hibernate 忽略该字段的一种方法是使用 @Transient 注释对其进行映射。
@Entity
@Table(name = "accounts")
public class Account
protected String accountId;
protected Collection<Contact> contacts;
@Transient
public Collection<Contact> getContacts()
return contacts;
//getters & setters
然后在您的服务类中,您可以执行以下操作:
public Account getAccountById(int accountId, Set<String> fetchPolicy)
Account account = accountRepository.findOne(accountId);
if(fetchPolicy.contains("contacts"))
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
return account;
希望这是您正在寻找的。顺便说一句,代码未经测试,所以您可能应该再次检查。
【讨论】:
【参考方案2】:您可以为此使用@Transactional
。
为此,您需要获取您的帐户实体 Lazily。
@Transactional
注解应该放在所有不可分割的操作周围。
在您的服务层中写入方法,该方法接受一个标志以急切地获取联系人。
@Transactional
public Account getAccount(String id, boolean fetchEagerly)
Account account = accountRepository.findOne(id);
//If you want to fetch contact then send fetchEagerly as true
if(fetchEagerly)
//Here fetching contacts eagerly
Object object = account.getContacts().size();
@Transactional 是一个可以在单个事务中进行多次调用的服务 不关闭与端点的连接。
希望你觉得这很有用。 :)
更多详情refer this link
【讨论】:
【参考方案3】:请找到一个使用 JPA 2.1 运行的示例。
设置你只想加载的属性(带有attributeNodes列表):
带有实体图注释的实体:
@Entity
@NamedEntityGraph(name = "accountGraph", attributeNodes =
@NamedAttributeNode("accountId"))
@Table(name = "accounts")
public class Account
protected String accountId;
protected Collection<Contact> contacts;
@OneToMany(fetch=FetchType.LAZY)
@JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
return contacts;
您的自定义界面:
public interface AccountRepository extends JpaRepository<Account, String>
@EntityGraph("accountGraph")
Account findOne(String id);
只有“accountId”属性会被急切加载。所有其他属性将在访问时延迟加载。
【讨论】:
感谢您花时间回答。在我的示例中,我不希望在联系人上填充任何属性。当我调用“accountRepository.findOne(5)”时,我希望存储库返回没有任何联系人的帐户实体。 不客气。您没有很多机制来控制 JPA 实体中加载或不加载的内容。你可以使用 EAGER 或 LAZY fetching,但你已经知道了。使用 DTO 应该是一种解决方法(例如,CustomAccount 是一个 Account 包装器)。 这里 EntityGraph 是静态绑定的。有没有办法在运行时绑定它,以便我们可以根据需要传递不同的EntityGraph? 这里是多个 NamedAttributeNode 和/或多个 NamedEntityGraphs docs.oracle.com/javaee/7/tutorial/… 的语法链接,如果链接失效,请在互联网上搜索此字符串:“多个 (at)NamedEntityGraph 定义可以通过对它们进行分组来应用于一个类在(at)NamedEntityGraphs 注释中。”将 (at) 替换为 at 符号(现在 SOF cmets 中允许使用 at 符号) @AndréBlaszczyk 嘿。我实现了这种方法,但我仍然得到了完整的对象水合。我发布了一个问题。如果你有机会看看它。谢谢***.com/questions/62162186/…【参考方案4】:Spring 数据不会忽略fetch=FetchType.Lazy
。
我的问题是我使用dozer-mapping
将我的实体转换为图表。显然dozer
调用 getter 和 setter 来映射两个对象,所以我需要添加一个自定义字段映射器配置来忽略 PersistentCollections...
GlobalCustomFieldMapper.java:
public class GlobalCustomFieldMapper implements CustomFieldMapper
public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping)
if (!(sourceFieldValue instanceof PersistentCollection))
// Allow dozer to map as normal
return;
if (((PersistentCollectiosourceFieldValue).wasInitialized())
// Allow dozer to map as normal
return false;
// Set destination to null, and tell dozer that the field is mapped
destination = null;
return true;
【讨论】:
【参考方案5】:如果您尝试将实体的结果集发送给客户端,我建议您使用数据传输对象 (DTO) 而不是实体。您可以直接在 HQL/JPQL 中创建 DTO。 例如
"select new com.test.MyTableDto(my.id, my.name) from MyTable my"
如果你想通过孩子
"select new com.test.MyTableDto(my.id, my.name, my.child) from MyTable my"
这样您就可以完全控制正在创建的内容并将其传递给客户端。
【讨论】:
Caused by: java.sql.SQLSyntaxErrorException: malformed numeric constant: . in statement
传递 my.child。以上是关于Spring Data JPA Repository:如何有条件地获取子实体的主要内容,如果未能解决你的问题,请参考以下文章
spring-data-jpa 和 spring-boot-starter-data-jpa 的区别
spring-data详解之spring-data-jpa:简单三步快速上手spring-data-jpa开发