Cómo cargar elementos extraídos perezosos de Hibernate / JPA en mi controlador


Tengo una clase de Persona:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

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

Con una relación de muchos a muchos que es perezosa.

En mi controlador tengo

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

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

Y el PersonRepository es solo este código, escrito de acuerdo con esta guía

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

Sin embargo, en este controlador realmente necesito los datos perezosos. ¿Cómo puedo activar su carga?

Intentar acceder a él fallará con

No pudo inicializar perezosamente una colección de roles: no.anochecer.momus.modelo.Persona.roles, no se pudo inicializar el proxy-no Sesión

U otras excepciones dependiendo de lo que intente.

Mi xml-descripción, en caso necesario.

Gracias.

Author: Matsemann, 2013-03-12

6 answers

Tendrá que hacer una llamada explícita a la colección lazy para inicializarla (la práctica común es llamar a .size() para este propósito). En Hibernate hay un método dedicado para esto (Hibernate.initialize()), pero JPA no tiene equivalente de eso. Por supuesto, tendrá que asegurarse de que la invocación se realiza, cuando la sesión todavía está disponible, así que anote su método de controlador con @Transactional. Una alternativa es crear una capa de servicio intermedia entre el Controlador y el Repositorio que podría exponer métodos que inicializan colecciones perezosas.

Actualización:

Tenga en cuenta que la solución anterior es fácil, pero resulta en dos consultas distintas a la base de datos (una para el usuario, otra para sus roles). Si desea lograr un mejor rendimiento, agregue el siguiente método a su interfaz de repositorio de 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);

}

Este método utilizará la cláusula fetch join de JPQL para cargar ansiosamente la asociación de roles en un solo viaje de ida y vuelta a la base de datos, y, por lo tanto, mitigará la penalización de rendimiento incurrida por las dos consultas distintas en la solución anterior.

 166
Author: zagyi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-02 13:33:10

Aunque este es un post antiguo, por favor considere usar @NamedEntityGraph (Persistencia Javax) y @EntityGraph (Spring Data JPA). La combinación funciona.

Ejemplo

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

Y luego el repositorio de primavera como se muestra a continuación

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

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

}
 25
Author: rakpan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-10 17:54:09

Tienes algunas opciones

  • Escriba un método en el repositorio que devuelva una entidad inicializada como R. J sugirió.

Más trabajo, mejor rendimiento.

  • Utilice OpenEntityManagerInViewFilter para mantener abierta la sesión durante toda la solicitud.

Menos trabajo, generalmente aceptable en entornos web.

  • Use una clase helper para inicializar entidades cuando sea necesario.

Menos trabajo, útil cuando OEMIV no está en opción, por ejemplo, en un columpio aplicación, pero también puede ser útil en implementaciones de repositorios para inicializar cualquier entidad de una sola vez.

Para la última opción, escribí una clase de utilidad, JpaUtils para inicializar entidades en algún deph.

Por ejemplo:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}
 12
Author: Jose Luis Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-12 12:29:21

Solo se puede cargar perezosamente mientras está dentro de una transacción. Por lo que podría acceder a la colección en su repositorio, que tiene una transacción - o lo que normalmente hago es un get with association, o establecer fetchmode a eager.

 6
Author: NimChimpsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:30

Creo que necesita OpenSessionInViewFilter para mantener su sesión abierta durante el renderizado de vistas (pero no es una buena práctica).

 6
Author: Michail Nikolaev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-12 11:51:50

Puedes hacer lo mismo así:

@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;
}

Simplemente use faqQuestions.getFaqAnswers().size () nin su controlador y obtendrá el tamaño si perezosamente lista inicializada, sin buscar la lista en sí.

 0
Author: neel4soft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-06 11:08:14