Hibernate getResultList()重复返回首行数据问题求助
问题重现
你在Spring环境中用Hibernate查询Authority实体时遇到了奇怪的问题:调用getResultList()拿到结果后,循环输出每个实例的email,却全都是第一行的email1@server.com。
你的查询代码:
String query = "from Authority"; session = sessionFactory.openSession(); List<Authority> authorities= session.createQuery(query).getResultList(); session.close(); for(Authority a: authorities){ System.out.println(a.getEmail()); }
User实体类关键代码:
@Entity @Table(name = "users") public class User { @Id @NotBlank @Size(max = 100) @Email @Column private String email; @OneToMany(cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true) private Set<Authority> authorities = new HashSet<>(); // 省略其他代码 }
Authority实体类关键代码:
@Entity @Table(name = "authorities") public class Authority { @Id @Column private String authority; @ManyToOne @JoinColumn(name = "email") private User user; // 省略其他代码 }
数据库中的authorities表数据:
email1@server.com ROLE_USER email2@server.com ROLE_USER email3@server.com ROLE_USER email4@server.com ROLE_USER
但实际输出结果却是:
email1@server.com email1@server.com email1@server.com email1@server.com
问题根源
问题出在Authority实体的主键设置上!
你给Authority的authority字段标注了@Id,但数据库里所有记录的authority值都是ROLE_USER——这意味着所有Authority实体的ID完全相同。
Hibernate是通过主键来识别不同实体实例的,当它加载第一个Authority实例后,后续遇到相同ID的记录时,会直接复用之前缓存的那个实例,自然它关联的User也是第一个加载的email1@server.com对应的对象,所以循环输出时全是同一个email。
解决方案
有两种常见的修复方式,选一种适合你的场景即可:
方案1:使用复合主键(推荐,符合数据库表的业务逻辑)
因为authorities表的业务主键应该是email + authority的组合(一个用户可以有多个权限,同一个权限也可以对应多个用户,但同一个用户的同一个权限唯一),所以我们可以创建一个复合主键类:
首先定义可嵌入的主键类:
@Embeddable public class AuthorityId implements Serializable { @Column(name = "email") private String email; @Column(name = "authority") private String authority; // 必须添加无参构造器 public AuthorityId() {} public AuthorityId(String email, String authority) { this.email = email; this.authority = authority; } // 重写equals和hashCode方法(必须) @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AuthorityId that = (AuthorityId) o; return Objects.equals(email, that.email) && Objects.equals(authority, that.authority); } @Override public int hashCode() { return Objects.hash(email, authority); } // getter和setter }
然后修改Authority实体类:
@Entity @Table(name = "authorities") public class Authority { @EmbeddedId private AuthorityId id; @ManyToOne @MapsId("email") // 关联复合主键中的email字段 @JoinColumn(name = "email") private User user; // 这里可以添加一个便捷的getter获取authority值 public String getAuthority() { return id.getAuthority(); } // 省略其他代码 }
方案2:添加独立的自增主键
如果不想用复合主键,可以给Authority表添加一个独立的自增ID字段,比如id:
修改Authority实体类:
@Entity @Table(name = "authorities") public class Authority { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增策略,根据数据库调整 private Long id; @Column private String authority; @ManyToOne @JoinColumn(name = "email") private User user; // 省略其他代码 }
同时记得给数据库的authorities表添加对应的自增id字段。
内容的提问来源于stack exchange,提问作者Eryk Tomaszewski

