J'ai un cas d'utilisation où il appelle les éléments suivants:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
Observez le @Transactional
a Propagation.REQUIRES_NEW et le référentiel utilise getOne. Lorsque je lance l'application, je reçois le message d'erreur suivant:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
Mais si je modifie le getOne(id)
par findOne(id)
tout ira bien.
BTW, juste avant que le cas d'utilisation appelle la méthode getUserControlById, il a déjà appelé la méthode insertUserControl
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
Les deux méthodes sont Propagation.REQUIRES_NEW car je fais un simple contrôle audit.
J'utilise la méthode getOne
car elle est définie dans JpaRepository interface et mon interface Repository s'étend à partir de là, je travaille bien sûr avec JPA.
L’interface JpaRepository s’étend de CrudRepository . La méthode findOne(id)
est définie dans CrudRepository
.
Mes questions sont:
getOne(id)
?getOne(id)
?Je travaille avec d'autres référentiels et tous utilisent la méthode getOne(id)
et tout fonctionne correctement, uniquement lorsque j'utilise Propagation.REQUIRES_NEW, il échoue.
Selon l’API getOne:
Retourne une référence à l'entité avec l'identifiant donné.
Selon l’API findOne:
Récupère une entité par son identifiant.
3) Quand devrais-je utiliser la méthode findOne(id)
?
4) Quelle est la méthode recommandée?
Merci d'avance.
1. Pourquoi la méthode getOne (id) échoue-t-elle?
Voir cette section dans la documentation . Le fait de remplacer la transaction déjà en place peut être à l'origine du problème. Cependant, sans plus d'informations, il est difficile de répondre à cette question.
2. Quand devrais-je utiliser la méthode getOne (id)?
Sans fouiller dans les éléments internes de Spring Data JPA, la différence semble être liée au mécanisme utilisé pour extraire l'entité.
Si vous regardez le JavaDoc pour getOne(ID)
sous voir aussi:
See Also:
EntityManager.getReference(Class, Object)
il semble que cette méthode ne fait que déléguer à la mise en œuvre du gestionnaire d'entités JPA.
Cependant, les docs for findOne(ID)
ne le mentionnent pas.
L'indice est également dans les noms des référentiels .JpaRepository
est spécifique à JPA et peut donc déléguer des appels au responsable d'entité si nécessaire .CrudRepository
est agnostique de la technologie de persistance utilisée. Regardez ici . Il est utilisé comme interface de marqueur pour plusieurs technologies de persistance comme JPA, Neo4J etc.
Donc, il n'y a pas vraiment de «différence» entre les deux méthodes pour vos cas d'utilisation, c'est simplement que findOne(ID)
est plus générique que la plus spécialisée getOne(ID)
. Lequel vous utilisez dépend de vous et de votre projet, mais je tiens personnellement à ce que le nom de findOne(ID)
rend le code moins spécifique à l'implémentation et ouvre la porte à des choses comme MongoDB, etc. sans trop de refactoring :)
La différence fondamentale est que getOne
est chargé paresseux et que findOne
ne l’est pas.
Prenons l'exemple suivant:
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!!!
}
T findOne(ID id)
(nom dans l'ancienne API)/Optional<T> findById(ID id)
(nom dans la nouvelle API) s'appuie sur EntityManager.find()
qui effectue un chargement entité rapide.
T getOne(ID id)
s'appuie sur EntityManager.getReference()
qui effectue un chargement parentité. Donc, pour assurer le chargement effectif de l'entité, il est nécessaire d'appeler une méthode dessus.
findOne()/findById()
est vraiment plus clair et simple à utiliser que getOne()
.
Donc, dans la plupart des cas, préférez findOne()/findById()
à getOne()
.
Du moins, la version 2.0
, Spring-Data-Jpa
a modifié findOne()
.
Auparavant, il était défini dans l'interface CrudRepository
comme:
T findOne(ID primaryKey);
Maintenant, la méthode unique findOne()
que vous trouverez dans CrudRepository
est celle qui est définie dans l'interface QueryByExampleExecutor
comme étant:
<S extends T> Optional<S> findOne(Example<S> example);
C'est finalement implémenté par SimpleJpaRepository
, l'implémentation par défaut de l'interface CrudRepository
.
Cette méthode est une requête par exemple et vous ne voulez pas que cela remplace.
En fait, la méthode ayant le même comportement est toujours présente dans la nouvelle API, mais le nom de la méthode a changé.
Il a été renommé de findOne()
à findById()
dans l'interface CrudRepository
:
Optional<T> findById(ID id);
Maintenant, il retourne une Optional
. Ce qui n’est pas si grave pour empêcher NullPointerException
.
Le choix actuel est donc maintenant entre Optional<T> findById(ID id)
et T getOne(ID id)
.
1) Le Optional<T> findById(ID id)
javadoc indique qu'il:
Récupère une entité par son identifiant.
Lorsque nous examinons l'implémentation, nous pouvons voir qu'il repose sur EntityManager.find()
pour effectuer la récupération:
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));
}
Et voici em.find()
est une méthode EntityManager
déclarée comme suit:
public <T> T find(Class<T> entityClass, Object primaryKey,
Map<String, Object> properties);
Son javadoc déclare:
Rechercher par clé primaire, en utilisant les propriétés spécifiées
La récupération d'une entité chargée semble donc attendue.
2) Alors que le T getOne(ID id)
javadoc déclare (l’emphase est à moi):
Renvoie un reference à l'entité avec l'identifiant donné.
En fait, la terminologie référence est vraiment une carte et l'API JPA ne spécifie aucune méthode getOne()
.
Donc, la meilleure chose à faire pour comprendre ce que le wrapper Spring fait est de regarder dans la mise en œuvre:
@Override
public T getOne(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
Ici em.getReference()
est une méthode EntityManager
déclarée comme:
public <T> T getReference(Class<T> entityClass,
Object primaryKey);
Et heureusement, le EntityManager
javadoc a mieux défini son intention (c'est moi qui souligne):
Obtient une instance dont l'état peut être récupéré paresseusement. Si le demandé instance n'existe pas dans la base de données, l'entité EntityNotFoundException est levé lorsque l'état de l'instance est accédé pour la première fois. (Le moteur d'exécution de persistance Fournisseur est autorisé à émettre une exception EntityNotFoundException Lorsque getReference est appelée.) L'application ne doit pas s'attendre à ce que l'état d'instance sera disponible lors du détachement, à moins qu'il ne s'agisse de accessible par l’application alors que le gestionnaire d’entités était ouvert.
Ainsi, l'invocation de getOne()
peut renvoyer une entité extraite paresseusement.
Ici, la récupération paresseuse ne fait pas référence aux relations de l'entité mais à l'entité elle-même.
Cela signifie que si nous appelons getOne()
et que le contexte de persistance est fermé, l'entité peut ne jamais être chargée et le résultat est vraiment imprévisible.
Par exemple, si l'objet proxy est sérialisé, vous pouvez obtenir une référence null
comme résultat sérialisé ou si une méthode est invoquée sur l'objet proxy, une exception telle que LazyInitializationException
est levée.
Donc, dans ce genre de situation, le jet de EntityNotFoundException
est la principale raison d'utiliser getOne()
pour traiter une instance qui n'existe pas dans la base de données car une situation d'erreur peut ne jamais être exécutée tant que l'entité n'existe pas.
Dans tous les cas, pour assurer son chargement, vous devez manipuler l'entité pendant que la session est ouverte. Vous pouvez le faire en appelant n'importe quelle méthode sur l'entité.
Ou une meilleure alternative, utilisez findById(ID id)
au lieu de.
Pour finir, deux questions pour les développeurs Spring-Data-JPA:
pourquoi ne pas avoir une documentation plus claire pour getOne()
? Le chargement paresseux des entités n'est vraiment pas un détail.
pourquoi avez-vous besoin d'introduire getOne()
pour envelopper EM.getReference()
?
Pourquoi ne pas simplement s'en tenir à la méthode encapsulée: getReference()
? Cette méthode EM est vraiment très particulière alors que getOne()
transmet un traitement si simple.
Les méthodes getOne
ne renvoient que la référence de la base de données (chargement différé) . Donc, en principe, vous êtes en dehors de la transaction (la Transactional
que vous avez déclarée dans la classe de service n'est pas prise en compte) et l'erreur se produit.
Je trouve vraiment très difficile de ces réponses. Du point de vue du débogage, j’ai presque passé 8 heures à connaître l’erreur stupide.
J'ai testé Spring + hibernate + bulldozer + projet Mysql. Pour être clair.
J'ai une entité utilisateur, une entité de livre. Vous faites les calculs de cartographie.
Les livres multiples étaient-ils liés à un utilisateur? Mais dans UserServiceImpl, j'essayais de le trouver par getOne (userId);
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;
}
Le résultat reste est
{
"collection": {
"version": "1.0",
"data": {
"id": 1,
"name": "TEST_ME",
"bookList": null
},
"error": null,
"statusCode": 200
},
"booleanStatus": null
}
Le code ci-dessus n'a pas récupéré les livres lus par l'utilisateur.
La liste de lecture a toujours été nulle à cause de getOne (ID). Après avoir changé pour findOne (ID). Le résultat est
{
"collection": {
"version": "1.0",
"data": {
"id": 0,
"name": "Annama",
"bookList": [
{
"id": 2,
"book_no": "The karma of searching",
}
]
},
"error": null,
"statusCode": 200
},
"booleanStatus": null
}