JPA的一对一、一对多、多对多查询
上一节直通车
级联类型
参考文章:
JPA概念解析:CascadeType(各种级联操作)详解
Lombok注解引发的问题
@Data
在JPA中要慎用,因为重写的toString、equals、hashCode都会将关联的实体类对象包含进去,导致执行查询时循环调用到栈溢出
建议:
- 面对一对多的情况(即类中包含其他类的集合结果集的),不使用
@Data
、@EqualsAndHashCode
、@ToString
,重写toString、equals和hashCode - 遇到equals和hashCode的问题时,依然使用
@EqualsAndHashCode
,但要通过属性exclude去掉集合类型的属性,例如:@EqualsAndHashCode(exclude = {"blogs","roles"})
。
User——用户实体(对应多个Blog、多个Role)
/**
* @Description 用户表
* @author Evad.Wu
* @date 2022-06-16
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(exclude = {"blogs","roles"})
@Accessors(chain = true)
@Entity
@Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
@Column(name = "id")
private Long id;
@Column(name = "username")
private String username;
@Column(name = "avatar")
private String avatar;
@Column(name = "email")
private String email;
@Column(name = "password")
private String password;
@Column(name = "status")
private Integer status;
@Column(name = "created")
private Date created;
@Column(name = "last_login")
private Date lastLogin;
@Column(name = "admin")
private boolean admin;
@Column(name = "telephone")
private String telephone;
@Column(name = "identification")
private String identification;
@Column(name = "introduction")
private String introduction;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private Set<Blog> blogs;
@ManyToMany(targetEntity = Role.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinTable(name = "tb_user_role",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", avatar='" + avatar + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
", status=" + status +
", created=" + created +
", lastLogin=" + lastLogin +
", admin=" + admin +
", telephone='" + telephone + '\'' +
", identification='" + identification + '\'' +
", introduction='" + introduction + '\'' +
'}';
}
}
Blog——博客实体(对应一个User)
/**
* @Description 博客表
* @author Evad.Wu
* @date 2022-06-17
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(exclude = {"reviewSet","user"})
@Accessors(chain = true)
@Entity
@Table(name = "blog")
public class Blog implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "user_id")
private Long userId;
@Column(name = "examine_id")
private String examineId;
@Column(name = "title")
private String title;
@Column(name = "description")
private String description;
@Column(name = "content")
private String content;
@Column(name = "created")
private Date created;
@Column(name = "status")
private Integer status;
@Column(name = "index_img")
private String indexImg;
@Column(name = "reading")
private Long reading;
@ManyToOne(targetEntity = User.class,cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name = "user_id",insertable = false,updatable = false)
private User user;
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", userId=" + userId +
", examineId='" + examineId + '\'' +
", title='" + title + '\'' +
", description='" + description + '\'' +
", content='" + content + '\'' +
", created=" + created +
", status=" + status +
", indexImg='" + indexImg + '\'' +
", reading=" + reading +
'}';
}
}
Role——角色实体(对应多个User)
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EqualsAndHashCode(callSuper = false)
@Builder
@Entity
@Table(name = "tb_role")
public class Role extends BaseModel implements Serializable {
private static final long serialVersionUID = 2480455193854374245L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "role_code")
private String roleCode;
@Column(name = "role_name")
private String roleName;
}
BaseModel——基本审计属性
@Data
@MappedSuperclass
@DynamicInsert
@EntityListeners(AuditingEntityListener.class)
public class BaseModel implements Serializable {
private static final long serialVersionUID = -1152053885220920998L;
/**
* 创建人
*/
@CreatedBy
@Column(name = "creator", updatable = false, length = 64)
private String creator;
/**
* 创建时间
*/
@CreatedDate
@Column(name = "create_time", updatable = false)
private Date createTime;
/**
* 修改人
*/
@LastModifiedBy
@Column(name = "modifier", length = 64)
private String modifier;
/**
* 修改时间
*/
@LastModifiedDate
@Column(name = "modify_time")
private Date modifyTime;
/**
* 数据可见
*/
@Column(name = "visible",columnDefinition = "TINYINT DEFAULT 1")
@ColumnDefault("1")
private Boolean visible;
/**
* 版本
*/
@Version
@Column(name = "rec_ver")
private Long recVer;
}
一对一、一对多(多对一)
区别只是类型是单个对象还是集合对象
User类
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private Set<Blog> blogs;
Blog类:
@ManyToOne(targetEntity = User.class,cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name = "user_id",insertable = false,updatable = false)
private User user;
一对多要相互依赖,首先有多对一,这里Blog类中需要定义一个User,并添加注解@JoinColumn
,指定name关联的字段。
然后User类中需要定义Blog集合,接收多个Blog对象
注意:insertable和updatable都置为false,因为Blog类中的userId和User类中的id都指向同一个数据库字段,这样就会导致级联更新时重复修改而报错。
测试代码:一对一
/**
* 一对多 1: User N: Blog
*/
@Test
public void oneMore() {
List<Long> userIds = new ArrayList<>();
userIds.add(2L);
userIds.add(4L);
List<User> userList = userRepository.findAllById(userIds);
for (User user : userList) {
System.out.println("user: " + user);
Set<Blog> blogs = user.getBlogs();
for (Blog blog : blogs) {
System.out.println("blog: " + blog);
}
}
}
测试代码:多对一
/**
* 多对一 N: Blog 1: User
*/
@Test
public void moreOne() {
List<Long> blogIds = new ArrayList<>();
blogIds.add(9L);
blogIds.add(10L);
List<Blog> blogList = blogRepository.findAllById(blogIds);
for (Blog blog : blogList) {
System.out.println("blogId: " + blog.getId());
System.out.println("user: " + blog.getUser());
}
}
多对多
Role
/**
* @Description 角色
* @author Evad.Wu
* @date 2022-06-21
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(exclude = {"reviewSet","user"})
@Entity
@Table(name = "tb_role")
public class Role {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "user_id")
private Long userId;
@Column(name = "role_name")
private String roleName;
@Override
public String toString() {
return "Role{" +
"id='" + id + '\'' +
", userId=" + userId +
", roleName='" + roleName + '\'' +
'}';
}
}
User
@ManyToMany(targetEntity = Role.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinTable(name = "tb_user_role",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles;
- 多对多不需要互相依赖,只要在需要使用的一方添加即可
- 多对多是需要中间表来连接两端的,需要添加@JoinTable注解,name为中间表的名字,如果数据库中没有则会创建
- joinColumns、inverseJoinColumns中
@JoinColumn
的name属性对应中间表中两端的连接字段,referencedColumnName则是每张表中用于连接的字段
测试代码:多对多
/**
* 多对多:N: User N: Role / N: Role N: User
*/
@Test
public void userRoleM2M() {
Optional<User> userOptional = userRepository.findById(51L);
User user = userOptional.get();
System.out.println("userId: " + user.getId());
Set<Role> roles = user.getRoles();
for (Role role : roles) {
System.out.println("role: " + role);
}
}