Hibernate的一对多关联映射
案例:一个老师对应多个学生
1.创建实体类
@Data
@ToString
public class Teacher{
private Long t_id;
private String name;
private Set<Student> studentSet = new HashSet();
}
@Data
@ToString
public class Student{
private Long s_id;
private String name;
private Teacher teacher;
}
2.配置实体类的映射配置文件
一的一方:
<class name="com.baidu.hibernate.domain.Teacher" table="tb_teacher">
<id name="t_id" column="t_id">
<generator class="identity"></generator>
</id>
<property name="name" column="name"></property>
<set name="studentSet">
<key column="fk_id"></key>
<one-to-many class="com.baidu.hibernate.domain.Student"></one-to-many>
</set>
</class>
多的一方:
<class name="com.baidu.hibernate.domain.Student" table="tb_teacher">
<id name="s_id" column="s_id">
<generator class="identity"></generator>
</id>
<property name="name" column="name"></property>
<many-to-one name="teacher" column="fk_id" class="com.baidu.hibernate.domain.Teacher"> </may-to-one>
</class>
3.在hibernate.cfg.xml中引入: Teacher.hbm.xml和Student.hbm.xml
<mapping resource="com/baidu/hibernate/domain/Teacher.hbm.xml"></mapping>
<mapping resource="com/baidu/hibernate/domain/Student.hbm.xml"></mapping>
4.测试保存,结果:一的一方和多的一方都必须为持久态,也就是都要执行session.save();如果只对一方进行保存,另一方不保存就会报异常TransientObjectException;即一个持久态对象关联了一个瞬时态对象,就会报错
Teacher t1 = new Teacher();
t1.setName("张三");
Student s1 = new Student();
s1.setName("李四");
t1.getStudentSet.add(s1);
session.save(t1);
session.save(s1);
少save一个对象,都会报错
5.解决方案:级联操作(cascade)
级联操作是指当主控方执行DDL时,与其关联的被控方也执行相应的DDL操作
步骤:
修改Teacher.hbm.xml或者Student.hbm.xml
Teacher.hbm.xml:
在set标签上加 cascade="save-update" 属性
Student.hbm.xml:
在many-to-one标签上加 cascade="save-update" 属性
如果在Teacher.hbm.xml中加上级联操作,当执行session.save(teacher)时,会自动对student也进行保存;如果在Student.hbm.xml中加上级联操作,当执行session.save(student)时,会自动对teacher也进行保存;
6.级联操作扩展
<set ... cascade="save-update,delete"></set>
save-update 级联保存或修改
delete 级联删除,要想获得级联删除的效果,session.delete()方法删除的必须是持久态对象,如果是自己new出来的瞬时态/托管态对象,由于外键属性中没有值,将达不到级联删除的效果
7.放弃外键维护inverse
当我们保存一组被外键关联了的数据时,会发现双方都对外键进行了维护,这样影响了性能;所以通常一对多的情况下,会让一的一方放弃对外键的维护,在Customer.hbm.xml中set标签上加inverse属性(默认是false),改成inverse="true"
8.cascade跟inverse的区别:
cascade强调的是级联操作,操作一个对象的同时操作关联的对象
inverse强调的是对外键的维护权
一对多双方共同维护外键,发送两条sql影响性能
多对多共同维护外键,就会报错,因为同一组数据会被插入两次,
Hibernate的多对多关系映射
案例:一个用户对应多个角色,一个角色也对应多个用户
1.创建实体类
@Data
@ToString
public class User{
private Long u_id;
private String u_name;
private Set<Role> roleSet = new HashSet();
}
@Data
@ToString
public class Role{
private Long r_id;
private String r_name;
private Set<User> userSet = new HashSet();
}
2.配置实体类的映射配置文件
<class name="com.baidu.hibernate.domain.User" table="tb_user">
<id name="u_id" column="u_id">
<generator class="identity"></generator>
</id>
<property name="u_name" column="u_name"></property>
<set name="userSet" table="tb_user_role">
<key column="user_no"></key>
<many-to-many class="com.baidu.hibernate.domain.Role" column="role_no">
</many-to-many>
</set>
</class>
<class name="com.baidu.hibernate.domain.Role" table="tb_role">
<id name="r_id" column="r_id">
<generator class="identity"></generator>
</id>
<property name="r_name" column="r_name"></property>
<set name="roleSet" table="tb_user_role">
<key column="role_no"></key>
<many-to-many class="com.baidu.hibernate.domain.User" column="user_no">
</many-to-many>
</set>
</class>
3.在hibernate.cfg.xml中引入: User.hbm.xml和Role.hbm.xml
<mapping resource="com/baidu/hibernate/domain/User.hbm.xml"></mapping>
<mapping resource="com/baidu/hibernate/domain/Role.hbm.xml"></mapping>
4.多对多的常用操作:
* 给某个用户添加角色
User user = session.get(User.class,1L);
Role role = session.get(Role.class,1L);
user.getRoleSet().add(role);
transaction.commit();
* 删除某个用户的角色
User user = session.get(User.class,1L);
Role role = session.get(Role.class,1L);
user.getRoleSet().romove(role);
transaction.commit();
* 改变某个用户的角色
User user = session.get(User.class,1L);
Role role1 = session.get(Role.class,1L);
Role role2 = session.get(Role.class,2L);
user.getRoleSet().romove(role1);
user.getRoleSet().add(role2);
transaction.commit();
表关系映射注意事项:
1.在多对多的表关系下,必须有一方放弃对外键的维护,否则双方都在中间表insert一条相同的数据,会导致异常;如果是一对多,双方都不放弃对外键的维护,最终一条insert和update,只会影响性能,但是不会造成异常
2.当执行保存操作时,如果一个对象中关联了另外一张表的对象,那么这两个对象都必须执行save方法,否则报TransientObjectException异常
3.在多对多的表关系下,不能双方都开启级联删除,否则可能会导致所有有关联的数据全部被删除
4.在多对多的表关系下,如果放弃对外键的维护,这一方将不能操作中间表;而一对多中,如果放弃外键的维护,这一方将不能操作外键
5.级联和外检维护权测试:
User user = session.get(User.class, 1L);
session.delete(user);
* User : inverse = "false" ;Role: inverse = "false"
sql: delete from tb_user_role where user_no = ?
delete from tb_user where user_id = ?
删除成功,删除tb_user/tb_user_role
* User : inverse = "true" ,Role: inverse = "false"
sql: delete from tb_user where user_id = ?
删除失败,user表放弃了对外键的维护,而user表中的数据受中间表的约束,因此删除失败
* User : inverse = "true" , cascade = "delete" ;Role: inverse = "false"
sql: delete from tb_user_role where role_no = ?
delete from tb_role where r_id = ?
delete from tb_user where u_id = ?
删除成功,由于对User设置了级联,user的持久态对象中保存了关联的roleSet,而role拥有维护外键的权限,根据roleSet删除中间表的关联数据,自然也删除了对tb_user表的约束,因此都能成功,删除tb_user/tb_user_role/tb_role
* User : inverse = "true" , cascade = "delete" ;Role: inverse = "true"
删除失败,双方都放弃对外键的维护,都被中间表约束
6.配置实体类映射配置文件多表对应关系的三大属性:name-->column-->class