Jpa基础知识点

  • JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
  • Spring Data JPA是较大的Spring Data系列的一部分,可轻松实现基于JPA的存储库。该模块处理对基于JPA的数据访问层的增强支持。它使构建使用数据访问技术的Spring支持的应用程序变得更加容易。

1.JPA的基本配置

  1. pom依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

2. 项目配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
# mysql 配置:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.16.11.211:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect #不加这句则默认为myisam引擎
open-in-view: true

3. 创建一个简单实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.kid1999.teacher_manager.model;
import lombok.Data;
import javax.persistence.*;

@Entity // 表示这是一个数据库实体
@Table(name = "user") // 表示对于的表,不设置name时 默认和类名相同
@Data
public class User {

@Id // 指定 ID
@GeneratedValue // 指定主键生成策略,默认自增长
@Column(name = "id")
private Integer id;

@Basic // 默认会加上这个注解,表示该属性到数据库同名字段的映射
private String addr;

@Column(name = "name") // 默认不写的时候=Basic 表示该属性到数据库name字段的映射
private String name;

@Transient // 表示此字段与数据库无关,取消@Basic
private String info;

}

@GeneratedValue JPA自带的几种主键生成策略:
TABLE: 使用一个特定的数据库表格来保存主键。
SEQUENCE: 根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator 指定生成主键使用的生成器(可能是orcale中自己编写的序列)。
IDENTITY: 主键由数据库自动生成(主要是支持自动增长的数据库,如mysql)。
AUTO: 主键由程序控制,也是GenerationType的默认值。

4. 创建一些关联关系的对象

  • 关联关系必须注意维护端和被维护端的选取
    1. 在一对多中,多的一方作为维护端,一的一方作为被维护端
    1. 在一对一中,任意选取一方作为维护端,宁一方作为被维护端
    1. 在多对多中,任意选取一方作为维护端,但必须记住中间表的关系由维护端负责

1. 多对一 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package io.kid1999.teacher_manager.model;
import lombok.Data;
import javax.persistence.*;

@Data
@Entity
@Table
public class Student {
@Id
private Integer studentId;
private String studentName;
private Integer gradeId;

/**
* 多对一 ManyToOne
* JPA中,many一端作为维护端,One一端作为被维护端
* many一方指定@ManyToOne注解 ,并使用 @JoinClomn指定关联的外键
*/

@ManyToOne(targetEntity = Grade.class,fetch = FetchType.LAZY) //对应的对象,且设置为懒加载(不在每一次查询时都进行多表关联)
@JoinColumn(name = "gradeId",insertable = false,updatable = false) //对应关联的键,在插入更新时不插入grade
private Grade grade;


// 必须重新toString 不然在打印时候会递归查询 grade
@Override
public String toString() {
return "Student{" +
"studentId=" + studentId +
", studentName='" + studentName + '\'' +
", gradeId=" + gradeId +
'}';
}
}

2. 一对多 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package io.kid1999.teacher_manager.model;

import lombok.Data;
import javax.persistence.*;
import java.util.List;

@Data
@Entity
@Table
public class Grade {
@Id
private Integer gradeId;
private String gradeName;

/**
* 一对多 OneTOMany
* 可以在 One 一方指定@OneToMany注释 并设置 mappedBy属性,以指定他是这一关联中被维护的一端
*/
@OneToMany(mappedBy = "grade")
private List<Student> students;
}

测试:学生和班级之间的多对一关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  @Autowired
private StudentRepository studentRepository;

@Autowired
private GradeRepository gradeRepository;

@Test
@Transactional
void TestManyToOne(){
Student student = new Student();
student.setStudentId(1212);
student.setStudentName("kid");
student.setGradeId(1);
studentRepository.save(student); // 插入
Grade grade = studentRepository.getOne(1212).getGrade();
System.out.println(grade.getGradeId() + grade.getGradeName());
}

@Test
@Transactional
void TestOnTomany(){
Grade grade = new Grade();
grade.setGradeId(2);
grade.setGradeName("math");
gradeRepository.save(grade); // 插入
List<Student> students = gradeRepository.getOne(1).getStudents(); // 外键查询
System.out.println(students);
}

3. 一对一 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package io.kid1999.teacher_manager.model;

import lombok.Data;
import javax.persistence.*;
import java.util.List;

