@[toc]
前言:之前一直用的都是Mybatis,最近由于工作原因,要使用JPA,因此整理一下学习笔记防止忘记,也希望能够帮到需要使用这个技术的人
1. Spring Data JPA 概念
- JPA(Java Persistence API,Java持久层api) 是一套ORM规范,使得应用程序以统一的方式访问持久层
- JPA 是
Hibernate
的一个抽象,从功能上来说是 Hibernate 的一个子集 - Spring Data JPA 是 Spring 的一个子项目,用于简化数据库访问,支持
nosql(redis,mongoDB)
和关系型数据库(jdbc,jpa
)存储
2. 使用
2.1 引入JPA依赖
- 创建SpringBoot项目,引入
JPA,MySQL,Web
依赖,和数据库连接池依赖 - 因为我本地的数据库版本是5.7,因此引入mysql依赖的时候标注version 版本号
org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web mysql mysql-connector-java runtime 5.1.28 com.alibaba druid-spring-boot-starter 1.1.10 复制代码 org.springframework.boot spring-boot-starter-test test
2.2 配置连接数据库参数和jpa参数
- 在resources 目录下的
application.properties
文件中
# 配置数据库连接池及数据库驱动spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.jdbc.Driver# 配置mysql的链接信息spring.datasource.username=rootspring.datasource.password=rootspring.datasource.url=jdbc:mysql://localhost:3306/jpaDemo?useUnicode=true&characterEncoding=utf-8#jpaspring.jpa.database=mysqlspring.jpa.database-platform=mysql#是否自动生成ddspring.jpa.generate-ddl=true# 生成方式 update 运行时在数据库生成表,若有更新则去更新数据spring.jpa.hibernate.ddl-auto=update# 格式化sql语句spring.jpa.properties.hibernate-format_sql=true# 控制台展示 JPA 框架生成的sql语句spring.jpa.show-sql=true# 解决 hibernate multiple merge 问题spring.jpa.properties.hibernate.event.merge.entity_copy_observer = allow # 使用JPA 创建表时,默认使用的存储引擎是MyISAM,通过指定数据库版本,可以使用InnoDBspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect复制代码
2.3 基本使用
2.3.1 创建实体类
package com.bisnow.demo.pojo;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;@Entitypublic class Book { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String bookname; private String author; get + set + toString}复制代码
2.3.2 对数据库操作
需要一个接口继承JpaRepository<实体类名,id类型>,在接口中有现成的方法或者自定义方法
package com.bisnow.demo.repository;import com.bisnow.demo.pojo.Book;import org.springframework.data.jpa.repository.JpaRepository;public interface BookRepository extends JpaRepository{}复制代码
- 运行建表插入数据
2.3.3 测试
@RunWith(SpringRunner.class)@SpringBootTestpublic class DemoApplicationTests { @Autowired BookRepository bookRepository; @Test public void test1(){ Listbooks = bookRepository.findAll(); books.forEach(b-> System.out.println(b)); }}复制代码
2.4 JPA 实体类常用注解
JPA项目运行的时候会根据实体类自动的去数据库建表
2.4.1 @Entity
表示这是一个实体类,默认情况下,类名就是表名
2.4.2 @Table
与
@Entity
并列使用,自定义数据库表名
2.4.3 @Id
标注主键
2.4.4 @GeneratedValue
- 同@Id 共同使用,标注主键的生成策略,通过
strategy
属性指定IDENTITY
采用数据库ID自增长的方式产生主键,oracle不支持AUTO
JPA自动选择合适的策略,是默认选项SEQUENCE
Oracle不支持主键自增长,其提供了一种叫做"序列(sequence)"的机制生成主键,GenerationType.SEQUENCE就可以作为主键生成策略,不足之处是只能使用于部分支持序列的数据库(Oracle,PostgreSQL,DB2),一般与@SequenceGenerator一起使用,@SequenceGenerator注解指定了生成主键的序列,不指定序列时自动生成一个序列SEQ_GEN_SEQUENCETABLE
:通过表产生主键,框架借由表模拟序列产生主键,使用该策略更易于做数据库移植。
package com.bisnow.demo.pojo;import javax.persistence.*;@Entity @Table(name = "t_person") public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; private Integer age; get + set 方法}复制代码
- 此时运行项目,可以在控制台看到建表语句,并且可以在数据库中查看表及表关系
- 删除之间建好的表。若将 @Table 属性注释,更改
@GeneratedValue
策略,则
@GeneratedValue(strategy = GenerationType.IDENTITY)复制代码
2.4.5 @Column
用来描述实体属性对应数据表字段,其中
name、nullable、unique、length
属性用的较多,分别用来描述字段名、字段是否可以null、字段是否唯一、字段的长度,实际开发中需要用其他属性时,可从参考文献获取api文档地址,查阅文档配置。
@Column(length = 400,name = "p_name",nullable = false,unique = true)private String name;复制代码
此时建表能看到
2.4.6 @Transient
在数据库中建表的时候忽略该属性
@Transientprivate String name;复制代码
2.4.7 @OneToOne 单向关联
两个实体一对一关系,例如人和身份证就是一对一的关系
查询包含关联属性的实体对象时,能同步从数据库中获取关联的实体对象,反过来不行
- 创建两个实体类 Person,Card
package com.bisnow.demo.pojo;import javax.persistence.*;@Entitypublic class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; @OneToOne //关联的字段 name属性可以标注表中字段的值 @JoinColumn(name = "card_id") private Card card; get + set + toString ...}复制代码
package com.bisnow.demo.pojo;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;@Entitypublic class Card { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String num; get + set + toString ... }复制代码
- 创建repository 接口,测试查询
@Autowired PersonRepository personRepository; @Autowired CardRepository cardRepository; @Test public void contextLoads() { Optionalperson = personRepository.findById(1); Person p = person.get(); System.out.println(p); Optional card = cardRepository.findById(1); Card c = card.get(); System.out.println(c); }复制代码
- 此时可以看到,查询Person能同步获取到对应的Card,但是查询Card获取不到
- 数据库中的表关系
2.4.8 @OneToMany和@ManyToOne 单向关联
两实体一对多关系,例如人和电话就是一对多关系,一个人可以有多个电话,一个电话只能属于一个人
2.4.8.1 @ManyToOne
- 创建实体类 Person 和 Phone
package com.bisnow.demo.pojo;import javax.persistence.*;@Entitypublic class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; get + set + toString ...}package com.bisnow.demo.pojo;import javax.persistence.*;@Entitypublic class Phone { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private Integer number; @ManyToOne @JoinColumn(name = "person_id") private Person person; get + set + toString ...}复制代码
- 插入数据
- 创建repository 接口,测试查询
@Autowired PersonRepository personRepository; @Autowired PhoneRepository phoneRepository; @Test public void test2(){ Listall = personRepository.findAll(); all.forEach(p-> System.out.println(p)); List all1 = phoneRepository.findAll(); all1.forEach(p-> System.out.println(p)); }复制代码
2.4.8.2 @OneToMany
- 创建实体类 Person 和 Phone
package com.bisnow.demo.pojo;import javax.persistence.*;@Entitypublic class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; @OneToMany(cascade = CascadeType.MERGE,fetch = FetchType.EAGER) @JoinColumn(name = "phone_id") private Listphone; get + set + toString ...}package com.bisnow.demo.pojo;import javax.persistence.*;@Entitypublic class Phone { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private Integer number; get + set + toString ...}复制代码
- 插入数据
- 创建repository 接口,测试查询
@Autowired PersonRepository personRepository; @Autowired PhoneRepository phoneRepository; @Test public void test2(){ Listall = personRepository.findAll(); all.forEach(p-> System.out.println(p)); List all1 = phoneRepository.findAll(); all1.forEach(p-> System.out.println(p)); }复制代码
2.4.8.3 fetch属性指明数据抓取策略
EAGER
即立即抓取LAZY
延迟加载
2.4.8.4 cascade属性指明级联特性
CascadeType.PERSIST
:级联持久化操作,当将实体保存至数据库时,其对应的关联实体也会被保存至数据库CascadeType.REMOVE
:级联删除操作,从数据库删除当前实体时,关联实体也会被删除CascadeType.DETACH
:级联脱管,当前实体被ORM框架脱管,关联实体也被同步脱管,处于脱管状态的实体,修改操作不能持久化到数据库CascadeType.MERGE
:级联合并操作,实体数据改变时,会相应的更新Course中的数据CascadeType.REFRESH
:级联刷新,当前实体修改保存至数据库时,关联的实体状态会重新从数据库加载,忽略掉先前的状态CascadeType.ALL
:包含上述所有级联操作
2.4.9 @ManyToMany
多对多的实体关系,例如学生和所选课程的关系,一个学生可以选多个课程,一个课程可被多个学生选择
- 创建实体类 Student 和 Course
@Entitypublic class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; @ManyToMany(targetEntity = Course.class,fetch = FetchType.EAGER) private Listcourses; get + set + toString ...}@Entitypublic class Course { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; get + set + toString}复制代码
- 插入数据
- 创建repository 接口,测试查询
@Autowired StudentRepository studentRepository; @Autowired CourseRepository courseRepository; @Test public void test3(){ Listall = studentRepository.findAll(); System.out.println(all); List all1 = courseRepository.findAll(); System.out.println(all1); }复制代码
2.5 对数据库操作
需要一个接口继承
JpaRepository<实体类名,id类型>
,可在接口中写现有方法或者自定义的方法
- 自定义方法规范
public interface BookRepository extends JpaRepository{ List getBookByBooknameContains(String bookname); List getBookByIdGreaterThanAndAuthorEndsWith(Integer id,String author); //表示自定义的 dml 操作方法 @Query(value = "select * from book where id = (select max(id) from book)",nativeQuery = true) Book getMaxIdBook(); // 两种传参方式 使用 ?1 的方式不需要 @Param 注解 // @Query(value = "select * from book where id > ?1",nativeQuery = true) @Query(value = "select * from book where id > :id",nativeQuery = true) List idGreaterThan(@Param("id") Integer id); //自定义 ddl 操作方法 需要 @Modifying 注解,告知此操作会对数据库的数据进行修改 // 对数据库的数据进行修改时 需要添加事务管理@Transactional(一般加在Service层) @Query(value = "update t_book set bookname = ?1 where id = ?2",nativeQuery = true) @Modifying int updateBookById(String bookname,Long id);}复制代码
- 插入数据并测试
@Autowired BookRepository bookRepository; @Test public void test4(){ Listb = bookRepository.getBookByBooknameContains("拾"); System.out.println(b); List b2 = bookRepository.getBookByIdGreaterThanAndAuthorEndsWith(2, "夏达"); System.out.println(b2); Book maxIdBook = bookRepository.getMaxIdBook(); System.out.println(maxIdBook); List books = bookRepository.idGreaterThan(2); System.out.println(books); }复制代码
- 通过
@Query
自定义 dml 和 ddl 方法 - 自定义 dml 方法可以通过 ?1、?2 或者 :s_id + (@Param("s_id")) 来传参
- 自定义ddl方法需要在自定义的方法上面加上
@Modifying
注解,并且在操作时需要添加事务管理@Transactional
(一般加在Service层)`
@Service@Transactionalpublic class BookService {}复制代码