vendredi 27 février 2015

Why does Spring close the DB-session in Runnable after first Repository method call?

I'm working on a Spring MVC App with JPA (Hibernate as Provider) and a PostgreSQL database. I wanted to insert results (of a time consuming task) into the database in background and created a serviceclass which implements Runnable. In the run method, I receive an entity from the repository but when I try to access a lazy collection of the entity, the database session is already closed (I get a lazy initialization exception).


The source code of my Background-Service:



@Service
@Scope("prototype")
public class ProjectServiceTestThread implements Runnable{

static Logger log = Logger.getLogger(ProjectServiceTestThread.class);

@Autowired
ProjectRepository projectRepository;

@Autowired
ScenarioRepository scenarioRepository;

@Override
@Transactional
public void run() {

List<Project> projectList = projectRepository.findByName("thread test project");
Project project;
project = projectList.get(0);

//A project can have multiple scenarios
Scenario scenario;
if (project.getScenarios().isEmpty()) { //this line fails -> lazyInitializationException - no Session
System.err.println("Creating new scenario");
scenario = new Scenario();
scenario.setName("thread test scenario");
scenario.setDescription(this + ".runServiceFunction at " + System.currentTimeMillis());
scenario.setProject(project);
scenario = scenarioRepository.save(scenario);
} else {
System.err.println("Using existing scenario");
scenario = project.getScenarios().iterator().next();
}
}
}


The Service and Spring TaskExecutor are Autowired in the Controller which is running on a Tomcat v8.0 Server. Controller code:



@Autowired
ProjectServiceTestThreadImpl testRunnable;

@Autowired
TaskExecutor taskExecutor;

@RequestMapping(value="openproject", method=RequestMethod.GET)
public String getStringProjects(Map<String, Object> model) throws InterruptedException
{
System.err.println(this + " before call to runnable ");
testRunnable.run();
taskExecutor.execute(testRunnable);

return "openproject";
}


The log shows that the database session closes right after the findByName Query:



09:50:31,438 TRACE JdbcCoordinatorImpl:525 - Closing prepared statement [select distinct project0_.prjid as prjid1_24_, project0_.createdby as createdb2_24_, project0_.createdon as createdo3_24_, project0_.description as descript4_24_, project0_.designtarget as designta5_24_, project0_.location as location6_24_, project0_.name as name7_24_, project0_.modelid as modelid11_24_, project0_.timehorizon as timehori8_24_, project0_.updatedby as updatedb9_24_, project0_.updatedon as updated10_24_ from public.project project0_ where lower(project0_.name) like ('%'||lower('thread test project')||'%')]
09:50:31,438 TRACE JdbcCoordinatorImpl:278 - Starting after statement execution processing [ON_CLOSE]
09:50:31,438 TRACE StatefulPersistenceContext:880 - Initializing non-lazy collections
09:50:31,439 TRACE SessionImpl:357 - Closing session
09:50:31,439 TRACE JdbcCoordinatorImpl:199 - Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl@3b95a16b]
09:50:31,439 TRACE LogicalConnectionImpl:178 - Closing logical connection
09:50:31,439 DEBUG LogicalConnectionImpl:246 - Releasing JDBC connection
09:50:31,439 DEBUG LogicalConnectionImpl:264 - Released JDBC connection
09:50:31,442 TRACE LogicalConnectionImpl:190 - Logical connection closed
09:50:31,445 TRACE TransactionSynchronizationManager:243 - Removed value [org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$DefaultCrudMethodMetadata@7cc8f09] for key [public abstract java.util.List eu.cite.repository.ProjectRepository.findByName(java.lang.String)] from thread [myExecutor-1]
09:50:31,451 TRACE LazyInitializationException:53 - failed to lazily initialize a collection of role: eu.cite.model.Project.scenarios, could not initialize proxy - no Session
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: eu.cite.model.Project.scenarios, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:575)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:155)
at org.hibernate.collection.internal.PersistentSet.isEmpty(PersistentSet.java:166)
at eu.cite.service.ProjectServiceTestThread.run(ProjectServiceTestThread.java:73)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)


