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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-01 01:30:14  来源:igfitidea点击:

Hibernate 4 + Spring Data CrudRepository, CLI application: failed to lazily initialize a collection: could not initialize proxy - no Session

javaspringhibernatespring-data

提问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::findByLoginand another in CategoryServiceImpl::deleteByUser). Changing Userentity 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::findByLoginand fetch dependent objects in service consumer later with CrudRepositoryand 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 categoriesproperty 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 categoriesproperty as eagerly fetched: @OneToMany(fetch = FetchType.EAGER, ...)(WARNING: could eat massive amount of memory), or use LEFT JOIN FETCHsyntax when querying for a user so its categoriesproperty 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 categoriesproperty 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());
}