【Spring Data JPA】英国伦敦资深工程师完整大师课

crud

1 course overview

3 psql

5 cloning repo


6 配置

spring.datasource.url=jdbc:postgresql://localhost:5432/amigoscode
spring.datasource.username=postgres
spring.datasource.password=123456
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true


7 connect to db

先运行分支 6,让 jpa 创建表


select * from student
join student_id_card sic on student.id = sic.student_id
join book b on student.id = b.student_id;

8 section overview

9 student class

package com.example.demo;

public class Student {

    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    private Integer age;

    public Student(Long id, String firstName, String lastName, String email, Integer age) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.age = age;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
}

10 connect to db

## 原来的是create-drop
spring.jpa.hibernate.ddl-auto=update

11 map class to tables using entity

package com.example.demo;

import javax.persistence.Entity;
import javax.persistence.Id;

// 指定在数据库中的表名
@Entity(name = "Student")
public class Student {

    @Id
    private Long id;
    // ...
}

12 sequence generator and value


13 column

package com.example.demo;

import javax.persistence.*;

import static javax.persistence.GenerationType.SEQUENCE;

// 指定在数据库中的表名
@Entity(name = "Student")
public class Student {

    @Id
    @SequenceGenerator(
            name = "student_sequence",
            sequenceName = "student_sequence",
            allocationSize = 1  // 每次自增多少
    )
    @GeneratedValue(
            strategy = SEQUENCE,
            // 使用上面我们自定义的生成器
            generator = "student_sequence"
    )
    @Column(
            name = "id",
            updatable = false
    )
    private Long id;

    @Column(
            name = "first_name",
            nullable = false,
            // 值类型
            columnDefinition = "TEXT"
    )
    private String firstName;

    @Column(
            name = "last_name",
            nullable = false,
            // 值类型
            columnDefinition = "TEXT"
    )
    private String lastName;

    /*
        Mysql数据库对于BLOB/TEXT这样类型的数据结构只能索引前N个字符。
        所以这样的数据类型不能作为主键,也不能是UNIQUE的。
        所以要换成VARCHAR,但是VARCHAR类型的大小也不能大于255,
        当VARCHAR类型的字段大小如果大于255的时候也会转换成小的TEXT来处理
     */
    @Column(
            name = "email",
            nullable = false,
            // 值类型
            columnDefinition = "varchar(255)",
            unique = true
    )
    private String email;

    @Column(name = "age",
        nullable = false)
    private Integer age;

    // ...
}


15 table and constraints

package com.example.demo;

import javax.persistence.*;

import static javax.persistence.GenerationType.SEQUENCE;

// 指定在数据库中的表名
@Entity(name = "Student")
@Table(
        name = "Student",
        uniqueConstraints = {
                @UniqueConstraint(
                        name = "student_email_unique", columnNames = "email"
                )
        }
)
public class Student {

    // ...

    @Column(
            name = "email",
            nullable = false,
            // 值类型
            columnDefinition = "varchar(255)"
//            unique = true
    )
    private String email;

   // ...
}

16 understanding repositories




17 studentRepository

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository studentRepository) {
        return args -> {
            Student maria = new Student(
                    "Maria",
                    "Jones",
                    "maria.jones@xx.com",
                    21
            );
            studentRepository.save(maria);
        };
    }
}


