请帮我实现延迟初始化。

我用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;
        }
    }
...

最佳答案

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

之后,您尝试访问一个用户列表,我认为您是从public List<User> listUsers()获得的,如果您理解以上段落,您可能已经知道我要说的内容。在这种方法中,您无需获取角色,离开事务,然后尝试使用<td>${user.roles}</td>访问角色。现在有一些可能的解决方案。

  • 首先,将实体发送到前端通常不是一个好主意,因为那样会引入不必要的耦合。我的建议是,您制作一个UserDto类,其中包含您需要显示的所有字段(但不再显示单个字段),其中包含一组RoleDto,将相同的内容应用于该字段,并转换您从中获得的用户列表将数据库放入listUsers()方法中的用户dto列表中。这样,您可以解决3个问题-> 1.您的代码已解耦2.您消除了数据冗余(例如,对于前端db字段而言是不必要的)3.您在从用户到用户dto的转换中获取了每个用户的角色。
  • 另一个选择是通过添加新的odt_code实体来实现它,从而将User实体与实现UserDetails的类分开。然后,您可以在登录时将所有 Spring 所需的信息添加到此类中,这基本上意味着在User方法中将必要的信息从UserDetailsImpl类传输到此新的loadUserByUsername(String username)类,以便可以充分实现UserDetails接口继承的所有方法。从那时起,任何角色或权限的验证都将通过非实体 UserDetailsImpl传递,这将消除您的变通办法,并且该类绝不会抛出LazyInitializationException,因为它不是实体。但是,由于可能存在数据差异,强烈建议不要从firstName复制诸如lastNameUser之类的字段。
  • (不推荐)如果您真的很懒,可以始终只在listUsers()方法中执行完全相同的解决方法,然后遍历用户并调用其getRoles()方法来获取角色。
  • 最后,我明白,无论出于何种原因,您都无法执行此操作,这就是为什么我将这一点留在最后,但是如果您不得不多次访问事务外的实体集合,那么FetchType.EAGER显然是最简单,最好的方法选项。从字面上看,这就是要解决的问题。
  • 关于java - 延迟初始化@ManyToMany。有什么错误?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/61675882/

    10-16 10:11