java Hibernate 4 + Spring Data CrudRepository,CLI 应用程序:无法延迟初始化集合:无法初始化代理 - 没有会话
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17265239/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Hibernate 4 + Spring Data CrudRepository, CLI application: failed to lazily initialize a collection: could not initialize proxy - no Session
提问by Max Romanovsky
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 访问使用 c3p0 连接池的 MySQL5 (InnoDB) 数据库上使用 JPA 注释实现的基于 Hibernate4 的持久层。
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. From my point of view it's an issue with two different transactions (one in UserServiceImpl::findByLogin
and another in CategoryServiceImpl::deleteByUser
). Changing User
entity to use Eager fetch type for Categories helps, but I want to use lazy loading in this method.
我是 Spring Data 和 Hibernate 的新手。从我的角度来看,这是两个不同事务(一个 inUserServiceImpl::findByLogin
和另一个 in CategoryServiceImpl::deleteByUser
)的问题。将User
实体更改为对类别使用 Eager fetch 类型会有所帮助,但我想在此方法中使用延迟加载。
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;
}
}
回答by Jukka
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 会话。
回答by gerrytan
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
在获取用户对象时未填充的用户对象的属性引起的(因为您将其设置为惰性)和/或休眠无法在会话关闭之前填充它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()
On deleteByLogin
,首先将用户对象合并到持久性上下文中,以便categories
在您调用时可以延迟加载该属性getCategories()
@Override
@Transactional
public void deleteByUser(User user) {
session.merge(user);
repository.delete(user.getCategories());
}