18 exploring repository methods

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository studentRepository) {
        return args -> {
            Student maria = new Student(
                    "Maria",
                    "Jones",
                    "maria.jones@xx.com",
                    21
            );
            Student ahmed = new Student(
                    "Ahmed",
                    "Ali",
                    "Ahmed.Ali@xx.com",
                    18
            );
            System.out.println("Adding maria and ahmed");
            studentRepository.saveAll(List.of(maria, ahmed)); // jdk 9

            System.out.println("Number of students: ");
            System.out.println(studentRepository.count());

            // 根据id获取
//            studentRepository.findById(2L).ifPresentOrElse(student -> {
//                System.out.println(student);
//            }, () -> {
//                System.out.println("Student with ID 2 not found");
//            });
            studentRepository.findById(2L)
                    .ifPresentOrElse(
                            System.out::println,
                            () -> System.out.println("Student with ID 2 not found")
                    );
            studentRepository.findById(3L)
                    .ifPresentOrElse(
                            System.out::println,
                            () -> System.out.println("Student with ID 2 not found")
                    );

            System.out.println("Select all students");
            List<Student> students = studentRepository.findAll();
            students.forEach(System.out::println);

            System.out.println("Delete maria");
            studentRepository.deleteById(1L);

            System.out.println("Number of students: ");
            System.out.println(studentRepository.count());
        };
    }
}

20 query methods

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository studentRepository) {
        return args -> {
            Student maria = new Student(
                    "Maria",
                    "Jones",
                    "maria.jones@xx.com",
                    21
            );
            Student ahmed = new Student(
                    "Ahmed",
                    "Ali",
                    "Ahmed.Ali@xx.com",
                    18
            );
            studentRepository.saveAll(List.of(maria, ahmed)); // jdk 9

            studentRepository.findStudentByEmail("ahmed.ali@xx.com")
                    .ifPresentOrElse(System.out::println,
                            ()-> System.out.println("Student with email ahmed.ali@xx.com not found"));
        };
    }
}
package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface StudentRepository extends JpaRepository<Student, Long> {
    Optional<Student> findStudentByEmail(String email);
}

21 query methods

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository studentRepository) {
        return args -> {
            Student maria = new Student(
                    "Maria",
                    "Jones",
                    "maria.jones@xx.com",
                    21
            );
            Student maria2 = new Student(
                    "Maria",
                    "Jones",
                    "maria2.jones@xx.com",
                    25
            );
            Student ahmed = new Student(
                    "Ahmed",
                    "Ali",
                    "Ahmed.Ali@xx.com",
                    18
            );
            studentRepository.saveAll(List.of(maria, maria2, ahmed)); // jdk 9

            studentRepository.findStudentByEmail("ahmed.ali@xx.com")
                    .ifPresentOrElse(System.out::println,
                            () -> System.out.println("Student with email ahmed.ali@xx.com not found"));

            studentRepository.findStudentByFirstNameEqualsAndAgeEquals(
                    "Maria", 21
            ).forEach(System.out::println);

            studentRepository.findStudentByFirstNameEqualsAndAgeIsGreaterThan(
                    "Maria", 18
            ).forEach(System.out::println);

            studentRepository.findStudentByFirstNameEqualsAndAgeIsGreaterThanEqual(
                    "Maria", 20
            ).forEach(System.out::println);
        };
    }
}
package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface StudentRepository extends JpaRepository<Student, Long> {
    Optional<Student> findStudentByEmail(String email);

    // =
    List<Student> findStudentByFirstNameEqualsAndAgeEquals(String firstName, Integer age);

    // >
    List<Student> findStudentByFirstNameEqualsAndAgeIsGreaterThan(String firstName, Integer age);

    // >=
    List<Student> findStudentByFirstNameEqualsAndAgeIsGreaterThanEqual(String firstName, Integer age);
}


22 jpql query methods

23 query

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.util.Optional;

public interface StudentRepository extends JpaRepository<Student, Long> {
    // 覆盖方法名,而用query里定义的sql
    @Query("SELECT s from Student s where s.email = ?1")
    Optional<Student> findStudentByEmail(String email);

    // =
    List<Student> findStudentByFirstNameEqualsAndAgeEquals(String firstName, Integer age);

    // >
    List<Student> findStudentByFirstNameEqualsAndAgeIsGreaterThan(String firstName, Integer age);

