你真的了解Jpa吗?

一、关于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官方说明
 官方给出的说明中总结几点:

  1. Spring Data JPA是开源项目Spring Data中的一员。
  2. 可以轻松的实现基于JPA的存储库。
  3. 让你Spring项目中操作数据库更简便。
  4. 不用再书写太多样板化的代码。
  5. 可以基于一定规则给你自动提供实现。

三、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语句。

关键词示例生成语句
AndfindByNameAndAgewhere name = ?1 and age = ?2
OrfindByNameOrAgewhere name = ?1 or age = ?2
Is,EqualsfindByNameIs,findByNameEqualswhere name = ?1
BetweenfindByAgeBetweenwhere age between ?1 and ?2
LessThanfindByAgeLessThanwhere age < ?1
LessThanEqualfindByAgeLessThanEqualwhere age <= ?1
GreaterThanfindByAgeGreaterThanwhere age > ?1
GreaterThanEqualfindByAgeGreaterThanEqualwhere age >= ?1
AfterfindByCreateDateAfterwhere create_date > ?1
BeforefindByCreateDateBeforewhere create_date < ?1
IsNullfindByAgeIsNullwhere age is null
IsNotNullfindByAgeIsNotNullwhere age is not null
LikefindByNameLikewhere name like %?1%
NotLikefindByNameNotLikewhere name not like %?1%
OrderByfindByNameOrderByAgeDescwhere name = ?1 order by age desc
InfindByNameInwhere name in (?1)
NotInfindByNameNotInwhere name not in (?1)

1.3.2 限制查询

关键词示例生成语句
Top10findTop10ByNamewhere name = ?1 limit 10
FirstfindFirstByNamewhere 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);
	}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页