Jpa除了单表操作,还有就是常见的一对多和多对多了。。

下面来两个例子。。。

1. 一对多

这个例子是一个用户有多个文章

springboot3 jpa 动态参数绑定 springboot配置jpa_set方法

1. 1 实体类

主表:User

@Entity
@Table(name = "t_user")
public class User implements Serializable {
    private static final long serialVersionUID = -5777961600230089298L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer id;
    private String username;
    private String password;
    private String nickname;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date updateTime = new Date();
    /**
     * 1代表删除,0代表没有删除
     */
    private String deleteStatus = "0";

    @OneToMany(targetEntity = Article.class)
    //设置外键
    @JoinColumn(name = "t_user_id",referencedColumnName = "user_id") 
    private List<Article> articles;
    
    //get,set方法省略 。。。

@JoinColumn: 是设置一个外键,name是外键的名字,referencedColumnName是主表的主键字段名称

从表:Article

@Entity
@Table(name = "t_article")
public class Article implements Serializable {
    private static final long serialVersionUID = 4272333391193664556L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "article_id")
    private Integer id;
    private String content;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "+8:00")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "+8:00")
    private Date updateTime = new Date();
    /**
     * 1代表删除,0代表没有删除
     */
    private String deleteStatus = "0";

    @ManyToOne(targetEntity = User.class)
    private User user;

运行就可以得到,这就得到了两张表了。

springboot3 jpa 动态参数绑定 springboot配置jpa_User_02

2. 多对多

还是上面的那个User表

@Entity
@Table(name = "t_user")
public class User implements Serializable {
    private static final long serialVersionUID = -5777961600230089298L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer id;
    private String username;
    private String password;
    private String nickname;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date updateTime = new Date();
    /**
     * 1代表删除,0代表没有删除
     */
    private String deleteStatus = "0";

    @OneToMany(targetEntity = Article.class)
    @JoinColumn //外键
    private List<Article> articles;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "t_user_role",
    joinColumns = {@JoinColumn(name = "t_user_id")},inverseJoinColumns = {@JoinColumn(name = "t_role_id")})
    private List<Role> roles;
    
	//get,set方法省略

@JoinTable:多对多必须要维护一个中间表,而中间表有两个属性分别对应了主表的id和从表的id。当然这个名字可以自己取。

从表:Role

@Entity
@Table(name = "t_role")
public class Role implements Serializable {
    private static final long serialVersionUID = -5711260965444546791L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Integer id;
    private String roleName;
    /**
     * 角色具体描述
     */
    private String content;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date updateTime = new Date();
    /**
     * 1代表删除,0代表没有删除
     */
    private String deleteStatus = "0";

    @ManyToMany(mappedBy = "roles")
    private List<User> users;
//  get,set方法省略

@ManyToMany: 这里有一个mapperBy,值是主表对应的的属性名。同时也意味着,该表放弃维护中间表,中间表由主表User来维护。

当然,一个User对应了多个Role,一个Role也对应了多个User,
谁是主表谁是从表,取决于谁被调用。

运行一下Springboot,可以得到三张表

springboot3 jpa 动态参数绑定 springboot配置jpa_set方法_03

再加一张表试试。。。。QAQ

一个用户(User)可以有多个角色 (Role),一个 角色可以有多个权限(Permission)

所以这里加入一张权限表(Permission)

既然加入了权限表,那么角色表Role也要改动了

Role表

@Entity
@Table(name = "t_role")
public class Role implements Serializable {
    private static final long serialVersionUID = -5711260965444546791L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Integer id;
    private String roleName;
    /**
     * 角色具体描述
     */
    private String content;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date updateTime = new Date();
    /**
     * 1代表删除,0代表没有删除
     */
    private String deleteStatus = "0";

