一、关于JPA
JPA全称Java Persistence API,是Sun官方在JDK5.0后提出的Java持久化规范(JSR 338),这些类存在于java.persistence包中。JPA的出现主要是为了简化持久层开发以及整合ORM实体映射技术,结束Hibernate、TopLink、ORM各自为营的局面。
JPA是吸收现有ORM架构的基础再发展,它易于使用,伸缩性强。总的来说,其具有以下三个特点:
- ORM映射元数据
支持xml和注解两种形式,元数据描述对象和表之间的映射关系。 - API
操作实体对象来进行数据库CRUD。 - 查询语言
通过面向对象,而非面向数据库的查询语言查询数据,降低与数据库的耦合。
二、Spring Data JPA
官方网址:https://spring.io/projects/spring-data-jpa
官方给出的说明中总结几点:
- Spring Data JPA是开源项目Spring Data中的一员。
- 可以轻松的实现基于JPA的存储库。
- 让你Spring项目中操作数据库更简便。
- 不用再书写太多样板化的代码。
- 可以基于一定规则给你自动提供实现。
三、JPA、Hibernate、Spring Data JPA三者之间的关系
JPA是一种规范,内部是由接口和抽象类组成的。而Hibernate是一套成熟的ORM框架,且实现了JPA规范,理论上来说也可以成其为JPA的一种实现方式。Spring Data JPA是Spring中提供的一套基于JPA规范封装的更高级的框架,它的基础实现还是使用Hibernate。
四、Spring Data JPA在Java中的实现
1.1 创建并配置SpringBoot项目
本例直接在官网上创建和下载,创建SpringBoot项目可以参考《从零到一搭建一个SpringBoot2.0项目》
查看mysql-connector-java版本,如果用的6.0以上,数据库驱动类就要用com.mysql.cj.jdbc.Driver而不是com.mysql.jdbc.Driver,并且给数据库连接就爱上serverTimezone参数,否则就会报错。
mysql配置名称在SpringBoot2.x和1.x之间还有区别,所以配置的时候先看org.springframework.boot.autoconfigure.jdbc.DataSourceProperties类:
根据名称来对应,比如驱动类的配置就需要spring.datasource.driverClassName。
mysql连接配置(在application.properties中配置):
# mysql连接配置
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
同样JPA的配置名称也可以在JpaProperties中找,完整路径:org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
jpa配置:
# Spring Data JPA配置
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hbm2ddl.auto的四种选项:
- create
每次运行该程序,没有表格会新建表格,表内有数据会清空; - create-drop
每次程序结束的时候会清空表; - update
每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新; - validate
运行程序会校验数据与数据库的字段类型是否相同,不同会报错;
完整配置:
server.port=8080
# mysql连接配置
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
# Spring Data JPA配置
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
1.2 简单接口调用
项目结构:
新建学生实体类:
package com.yl.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "student")
public class Student {
@Id
@GenericGenerator(name = "uuidGenerator", strategy = "uuid")
@GeneratedValue(generator = "uuidGenerator")
private String id;
@Column
private String name;
@Column
private Integer age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
- @Entity
表示其是ORM实体; - @Table(name = “student”)
表示对应表的名称为student; - @Id
表示本字段为主键; - @GenericGenerator(name = “uuidGenerator”, strategy = “uuid”)
表示主键生成器,命名为uuidGenerator,策略为uuid; - @GeneratedValue(generator = “uuidGenerator”)
自动生成值,生成规则为上面定义的名称; - @column
表示是表中的一个字段,名称会根据驼峰自动映射。如果该实体中某字段不需要称为表中一个字段,需要在字段上加@Transient注解。
这种形式表示主键自动递增。
数据访问层:
package com.yl.jpa.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.yl.jpa.entity.Student;
@Repository
public interface StudentDao extends JpaRepository<Student, String> {
}
需要加上@Repository注解,继承JpaRepository,Studnet为其实体类,String为主键类型。
继承类之后,就可以调用默认方法了,如:
业务层:
package com.yl.jpa.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.yl.jpa.dao.StudentDao;
import com.yl.jpa.entity.Student;
import com.yl.jpa.service.IStudentService;
@Service
public class StudentServiceImpl implements IStudentService {
@Autowired
private StudentDao studentDao;
@Override
public void save(Student student) {
studentDao.save(student);
}
}
业务接口:
package com.yl.jpa.service;
import com.yl.jpa.entity.Student;
public interface IStudentService {
void save(Student student);
}
控制层:
package com.yl.jpa.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.yl.jpa.entity.Student;
import com.yl.jpa.service.IStudentService;
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
private IStudentService studentService;
@PostMapping("/save")
public String doSave(@RequestBody Student student) {
studentService.save(student);
return "success";
}
}
启动项目,项目启动成功后,可以看到自动运行了创建表语句:
使用postman进行接口测试:
控制台中打印insert语句:
查看新增的记录:
1.3 查询
1.3.1 名称匹配查询
在Spring Data JPA中,只要你根据规则写了repository方法,就不用自己写sql语句,JPA会自动生成对应的sql语句。
关键词 | 示例 | 生成语句 |
---|---|---|
And | findByNameAndAge | where name = ?1 and age = ?2 |
Or | findByNameOrAge | where name = ?1 or age = ?2 |
Is,Equals | findByNameIs,findByNameEquals | where name = ?1 |
Between | findByAgeBetween | where age between ?1 and ?2 |
LessThan | findByAgeLessThan | where age < ?1 |
LessThanEqual | findByAgeLessThanEqual | where age <= ?1 |
GreaterThan | findByAgeGreaterThan | where age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | where age >= ?1 |
After | findByCreateDateAfter | where create_date > ?1 |
Before | findByCreateDateBefore | where create_date < ?1 |
IsNull | findByAgeIsNull | where age is null |
IsNotNull | findByAgeIsNotNull | where age is not null |
Like | findByNameLike | where name like %?1% |
NotLike | findByNameNotLike | where name not like %?1% |
OrderBy | findByNameOrderByAgeDesc | where name = ?1 order by age desc |
In | findByNameIn | where name in (?1) |
NotIn | findByNameNotIn | where name not in (?1) |
… |
1.3.2 限制查询
关键词 | 示例 | 生成语句 |
---|---|---|
Top10 | findTop10ByName | where name = ?1 limit 10 |
First | findFirstByName | where name =? limit 1 |
1.3.3 自定义查询
使用@Query可以实现自定义查询
@Query("select s from Student s where name = :name")
List<Student> queryByName(@Param("name") String name);
@Query("select s from Student s where name = ?1")
List<Student> queryByName2(@Param("name") String name);
@Query(value = "select * from student where age = ?1", nativeQuery = true)
List<Student> queryByAge(@Param("age") Integer age);
@Query(value = "select * from student where age = :age", nativeQuery = true)
List<Student> queryByAge2(@Param("age") Integer age);
这四种方式都可以实现查询,但是需要注意,nativeQuery=true表示是原生sql查询,不能写入java实体,比如Student 。
1.3.4 分页查询
可以使用PageRequest封装页码分页大小,排序数据,并传入findAll方法:
@Override
public Page<Student> findPage(PageRequest pageRequest) {
return studentDao.findAll(pageRequest);
}
@GetMapping("page")
public Page<Student> doGetPage(HttpServletRequest request){
Integer no = Integer.valueOf(request.getParameter("no"));
Integer size = Integer.valueOf(request.getParameter("size"));
Sort sort = Sort.by(Direction.DESC, "age");
return studentService.findPage(PageRequest.of(no, size, sort));
}
1.4 修改删除
@Transactional
@Modifying
@Query("delete from Student where name = ?1")
int deleteByName(String name);
需要在方法上添加@Modifying注解,且在调用方法内加@Transactional注解,否则报错。
1.5 关联操作
1.5.1 多对多
修改学生实体类:
package com.yl.jpa.entity;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "student")
public class Student {
@Id
@GenericGenerator(name = "uuidGenerator", strategy = "uuid")
@GeneratedValue(generator = "uuidGenerator")
private String id;
@Column
private String name;
@Column
private Integer age;
@ManyToMany(targetEntity = Course.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "student_course", joinColumns = {@JoinColumn(name = "student_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "course_id", referencedColumnName = "id")})
private List<Course> courseList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Course> getCourseList() {
return courseList;
}
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
}
新建课程实体类:
package com.yl.jpa.entity;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "course")
public class Course {
@Id
@GenericGenerator(name = "uuidGenerator", strategy = "uuid")
@GeneratedValue(generator = "uuidGenerator")
private String id;
@Column
private String courseName;
@ManyToMany(targetEntity = Student.class, fetch = FetchType.LAZY)
@JoinTable(name = "student_course", joinColumns = {@JoinColumn(name = "course_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "student_id", referencedColumnName = "id")})
private List<Student> studentList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
}
学生和课程是多对多,所以需要设置@ManyToMany。
joinColumns表示当前对象的配置,其中course_id为表student_course中的字段,对应着本对象中的id值。
inverseJoinColumns表示对方对象的配置,其中student_id为表student_course中的字段,对应着对方对象中的id值。
重启项目,会发现自动创建了student_course表:
保存学生实体:
其中的课程以及关联表也会新增:
查询学生实体:
关联信息也被查询出来。
1.5.2 一对多
新建Address类,学生对地址是一对多的关系。
package com.yl.jpa.entity;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "address")
public class Address {
@Id
@GenericGenerator(name = "uuidGenerator", strategy = "uuid")
@GeneratedValue(generator = "uuidGenerator")
private String id;
private String address;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "student_id")
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
- @JoinColumn(name = “student_id”)
表示在Address表中增加一列,列名叫做student_id,关联student的id。
1.5.3 多对一
地址对学生是多对一的关系,所以修改Student类:
package com.yl.jpa.entity;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "student")
public class Student {
@Id
@GenericGenerator(name = "uuidGenerator", strategy = "uuid")
@GeneratedValue(generator = "uuidGenerator")
private String id;
@Column
private String name;
@Column
private Integer age;
@ManyToMany(targetEntity = Course.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "student_course", joinColumns = {@JoinColumn(name = "student_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "course_id", referencedColumnName = "id")})
private List<Course> courseList;
@OneToMany(mappedBy = "student")
private List<Address> addressList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Course> getCourseList() {
return courseList;
}
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
public List<Address> getAddressList() {
return addressList;
}
public void setAddressList(List<Address> addressList) {
this.addressList = addressList;
}
}
- @OneToMany(mappedBy = “student”)
表示关系由student方维护。
1.6 对象状态
Spring Data JPA中对象的状态如下:
- 瞬时状态(new/transient):没有主键,不与持久化上下文关联,即 new 出的对象(但不能指定id的值,若指定则是游离态而非瞬时态)
- 托管状态(persistent):使用EntityManager进行find或者persist操作返回的对象即处于托管状态,此时该对象已经处于持久化上下文中(被EntityManager监控),任何对该实体的修改都会在提交事务时同步到数据库中。
- 游离状态(detached):有主键,但是没有跟持久化上下文关联的实体对象。
- 删除状态 (deleted):当调用EntityManger对实体进行remove后,该实体对象就处于删除状态。其本质也就是一个瞬时状态的对象。
需要注意的是,在一个事务方法中,即使你是在调用save方法之后对实体重新赋值,那么新赋的值也会被修改到数据库里。因为此时是在一个事务中,且对象还是托管状态。
如:
@Transactional
@Override
public void save(Student student) {
studentDao.save(student);
student.setAge(100);
}
save之后对age赋值。使用postman测试:
得出的结果是100,这显然不是我们预期的结果。
我们可以用BeanUtils.copyProperties克隆一份对象进行操作。
@Transactional
@Override
public void save(Student student) {
studentDao.saveAndFlush(student);
Student student2 = new Student();
BeanUtils.copyProperties(student, student2);
student2.setAge(100);
}