Ich habe eine Personenklasse:
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
@ManyToMany(fetch = FetchType.LAZY)
private List<Role> roles;
// etc
}
Bei einem Verhältnis von vielen zu vielen ist das faul.
In meinem Controller habe ich
@Controller
@RequestMapping("/person")
public class PersonController {
@Autowired
PersonRepository personRepository;
@RequestMapping("/get")
public @ResponseBody Person getPerson() {
Person person = personRepository.findOne(1L);
return person;
}
}
Und das PersonRepository ist nur dieser Code, der gemäß dieser Anleitung geschrieben wurde
public interface PersonRepository extends JpaRepository<Person, Long> {
}
In diesem Controller brauche ich allerdings eigentlich die Lazy-Daten. Wie kann ich das Laden auslösen?
Der Versuch, darauf zuzugreifen, schlägt mit fehl
konnte eine Sammlung von Rollen nicht träge initialisieren: no.dusken.momus.model.Person.roles, Proxy konnte nicht initialisiert werden - keine Sitzung
oder andere ausnahmen je nachdem was ich versuche.
Mein xml-description , falls erforderlich.
Vielen Dank.
Sie müssen die Lazy Collection explizit aufrufen, um sie zu initialisieren (es ist üblich, .size()
zu diesem Zweck aufzurufen). In Hibernate gibt es dafür eine spezielle Methode (Hibernate.initialize()
), aber JPA hat keine Entsprechung dazu. Natürlich müssen Sie sicherstellen, dass der Aufruf erfolgt, wenn die Sitzung noch verfügbar ist. Kommentieren Sie also Ihre Controller-Methode mit @Transactional
. Eine Alternative besteht darin, eine Service-Zwischenschicht zwischen dem Controller und dem Repository zu erstellen, die Methoden verfügbar machen kann, mit denen verzögerte Sammlungen initialisiert werden.
Bitte beachten Sie, dass die obige Lösung einfach ist, aber zu zwei unterschiedlichen Abfragen an die Datenbank führt (eine für den Benutzer, eine andere für ihre Rollen). Wenn Sie eine bessere Leistung erzielen möchten, fügen Sie der JPA-Repository-Schnittstelle von Spring Data die folgende Methode hinzu:
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);
}
Diese Methode verwendet die Klausel fetch join von JPQL, um die Rollenzuordnung in einem einzelnen Roundtrip in die Datenbank zu laden, und mildert daher die Leistungseinbußen, die durch die beiden unterschiedlichen Abfragen in der obigen Lösung verursacht werden.
Obwohl dies ein alter Beitrag ist, sollten Sie die Verwendung von @NamedEntityGraph (Javax Persistence) und @EntityGraph (Spring Data JPA) in Betracht ziehen. Die Kombination funktioniert.
Beispiel
@Entity
@Table(name = "Employee", schema = "dbo", catalog = "ARCHO")
@NamedEntityGraph(name = "employeeAuthorities",
attributeNodes = @NamedAttributeNode("employeeGroups"))
public class EmployeeEntity implements Serializable, UserDetails {
// your props
}
und dann die Feder repo wie unten
@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String> {
@EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
EmployeeEntity getByUsername(String userName);
}
Sie haben einige Möglichkeiten
Mehr Arbeit, beste Leistung.
Weniger Arbeit, in der Regel in Webumgebungen akzeptabel.
Weniger Arbeit, nützlich, wenn OEMIV nicht zur Verfügung steht, z. B. in einer Swing-Anwendung, aber möglicherweise auch bei Repository-Implementierungen, um eine Entität auf einmal zu initialisieren.
Für die letzte Option habe ich eine Utility-Klasse geschrieben, JpaUtils , um Entitäten bei einer bestimmten Tiefe zu initialisieren.
Zum Beispiel:
@Transactional
public class RepositoryHelper {
@PersistenceContext
private EntityManager em;
public void intialize(Object entity, int depth) {
JpaUtils.initialize(em, entity, depth);
}
}
es kann nur während einer Transaktion faul geladen werden. Sie können also auf die Sammlung in Ihrem Repository zugreifen, die eine Transaktion enthält - oder was normalerweise ist dies ein get with association
oder setze fetchmode auf eager.
Ich denke, Sie brauchen OpenSessionInViewFilter , um Ihre Sitzung während des Renderns der Ansicht offen zu halten (aber es ist keine allzu gute Übung).
Das können Sie auch so machen:
@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;
}
Verwenden Sie einfach faqQuestions.getFaqAnswers (). Size () in Ihrem Controller und Sie erhalten die Größe, wenn Sie die Liste träge initialisieren, ohne die Liste selbst abzurufen.