it-swarm.com.de

Wie lade ich faul abgerufene Objekte aus Hibernate / JPA in meinen Controller?

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.

128
Matsemann

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.

Aktualisieren:

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.

186
zagyi

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);

}
32
rakpan

Sie haben einige Möglichkeiten

  • Schreiben Sie eine Methode im Repository, die eine initialisierte Entität wie von R.J vorgeschlagen zurückgibt.

Mehr Arbeit, beste Leistung.

  • Verwenden Sie OpenEntityManagerInViewFilter, um die Sitzung für die gesamte Anforderung offen zu halten.

Weniger Arbeit, in der Regel in Webumgebungen akzeptabel.

  • Verwenden Sie bei Bedarf eine Hilfsklasse, um Entitäten zu initialisieren.

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);
    }
}
13

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.

6
NimChimpsky

Ich denke, Sie brauchen OpenSessionInViewFilter , um Ihre Sitzung während des Renderns der Ansicht offen zu halten (aber es ist keine allzu gute Übung).

6

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.

0
neel4soft