I'm starting with Spring and I've come across this common mistake. After reading a lot and trying many of the suggested solutions (for loops for initializing, using transactional etc) in StackOverflow and other sites I have been unable to fix it.
The objects involved are an Actor object that has an @manytomany relationship with a Peli object (intermediate table id_actor / id_peli). In addition there is a relation @onetomany between Peli and Genre (id_genero in the table pelis).
The screen in question is a form where it is about loading a list with all the actors.
Here is the relevant code:
Actor.java
package com.companyname.cine.domain;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name = "actores")
public class Actor implements Serializable {
private Integer id;
private String nombre;
private String biografia;
private List<Peli> pelis;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
//otros setters y getters
@ManyToMany(mappedBy = "actores")
public List<Peli> getPelis() {
return pelis;
}
}
Peli.java
package com.companyname.cine.domain;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name = "pelis")
public class Peli implements Serializable {
private Integer id;
private String titulo;
private String argumento;
private Integer id_genero;
private List<Actor> actores;
public Peli(String titulo, String argumento, Integer id_genero) {
//constructor
}
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
//otros setters y getters, hashcode, equals, toString etc
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "actores_pelis", joinColumns = @JoinColumn(name = "id_actor", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "id_peli", referencedColumnName = "id"))
public List<Actor> getActores() {
return actores;
}
}
ActorDaoImpl.java
package com.companyname.cine.repository;
import java.util.List;
import javax.persistence.*;
import org.hibernate.Hibernate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.companyname.cine.domain.*;
@Repository
public class ActorDaoImpl implements ActorDao {
private EntityManager em = null;
public EntityManager getEntityManager() {
return em;
}
@PersistenceContext
public void setEntityManager(EntityManager em) {
this.em = em;
}
@SuppressWarnings("unchecked")
@Transactional
public List<Actor> findByPeli(int id_peli) {
// TODO Auto-generated method stub
List<Actor> actores = em.createQuery("select distinct a from Peli p join p.actores a where p.id=:id")
.setParameter("id", id_peli).getResultList();
//intento de inicializar la coleccion
for (Actor actor : actores) {
for (Peli peli : actor.getPelis()) {
peli.getId();
}
}
return actores;
}
@SuppressWarnings("unchecked")
@Transactional //<- intento de mantener la sesion abierta para no perder el acceso al objeto
public List<Actor> findAll() {
List<Actor> actores = em.createQuery("select a from Actor a order by a.id").getResultList();
//intento de inicializar la coleccion
for (Actor actor : actores) {
for (Peli peli : actor.getPelis()) {
peli.getId();
}
}
return actores;
}
//otros métodos (save, update, delete, findById, etc etc
}
}
PeliDaoImpl.java
package com.companyname.cine.repository;
import java.util.List;
import javax.persistence.*;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.companyname.cine.domain.*;
@Repository
public class PeliDaoImpl implements PeliDao {
private EntityManager em;
public EntityManager getEntityManager() {
return em;
}
@PersistenceContext
public void setEntityManager(EntityManager em) {
this.em = em;
}
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public List<Peli> findAll() {
List<Peli> pelis = em.createQuery("select p from Peli p order by p.id").getResultList();
for (Peli peli : pelis) {
for (Actor actor : peli.getActores()) {
actor.getId();
}
}
return pelis;
}
@Transactional(readOnly = true)
public Peli findById(int id) {
return em.find(Peli.class, id);
}
//métodos save, update, delete
}
ActorServiceImpl.java
package com.companyname.cine.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.companyname.cine.domain.Actor;
import com.companyname.cine.repository.ActorDao;
@Service("actorService")
public class ActorServiceImpl implements ActorService {
ActorDao actorDao;
@Autowired
public void setActorDao(ActorDao actorDao) {
this.actorDao = actorDao;
}
public Actor findById(int id) {
return actorDao.findById(id);
}
public List<Actor> findByPeli(int id){
List<Actor> actores = actorDao.findByPeli(id);
return actores;
}
public List<Actor> findAll() {
List<Actor> resultado = actorDao.findAll();
return resultado;
}
//metodos save, update, delete
}
PeliServiceImpl.java
package com.companyname.cine.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.companyname.cine.domain.Peli;
import com.companyname.cine.repository.PeliDao;
@Service("peliService")
public class PeliServiceImpl implements PeliService {
PeliDao peliDao;
@Autowired
public void setPeliDao(PeliDao peliDao) {
this.peliDao = peliDao;
}
public Peli findById(int id) {
return peliDao.findById(id);
}
public List<Peli> findAll() {
return peliDao.findAll();
}
//metodos save, update, delete
}
PeliController.java
package com.companyname.cine.web;
import java.util.List;
import org.springframework.*;
import com.companyname.cine.domain.*;
import com.companyname.cine.service.*;
@Controller("/peli")
public class PeliController {
@Autowired
private PeliService peliService;
@Autowired
private GeneroService generoService;
@Autowired
private ActorService actorService;
// Este es el método donde se produce el problema
@RequestMapping(value = "/peli/{id}/update", method = RequestMethod.GET)
@Transactional
public String showUpdatePeliForm(@PathVariable("id") int id, Model model) {
Peli peli = peliService.findById(id);
model.addAttribute("peliform", peli);
model.addAttribute("listaGeneros", generoService.findAll());
//++++++++++++++++++++++++++
//lo pongo así para facilitar el debug, pero el problema es al renderizar listaActores en la template
List <Actor> listaActores = actorService.findAll();
model.addAttribute("listaActores",listaActores);
//++++++++++++++++++++++++++
List <Actor> listaActoresEnPeli = actorService.findByPeli(id);
model.addAttribute("listaActoresEnPeli", listaActoresEnPeli);
return "pelis/peliform";
}
//metodos de save, delete y update, mostrar formulario de alta, etc
}
Template pelis / peliform summarized
<%@ include file="/WEB-INF/views/include.jsp"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<title><fmt:message key="title" /></title>
<style>
.error {
color: red;
}
</style>
</head>
<body>
<h1>
<fmt:message key="editpeli.heading" />
</h1>
<form:form method="post" modelAttribute="peliform"
action="/cine/pelis.htm">
<table>
<tr>
<td>Seleccionar más actores: <form:select multiple="true" path="actores">
<form:options items="${listaActores}" itemLabel="nombre"
itemValue="id"></form:options>
</form:select></td>
</tr>
</table>
<br>
<form:hidden path="id" />
<input type="submit" value="Execute">
</form:form>
<a href="<c:url value="../../pelis.htm"/>">Home</a>
</body>
</html>
Application-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- holding properties for database connectivity / -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- enabling annotation driven configuration / -->
<context:annotation-config />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource" p:jpaVendorAdapter-ref="jpaAdapter">
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
<property name="persistenceUnitName" value="cinePU"></property>
</bean>
<bean id="jpaAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:database="${jpa.database}" p:showSql="${jpa.showSql}" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory" />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Scans the classpath of this application for @Components to deploy as
beans -->
<context:component-scan base-package="com.companyname.cine.repository,com.companyname.cine.service, com.companyname.cine.validator" />
</beans>
Let's see if someone can guide me a bit to tell me what I'm wrong about or what I'm forgetting.
Thanks in advance!