JPA继续探索

本文深入探讨了JPA中的关系映射,包括第一、二、三范式,以及单向一对多和级联操作。重点讨论了级联保存的目的、配置以及可能遇到的问题,如级联删除的危险性。同时,文章还涵盖了单向多对多的映射配置和级联保存,强调了在双向多对多关系中正确保存的策略以及级联删除的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

范式

即不同的规范要求,在设计关系型数据库时,要遵从不同的规范要求,设计出合理的关系型数据库。

第一范式【1NF】:

具备原子性(最小单元,不可分割),主要是指表里的列,一个列一旦被创建后就不能再分开,一个列只能存放一个值。

第二范式【2NF】:

设计的表里面都应该要有一个唯一标识来区分表里每一行的数据,即主键。

第三范式【3NF】:

一个表里面尽量不要存放其他表的非关键性信息,即再表里存放外键。

单向一对多

1.1. 映射配置

@Entity
public class Product {//多方
  @Id
  @GeneratedValue
  private Long id;
  private String name;

@Entity
public class ProductDir {//一方
  @Id
  @GeneratedValue
  private Long id;
  private String name;
  @OneToMany
  // 必须配置外键id,否则会多生成一张表,形成多对多的关系
  @JoinColumn(name = "dir_id")//设置外键
  // 建议实例化,用的时候不需要在实例化,这里和单向多对一要求不同
  private Set<Product> products = new HashSet<Product>();  

结论:

不论是先保存一方再保存多方,还是先保存多方再保存一方,使用一对多的时候,它都会至少执行5条SQL语句完成保存功能(如下)。我们一般不使用单向一对多(原因:性能太差)
//配置关系

		dir.getProducts().add(product1);
        dir.getProducts().add(product2);
        product1.setDir(dir);
        product2.setDir(dir);

Hibernate: insert into ProductDir (name) values (?)
Hibernate: insert into Product (name) values (?)
Hibernate: insert into Product (name) values (?)
Hibernate: update Product set dir_id=? where id=?
Hibernate: update Product set dir_id=? where id=?

获取代码

用延迟加载提高性能的

修改成FetchType.EAGER后,不管有没有找类型项目的商品,它都会马上发送SQL来去查询数据。

但是这种做法严重影响性能。–关联(join)查询影响性能

在配置映射关系的时候如果是@ManyToOne(fetch=FetchType.LAZY),@OneToMany(fetch=FetchType.LAZY) ,@ManyToMany(fetch=FetchType.LAZY) 都是默认使用延迟加载提高性能的。

只有Many在后面都是使用延迟加载获取数据。

集合映射

因为Hibernate在创建了集合对象后是使用了它的的集合类PersistentSet来接收数据的(而不是我们写的那个HashSet)。如果我们使用接口声明(如Set),那么它的集合类PersistentSet也是实现了这个接口。所以它可以把这个类赋给Set。 但是我们使用HashSet,它们关系是平级的,不能进行转换,所以就出现错误了。

结论:声明集合的时候必须使用接口

级联

操作一方,导致另一方受到影响

级联保存的目的:

我们直接保存商品类别(如:ProductDir,一方),就把它对应的商品(Product,多方)也一起保存起来

级联的配置:

映射配置:

@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "dir")
private Set<Product> products = new HashSet<Product>();

@OneToMany(cascade = CascadeType.PERSIST, mappedBy = “dir”)

1.mappedBy = "dir"表示一方的关系参照多方Prodcut属性dir来管理

2.cascade = CascadeType.PERSIST

3.必须两边都建立关系

 // 只能由一方建立到多方的关系(设置外键)
  dir.getProducts().add(product);
  dir.getProducts().add(product2);
  // 指建立多方到一方的关系
  product.setDir(dir);
  product2.setDir(dir);

4.entityManager.persist(dir);只能保存一方

级联删除

可能会遇到的各种情况

  1. 级联删除:删除一方,然后级联删除多方(危险操作)

  2. 从一方去删除一个解除关系(外键的值为null)的多方:先获取一方,在删除一个多方