@Data
@Entity
@Table
public class Grade {
@Id
private Integer gradeId;
private String gradeName;

/**
* 一对多 OneTOMany
* 可以在 One 一方指定@OneToMany注释 并设置 mappedBy属性,以指定他是这一关联中被维护的一端
*/
@OneToMany(mappedBy = "grade", fetch = FetchType.LAZY)
private List<Student> students;

/**
* 一对一 被维护端
*/
@OneToOne(mappedBy = "grade")
private Teacher teacher;

@Override
public String toString() {
return "Grade{" +
"gradeId=" + gradeId +
", gradeName='" + gradeName + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.kid1999.teacher_manager.model;

import lombok.Data;
import javax.persistence.*;

@Entity
@Table
@Data
public class Teacher {
@Id
@GeneratedValue
private Integer teacherId;
private String teacherName;
private Integer gradeId;

// OnToOne 维护端
@OneToOne
@JoinColumn(name = "gradeId",insertable = false,updatable = false,unique = true) // 查询唯一
private Grade grade;

// 重写toString
@Override
public String toString() {
return "Teacher{" +
"teacherId=" + teacherId +
", taacherName=" + teacherName +
", gradeId=" + gradeId +
'}';
}
}

测试 教师和班级之间的一对一关系

1
2
3
4
5
6
7
8
9
10
11
@Transactional
void TestOneToOne(){
// Teacher teacher = new Teacher();
// teacher.setGradeId(2);
// teacher.setTeacherName("hello");
// teacherResitory.saveAndFlush(teacher);
Teacher teacher1 = teacherResitory.getOne(7);
Grade grade = teacher1.getGrade();
System.out.println(grade);
System.out.println(grade.getStudents());
}

3.多对多 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package io.kid1999.teacher_manager.model;

import lombok.Data;
import javax.persistence.*;
import java.util.List;

@Data
@Entity
@Table
public class Student {
@Id
private Integer studentId;
private String studentName;

/**
* 多对一 ManyToOne
* JPA中,many一端作为维护端,One一端作为被维护端
* many一方指定@ManyToOne注解 ,并使用 @JoinClomn指定关联的外键
*/

private Integer gradeId;
@ManyToOne(targetEntity = Grade.class,fetch = FetchType.LAZY) //对应的对象,且设置为懒加载(不在每一次查询时都进行多表关联)
@JoinColumn(name = "gradeId",insertable = false,updatable = false) //对应关联的键,在插入更新时不插入grade
private Grade grade;


/**
* 多对多的维护端 需要一个中间表进行维护
*/
@ManyToMany
@JoinTable(name = "students_roles", // 指定中间表名
joinColumns ={@JoinColumn(name = "studentId",referencedColumnName = "studentId")}, // 指定中间表和student的关联
inverseJoinColumns = {@JoinColumn(name = "roleId",referencedColumnName = "roleId")} // 指定中间表和role的关联
)
private List<Role> roles;

// 必须重新toString 不然在打印时候会递归查询 grade
@Override
public String toString() {
return "Student{" +
"studentId=" + studentId +
", studentName='" + studentName + '\'' +
", gradeId=" + gradeId +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package io.kid1999.teacher_manager.model;

import lombok.Data;
import javax.persistence.*;
import java.util.List;

@Data
@Entity
@Table
public class Role {
@Id
private Integer roleId;
private String roleName;

/**
* 多对多 被维护端
*/
@ManyToMany(mappedBy = "roles")
private List<Student> students;
}

测试 学生和身份之间的多对多关系

  • 选取的学生作为维护端 所以只能在学生注册时添加对应的关系
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Transactional
    void TestManyToMany(){
    Student student = new Student();
    student.setStudentId(123);
    student.setStudentName("kid");
    student.setGradeId(2);
    student.setRoles(roleRepository.findAll());
    studentRepository.save(student); // 插入

    // Role role = new Role();
    // role.setRoleId(1);
    // role.setRoleName("hello");
    // roleRepository.save(role);

    // Student student = studentRepository.getOne(111);
    // List<Role> roles = student.getRoles();
    // System.out.println(roles);
    }

注意点:

  1. 维护端和被维护端的选取
  2. 懒加载
  3. JoinColumn关联列是否需要插入和更新
  4. toString的死循环问题

4. Repository 层 实现 Repository 标准

1
2
3
@Repository
public interface AccountRepository extends JpaRepository<Account,String> {
}

5.Repository 的继承关系

  • epository:空接口,表名任何继承它的均为仓库接口类
  • CrudRepository:继承Repository,实现了一组CRUD相关的方法
  • PagingAndSortingRepository:继承CrudRepository,实现了一组分页、排序相关的方法
  • JpaRepository:继承PagingAndSortRepository,实现一组JPA规范相关的方法
    自定义的XxxRepository需要继承JpaRepository,这样该接口就具备了通用的数据访问控制层的能力。

6.Repository的几种写法

自带的方法

  • 如: Save(), findAll(), getXXX() ….

    简单命名拼凑

1
List<User> findUserByName(String name);

使用HSQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**  2. HQL
* HQL 和 JPQL 语法是一样的
* sql常用关键字和JPQL一样
* JPQL特点: 字段,表名 由对象类型和属性代替 (区分大小写)
* 编码规范: Sql常用关键字全部大写,不能使用 *
*/

// 1.通过 ?1 传参
@Query("SELECT u FROM User u WHERE u.name = ?1")
List<User> findUserByHSql(String name);

// 2.通过 参数名 传参
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findUserByHsqlAndParams(@Param("name") String name);
  • ?加数字表示占位符,?1代表在方法参数里的第一个参数,区别于其他的index,这里从1开始
  • JPQL的语法中,表名的位置对应Entity的名称,字段对应Entity的属性
  • :加上变量名,这里是与方法参数中有@Param的值匹配的,而不是与实际参数匹配的

使用 原始 SQL

  • 雷同HQL, 在@Query 中 加入 nativeQuery = true
  • 注意在修改数据的操作中必须加 @Transactional 事务注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//更新
@Transactional
@Modifying
@Query(value = "update user a set a.name=?1 where a.id= ?2",nativeQuery = true)
int updateName(String name,Integer id);

// 删除
@Transactional
@Modifying
@Query(value = "delete from user where id = ?1",nativeQuery = true)
void deletIdBysql(Integer id);


// 使用 HQL 删除数据
@Transactional
@Modifying
@Query("delete from User u where u.id = ?1")
void deletId(Integer id);
  • 数据修改操作需要加上事务注解,保证数据的一致性