Spring中封装了Hibernate,虽然极大的方便了我们的开发。但是有些问题还是需要我们自己去读一下Hiberate才能明白其中的道理。
Create the domain model create entity and Repositiry and use @ManyToMany 让我们从@ManyToMany注解入手,一起来看一下我们能学到什么吧! IDE:idea 框架:Spring 让我们先举一个简单的例子.看一下域模型 我们说学生和老师是多对多的关系,一个学生会有多个老师,一个老师会有多个学生。下面就让我们来建立实体吧!scr/main/java/com.mengyunzhi/repository/Student.java
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
@Entity
public class Student {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//姓名
private String name;
//班级
private String klass;
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(
name = "student_teacher",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "teacher_id")
)
private Set<Teacher> teachers = new HashSet<>();
public Student() {
}
public Student(String name, String klass, Set<Teacher> teachers) {
this.name = name;
this.klass = klass;
this.teachers = teachers;
}
//getter and setter
........
}
scr/main/java/com.mengyunzhi/repository/StudentRepository.java1
2
public interface StudentRepository extends CrudRepository<Student, Long> {
}
scr/main/java/com.mengyunzhi/repository/Teacher.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
public class Teacher {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//姓名
private String name;
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(
name = "student_teacher",
joinColumns = @JoinColumn(name = "teacher_id"),
inverseJoinColumns = @JoinColumn(name = "student_id")
)
private Set<Student> students = new HashSet<>();
public Teacher() {
}
public Teacher(String name, Set<Student> students) {
this.name = name;
this.students = students;
}
//getter and setter
.........
}
scr/main/java/com.mengyunzhi/repository/TeacherRepository.java1
2
public interface TeacherRepository extends CrudRepository<Teacher, Long> {
}
execute the application 我们可以看到数据库中除了teacher和student这两张表,还多了student_teacher者张表。这是因为常规的@ManyToMany会隐藏这张链接表,没有对应的Java类1
2
3
4
5
6
+------------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------+------+-----+---------+-------+
| teacher_id | bigint(20) | NO | PRI | NULL | |
| student_id | bigint(20) | NO | PRI | NULL | |
+------------+------------+------+-----+---------+-------+
Unit test unit test scr/test/java/com.mengyunzhi/repository/Student.java1
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
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentRepositoryTest {
@Autowired //自动实例化
private StudentRepository studentRepository;
@Autowired
private TeacherRepository teacherRepository;
static private Logger logger = Logger.getLogger(StudentRepositoryTest.class.getName());
@Test
public void saveTest() {
logger.info("--------------------student save test-----------------------");
logger.info("新建新建一个教师实体");
Teacher teacher = new Teacher();
logger.info("保存教师实体");
teacherRepository.save(teacher);
logger.info("新建一个学生实体");
Student student = new Student();
logger.info("关联教师");
Set<Teacher> teachers = new HashSet<>();
teachers.add(teacher);
student.setTeachers(teachers);
logger.info("保存学生实体");
studentRepository.save(student);
logger.info("断言保存成功");
assertThat(student.getId()).isNotNull();
}
}
然后我们会发现如下的报错信息:
1
2
3
4
5
6
7
8
2017-06-06 19:09:02.445 INFO 23472 --- [ main] c.m.repository.StudentRepositoryTest : --------------------student save test-----------------------
2017-06-06 19:09:02.447 INFO 23472 --- [ main] c.m.repository.StudentRepositoryTest : 新建新建一个教师实体
2017-06-06 19:09:02.447 INFO 23472 --- [ main] c.m.repository.StudentRepositoryTest : 保存教师实体
2017-06-06 19:09:02.582 INFO 23472 --- [ main] c.m.repository.StudentRepositoryTest : 新建一个学生实体
2017-06-06 19:09:02.582 INFO 23472 --- [ main] c.m.repository.StudentRepositoryTest : 关联教师
2017-06-06 19:09:02.583 INFO 23472 --- [ main] c.m.repository.StudentRepositoryTest : 保存学生实体
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.mengyunzhi.repository.Teacher; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.mengyunzhi.repository.Teacher
然后我们大体翻译一下错误就会发现原来是将处于分离状态的实体传递给持久化。然后我们会发现原因出现在了这句话上:1
@ManyToMany(cascade = CascadeType.PERSIST)
这句话大体中 cascade = CascadeType.PERSIST的意思是当该集合引用新Teacher是,Hibernate会让其持久化。但是为什么会错呢?让我们来分析一下原因:
entity status 状态迁移 new Java操作符创建的实例是瞬时的。也就是说当这句话Teacher teacher = new Teacher()的时候,teacher是瞬时实例,我们可以用EntityManager具有数据#persist()方法能够是teacher这个对象变得持久化。这里的持久化你可以简单的理解成保存到数据库的过程。
持久化状态 持久化状态是我们能够对数据进行操作CRUD操作的时候对数据的一个事务过程。
移除状态 当我们数据库中的一条记录删除后,这个实体对象就会处于移除状态
分离状态 当我们通过唯一标识从数据库里面取出数据findOne(id), 保存数据save(teacher)的时候,这时候Hiberinate已经关闭了持久化上下文,实体对象将处于游离状态。而此时我们需要刷新数据库中的记录,我们就需要调用merge()方法在新的工作单元中进行修改。也就是刷新操作。
bug修复 如果对于一个游离态(detached)的实体执行persist操作,则抛出IllegalArgumentException异常。所有我们就需要将1
@ManyToMany(cascade = CascadeType.PERSIST)
改为1
@ManyToMany(cascade = CascadeType.MERGE)
然后我们就会发现能够单元测试成功啦!哈哈哈
级联操作 级联更新 我知道我写的很不到位,可能你还没有感受到我讲这些的目的,我讲这些就是为了引出后面的级联操作操作。但是很遗憾好像没有达到目的。 然我们来看看Hibernate级联更新:测试代码做如下修改,然后我们需要打一个断点在如下位置:
scr/test/java/com.mengyunzhi/repository/Student.java
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
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentRepositoryTest {
@Autowired //自动实例化
private StudentRepository studentRepository;
@Autowired
private TeacherRepository teacherRepository;
static private Logger logger = Logger.getLogger(StudentRepositoryTest.class.getName());
@Test
public void saveTest() {
logger.info("--------------------student save test-----------------------");
logger.info("新建新建一个教师实体");
Teacher teacher = new Teacher();
logger.info("保存教师实体");
teacherRepository.save(teacher);
logger.info("新建一个学生实体");
Student student = new Student();
logger.info("关联教师");
Set<Teacher> teachers = new HashSet<>();
teachers.add(teacher);
student.setTeachers(teachers);
logger.info("更新教师");
teacher.setName("TeacherPan");
teacherRepository.save(teacher);
logger.info("保存学生实体");
studentRepository.save(student);
logger.info("断言保存成功");
assertThat(student.getId()).isNotNull();
}
}
然后运行程序。 程序停止在断点处,我们来查teacher表1
2
3
4
5
+----+------+
| id | name |
+----+------+
| 1 | NULL |
+----+------+
然后接着运行程序,然后你再次查看teacher表的时候就会发现:
1
2
3
4
5
+----+------------+
| id | name |
+----+------------+
| 1 | TeacherPan |
+----+------------+
对没有错,当我们更新teacher的时候,如果teachers中有此游离态状态的实体对象,这样,我们保存student就会自动更新这个teacher实体对象。
级联删除 cascade = CascadeType.REMOVE代表级联删除。 然后我们可以修改scr/test/java/com.mengyunzhi/repository/Student.java文件,并打一个断点。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
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentRepositoryTest {
@Autowired //自动实例化
private StudentRepository studentRepository;
@Autowired
private TeacherRepository teacherRepository;
static private Logger logger = Logger.getLogger(StudentRepositoryTest.class.getName());
@Test
public void saveTest() {
logger.info("--------------------student save test-----------------------");
logger.info("新建新建一个教师实体");
Teacher teacher = new Teacher();
logger.info("保存教师实体");
teacherRepository.save(teacher);
logger.info("新建一个学生实体");
Student student = new Student();
logger.info("关联教师");
Set<Teacher> teachers = new HashSet<>();
teachers.add(teacher);
student.setTeachers(teachers);
logger.info("保存学生实体");
studentRepository.save(student);
logger.info("断言保存成功");
assertThat(student.getId()).isNotNull(); //此处打断点
logger.info("删除教师");
teacherRepository.delete(teacher.getId());
}
}
然后运行程序。 程序停止在断点处,我们来查student_teacher表
1
2
3
4
5
+------------+------------+
| teacher_id | student_id |
+------------+------------+
| 1 | 1 |
+------------+------------+
然后接着运行程序,再次查看student_teacher表
发现数据也被删除了。没有错,这就是级联删除。嘻嘻!
级联保存 CascadeType.PERSIST:级联新增(又称级联保存) 自己写代码吧!
级联所有 如果你向一句话完成所有级联,那你就用CascadeType.ALL吧!
Code Download 参考文章 《Hibernate实战》第八章第三节,第十章Spring Data Jpa 实体状态分析 Hibernate cascade级联属性的CascadeType的用法 CSDNJPA——管理实体