    // 现在用自定义的名称也没事
    @Query("select s from Student s where s.firstName = ?1 and s.age >= ?2")
    List<Student> findByFirstNameAndAgeGq(String firstName, Integer age);
}

24 native queries

query 里写的 jpql 是无法直接在数据库里执行的



使用原生sql

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
    // ...
    // >=
    @Query("select s from Student s where s.firstName = ?1 and s.age >= ?2")
    List<Student> findByFirstNameAndAgeGq(String firstName, Integer age);

    // 使用原生sql
    @Query(value = "select * from student where first_name = ?1 and age >= ?2"
            , nativeQuery = true)
    List<Student> findByFirstNameAndAgeGqNative(String firstName, Integer age);
}

使用原生 sql,如果换了数据库,可能就会无法使用,所以建议使用 jpql

25 named parameters

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
    // ...
    @Query(value = "select * from student" +
            " where first_name = :firstName and age >= :age"
            , nativeQuery = true)
    List<Student> findByFirstNameAndAgeGqNative(
            @Param("firstName") String firstName,
            @Param("age") Integer age);
}

26 modifying

执行删除修改必需在事务中

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
    // ...
    // 表示不需要将结果映射为实体,只是对数据进行操作
    @Modifying
    @Transactional // 事务
    @Query("delete from Student u where u.id = ?1")
    int deleteStudentById(Long id);
}

27 installing faker

<dependency>
    <groupId>com.github.javafaker</groupId>
    <artifactId>javafaker</artifactId>
    <version>1.0.2</version>
</dependency>

28 saving random students using faker

package com.example.demo;

import com.github.javafaker.Faker;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository studentRepository) {
        return args -> {
            Faker faker = new Faker();
            for (int i = 0; i < 20; i++) {
                String firstName = faker.name().firstName();
                String lastName = faker.name().lastName();
                String email = String.format("%s.%s@xxx.com", firstName, lastName);
                Student student = new Student(
                        firstName,
                        lastName,
                        email,
                        faker.number().numberBetween(17, 55)
                );
                studentRepository.save(student);
            }
        };
    }
}

29 sort

package com.example.demo;

import com.github.javafaker.Faker;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.Sort;

import java.util.List;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository studentRepository) {
        return args -> {
            generateRandomStudents(studentRepository);
//            Sort sort = Sort.by(
//                    Sort.Direction.ASC, "firstName"
//            );
            Sort sort = Sort.by("firstName").ascending()
                    .and(Sort.by("age").descending());
            studentRepository.findAll(sort)
                    .forEach(student ->
                            System.out.println(
                                    student.getFirstName() + " " + student.getAge()
                            ));
        };
    }

    private void generateRandomStudents(StudentRepository studentRepository) {
        Faker faker = new Faker();
        for (int i = 0; i < 20; i++) {
            String firstName = faker.name().firstName();
            String lastName = faker.name().lastName();
            String email = String.format("%s.%s@xxx.com", firstName, lastName);
            Student student = new Student(
                    firstName,
                    lastName,
                    email,
                    faker.number().numberBetween(17, 55)
            );
            studentRepository.save(student);
        }
    }
}

30 paging and sorting

package com.example.demo;

import com.github.javafaker.Faker;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;

import java.util.List;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository studentRepository) {
        return args -> {
            generateRandomStudents(studentRepository);

            PageRequest page = PageRequest.of(
                    0, 5,
                    Sort.by("firstName").ascending());
            studentRepository.findAll(page);
            System.out.println(page);
        };
    }

    // ...
}

31 inspect paging and sorting in debug mode



relation

32 intro

33 student id card entity

package com.example.demo;

import javax.persistence.*;

import static javax.persistence.GenerationType.SEQUENCE;

@Entity(name = "StudentIdCard")
@Table(name = "StudentIdCard",
        uniqueConstraints = {
                @UniqueConstraint(
                        name = "student_id_card_number_unique",
                        columnNames = "card_number"
                )
        })
public class StudentIdCard {