Configuration:



<context:component-scan base-package="eu.cite.repository, eu.cite.service" scoped-proxy="targetClass" />
<jpa:repositories base-package="eu.cite.repository" />

<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="punit"/>
<property name="dataSource" ref="dataSource"></property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"></property>
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<entry key="hibernate.format_sql" value="true"/>
<entry key="hibernate.jdbc.batch_size" value="50"/>
<entry key="hibernate.order_inserts" value="true"/>
</map>
</property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

<bean id="ModelMapper" class="org.modelmapper.ModelMapper"></bean>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.postgresql.Driver"></property>
<property name="url" value="jdbc:postgresql://localhost:5432/Cite?autoReconnect=true"></property>
<property name="username" value="cite"></property>
<property name="password" value="***"></property>
</bean>

<task:executor id="myExecutor"/>
<task:executor id="myExecutor" pool-size="5"/>


Can somebody tell me why it does not work this way? I figured out some ways to make it work, but I don't understand why it does not work with the code above. These three approaches correctly insert the Scenario, without closing the database Session in between:



  1. remove @Service from ProjectServiceTestThread and register the bean in the config manually

  2. Not implement Runnable and annotate the run() method of ProjectServiceTestThread with @Async

  3. Not using the Spring Task Executor


Edit - Project entity:



@Entity
@Table(name = "project", schema = "public", uniqueConstraints = @UniqueConstraint(columnNames = "name"))
public class Project implements java.io.Serializable {

private int prjid;
private SimulationModel simulationmodel;
private String name;
private String description;
private String designtarget;
private Date timehorizon;
private String location;
private Date createdon;
private Date updatedon;
private Integer createdby;
private Integer updatedby;
private Set<ObjectiveFunction> objectivefunctions = new HashSet<ObjectiveFunction>(
0);
private Set<Scenario> scenarios = new HashSet<Scenario>(0);
private Set<ScenarioGenerator> scenariogenerators = new HashSet<ScenarioGenerator>(
0);
private List<Component> components = new ArrayList<Component>();
private Set<OptConstraint> optconstraints = new HashSet<OptConstraint>(0);
private Set<SearchConstraint> searchconstraints = new HashSet<SearchConstraint>(
0);
private Set<Metric> metrics = new HashSet<Metric>(0);
private Set<UserGroupProject> usergroupprojects = new HashSet<UserGroupProject>(
0);
private Set<ExtParam> extparams = new HashSet<ExtParam>(0);

public Project() {
}

public Project(int prjid, String name) {
this.prjid = prjid;
this.name = name;
}

public Project(int prjid, SimulationModel simulationmodel, String name,
String description, String designtarget, Date timehorizon, String location,
Date createdon, Date updatedon, Integer createdby,
Integer updatedby, Set<ObjectiveFunction> objectivefunctions,
Set<Scenario> scenarios, Set<ScenarioGenerator> scenariogenerators,
List<Component> components, Set<OptConstraint> optconstraints,
Set<SearchConstraint> searchconstraints, Set<Metric> metrics,
Set<UserGroupProject> usergroupprojects, Set<ExtParam> extparams) {
this.prjid = prjid;
this.simulationmodel = simulationmodel;
this.name = name;
this.description = description;
this.designtarget = designtarget;
this.timehorizon = timehorizon;
this.location = location;
this.createdon = createdon;
this.updatedon = updatedon;
this.createdby = createdby;
this.updatedby = updatedby;
this.objectivefunctions = objectivefunctions;
this.scenarios = scenarios;
this.scenariogenerators = scenariogenerators;
this.components = components;
this.optconstraints = optconstraints;
this.searchconstraints = searchconstraints;
this.metrics = metrics;
this.usergroupprojects = usergroupprojects;
this.extparams = extparams;
}

@SequenceGenerator(name="project_prjid_seq",sequenceName="project_prjid_seq") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="project_prjid_seq")
@Id
@Column(name = "prjid", unique = true, nullable = false)
public int getPrjid() {
return this.prjid;
}

