超详细的Hibernate关联关系之双向的一对多关联关系的CRUD操作————学习至黑马程序员****
说明:
1.这里是使用 客户-联系人 来做这个双向一对多关联映射的。即,一个客户对应多个联系人,多个联系人对应一个客户,因此 客户 和 联系人 是一对多 关联关系。
2.有关SQL语句:只需在hibernate.cfg.xml配置文件中加上以下配置,然后当类获取Session时,即可让hibernate通过映射文件来自动生成数据表。
<!-- update表示检测实体类的映射配置和数据库的表结构是否一致 -->
<property name="hibernate.hbm2ddl.auto">update</property>
3.本示例的代码中有详细的注释,具体请参考注释内容。
4.学习多表映射配置要遵循的步骤:
①确定两张表之间的关系。
②在数据库中实现两张表之间的关系建立。
③在实体类中描述出两个实体之间的关系。
④在映射配置文件中建立两个实体和两张表之间的关系。
============================================================
============================================================
下面开始编写测试代码:
1.一对多关系映射配置及操作(客户和联系人两张表)
①确定两张表之间的关系:
a.一个客户可以包含多个联系人。
b.多个联系人可以属于同一个客户。
c.客户和联系人之间的关系是一对多。
②在数据库中实现两张表之间的关系建立:
a.实现一对多的关系,靠外键。
b.客户表是主表,联系人表是从表。
c.我们需要在联系人表中添加外键。
③在实体类中描述出两个实体之间的关系:
a.主表的实体类应该包含从表实体类的集合引用。
b.从表的实体类应该包含主表实体类的对象引用。
④在映射配置文件中建立两个实体和两张表之间的关系
============================
2.项目结构
============================
首先是pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lin</groupId>
<artifactId>Hibernate_OneToMany_heima</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.10.Final</version>
</dependency>
<!-- MySQL驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<!-- jstl、servlet-api、junit -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
============================
4.实体类和其对应的映射文件:
这里要注意的一点就是,如果要生成类的toString()方法,那么不要包含关联的实体类。
Customer.java
package com.lin.domain;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* 客户的实体类
* */
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
private Long custId;
private String custName;
private String custSource;
private String sustIndustry;
private String custLevel;
private String custAddress;
private String custPhone;
//一对多关系映射:多的一方
//主表实体应该包含从表实体的集合引用
private Set<LinkMan> linkMans = new HashSet<LinkMan>(0);
public Customer() {
super();
}
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getSustIndustry() {
return sustIndustry;
}
public void setSustIndustry(String sustIndustry) {
this.sustIndustry = sustIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", sustIndustry=" + sustIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.lin.domain">
<!-- name指定持久化类的类名,table指定数据表的表名 -->
<class name="Customer" table="cst_customer">
<id name="custId" column="cust_id">
<generator class="native"/>
</id>
<property name="custName" column="cust_name"></property>
<property name="custSource" column="cust_source"></property>
<property name="sustIndustry" column="sust_industry"></property>
<property name="custLevel" column="cust_level"></property>
<property name="custAddress" column="cust_address"></property>
<property name="custPhone" column="cust_phone"></property>
<!--
一对多关系映射:主表实体的映射配置
涉及的标签:
set:
作用:用于配置set集合属性。
属性: name:指定实体类中set集合的属性名称。
table:指定从表的名称。在一对多配置时可不写。
inverse:是否放弃维护关联关系的权利
true:放弃
false:不放弃(默认),如果不放弃,那么在双向一对多中保存数据的时候会有多余的update语句。
cascade:配置级联操作。
级联保存更新的取值:save-update
lazy:配置是否使用延迟加载
true:使用延迟加载(默认)
false:不使用延迟加载
key:
作用:用于映射外键字段。
属性:column:指定外键字段名称。
one-to-many:
作用:用于建立一对多的映射配置。
属性:class:用于指定从表实体的名称。
-->
<set name="linkMans" table="cst_linkman" inverse="true" cascade="save-update" lazy="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"></one-to-many>
</set>
</class>
</hibernate-mapping>
LinkMan.java
package com.lin.domain;
import java.io.Serializable;
/**
* 联系人的实体类
* */
public class LinkMan implements Serializable {
private static final long serialVersionUID = 1L;
private Long lkmId;
private String lkmName;
private String lkmGender;
private String lkmPhone;
private String lkmMobile;
private String lkmEmail;
private String lkmPosition;
private String lkmMemo;
//一对多关系映射,多的一方。
//从表实体包含主表实体的对象引用
private Customer customer;
public Long getLkmId() {
return lkmId;
}
public void setLkmId(Long lkmId) {
this.lkmId = lkmId;
}
public String getLkmName() {
return lkmName;
}
public void setLkmName(String lkmName) {
this.lkmName = lkmName;
}
public String getLkmGender() {
return lkmGender;
}
public void setLkmGender(String lkmGender) {
this.lkmGender = lkmGender;
}
public String getLkmPhone() {
return lkmPhone;
}
public void setLkmPhone(String lkmPhone) {
this.lkmPhone = lkmPhone;
}
public String getLkmMobile() {
return lkmMobile;
}
public void setLkmMobile(String lkmMobile) {
this.lkmMobile = lkmMobile;
}
public String getLkmEmail() {
return lkmEmail;
}
public void setLkmEmail(String lkmEmail) {
this.lkmEmail = lkmEmail;
}
public String getLkmPosition() {
return lkmPosition;
}
public void setLkmPosition(String lkmPosition) {
this.lkmPosition = lkmPosition;
}
public String getLkmMemo() {
return lkmMemo;
}
public void setLkmMemo(String lkmMemo) {
this.lkmMemo = lkmMemo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "LinkMan [lkmId=" + lkmId + ", lkmName=" + lkmName + ", lkmGender=" + lkmGender + ", lkmPhone="
+ lkmPhone + ", lkmMobile=" + lkmMobile + ", lkmEmail=" + lkmEmail + ", lkmPosition=" + lkmPosition
+ ", lkmMemo=" + lkmMemo + "]";
}
}
LinkMan.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.lin.domain">
<!-- name指定持久化类的类名,table指定数据表的表名 -->
<class name="LinkMan" table="cst_linkman">
<id name="lkmId" column="lkm_id">
<generator class="native"/>
</id>
<property name="lkmName" column="lkm_name"></property>
<property name="lkmGender" column="lkm_gender"></property>
<property name="lkmPhone" column="lkm_phone"></property>
<property name="lkmMobile" column="lkm_mobile"></property>
<property name="lkmEmail" column="lkm_email"></property>
<property name="lkmPosition" column="lkm_position"></property>
<property name="lkmMemo" column="lkm_memo"></property>
<!--
一对多关系映射:从表实体的映射配置
涉及的标签:many-to-one.
作用:建立多对一的映射配置
属性: name:从表实体中引用主表实体对象引用的名称
class:指定属性所对应的实体类名称
column:指定从表中外键字段的名称
lazy:配置是否使用延迟加载
false:使用立即加载
proxy:是看load方法是延迟加载还是立即加载
no-proxy:不使用,不用管。
-->
<many-to-one name="customer" class="Customer" column="lkm_cust_id" cascade="save-update" lazy="proxy"></many-to-one>
</class>
</hibernate-mapping>
============================
5.Hibernate的配置文件:
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<!--
Hibernate配置文件包含了连接持久层与映射文件所需的基本信息。
(Hibernate配置文件主要用来配置数据库连接以及Hibernate运行时所需的各个属性的值。)
-->
<hibernate-configuration>
<session-factory>
<!-- 数据库连接设置 -->
<!-- 配置数据库JDBC驱动 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- 配置数据库连接URL -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>
<!-- 配置数据库用户名 -->
<property name="hibernate.connection.username">root</property>
<!-- 配置数据库密码 -->
<property name="hibernate.connection.password">000000</property>
<!-- 配置JDBC内置连接池 -->
<property name="connection.pool_size">1</property>
<!-- 配置数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 配置Hibernate采用何种方式生成DDL语句 -->
<!-- update表示检测实体类的映射配置和数据库的表结构是否一致 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 输出运行时生成的SQL语句 -->
<property name="show_sql">true</property>
<!-- 列出所有的映射文件 -->
<mapping resource="hibernate/mappings/LinkMan.hbm.xml" />
<mapping resource="hibernate/mappings/Customer.hbm.xml" />
</session-factory>
</hibernate-configuration>
============================
6.Hibernate的工具类:
在这个类中添加了main方法,运行main方法后hibernate即可根据映射文件自动生成表。
HibernateUtil.java
package com.lin.utils;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static SessionFactory sessionFactory;
private static Configuration configuration;
//创建线程局部变量threadLocal,用来保存Hibernate的Session
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
//使用静态代码块初始化Hibernate
static{
try{
//如果不指定hibernate的配置文件位置,那么它会默认到classpath路径下查找名为hibernate.cfg.xml的文件
Configuration cfg = new Configuration().configure("/hibernate/hibernate.cfg.xml");
//创建SessionFactory
sessionFactory = cfg.buildSessionFactory();
}catch(Throwable ex){
throw new ExceptionInInitializerError(ex);
}
}
//获得SessionFactory
public static SessionFactory getSessionFactory(){
return sessionFactory;
}
//获得ThreadLocal对象管理的Session实例
public static Session getSession() throws HibernateException {
Session session = (Session)threadLocal.get();
if(session == null || session.isOpen()){
if(sessionFactory == null){
rebuildSessionFactory();
}
//通过SessionFactory对象创建Session对象
session = (sessionFactory != null)?sessionFactory.openSession():null;
//将新打开的Session实例保存到线程局部变量threadLocal中
threadLocal.set(session);
}
return session;
}
//关闭Session实例
public static void closeSession() throws HibernateException {
//从线程局部变量threadLocal中获取之前存入的Session实例
Session session = (Session)threadLocal.get();
threadLocal.set(null);
if(session != null){
session.close();
}
}
//重建SessionFactory
public static void rebuildSessionFactory() {
try{
configuration.configure("/hibernate/hibernate.cfg.xml");
sessionFactory = configuration.buildSessionFactory();
}catch(Exception e){
System.out.println("Error Creating SessionFactory ");
e.printStackTrace();
}
}
//关闭缓存和连接池
public static void shutdown(){
getSessionFactory().close();
}
//main方法
public static void main(String[] args){
HibernateUtil.getSession();
}
}
============================
7.Hibernate一对多关联关系的CRUD测试:
package com.lin.test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.lin.domain.Customer;
import com.lin.domain.LinkMan;
import com.lin.utils.HibernateUtil;
/**
* 一对多关系的CRUD操作
* */
public class HibernateOneToManyTest {
/**
* 保存操作。
* 正常的保存:创建一个新的联系人(数据库中没有),需要关联一个客户(数据库中有)
* */
@Test
public void saveTest1(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//1.查询一个客户
Customer c1 = (Customer) session.get(Customer.class, 1L);
//2.创建一个新的联系人
LinkMan l = new LinkMan();
l.setLkmName("联系人1");
//3.建立客户和联系人的关联关系(让联系人知道属于哪个客户即可)
l.setCustomer(c1);
//4.保存联系人
session.save(l);
tx.commit();
session.close();
}
/**
* 特殊的情况:
* 创建一个客户(数据库中没有)和一个联系人(数据库中没有)。
* 建立联系人和客户的双向关联关系。
* 使用符合原则的保存。(原则是:先保存主表实体,再保存从表实体)。
*
*
* 此时保存会有问题,
* 我们保存两个实体,应该只有两条insert语句,而执行结果却多了一条update语句。
* 解决办法:
* 让客户在执行保存的时候,放弃维护关联关系的权利。
* 配置的方式:
* 在Customer的映射配置文件中的set标签上使用inverse属性。
* inverse的含义:是否放弃维护关联关系的权利
* true:放弃
* false:不放弃(默认)
* */
@Test
public void saveTest2(){
//1.创建一个客户
Customer c1 = new Customer(); //瞬时态
c1.setCustName("客户2");
//2.创建一个新的联系人
LinkMan l = new LinkMan(); //瞬时态
l.setLkmName("联系人2");
//3.建立客户和联系人的关联关系(双向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//4.保存,要符合原则
session.save(c1); //持久态,有一级缓存和快照
session.save(l); //持久态,有一级缓存和快照
tx.commit();
session.close();
}
/**
* 保存操作:级联保存
*
* 使用级联保存,
* 1.也可以配置在many-to-one上。
* 2.配置的方式是找到Customer的映射配置文件中的Set标签,在上面加入cascade属性:
* cascade:配置级联操作。
* 级联保存更新的取值:save-update
*/
@Test
public void saveTest3(){
//1.创建一个客户
Customer c1 = new Customer(); //瞬时态
c1.setCustName("客户3");
//2.创建一个新的联系人
LinkMan l = new LinkMan(); //瞬时态
l.setLkmName("联系人3");
//3.建立客户和联系人的关联关系(双向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//4.保存,要符合原则
session.save(c1); //持久态,有一级缓存和快照
tx.commit();
session.close();
}
/**
* 级联保存2
* */
@Test
public void saveTest4(){
//1.创建一个客户
Customer c1 = new Customer(); //瞬时态
c1.setCustName("客户4");
//2.创建一个新的联系人
LinkMan l = new LinkMan(); //瞬时态
l.setLkmName("联系人4");
//3.建立客户和联系人的关联关系(双向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//4.保存,要符合原则
session.save(l); //持久态,有一级缓存和快照
tx.commit();
session.close();
}
/**
* 更新操作
*
* 要求:
* 创建一个新的联系人,查询一个已有客户。
* 建立新联系人和已有客户的双向关联关系
* 更新客户
* */
@Test
public void updateTest1(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//1.查询一个客户
Customer c1 = (Customer) session.get(Customer.class, 1L);
//2.创建一个新的联系人
LinkMan l = new LinkMan();
l.setLkmName("联系人5");
//3.建立客户和联系人的关联关系(双向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
//4.更新客户
session.update(c1);
tx.commit();
session.close();
}
/**
* 删除操作
* 1.删除从表数据就是单表操作。
* 2.删除主表数据:
* 看有没有从表数据引用
* 没有引用:就是单表操作,直接删。
* 有引用:
* 在删除时,hibernate会把从表中的外键字段置为null,然后再删除主表数据。(注意:主表中要不能放弃维护从表数据,即inverse="false" )
* 如果外键字段有非空约束,则hibernate不能更新外键字段为null,会报错。
* 此时,如果仍然想删除,就需要使用级联删除。可将cascade="save-update,delete",同时必须配置inverse="true"
*
* 级联删除的使用: 在实际开发中,要慎重!
*
* */
@Test
public void deleteTest(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//1.查询一个客户
Customer c1 = (Customer) session.get(Customer.class, 3L);
//2.删除id为1的客户
session.delete(c1);
tx.commit();
session.close();
}
/****************************************************
* Hibernate 中的查询操作:OID查询,HQL查询,QBC查询,SQL查询,对象导航查询。
*
*
* 以下通过一对多的来演示对象导航查询。
* 当两个实体之间有关联关系时(关联关系可以使一对一,一对多(多对一),多对多中的任意一种)。
* 我们通过调用getXxx方法即可实现查询功能(功能是由hibernate提供的)
* 例如:
* customer.getLinkMans()就可以得到当前客户下的所有联系人
* linkman.getCustomer()就可以得到当前联系人下的所属客户。
*
*
*三种lazy属性的情况:
* class标签的lazy属性:它只能管load方法是否是延迟加载。
* set标签的lazy属性: 它管查询关联的集合对象是否是延迟加载。
* many-to-one的lazy属性:它管查询关联的主表实体是否是立即加载。
*
***************************************************/
/**
* 查询id为1的客户下所有的联系人
* 一对多时,根据一的一方查询多的一方时,需要使用延迟加载。(默认配置就是使用延迟加载)
* (如果这里需要改成立即加载,那么找到Customer的映射文件中的set标签,添加lazy属性,即 lazy="false"即可)
* */
@Test
public void selectTest1(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
Customer c = (Customer) session.get(Customer.class, 1L);
System.out.println(c);
System.out.println(c.getLinkMans());
tx.commit();
session.close();
}
/**
* 查询id为1的联系人所属的客户
* 多对一时,根据多的一方查询一的一方时,不需要使用延迟加载,而是使用立即加载,这里需要配置一下。(找到LinkMan的配置文件的many-to-one标签添加laze属性,即lazy="false"即可)
* 需要找到联系人的映射配置文件:在many-to-one标签上使用lazy属性
* 取值有:
* false:使用立即加载。
* proxy:是看load方法是延迟加载还是立即加载。
* no-proxy:不使用,不用管。
* */
@Test
public void selectTest2(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
LinkMan l = (LinkMan) session.get(LinkMan.class, 1L);
System.out.println(l);
System.out.println(l.getCustomer());
tx.commit();
session.close();
}
/**
* 关于load方法改为立即加载的方式。
* 找到查询实体的映射配置文件,它的class标签上也有一个lazy属性。
* 含义是:是否延迟加载
* true:延迟加载(默认值)
* flase:立即加载
* */
@Test
public void selectTest3(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
Customer c = (Customer) session.load(Customer.class,1L);
System.out.println(c);
tx.commit();
session.close();
}
}
==========================
至此,一对多关联关系的全部代码都在上面。测试效果就免了~