pWoz
pWoz

Reputation: 1679

'no session or session was closed' with JPA 2 and EJB 3.1

I have stateless session bean with this method:

@Override
public List<Character> getUserCharacters(int userId) {
    User user = em.find(User.class, userId);
    if(user != null)
        return user.getCharacters();
    else
        return null;
}

where User class if defined in this way:

@Entity
@Table(name="Users")
public class User implements Serializable{

    /**  */

private static final long serialVersionUID = 8119486011976039947L;

@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
private int id;

@ManyToMany(fetch=FetchType.EAGER)
private Set<Role> roles;

@OneToMany(mappedBy="owner",fetch=FetchType.LAZY)
private List<com.AandP.game.model.characters.Character> characters;

public User() {
    creationDate = new Date();
    roles = new HashSet<Role>();
    }
}

But when i execute this method (from my @Named bean) i receive exception:

 org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.AandP.game.model.User.characters, no session or session was closed

According to the JPA 2.0 specification session should stay alive for a whole transaction. In this situation transaction (in my opinion) last for a whole method call (there is no additional transaction attributes on class or method).

So the question is: what is wrong with this code and how can I load class properties in a lazy way.

Upvotes: 0

Views: 6719

Answers (4)

mericano1
mericano1

Reputation: 2913

According to the JPA 2.0 specification session should stay alive for a whole transaction. In this situation transaction (in my opinion) last for a whole method call (there is no additional transaction attributes on class or method).

That's true, but it does not include the serialization of the returned objects.

I had the same issue exporting a session bean as a web service. Read more about the issue here.

If you have a similar use would strongly suggest you to return plain objects and not entities. You can use some bean mapping framework like we did. We used Dozer.

Upvotes: 1

dmotta
dmotta

Reputation: 1903

2013-07-24 19:47:07,387 ERROR: - failed to lazily initialize a collection of role: com.xxxx.domain.DenialMaster.denialDetails, no session or session was closed null

My config:

<tx:annotation-driven transaction-manager="transactionManagerDL" />

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

<bean id="emfDL" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSourceDL" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="mappingResources">
        <value>META-INF/orm.xml</value>
    </property>
    <property name="packagesToScan" value="${dl.entity.packages}" />
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">${catalog.org.hibernate.dialect}</prop>
            <prop key="hibernate.max_fetch_depth">${catalog.org.hibernate.jdbc.max.fetchdepth}</prop>
            <prop key="hibernate.jdbc.fetch_size">${catalog.org.hibernate.jdbc.fetchsize}</prop>
            <prop key="hibernate.jdbc.batch_size">${catalog.org.hibernate.jdbc.batchsize}</prop>
            <prop key="hibernate.show_sql">${catalog.org.hibernate.showsql}</prop>
            <prop key="hibernate.connection.autocommit">${catalog.org.hibernate.connection.autocommit}
            </prop>

        </props>
    </property>
</bean>

SOLUTION: In your Class ServiceImpl add the @Transactional(readOnly = true) Example:

@Override
@Transactional(readOnly = true)
public YourBean findById(long id) throws Exception {
    return yourDAO.findOne(id);
}

and @LazyCollection(LazyCollectionOption.FALSE):

  @OneToMany(fetch=FetchType.LAZY)
  @JoinColumn(name = "idMaster")
  @LazyCollection(LazyCollectionOption.FALSE)
  public List<DenialDetail> getDenialDetails() {
    return denialDetails;
  }

Upvotes: 0

hsanders
hsanders

Reputation: 1898

pWoz: What Bozho says is correct. The one caveat I'd add is if you're not entering into that method through an interface (Local or Remote), that annotation doesn't matter. If this is what your bean looks like:

@Stateless
public class MyUserBean implements UserBeanLocal {
...

  public void doSomeStuffWithUserCharactersById(int id) {
     List<Character>userCharacters = getUserCharacters(id);
  }

  @Override
  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public List<Character> getUserCharacters(int userId) {
   User user = em.find(User.class, userId);
   if(user != null)
     return user.getCharacters();
    else
     return null;
  }
}

That attribute's not going to be honored because within the same session bean, the first business method invoked determines the transaction context and behavior. Now, I can't tell for certain if that's what's going on in this instance because I don't see the rest of your source code, but that's a very common mistake.

Upvotes: 0

Bozho
Bozho

Reputation: 597362

You need to specify @TransactionAttribute so that your method is transactional. Otherwise a readonly transaction and a new underlying session is started for each entity manager operation. This, combined with a lazy collection, means that when you fetch the collection the original session gets closed.

Upvotes: 0

Related Questions