【问题标题】:Lazy initialization @ManyToMany. What is the error?延迟初始化@ManyToMany。错误是什么?
【发布时间】:2020-05-08 09:18:02
【问题描述】:

请帮我实现延迟初始化。

我在 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

Google 指向这些链接:First Link / Second Link

建议添加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>
...

UserDaoImp

@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】:

    FetchType.LAZY 所做的是,当包含它的实体从数据库返回时,它不采用它所应用的实体集合(或在某些情况下为实体,例如 @OneToOne)。它稍后获取它发出一个额外的请求如果它的getter方法在在同一个事务中被调用。您找到的解决方法有效,因为您在getAuthorities() 中调用了getter getRoles()(尽管您没有将其添加到问题中),因此显式地获取了一组角色。

    之后,您尝试访问我假设您从public List&lt;User&gt; listUsers() 获得的用户列表,如果您理解上述段落,您可能已经知道我要说什么。在该方法中,您不获取角色,离开事务,然后尝试使用&lt;td&gt;${user.roles}&lt;/td&gt; 访问角色。现在有一些可能的解决方案来解决这个问题。

    • 首先,将实体发送到前端通常不是一个好主意,因为这样会引入不必要的耦合。我建议您创建一个 UserDto 类,其中包含您需要显示的所有字段(但不能再显示一个),其中包含一组 RoleDto ,同样的事情适用于您的列表您从 db 获得的用户到 listUsers() 方法中的用户 dtos 列表中。这样您就可以解决 3 个问题 -> 1. 您的代码是解耦的 2. 您消除了数据冗余(例如,对于前端 db 字段来说是不必要的) 3. 在从用户转换为用户 dto 时获取每个用户的角色。
    • 另一种选择是将User 实体与实现UserDetails 的类分开,方法是添加一个新类来实现它。然后,您可以在登录时将 spring 需要的所有信息添加到此类,这基本上意味着将必要的信息从 User 类传输到 loadUserByUsername(String username) 方法中的这个新的 UserDetailsImpl 类,以便所有方法都由 @987654335 继承@interface 可以充分实现。从那时起,任何对角色或权限的验证都将通过 non-entity UserDetailsImpl,这将消除您对解决方法的需要,并且该类将永远不会抛出 LazyInitializationException,因为它不是实体.但是,由于可能存在数据差异,强烈建议不要从 User 复制 firstNamelastName 等字段。
    • (不推荐)如果您真的很懒惰,您可以随时在 listUsers() 方法中执行完全相同的解决方法,然后遍历用户并调用他们的 getRoles() 方法来获取角色。
    • 最后我明白无论出于何种原因你都不能这样做,这就是为什么我把这一点放在最后,但如果你必须在很多情况下访问事务之外的实体集合,那么 FetchType.EAGER 显然是最简单和最好的选择。这实际上就是它要解决的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-23
      • 2010-11-02
      • 2020-05-10
      • 1970-01-01
      • 2011-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多