当使用getOne和findOne方法时,Spring Data JPA
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当使用getOne和findOne方法时,Spring Data JPA相关的知识,希望对你有一定的参考价值。
我有一个用例,它调用以下内容:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
观察@Transactional
有Propagation.REQUIRES_NEW,存储库使用getOne。当我运行该应用程序时,收到以下错误消息:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
但如果我用getOne(id)
改变findOne(id)
一切正常。
顺便说一下,就在用例调用getUserControlById方法之前,它已经调用了insertUserControl方法
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
两种方法都是Propagation.REQUIRES_NEW,因为我正在进行简单的审计控制。
我使用getOne
方法,因为它在JpaRepository接口中定义,我的Repository接口从那里扩展,我当然正在使用JPA。
JpaRepository接口从CrudRepository扩展。 findOne(id)
方法在CrudRepository
中定义。
我的问题是:
- 为什么
getOne(id)
方法失败了? - 什么时候应该使用
getOne(id)
方法?
我正在使用其他存储库,所有使用getOne(id)
方法,所有工作正常,只有当我使用Propagation.REQUIRES_NEW它失败。
根据getOne API:
返回对具有给定标识符的实体的引用。
根据findOne API:
按ID查找实体。
3)什么时候应该使用findOne(id)
方法?
4)建议使用哪种方法?
提前致谢。
1.为什么getOne(id)方法失败?
请参阅本节in the docs。覆盖已就位的事务可能会导致问题。但是,如果没有更多信息,这个很难回答。
2.什么时候应该使用getOne(id)方法?
如果不深入研究Spring Data JPA的内部结构,差异似乎在于用于检索实体的机制。
如果你看看JavaDoc for getOne(ID)
,请参阅:
See Also:
EntityManager.getReference(Class, Object)
似乎这个方法只是委托给JPA实体管理器的实现。
然而,docs的findOne(ID)
没有提到这一点。
线索也在存储库的名称中。 JpaRepository
是特定于JPA的,因此如果需要,可以将调用委托给实体管理器。 CrudRepository
不了解所使用的持久性技术。 Look here。它被用作多种持久性技术的标记接口,如JPA,Neo4J等。
因此,对于您的用例,这两种方法并没有真正的“差异”,只是findOne(ID)
比更专业的getOne(ID)
更通用。您使用哪一个取决于您和您的项目,但我个人会坚持使用findOne(ID)
,因为它会降低您的代码实现特定性,并打开大门,以便将来移动到像MongoDB等等,而不需要太多的重构:)
基本的区别是getOne
是懒惰加载而findOne
不是。
请考虑以下示例:
public static String NON_EXISTING_ID = -1;
...
MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID);
MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID);
if(findEnt != null) {
findEnt.getText(); // findEnt is null - this code is not executed
}
if(getEnt != null) {
getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!!
}
TL; DR
T findOne(ID id)
(旧API中的名称)/ Optional<T> findById(ID id)
(新API中的名称)依赖于执行实体急切加载的EntityManager.find()
。
T getOne(ID id)
依赖于执行实体延迟加载的EntityManager.getReference()
。因此,为了确保实体的有效加载,需要在其上调用方法。
findOne()/findById()
比getOne()
更清晰,更易于使用。
所以在大多数情况下,赞成findOne()/findById()
而不是getOne()
。
API变更
从至少,2.0
版本,Spring-Data-Jpa
修改findOne()
。
以前,它在CrudRepository
界面中定义为:
T findOne(ID primaryKey);
现在,您将在findOne()
中找到的单个CrudRepository
方法是在QueryByExampleExecutor
界面中定义的方法:
<S extends T> Optional<S> findOne(Example<S> example);
这最终由SimpleJpaRepository
实现,CrudRepository
是findOne()
接口的默认实现。
此方法是通过示例搜索查询,您不希望将其作为替换。
实际上,具有相同行为的方法仍然存在于新API中,但方法名称已更改。
它在findById()
界面中从CrudRepository
重命名为Optional<T> findById(ID id);
:
Optional
现在它返回一个NullPointerException
。这对于防止Optional<T> findById(ID id)
来说并不是那么糟糕。
所以,现在实际的选择是在T getOne(ID id)
和Optional<T> findById(ID id)
javadoc之间。
两种不同的方法依赖于两种不同的JPA EntityManager检索方法
1)EntityManager.find()
声明它:
按ID查找实体。
在我们研究实现时,我们可以看到它依赖于public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}
来进行检索:
em.find()
在这里EntityManager
是一个public <T> T find(Class<T> entityClass, Object primaryKey,
Map<String, Object> properties);
方法声明为:
T getOne(ID id)
javadoc
它的javadoc说:
使用指定的属性按主键查找
因此,预计检索加载的实体。
2)虽然getOne()
国家(重点是我的):
返回对具有给定标识符的实体的引用。
实际上,参考术语实际上是板,而JPA API没有指定任何@Override
public T getOne(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
方法。
因此,了解Spring包装器的作用最好的方法是查看实现:
em.getReference()
这里EntityManager
是一个public <T> T getReference(Class<T> entityClass,
Object primaryKey);
方法,声明为:
EntityManager
幸运的是,getOne()
javadoc更好地定义了它的意图(重点是我的):
获取一个实例,其状态可能会被懒惰地取出。如果数据库中不存在请求的实例,则在首次访问实例状态时将引发EntityNotFoundException。 (在调用getReference时,允许持久性提供程序运行时抛出EntityNotFoundException。)除非在实体管理器打开时应用程序访问实例状态,否则应用程序不应期望实例状态在分离时可用。
因此,调用getOne()
可能会返回一个延迟获取的实体。
这里,延迟提取不是指实体的关系,而是实体本身。
这意味着如果我们调用null
然后关闭Persistence上下文,则实体可能永远不会被加载,因此结果实际上是不可预测的。
例如,如果代理对象是序列化的,则可以将LazyInitializationException
引用作为序列化结果获取,或者如果在代理对象上调用方法,则会引发诸如EntityNotFoundException
之类的异常。
所以在这种情况下,getOne()
的抛出是使用findById(ID id)
处理数据库中不存在的实例作为错误情况的主要原因,可能永远不会在实体不存在时执行。
在任何情况下,为了确保其加载,您必须在会话打开时操纵实体。您可以通过调用实体上的任何方法来完成此操作。
或者更好的选择使用getOne()
而不是。
为什么API如此不清楚?
为了完成,Spring-Data-JPA开发人员有两个问题:
- 为什么不为
getOne()
提供更清晰的文档?实体延迟加载实际上不是一个细节。 - 为什么你需要引入
EM.getReference()
来包装getReference()
? 为什么不简单地坚持包裹的方法:getOne()
?这种EM方法非常特别,而getOne
传达了如此简单的处理。
Transactional
方法仅返回DB(延迟加载)的引用。所以基本上你是在事务之外(不考虑你在服务类中声明的public UserDTO getById(int userId) throws Exception {
final User user = userDao.getOne(userId);
if (user == null) {
throw new ServiceException("User not found", HttpStatus.NOT_FOUND);
}
userDto = mapEntityToDto.transformBO(user, UserDTO.class);
return userDto;
}
),并且发生错误。
从上面的答案中我真的很难找到。从调试的角度来看,我几乎花了8个小时才知道愚蠢的错误。
我有测试spring + hibernate + dozer + mysql项目。要清楚。
我有用户实体,书籍实体。您进行映射计算。
是多个书籍绑定到一个用户。但在UserServiceImpl中,我试图通过getOne(userId)找到它;
{
"collection": {
"version": "1.0",
"data": {
"id": 1,
"name": "TEST_ME",
"bookList": null
},
"error": null,
"statusCode": 200
},
"booleanStatus": null
其余的结果是
以上是关于当使用getOne和findOne方法时,Spring Data JPA的主要内容,如果未能解决你的问题,请参考以下文章
CrudRepository findOne() 和 JpaRepository getOne() 的区别
spring data jpa使用repository进行查询,使用userRepository.getOne(id)和userRepository.findById(id)无法从数据库查询到数据