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.
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.
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.
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);
}
Vous avez quelques options
Plus de travail, meilleure performance.
Moins de travail, généralement acceptable dans les environnements Web.
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);
}
}
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.
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).
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.