您现在的位置是:首页 >技术教程 >Spring Data JPA|至尊荣耀篇网站首页技术教程
Spring Data JPA|至尊荣耀篇
?作者简介:练习时长两年半的Java up主
?个人主页:程序员老茶
? ps:点赞?是免费的,却可以让写博客的作者开兴好久好久?
?系列专栏:Java全栈,计算机系列(火速更新中)
? 格言:种一棵树最好的时间是十年前,其次是现在
?动动小手,点个关注不迷路,感谢宝子们一键三连
目录
课程名:Java
内容/作用:知识点/设计/实验/作业/练习
学习:Java
Spring Data JPA
2001年推出了Hibernate,是一个全自动ORM框架,可以不用编写SQL语句,就能实现对数据库的操作。
SUN公司在Hibernate的基础上,制定了Java Persistence API(Java持久化API),简称JPA,是一套访问操作数据库的规范,由一系列抽象类和接口组成。
后来Spring团队在SUN公司指定的JPA这套规范下,推出了Spring Data JPA,是JPA这套规范的具体实现。
如今常说的JPA,常指Spring Data JPA。
SpringBoot集成Spring Data JPA
1.创建SpringBoot项目,选择依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IcrGiSis-1685283233954)(Spring Data JPA.assets/image-20230522091237459.png)]
2.编辑配置文件,设置要连接的数据库信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
# 指定数据库类型
spring.jpa.database=mysql
# 打印sql语句
spring.jpa.show-sql=true
3.创建实体类
-
类上加@Entity注解,来自于javax.presistence包中
-
主键属性上加
- @Id标明主键
- **@GeneratedValue(strategy = GenerationType.IDENTITY)**设置MySQL数据库主键生成策略,数据库设置为自增
-
其他属性名与字段名一致或驼峰命名法
- 如果字段名和属性名不一致,使用**@Column(name=“字段名”)**注解指定该属性对应的字段名
@Entity
@Data
public class BookInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer bookId;
private Integer typeId;
private String bookName;
private String bookAuthor;
private Integer bookNum;
private Integer bookPrice;
@Column(name = "publisher_date")
private String date;
private String bookImg;
}
4.数据访问层接口
-
类上添加@Repository主键
-
继承JpaRepository<T,ID>接口,T表示实体类的类型,ID表示主键的数据类型
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {
}
5.测试常用方法
方法名 | 返回值 | 说明 |
---|---|---|
findAll() | List | 查询所有数据 |
findById(主键) | Optional | 根据主键查询,返回的对象调用isPresent()结果为true,表示查询到了数据,调用get()得到查询的数据 |
findAllById(主键集合) | List | 根据主键集合查询 |
save(T entity) | T | 添加或修改。如果参数对象中没有主键属性,执行添加,返回添加后的对象,会包含自增ID;如果参数对象中有主键属性且值存在,执行修改,返回修改后的对象。 |
delete(T entity) | void | 根据对象删除。如果对象中有主键属性且值存在,执行删除。 |
deleteAll(对象集合) | void | 根据对象集合删除。如果对象中有主键属性且值存在,执行删除。 |
JPA进阶
分页查询
调用数据访问层中**findAll(Pageable pageable)**方法,即可实现分页。
参数Pageable是一个接口,通过其实现类PageRequest的静态方法**of(int page,int size)**获取对象,
当做Pageable使用,page从0开始表示第一页。
@Test
void test4(){
//PageRequest.of(0, 5)PageRequest是Pageable的实现类,通过该构造方法创建对象,默认0表示第一页
Page<BookInfo> pageInfo = bookInfoDao.findAll(PageRequest.of(0, 5));
System.out.println("总记录数"+pageInfo.getTotalElements());
System.out.println("总页数"+pageInfo.getTotalPages());
System.out.println("当前页"+pageInfo.getNumber());
System.out.println("当前页显示的数量"+pageInfo.getSize());
System.out.println("分页查询到的数据集合"+pageInfo.getContent());
System.out.println("是否是首页"+pageInfo.isFirst());
System.out.println("是否是尾页"+pageInfo.isLast());
System.out.println("是否是有下一页"+pageInfo.hasNext());
System.out.println("是否是首上一页"+pageInfo.hasPrevious());
}
条件查询
在JPA中,使用自定义方法名自动生成对应的SQL语句,实现条件查询。
如定义了queryById(int id),表示根据id查询,生成select * from 表 where id=?
方法命名格式
[xxx] [By] [字段对应的属性名] [规则] [Or/And] [字段对应的属性名] [规则]…
-
xxx可以是find、get、query、search
-
方法如果有参数,参数的属性和方法名中的参数顺序一致
如findByBookNameAndBookAuthor(String bookName,String bookAuthor)
对应的sql语句为select * from book_info where book_name =? and book_author=?
常用规则
规则 | 方法名 | SQL中的条件 |
---|---|---|
指定值 | findByBookName(String name) | book_name = name |
Or/And | findByBookNameOrBookAuthor(String name,String author) | book_name=name or book_author=author |
After/Before | findByBookPriceAfter(double price) | book_price > price |
GreaterThanEqual/LessThanEqual | getByBookNumLessThanEqual(int num) | book_num <= num |
Between | getByBookPriceBetween(int a,int b) | book_price between a and b |
In/NotIn | getByBookAuthorIn(String… authors) getByBookAuthorIn(Collection authors) | book_author in (?,?,?..) |
IsNull/IsNotNull | queryByDateIsNull() | date is null |
Like/NotLike | queryByBookNameLike(String keyword) | book_name like ‘%keyword%’,实参为%keyword% |
Containts/NotContains | queryByBookNameContains(String keyword) | book_name like ‘%keyword%’,实参为keyword |
StartsWith/EndsWith | queryByBookAuthorStartsWith(String keyword) | book_author like ‘keyword%’ |
无条件排序:searchByOrderBy字段Desc/Asc | searchByOrderByBookId(int typeId) | order by book_id asc |
有条件排序:searchBy条件OrderBy字段Desc/Asc | searchByTypeIdOrderByBookIdDesc(int typeId) | type_id=type_id order by book_id desc |
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo, Integer> {
//根据书名查询 book_name=?
List<BookInfo> findByBookName(String bookName);
//查询指定作者和大于指定价格 book_author=? and book_price>?
List<BookInfo> findByBookAuthorAndBookPriceAfter(String author, Integer price);
//查询库存小于等于指定值 book_num<=?
List<BookInfo> getByBookNumLessThanEqual(Integer num);
//开区间范围查询 book_price>? and book_price<?
List<BookInfo> getByBookPriceAfterAndBookPriceBefore(Integer a, Integer b);
//闭区间范围查询 book_price between a and b
List<BookInfo> getByBookPriceBetween(Integer a, Integer b);
//空值查询 date is null
List<BookInfo> queryByDateIsNull();
//模糊查询 like 自定义参数 book_name like ?
List<BookInfo> queryByBookNameLike(String keyword);
//模糊查询 like '%参数%' book_name like '%?%'
List<BookInfo> queryByBookNameContains(String keyword);
//模糊查询 like 金% book_name like '?%'
List<BookInfo> queryByBookAuthorStartsWith(String keyword);
//无条件排序 order by book_id desc
List<BookInfo> searchByOrderByBookIdDesc();
//条件排序 type_id=? order by book_price asc
List<BookInfo> searchByTypeIdOrderByBookPrice(Integer typeId);
}
条件分页查询
只需在定义的方法的参数里,添加Pageable参数;方法的返回值改为Page类型
//多条件分页 根据书名和作者的关键字分页查询
Page<BookInfo> getByBookNameContainsAndBookAuthorContains(String bookName, String bookAuthor, Pageable pageable);
统计函数分组查询
自定义SQL
在数据访问层接口中的方法上,可以通过@Query注解,自定义SQL语句。
默认要使用HQL(Hibernate)专用格式的SQL语句。
如果要使用原生的SQL语句,需要添加nativeQuery=true属性,用value属性定义SQL语句。
//每个作者的图书数量
@Query(nativeQuery = true,value = "select book_author,count(book_id) from book_info group by book_author")
List testQuery();
List list = bookInfoDao.testQuery();
//遍历查询到的数据的每一行
for (Object row : list) {
//将每一行转换为数组
Object[] obj = (Object[]) row;
//自定义的sql要查询哪些数据就获取对应的索引
System.out.println(obj[0] +"--"+ obj[1]);
}
自定义SQL中带参数
SQL语句中的":xxx"表示参数
如果方法的形参名和xxx名称一致时直接使用;如果不一致,在形参上加入@Param设置SQL中参数的名称。
//根据作者查询该作者的图书总库存
@Query(nativeQuery = true,value = "select book_author,sum(book_num) from book_info where book_author = :zuozhe")
//List testQuest2(String zuozhe);
List testQuest2(@Param("zuozhe") String xxx);
关联查询
主表book_type
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yu1ylYgS-1685283233956)(Spring Data JPA.assets/image-20230522152545307.png)]
从表book_info
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Hfpp0rN-1685283233957)(Spring Data JPA.assets/image-20230522152606894.png)]
实体类
主表实体BookType
@Entity
@Data
@Table(name = "book_type")
public class BookType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer typeId;
private String typeName;
}
从表实体Book
- 无需写出外键字段属性
- 额外添加外键字段对应的实体对象属性
@Entity
@Data
@Table(name = "book_info")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer bookId;
//private Integer typeId;//外键字段
private String bookName;
private String bookAuthor;
private Integer bookNum;
private Integer bookPrice;
@Column(name = "publisher_date")
private String date;
private String bookImg;
//外键对应的实体对象
//多对一查询,以当前从表信息为主,关联主表信息
@JoinColumn(name = "type_id")//使用type_id进行子查询
@ManyToOne//多对一
private BookType bt;
}
使用
- 查询时正常调用数据访问层中的方法,会自动给外键字段对应的对象赋值
@Test
void test(){
bookInfoDao.findAll().forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wlgkpq9c-1685283233958)(Spring Data JPA.assets/image-20230522153712332.png)]
- 添加时,由于没有外键字段对应的属性,赋值时需要通过外键对象.外键字段进行赋值
如
@RequestMapping("/insert")
@ResponseBody
public Book saveOrUpdate(Book book) {
System.out.println(book);
return bookInfoDao.save(book);
}
实际访问时给type_id字段传参,需要写为bt.typeId
http://localhost:8080/insert?bookName=xx&bookAuthor=yy&bookNum=1&bookPrice=3&bt.typeId=2
-
如果要使用外键字段查询时,条件命名为外键对象名_外键对象属性名,如Bt_TypeId。
该方法同时也是多对一查询
//根据type_id查询 //findBy外键对象名_外键对象属性名 List<Book> findByBt_TypeId(Integer typeId);
前后端分离项目
前后端分离,就是将web应用中的前端页面和后端代码分开完成、部署。
- 前后端的开发者只需完成各自的事情,最终以文档的形式约定数据接口(URL、参数、返回值、请求方式等)
- 前后端分别部署在各自的服务器中
- 后端只负责处理数据并提供访问接口(路径),以RESTFul风格的JSON格式传输数据
- 前端只负责渲染页面和展示数据
传统项目和前后端分离项目对比
传统项目
前端和后端的代码运行在一个服务器上,页面经由controller跳转
SSM项目、图书管理系统、答题系统
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBlPgEOf-1685283233959)(Spring Data JPA.assets/image-20230523091503776.png)]
前后端分离项目
前后端的代码分别运行在各自的服务器上
后端提供JSON格式字符串的数据
前端负责跳转、解析JSON数据。
酒店房间管理系统
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGeIu5Uu-1685283233960)(Spring Data JPA.assets/image-20230523091924388.png)]
前后端分离项目后端控制层设计
RestFul风格
风格,不是标准,可以不用强制遵循。
RESTFul分格:用不同的请求方式去访问同一个URL地址时,执行不同的操作。
特点
- 通过URL就能知道当前在哪个模块
- 用不同的请求方式决定执行某个操作
- 通过返回的状态码得到操作结果
使用RESTFul风格和普通方式对比
普通方式
localhost:8080/book/queryAll 查询所有
localhost:8080/book/findById?id=1001 查询单个
localhost:8080/book/insert?bookName=xx&bookAuthor=yy 添加
localhost:8080/book/update?bookName=xx&bookAuthor=yy 修改
localhost:8080/book/delelte?id=1001 删除单个
RESTFul风格
localhost:8080/book 查询所有,使用get请求
localhost:8080/book/1001 查询单个,使用get请求
localhost:8080/book 添加,使用post请求
localhost:8080/book 修改,使用put请求
localhost:8080/book/1001 删除单个,使用delete请求
请求方式设计
-
在请求映射的命名上,统一使用小写字母的名词形式表示当前位于哪个模块。如/book,/user,/student
-
访问时如果要传参,使用"/模块名/参数"方式,配合controller中的@PathVariable获取
@GetMapping("/student/{id}") public Student findById(@PathVariable("id")Integer id){ return service.findById(id); }
-
在controller的方法上,使用@XXXMapping()设置访问该方法的请求方式
- @GetMapping(“路径”) 查询
- @PostMapping(“路径”) 添加
- @PutMapping(“路径”) 修改
- @DeleteMapping(“路径”) 删除
- @RequestMapping(value=“路径”,method=RequestMethod.GET/POST/PUT/DELETE)
-
如果访问时的请求方式不匹配,会报405异常
-
在同一个controller中,不能出现两个请求方式和路径都一致的方法
返回值设计
前后端分离项目的controller方法的返回值也需要进行同一。
返回值通常包含以下信息
- 传递状态,用数字表示。Integer code;
- 传递消息,用字符串表示。String msg;
- 传递集合,用集合表示。List list;
- 返回对象,用对象表示。Object obj;
将这些信息封装到一个对象中,这个对象称为返回结果类ResultData对象。
ResultData类具体设计
@Data
public class ResultData<T>{
private Integer code;//0表示正常,其他自定义
private String msg;
private List<T> list;
private Object obj;
//查询返回集合时
public ResultData(String msg,List<T> list){
this.code=0;
this.msg=msg;
this.list=list;
}
//返回对象时,如分页查询、添加或修改
public ResultData(String msg,Object obj){
this.code=0;
this.msg=msg;
this.obj=obj;
}
//无需返回数据时,如删除
public ResultData(String msg){
this.code=0;
this.msg=msg;
}
//请求失败时
public ResultData(Integer code,String msg){
this.code=code;
this.msg=msg;
}
public static<T> ResultData ok(String msg,List<T> list){
return new ResultData(msg,list);
}
public static ResultData ok(String msg,Object obj){
return new ResultData(msg,obj);
}
public static ResultData ok(String msg){
return new ResultData(msg);
}
public static ResultData error(Integer code,String msg){
return new ResultData(code,msg);
}
}
PostMan
Postman API Platform | Sign Up for Free
HTTP测试工具
前端页面使用ajax提交单个JSON格式的对象
$.ajax({
url:"http://localhost:8080/unit",
data:JSON.stringify(unitInfo),
contentType:"application/json;charset=utf-8",//发送的数据格式为json
dataType:"json",//发送的数据格式为json
type:"post"
});
@PostMapping("/unit")
public Unit saveOrUpdate(@RequestBody Unit unit){//如果前端提交JSON对象,需要给参数添加@RequestBody注解
//return unitDao.save(unit);
System.out.println(unit);
return null;
}
前端页面使用ajax提交JSON格式的字符串
$.ajax({
url:"http://localhost:8080/unit",
type:"post",
data:{
userList:JSON.stringify(userList),
unitInfo:JSON.stringify(unitInfo)
},
traditional:true,//发送原生数据
success:function(res){
if(res.code==0){
alert(res.msg);
}
}
});
@PostMapping("/unit")
public ResultData saveOrUpdate(@RequestParam("unitInfo") String jsonUnit, @RequestParam("userList") String jsonUsers) throws JsonProcessingException {
//将JSON格式字符串转换为对应的对象
//System.out.println(jsonUnit);
//System.out.println(jsonUsers);
//ObjectMapper类用于JSON字符串和对象直接转换
ObjectMapper jsonTool = new ObjectMapper();
//将某个json字符串转换为对应的对象
Unit unit = jsonTool.readValue(jsonUnit, Unit.class);
//添加成功后,获取添加的自增id
unit = unitDao.save(unit);
//将某个json字符串转换为对应的对象数组
Userinfo[] userinfos = jsonTool.readValue(jsonUsers, Userinfo[].class);
//遍历数组,给每个userinfo对象设置unit参数
for (Userinfo userinfo : userinfos) {
userinfo.setUnit(unit);
//userinfoDao.save(userinfo);
}
//批量添加
userinfoDao.saveAll(Arrays.asList(userinfos));
return ResultData.ok("添加成功");
}
SpringBoot中的文件上传和读取excel
在application.properties中
#上传文件大小限制 10M
spring.servlet.multipart.max-file-size=10485760
上传表单
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
easyexcel依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.1</version>
</dependency>
controller
@PostMapping("/upload")
public ResultData upload(MultipartFile file) throws IOException {
String oldName = file.getOriginalFilename();
//获取当前项目根目录绝对路径,即D:230202frameworkdemo练习sbjpa
String contextPath = System.getProperty("user.dir");
File target = new File(contextPath + "\src\main\resources\static\upload", UUID.randomUUID() + oldName.substring(oldName.lastIndexOf(".")));
file.transferTo(target);
//读取excel
EasyExcel.read(target, Userinfo.class, new PageReadListener<Userinfo>(dataList -> {
userinfoDao.saveAll(dataList);
//target.delete();
})).sheet().doRead();
return ResultData.ok("批量添加成功");
}
实体类
@Entity
@Data
public class Userinfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ExcelProperty("姓名")
private String username;
@ExcelProperty("密码")
private String password;
private String sex;
private Integer userState;
private String idcard;
@JoinColumn(name = "unit_id")
@ManyToOne
private Unit unit;
}
excel文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bZBODSFC-1685283233961)(Spring Data JPA.assets/image-20230523175726900.png)]
总结与分析
好好学习,天天向上。
往期专栏 |
---|
Java全栈开发 |
数据结构与算法 |
计算机组成原理 |
操作系统 |
数据库系统 |
物联网控制原理与技术 |