懒惰初始化@ManyToMany。错误是什么?

问题描述 投票:4回答:1

请帮我实现懒惰初始化。

我用Spring Core、Spring Security和Hibernate编写了Java代码。有两个用户和角色实体。我将它们与 @ManyToMany(fetch = FetchType.EAGER). 但是对于任务,我需要实现懒惰初始化。

如果我设置 @ManyToMany(fetch = FetchType.LAZY),授权成功后出现错误。

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: web.model.User.roles, could not initialize proxy - no Session

谷歌引导到这些链接。第一个链接 第二个链接

它建议增加 user.getAuthorities () method.size();.

这是不是有什么变通的办法呢?

然后授权成功,但是用懒惰初始化,我无法访问角色。而且在jsp页面上也不显示。

org.apache.jasper.JasperException: An error occurred during processing [/WEB-INF/pages/admin_panel.jsp] in line [71]

68:             <td>${user.lastName}</td>
69:             <td>${user.username}</td>
70:             <td>${user.password}</td>
71:             <td>${user.roles}</td>
72:             <td>


Stacktrace:
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:617)

下面我将介绍代码的主要部分。如果有遗漏的地方,那就告诉我吧。

我的代码。

用户

@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "firstName")
private String firstName;

@Column(name = "lastName")
private String lastName;

@Column(name = "username")
private String username;

@Column(name = "password")
private String password;

@ManyToMany(fetch = FetchType.LAZY) // there was an EAGER here.
@JoinTable(name="user_role",
        joinColumns={@JoinColumn(name="userId")},
        inverseJoinColumns={@JoinColumn(name="roleId")})
private Set<Role> roles;

public User() {
}
...

类UserServiceImp

@Service
public class UserServiceImp implements UserService {

    @Autowired
    private UserDao userDao;

    @Transactional
    @Override
    public void add(User user) {
        userDao.add(user);
    }

    @Transactional
    public List<User> listUsers() {
        return userDao.listUsers();
    }

    @Transactional
    public boolean deleteUserById(long id) {
        return userDao.deleteUserById(id);
    }

    @Transactional(readOnly = true)
    public User getUserById(long id) {
        return userDao.getUserById(id);
    }

    @Transactional
    public void update(User user) {
        userDao.update(user);
    }

    @Transactional
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.getUserByName(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        int size = user.getAuthorities().size(); //added this line as indicated in the links
        return user;
    }
}

页面 *.jsp

...
<c:forEach items="${users}" var="user">
    <tr>
        <td>${user.id}</td>
        <td>${user.firstName}</td>
        <td>${user.lastName}</td>
        <td>${user.username}</td>
        <td>${user.password}</td>
        <td>${user.roles}</td>
        <td>
            <a href="<c:url value='${pageContext.request.contextPath}/admin/editUser?id=${user.id}'/>" >Edit</a>
            |
            <a href="<c:url value='${pageContext.request.contextPath}/admin/deleteUser/${user.id}'/>" >Delete</a>
        </td>
    </tr>
</c:forEach>
...

用户体验

@Repository
public class UserDaoImp implements UserDao {

    @PersistenceContext
    private EntityManager manager;

   ...

    @Override
    @SuppressWarnings("unchecked")
    public User getUserByName(String username) {
        Query query = manager.createQuery("FROM User u WHERE u.username = : username");
        query.setParameter("username", username);
        try {
            return (User) query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }
...
java spring hibernate many-to-many lazy-initialization
1个回答
1
投票

什么是 FetchType.LAZY 的作用是,当包含实体的实体从db中返回时,它不会把它所应用的实体集合(或在某些情况下实体,如 @OneToOne)应用到的实体集合上,当包含它的实体从db中返回时。它做了一个额外的请求到 取来 其后 如果 它的getter方法被称为 同行. 你找到的变通方法是可行的,因为你调用了getter的 getRoles()getAuthorities() (尽管你没有在问题中加入这一点)因此,你要明确地获取角色集。

之后,你尝试访问用户列表,我想你应该是从 public List<User> listUsers() 如果你理解了上面的段落,你可能已经知道我要说什么了。在这种方法中,你不获取角色,离开事务,然后尝试用 <td>${user.roles}</td>. 现在有几种可能的解决方案来解决这个问题。

  • 首先,将实体发送到前端通常不是一个好主意,因为这样你会引入不必要的耦合。我建议你做一个 UserDto 类,它包含了你需要显示的所有字段(但不能再多一个字段),它有一个Set of RoleDto 中的用户列表,并将你从db中得到的用户列表转换为用户dtos列表。listUsers() 方法,这样你就解决了3个问题-> 1. 这样你就解决了3个问题-> 1.你的代码是解耦的 2.你消除了数据冗余(例如前台db字段不需要) 3.当从用户转换到用户dtos时,你可以获取每个用户的角色。
  • 另一种选择是将 User 实体的类,实现 UserDetails 通过添加一个新的类来实现它。然后你可以在登录时向这个类添加所有弹簧所需的信息,这基本上意味着将必要的信息从 User 类到这个新的 UserDetailsImpl 课堂上 loadUserByUsername(String username) 方法,从而使所有由 UserDetails 接口可以得到充分的实现。从这一点上,任何角色或权限的验证都将通过 非实体 UserDetailsImpl 这将消除对你的变通方法的需求,而且该类永远不会抛出一个 LazyInitializationException 因为它不是一个实体。但复制像 firstNamelastNameUser 是非常不鼓励的,因为可能的数据差异。
  • (不推荐)如果你觉得真的很懒,你总是可以做完全相同的变通方法。listUsers() 方法,只需在用户中迭代并调用他们的 getRoles() 方法来获取角色。
  • 最后,我明白无论什么原因,你不能这样做,这就是为什么我把这一点留到最后,但如果你必须在很多场合访问一个实体的集合,在事务之外,那么你可以使用 FetchType.EAGER 显然是最简单、最好的选择。这简直就是它所要解决的问题。
© www.soinside.com 2019 - 2024. All rights reserved.