您现在的位置是:首页 >技术教程 >Spring Data JPA|至尊荣耀篇网站首页技术教程

Spring Data JPA|至尊荣耀篇

程序员老茶 2024-07-01 11:59:40
简介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/AndfindByBookNameOrBookAuthor(String name,String author)book_name=name or book_author=author
After/BeforefindByBookPriceAfter(double price)book_price > price
GreaterThanEqual/LessThanEqualgetByBookNumLessThanEqual(int num)book_num <= num
BetweengetByBookPriceBetween(int a,int b)book_price between a and b
In/NotIngetByBookAuthorIn(String… authors)
getByBookAuthorIn(Collection authors)
book_author in (?,?,?..)
IsNull/IsNotNullqueryByDateIsNull()date is null
Like/NotLikequeryByBookNameLike(String keyword)book_name like ‘%keyword%’,实参为%keyword%
Containts/NotContainsqueryByBookNameContains(String keyword)book_name like ‘%keyword%’,实参为keyword
StartsWith/EndsWithqueryByBookAuthorStartsWith(String keyword)book_author like ‘keyword%’
无条件排序:searchByOrderBy字段Desc/AscsearchByOrderByBookId(int typeId)order by book_id asc
有条件排序:searchBy条件OrderBy字段Desc/AscsearchByTypeIdOrderByBookIdDesc(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全栈开发
数据结构与算法
计算机组成原理
操作系统
数据库系统
物联网控制原理与技术
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。