延迟初始化@ManyToMany。错误是什么?
Lazy initialization @ManyToMany. What is the error?
拜托,帮我实现惰性初始化。
我在 Spring 核心、Spring 安全和 Hibernate 中编写了 Java 代码。有两个用户和角色实体。我 link 他们和 @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 指向这些 link: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)
下面我将介绍代码的主要部分。如果缺少什么,请告诉我。
我的代码:
User
@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() {
}
...
class 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;
}
}
page *.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
)。它会额外请求 fetch 它稍后 如果 它的 getter 方法在同一事务中被调用 。您找到的解决方法有效,因为您在 getAuthorities()
中调用 getter getRoles()
(尽管您没有将其添加到问题中)因此显式获取角色集。
之后你尝试访问我假设你从 public List<User> listUsers()
获得的用户列表,如果你理解了上面的段落,你可能已经知道我要说什么了。在该方法中,您不获取角色,离开事务,然后尝试使用 <td>${user.roles}</td>
访问角色。现在这个问题有几个可能的解决方案。
- 首先,将实体发送到前端通常不是一个好主意,因为那样会引入不必要的耦合。我建议你制作一个
UserDto
class ,其中包含你需要显示的所有字段(但不是一个),其中包含一组 RoleDto
相同的事情适用,您将从数据库获得的用户列表转换为 listUsers()
方法中的用户 dto 列表。这样你解决了 3 个问题 -> 1. 你的代码是解耦的 2. 你消除了数据冗余(例如,对于前端数据库字段来说是不必要的)3. 当从用户转换为用户 dtos 时,你获取每个用户的角色。
- 另一种选择是将
User
实体与 class 实现 UserDetails
分开,方法是添加一个新的 class 来实现它。然后,您可以在登录时向此 class 添加 spring 需要的所有信息,这基本上意味着将必要的信息从 User
class 传输到这个新的 UserDetailsImpl
class 在 loadUserByUsername(String username)
方法中,以便可以充分实现 UserDetails
接口继承的所有方法。从那时起,对角色或权限的任何验证都将通过 non-entity UserDetailsImpl
,这将消除您对解决方法的需求,并且 class 永远不会抛出a LazyInitializationException
因为它不是一个实体。但是从 User
复制 firstName
和 lastName
等字段是非常不鼓励的,因为可能存在数据差异。
- (不推荐)如果你真的很懒惰,你总是可以在
listUsers()
方法中做完全相同的解决方法,只需遍历用户并调用他们的 getRoles()
方法来获取角色。
- 最后我明白,无论出于何种原因你不能这样做,这就是为什么我把这一点放在最后,但如果你必须在很多情况下访问事务之外的实体集合,那么
FetchType.EAGER
显然是最简单和最好的选择。这就是它要解决的问题。
拜托,帮我实现惰性初始化。
我在 Spring 核心、Spring 安全和 Hibernate 中编写了 Java 代码。有两个用户和角色实体。我 link 他们和 @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 指向这些 link: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)
下面我将介绍代码的主要部分。如果缺少什么,请告诉我。
我的代码:
User
@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() {
}
...
class 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;
}
}
page *.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
)。它会额外请求 fetch 它稍后 如果 它的 getter 方法在同一事务中被调用 。您找到的解决方法有效,因为您在 getAuthorities()
中调用 getter getRoles()
(尽管您没有将其添加到问题中)因此显式获取角色集。
之后你尝试访问我假设你从 public List<User> listUsers()
获得的用户列表,如果你理解了上面的段落,你可能已经知道我要说什么了。在该方法中,您不获取角色,离开事务,然后尝试使用 <td>${user.roles}</td>
访问角色。现在这个问题有几个可能的解决方案。
- 首先,将实体发送到前端通常不是一个好主意,因为那样会引入不必要的耦合。我建议你制作一个
UserDto
class ,其中包含你需要显示的所有字段(但不是一个),其中包含一组RoleDto
相同的事情适用,您将从数据库获得的用户列表转换为listUsers()
方法中的用户 dto 列表。这样你解决了 3 个问题 -> 1. 你的代码是解耦的 2. 你消除了数据冗余(例如,对于前端数据库字段来说是不必要的)3. 当从用户转换为用户 dtos 时,你获取每个用户的角色。 - 另一种选择是将
User
实体与 class 实现UserDetails
分开,方法是添加一个新的 class 来实现它。然后,您可以在登录时向此 class 添加 spring 需要的所有信息,这基本上意味着将必要的信息从User
class 传输到这个新的UserDetailsImpl
class 在loadUserByUsername(String username)
方法中,以便可以充分实现UserDetails
接口继承的所有方法。从那时起,对角色或权限的任何验证都将通过 non-entityUserDetailsImpl
,这将消除您对解决方法的需求,并且 class 永远不会抛出aLazyInitializationException
因为它不是一个实体。但是从User
复制firstName
和lastName
等字段是非常不鼓励的,因为可能存在数据差异。 - (不推荐)如果你真的很懒惰,你总是可以在
listUsers()
方法中做完全相同的解决方法,只需遍历用户并调用他们的getRoles()
方法来获取角色。 - 最后我明白,无论出于何种原因你不能这样做,这就是为什么我把这一点放在最后,但如果你必须在很多情况下访问事务之外的实体集合,那么
FetchType.EAGER
显然是最简单和最好的选择。这就是它要解决的问题。