    @Id
    @SequenceGenerator(
            name = "student_card_id_sequence",
            sequenceName = "student_card_id_sequence",
            allocationSize = 1
    )
    @GeneratedValue(
            strategy = SEQUENCE,
            generator = "student_card_id_sequence"
    )
    @Column(
            name = "id",
            updatable = false
    )
    private Long id;

    @Column(
            name = "card_number",
            nullable = false,
            length = 15

    )
    private String cardNumber;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCardNumber() {
        return cardNumber;
    }

    public void setCardNumber(String cardNumber) {
        this.cardNumber = cardNumber;
    }
}

34 one_to_one and joinColumn

// ...

public class StudentIdCard {
    // ...

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(
            name = "student_id",
            // 指向student.id
            referencedColumnName = "id"
    )
    private Student student;

    // ...
}

35 student_card repository

package com.example.demo;

import org.springframework.data.repository.CrudRepository;

public interface StudentIdCardRepository
        extends CrudRepository<StudentIdCard, Long> {
}


36 saving student_card

package com.example.demo;

import com.github.javafaker.Faker;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;

import java.util.List;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(
            StudentRepository studentRepository,
            StudentIdCardRepository studentIdCardRepository
    ) {
        return args -> {
            Faker faker = new Faker();
            String firstName = faker.name().firstName();
            String lastName = faker.name().lastName();
            String email = String.format("%s.%s@xxx.com", firstName, lastName);
            Student student = new Student(
                    firstName,
                    lastName,
                    email,
                    faker.number().numberBetween(17, 55)
            );
            StudentIdCard studentIdCard =
                    new StudentIdCard("123456789", student);

            studentIdCardRepository.save(studentIdCard);
        };
    }
    // ...
}

保持 idcard 的同时也保持 student



37 cascades type

为什么保持 idcard 也会保持 student,因为 cascade 的意思是传播操作,这里的 all 表示所有对关联表的操作都会传播到该实体


38 hibernate entity lifecycle

39 fetch type

// ...
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(
            StudentRepository studentRepository,
            StudentIdCardRepository studentIdCardRepository
    ) {
        return args -> {
            // ...
            studentIdCardRepository.findById(1L)
                    .ifPresent(System.out::println);
        };
    }

    // ...
}

如果报错 No default constructor,就给 StudentIdCard 加一个无参构造方法

默认 fetch 模式是 eager,会把关联的实体一起查出来

40 uni vs BiDirectional(双向) on 1to1 relationship

// ...
public class Student {
    // ...

    // (和idcard实体)形成双向关系
    @OneToOne(mappedBy = "student")
    private StudentIdCard studentIdCard;

    // ...
}

双向关系的实体不要都在 tostring 里打印对方,会报错 StackOverflow



41 orphan removal

希望删除 idcard 的时候不级联删除 student,而删除 student 的时候级联删除 idcard

42 foreign key

43-44 exercise: book entity

package com.example.demo;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity(name = "book")
@Table(name = "book")
public class Book {
    @Id
    @SequenceGenerator(
            name = "book_sequence",
            sequenceName = "book_sequence",
            allocationSize = 1
    )
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "book_sequence"
    )
    @Column(name = "id", updatable = false)
    private Long id;

    @Column(
            name = "created_at",
            nullable = false,
            columnDefinition = "TIMESTAMP WITHOUT TIME ZONE"
    )
    private LocalDateTime createdAt;

    @Column(
            name = "book_name",
            nullable = false
    )
    private String bookName;

    @ManyToOne
    @JoinColumn(
            name = "student_id",
            nullable = false,
            // 指向student.id
            referencedColumnName = "id",
            foreignKey = @ForeignKey(
                    name = "student_book_fk"
            )
    )
    private Student student;

    public Book() {
    }

    public Book(LocalDateTime createdAt, String bookName) {
        this.createdAt = createdAt;
        this.bookName = bookName;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", createdAt=" + createdAt +
                ", bookName='" + bookName + '\'' +
                ", student=" + student +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
}