public void setPrjid(int prjid) {
this.prjid = prjid;
}

@ManyToOne(fetch = FetchType.LAZY,cascade=CascadeType.PERSIST)
@JoinColumn(name = "modelid")
public SimulationModel getSimulationmodel() {
return this.simulationmodel;
}

public void setSimulationmodel(SimulationModel simulationmodel) {
this.simulationmodel = simulationmodel;
}

@Column(name = "name", unique = true, nullable = false, length = 50)
public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

@Column(name = "description")
public String getDescription() {
return this.description;
}

public void setDescription(String description) {
this.description = description;
}

@Column(name = "designtarget", length = 50)
public String getDesigntarget() {
return this.designtarget;
}

public void setDesigntarget(String designtarget) {
this.designtarget = designtarget;
}

@Temporal(TemporalType.TIME)
@Column(name = "timehorizon", length = 15)
public Date getTimehorizon() {
return this.timehorizon;
}

public void setTimehorizon(Date timehorizon) {
this.timehorizon = timehorizon;
}

@Column(name = "location")
public String getLocation() {
return this.location;
}

public void setLocation(String location) {
this.location = location;
}

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "createdon", length = 22)
public Date getCreatedon() {
return this.createdon;
}

public void setCreatedon(Date createdon) {
this.createdon = createdon;
}

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "updatedon", length = 22)
public Date getUpdatedon() {
return this.updatedon;
}

public void setUpdatedon(Date updatedon) {
this.updatedon = updatedon;
}

@Column(name = "createdby")
public Integer getCreatedby() {
return this.createdby;
}

public void setCreatedby(Integer createdby) {
this.createdby = createdby;
}

@Column(name = "updatedby")
public Integer getUpdatedby() {
return this.updatedby;
}

public void setUpdatedby(Integer updatedby) {
this.updatedby = updatedby;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<ObjectiveFunction> getObjectivefunctions() {
return this.objectivefunctions;
}

public void setObjectivefunctions(Set<ObjectiveFunction> objectivefunctions) {
this.objectivefunctions = objectivefunctions;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<Scenario> getScenarios() {
return this.scenarios;
}

public void setScenarios(Set<Scenario> scenarios) {
this.scenarios = scenarios;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<ScenarioGenerator> getScenariogenerators() {
return this.scenariogenerators;
}

public void setScenariogenerators(Set<ScenarioGenerator> scenariogenerators) {
this.scenariogenerators = scenariogenerators;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
@OrderBy("componentid")
public List<Component> getComponents() {
return this.components;
}

public void setComponents(List<Component> components) {
this.components = components;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<OptConstraint> getOptconstraints() {
return this.optconstraints;
}

public void setOptconstraints(Set<OptConstraint> optconstraints) {
this.optconstraints = optconstraints;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<SearchConstraint> getSearchconstraints() {
return this.searchconstraints;
}

public void setSearchconstraints(Set<SearchConstraint> searchconstraints) {
this.searchconstraints = searchconstraints;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<Metric> getMetrics() {
return this.metrics;
}

public void setMetrics(Set<Metric> metrics) {
this.metrics = metrics;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<UserGroupProject> getUsergroupprojects() {
return this.usergroupprojects;
}

public void setUsergroupprojects(Set<UserGroupProject> usergroupprojects) {
this.usergroupprojects = usergroupprojects;
}

@OneToMany(fetch = FetchType.LAZY, mappedBy = "project")
public Set<ExtParam> getExtparams() {
return this.extparams;
}

public void setExtparams(Set<ExtParam> extparams) {
this.extparams = extparams;
}

Aucun commentaire:

Enregistrer un commentaire