web-dev-qa-db-fra.com

Comment charger des éléments récupérés paresseux depuis Hibernate / JPA dans mon contrôleur

J'ai une classe de personne:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

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

Avec une relation plusieurs à plusieurs, c'est paresseux.

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 selon ce guide

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

Cependant, dans ce contrôleur , j'ai réellement besoin des données paresseuses. Comment puis-je déclencher son chargement?

Essayer d'y accéder échouera avec

n'a pas réussi à initialiser paresseusement une collection de rôles: no.dusken.momus.model.Person.roles, impossible d'initialiser le proxy - pas de session

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

Mon xml-description , au besoin.

Merci.

128
Matsemann

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

Mise à jour:

Veuillez noter que la solution ci-dessus est simple, mais entraîne deux requêtes distinctes dans 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 à l'interface de votre 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 clause fetch join de JPQL pour charger avec empressement l'association de rôles dans un seul aller-retour vers la base de données, et atténuera donc la pénalité de performance induite par les deux requêtes distinctes de la solution ci-dessus.

186
zagyi

Bien qu'il s'agisse d'un ancien message, veuillez envisager d'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
}

puis le repo de printemps 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);

}
32
rakpan

Vous avez quelques options

  • Ecrivez une méthode sur le référentiel qui renvoie une entité initialisée comme suggéré par R.J.

Plus de travail, meilleure performance.

  • Utilisez OpenEntityManagerInViewFilter pour garder la session ouverte pour toute la demande.

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

  • Utilisez une classe d'assistance pour initialiser des entités si nécessaire.

Moins de travail, utile lorsque OEMIV n’est pas une option, par exemple dans une application Swing, mais peut également s’avérer utile dans les implémentations de référentiel pour initialiser n’importe quelle entité en une seule fois.

Pour la dernière option, j’ai écrit une classe d’utilité, JpaUtils , pour initier des entités à un certain degré.

Par exemple:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

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

il ne peut être chargé que paresseusement pendant une transaction. Vous pouvez donc accéder à la collection de votre référentiel, qui contient une transaction - ou quoi je fais normalement un get with association, ou définir fetchmode sur désireux.

6
NimChimpsky

Je pense que vous avez besoin de OpenSessionInViewFilter pour garder votre session ouverte pendant le rendu de la vue (mais ce n’est pas une trop bonne pratique).

6
Michail Nikolaev

Vous pouvez faire la même chose comme ça:

@Override
public FaqQuestions getFaqQuestionById(Long questionId) {
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    FaqQuestions faqQuestions = null;
    try {
        faqQuestions = (FaqQuestions) session.get(FaqQuestions.class,
                questionId);
        Hibernate.initialize(faqQuestions.getFaqAnswers());

        tx.commit();
        faqQuestions.getFaqAnswers().size();
    } finally {
        session.close();
    }
    return faqQuestions;
}

Utilisez simplement faqQuestions.getFaqAnswers (). Size () dans votre contrôleur et vous obtiendrez la taille si la liste est paresseusement intialisée, sans la récupérer elle-même.

0
neel4soft