【Hibernate】Re07 关系映射处理
一、单向多对一关系映射处理
演示案例列举了员工与部门的关系,一个部门下具有多个员工,相反的一个员工只隶属于一个部门下面
Maven依赖坐标:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-agroal</artifactId> <version>5.4.21.Final</version> </dependency> <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-c3p0 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> <version>5.4.21.Final</version> </dependency>
如果缺组件,就再补上这个:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> <version>5.4.21.Final</version> </dependency>
部门类编写:
package cn.zeal4j.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 19:51 */ @Data @AllArgsConstructor @NoArgsConstructor public class Department implements Serializable { private static final long serialVersionUID = -2834030425366265977L; private Integer departmentId; private String departmentName; private String departmentLocation; }
对应的部门Hibernate映射文件:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.zeal4j.domain" > <!-- 实体类包映射 --> <class name="Department" table="department"> <id name="departmentId" column="department_id" type="java.lang.Integer" access="property" > <generator class="increment" /> </id> <property name="departmentName" column="department_name" type="java.lang.String" access="property"/> <property name="departmentLocation" column="department_location" type="java.lang.String" access="property" /> </class> </hibernate-mapping>
员工类编写:
package cn.zeal4j.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 19:59 */ @Data @AllArgsConstructor @NoArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = -6524261539072181203L; private Integer employeeNo; private String employeeName; private Department department;// 多个员工中的每一个员工对应一个部门 }
对应的员工Hibernate映射文件:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.zeal4j.domain" > <!-- 实体类包映射 --> <class name="Employee" table="employee"> <id name="employeeNo" column="employee_no" type="java.lang.Integer" access="property" > <generator class="increment" /> </id> <property name="employeeName" column="employee_name" type="java.lang.String" access="property"/> <many-to-one name="department" column="department_no" class="Department" /> </class> </hibernate-mapping>
Hibernate核心配置文件编写:
注意数据库hibernate要存在!!!
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 基本链接参数 --> <property name="connection.url">jdbc:mysql://localhost:3308/hibernate?serverTimezone=Asia/Shanghai</property> <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property> <property name="connection.username">root</property> <property name="connection.password">123456</property> <!-- c3p0数据源配置 --> <property name="hibernate.c3p0.max_size">10</property> <property name="hibernate.c3p0.min_size">5</property> <property name="hibernate.c3p0.idle_test_period">2000</property> <property name="hibernate.c3p0.timeout">2000</property> <property name="dialect">org.hibernate.dialect.MySQL57Dialect</property> <!-- 数据库版本方言 --> <property name="show_sql">true</property> <!-- 是否让控制台输出SQL语句 --> <property name="format_sql">true</property> <!-- 控制台输出的SQL格式化 --> <property name="hbm2ddl.auto">update</property> <!-- 数据库表的生成策略 --> <mapping resource="hibernate/mapping/Department.hbm.xml"/> <mapping resource="hibernate/mapping/Employee.hbm.xml" /> </session-factory> </hibernate-configuration>
工具类编写:
package cn.zeal4j.util; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 18:43 */ public class HibernateUtil { static { final StandardServiceRegistry registry = new StandardServiceRegistryBuilder(). configure("hibernate/hibernate.cfg.xml").build(); sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory(); } private HibernateUtil() {} private static final SessionFactory sessionFactory; public static Session getSession() { return sessionFactory.openSession(); } public static void destroyFactory() { sessionFactory.close(); } }
测试类编写:
package cn.zeal4j.test; import cn.zeal4j.domain.Department; import cn.zeal4j.domain.Employee; import cn.zeal4j.util.HibernateUtil; import org.hibernate.Session; import org.hibernate.Transaction; import org.junit.Test; import java.io.Serializable; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 20:30 */ public class HibernateTest { @Test public void manyToOneTest() { Session session = HibernateUtil.getSession(); Transaction transaction = session.beginTransaction(); Employee employee = new Employee(null, "李四", null); Department department = new Department(null, "行政部", "地址位置"); employee.setDepartment(department); Serializable save = session.save(department); // 注意,多对一的一要先存在,因为两表关系Hibernate会自动创建强制外键约束 Serializable save2 = session.save(employee); // 有一的一方存在,多的一方才能够成功绑定外键 System.out.println(save); // 返回的序列化对象是映射对象的主键值 System.out.println(save2); transaction.commit(); // 若没发生异常,表示操作成功,提交事务 session.close(); // 资源释放 HibernateUtil.destroyFactory(); } }
运行结果:
先是数据表的创建
Hibernate: create table department ( department_id integer not null, department_name varchar(255), department_location varchar(255), primary key (department_id) ) engine=InnoDB Hibernate: create table employee ( employee_no integer not null, employee_name varchar(255), department_no integer, primary key (employee_no) ) engine=InnoDB Hibernate: alter table employee add constraint FKawh85bpyoqubu0i27ud5ufld9 foreign key (department_no) references department (department_id) 九月 26, 2020 8:38:38 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] Hibernate: select max(employee_no) from employee
然后是记录的插入:
Hibernate: select max(department_id) from department Hibernate: select max(employee_no) from employee 2 2 Hibernate: insert into department (department_name, department_location, department_id) values (?, ?, ?) Hibernate: insert into employee (employee_name, department_no, employee_no) values (?, ?, ?)
两表的字段也是正常的:
查询方法:
@Test public void manyToOneQuery() { Session session = HibernateUtil.getSession(); Department department = new Department(); department.setDepartmentId(1); final String HQL_QUERY = "FROM cn.zeal4j.domain.Employee AS emp WHERE emp.department = ?0 "; Query<Employee> employeeQuery = session.createQuery(HQL_QUERY, Employee.class); employeeQuery.setParameter(0, department); Employee employee = employeeQuery.uniqueResult(); System.out.println(employee); session.close(); HibernateUtil.destroyFactory(); }
在写SpringDataJPA的时候一样的问题,你也需要使用升级版的HQL语句
需要在参数占位符的后面指定该参数的索引值,虽然语法上被IDEA提示报错,但是运行时不会出现问题
二、双向一对多关系映射处理
在上述的基础上这样配置映射类:
package cn.zeal4j.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.HashSet; import java.util.Set; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 19:51 */ @Data @AllArgsConstructor @NoArgsConstructor public class Department implements Serializable { private static final long serialVersionUID = -2834030425366265977L; private Integer departmentId; private String departmentName; private String departmentLocation; private Set<Employee> employeeSet = new HashSet<>(); // 建立双向一对多关系映射 }
部门Hibernate映射文件:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.zeal4j.domain" > <!-- 实体类包映射 --> <class name="Department" table="department"> <id name="departmentId" column="department_id" type="java.lang.Integer" access="property" > <generator class="increment" /> </id> <property name="departmentName" column="department_name" type="java.lang.String" access="property"/> <property name="departmentLocation" column="department_location" type="java.lang.String" access="property" /> <set name="employeeSet" cascade="all"> <key column="department_no" /> <one-to-many class="Employee" /> </set> <!-- cascade属性 : 默认 none session操作当前对象时,忽略其他关联对象,例如不设置cascade或者设置为none值,这里的employeeSet就会忽略 插入与更新 save-update 级联相关的游离对象,在执行插入和更新的操作时 删除 delete 执行删除操作时,级联相关对象 全部级联 执行上述的操作时级联相关对象 --> </class> </hibernate-mapping>
测试插入:
首先因为更改了类的原因,之前的测试单元就会编译出错,直接注释掉,然后再清空之前的记录:
要注意外键的约束问题,不能直接情况部门表
TRUNCATE TABLE `employee`; SET FOREIGN_KEY_CHECKS = 0; TRUNCATE TABLE `department`;
或者删除表让Hibernate重建:
DROP TABLE `department`, `employee`
参考地址:
https://www.cnblogs.com/fu-yong/p/9889503.html
测试类编写:
@Test public void two_wayManyToOneTest() { Session session = HibernateUtil.getSession(); Transaction transaction = session.beginTransaction(); Department department = new Department(); department.setDepartmentLocation("xx省xx市xx区xx路xxx号xx栋xx单元"); department.setDepartmentName("开发部"); Employee employee1 = new Employee(null, "张三", null); // 如果注入了部门,插入将会造成堆溢出,指针嵌套了 Employee employee2 = new Employee(null, "李四", null); Employee employee3 = new Employee(null, "王五", null); department.getEmployeeSet().add(employee1); department.getEmployeeSet().add(employee2); department.getEmployeeSet().add(employee3); session.save(department); transaction.commit(); session.close(); HibernateUtil.destroyFactory(); }
测试结果:
Hibernate: select max(department_id) from department Hibernate: select max(employee_no) from employee Hibernate: insert into department (department_name, department_location, department_id) values (?, ?, ?) Hibernate: insert into employee (employee_name, department_no, employee_no) values (?, ?, ?) Hibernate: insert into employee (employee_name, department_no, employee_no) values (?, ?, ?) Hibernate: insert into employee (employee_name, department_no, employee_no) values (?, ?, ?) Hibernate: update employee set department_no=? where employee_no=? Hibernate: update employee set department_no=? where employee_no=? Hibernate: update employee set department_no=? where employee_no=?
效果:
级联删除操作:
测试类
@Test public void two_wayManyToOneTest2() { Session session = HibernateUtil.getSession(); Transaction transaction = session.beginTransaction(); // 要先从数据库中查询出来 Department department = session.get(Department.class, 1); // 再来删除 session.delete(department); transaction.commit(); session.close(); HibernateUtil.destroyFactory(); }
测试结果:
Hibernate: select department0_.department_id as departme1_0_0_, department0_.department_name as departme2_0_0_, department0_.department_location as departme3_0_0_ from department department0_ where department0_.department_id=? Hibernate: select employeese0_.department_no as departme3_1_0_, employeese0_.employee_no as employee1_1_0_, employeese0_.employee_no as employee1_1_1_, employeese0_.employee_name as employee2_1_1_, employeese0_.department_no as departme3_1_1_ from employee employeese0_ where employeese0_.department_no=? Hibernate: update employee set department_no=null where department_no=? Hibernate: delete from employee where employee_no=? Hibernate: delete from employee where employee_no=? Hibernate: delete from employee where employee_no=? Hibernate: delete from department where department_id=?
效果:
这里需要注意一些问题:
使用了Lombok不独立写出SET映射对象的方法,会导致Hibernate级联删除操作的时候指针嵌套,堆溢出错误
不使用查询出来的对象,而是随便new的一个对象,给定OID值取删除,并不会触发Hibernate级联删除操作
Hibernate根据给定的OID值删除部门记录本身之后不再级联下面的员工进行删除
Set关系维护:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.zeal4j.domain" > <!-- 实体类包映射 --> <class name="Department" table="department"> <id name="departmentId" column="department_id" type="java.lang.Integer" access="property" > <generator class="increment" /> </id> <property name="departmentName" column="department_name" type="java.lang.String" access="property"/> <property name="departmentLocation" column="department_location" type="java.lang.String" access="property" /> <set name="employeeSet" cascade="all" inverse="false" > <key column="department_no" /> <one-to-many class="Employee" /> </set> <!-- cascade属性 : 默认 none session操作当前对象时,忽略其他关联对象,例如不设置cascade或者设置为none值,这里的employeeSet就会忽略 插入与更新 save-update 级联相关的游离对象,在执行插入和更新的操作时 删除 delete 执行删除操作时,级联相关对象 全部级联 执行上述的操作时级联相关对象 --> <!-- inverse属性: 该属性用以指定关联关系的方向 默认值false, 表示不反转,由本映射类作为主关系方,进行关系维护 true,表示反转,由被映射类作为主关系方,进行关系维护 --> </class> </hibernate-mapping>
三、单向多对多关系映射处理
演示案例的关系类是:项目类和员工类,一个项目由多个员工合作,一个员工同时也在开发多个项目
单向多对多只需要其中的任意一方维护外键关系即可
重新创建一个新的模块,并且删除之前的数据表:
项目类
package cn.zeal4j.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.HashSet; import java.util.Set; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 22:08 */ @Data @AllArgsConstructor @NoArgsConstructor public class Project implements Serializable { private static final long serialVersionUID = -4867120608900517525L; private Integer projectId; private String projectName; private Set<Employee> employeeSet = new HashSet<>(); public Set<Employee> getEmployeeSet() { return employeeSet; } }
员工类:
package cn.zeal4j.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 22:08 */ @Data @AllArgsConstructor @NoArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = -6524261539072181203L; private Integer employeeId; private String employeeName; }
员工类的Hibernate映射文件就按照单表配置即可
重点是项目类的Hibernate映射文件配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.zeal4j.domain" > <!-- 实体类包映射 --> <class name="Project" table="project" > <id name="projectId" column="project_id" type="java.lang.Integer" access="property"> <generator class="increment" /> </id> <property name="projectName" column="project_name" type="java.lang.String" access="property"/> <set name="employeeSet" cascade="all"> <key column="project_id"></key> <many-to-many class="Employee" column="employee_id" /> </set> </class> </hibernate-mapping>
Hibernate配置文件:
和之前一样,只要绑好这个就行了
<mapping resource="hibernate/mapping/Project.hbm.xml"/> <mapping resource="hibernate/mapping/Employee.hbm.xml" />
要注意的一些问题:
多对多关系必然是需要一张中间表来维护关系,但是单向维护并没有指定第三张关系维护表的内容
测试单元:
@Test public void getTest() { Session session = HibernateUtil.getSession(); Transaction transaction = session.beginTransaction(); Employee employee1 = new Employee(null, "员工01"); Employee employee2 = new Employee(null, "员工02"); Project project1 = new Project(); project1.setProjectName("项目01"); Project project2 = new Project(); project2.setProjectName("项目02"); // 多对多对象处理 project1.getEmployeeSet().add(employee1); project1.getEmployeeSet().add(employee2); project2.getEmployeeSet().add(employee1); project2.getEmployeeSet().add(employee2); // 开始操作 session.save(project1); session.save(project2); transaction.commit(); session.close(); HibernateUtil.destroyFactory(); }
语句输出:
Hibernate: create table employee ( employee_id integer not null, employee_name varchar(255), primary key (employee_id) ) engine=InnoDB Hibernate: create table project ( project_id integer not null, project_name varchar(255), primary key (project_id) ) engine=InnoDB Hibernate: create table Project_employeeSet ( project_id integer not null, employee_id integer not null, primary key (project_id, employee_id) ) engine=InnoDB Hibernate: alter table Project_employeeSet add constraint FKl70mnietqkmll2ccdn4vtm58n foreign key (employee_id) references employee (employee_id) Hibernate: alter table Project_employeeSet add constraint FKns1stqcwdxpogvyhauuj82xul foreign key (project_id) references project (project_id) 九月 26, 2020 10:35:19 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] Hibernate: select max(project_id) from project Hibernate: select max(employee_id) from employee Hibernate: insert into project (project_name, project_id) values (?, ?) Hibernate: insert into employee (employee_name, employee_id) values (?, ?) Hibernate: insert into employee (employee_name, employee_id) values (?, ?) Hibernate: insert into project (project_name, project_id) values (?, ?) Hibernate: insert into Project_employeeSet (project_id, employee_id) values (?, ?) Hibernate: insert into Project_employeeSet (project_id, employee_id) values (?, ?) Hibernate: insert into Project_employeeSet (project_id, employee_id) values (?, ?) Hibernate: insert into Project_employeeSet (project_id, employee_id) values (?, ?)
外键表的处理也是正确无误的
四、双向多对多关系映射处理
双向绑定,对应的员工类和Hibernate映射文件也是同样的赋予对应的对象
注意双方都不要使用Lombok,否则还是会造成堆溢出错误!!!
package cn.zeal4j.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.HashSet; import java.util.Set; /** * @author Administrator * @file IntelliJ IDEA Hibernate-Tutorial * @create 2020 09 26 22:08 */ @Data @AllArgsConstructor @NoArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = -6524261539072181203L; private Integer employeeId; private String employeeName; private Set<Project> projectSet = new HashSet<>(); public Set<Project> getProjectSet() { return projectSet; } }
映射文件:
注意这里,只能指定一个主方进行关系维护,不允许双方都是关系维护者
还有set标签,双方都指定table属性的为同一个表明,设置的字段也是正好相反的两个字段
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.zeal4j.domain" > <!-- 实体类包映射 --> <class name="Employee" table="employee"> <id name="employeeId" column="employee_id" type="java.lang.Integer" access="property" > <generator class="increment" /> </id> <property name="employeeName" column="employee_name" type="java.lang.String" access="property"/> <set name="projectSet" cascade="all" table="project_employee_map" inverse="true" > <key column="employee_id" /> <many-to-many class="Project" column="project_id" /> </set> </class> </hibernate-mapping>
测试类
@Test public void eachOtherManyToMany() { Session session = HibernateUtil.getSession(); Transaction transaction = session.beginTransaction(); Employee employee1 = new Employee(); employee1.setEmployeeName("员工01"); Employee employee2 = new Employee(); employee2.setEmployeeName("员工02"); Project project1 = new Project(); project1.setProjectName("项目01"); Project project2 = new Project(); project2.setProjectName("项目02"); project1.getEmployeeSet().add(employee1); project1.getEmployeeSet().add(employee2); project2.getEmployeeSet().add(employee1); employee1.getProjectSet().add(project1); employee1.getProjectSet().add(project2); employee2.getProjectSet().add(project1); session.save(project1); session.save(project2); transaction.commit(); session.close(); HibernateUtil.destroyFactory(); }
打印的SQL语句:
Hibernate: create table employee ( employee_id integer not null, employee_name varchar(255), primary key (employee_id) ) engine=InnoDB Hibernate: create table project ( project_id integer not null, project_name varchar(255), primary key (project_id) ) engine=InnoDB Hibernate: create table project_employee_map ( project_id integer not null, employee_id integer not null, primary key (project_id, employee_id) ) engine=InnoDB Hibernate: alter table project_employee_map add constraint FKo9hw3isyxdis78q1kjx6dgsx4 foreign key (employee_id) references employee (employee_id) Hibernate: alter table project_employee_map add constraint FKlwh46ok6c1axys3ooh2e5rwfo foreign key (project_id) references project (project_id) 九月 26, 2020 11:06:31 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] Hibernate: select max(project_id) from project Hibernate: select max(employee_id) from employee Hibernate: insert into project (project_name, project_id) values (?, ?) Hibernate: insert into employee (employee_name, employee_id) values (?, ?) Hibernate: insert into employee (employee_name, employee_id) values (?, ?) Hibernate: insert into project (project_name, project_id) values (?, ?) Hibernate: insert into project_employee_map (project_id, employee_id) values (?, ?) Hibernate: insert into project_employee_map (project_id, employee_id) values (?, ?) Hibernate: insert into project_employee_map (project_id, employee_id) values (?, ?)是
还是不知道为什么Lombok堆溢出的错误,报错指向了@Data注解
可能单独写SetterGetter会有用吧