178 votes

Comment récupérer les associations FetchType.LAZY avec JPA et Hibernate dans un Spring Controller ?

J'ai une classe Personne :

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Role> roles;
    // etc
}

Avec une relation many-to-many qui est paresseuse.

Dans mon contrôleur, j'ai

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    PersonRepository personRepository;

    @RequestMapping("/get")
    public @ResponseBody Person getPerson() {
        Person person = personRepository.findOne(1L);
        return person;
    }
}

Et le PersonRepository est juste ce code, écrit en fonction de ce guide

public interface PersonRepository extends JpaRepository<Person, Long> {
}

Cependant, dans ce contrôleur J'ai en fait besoin du lazy-data. Comment puis-je déclencher son chargement ?

Essayer d'y accéder échouera avec

a échoué dans l'initialisation paresseuse d'une collection de rôles : no.dusken.momus.model.Person.roles, n'a pas pu initialiser le proxy - no Session

ou d'autres exceptions en fonction de ce que j'essaie.

Mon xml-description en cas de besoin.

Gracias.

249voto

zagyi Points 6883

Vous devrez faire un appel explicite sur la collection paresseuse afin de l'initialiser (la pratique courante est d'appeler .size() à cette fin). Dans Hibernate, il existe une méthode dédiée à cet effet ( Hibernate.initialize() ), mais JPA n'a pas d'équivalent de cela. Bien sûr, vous devrez vous assurer que l'invocation est faite lorsque la session est encore disponible, donc annotez votre méthode de contrôleur avec @Transactional . Une alternative est de créer une couche de service intermédiaire entre le contrôleur et le référentiel qui pourrait exposer des méthodes qui initialisent les collections paresseuses.

Mise à jour :

Veuillez noter que la solution ci-dessus est facile, mais entraîne deux requêtes distinctes à la base de données (une pour l'utilisateur, une autre pour ses rôles). Si vous souhaitez obtenir de meilleures performances, ajoutez la méthode suivante à votre interface de référentiel Spring Data JPA :

public interface PersonRepository extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p JOIN FETCH p.roles WHERE p.id = (:id)")
    public Person findByIdAndFetchRolesEagerly(@Param("id") Long id);

}

Cette méthode utilisera la méthode JPQL récupérer rejoindre pour charger rapidement l'association de rôles en un seul aller-retour vers la base de données, et atténuera donc la pénalité de performance encourue par les deux requêtes distinctes dans la solution ci-dessus.

42voto

rakpan Points 2453

Bien qu'il s'agisse d'un vieux post, pensez à utiliser @NamedEntityGraph (Javax Persistence) et @EntityGraph (Spring Data JPA). La combinaison fonctionne.

Exemple

@Entity
@Table(name = "Employee", schema = "dbo", catalog = "ARCHO")
@NamedEntityGraph(name = "employeeAuthorities",
            attributeNodes = @NamedAttributeNode("employeeGroups"))
public class EmployeeEntity implements Serializable, UserDetails {
// your props
}

et ensuite le repo de Spring comme ci-dessous

@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String>           {

    @EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
    EmployeeEntity getByUsername(String userName);

}

15voto

Jose Luis Martin Points 4340

Vous avez quelques options

  • Ecrire une méthode sur le référentiel qui retourne une entité initialisée comme R.J l'a suggéré.

Plus de travail, meilleures performances.

  • Utilisez OpenEntityManagerInViewFilter pour garder la session ouverte pendant toute la requête.

Moins de travail, généralement acceptable dans les environnements web.

  • Utilisez une classe d'aide pour initialiser les entités lorsque cela est nécessaire.

Moins de travail, utile lorsque l'OEMIV n'est pas une option, par exemple dans une application Swing, mais peut être utile aussi sur les implémentations de référentiel pour initialiser toute entité en une seule fois.

Pour la dernière option, j'ai écrit une classe utilitaire, JpaUtils pour initialiser les entités à un certain stade.

Par exemple :

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}

15voto

Vlad Mihalcea Points 3628

Données de printemps JpaRepository

Les données du printemps JpaRepository définit les deux méthodes suivantes :

Cependant, dans votre cas, vous n'avez pas appelé non plus getOne o findById :

Person person = personRepository.findOne(1L);

Donc, je suppose que le findOne est une méthode que vous avez définie dans le fichier PersonRepository . Cependant, le findOne n'est pas très utile dans votre cas. Puisque vous devez récupérer le Person avec est roles il est préférable d'utiliser une collection findOneWithRoles à la place.

Méthodes personnalisées de Spring Data

Vous pouvez définir un PersonRepositoryCustom comme suit :

public interface PersonRepository
    extends JpaRepository<Person, Long>, PersonRepositoryCustom { 

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}

Et définissez sa mise en œuvre comme suit :

public class PersonRepositoryImpl implements PersonRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Person findOneWithRoles(Long id)() {
        return entityManager.createQuery("""
            select p 
            from Person p
            left join fetch p.roles
            where p.id = :id 
            """, Person.class)
        .setParameter("id", id)
        .getSingleResult();
    }
}

C'est ça !

9voto

NimChimpsky Points 20263

Il ne peut être chargé paresseusement qu'au sein d'une transaction. Ainsi, vous pouvez accéder à la collection dans votre référentiel, qui possède une transaction - ou ce qui suit Je fais normalement un get with association ou définissez le mode de récupération comme rapide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X