目录
ORMHibernate,JPA和SpringDataJpaJPA入门案列JPA的API介绍getOne和findOne的比较JPQL的基本使用specification的基本使用example的基本使用
多表设计
表之间的划分 ★ 分析步骤 ★多表设计(一对多) ★多表设计(多对多) ★对象导航查询
表之间的划分
数据库中多表之间存在着三种关系,如图所示
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
分析步骤
在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以今天我们的学习重点是:掌握配置实体之间的关联关系。
第一步:首先确定两张表之间的关系。
如果关系确定错了,后面做的所有操作就都不可能正确。
第二步:在数据库中实现两张表的关系
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射(重点)
一对多
例子
我们采用的示例为客户和联系人。
客户:指的是一家公司,我们记为A。
联系人:指的是A公司中的员工。
在不考虑兼职的情况下,公司和员工的关系即为一对多。
表关系建立
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
创建数据库表
CREATE TABLE cst_customer
(
cust_id
bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
cust_name
varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
cust_source
varchar(32) DEFAULT NULL COMMENT '客户信息来源',
cust_industry
varchar(32) DEFAULT NULL COMMENT '客户所属行业',
cust_level
varchar(32) DEFAULT NULL COMMENT '客户级别',
cust_address
varchar(128) DEFAULT NULL COMMENT '客户联系地址',
cust_phone
varchar(64) DEFAULT NULL COMMENT '客户联系电话',
PRIMARY KEY (`cust_id
`)
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8
;
CREATE TABLE cst_linkman
(
lkm_id
bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
lkm_name
varchar(16) DEFAULT NULL COMMENT '联系人姓名',
lkm_gender
char(1) DEFAULT NULL COMMENT '联系人性别',
lkm_phone
varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
lkm_mobile
varchar(16) DEFAULT NULL COMMENT '联系人手机',
lkm_email
varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
lkm_position
varchar(16) DEFAULT NULL COMMENT '联系人职位',
lkm_memo
varchar(512) DEFAULT NULL COMMENT '联系人备注',
lkm_cust_id
bigint(32) NOT NULL COMMENT '客户id(外键)',
PRIMARY KEY (`lkm_id
`),
KEY `FK_cst_linkman_lkm_cust_id
` (`lkm_cust_id
`),
CONSTRAINT `FK_cst_linkman_lkm_cust_id
` FOREIGN KEY (`lkm_cust_id
`) REFERENCES `cst_customer
` (`cust_id
`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
;
创建实体类映射
Customer
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name
="cst_customer")
public class Customer implements Serializable {
@Id
@GeneratedValue(strategy
= GenerationType
.IDENTITY
)
@Column(name
="cust_id")
private Long custId
;
@Column(name
="cust_name")
private String custName
;
@Column(name
="cust_source")
private String custSource
;
@Column(name
="cust_industry")
private String custIndustry
;
@Column(name
="cust_level")
private String custLevel
;
@Column(name
="cust_address")
private String custAddress
;
@Column(name
="cust_phone")
private String custPhone
;
@OneToMany(mappedBy
= "customer",cascade
=CascadeType
.ALL
,fetch
=FetchType
.LAZY
)
private Set
<LinkMan> linkMans
=new HashSet<>();
@Override
public String
toString() {
return "Customer{" +
"custId=" + custId
+
", custName='" + custName
+ '\'' +
", custSource='" + custSource
+ '\'' +
", custIndustry='" + custIndustry
+ '\'' +
", custLevel='" + custLevel
+ '\'' +
", custAddress='" + custAddress
+ '\'' +
", custPhone='" + custPhone
+ '\'' +
", linkMans=" + linkMans
+
'}';
}
}
LinkMan
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name
="cst_linkman")
public class LinkMan implements Serializable {
@Id
@GeneratedValue(strategy
=GenerationType
.IDENTITY
)
@Column(name
="lkm_id")
private Long lkmId
;
@Column(name
="lkm_name")
private String lkmName
;
@Column(name
="lkm_gender")
private String lkmGender
;
@Column(name
="lkm_phone")
private String lkmPhone
;
@Column(name
="lkm_mobile")
private String lkmMobile
;
@Column(name
="lkm_email")
private String lkmEmail
;
@Column(name
="lkm_position")
private String lkmPosition
;
@Column(name
="lkm_memo")
private String lkmMemo
;
@ManyToOne(targetEntity
= Customer
.class,cascade
=CascadeType
.ALL
)
@JoinColumn(name
="lkm_cust_id")
private Customer customer
;
@Override
public String
toString() {
return "LinkMan{" +
"lkmId=" + lkmId
+
", lkmName='" + lkmName
+ '\'' +
", lkmGender='" + lkmGender
+ '\'' +
", lkmPhone='" + lkmPhone
+ '\'' +
", lkmMobile='" + lkmMobile
+ '\'' +
", lkmEmail='" + lkmEmail
+ '\'' +
", lkmPosition='" + lkmPosition
+ '\'' +
", lkmMemo='" + lkmMemo
+ '\'' +
'}';
}
}
创建数据库操作类
@Repository
public interface CustomerRepository extends JpaRepository<Customer,Long>, JpaSpecificationExecutor
<Customer> {
}
@Repository
public interface LinkManRepository extends JpaRepository<LinkMan,Long>, JpaSpecificationExecutor
<LinkMan> {
}
注解说明
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
操作
@Test
@Transactional(rollbackFor
= Exception
.class)
@Rollback(false)
public void testSave(){
Customer customer
=new Customer();
customer
.setCustName("百度");
LinkMan linkMan
=new LinkMan();
linkMan
.setLkmName("小李");
linkMan
.setCustomer(customer
);
this.customerRepository
.save(customer
);
this.linkManRepository
.save(linkMan
);
}
删除
删除从表数据:可以随时任意删除
删除主表数据
有从表数据
1). 在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
2). 如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
3). 如果还想删除,使用级联删除引用
没有从表数据引用:随便删
级联操作
级联操作:指操作一个对象同时操作它的关联对象
使用方法:只需要在操作主体的注解上配置cascade
cascade:配置级联操作
CascadeType.MERGE 级联更新CascadeType.PERSIST 级联保存:CascadeType.REFRESH 级联刷新:CascadeType.REMOVE 级联删除:CascadeType.ALL 包含所有
@OneToMany(mappedBy
= "customer",cascade
=CascadeType
.ALL
,fetch
=FetchType
.LAZY
)
private Set
<LinkMan> linkMans
=new HashSet<>();
级联删除
@Test
@Transactional(rollbackFor
= Exception
.class)
@Rollback(false)
public void testRemove(){
Customer customer
= this.customerRepository
.getOne(28L
);
this.customerRepository
.delete(customer
);
}
出现的一个错误
👉 解决办法:SpringDataJpa在一对多、多对多关系的级联操作时出现StackOverflowError(是真滴坑)
多对多
例子
我们采用的示例为用户和角色
用户:指的是咱们班的每一个同学。
角色:指的是咱们班同学的身份信息。
比如A同学,它是我的学生,其中有个身份就是学生,还是家里的孩子,那么他还有个身份是子女。
同时B同学,它也具有学生和子女的身份。
那么任何一个同学都可能具有多个身份。同时学生这个身份可以被多个同学所具有。
所以我们说,用户和角色之间的关系是多对多。
表关系建立
多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:
创建实体类
Role
@Entity
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table(name
="sys_role")
public class Role {
@Id
@GeneratedValue(strategy
= GenerationType
.IDENTITY
)
@Column(name
="role_id")
private Long roleId
;
@Column(name
="role_name")
private String roleName
;
@ManyToMany(mappedBy
= "roles",cascade
= CascadeType
.ALL
)
private Set
<User> users
=new HashSet<>();
@Override
public String
toString() {
return "Role{" +
"roleId=" + roleId
+
", roleName='" + roleName
+ '\'' +
'}';
}
}
User
@Entity
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table(name
="sys_user")
public class User {
@Id
@GeneratedValue(strategy
= GenerationType
.IDENTITY
)
@Column(name
="user_id")
private Long userId
;
@Column(name
="user_name")
private String userName
;
@Column(name
="age")
private Integer age
;
@ManyToMany(targetEntity
= Role
.class,cascade
= CascadeType
.ALL
)
@JoinTable(name
="sys_user_role",
joinColumns
= {@JoinColumn(name
="sys_user_id",referencedColumnName
= "user_id")},
inverseJoinColumns
= {@JoinColumn(name
="sys_role_id",referencedColumnName
= "role_id")})
private Set
<Role> roles
=new HashSet<>();
@Override
public String
toString() {
return "User{" +
"userId=" + userId
+
", userName='" + userName
+ '\'' +
", age=" + age
+
", roles=" + roles
+
'}';
}
}
创建数据库操作类
@Repository
public interface RoleRepository extends JpaRepository<Role,Long>, JpaSpecificationExecutor
<Role> {
}
@Repository
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor
<User> {
}
注解说明
@ManyToMany
作用:用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。
@JoinTable
作用:针对中间表的配置
属性:
name:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
操作
@Test
@Transactional(rollbackFor
= Exception
.class)
@Rollback(false)
public void testSave(){
User user
=new User();
user
.setUserName("小李");
Role role
=new Role();
role
.setRoleName("java程序员");
user
.getRoles().add(role
);
this.userRepository
.save(user
);
this.roleRepository
.save(role
);
}
注意
如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃。
将
@ManyToMany(targetEntity
= User
.class,cascade
= CascadeType
.ALL
)
@JoinTable(name
="sys_user_role",
joinColumns
= {@JoinColumn(name
="sys_role_id",referencedColumnName
= "role_id")},
inverseJoinColumns
= {@JoinColumn(name
="sys_user_id",referencedColumnName
= "user_id")})
private Set
<User> users
=new HashSet<>();
改为:
@ManyToMany(mappedBy
= "roles",cascade
= CascadeType
.ALL
)
private Set
<User> users
=new HashSet<>();
级联删除
@Test
@Transactional(rollbackFor
= Exception
.class)
@Rollback(false)
public void testDelete(){
this.userRepository
.deleteById(1L
);
}