JPA定义了一系列对象持久化的标准。
(零)配置
Maven导入以下包:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
在application.yml进行以下配置:
spring:
profiles:
active: dev
#进行MySQL数据库配置
datasource:
#数据库驱动名
#MySQL8以前版本使用com.mysql.jdbc.Driver
#MySQL8以后版本使用下列驱动:
driver-class-name: com.mysql.cj.jdbc.Driver
#数据库要链接的url
#serverTimezone不是必选项,但某些时候会报错,详细原因自行百度
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
#用户名与密码
username: root
password: s14568460s
#JPA配置
jpa:
hibernate:
#该项决定项目运行时以何种方式建表。
#create:启动时删数据库中的表,然后创建,退出时不删除数据表
#create-drop:启动时删数据库中的表,然后创建,退出时删除数据表。如果表不存在报错
#update:如果启动时表格式不一致则更新表,原有数据保留
#validate:项目启动表结构进行校验,如果不一致则报错
ddl-auto: create
#JPA更新某个版本后,使用@GeneratedValue时做不到自增,加上这个就好了
#看不懂但是大为震撼
#也可以在@GeneratedValue中设置strategy = GenerationType.IDENTITY,就可以忽略下面被注释掉的这句了
#use-new-id-generator-mappings: false
#在控制台显示SQL语句
show-sql: true
(一)JPA的简单使用(CRUD)
可以看下面两个文章理解,比我讲得好多了:springDataJpa的入门操作(基本CRUD)_tangiwang的博客-CSDN博客
Spring Boot 2.x 之 Spring Data JPA, Hibernate 5 - CokeCode - 博客园 (cnblogs.com)
博客园的那篇文章讲各个注解讲的很细,强烈建议阅读。
记得每次在方法内执行完增改后要对JpaRepository类型的对象调用save方法。save的参数为new的表。删直接调用dao层的delete方法就好。
(二)@Query注解
@Query注解可以指定JpaRepository方法对应的查询HSQL。
@Query中的参数HQL默认是面向对象的语句,与真实SQL语句有偏差。在该注解下,应当使用实体类的类名和属性名,而非SQL库中的表名和字段名。
想要在@Query输入原生SQL语句,应将其中参数nativeQuery
设置为true。
@Query也可以实现更新与删除:@Query 自定义查询及更新删除_Aimyone的博客-CSDN博客_query 删除,但实现不了insert: springData学习(二)@Query注解详解_java_chegnxuyuan的博客-CSDN博客。
(三)对象关联关系
对象之间的关联有一对一、一对多和多对多。
一对一:一个人对应一个ID
一对多:一个班对多个人/一个人对多门课
多对多:多个项目对应多个员工
抽象成数据库中的表,我们不难发现对象关联关系的实用性,如某表中的字段可能对应另一个表整表。
接下来讲讲具体操作。
0、键的链接
见下文:数据库设计(一对一、一对多、多对多)皮一下很开心的猴头-CSDN博客数据库一对一
1、一对多/多对一
假设有以下两表:
Person:id(主键),name,age
Class:classId(主键),className
接下来对二者的classId进行链接。
//在Class类下,建立一个属性personList,使得一个班级对应多人
@OneToMany(mappedBy = "class") //mappedBy用于创建一对多的映射关系,其值为Person类中对应的属性名称
//mappedBy声明自己不是关系维护端,一切由对方维护
private List<Person> personList;
//在Person类下,建立一个class属性,使得多人对应一个班级
@ManyToOne
@JoinColumn(name = "cid") //指定外键列,name默认值为变量名
private Class class;
(1)各自的增更,与之前的CRUD操作相同。
(2)主键表查询(查询Class中personList):有下面两种方法。
①设置立即加载:调用classRespository.findAll(),在默认情况下实行懒加载策略,即不查询外部链接的库,只查询自身的字段。针对此,我们可以在@OneToMany
中将fetch属性(默认值为FetchType.LAZY
)改为FetchType.EAGER
。
②延长Session生命周期:声明事务@Transactional。
(3)外键表查询(查询Person中的class):与CRUD操作相同,因为其为立即加载,并非上述的懒加载。
(4)删除主键表(删除Class类中某一项):直接删除会报错,因为Person类中的class还在链接。有以下解决方法:
①级联删除:在Class类中的OneToMany中对属性cascade(默认空)设置CascadeType.ALL。关于级联的具体操作可以看Hibernate @OneToMany 及 @Cascade级联操作 - 云+社区 - 腾讯云 (tencent.com)。
②断开键的连接,再删除主键表:先用update将外键设置为null,断开主外键连接,再执行删除操作(会报no session,需添事务延长session周期)。
(5)删除外键表(删除Person类中某一项):直接删除即可。
这里有一个问题:@OneToMany
中fetch设置为FetchType.EAGER
时会导致只输出两条SELECT语句而无法删除Person中的某项,解决方法是删除这句或在@ManyToOne
中设置此项为LAZY(摘自JPA关于fetch=FetchType.EAGER级联删除的问题_C.-CSDN博客
)。
(6)添加主键表信息同时添加外键表信息(添加Class同时添加Person):注意双向维护,主键和外键都需要添加。后续更新:若报错,应关注瞬时态对象与持久态对象,下文有提到。
2、多对多
假设有以下两表:
Subject: sId(主键), sName
Person: pId(主键), pName
接下来对两表进行链接。注意的是,只应当指定一个表进行主键的维护,防止主键重复。
//在Subject类下,建立一个属性pList,使得一门课对应多人
@ManyToMany(mappedBy = "sList") //Subject类放弃外键维护权
private List<Person> pList = new ArrayList<>();
//在Person类下,建立一个属性sList,使得一人对应多门课
@ManyToMany
@JoinTable(name = "sub_person",
uniqueConstraints = {@UniqueConstraint(columnNames = {"p_id", "s_id"})},
joinColumns = {@JoinColumn(name="p_id", referencedColumnName="pId")},
inverseJoinColumns = {@JoinColumn(name="s_id", referencedColumnName="sId")})
//@JoinTable描述当前实体类与中间表之间的关系
//name值为中间表名
//uniqueConstraints中间两个字段名(非属性名)组合起来组成联合主键,其余一大坨照抄(注解相互嵌套不想解释了)
//joinColumns表示当前对象在中间表中的外键,name为外键名,referencedColumnName为参照主键表的主键名称
//inverseJoinColumns表示对方在中间表的外键,与joinColumns类似,不再赘述。
private List<Subject> sList = new ArrayList<>();
Subject通过mappedBy属性的设置放弃了外键维护权利,以后就由Person表维护两表关系了。
看着复杂,其实就是一对多的补充扩展。
(不过确实好难理解55555)
接下来是CRUD。
(1)添加操作:
首先new几个Subject对象和Person对象。
在Subject类直接添加Person到pList并对subjectDao进行save:能正常运行只能添加Subject,中间表和Person无法添加,因为pList加了mappedBy属性,不负责外键维护。被维护的表不需要向键内添加任何数据。
在Person类直接添加Subject到sList并对personDao进行save:编译错误。现在Subject没有受session管理而Person受session管理,直接save不能成功(具体原因:瞬时态对象异常。仅进行personDao.save(person)时,person为持久态对象,subject仍保留为瞬时态对象)。
有两种方法实现,①若既要添加新学生又要建立学生与课的关系,就把Subject交给session管理,即:进行personDao.save(person)前,进行subjectDao.save(subject)(将subject变为持久态对象);②若表内有学生,仅需建立学生与课的关系,可通过subjectDao.getOne取出对象并交给属性引用,即Subject sub = subjectDao.getOne(1)
(Dao从数据库查询出来sub)然后再直接添加。
(2)更新操作:仅能通过有外键维护权利(此文为Person)进行两表关系的更新,各自更新各自操作(与添加极其类似)。
(3)查询操作:Person查询sList,与一对多类似,需要设置立即加载,或延长session生命周期;Subject只能查询自己的属性,不能查询pList。
(4)删除操作:删除Person将从Person表删除学生数据,并从中间表删除学生与课的关系;直接删除Subject会失败(可能在中间表被引用)。
3、一对一
假设有以下两表:
Boy: boyName
Girl: girlName
对两表进行连接:
//在Girl类
@OneToOne(mappedBy = "girl")
private Boy boy;
//在Boy类
@OneToOne
@JoinColumn(name = "girlName", unique = true)
//unique表示外键唯一
private Girl girl;
不翻译了,根据多对多理解就行。
(1)添加操作:正常添加即可。注意应从维护端维护键的关系,且应关注瞬时态对象与持久态对象的关系。
(2)更新操作:正常更新即可。涉及两表关系应从维护端更新。
(3)查询操作:与上文一对多、多对多类似。
(4)删除操作:与上文类似。注意,若被维护端有引用(Girl中boy属性不为空),无法直接删除。
(四)延长Session生命周期
上文在被维护端进行查询和删除过程中都会报no session,我们需要添加一个注解@Transactional开启事务。
以一对多删除Subject为例。
public class SubjectService{
@AutoWired
SubjectDao subjectDao;
@AutoWired
PersonDao personDao;
@Transactional //开启事务,延长session生命周期到service层
public void deleteSubjectWithStudent(){
//删除所有课程与学生
List<Subject> subjects = subjectDao.findAll;
for(Subject Subject : subjects){
List<Person> persons = subject.getPersonList();
for(Person person : persons){
personDao.delete(person);
}
subject.setPersonList(null);
subjectDao.deleteById(subject);
}
}
}
这样可以做到将session的生命周期延长到service层。但要延长到view层,我们需要使用过滤器OpenSessionInViewFIlter
(SpringBoot配置好了)。只需导入spring-boot-starter-web即可。