  3. 从一方去删除所有解除关系的多方:先获取一方,在删除所有多方

1\级联删除:删除一方,然后级联删除多方(危险操作)

示例:删除商品分类(ProductDir,一方),它下面的所有商品(Product,多方)都要删除

​ 如果我们直接删除的话,就发现报一个错(外键关系不能删除)

Cannot delete or update a parent row: a foreign key constraint fails (jpa.product, CONSTRAINT FK_dt353uy1bfcml55ixct2txdu2 FOREIGN KEY (dir_id) REFERENCES productdir (id))

所以必须配置一个级联 cascade = CascadeType.REMOVE

@Test
    //删除一方,然后级联删除多方(危险操作)
    public void testDel() throws Exception{
        EntityManager entityManager = JPAUtils.getEntityManager();
        entityManager.getTransaction().begin();

        ProductDir dir = entityManager.find(ProductDir.class, 2L);
        entityManager.remove(dir);
        entityManager.getTransaction().commit();
        entityManager.close();
    }

2\从一方去删除一个解除关系(外键的值为null)的多方:先获取一方,在删除一个多方

//映射配置
// @OneToMany(mappedBy = "dir",orphanRemoval=true)
// orphanRemoval=true可以从一方删除解除关系(就是外键dir_id=null)的多方

@Test
public void delete2() throws Exception {
  EntityManager entityManager = JPAUtils.getEntityManager();
  entityManager.getTransaction().begin();

  ProductDir dir = entityManager.find(ProductDir.class, 1L);
  Product product = entityManager.find(Product.class, 1L);
  // 删除所有和当前对象解除关联关系的对象
  dir.getProducts().remove(product);

  entityManager.getTransaction().commit();
  entityManager.close();
}

3\从一方去删除所有解除关系的多方:先获取一方,在删除所有多方

// @OneToMany(mappedBy = "dir",orphanRemoval=true)
@Test
public void delete3() throws Exception {
  EntityManager entityManager = JPAUtils.getEntityManager();
  entityManager.getTransaction().begin();

  ProductDir dir = entityManager.find(ProductDir.class, 1L);
  // 删除所有和当前对象解除关联关系的对象
  dir.getProducts().clear();

  entityManager.getTransaction().commit();
  entityManager.close();
}

级联操作总结

想一下,能不能即支持级联操作删除,又支持级联保存

cascade = CascadeType.ALL

一般项目开发很少配置cascade

强级联CascadeType.ALL

如果要配,尽量为cascade = CascadeType.PERSIST

如果配置CascadeType.REMOVE,CascadeType.ALL包含级联删除,级联保存,是在删除的时候比较危险操作

单向多对多

我们保存三个用户(user),两个角色(role)来进来多对多的测试

一个用户拥有多个角色

反过来一个角色也可以拥有多个用户

相当于2个多对一叠加变成多对多,多了一张中间表user_role

级联保存

获取entityManager后运行就可以看到生成相应的表

//映射配置,其中一方设置
 	@ManyToMany
    //JoinTable中间表格,joinColumns列名,两个主键,两个外键
    @JoinTable(name = "user_role",joinColumns = @JoinColumn(name = "uid")
            ,inverseJoinColumns =@JoinColumn(name = "rid") )
    private Set<Role> roles = new HashSet<Role>();
双向多对多

//双向的正确保存思路:
//让多方来维护外外键,让一方放弃管理

//mappedBy=“dir” 让一方放弃管理外键,让多方来维护外键 类似单向的多对一

//角色配置
@Entity
@Table(name = "t_role")
public class Role {
  @Id
  @GeneratedValue
  private Long id;
  private String name;
  @ManyToMany
  @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "role_id") },inverseJoinColumns
 = {
      @JoinColumn(name = "user_id") })
  private Set<User> users = new HashSet`<User>();
@Entity
@Table(name = "t_user")
public class User {
  @Id
  @GeneratedValue
  private Long id;
  private String name;
  // 多对多:一个用户拥有多个角色
  // @ManyToMany注释表示Teacher是多对多关系的一端。
  // @JoinTable描述了多对多关系的数据表关系。name属性指定中间表名称,joinColumns定义中间表与Teacher表的外键关系。
  // 中间表Teacher_Student的Teacher_ID列是Teacher表的主键列对应的外键列
  @ManyToMany
  @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns= {
      @JoinColumn(name = "role_id") })//特别注意:多对多配置的时候,需要有关联表,双向关系一定让它们在外表的列名匹配
  private Set<Role> roles = new HashSet<Role>();
级联保存

User映射配置

@ManyToMany(cascade = CascadeType.PERSIST)

测试代码

// User:@ManyToMany(cascade = CascadeType.PERSIST)
@Test
public void persist2() throws Exception {
  // 保存2个用户,保存3个角色(5条)
  User user1 = new User("user1");
  User user2 = new User("user2");

  Role role1 = new Role("role1");
  Role role2 = new Role("role2");
  Role role3 = new Role("role3");

  // 保存中间表:建立用户到角色关系user1(role1,role2),user2(role1,role2,role3)(5条)
  user1.getRoles().add(role1);
  user1.getRoles().add(role2);

  user2.getRoles().add(role1);
  user2.getRoles().add(role2);
  user2.getRoles().add(role3);

  EntityManager entityManager = JPAUtils.getEntityManager();
  entityManager.getTransaction().begin();

  // 保存用户的同时,级联保存角色
  entityManager.persist(user1);
  entityManager.persist(user2);

  entityManager.getTransaction().commit();
  entityManager.close();
  System.out.println("------------------------");
}

删除user1(由Hibernate自动处理,先删除中间表,再删除user1)

// 注意:User对象是有管理中间表的权限的

@Test
public void delete1() throws Exception {

  EntityManager entityManager = JPAUtils.getEntityManager();
  entityManager.getTransaction().begin();

  User user = entityManager.find(User.class, 1L);
  entityManager.remove(user);

  entityManager.getTransaction().commit();
  entityManager.close();
}

1.1. 删除user1的所有包含角色(role1,role2),不能删除user1(只删除中间表)

// 注意:User对象是有管理中间表的权限的

@Test

public void delete2() throws Exception {

  EntityManager entityManager = JPAUtils.getEntityManager();
  entityManager.getTransaction().begin();

  User user = entityManager.find(User.class, 1L);
  user.getRoles().clear();
    
  entityManager.getTransaction().commit();
  entityManager.close();
}

1.1. 修改角色:先删除在添加

1.1.1. 测试代码

// 注意:User对象是有管理中间表的权限的
// 关系user1(role1,role2)->关系user1(role1,role3)
@Test
public void delete4() throws Exception {

  EntityManager entityManager = JPAUtils.getEntityManager();
  entityManager.getTransaction().begin();

  User user = entityManager.find(User.class, 1L);
  Role role2 = entityManager.find(Role.class, 2L);
  Role role3 = entityManager.find(Role.class, 3L);
  user.getRoles().remove(role2);
  user.getRoles().add(role3);
  
  entityManager.getTransaction().commit();
  entityManager.close();
}

1.1. 级联删除

1.1.1. 一边配置(危险)

// User:@ManyToMany(cascade = CascadeType.REMOVE)
//注意:User对象是有管理中间表的权限的
@Test
public void delete5() throws Exception {
  EntityManager entityManager = JPAUtils.getEntityManager();
  entityManager.getTransaction().begin();

  User user = entityManager.find(User.class, 1L);
  entityManager.remove(user);

  entityManager.getTransaction().commit();
  entityManager.close();
}

1.1.1. 二边配置(非常危险)

//User:@ManyToMany(cascade = CascadeType.REMOVE)
//Role:@ManyToMany(cascade = CascadeType.REMOVE)
//注意:User,Role对象是有管理中间表的权限的
  @Test
  public void delete6() throws Exception {
    EntityManager entityManager = JPAUtils.getEntityManager();
    entityManager.getTransaction().begin();

    User user = entityManager.find(User.class, 1L);
    entityManager.remove(user);
      
    entityManager.getTransaction().commit();
    entityManager.close();
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值