🙈作者简介:练习时长两年半的Java up主
🙉个人主页:程序员老茶
🙊 ps:点赞👍是免费的,却可以让写博客的作者开兴好久好久😎
📚系列专栏:Java全栈,计算机系列(火速更新中)
💭 格言:种一棵树最好的时间是十年前,其次是现在
🏡动动小手,点个关注不迷路,感谢宝子们一键三连
目录
- 课程名:Java
- 内容/作用:知识点/设计/实验/作业/练习
- 学习:Java
- Spring Data JPA
- SpringBoot集成Spring Data JPA
- 1.创建SpringBoot项目,选择依赖
- 2.编辑配置文件,设置要连接的数据库信息
- 3.创建实体类
- 4.数据访问层接口
- 5.测试常用方法
- JPA进阶
- 分页查询
- 条件查询
- 方法命名格式
- 常用规则
- 条件分页查询
- 统计函数分组查询
- 自定义SQL
- 自定义SQL中带参数
- 关联查询
- 实体类
- 使用
- 前后端分离项目
- 传统项目和前后端分离项目对比
- 传统项目
- 前后端分离项目
- 前后端分离项目后端控制层设计
- RestFul风格
- 请求方式设计
- 返回值设计
- ResultData类具体设计
- PostMan
- 前端页面使用ajax提交单个JSON格式的对象
- 前端页面使用ajax提交JSON格式的字符串
- SpringBoot中的文件上传和读取excel
- 总结与分析
课程名: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 bList<BookInfo> getByBookPriceBetween(Integer a, Integer b);//空值查询 date is nullList<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 descList<BookInfo> searchByOrderByBookIdDesc();//条件排序 type_id=? order by book_price ascList<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",//发送的数据格式为jsondataType:"json",//发送的数据格式为jsontype:"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);//添加成功后,获取添加的自增idunit = 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:\230202\framework\demo练习\sbjpaString contextPath = System.getProperty("user.dir");File target = new File(contextPath + "\\src\\main\\resources\\static\\upload", UUID.randomUUID() + oldName.substring(oldName.lastIndexOf(".")));file.transferTo(target);//读取excelEasyExcel.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")@ManyToOneprivate Unit unit;
}
excel文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bZBODSFC-1685283233961)(Spring Data JPA.assets/image-20230523175726900.png)]
总结与分析
好好学习,天天向上。
往期专栏 |
---|
Java全栈开发 |
数据结构与算法 |
计算机组成原理 |
操作系统 |
数据库系统 |
物联网控制原理与技术 |