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 是无法直接在数据库里执行的
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);
}
}
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;
}
}
56 link entities to bridge table
修改 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);
};
}
58 adding extra column to link table
@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);
}