范式
即不同的规范要求,在设计关系型数据库时,要遵从不同的规范要求,设计出合理的关系型数据库。
第一范式【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);只能保存一方
级联删除
可能会遇到的各种情况
-
级联删除:删除一方,然后级联删除多方(危险操作)
-
从一方去删除一个解除关系(外键的值为null)的多方:先获取一方,在删除一个多方
-
从一方去删除所有解除关系的多方:先获取一方,在删除所有多方
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();
}
}