    @JsonIgnore
    @ManyToMany(mappedBy = "roles")
    private List<User> users;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "t_role_permission",
    joinColumns = {@JoinColumn(name = "t_role_id")},
    inverseJoinColumns = @JoinColumn(name = "t_permission_id"))
    private List<Permission> permissions;
// 忽略get,set方法

在这次角色(Role)与权限表 (Permission)中,我让Role当主表,也就是Role来维护中间表,Permission表放弃维护权。

Permission表

@Entity
@Table(name = "t_permission")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "permission_Id")
    private Integer id;
    private String permissionName;
    private String content;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "+8:00")
    private Date updateTime = new Date();
    /**
     * 1代表删除,0代表没有删除
     */
    private String deleteStatus = "0";
	
	@JsonIgnore
    @ManyToMany(mappedBy = "permissions")
    private List<Role> roles;
//忽略get,set方法
}

@JsonIgnore: 这个是spring的一个注解,意思是转成json的时候忽略这个字段,可以防止在controller调用的时候出现栈溢出

再次启动Springboot,
可以看到Jpa为我们创建了两张中间表、总共是五张表

springboot3 jpa 动态参数绑定 springboot配置jpa_User_04

3. 最后决定来测试一下

先简单的插入一些数据

@SpringBootTest
class BootjpaApplicationTests {
    @Autowired
    private PermissionDao permissionDao;
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;

    @Test
    @Transactional
    @Rollback(value = false)
    void contextLoads() {
        //添加和查找权限
        Permission permission1 = new Permission();
        permission1.setContent("添加");
        permission1.setPermissionName("add");
        permission1.setCreateTime(new Date());

        Permission permission2 = new Permission();
        permission2.setContent("查找");
        permission2.setPermissionName("query");
        permission2.setCreateTime(new Date());

        ArrayList<Permission> permissions = new ArrayList<>();
        permissions.add(permission1);
        permissions.add(permission2);

        //添加两个权限
        permissionDao.saveAll(permissions);

        Role role = new Role();
        role.setContent("只有添加和查找的管管理");
        role.setRoleName("C管理员");
        role.setCreateTime(new Date());
        role.setPermissions(permissions);

        Role role2 = new Role();
        role2.setContent("只有看看");
        role2.setRoleName("游客");
        role2.setCreateTime(new Date());
        role2.setPermissions(new ArrayList<Permission>(){{add(permission2);}});

        ArrayList<Role> roles = new ArrayList<>();
        roles.add(role);
        roles.add(role2);

        //添加两个角色
        roleDao.saveAll(roles);

        User user = new User();
        user.setUsername("liliya");
        user.setPassword(SecureUtil.md5("123"));
        user.setNickname("莉莉娅");
        user.setCreateTime(new Date());
        user.setRoles(roles);

        //添加这个用户
        userDao.save(user);


    }

接下来在Controller层搞一手打印。。。

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserDao userDao;
	 @GetMapping("/1")
    public Optional<User> queryById(){
         return userDao.findById(1);
    }
}

注意事项: 在springboot中事务是不会提交的,所以可以想我一样加入 @Transactional , @Rollback(value = false)这两个注解

springboot3 jpa 动态参数绑定 springboot配置jpa_set方法_05

出现了bug,本来这个地方是两个角色,但是给我查出三个角色了。。

有两个是相同的,所以建议还是用set,不用list。。

对于同一个User查出了两个一模一样的Role,

springboot3 jpa 动态参数绑定 springboot配置jpa_List_06


但是查看中间表的时候是没有问题 。。

springboot3 jpa 动态参数绑定 springboot配置jpa_List_07

感觉这个是Jpa的bug,,

换了一个语句就好了。。。

在dao层换了这个。。

springboot3 jpa 动态参数绑定 springboot配置jpa_List_08

这波就OK了。。。

springboot3 jpa 动态参数绑定 springboot配置jpa_set方法_09

最后

关于要注意的

  1. 如果是在测试类中使用,建议加上 @Transactional @Rollback(value = false)
  2. 重写toString方法打印的时候,注意一下理关系,很容易就出现栈溢出了,
  3. 还有一个是@JsonIgnore,这个放到从表的集合上就好了,防止jpa反复查询带来的栈溢出。。