框架
一套规范。
实际是他人实现的一系列接口和类的集合。通入导入对应框架的jar文件(maven项目导入对应的依赖),进行适当的配置,就能使用其中的所有内容。
开发者可以省去很多模板代码,如dao中的CRUD,MVC模式下层与层之间的关联。只需要集中精力实现项目中的业务逻辑部分。
Java主流框架
Spring、SpringMVC、MyBatis、MyBatisPlus、Hibernate、JPA等。
SSH:最初是Spring+Stucts2+Hibernate组成,之后Stucts2被SpringMVC取代。
SSM:Spring+SpringMVC+MyBatis
新项目使用SpringBoot,早起的SSH项目由于维护成本高,基本不会推翻重做,但会维护一些SSM项目。
无论是SSH还是SSM,Spring、SpringMVC必不可少。从2004年推出至今,依旧是主流框架中不可获取的一部分。
Spring
概念
一个轻量级开源的Java框架。是一个管理项目中对象的容器,同时也是其他框架的粘合器,目的就是对项目进行解耦。
轻量级:对原有代码的侵入很小。
Spring的核心是IOC控制反转和AOP面向切面编程
组成
名词解释
IOC
Inversion Of Control 控制反转
DI
Dependency Injection 依赖注入
举例说明
用代码描述场景:员工食堂每天提供事物
-
食物:米饭类
public class Rice{ public Rice(){ sout("今天吃米饭"); } }
-
食物:面条类
public class Noodles{ public Noodles(){ sout("今天吃面条"); } }
-
厨师类
public class Cook{ //定义一个做饭的方法,做什么饭new什么对象,创建食物对象的权限,是由当前厨师类决定。 public void cooking(){ //new Rice(); new Noodles(); } }
-
员工:main方法
psvm(){ Cook cook = new Cook(); cook.cooking(); }
这种方式,Cook类中的cooking()方法创建什么对象,就输出什么内容。(厨师做什么,员工就吃什么)。
如果有人想要吃指定食物,就要修改源代码cooking()方法中创建的对象。
解决方案:定义一个接口:Food
public interface Food{
void info();
}
让原本的所有食物Rice类和Noodles类实现该接口
public class Rice implements Food{
@Override
public void info(){
sout("今天吃米饭");
}
}
public class Noodles implements Food{
@Override
public void info(){
sout("今天吃面条");
}
}
给厨师Cook类中cooking()方法定义一个参数:Food接口
public class Cook{
public void cooking(Food food){
food.info();
}
}
这时Cook对象调用cooking()方法时,需要提供一个Food接口类型的实现类。
psvm(){
Cook cook = new Cook();
//传递什么参数,旧调用该参数重写后的方法
cook.cooking(new Rice());
cook.cooking(new Noodles());
}
整个过程中,将创建什么食物对象的控制权,由厨师交给用户,这就是控制反转(IOC)。
厨师对象调用cooking()方法的参数,就是对于食物Food对象的依赖,是由用户注入进来的,这就是依赖注入(DI)。
这样一来,各个对象之间互相独立(没有在某个类中new另一个类的对象),降低了代码之间的耦合度。
总结:控制反转(IOC)是一种思想,就是让创建对象的控制权由自身交给第三方,控制反转这种思想,通过依赖注入(DI)的方式实现。
IOC和DI其实都是在描述控制反转,IOC是思想,DI是具体实现方式。
这里的第三方,就是Spring。Spring是一个容器,可以管理所有对象的创建和他们之间的依赖关系。
可以理解为:“Spring就是用来管理对象的,在需要用到某个对象的时候,帮我们自动创建”。
如Servlet+JSP模式写Web项目时,会在控制层Servlet中创建业务逻辑层Service对象,在Service层中创建数据访问层Dao对象。
有了Spring后,就不会出现new这些对象的代码了。
Spring需要导入对应的jar文件后,定义一个配置文件,在该配置文件中配置程序运行过程中所需的对象。
AOP
Aspect Orintend Programming 面向切面编程
Spring控制台应用
1.创建一个普通的Maven项目,不选择模板
2.添加Spring核心依赖
jdk8用5.x版本
<!-- spring-context表示spring核心容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
3.创建一个Java类
package com.hqyj.spring01;
/*
* 定义一个普通的Java类
* PlainOrdinaryJavaObject pojo 相当于简化的javabean
* entity vo dto pojo 都是在描述实体类
* */
public class PlainOrdinaryJavaObject {
public void fun(){
System.out.println("一个PlainOrdinaryJavaObject(普通的Java类)对象");
}
}
4.创建Spring配置文件
创建一个Spring的配置文件,在其中注入上一步创建的类的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5ds9LWh-1676442546356)(D:\框架\Spring01.assets\image-20230112110130552.png)]
在resources目录下,创建一个xml文件,选择spring config,通常命名为application
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean标签表示,在Spring容器中,注入某个类的对象-->
<!--class属性表示要注入哪个类,写类的全限定名(包名+类名)-->
<!--id表示给类的对象的名称-->
<bean class="com.hqyj.spring01.PlainOrdinaryJavaObject" id="pojo"></bean>
</beans>
5.创建main方法所在类
解析Spring配置文件,获取注入的对象。
package com.hqyj.spring01;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//创建一个自定义类的对象
//传统方式创建对象
//PlainOrdinaryJavaObject pojo = new PlainOrdinaryJavaObject();
//使用spring容器获取对象
//创建一个用于解析Spring配置文件的对象,参数为配置文件的名称。实际就是获取Spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
//获取容器中的对象
/*
只通过名称获取,返回值为Object类型,需要转换
Object obj = context.getBean("pojo");
PlainOrdinaryJavaObject pojo=(PlainOrdinaryJavaObject) obj;
*/
//使用这种方式,直接用指定类型接收
PlainOrdinaryJavaObject pojo = context.getBean("pojo", PlainOrdinaryJavaObject.class);
//能成功调用方法,说明Spring管理了该类的对象
pojo.fun();
//关闭Spring容器
context.close();
}
}
思考
-
1.对象在什么时候创建?
默认情况下,在解析Spring配置文件的时候,自动创建一个对象。
-
2.如果不想在解析时自动创建怎么办?
在配置文件的某个
<bean>
标签中,添加一个"lazy-init=true"属性,表示该对象设置为懒加载,在初始化Spring容器时不会创建对象,只有在调用getBean()时才会创建对象
-
3.如果设置为懒加载,是不是每次调用getBean(),都会创建一个对象呢?
默认情况下,Spring容器只会创建一个对象。
在配置文件的某个
<bean>
标签中,添加一个"scope=‘prototype’"属性,表示每次调用getBean()方法,就会创建一个对象。该属性的值默认为"singleton",表示单例模式,只会创建一个对象。
总结:默认情况下,通过<bean>
标签定义的类,在Spring容器初始化时,创建一个对象。
bean标签常用属性
属性 | 作用 |
---|---|
class | 定义类的全限定名 |
id | 定义对象的名称 |
lazy-init | 是否为懒加载。默认值为false,在解析配置文件时就会创建对象。设置为true表示懒加载,只有在getBean()时才会创建对象。 |
scope | 单例/原型模式。默认值为singleton,表示单例模式,只会创建一个对象。设置为prototype,表示原型模式,每调getBean()就创建一个对象。 |
init-method | 初始化时触发的方法。在创建完该对象时自动调用的方法。该方法只能是无参方法,该属性的值只需要写方法名即可 |
destory-method | 销毁时触发的方法。Spring容器关闭时自动调用的方法,该方法只能是无参方法。只有在单例模式下有效。 |
属性注入
给某个bean添加属性的方式有两种:构造器注入和setter注入
setter注入
这种方式注入属性时,类中必须要有set方法
在bean标签中,加入<property></property>
标签,
该标签的name属性通常表示该对象的某个属性名,但实际是setXXX()方法中的XXX单词。
如有age属性,但get方法为getNianLing(),name属性就需要写成nianLing。
该标签的value属性表示给该类中的某个属性赋值,该属性的类型为原始类型或String。
该标签的ref属性表示给该类中除String以外的引用类型属性赋值,值为Spring容器中另一个bean的id。
<!--注入Car类对象并用set方式注入其属性-->
<bean class="com.hqyj.spring01.Car" id="c">
<!--该属性是字符串或原始类型,使用value赋值-->
<property name="brand" value="宝马"></property>
<!--name并不是类中是属性名,而是该属性对应的getXXX()方法中XXX的名称-->
<!--如Car类中有color属性,但get方法名为getColo(),这里就要写为colo-->
<property name="colo" value="白色"></property>
</bean>
<!--注入Person类对象并用set方式注入其属性-->
<bean class="com.hqyj.spring01.Person" id="p1">
<property name="name" value="王海"></property>
<property name="age" value="22"></property>
<!--属性是引用类型,需要通过ref赋值,值为另外的bean的id ref即references-->
<property name="car" ref="c"></property>
</bean>
构造方法注入
这种方式注入属性时,类中必须要有相应的构造方法
在bean标签中,加入<constructor-arg></constructor-arg>
标签,
该标签的name属性表示构造方法的参数名,index属性表示构造方法的参数索引。
赋值时,原始类型和字符串用value,引用类型用ref。
<!--注入Person类对象并用构造方法注入其属性-->
<bean class="com.hqyj.spring01.Person" id="p2">
<!--constructor-arg表示构造方法参数 name是参数名 index是参数索引-->
<constructor-arg name="name" value="张明"></constructor-arg>
<constructor-arg index="1" value="20"></constructor-arg>
<constructor-arg name="car" ref="c"></constructor-arg>
</bean>
复杂属性注入
/*
* 定义电影类
* */
public class Movie {
//电影名
private String movieName;
//导演
private String director;
//时长
private int duration;
//主演
private List<String> playerList;
//类型
private String movieType;
//放映时间,最终格式为yyyy/MM/dd HH:mm:ss
private String showTime;
}
List类型的属性
<!--注入Movie对象-->
<bean class="com.hqyj.spring01.Movie" id="movie1">
<property name="movieName" value="夏洛特烦恼"></property>
<property name="director" value="闫非、彭大魔"></property>
<property name="duration" value="87"></property>
<!--List类型属性赋值-->
<property name="playerList" >
<!--使用list标签-->
<list>
<!--如果集合中保存的是引用类型,使用ref标签-->
<!--如果集合中保存的是原始类型或字符串,使用value标签-->
<value>沈腾</value>
<value>艾伦</value>
<value>马丽</value>
</list>
</property>
<property name="movieType" value="喜剧"></property>
<property name="showTime" value="2010/06/01 0:0:0"></property>
</bean>
Set类型的属性
<!--注入PetShop对象-->
<bean class="com.hqyj.spring01.test3.PetShop" id="petShop">
<property name="shopName" value="xxx宠物店"></property>
<property name="petSet">
<!--Set类型的属性-->
<set>
<!--如果中保存的是原始类型或字符串,使用value子标签-->
<!--如果保存的是引用类型,使用ref子标签加bean属性设置对应的id-->
<ref bean="p1"></ref>
<ref bean="p2"></ref>
<ref bean="p3"></ref>
</set>
</property>
</bean>
Map类型的属性
<!--注入Cinema对象-->
<bean class="com.hqyj.spring01.Cinema" id="cinema">
<property name="name" value="万达影城"></property>
<property name="timeTable">
<!--Map类型属性赋值,标明键和值的类型-->
<map key-type="java.lang.Integer" value-type="com.hqyj.spring01.Movie">
<!--entry标签表示键值对 如果键值对都是原始类型或字符串,使用key和value-->
<!--如果键值对中有引用类型,使用key-ref或value-ref-->
<entry key="1" value-ref="movie1"></entry>
<entry key="2" value-ref="movie2"></entry>
</map>
</property>
</bean>
属性值如果通过某个方法调用而来
如使用String保存yyyy/MM/dd格式的日期,需要通过SimpleDateFormat对象调用parse()方法而来
<!--
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
-->
<!--注入SimpleDateFormat对象,设置日期格式-->
<bean class="java.text.SimpleDateFormat" id="sdf">
<!--如果构造方法注入时,该构造方法只有一个参数,可以不用写name或index-->
<constructor-arg value="yyyy/MM/dd"></constructor-arg>
</bean>
<!--
Date now = new Date();
-->
<!--注入Date对象-->
<bean class="java.util.Date" id="now"></bean>
<!--注入Pet对象-->
<bean class="com.hqyj.spring01.test3.Pet" id="p1">
<property name="petType" value="哈士奇"></property>
<property name="petNickName" value="小哈"></property>
<!--使用当前时间作为该属性的值,以yyyy/MM/dd-->
<!--
sdf.format(now);
-->
<property name="petBirthday">
<!--如果某个值是通过某个bean调用了某个方法而来-->
<bean factory-bean="sdf" factory-method="format">
<!--方法的实际参数-->
<constructor-arg ref="now"></constructor-arg>
</bean>
</property>
</bean>
属性自动注入autowire
以上所有案例中,如果要在A对象中注入一个引用类型的对象B,都是手动将对象B注入到对象A中。
如在Person中注入Car对象,在Cinema中注入Movie等。
这种情况下,如果当某个被注入的bean的id更改后,所有引用了该bean的地方都要进行修改。
所以将手动注入更改为自动注入(自动装配),就无需添加相应的<property>
标签,甚至可以无需定义bean的id。
实现自动注入
在某个bean标签中,加入autowire属性,设置值为"byName"或"byType"。通常设置为"byType"。
Car类
package com.hqyj.spring01.test1;
import java.util.HashMap;
public class Car {
private String brand;
private String color;
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", color='" + color + '\'' +
'}';
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Person类
package com.hqyj.spring01.test1;
public class Person {
private String name;
private int age;
private Car car;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", car=" + car +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
//自动注入时,必须要有该方法
//byType方式自动注入,会自动在容器中寻找该方法参数类型
//byName方式自动注入,会自动在容器中寻找该方法setCar中的car这个id
public void setCar(Car car) {
this.car = car;
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--在容器中注入Car类型的bean-->
<bean class="com.hqyj.spring01.test1.Car" id="car">
<property name="brand" value="宝马"></property>
<property name="colo" value="白色"></property>
</bean>
<!--注入Person类的bean-->
<!--autowire="byType"表示自动检测该类中是否需要使用引用类型参数,如果容器中正好有唯一的一个对应类型的bean,就会自动赋值给对应的属性-->
<bean class="com.hqyj.spring01.test1.Person" id="person" autowire="byType">
<property name="name" value="赵明"></property>
<property name="age" value="26"></property>
</bean>
<!--autowire="byName"表示自动检测该类中的setXXX方法,如果某个setXXX方法的XXX和容器某个对象的id对应,就会自动赋值给对应的属性-->
<bean class="com.hqyj.spring01.test1.Person" id="person2" autowire="byName">
<property name="name" value="王海"></property>
<property name="age" value="26"></property>
</bean>
</beans>
Main
package com.hqyj.spring01.test1;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person = context.getBean("person", Person.class);
//此时打印时,会输出姓名、年龄和Car对象
System.out.println(person);
context.close();
}
}
autowire属性的值
-
byType
- 类中要有被注入的属性的setter()方法
- 被自动注入的对象可以没有id
- Spring容器中,某个对象的类型要与该setter()方法的参数类型一致,且容器中只有一个该类型的对象。
- 如setCar(Car c),Spring就会自动在容器中寻找类型为Car的对象自动装配
-
byName
- 类中要有被注入的属性的setter()方法
- 被自动注入的对象必须要有id
- 实际是根据setXXX()方法set后的单词XXX关联
- 如setCar(Car c),Spring就会自动在容器中寻找id为car的对象自动装配
在Web项目中,可以利用自动装配,在控制层中自动装配业务逻辑层的对象,在业务逻辑层中自动装配数据访问层的对象。
配置文件
<!--省略movie对象-->
<!--注入Cinema对象-->
<bean class="com.hqyj.spring01.test2.Cinema" id="cinema">
<property name="name" value="万达影城"></property>
<property name="timeTable">
<!--Map类型属性赋值,标明键和值的类型-->
<map key-type="java.lang.Integer" value-type="com.hqyj.spring01.test2.Movie">
<!--entry标签表示键值对 如果键值对都是原始类型或字符串,使用key和value-->
<!--如果键值对中有引用类型,使用key-ref或value-ref-->
<entry key="1" value-ref="movie1"></entry>
<entry key="2" value-ref="movie2"></entry>
</map>
</property>
</bean>
<!--该类中有一个Cinema类型的属性,这里使用了autowire=byType自动装配,会在容器中寻找Cinema类型的对象自动赋值给属性-->
<bean class="com.hqyj.spring01.test2.dao.CinemaDao" autowire="byType"></bean>
<!--该类中有一个CinemaDao类型的属性,这里使用了autowire=byType自动装配,会在容器中寻找CinemaDao类型的对象自动赋值给属性-->
<bean class="com.hqyj.spring01.test2.controller.CinemaController" id="controller" autowire="byType"></bean>
Spring Data JPA
2001年推出了Hibernate,是一个全自动ORM框架。可以不用编写SQL语句,就能实现对数据库的持久化操作。
SUN公司在Hibernate的基础上,制定了JPA,全称 Java Persisitence API,中文名Java持久化API,
是一套Java访问数据库的规范,由一系列接口和抽象类构成。
后来Spring团队在SUN公司制定的JPA这套规范下,推出了Spring Data JPA,是JPA的具体实现。
如今常说的JPA,通常指Spring Data JPA。
SpringBoot集成Spring Data JPA
1.创建SpringBoot项目,选择依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8Cg1hfr-1676442851783)(day16-SpringBoot+JPA.assets/image-20230213141450488.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**注解
-
主键属性上加
-
@Id注解标明主键
-
**@GeneratedValue(strategy = GenerationType.IDENTITY)**设置MySQL数据库主键生成策略,数据库设置为自增
-
-
其他属性名与字段名一致或驼峰命名法
- 如果字段名多个单词之间用_,使用驼峰命名法
- 如果不相同,使用**@Column(name=“字段名”)**注解指定该属性对应的字段名
@Data
@Entity
/*
* 实体类的属性名建议使用驼峰命名法
* */
public class BookInfo {
@Id//主键字段
//主键生成策略,GenerationType.IDENTITY表示MySQL自增
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer bookId;
private Integer typeId;
private String bookName;
private String bookAuthor;
//如果字段名和属性名不一致,使用@Column指定字段名
@Column(name = "book_price")
private Integer price;
private Integer bookNum;
private String publisher_date;
}
4.数据访问层接口
- 类上加@Repository注解
- 继承JpaRepository<实体类型,主键类型>接口
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {
}
5.测试常用方法
方法名 | 返回值 | 作用 |
---|---|---|
findAll() | List | 查询所有数据。 |
save(T entity) | T entity | 添加或修改。如果不存在主键属性或主键值不存在,执行添加;如果存在主键属性且有该主键值,执行修改。 |
delete(T entity) | void | 根据对象删除。如果该对象中有主键属性且有该主键值,根据该主键值删除。 |
findById(主键) | Optional | 根据主键值查询。返回的对象调用isPresent()结果为true,表示查询到了数据,继续调用get()得到查询到的对象。 |
@SpringBootTest
class Day16SpringBootJpaApplicationTests {
@Autowired
private BookInfoDao bookInfoDao;
@Test
void contextLoads() {
List<BookInfo> all = bookInfoDao.findAll();
all.forEach(System.out::println);
}
@Test
void insert() {
BookInfo bookInfo = new BookInfo();
bookInfo.setBookName("测试");
bookInfo.setBookAuthor("测试");
bookInfo.setBookNum(156);
bookInfo.setPrice(20);
bookInfo.setTypeId(1);
//save()方法调用时,如果对象中没有主键或主键值不存在,作为添加使用
BookInfo save = bookInfoDao.save(bookInfo);
//添加成功后,会自动获取主键自增的值
System.out.println(save);
}
@Test
void update() {
BookInfo bookInfo = new BookInfo();
bookInfo.setBookName("xxxxxxxxxxx");
bookInfo.setBookAuthor("测试");
bookInfo.setBookNum(356);
bookInfo.setPrice(23);
bookInfo.setTypeId(1);
//save()方法调用时,如果对象中有主键且存在,作为修改使用
BookInfo save = bookInfoDao.save(bookInfo);
//修改成功后,返回修改后的对象
System.out.println(save);
}
@Test
void delete() {
//根据主键值删除,如果值不存在,会报错
//bookInfoDao.deleteById(36);
//根据对象删除,如果对象中包含主键值则删除,如果没有值或不存在,不会报错
BookInfo bookInfo = new BookInfo();
//bookInfo.setBookId(330);
bookInfoDao.delete(bookInfo);
}
@Test
void findOne() {
//根据主键查询,返回值Optional类型
Optional<BookInfo> byId = bookInfoDao.findById(60);
//isPresent()如果为true表示查询到了数据
if (byId.isPresent()) {
//get()将查询到的数据转换为对应的实体类
BookInfo bookInfo=byId.get();
System.out.println(bookInfo);
}else{
System.out.println("未查询到数据");
}
}
}
JPA进阶
分页查询
调用数据访问层中的**findAll(Pageable pageable)**方法,即可实现分页。
参数Pageable是org.springframework.data.domain包中的一个接口,通过其实现类
PageRequest,调用静态方法of(int page,int size),当做Pageable对象使用。
这里的page从0开始为第一页。
@Test
void queryByPage(){
//PageRequest是Pageable的实现类,调用静态方法of(int page,int size)
//这里的page的值0表示第一页
//调用findAll(Pageable pageable)方法,返回分页模型对象
Page<BookInfo> pageInfo = bookInfoDao.findAll(PageRequest.of(0,5));
//分页相关数据
System.out.println("总记录数"+pageInfo.getTotalElements());
System.out.println("最大页数"+pageInfo.getTotalPages());
System.out.println("分页后的数据集合"+pageInfo.getContent());
System.out.println("当前页数"+pageInfo.getNumber());
System.out.println("每页显示的记录数"+pageInfo.getSize());
System.out.println("是否还有下一页"+pageInfo.hasNext());
System.out.println("是否还有上一页"+pageInfo.hasPrevious());
}
条件查询
在JPA中,使用自定义方法名自动生成对应的SQL语句,实现条件查询。
如在dao中定义了queryById(int id)方法,就表示根据id查询,自动生成sql语句。
方法命名格式
[xxx] [By] [字段对应的属性名] [规则] [Or/And] [字段对应的属性名] [规则] …
- **xxx可以是find、get、query、search
- 方法如果有参数,参数的顺序和方法名中的参数顺序一致
如findByBookNameAndBookAuthor(String bookName,String bookAuthor),
对应的sql语句为 select * from book 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/Befor | findByBookPriceAfter(double price) | book_price > price |
GreaterThanEqual/LessThanEqual | findByBookNumLessThanEqual(int num) | book_num <= num |
Between | findByBookNumBetween(int min,int max) | book_num between min and max |
Is[Not]Null | findByPublisherDateIsNull() | publish_date is null |
[Not]Like | findByBookNameLike(String condition) | book_name like ‘condition’ |
[Not]Contains | findByBookNameContains(String keyword) | book_name like ‘%keyword%’ |
StartsWith/EndsWith | findByBookNameStartsWith(String firstName) | book_name like ‘firstName%’ |
无条件排序:findAllByOrderBy字段[Desc/Asc] | findAllByOrderByBookId() | order by book_id asc |
有条件排序:findAllBy条件OrderBy字段[Desc/Asc] | findAllByTypeIdOrderByBookIdDesc() | type_id = ? order by book_id desc |
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {
//指定值查询
//根据书名查询
List<BookInfo> getAllByBookName(String x);
//查询价格大于指定值 字段对应的属性名 After/GreaterThan
List<BookInfo> findAllByPriceAfter(int price);
//查询价格小于于指定值 字段对应的属性名 Before/LessThan
List<BookInfo> findAllByPriceLessThan(int price);
//查询库存大于等于指定值 GreaterThanEqual
List<BookInfo> queryAllByBookNumGreaterThanEqual(int num);
//查询库存在指定闭区间内 Between(int min,int max)
List<BookInfo> findAllByBookNumBetween(int min,int max);
//空值查询 null
//查询出版日期为空 IsNull/IsNotNull
List<BookInfo> findAllByPublisherDateIsNull();
//书名中带有关键字 Like/NotLike 实参一定要使用%或_
List<BookInfo> getAllByBookNameLike(String keyword);
//作者名中带有关键字 Contains/NotContains 实参只需要关键字
List<BookInfo> getAllByBookAuthorContains(String keyword);
//指定作者的姓 指定开头/结尾 StartsWith/EndsWith
List<BookInfo> getAllByBookAuthorStartsWith(String keyword);
//查询所有数据,按价格降序 无条件排序 OrderBy字段[Desc/Asc]
List<BookInfo> getAllByOrderByPriceDesc();
//查询指定类型,按id降序
List<BookInfo> getAllByTypeIdOrderByBookIdDesc(Integer typeId);
}
条件分页查询
只需在定义方法时,将方法的返回值设置为Page类型,在参数中就如Pageable对象
//根据作者和书名的关键字分页查询
Page<BookInfo> getAllByBookNameContainsOrBookAuthorContains(
String nameKeyword,
String authorKeyword,
Pageable pageable);
Page<BookInfo> pageInfo = bookInfoDao.getAllByBookNameContainsOrBookAuthorContains("龙","山",PageRequest.of(0,5));
//分页相关数据保存在pageInfo对象中
聚合函数分组查询
自定义SQL
在数据访问层接口中的方法上,可以加入@Query注解,默认要使用HQL(Hibernate专用)格式的语句。
如果要使用原生的SQL语句,需要添加nativeQuery=true属性,用value属性定义SQL语句
/*
* 在JPA中,如果要使用自定义的SQL语句
* nativeQuery = true 开启原生SQL语句
* value="sql语句"
* */
@Query(nativeQuery = true, value = "select book_author,count(book_id) from book_info group by book_author")
List testQuery();
@Test
void test(){
List list = bookInfoDao.testQuery();
//查询的结果为集合,集合中保存的是每一行数据
for (Object row : list) {
//每一行页数一个对象数组
Object[] obj= (Object[])row;
//根据索引得到查询出的内容
System.out.println(obj[0]+"---"+obj[1]);
}
}
自定义SQL中带参数
SQL语句中的":XXX"表示参数
如果方法的形参名和xxx一致时直接使用,如果不一致,在形参上加入@Param注解设置形参名
/*
* 根据作者查询其图书总库存
* 使用":形参名"在SQL语句中带参数
* 在方法中通过@Prama定义形参
* */
@Query(nativeQuery = true, value = " select book_author,sum(book_num) from book_info where book_author=:zuozhe")
List testQuery3(@Param("zuozhe") String xxx);
@Test
void test(){
List list = bookInfoDao.testQuery3("金庸");
//查询的结果为集合,集合中保存的是每一行数据
for (Object row : list) {
//每一行页数一个对象数组
Object[] obj= (Object[])row;
//根据索引得到查询出的内容
System.out.println(obj[0]+"---"+obj[1]);
}
}
关联查询
主表book_type
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-enMZRXyj-1676442851784)(day16day17-SpringBoot+JPA.assets/image-20230214105234875.png)]
从表book_info
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-COMeKRsJ-1676442851784)(day16day17-SpringBoot+JPA.assets/image-20230214105247332.png)]
实体类
主表实体BookType
@Entity
@Data
public class BookType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer typeId;
private String typeName;
}
从表实体BookInfo
- 无需写出外键字段属性
- 额外添加外键字段对应的实体类对象属性
@Data
@Entity
public class BookInfo {
@Id//主键字段
//主键生成策略,GenerationType.IDENTITY表示MySQL自增
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer bookId;
//private Integer typeId;
private String bookName;
private String bookAuthor;
//如果字段名和属性名不一致,使用@Column指定字段名
@Column(name = "book_price")
private Integer price;
private Integer bookNum;
private String publisherDate;
//多对一查询,以当前从表信息为主体,关联相应的主表信息
@JoinColumn(name = "type_id")//使用type_id字段进行多对一查询
@ManyToOne//多对一
private BookType bt;
}
使用
-
多对一查询,调用dao中的查询方法,就会自动给外键字段对应的对象赋值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i0uAebfp-1676442851785)(day16day17-SpringBoot+JPA.assets/image-20230214111732161.png)]
-
添加时,参数所需外键字段,使用外键字段对应的对象名代替
//添加 @PostMapping("/book") public RestResult<BookInfo> insert(BookInfo bookInfo) { return RestResult.ok("添加成功", bookInfoDao.save(bookInfo)); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNnDpRnL-1676442851787)(day16day17-SpringBoot+JPA.assets/image-20230214113125279.png)]
-
如果根据外键字段查询,要在从表dao中定义方法findBy外键对象名_外键对象属性名(数据类型 外键字段)
//如果根据外键字段查询,方法名写为 By外键实体类属性名_属性名 List<BookInfo> getAllByBt_TypeId(Integer typeId);