Reputation: 1679
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
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
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
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
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