[英]Hibernate 4 + Spring Data CrudRepository, CLI application: failed to lazily initialize a collection: could not initialize proxy - no Session
I am trying to run Spring-based CLI application which uses Spring Data CrudRepository to access Hibernate4-based persistence layer implemented using JPA annotations over MySQL5 (InnoDB) database using c3p0 connection pool. 我正在尝试运行基于Spring的CLI应用程序,该应用程序使用Spring Data CrudRepository来访问基于Hibernate4的持久层,该持久层通过使用c3p0连接池的MySQL5(InnoDB)数据库通过JPA注释实现。
I receive the following exception: 我收到以下异常:
Exception in thread "main" java.lang.RuntimeException: org.springframework.orm.hibernate3.HibernateSystemException: failed to lazily initialize a collection of role: <package>.entity.User.categories, could not initialize proxy - no Session; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: <package>.entity.User.categories, could not initialize proxy - no Session
I'm newbie with Spring Data and Hibernate. 我是Spring Data和Hibernate的新手。 From my point of view it's an issue with two different transactions (one in
UserServiceImpl::findByLogin
and another in CategoryServiceImpl::deleteByUser
). 从我的角度来看,这是两个不同事务的问题(一个在
UserServiceImpl::findByLogin
,另一个在CategoryServiceImpl::deleteByUser
)。 Changing User
entity to use Eager fetch type for Categories helps, but I want to use lazy loading in this method. 将
User
实体更改为对类别使用Eager访存类型会有所帮助,但是我想在此方法中使用延迟加载。
Can I still use lazy fetch type in UserServiceImpl::findByLogin
and fetch dependent objects in service consumer later with CrudRepository
and Spring-managed transactions in Service Layer? 我是否仍可以在
UserServiceImpl::findByLogin
使用延迟获取类型并稍后在服务层中使用CrudRepository
和Spring管理的事务在服务使用者中获取依赖对象?
Excerpt from the application which causes an exception: 来自应用程序的摘录,它导致异常:
User user = userService.findByLogin(login);
categoryService.deleteByUser(user);
EDIT: I tried to use EntityManager::merge
, but with no luck: 编辑:我试图使用
EntityManager::merge
,但没有运气:
@Service
@Repository
@Transactional
public class CategoryServiceImpl implements CategoryService, InitializingBean {
@Autowired
private CategoryRepository repository;
@Autowired
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
@Override
public void afterPropertiesSet() throws Exception {
entityManager = entityManagerFactory.createEntityManager();
}
@Override
@Transactional(readOnly = true)
public Category findById(Long categoryId) {
return repository.findOne(categoryId);
}
@Override
@Transactional
public Category save(Category category) {
return repository.save(category);
}
@Override
@Transactional
public void delete(Category category) {
repository.delete(category);
}
@Override
@Transactional
public void deleteByUser(User user) {
entityManager.merge(user);
repository.delete(user.getCategories());
}
}
Services (injected with @Autowired
): User service: 服务(注入
@Autowired
):用户服务:
package <package>.service.jpa;
import com.google.common.collect.Lists;
import <package>.entity.User;
import <package>.repository.UserRepository;
import <package>.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Repository
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository repository;
@Override
@Transactional(readOnly = true)
public List<User> findAll() {
return Lists.newArrayList(repository.findAll());
}
@Override
@Transactional(readOnly = true)
public User findById(Long userId) {
return repository.findOne(userId);
}
@Override
@Transactional(readOnly = true)
public User findByLogin(String login) {
return repository.findByLogin(login);
}
@Override
@Transactional
public User save(User user) {
return repository.save(user);
}
@Override
@Transactional
public void delete(User user) {
repository.delete(user);
}
}
Category service: 分类服务:
package <package>.service.jpa;
import <package>.entity.Category;
import <package>.entity.User;
import <package>.repository.CategoryRepository;
import <package>.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Repository
@Transactional
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryRepository repository;
@Override
@Transactional(readOnly = true)
public Category findById(Long categoryId) {
return repository.findOne(categoryId);
}
@Override
@Transactional
public Category save(Category category) {
return repository.save(category);
}
@Override
@Transactional
public void delete(Category category) {
repository.delete(category);
}
@Override
@Transactional
public void deleteByUser(User user) {
repository.delete(user.getCategories());
}
}
Entities: User: 实体:用户:
package <package>.entity;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table
public class User {
private Long userId;
private int version;
private String login;
private Set<Category> categories = new HashSet<Category>();
private Set<CategoryFeed> categoryFeeds = new HashSet<CategoryFeed>();
@Id
@GeneratedValue
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
@Version
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
@Column
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Category> getCategories() {
return categories;
}
public void setCategories(Set<Category> categories) {
this.categories = categories;
}
public void addCategory(Category category) {
categories.add(category);
}
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<CategoryFeed> getCategoryFeeds() {
return categoryFeeds;
}
public void setCategoryFeeds(Set<CategoryFeed> categoryFeeds) {
this.categoryFeeds = categoryFeeds;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof User)) {
return false;
}
User user = (User) o;
if (login != null ? !login.equals(user.login) : user.login != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return login != null ? login.hashCode() : 0;
}
}
Category: 类别:
package <package>.entity;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"userId", "title"}))
public class Category {
public static final String ROOT_CATEGORY_TITLE = "";
private Long categoryId;
private int version;
private User user;
private String title = ROOT_CATEGORY_TITLE;
private Set<CategoryFeed> feeds = new HashSet<CategoryFeed>();
@Id
@GeneratedValue
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
@Version
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
@ManyToOne
@JoinColumn(name = "userId")
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Column(name = "title")
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@ManyToMany(mappedBy = "categories", cascade = CascadeType.ALL)
public Set<CategoryFeed> getFeeds() {
return feeds;
}
public void setFeeds(Set<CategoryFeed> feeds) {
this.feeds = feeds;
}
public void addFeed(CategoryFeed feed) {
this.feeds.add(feed);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Category)) {
return false;
}
Category category = (Category) o;
if (title != null ? !title.equals(category.title) : category.title != null) {
return false;
}
if (user != null ? !user.equals(category.user) : category.user != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = user != null ? user.hashCode() : 0;
result = 31 * result + (title != null ? title.hashCode() : 0);
return result;
}
}
Run this code: 运行此代码:
User user = userService.findByLogin(login);
categoryService.deleteByUser(user);
within a single transaction like so: 在单个交易中,如下所示:
@Transactional
public void deleteCategoriesByUser(String login) {
User user = userService.findByLogin(login);
categoryService.deleteByUser(user);
}
which will make sure the same Hibernate session is used for both operations. 这将确保两个操作都使用相同的Hibernate会话。
I don't think your problem is to do with transaction, you seem to have setup your declarative transaction boundaries properly (default Spring transaction propagation is REQUIRED -- meaning if you call nested method decorated with @Transactional
, no overlapping transaction is created) 我不认为您的问题与事务有关,您似乎已经正确设置了声明性事务边界(默认为Spring事务传播是必需的-这意味着,如果调用以
@Transactional
装饰的嵌套方法,则不会创建任何重叠的事务)
Your problem seem to be cause by categories
property of the user object not populated when you're fetching it (because you set it to lazy) AND/OR hibernate unable to populate it before session is closed on deleteByLogin
-- hence the object is already detached. 您的问题似乎是由于在获取用户对象时未填充用户对象的
categories
属性引起的(因为您将其设置为lazy)和/或休眠状态无法在deleteByLogin
关闭会话之前填充它-因此该对象已经存在分离。
As fas as I can see there are two method to resolve this: 据我所知,有两种方法可以解决此问题:
1. Eagerly fetch the categories property of a user 1.认真获取用户的类别属性
Mark categories
property as eagerly fetched: @OneToMany(fetch = FetchType.EAGER, ...)
(WARNING: could eat massive amount of memory), or use LEFT JOIN FETCH
syntax when querying for a user so its categories
property is populated 将
categories
属性标记为急切获取: @OneToMany(fetch = FetchType.EAGER, ...)
(警告:可能会消耗大量内存),或者在查询用户时使用LEFT JOIN FETCH
语法,以便填充其categories
属性
select distinct u from User as u left join fetch u.categories where u.login = :login
2. Merge the detached user on deleteByLogin
2.将分离的用户合并到
deleteByLogin
On deleteByLogin
, merge the user object first into the persistence context so the categories
property can be lazily loaded whenever you call getCategories()
在
deleteByLogin
,首先合并用户对象为持久化上下文这样categories
属性可以延迟加载,只要你拨打getCategories()
@Override
@Transactional
public void deleteByUser(User user) {
session.merge(user);
repository.delete(user.getCategories());
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.