45 many to bidirectional

// ...

public class Student {

    // ...


    @OneToMany(
            // -> book.student
            mappedBy = "student",
            orphanRemoval = true,
            // save & delete
            cascade = {CascadeType.PERSIST,CascadeType.REMOVE}
    )
    private List<Book> books = new ArrayList<>();

    // ...

    public void addBook(Book book){
        if(!this.books.contains(book)){
            this.books.add(book);
            book.setStudent(this);
        }
    }

    public void removeBook(Book book){
        if(this.books.contains(book)){
            this.books.remove(book);
            book.setStudent(null);
        }
    }
    // ...
}

46 test many to bidirectional

check the manual that corresponds to your MySQL server version for the right syntax to use near ‘WITHOUT TIME ZONE not null

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(
            StudentRepository studentRepository,
            StudentIdCardRepository studentIdCardRepository
    ) {
        return args -> {
            Faker faker = new Faker();
            String firstName = faker.name().firstName();
            String lastName = faker.name().lastName();
            String email = String.format("%s.%s@xxx.com", firstName, lastName);
            Student student = new Student(
                    firstName,
                    lastName,
                    email,
                    faker.number().numberBetween(17, 55)
            );

            student.addBook(
                    new Book(LocalDateTime.now().minusDays(4),
                            "Clean Code")
            );
            student.addBook(
                    new Book(LocalDateTime.now(),
                            "Think and Grow Rich")
            );
            student.addBook(
                    new Book(LocalDateTime.now().minusYears(1),
                            "Spring Data JPA")
            );

            StudentIdCard studentIdCard =
                    new StudentIdCard("123456789", student);

            studentIdCardRepository.save(studentIdCard);
        };
    }
}

47 understanding eager fetch type for one to many



应该使用 lazy,因为如果关联的数据量大,会减慢程序运行速度

48 save student instead of idcard

org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.example.demo.Student.studentIdCard -> com.example.demo.StudentIdCard


49 course entity

package com.example.demo;

import javax.persistence.*;

@Entity(name = "Course")
@Table(name = "course")
public class Course {

