JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。 [1]
Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。
2.1标准化
JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
2.2容器级特性的支持
JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
2.3简单方便
JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
2.4查询能力
JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
2.4高级特性
JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
注解 | 解释 |
---|---|
@Entity | 声明类为实体或表。 |
@Table | 声明表名。 |
@Basic | 指定非约束明确的各个字段。 |
@Embedded | 指定类或它的值是一个可嵌入的类的实例的实体的属性。 |
@Id | 指定的类的属性,用于识别(一个表中的主键)。 |
@GeneratedValue | 指定如何标识属性可以被初始化,例如自动、手动、或从序列表中获得的值。 |
@Transient | 指定的属性,它是不持久的,即:该值永远不会存储在数据库中。 |
@Column | 指定持久属性栏属性。 |
@SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值。它创建了一个序列。 |
@TableGenerator | 指定在@GeneratedValue批注指定属性的值发生器。它创造了的值生成的表。 |
@AccessType | 这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),则可以直接访问变量并且不需要getter和setter,但必须为public。如果设置@AccessType(PROPERTY),通过getter和setter方法访问Entity的变量。 |
@JoinColumn | 指定一个实体组织或实体的集合。这是用在多对一和一对多关联。 |
@UniqueConstraint | 指定的字段和用于主要或辅助表的唯一约束。 |
@ColumnResult | 参考使用select子句的SQL查询中的列名。 |
@ManyToMany | 定义了连接表之间的多对多一对多的关系。 |
@ManyToOne | 定义了连接表之间的多对一的关系。 |
@OneToMany | 定义了连接表之间存在一个一对多的关系。 |
@OneToOne | 定义了连接表之间有一个一对一的关系。 |
@NamedQueries | 指定命名查询的列表。 |
@NamedQuery | 指定使用静态名称的查询。 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PmCjfikY-1635517419814)(Image/1635323358692.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOHIMgGA-1635517419816)(Image/1635323445865.png)]
必要的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
#连接数据库的四大参数
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jpa
spring.datasource.username=root
spring.datasource.password=123
#jpa的相关配置
# 开发阶段可以 设置为true,开启了逆向工程:在实际上线运行阶段:实体类和底层的数据库表都是已经存在,所以是false
# 数据库和java 逆向工程 正向工程
# 逆向工程 : 在数据库的表,然后数据库表可以生成实体类;
# 正向工程 :存在实体类,然后根据实体类,生成底层的表;
spring.jpa.generate-ddl=true
#create:设置为create,每次运行程序都会将原来的数据表删除,然后重新创建一个表
#create-drop: 每次创建一个数据表,数据表使用完毕之后,将数据表再次删除
#none 将功能不生效
#update如果你设定的实体类发生了改变,数据表会更新
# 如果数据库当中有数据表,就会使用原来的表。没有数据表,就会创建一个数据表,
#validate 实体类和数据表进行校验,如果属性或者个数不一致,就会抛出异常。
spring.jpa.hibernate.ddl-auto=update
#操作实体对象的时候,会跟我们生成sql语句;false不生成语句;
spring.jpa.show-sql=true
#指定了数据库的类型:
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
4.1 创建PetDao接口
/**
* Pet 实体类,对应底层数据库表,mysql数据库当中没有t_pet表,可以使用jpa提供的正向工程。
* 实体类----》关系表
*
* @Entity注解:表明当前是实体类,当前的实体类和底层的t_pet 关系表进行映射
*/
@Data
@Entity(name = "t_pet") //指定该类是一个实体。此注释应用于实体类。
public class Pet {
@Id //id唯一值
//指定了当前主键生成策略 GenerationType.IDENTITY 自增
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
//@Column 注解 标识pname就是一个普通的列;注解当中可以指定很多属性,我们可以都使用默认值
@Column
private int pname;
@Column
private String color;
}
//测试类
@SpringBootTest
class JpaSpringBootApplicationTests {
@Test
void contextLoads() {
System.out.println("table 创建成功");
}
}
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WvbLXhvY-1635517419817)(Image/1635341090966.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8v1RmJwb-1635517419818)(Image/1635341203630.png)]
主要继承JpaRepository接口
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 创建PetDao接口,实现pet实体类的操作
* jpaRepository jpa提供的一个接口:接口当中定义了一些实体的基本操作
* T 指定了具体操作的实现类:Pet实体类
* ID 指定主键字段的类型(实体类当中带有 id注解的属性) Integer
*
*
* 接口可以继承其它接口,但是接口不能实现
*/
public interface PetDao extends JpaRepository<Pet,Integer> {
//这里面可以自定义方法
}
//对jpa提供的接口方法进行测试;
/**
* 注入PetDao接口,只要是JpaRepository接口的子接口,自动被纳入到Spring容器
* 纳入spring容器的是实现类的对象(代理对象) ,产生的代理对象的id是接口首字母小写
* >
*/
@Autowired
PetDao petDao; //springboot在启动的时候,底层使用了动态代理的方式获得一个接口的实现类,完成注入
/**
* save 方法
* 如果没有指定id字段,直接会进行一个insert操作
* 如果指定id字段,会根据id先去查询
* 如果查询到id对应的记录存在的话,进行一个更新操作
* 否则继续进行新增
*/
@Test
void addPet(){
System.out.println("pet add");
Pet pet = new Pet();
pet.setId(14); //是自增,不需要手动的设置值
pet.setPname("大耳朵图图");
pet.setColor("我说他同喜同喜");
//完成添加操作
petDao.save(pet);
}
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vmU7ruiW-1635517419820)(Image/1635406600218.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sezvhYLR-1635517419821)(Image/1635406633842.png)]
//查询操作:根据Id查询
@Test
void findPet(){
//获得一个Optional对象 // 可能包含也可能不包含非空值的容器对象
Optional<Pet> byId = petDao.findById(10);
Pet pet = byId.get(); //通过get返回Pet对象
System.out.println(pet.getId()+""+pet.getPname());
}
如果没有查询到这个id的数据
则返回
NoSuchElementException: No value present :列表为空
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rt2QqtM5-1635517419824)(Image/1635410078274.png)]
因为Optional源码里面已经判断好了:如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VHQkqI5-1635517419826)(Image/1635410209135.png)]
查询全部
/**
* 列表查询:返回的是一个列表集合
* findAll():没有指定任何参数,查询列表
* findAll():在使用时可以指定参数,Sort对象,指定排序字段,升序或降序
*/
@Test
void findAllPet(){
//获得一个Optional对象 // 可能包含也可能不包含非空值的容器对象
List<Pet> all = petDao.findAll();
for (Pet pet : all) {
System.out.println(pet.getId()+""+pet.getPname());
}
}
@Test
void findAllSort(){
//id 是你要指定排序的属性
List<Pet> all = petDao.findAll(Sort.by(Sort.Direction.DESC,"id"));
for (Pet pet : all) {
System.out.println(pet.getId());
}
}
/**
* 分页查询 ====================
*
*/
@Test
void findAllPage(){
/*
pageable:接口类型
pageRequest: 接口的实现对象: 实现类不是直接new,构造器已经protect 保护起来了
pageRequest :类当中提供了of方法,返回了本类对象
of()方法:static静态方法
参数一:
page :查询第一页,用户指定 ;0表示第一页
size :当前显示的记录数
Direction.ASE 指定升序排序;DESC指定的降序排序
properties:指定具体的哪个属性进行排序
*/
//Sort sort = Sort.by(Sort.Direction.DESC,"id");
// List all1 = petDao.findAll(sort); //底层是一个Like集合
Pageable pageable = PageRequest.of(0,2, Sort.Direction.DESC,"id");
Page<Pet> all = petDao.findAll(pageable); //底层是一个迭代器
for (Pet pet : all) {
System.out.println(pet.getId());
}
System.out.println("总页录数:"+all.getTotalElements());
System.out.println("总页数:"+all.getTotalPages());
System.out.println("当前页数:"+(all.getNumber()+1)); //用户显示页数从1开始
System.out.println("每页记录数"+all.getSize());
System.out.println("当前页记录数:"+all.getNumberOfElements());
}
/**
* 删除操作
* delete(pet) 传递pet对象,删除传递对象:
* 删除执行步骤
* (1) 先根据id进行一个查询
* (2) 执行delete操作
*/
@Test
void deletePet(){
//通过对象删除
// Pet pet = new Pet();
// pet.setId(3);
// petDao.delete(pet);
// or或
//通过id删除
petDao.deleteById(2);
}
(1)petDao是我们创建的接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVPhiWQe-1635517419827)(Image/1635421711527.png)]
(2)这是接口的代码,什么也没有
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xreyprMQ-1635517419829)(Image/1635421752919.png)]
(3)但是它继承了 JpaRepository
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WndYua18-1635517419831)(Image/1635421892011.png)]
所以本身就会去你给的实体类去执行
public interface PetDao extends JpaRepository<Pet,Integer> {
//这里面可以自定义方法
/*
自定义查询:
(1)什么时候自定义查询
jpaRepository接口当中提供的方法不能正常满足实际业务需求,此时我们需要进行自定义查询
PetDao接口当中自定义方法
(2)方法定义注意事项
方法的返回值是根据实际的业务需求定义的: List Pet
方法的名称必须满足规范:findByXxx findBy固定开始 Xxx属性名称:findByPname
参数列表:根据实际的业务需求定义
注意:方法的名称必须满足相应的规范
*/
List<Pet> findByPname(String panem);
List<Pet> findBycolor(String color);
//根据id查询1 到 5 的范围 + 然后通过id排序
List<Pet> findByIdBetweenOrderById(int id,int id1);
}
//测试类=====================
@Test
void text(){
// List value = petDao.findByColor("爱人");
// System.out.println(value.toString());
// Pet byPnameAndColor = petDao.findByPnameAndColor("哥哥", "阿尼");
// System.out.println(byPnameAndColor.toString());
List<Pet> byIdBetweenOrderById = petDao.findByIdBetweenOrderById(1, 4);
System.out.println(byIdBetweenOrderById.toString());
}
问题解析:findByXxx
必须是你的实体类属性名,是否封装有get和set无关,要注意大小写
否则:异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7O0Vxgwh-1635517419833)(Image/1635505800531.png)]
JPQL语言,就是Java Persistence Query Language的简称。JPQL是一种和SQL非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的SQL查询,从而屏蔽不同数据库的差异。JPQL语言的语句可以是select语句、update 语句或delete语句,它们都通过 Query 接口封装执行。
什么是Query接口
Query接口封装了执行数据库查询的相关方法。调用 EntityManager 的 createQuery、createNamedQuery 及 createNativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。
JPQL 和 SQL 有很多相似之处。它们都用于访问和操作数据库数据。二者都使用非过程语句
JPQL 和 SQL 的主要区别在于,JPQL处理 JPA 实体是面向对象的,
SQl直接在数据库空间内对表、列、行等关系数据进行处理。
sql语句:
数据库表名,列名
select * from tableName
jpql语句:
实体类名,属性名
select attrName1,attrName from entityName
JPQL书写规范
1.里面不能出现表名,列名,只能出现java的属性名,区分大小写
2.出现的SQL关键字是一样的意思,关键字不区分大小写
3.不能出现select * 要写select别名
@Query(value = "select pet from com.yang.domain.Pet as pet")
public List<Pet> jpql();
//测试===========
@Test
void text1(){
List<Pet> jpql = petDao.jpql();
for (Pet pet : jpql) {
System.out.println(pet.toString());
}
}
结果:
Hibernate: select pet0_.id as id1_0_, pet0_.color as color2_0_, pet0_.pname as pname3_0_ from t_pet pet0_
Pet(id=1, pname=大耳朵图图, color=hibernate)
Pet(id=4, pname=哥哥, color=阿尼)
Pet(id=5, pname=触发, color=爱人)
注意
@Query(value = "select id,pname,color from com.yang.domain.Pet as pet")
public List<Pet> jpql1();
//测试
@Test
void text1(){
List<Pet> pets = petDao.jpql1();
for (Pet pet : pets) {
System.out.println(pet.toString());
}
}
结果:
类型转换异常
ConversionFailedException: Failed to convert from type [java.lang.Object[]]
因为我们写的Sql语句是多个列名,返回的是一个Object[]对象,和我们的接收对象不一样,导致异常
解决办法:
//=================
@Query(value = "select id,pname,color from com.yang.domain.Pet as pet")
public List<Object[]> jpql1();
测试: 要什么类型就转换什么类型
@Test
void text1(){
List<Object[]> objects = petDao.jpql1();
for (Object[] object : objects) {
System.out.println(Arrays.toString(object));
}
}
//=================
或者如下: 将列名直接给到构造方法,这样返回的就是对象
@Query(value = "select new com.yang.domain.Pet(id,pname,color) from com.yang.domain.Pet as pet")
public List<Pet> jpql2();
测试:
@Test
void text1(){
List<Pet> pets = petDao.jpql2();
for (Pet pet11 : pets) {
System.out.println(pet11.toString());
}
}
结果:
Pet(id=1, pname=大耳朵图图, color=hibernate)
Pet(id=4, pname=哥哥, color=阿尼)
Pet(id=5, pname=触发, color=爱人)
注意:这种方式,要先有这个构造方法,不然报错
方式一:?1 表示第一个参数 ?2 表示第二个参数…
方式二: 使用 @Param(“ name ”) 注解 获取用它 : name 注意 这个 “ :” 符号,不要忽略