首先介绍一下一对多是什么,一对多关系是关系数据库中两个表之间的一种关系,该关系中第一个表中的单个行可以与第二个表中的一个或多个行相关,但第二个表中的一个行只可以与第一个表中的一个行相关。
例如:我有一个Customer类表示客户,一个Linkman类表示联系人。在这个关系中,我定义一个客户可以对应多个联系人,而一个联系人只能对应一个客户,那么这里的客户就是“一”,联系人就是“多”。
一对多关系的建表原则:在“多”的一方创建字段作为外键,指向“一”的一方的主键。
例如上图的例子,在“多”的一方创建一个外键cid,指向“一”的一方。例子中的客户张三就有两个联系人。
首先创建两个实体类Customer类和Linkman类,注意在这两个类中要相互关联
我的Customer类中采用Set集合的方式关联Linkman类来表示一个客户可以有多个联系人
package com.hibernate.entity; import java.util.HashSet; import java.util.Set; public class Customer { //客户id private Integer cid; //客户姓名 private String cName; //客户等级 private String cLevel; //客户来源 private String cSource; //客户联系电话 private String cPhone; //客户邮箱 private String cMail; //联系人 private Set<Linkman> linkmans = new HashSet<Linkman>(); public Set<Linkman> getLinkmans() { return linkmans; } public void setLinkmans(Set<Linkman> linkmans) { this.linkmans = linkmans; } public Integer getCid() { return cid; } public String getcName() { return cName; } public String getcLevel() { return cLevel; } public String getcSource() { return cSource; } public String getcPhone() { return cPhone; } public String getcMail() { return cMail; } public void setCid(Integer cid) { this.cid = cid; } public void setcName(String cName) { this.cName = cName; } public void setcLevel(String cLevel) { this.cLevel = cLevel; } public void setcSource(String cSource) { this.cSource = cSource; } public void setcPhone(String cPhone) { this.cPhone = cPhone; } public void setcMail(String cMail) { this.cMail = cMail; } @Override public String toString() { return "Customer [cid=" + cid + ", cName=" + cName + ", cLevel=" + cLevel + ", cSource=" + cSource + ", cPhone=" + cPhone + ", cMail=" + cMail + ", linkmans=" + linkmans + "]"; } }
package com.hibernate.entity; public class Linkman { //联系人id private Integer lkm_id; //联系人姓名 private String lkm_name; //联系人性别 private String lkm_gender; //联系人电话 private String lkm_phone; //所属客户 private Customer lkm_customer; public Customer getLkm_customer() { return lkm_customer; } public void setLkm_customer(Customer lkm_customer) { this.lkm_customer = lkm_customer; } public Integer getLkm_id() { return lkm_id; } public String getLkm_name() { return lkm_name; } public String getLkm_gender() { return lkm_gender; } public String getLkm_phone() { return lkm_phone; } public void setLkm_id(Integer lkm_id) { this.lkm_id = lkm_id; } public void setLkm_name(String lkm_name) { this.lkm_name = lkm_name; } public void setLkm_gender(String lkm_gender) { this.lkm_gender = lkm_gender; } public void setLkm_phone(String lkm_phone) { this.lkm_phone = lkm_phone; } @Override public String toString() { return "Linkman [lkm_id=" + lkm_id + ", lkm_name=" + lkm_name + ", lkm_gender=" + lkm_gender + ", lkm_phone=" + lkm_phone + ", lkm_customer=" + lkm_customer + "]"; } }
实体类有了,我们可以开始配置hibernate的对象映射文件了,推荐采用一个实体类创建一个对象映射文件的方式,注意在这里的配置中我们就要进行级联的相关配置了
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> <class name="com.hibernate.entity.Customer" table="hibernate_Customer"> <id name="cid" column="cid"> <generator class="native"></generator> </id> <property name="cName" column="cName"></property> <property name="cLevel" column="cLevel"></property> <property name="cSource" column="cSource"></property> <property name="cPhone" column="cPhone"></property> <property name="cMail" column="cMail"></property> <!-- 在客户映射文件中表示所有联系人 --> <!-- set标签表示集合,name属性写客户实体类中set集合的名称 --> <set name="linkmans" cascade="save-update,delete"> <!-- 一对多建表,有外键 hibernate机制:双向维护外键,即在“多”和“一” 双方都要配置外键,命名外键为clid --> <key column="clid"></key> <!-- 一对多 :class中写实体类全路径 --> <one-to-many class="com.hibernate.entity.Linkman"/> </set> </class> </hibernate-mapping>
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> <!-- 配置类和表对应 class name:实体类全路径名 table:数据库表名 --> <class name="com.hibernate.entity.Linkman" table="hibernate_Linkman"> <!-- 配置实体类id和表id对应 hibernate要求实体类要有一个属性为唯一值 hibernate要求表中要有字段作为唯一值 --> <!-- id标签 name属性:实体类中id的属性名 column:生成的表字段名称 --> <id name="lkm_id" column="lkm_id"> <!-- native:生成表id为主键自动增长 --> <generator class="native"></generator> </id> <!-- property标签配置实体类其他属性 --> <property name="lkm_name" column="lkm_name"></property> <property name="lkm_gender" column="lkm_gender"></property> <property name="lkm_phone" column="lkm_phone"></property> <!-- 表示联系人所属客户 name属性:表示多对一中的“一”,即此处为客户,写lkm_customer名称 class属性:customer全路径 column属性:外键名称 --> <many-to-one name="lkm_customer" class="com.hibernate.entity.Customer" column="clid"></many-to-one> </class> </hibernate-mapping>
• <many-to-one> 元素来映射组成关系
– name: 设定待映射的持久化类的属性的名字
– column: 设定和持久化类的属性对应的表的外键
– class:设定待映射的持久化类的属性的类型
• <set> 元素来映射持久化类的 set 类型的属性
– name: 设定待映射的持久化类的属性的
• <key> 元素设定与所关联的持久化类对应的表的外键
– column: 指定关联表的外键名
• <one-to-many> 元素设定集合属性中所关联的持久化类
class: 指定关联的持久化类的类名
配置了对象映射文件后就是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-configuration> <session-factory> <!-- 1.配置数据库信息 --> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/javadatabase</property> <property name="connection.username">root</property> <property name="connection.password">*******</property> <!-- 2.配置hibernate信息(可选) --> <!-- thread:Session对象的生命周期与本地线程绑定 jta:Session对象的生命周期与JTA事务绑定 managed:Hibernate委托程序来管理Session对象的生命周期 --> <property name="hibernate.current_session_context_class">thread</property> <!-- 输出底层sql语句 --> <property name="show_sql">true</property> <!-- 输出底层sql语句格式 --> <property name="format_sql">true</property> <!-- hibernate帮创建表,需要配置之后 update:如果已经表存在,则更新,不存在,则创建 --> <property name="hbm2ddl.auto">update</property> <!-- 配置数据库方言 在MySQL里面实现分页关键字limit,只能使用MySQL里 在oracle数据库,实现发页用rownum 让hibernate框架识别不同数据库的自己特有的语句 --> <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property> <!-- 3.把映射文件放到核心配置文件中 --> <mapping resource="Customer.hbm.xml"/> <mapping resource="Linkman.hbm.xml"/> </session-factory> </hibernate-configuration>
级联操作:
1.级联保存:
这里我配置了<set>标签的cascade属性进行简化,即在“一”的一方的对象映射文件中进行配置
对应上文Customer.hbm.xml中的save-update
<set name="linkmans" cascade="save-update,delete">
测试代码如下:
//简化级联保存,需在配置“一”一方的对象映射文件中为<set>标签配置cascade属性为save-update @Test public void testSave(){ Session session = null; Transaction tx = null; try { session = HibernateUtil.getSession(); tx = session.beginTransaction(); //创建客户 Customer customer = new Customer(); customer.setcName("张三"); customer.setcLevel("vip"); customer.setcSource("网站"); customer.setcPhone("13622222222"); customer.setcMail("[email protected]"); //创建联系人 Linkman linkman = new Linkman(); linkman.setLkm_name("王舞"); linkman.setLkm_gender("女"); linkman.setLkm_phone("13678944321"); //关联客户和联系人 customer.getLinkmans().add(linkman); //保存 session.save(customer); tx.commit(); } catch (Exception e) { tx.rollback(); }finally { session.close(); } }
执行完成后,可以发现数据库中创建了对应的两张表
表中信息如下:
接下来我们看看控制台中的sql语句的输出
首先创建了两张表
然后为hibernate_Linkman添加了外键指向hibernate_Customer的cid
再为两张表添加数据
最后为hibernate_Linkman中的外键赋值
以上就是创建并保存的全过程。
级联修改
这里我们首先看看数据库中修改前的数据
接下来我们将李四的联系人赵柳,改到张三的联系人中
测试代码如下
//级联修改 @Test public void testUpdate(){ Session session = null; Transaction tx = null; try { session = HibernateUtil.getSession(); tx = session.beginTransaction(); //根据id获取客户 Customer customer = session.get(Customer.class, 1); //获取联系人 Linkman linkman = session.get(Linkman.class, 2); //修改持久态对象实现数据库自动更新 customer.getLinkmans().add(linkman); linkman.setLkm_customer(customer); tx.commit(); } catch (Exception e) { tx.rollback(); }finally { session.close(); } }
此时我们发现数据库中数据改变了
但此时我们看到控制台中的update语句执行了两次
Hibernate: select customer0_.cid as cid1_0_0_, customer0_.cName as cName2_0_0_, customer0_.cLevel as cLevel3_0_0_, customer0_.cSource as cSource4_0_0_, customer0_.cPhone as cPhone5_0_0_, customer0_.cMail as cMail6_0_0_ from hibernate_Customer customer0_ where customer0_.cid=? Hibernate: select linkman0_.lkm_id as lkm_id1_1_0_, linkman0_.lkm_name as lkm_name2_1_0_, linkman0_.lkm_gender as lkm_gend3_1_0_, linkman0_.lkm_phone as lkm_phon4_1_0_, linkman0_.clid as clid5_1_0_ from hibernate_Linkman linkman0_ where linkman0_.lkm_id=? Hibernate: select linkmans0_.clid as clid5_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_name as lkm_name2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_phone as lkm_phon4_1_1_, linkmans0_.clid as clid5_1_1_ from hibernate_Linkman linkmans0_ where linkmans0_.clid=? Hibernate: update hibernate_Linkman set lkm_name=?, lkm_gender=?, lkm_phone=?, clid=? where lkm_id=? Hibernate: update hibernate_Linkman set clid=? where lkm_id=?
这就说明效率并不高,同样的数据进行了两次update。
这里就要介绍一下<set>的inverse属性了
关系
在 一对多 关系中,将“多”的一方设为主控方将有助于性能改善
接下来我们进行配置,将Customer.hbm.xml中的<set>标签添加inverse属性值为true,即Customer放弃关系维护,而交由Linkman来进行关系的维护(因为不设置inverse属性则默认为false)
<set name="linkmans" cascade="save-update,delete" inverse="true">
然后我们再把刚刚做的数据库修改再改回去。
@Test public void testUpdate(){ Session session = null; Transaction tx = null; try { session = HibernateUtil.getSession(); tx = session.beginTransaction(); //根据id获取客户 Customer customer = session.get(Customer.class, 2); //获取联系人 Linkman linkman = session.get(Linkman.class, 2); //修改持久态对象实现数据库自动更新 customer.getLinkmans().add(linkman); linkman.setLkm_customer(customer); tx.commit(); } catch (Exception e) { tx.rollback(); }finally { session.close(); } }
可以看到数据已经修改回原来的值了
现在控制台中的输出如下:
Hibernate: select customer0_.cid as cid1_0_0_, customer0_.cName as cName2_0_0_, customer0_.cLevel as cLevel3_0_0_, customer0_.cSource as cSource4_0_0_, customer0_.cPhone as cPhone5_0_0_, customer0_.cMail as cMail6_0_0_ from hibernate_Customer customer0_ where customer0_.cid=? Hibernate: select linkman0_.lkm_id as lkm_id1_1_0_, linkman0_.lkm_name as lkm_name2_1_0_, linkman0_.lkm_gender as lkm_gend3_1_0_, linkman0_.lkm_phone as lkm_phon4_1_0_, linkman0_.clid as clid5_1_0_ from hibernate_Linkman linkman0_ where linkman0_.lkm_id=? Hibernate: select linkmans0_.clid as clid5_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_name as lkm_name2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_phone as lkm_phon4_1_1_, linkmans0_.clid as clid5_1_1_ from hibernate_Linkman linkmans0_ where linkmans0_.clid=? Hibernate: update hibernate_Linkman set lkm_name=?, lkm_gender=?, lkm_phone=?, clid=? where lkm_id=?
可以看到update语句只有一句了,则提高了效率。
级联删除
先看目前数据库中数据的情况
接下来我们删除第二个客户李四,由于第二个客户李四的联系人是赵柳,那么删除李四的同时也会删除赵柳。
现在控制台中的输出如下:
//级联删除,在“一”的一方<set>标签中配置cascade属性为delete(多个值用英文的“,”隔开) @Test public void testDelete(){ Session session = null; Transaction tx = null; try { session = HibernateUtil.getSession(); tx = session.beginTransaction(); //根据id获取客户 Customer customer = session.get(Customer.class, 2); //删除 session.delete(customer); tx.commit(); } catch (Exception e) { tx.rollback(); }finally { session.close(); } }
执行之后数据库的变化
发现两张表的第二个记录都被删除了。
接着我们看看控制台中的sql语句的输出
Hibernate: select customer0_.cid as cid1_0_0_, customer0_.cName as cName2_0_0_, customer0_.cLevel as cLevel3_0_0_, customer0_.cSource as cSource4_0_0_, customer0_.cPhone as cPhone5_0_0_, customer0_.cMail as cMail6_0_0_ from hibernate_Customer customer0_ where customer0_.cid=? Hibernate: select linkmans0_.clid as clid5_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_name as lkm_name2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_phone as lkm_phon4_1_1_, linkmans0_.clid as clid5_1_1_ from hibernate_Linkman linkmans0_ where linkmans0_.clid=? Hibernate: delete from hibernate_Linkman where lkm_id=? Hibernate: delete from hibernate_Customer where cid=?我们发现一个细节,hibernate是先删除了linkman再删除的customer。这是因为linkman中含有一个外键,若先删除customer,则有可能linkman中原来所指向的customer就没有了,那么逻辑上的对应关系就不存在了,所以数据库不允许我们这样操作。