    @Id
    @SequenceGenerator(
            name = "course_sequence",
            sequenceName = "course_sequence",
            allocationSize = 1
    )
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "course_sequence"
    )
    @Column(name = "id", updatable = false)
    private Long id;

    @Column(
            name = "department",
            nullable = false,
            columnDefinition = "TEXT"
    )
    private String department;


    @Column(
            name = "name",
            nullable = false,
            columnDefinition = "TEXT"
    )
    private String name;

    @Override
    public String toString() {
        return "Course{" +
                "id=" + id +
                ", department='" + department + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

    public Course(String department, String name) {
        this.department = department;
        this.name = name;
    }

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Course() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

50 many to many join columns

// ...
public class Student {
    // ...
    @ManyToMany(
            cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
    )
    @JoinTable(
            // student <-> course 的中间表
            name = "enrolment",
            joinColumns = @JoinColumn(
                    name = "student_id",
                    foreignKey = @ForeignKey(name = "enrolment_student_fk")
            ),
            // 反 join columns
            inverseJoinColumns = @JoinColumn(
                    name = "course_id",
                    foreignKey = @ForeignKey(name = "enrolment_course_fk")
            )
    )
    private List<Course> courses = new ArrayList<>();

    // ...
}

51 many to many mapper by

// 给course加字段
    @ManyToMany(
        // 指向student.courses
            mappedBy = "courses"
    )
    private List<Student> students = new ArrayList<>();

52 methods to add and remove course

在 student 里添加

    public void enrolToCourse(Course course) {
        courses.add(course);
        course.getStudents().add(this);
    }

    public void removeCourse(Course course) {
        courses.remove(course);
        course.getStudents().remove(this);
    }

53 many to many relationship in action

    @Bean
    CommandLineRunner commandLineRunner(
            StudentRepository studentRepository,
            StudentIdCardRepository studentIdCardRepository
    ) {
        return args -> {
            Faker faker = new Faker();
            String firstName = faker.name().firstName();
            String lastName = faker.name().lastName();
            String email = String.format("%s.%s@xxx.com", firstName, lastName);
            Student student = new Student(
                    firstName,
                    lastName,
                    email,
                    faker.number().numberBetween(17, 55)
            );

            student.addBook(
                    new Book(LocalDateTime.now().minusDays(4),
                            "Clean Code")
            );
            student.addBook(
                    new Book(LocalDateTime.now(),
                            "Think and Grow Rich")
            );
            student.addBook(
                    new Book(LocalDateTime.now().minusYears(1),
                            "Spring Data JPA")
            );

            StudentIdCard studentIdCard =
                    new StudentIdCard("123456789", student);

            student.setStudentIdCard(studentIdCard);

            student.enrolToCourse(
                    new Course("Computer Science", "IT"));


            student.enrolToCourse(
                    new Course("Amigoscode Spring Data JPA", "IT"));

            studentRepository.save(student);

//            studentIdCardRepository.findById(1L)
//                    .ifPresent(System.out::println);
//
            studentRepository.findById(1L)
                    .ifPresent(s -> {
                        System.out.println("fetch book lazy...");
                        List<Book> books = student.getBooks();
                        books.forEach(book -> {
                            System.out.println(s.getFirstName() + " borrowed " + book.getBookName());
                        });
                    });

//            studentRepository.deleteById(1L);
        };
    }

54 embeddable

package com.example.demo;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;

@Embeddable
public class EnrolmentId implements Serializable {

    @Column(name = "student_id")
    private Long studentId;

    @Column(name = "course_id")
    private Long courseId;

    public EnrolmentId() {
    }

    public EnrolmentId(Long studentId, Long courseId) {
        this.studentId = studentId;
        this.courseId = courseId;
    }

    public Long getStudentId() {
        return studentId;
    }

    public void setStudentId(Long studentId) {
        this.studentId = studentId;
    }

    public Long getCourseId() {
        return courseId;
    }

    public void setCourseId(Long courseId) {
        this.courseId = courseId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        EnrolmentId that = (EnrolmentId) o;
        return Objects.equals(studentId, that.studentId) && Objects.equals(courseId, that.courseId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(studentId, courseId);
    }
}

生成equal和hash方法全部默认选项

55 embeddable and maps id

package com.example.demo;

import javax.persistence.*;

@Entity(name = "Enrolment")
@Table(name = "enrolment")
public class Enrolment {

    @EmbeddedId
    // 复合主键
    private EnrolmentId id;

    @ManyToOne
    // enrolmentId.studentId
    @MapsId("studentId")
    @JoinColumn(name = "student_id")
    private Student student;


    @ManyToOne
    @MapsId("courseId")
    @JoinColumn(name = "course_id")
    private Course course;

    public Enrolment(Student student, Course course) {
        this.student = student;
        this.course = course;
    }

    public Enrolment() {
    }

    public EnrolmentId getId() {
        return id;
    }

    public void setId(EnrolmentId id) {
        this.id = id;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Course getCourse() {
        return course;
    }

    public void setCourse(Course course) {
        this.course = course;
    }
}

修改 student 的 course 字段

    @OneToMany(
            cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
            // -> enrolment.student
            mappedBy = "student"
    )
    private List<Enrolment> enrolments = new ArrayList<>();

    public void addEnrolment(Enrolment enrolment) {
        if (!this.enrolments.contains(enrolment)) {
            enrolments.add(enrolment);
        }
    }

    public void removeEnrolment(Enrolment enrolment) {
        enrolments.remove(enrolment);
    }

修改 course 的 student 字段

    @OneToMany(
            // enrolment.course
            mappedBy = "course",
            cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
    )
    private List<Enrolment> enrolments = new ArrayList<>();

    public void addEnrolment(Enrolment enrolment) {
        if (!this.enrolments.contains(enrolment)) {
            enrolments.add(enrolment);
        }
    }

    public void removeEnrolment(Enrolment enrolment) {
        enrolments.remove(enrolment);
    }

57 testing changes

需要手动指定 id(复合主键)

    @Bean
    CommandLineRunner commandLineRunner(
            StudentRepository studentRepository,
            StudentIdCardRepository studentIdCardRepository
    ) {
        return args -> {
            Faker faker = new Faker();
            String firstName = faker.name().firstName();
            String lastName = faker.name().lastName();
            String email = String.format("%s.%s@xxx.com", firstName, lastName);
            Student student = new Student(
                    firstName,
                    lastName,
                    email,
                    faker.number().numberBetween(17, 55)
            );

            student.addBook(
                    new Book(LocalDateTime.now().minusDays(4),
                            "Clean Code")
            );
            student.addBook(
                    new Book(LocalDateTime.now(),
                            "Think and Grow Rich")
            );
            student.addBook(
                    new Book(LocalDateTime.now().minusYears(1),
                            "Spring Data JPA")
            );

            StudentIdCard studentIdCard =
                    new StudentIdCard("123456789", student);

            student.setStudentIdCard(studentIdCard);

//            student.enrolToCourse(
//                    new Course("Computer Science", "IT"));
//
//
//            student.enrolToCourse(
//                    new Course("Amigoscode Spring Data JPA", "IT"));

            student.addEnrolment(new Enrolment(
                    new EnrolmentId(1L, 1L),
                    student, new Course("Computer Science", "IT")
            ));

            student.addEnrolment(new Enrolment(
                    new EnrolmentId(1L, 2L),
                    student, new Course("Amigoscode Spring Data JPA", "IT")
            ));

            studentRepository.save(student);

//            studentIdCardRepository.findById(1L)
//                    .ifPresent(System.out::println);
//
            studentRepository.findById(1L)
                    .ifPresent(s -> {
                        System.out.println("fetch book lazy...");
                        List<Book> books = student.getBooks();
                        books.forEach(book -> {
                            System.out.println(s.getFirstName() + " borrowed " + book.getBookName());
                        });
                    });

//            studentRepository.deleteById(1L);
        };
    }
    @Column(
            name = "created_at",
            nullable = false,
            columnDefinition = "TIMESTAMP"
    )
    private LocalDateTime createdAd;

    public Enrolment(EnrolmentId id, Student student, Course course, LocalDateTime time) {
        this.id = id;
        this.student = student;
        this.course = course;
        this.createdAd = time;
    }

    public LocalDateTime getCreatedAd() {
        return createdAd;
    }

    public void setCreatedAd(LocalDateTime createdAd) {
        this.createdAd = createdAd;
    }

59 foreign key

    @ManyToOne
    // enrolmentId.studentId
    @MapsId("studentId")
    @JoinColumn(
            name = "student_id",
            foreignKey = @ForeignKey(
                    name = "enrolment_student_id_fk"
            )
    )
    private Student student;

    @ManyToOne
    @MapsId("courseId")
    @JoinColumn(
            name = "course_id",
            foreignKey = @ForeignKey(
                    name = "enrolment_course_id_fk"
            )
    )
    private Course course;

60 database transaction

事务保证数据一致性

61 working with transaction

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Repository
@Transactional(readOnly = true)
public interface StudentRepository extends JpaRepository<Student, Long> {
    // ...

    // 表示不需要将结果映射为实体,只是对数据进行操作
    @Modifying
    @Transactional // 事务,默认非只读
    @Query("delete from Student u where u.id = ?1")
    int deleteStudentById(Long id);
}






  目录