在国内,你可能会发现大部分项目用的orm都是Mybatis或者Mybatis-plus,对于个人来讲,我也挺喜欢Mybatis,毕竟它开发起来非常的自由;但是对于简单curd操作jpa效率上还是可以的,所以作为一个合格的Java程序员,熟练使用jpa时非常有必要的。
1.什么是jpa?
jpa
是Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层。
Jpa与Hibernate的关系
- JPA 是 Hibernate 的一个抽象(就像JDBC和JDBC驱动的关系);
- JPA 是规范:JPA 本质上就是一种 ORM 规范,不是ORM 框架,这是因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现;
- Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现
- 从功能上来说, JPA 是 Hibernate 功能的一个子集
JPA包含的技术
- ORM 映射元数据:JPA 支持 XML 和 JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。
- JPA 的 API:用来操作实体对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的 JDBC 和 SQL 代码中解脱出来。
- 查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的 SQL 紧密耦合。
2.Spring Data Jpa 环境搭建
引入依赖
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--Spring boot Web容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-spring-boot-starter.version}</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
配置文件
server:
port: 8848
spring:
jackson:
time-zone: GMT+8
#配置 Jpa
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
open-in-view: true
# 数据库平台
database-platform: mysql
show-sql: true
database: mysql
# 每次启动项目时,数据库初始化策略
hibernate:
ddl-auto: update
datasource:
druid:
db-type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/tem-jpa?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
username: root
password: 123456
# 初始连接数
initial-size: 5
# 最小连接数
min-idle: 15
# 最大连接数
max-active: 30
# 超时时间(以秒数为单位)
remove-abandoned-timeout: 180
# 获取连接超时时间
max-wait: 3000
# 连接有效性检测时间
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存的时间
max-evictable-idle-time-millis: 900000
# 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
test-while-idle: true
# 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个
test-on-borrow: true
# 是否在归还到池中前进行检验
test-on-return: false
# 检测连接是否有效
validation-query: select 1
# 配置监控统计
webStatFilter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: false
login-username: root
login-password: 123
filter:
stat:
enabled: true
# 记录慢SQL
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true
每次启动项目时的初始化策略有4种:
- create:
每次应用启动的时候会重新根据实体建立表,之前的表和数据都会被删除。- create-drop:
和上面的功能一样,但是多了一样,就是在应用关闭的时候,也就是sessionFactory一关闭,会把表删除。- update:
最常用的,第一次启动根据实体建立表结构,之后启动会根据实体的改变更新表结构,之前的数据都在。- validate:
会验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值,运行程序会校验实体字段与数据库已有的表的字段类型是否相同,不同会报错
3.增删改查一套
准备实体类
@ApiModel(value = "用户表")
@Data
@Table(name = "student")
@Entity
public class Student {
@ApiModelProperty(value = "自增id")
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "用户名")
@Column(name = "username")
private String username;
@ApiModelProperty(value = "密码")
@Column(name = "password")
private String password;
}
@ApiModelProperty注解为swagger注解,可以删除
编写持久层
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
增加接口
@PostMapping
public Object save(@RequestBody Student user){
Student save = studentRepository.save(user);
return save;
}
删除接口
@DeleteMapping("/{id}")
public Object delete(@PathVariable Integer id){
studentRepository.deleteById(id);
return "delete success";
}
修改接口
@PutMapping
public Object update(@RequestBody Student user){
Student save = studentRepository.save(user);
return save;
}
普通查询接口
@GetMapping
public Object getList(){
List<Student> userList = studentRepository.findAll();
return userList;
}
分页查询接口
@PostMapping(value = "/pageList")
public Object getListByPage(@RequestBody Student student){
Pageable pageable = PageRequest.of(0,10);
Example<Student> studentExample = Example.of(student, ExampleMatcher.matching().withMatcher("username", ExampleMatcher.GenericPropertyMatcher::contains));
Page<Student> studentPage = studentRepository.findAll(studentExample, pageable);
return studentPage;
}
参考自官网例子,jpa增删改查一套[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBBGKVDq-1623835238747)(C:\Users\dpf\AppData\Roaming\Typora\typora-user-images\image-20210614200259922.png)]
在持久层,除了使用JPQL
已经定义好的还可以通过注解自己写sql
,如下代码:
/**
* nativeQuery = true 使用原生的sql查询
* @param username
* @return
*/
@Query(value = "select id,username,password from student where username = :username",nativeQuery = true)
List<Student> getListByName(@Param("username") String username);
@Query(value = "select id,username,password from student where username = ?1",nativeQuery = true)
List<Student> getListByName2( String username);
@Transactional
@Modifying
@Query(value = "update student set username=:#{#student.username},password=:#{#student.password} where id=:#{#student.id}",nativeQuery = true)
int updateStudent(@Param("student") Student student);
如果不使用@Param注解,可以使用
?阿拉伯数字
进行取值,@Query后面可以直接写sql语句,nativeQuery = true
表示使用原生sql进行查询,如果进行修改使用@Modifying
注解
4. Repository详细介绍
基础介绍
在Mybatis/Mybatis-plus
中我们一般将持久层取名为xxxDao
,但是在Jpa
中我们一般取名为为xxxRepository
,这个xxxRepository
到底有什么特别的呢?首先我们看看它的继承关系图:
从图中可以看出实现类不少,我们可以从以下几个进行学习
- Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法
public interface Repository<T, ID extends Serializable> { }
- 若我们定义的接口继承了 Repository, 则该接口会被 IOC 容器识别为一个 Repository Bean,进而纳入到 IOC 容器中,进而可以在该接口中定义满足一定规范的方法。
- Spring Data可以让我们只定义接口,只要遵循 Spring Data 的规范,就无需写实现类
基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能,它的几个常用的实现类如下:
- CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
- PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
- JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
- 自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。
- JpaSpecificationExecutor: 不属于Repository 体系,实现一组 JPA Criteria 查询相关的方法
方法定义规范
如果你得多了,jpa持久层的方法好像都是遵守某一规范的,例如:查询,一般都是findAaaByBbb
,删除一般都是deleteByXxx
,只要你按照规范定义方法它就可以帮你实现相关方法。jpa支持以下方法关键字:
5.多表查询
在关系型数据库中,表与表之间的关系有:一对一、一对多、多对多。这里对1对多和多对多进行演示。
一对多
这里以学生和班级进行举例,一个班级对应多个学生。
班级实体类如下
@Data
@Table(name = "cla")
@Entity
public class Cla implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@OneToMany(targetEntity = Student.class,mappedBy = "cla")
private List<Student> studentList;
}
学生实体类
@Data
@Table(name = "student")
@Entity
public class Student implements Serializable {
@ApiModelProperty(value = "自增id")
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "用户名")
@Column(name = "username")
private String username;
@ApiModelProperty(value = "密码")
@Column(name = "password")
private String password;
@ManyToOne(targetEntity = Cla.class)
@JsonBackReference// 解决序列化无限递归问题
private Cla cla;
}
注解说明
@OneToMany:建立一对多的映射关系(主表)
- targetEntityClass:指定一对多多对应哪个类(常用)
- mappedBy:指定从表实体类中引用主表对象的名称(上面学生从表班级对象名称cla)。(常用)
- cascade:指定要使用的级联操作
- CascadeType.ALL: 进行级联操作,all表示级联所有(insert,delete,update)
- CascadeType.merge :更新
- CascadeType.persist:保存
- CascadeType.remove :删除
- fetch:指定是否采用延迟加载true/false
- orphanRemoval:是否使用孤儿删除(非级联)
@ManyToOne:建立多对一的映射关系(从表)
- targetEntityClass:指定多对一的一是哪个类(常用)
- cascade:指定要使用的级联操作
- fetch:指定是否采用延迟加载
@JoinColumn:用于定义主键字段和外键字段的对应关系
- name:指定外键字段的名称(常用)
- referencedColumnName:指定引用主表的主键字段名称(常用)
- unique:是否唯一。默认值不唯一
- nullable:是否允许为空。默认值允许。
- insertable:是否允许插入。默认值允许。
- updatable:是否允许更新。默认值允许。
多对多
这里以商品和订单举例,一个商品可以对应多个订单,一个订单也可以对应多个商品。多对多需要第三个关系表来维系多对多关系。
商品表如下
@Data
@Entity
@Table(name = "t_product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@ManyToMany(mappedBy = "productList")
@JsonBackReference
private List<Order> orderList;
}
订单表如下
@Data
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@ManyToMany(targetEntity = Product.class)
@JoinTable(name = "product_order",
joinColumns = {@JoinColumn(name = "order_id",referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "product_id",referencedColumnName = "id")}
)
@JsonBackReference
private List<Product> productList;
}
注解说明
@ManyToMany:用于映射多对多关系
- cascade:配置级联操作。
- fetch:配置是否采用延迟加载。
- targetEntity:配置对多的时哪个实体类。
- mappedBy:指定从表实体类中引用主表对象的名称。(常用)
@JoinTable:用来配置中间表的相关属性
- nam:配置中间表的表名
- joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
- inverseJoinColumn:中间表的外键字段关联对方表的主键字段