java - mediante - spring mvc 4 tutorial español
Cómo cargar elementos perezosos de Hibernate/JPA en mi controlador (6)
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 flojo.
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 vagos. ¿Cómo puedo activar su carga?
Tratar de acceder fallará con
Error al inicializar lentamente una colección de roles: no.dusken.momus.model.Person.roles, no se pudo inicializar el proxy - sin Session
u otras excepciones según lo que intento.
Mi xml-description , en caso de ser necesario.
Gracias.
Aunque esta es una publicación anterior, considere usar @NamedEntityGraph (Javax Persistence) 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 abajo
@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String> {
@EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
EmployeeEntity getByUsername(String userName);
}
Creo que necesitas OpenSessionInViewFilter para mantener tu sesión abierta durante la visualización (pero no es una buena práctica).
Deberá hacer una llamada explícita a la colección perezosa para inicializarla (la práctica común es llamar a .size()
para este fin). En Hibernate hay un método dedicado para esto ( Hibernate.initialize()
), pero JPA no tiene equivalente. Por supuesto, deberá asegurarse de que la invocación esté completa, cuando la sesión aún esté disponible, de modo que anote el método de su controlador con @Transactional
. Una alternativa es crear una capa de Servicio intermedio entre el Controlador y el Repositorio que podría exponer los métodos que inicializan las colecciones perezosas.
Actualizar:
Tenga en cuenta que la solución anterior es fácil, pero da como resultado 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.
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 () para su controlador y obtendrá el tamaño si la lista se actualiza de forma lenta, sin recuperar la lista.
Tienes algunas opciones
- Escriba un método en el repositorio que devuelva una entidad inicializada como sugirió RJ.
Más trabajo, mejor rendimiento.
- Use OpenEntityManagerInViewFilter para mantener la sesión abierta para toda la solicitud.
Menos trabajo, generalmente aceptable en entornos web.
- Use una clase auxiliar para inicializar entidades cuando sea necesario.
Menos trabajo, útil cuando OEMIV no es una opción, por ejemplo en una aplicación Swing, pero también puede ser útil en implementaciones de repositorio para inicializar cualquier entidad en una sola toma.
Para la última opción, escribí una clase de utilidad, JpaUtils para iniciar 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);
}
}
solo se puede cargar de forma perezosa dentro de una transacción. Para que pueda acceder a la colección en su repositorio, que tiene una transacción, o lo que normalmente hago es get with association
una get with association
, o configurar fetchmode a impaciente.