1、实体关联关系映射
ORM是Hibernate的理论基础,所以映射在Hibernate中占有非常重要的地位,在Hibernate中就是通过映射将持久化类和数据库表进行关联的。
1.1 数据模型与领域模型
数据模型
数据模型是对数据库特征的抽象,也就是用户从数据库中看到的模型,例如一张数据表或者用户从数据表中所看到的存储信息,此模型既要面向用户又要面向系统,面向用户是需要将存储数据完整地展现在用户面前,使用户可以对数据进行增、删、改、查的操作;面向系统是对告诉计算机如何对数据进行有效的管理。主要用于对数据库管理系统(DBMS)的实现。
领域模型
领域模型是对现实世界中的对象的可视化表现,又称为概念模型、领域对象模型或分析对象模型。没有所谓唯一正确的领域模型。所有模型都是对我们试图要理解的领域的近似。领域模型主要是在特定群体中用于理解和沟通的工具。有效的领域模型捕获了当前需求语境下的本质抽象和理解领域所需要的信息,并且可以帮助人们理解领域的概念、术语和关系。它是现实世界与计算机之间的一条无形的纽带,也是需求分析设计人员一件强有力的工具。
备注:了解搭建Hibernate开发环境的详细说明,请浏览本博客的文章:Hibernate搭建开发环境
1.2 多对一单向关联
关联是类(类的实例)之间的关系,表示有意义和值得关注的连接。
单向多对一的映射实现比较简单,在平时的应用中单向多对一的映射也是很常见的,将以产品和生产商为例,实现单向的多对一映射。两个持久化类的依赖关系如下图所示。将类Product引用了类Factory,但是类Factory没有引用类Product。在类Product映射的表tab_product中建立外键factoryid关联类Factory的映射表tab_factory的主键factoryid。
【示例】建立产品对象与生产商对象的多对一单向关联,并利用映射关系查询完整的图书信息。
(1)在MySQL数据库创建tab_product表和tab_factory表,并添加相关数据。
-- 创建“产品信息”数据表
CREATE TABLE IF NOT EXISTS tab_product
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '产品ID',
NAME VARCHAR(20) NOT NULL COMMENT '产品名称',
price DOUBLE NOT NULL COMMENT '产品价格',
factoryid INT COMMENT '生产商ID'
) COMMENT = '产品信息表';
-- 创建“生产商信息”数据表
CREATE TABLE IF NOT EXISTS tab_factory
(
factoryid INT AUTO_INCREMENT PRIMARY KEY COMMENT '生产商ID',
NAME VARCHAR(20) NOT NULL COMMENT '生产商名称'
) COMMENT = '生产商信息表';
-- 添加数据
INSERT INTO tab_product(NAME,price,factoryid) VALUES('Java Web编程宝典',79.8,1);
INSERT INTO tab_product(NAME,price,factoryid) VALUES('Java从入门到精通',69.6,1);
INSERT INTO tab_factory(NAME) VALUES('明日科技');
(2)创建名称为Product.java产品信息的持久化类。
package com.pjb.entity;
/**
* 产品信息的持久化类
* @author pan_junbiao
**/
public class Product
{
private int id; //产品ID
private String name; //产品名称
private double price; //产品价格
private Factory factory; //生产商ID
//默认的构造方法
public Product() {
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public double getPrice()
{
return price;
}
public void setPrice(double price)
{
this.price = price;
}
public Factory getFactory()
{
return factory;
}
public void setFactory(Factory factory)
{
this.factory = factory;
}
}
(3)创建名称为Product.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.pjb.entity.Product" table="tab_product">
<!-- id值 -->
<id name="id" column="id" type="int">
<generator class="native"/>
</id>
<!-- 产品名称 -->
<property name="name" type="string" length="20">
<column name="name"/>
</property>
<!-- 产品价格 -->
<property name="price" type="double">
<column name="price"/>
</property>
<!-- 多对一关联映射 -->
<many-to-one name="factory" class="com.pjb.entity.Factory">
<!-- 映射的字段(数据表的字段) -->
<column name="factoryid"/>
</many-to-one>
</class>
</hibernate-mapping>
说明:<many-to-one>元素:定义一个持久化类与另一个持久化类的关联,这种关联是数据库表的多对一关联,需要此持久化类映射表的外键引用另一个持久化类映射表的主键,也就是映射的字段。其中name属性的值是持久化类中的属性,class属性就是关联的目标持久化类。
(4)创建名称为Factory.java生产商信息的持久化类。
package com.pjb.entity;
/**
* 生产商信息的持久化类
* @author pan_junbiao
**/
public class Factory
{
private int factoryid; //生产商ID
private String name; //生产商名称
//默认的构造方法
public Factory() {
}
public int getFactoryid()
{
return factoryid;
}
public void setFactoryid(int factoryid)
{
this.factoryid = factoryid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
(5)创建名称为Factory.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.pjb.entity.Factory" table="tab_factory">
<!-- id值 -->
<id name="factoryid" column="factoryid" type="int">
<generator class="native"/>
</id>
<!-- 生产商名称 -->
<property name="name" type="string" length="20">
<column name="name"/>
</property>
</class>
</hibernate-mapping>
(6)创建GetProduct.java类,获取产品信息。
package com.pjb.main;
import com.pjb.entity.Product;
import com.pjb.entity.Factory;
import com.pjb.entity.User;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.Session;
/**
* 获取产品信息
* @author pan_junbiao
**/
public class GetProduct
{
public static void main(String[] args)
{
Session session = null; //声明Session对象
try {
//Hibernate的持久化操作
session = HibernateUtil.getSession();//获取Session
Product product = (Product)session.get(Product.class,1); //装载对象
System.out.println("产品名称:"+product.getName());
System.out.println("产品价格:"+product.getPrice()+"元");
System.out.println("产品商:"+product.getFactory().getName());
} catch (Exception e) {
e.printStackTrace();
} finally{
HibernateUtil.closeSession();//关闭Session
}
}
}
执行结果:
从控制台输出的信息可以看到,当查询生产商名称时,Hibernate又自动输出了一条语句进行查询,单向多对一关联时,只能通过主动方对被动方进行级联更新。也就是说,想要获取某件商品的生产商信息,需要先加载该产品的持久化对象。
1.3 多对一双向关联
在进行双向多对一的关联的时候,Hibernate既可以通过主控方实体加载被控方的实体,同时也可以通过被控方实体加载对应的主控方实体。也就是说在单向一对多的基础上,在被控方(类Product)中配置与主控方(类Factory)对应的多对一关系。本节仍将以生产商对象(类Factory)与产品对象(类Product)为例,讲解Hibernate的多对一双向关联。类Factory与类Product的关联关系如下图。
【示例】获取生产商下的所有产品。
(1)修改Factory.java生产商信息的持久化类。
package com.pjb.entity;
import java.util.List;
import java.util.Set;
/**
* 生产商信息的持久化类
* @author pan_junbiao
**/
public class Factory
{
private int factoryid; //生产商ID
private String name; //生产商名称
private Set<Product> products; //产品列表
//默认的构造方法
public Factory() {
}
public int getFactoryid()
{
return factoryid;
}
public void setFactoryid(int factoryid)
{
this.factoryid = factoryid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Set<Product> getProducts()
{
return products;
}
public void setProducts(Set<Product> products)
{
this.products = products;
}
}
(2)修改Factory.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.pjb.entity.Factory" table="tab_factory">
<!-- id值 -->
<id name="factoryid" column="factoryid" type="int">
<generator class="native"/>
</id>
<!-- 生产商名称 -->
<property name="name" type="string" length="20">
<column name="name"/>
</property>
<!-- 产品列表 -->
<set name="products" inverse="true">
<key column="factoryid"/>
<one-to-many class="com.pjb.entity.Product"/>
</set>
</class>
</hibernate-mapping>
(6)在hibernate.cfg.xml配置文件中添加映射文件信息。
<mapping resource="com/pjb/entity/Product.hbm.xml"/>
<mapping resource="com/pjb/entity/Factory.hbm.xml"/>
(7)创建GetProducts.java类,获取生产商下的所有产品。
package com.pjb.main;
import com.pjb.entity.Factory;
import com.pjb.entity.Product;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.Session;
import java.util.Set;
/**
* 获取产品列表
* @author pan_junbiao
**/
public class GetProducts
{
public static void main(String[] args)
{
Session session = null; //声明Session对象
try {
//Hibernate的持久化操作
session = HibernateUtil.getSession();//获取Session
Factory factory = (Factory)session.get(Factory.class,1); //装载对象
System.out.println("生产商:"+factory.getName());
//获取商品集合对象
Set<Product> productSet = factory.getProducts();
for(Product item : productSet)
{
System.out.println("产品名称:"+ item.getName() + "|| 产品价格:" + item.getPrice()+"元");
}
} catch (Exception e) {
e.printStackTrace();
} finally{
HibernateUtil.closeSession();//关闭Session
}
}
}
执行结果:
1.4 一对一主键关联
一对一的主键关联是指两个表之间通过主键形成一对一的映射。例如每个公民只允许拥有一个身份证,公民与身份证就是一对一的关系。定义两张数据表,分别是表tab_people(公民表)和tab_idcard(身份证表),两表之间的关联关系如下图所示。
从两张表的关联关系可以看出,只要程序知道一张表的信息,就可以获取另外一张表的信息。也就是说,在Hibernate中两个表所映射的实体对象必然是相互引用的,建立的是双向的一对一的主键关联关系。
在Hibernate中既有单向的一对一主键关联关系,也有双向的一对一主键关联关系,这里仅以双向的一对一主键关联关系进行讲解。
【示例】一对一主键关联,获取公民信息与身份证号码。
(1)在MySQL数据库创建tab_people表和tab_idcard表,并添加相关数据。
-- 判断数据表是否存在,存在则删除
DROP TABLE IF EXISTS tab_people;
DROP TABLE IF EXISTS tab_idcard;
-- 创建“公民信息”数据表
CREATE TABLE IF NOT EXISTS tab_people
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '公民ID',
NAME VARCHAR(45) NOT NULL COMMENT '公民名称',
sex VARCHAR(2) COMMENT '公民性别',
age INT COMMENT '公民年龄'
) COMMENT = '公民信息表';
-- 创建“身份证信息”数据表
CREATE TABLE IF NOT EXISTS tab_idcard
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '身份证ID',
idCard_code VARCHAR(45) COMMENT '身份证号码'
) COMMENT = '身份证信息表';
-- 添加数据
INSERT INTO tab_people(NAME,sex,age) VALUES('pan_junbiao的博客','男',32);
INSERT INTO tab_idcard(idCard_code) VALUE('123456789');
(2)创建名称为People.java公民信息的持久化类。
package com.pjb.entity;
/**
* 公民信息的持久化类
* @author pan_junbiao
**/
public class People
{
private int id; //公民ID
private String name; //公民名称
private String sex; //公民性别
private int age; //公民年龄
private IDcard idcard; //身份证实体对象
//默认的构造方法
public People() {
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex = sex;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public IDcard getIdcard()
{
return idcard;
}
public void setIdcard(IDcard idcard)
{
this.idcard = idcard;
}
}
(3)创建名称为People.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.pjb.entity.People" table="tab_people">
<!-- id值 -->
<id name="id" column="id" type="int">
<generator class="native"/>
</id>
<!-- 公民姓名 -->
<property name="name" type="string" length="45">
<column name="name"/>
</property>
<!-- 公民性别 -->
<property name="sex" type="string" length="2">
<column name="sex"/>
</property>
<!-- 公民年龄 -->
<property name="age" type="int">
<column name="age"/>
</property>
<!-- 一对一映射 -->
<one-to-one name="idcard" class="com.pjb.entity.IDcard" cascade="all"/>
</class>
</hibernate-mapping>
(4)创建名称为IDcard.java身份证信息的持久化类。
package com.pjb.entity;
/**
* 身份证信息的持久化类
* @author pan_junbiao
**/
public class IDcard
{
private int id; //身份证ID
private String idCardCode; //身份证号码
private People people; //公民实体对象
//默认的构造方法
public IDcard() {
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getIdCardCode()
{
return idCardCode;
}
public void setIdCardCode(String idCardCode)
{
this.idCardCode = idCardCode;
}
public People getPeople()
{
return people;
}
public void setPeople(People people)
{
this.people = people;
}
}
(5)创建名称为IDcard.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.pjb.entity.IDcard" table="tab_idcard">
<!-- id值 -->
<id name="id" column="id" type="int">
<!-- 外键生成 -->
<generator class="foreign">
<param name="property">people</param>
</generator>
</id>
<!-- 公民身份证号 -->
<property name="idCardCode" type="string" length="45" not-null="true">
<column name="idCard_code"/>
</property>
<!-- 一对一映射 -->
<one-to-one name="people" class="com.pjb.entity.People" cascade="all"/>
</class>
</hibernate-mapping>
(6)在hibernate.cfg.xml配置文件中添加映射文件信息。
<mapping resource="com/pjb/entity/People.hbm.xml"/>
<mapping resource="com/pjb/entity/IDcard.hbm.xml"/>
(7)创建GetPeople.java类,获取公民信息与身份证号码。
package com.pjb.main;
import com.pjb.entity.IDcard;
import com.pjb.entity.People;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.Session;
import java.util.Set;
/**
* 获取公民信息与身份证号码
* @author pan_junbiao
**/
public class GetPeople
{
public static void main(String[] args)
{
Session session = null; //声明Session对象
try {
//Hibernate的持久化操作
session = HibernateUtil.getSession(); //获取Session
People people = (People)session.get(People.class,1); // 获取公民信息,同时取得身份证信息
System.out.println("获取公民信息,同时取得身份证信息:");
System.out.println("公民ID:"+people.getId());
System.out.println("公民名称:"+people.getName());
System.out.println("公民性别:"+people.getSex());
System.out.println("公民年龄:"+people.getAge());
System.out.println("身份证号码:"+people.getIdcard().getIdCardCode());
System.out.println("=====================================================");
IDcard iDcard = (IDcard)session.get(IDcard.class,1); // 获取身份证信息,同时取得公民信息
System.out.println("获取身份证信息,同时取得公民信息:");
System.out.println("身份证号码:"+iDcard.getIdCardCode());
System.out.println("公民ID:"+iDcard.getPeople().getId());
System.out.println("公民名称:"+iDcard.getPeople().getName());
System.out.println("公民性别:"+iDcard.getPeople().getSex());
System.out.println("公民年龄:"+iDcard.getPeople().getAge());
} catch (Exception e) {
e.printStackTrace();
} finally{
HibernateUtil.closeSession();//关闭Session
}
}
}
执行结果:
Hibernate执行脚本:
SELECT people0_.id AS id1_2_0_, people0_.name AS name2_2_0_, people0_.sex AS sex3_2_0_, people0_.age AS age4_2_0_, idcard1_.id AS id1_1_1_, idcard1_.idCard_code AS idCard_c2_1_1_
FROM tab_people people0_
LEFT OUTER JOIN tab_idcard idcard1_ ON people0_.id=idcard1_.id
WHERE people0_.id=?
1.5 一对一外键关联
一对一外键关联的配置比较简单,同样以公民实体对象和身份证实体对象为例,在表tab_people(公民表)中添加一个新的字段“card_id”,作为该表的外键,同时需要保证该字段的唯一性,否则就不是一对一映射关系了,而是一对多映射关系。表tab_people和tab_idcard(身份证表)之间的关联关系如下图所示。
【示例】一对一外键关联,获取公民信息与身份证号码。
(1)在MySQL数据库创建tab_people表和tab_idcard表,并添加相关数据。
-- 判断数据表是否存在,存在则删除
DROP TABLE IF EXISTS tab_people;
DROP TABLE IF EXISTS tab_idcard;
-- 创建“身份证信息”数据表
CREATE TABLE IF NOT EXISTS tab_idcard
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '身份证ID',
idCard_code VARCHAR(45) COMMENT '身份证号码'
) COMMENT = '身份证信息表';
-- 创建“公民信息”数据表
CREATE TABLE IF NOT EXISTS tab_people
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '公民ID',
NAME VARCHAR(45) NOT NULL COMMENT '公民名称',
sex VARCHAR(2) COMMENT '公民性别',
age INT COMMENT '公民年龄',
card_id INT UNIQUE COMMENT '身份证ID',
-- 创建外键约束
FOREIGN KEY fk_card_id (card_id)
REFERENCES tab_idcard(id)
) COMMENT = '公民信息表';
-- 添加数据
INSERT INTO tab_idcard(idCard_code) VALUE('123456789');
INSERT INTO tab_people(NAME,sex,age,card_id) VALUES('pan_junbiao的博客','男',32,1);
(2)创建名称为People.java公民信息的持久化类。
package com.pjb.entity;
/**
* 公民信息的持久化类
* @author pan_junbiao
**/
public class People
{
private int id; //公民ID
private String name; //公民名称
private String sex; //公民性别
private int age; //公民年龄
private IDcard idcard; //关联的身份证对象
//默认的构造方法
public People() {
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex = sex;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public IDcard getIdcard()
{
return idcard;
}
public void setIdcard(IDcard idcard)
{
this.idcard = idcard;
}
}
(3)创建名称为People.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.pjb.entity.People" table="tab_people">
<!-- id值 -->
<id name="id" column="id" type="int">
<generator class="native"/>
</id>
<!-- 公民姓名 -->
<property name="name" type="string" length="45">
<column name="name"/>
</property>
<!-- 公民性别 -->
<property name="sex" type="string" length="2">
<column name="sex"/>
</property>
<!-- 公民年龄 -->
<property name="age" type="int">
<column name="age"/>
</property>
<!-- 一对一外键关联映射 -->
<many-to-one name="idcard" unique="true">
<column name="card_id"/>
</many-to-one>
</class>
</hibernate-mapping>
说明:从配置的过程中可以发现,一对一外键关联实际上就是多对一关联的一个特例而已,需要保证关联字段的唯一性,在<many-to-one>元素中通过unique属性就可以实现关联字段的唯一性。
(4)创建名称为IDcard.java身份证信息的持久化类。
package com.pjb.entity;
/**
* 身份证信息的持久化类
* @author pan_junbiao
**/
public class IDcard
{
private int id; //身份证ID
private String idCardCode; //身份证号码
//默认的构造方法
public IDcard() {
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getIdCardCode()
{
return idCardCode;
}
public void setIdCardCode(String idCardCode)
{
this.idCardCode = idCardCode;
}
}
(5)创建名称为IDcard.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.pjb.entity.IDcard" table="tab_idcard">
<!-- id值 -->
<id name="id" column="id" type="int">
<generator class="native"/>
</id>
<!-- 公民身份证号 -->
<property name="idCardCode" type="string" length="45" not-null="true">
<column name="idCard_code"/>
</property>
</class>
</hibernate-mapping>
(6)在hibernate.cfg.xml配置文件中添加映射文件信息。
<mapping resource="com/pjb/entity/People.hbm.xml"/>
<mapping resource="com/pjb/entity/IDcard.hbm.xml"/>
(7)创建GetPeople.java类,获取公民信息与身份证号码。
package com.pjb.main;
import com.pjb.entity.People;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.Session;
import java.util.Set;
/**
* 获取公民信息与身份证号码
* @author pan_junbiao
**/
public class GetPeople
{
public static void main(String[] args)
{
Session session = null; //声明Session对象
try {
//Hibernate的持久化操作
session = HibernateUtil.getSession(); //获取Session
People people = (People)session.get(People.class,1); //装载对象
System.out.println("公民ID:"+people.getId());
System.out.println("公民名称:"+people.getName());
System.out.println("公民性别:"+people.getSex());
System.out.println("公民年龄:"+people.getAge());
System.out.println("身份证号码:"+people.getIdcard().getIdCardCode());
} catch (Exception e) {
e.printStackTrace();
} finally{
HibernateUtil.closeSession();//关闭Session
}
}
}
执行结果:
1.6 多对多关联
多对多关联关系是Hibernate中比较特殊的一种关联关系,它与一对一和多对一关联关系不同,需要通过另外的一张表保存多对多的映射关系。该节将以应用系统中的权限分配为例讲解多对多的关联关系,例如用户可以拥有多个系统的操作权限,而一个权限又可以被赋予多个用户,这就是典型的多对多关联映射关系。其中用户表(tab_user)和权限表(tab_user)的表关系如下图所示。
说明:由于多对多关联关系的查询对第3个表进行反复查询,在一定程度上会影响系统的性能效率,所以在应用中尽量少使用多对多关联关系的表结果。
【示例】建立用户对象与权限对象的多对多关联关系,查询用户admin所拥有的权限,以及权限“新闻管理”被赋予了哪些用户。
(1)在MySQL数据库创建用户表(tab_user)、权限表(tab_role)和映射表(tab_mapping),并添加相关数据。
-- 判断数据表是否存在,存在则删除
DROP TABLE IF EXISTS tab_user;
DROP TABLE IF EXISTS tab_role;
DROP TABLE IF EXISTS tab_mapping;
-- 创建“用户信息”数据表
CREATE TABLE IF NOT EXISTS tab_user
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
NAME VARCHAR(45) NOT NULL COMMENT '用户名称'
) COMMENT = '用户信息表';
-- 创建“权限信息”数据表
CREATE TABLE IF NOT EXISTS tab_role
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '权限ID',
role_name VARCHAR(45) NOT NULL COMMENT '权限名称'
) COMMENT = '权限信息表';
-- 创建“映射信息”数据表
CREATE TABLE IF NOT EXISTS tab_mapping
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '映射ID',
user_id INT COMMENT '用户Id',
role_id INT COMMENT '权限Id'
) COMMENT = '映射信息表';
-- 添加数据
INSERT INTO tab_user(NAME) VALUES('admin'),('pan_junbiao的博客');
INSERT INTO tab_role(role_name) VALUES('新闻管理员'),('系统管理员'),('广告管理员');
INSERT INTO tab_mapping(user_id,role_id) VALUES(1,1),(1,2),(1,3),(2,1);
(2)创建名称为User.java用户信息的持久化类。
package com.pjb.entity;
import java.util.Set;
/**
* 用户信息的持久化类
* @author pan_junbiao
**/
public class User
{
private Integer id;//唯一性标识
private String name;//用户名称
private Set<Role> roles;//引用的权限实体对象集合
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
(3)创建名称为User.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">
<!-- User实体对象 -->
<hibernate-mapping>
<class name="com.pjb.entity.User" table="tab_user">
<!-- 主键id -->
<id name="id">
<generator class="native"/>
</id>
<!-- 用户名称 -->
<property name="name" not-null="true" />
<!-- 多对多关联关系 -->
<set name="roles" table="tab_mapping">
<key column="user_id"></key>
<many-to-many class="com.pjb.entity.Role" column="role_id"/>
</set>
</class>
</hibernate-mapping>
(4)创建名称为Role.java权限信息的持久化类。
package com.pjb.entity;
import java.util.Set;
/**
* 权限信息的持久化类
* @author pan_junbiao
**/
public class Role
{
private Integer id;//唯一性标识
private String roleName;//权限名称
private Set<User> users;//引用的用户实体对象集合
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
(5)创建名称为Role.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">
<!-- Role实体对象 -->
<hibernate-mapping>
<class name="com.pjb.entity.Role" table="tab_role">
<!-- 主键id -->
<id name="id">
<generator class="native"/>
</id>
<!-- 权限名称 -->
<property name="roleName" not-null="true">
<column name="role_name"/>
</property>
<!-- 多对多关联关系 -->
<set name="users" table="tab_mapping">
<key column="role_id"></key>
<many-to-many class="com.pjb.entity.User" column="user_id"/>
</set>
</class>
</hibernate-mapping>
(6)在hibernate.cfg.xml配置文件中添加映射文件信息。
<mapping resource="com/pjb/entity/User.hbm.xml"/>
<mapping resource="com/pjb/entity/Role.hbm.xml"/>
(7)创建Manager.java类,查询用户admin所拥有的权限,以及权限“新闻管理”被赋予了哪些用户。
package com.pjb.main;
import com.pjb.entity.User;
import com.pjb.entity.Role;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.Session;
import java.sql.SQLOutput;
import java.util.Set;
/**
* 获取权限信息
* @author pan_junbiao
**/
public class Manager
{
public static void main(String[] args)
{
Session session = null; //声明Session对象
try {
//Hibernate的持久化操作
session = HibernateUtil.getSession();//获取Session
User user = (User)session.get(User.class,1); //装载用户对象
System.out.println(user.getName()+"用户所拥有的权限为:");
Set<Role> roleSet = user.getRoles(); //获取权限对象集合
for(Role roleItem : roleSet)
{
System.out.println(roleItem.getRoleName());
}
System.out.println("=====================================================");
Role role = (Role)session.get(Role.class,1); //转载权限对象
System.out.println(role.getRoleName()+"权限被赋予用户:");
Set<User> userSet = role.getUsers(); //获取用户对象集合
for(User userItem : userSet)
{
System.out.println(userItem.getName());
}
} catch (Exception e) {
e.printStackTrace();
} finally{
HibernateUtil.closeSession();//关闭Session
}
}
}
执行结果:
1.7 级联操作
在数据库操作中常常利用主外键约束来保护数据库数据操作的一致性,例如在公民表和身份证表的一对一关系中,如果单独删除公民表中的某条公民信息是不被允许的,需要同时删除身份证表中关联的信息,也就是说两个表的操作需要同步进行,在这种情况下就需要Hibernate的级联操作。
级联(cascade)操作指的是当主控方执行save、update或delete操作时,关联对象(被控方)是否进行同步操作。在映射文件中通过对cascade属性的设置决定是否对关联对象采用级联操作。
cascade属性的设置说明:
参数 | 说明 |
---|---|
all | 所有情况下均采用级联操作。 |
none | 默认参数,所有情况下均不采用级联操作。 |
save-update | 在执行save-update方法时执行级联操作。 |
delete | 在执行delete方法时执行级联操作。 |
【示例】利用级联操作删除公民表中的信息和其在身份证表中的所关联的信息。
(1)在公民实体对象的映射文件People.hbm.xml的一对一关联关系的设置中设置关联对象的级联操作cascade属性。
<?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.pjb.entity.People" table="tab_people">
<!-- id值 -->
<id name="id" column="id" type="int">
<generator class="native"/>
</id>
<!-- 公民姓名 -->
<property name="name" type="string" length="45">
<column name="name"/>
</property>
<!-- 公民性别 -->
<property name="sex" type="string" length="2">
<column name="sex"/>
</property>
<!-- 公民年龄 -->
<property name="age" type="int">
<column name="age"/>
</property>
<!-- 一对一映射 -->
<one-to-one name="idcard" class="com.pjb.entity.IDcard" cascade="all"/>
</class>
</hibernate-mapping>
(2)创建DeletePeople.java类,执行级联操作,删除公民信息。
package com.pjb.main;
import com.pjb.entity.People;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
/**
* 级联操作,删除公民信息
* @author pan_junbiao
**/
public class DeletePeople
{
public static void main(String[] args)
{
Session session = null; //声明Session对象
Transaction ts = null; //声明事务对象
try {
//Hibernate的持久化操作
session = HibernateUtil.getSession(); //获取Session
People people = (People)session.get(People.class,1); // 获取公民信息
if(people!=null)
{
ts = session.beginTransaction(); //开启事务
session.delete(people); //删除持久化对象
ts.commit(); //事务提交
System.out.println("删除成功");
}
else
{
System.out.println("公民信息不存在");
}
} catch (Exception e) {
//事务回滚
if(ts!=null)
{
ts.rollback();
}
System.out.println("删除失败");
e.printStackTrace();
} finally{
HibernateUtil.closeSession();//关闭Session
}
}
}
执行结果:
在main()方法中只执行了一个Session的delete()方法,但是从控制台输出的语句可以看出,Hibernate执行了两次删除操作,在删除公民表中信息内容的同时也删除了身份证表中关联的信息内容。第一条SQL语句是Hibernate装载公民实体对象时的select语句。第二条与第三条是删除装载对象及其关联对象的delete语句。
2、Hibernate查询语言
HQL(Hibernate Query Language)是完全面向对象的查询语言,它提供了更加面向对象的封装,它可以理解如多态、继承和关联的概率。HQL看上去与SQL语言相似,但它却提供了更加强大的查询功能。它是Hibernate官方推荐的查询模式。
2.1 了解HQL语言
HQL语句与SQL语句是相似的,其基本的使用习惯也与SQL是相同的,由于HQL是面向对象的查询语言,所以它需要从目标对象中查询信息并返回匹配单个实体对象或多个实体对象的集合,而SQL语句是从数据库表中查找指定信息返回的是单条信息或多个信息的集合。Hibernate在3.0版本以后可以使用HQL执行update和delete的操作,但是不推荐使用这种方式。
注意:HQL语句是区分大小写的,虽然SQL语句不区分大小写。因为HQL是面向对象的查询语句,它的查询目标是实体对象,也就是Java类,Java类是区分大小写的。
HQL的基本语法如下:
select "对象.属性名"
from "对象"
where "过滤条件"
group by "对象.属性名" having "分组条件"
order by "对象.属性名"
【示例】在实际应用中的HQL语句。
select * from Employee emp where emp.flag='1'
该语句等价于:
from Employee emp where emp.flag='1'
2.2 实体对象查询
在HQL语句中,可以通过from子句对实体对象进行直接查询,例通过from子句查询实体。
from Person
在大多数的情况下,最好为查询的实体对象指定一个别名,方便在查询语句的其他地方引用实体对象,别名的命名方法如下:
from Person per
技巧:别名的首字母最好小写,这是HQL语句的规范写法,与Java中变量的命名规则是一致的,避免与语句中的实体对象混淆。
【示例】通过from子句查询指定字段数据。
select Person(id,name) from Person per
这种查询方式,通过new关键字对实体对象动态实例化,将指定的实体对象属性进行重新封装,即不失去数据的封装性,又可以提高查询的效率。
注意:上面的语句中最好不要使用以下语句进行查询,例如:
select per.id,per.name from Person per
因为此语句返回的并不是原有的对象实体状态,而是一个Object类型的数组,它破坏了数据原有的封装性。
【示例】使用HQL语言,获取雇员列表。
(1)在MySQL数据库创建雇员信息表(tab_Employee),并添加相关数据。
-- 判断数据表是否存在,存在则删除
DROP TABLE IF EXISTS tab_Employee;
-- 创建“雇员信息”数据表
CREATE TABLE IF NOT EXISTS tab_Employee
(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '雇员ID',
NAME VARCHAR(45) NOT NULL COMMENT '雇员名称',
sex VARCHAR(2) NOT NULL COMMENT '雇员性别',
age INT COMMENT '雇员年龄'
) COMMENT = '雇员信息表';
-- 添加数据
INSERT INTO tab_Employee(NAME,sex,age) VALUES('小明','男',32),('小红','女',25),('小强','男',28);
(2)创建名称为Employee.java雇员信息的持久化类。
package com.pjb.entity;
/**
* 雇员信息的持久化类
* @author pan_junbiao
**/
public class Employee
{
private int id; //雇员ID
private String name; //雇员名称
private String sex; //雇员性别
private int age; //雇员年龄
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex = sex;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
(3)创建名称为Employee.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">
<!-- Employee实体对象 -->
<hibernate-mapping>
<class name="com.pjb.entity.Employee" table="tab_Employee">
<!-- 主键id -->
<id name="id">
<generator class="native"/>
</id>
<!-- 雇员名称 -->
<property name="name" type="string" not-null="true" length="45">
<column name="name"/>
</property>
<!-- 雇员性别-->
<property name="sex" type="string" not-null="true" length="2">
<column name="sex"/>
</property>
<!-- 雇员年龄-->
<property name="age" type="int" >
<column name="age"/>
</property>
</class>
</hibernate-mapping>
(4)在hibernate.cfg.xml配置文件中添加映射文件信息。
<mapping resource="com/pjb/entity/Employee.hbm.xml"/>
(5)创建GetEmployee.java类,使用HQL语言,获取雇员列表。
package com.pjb.main;
import com.pjb.entity.Employee;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.query.Query;
import java.util.List;
/**
* 使用HQL语言,获取雇员列表
* @author pan_junbiao
**/
public class GetEmployee
{
public static void main(String[] args)
{
Session session = null; //声明Session对象
try {
session = HibernateUtil.getSession(); //获取Session
String hql = "from Employee emp"; //查询HQL语句
Query q = session.createQuery(hql); //执行查询操作
List<Employee> employeeList = q.list(); //将返回的对象转换为List集合
for(Employee empItem : employeeList)
{
System.out.println("编号:"+empItem.getId()+" || 姓名:"+empItem.getName()+" || 性别:"+empItem.getSex()+" || 年龄:"+empItem.getAge());
}
} catch (Exception e) {
e.printStackTrace();
} finally{
HibernateUtil.closeSession();//关闭Session
}
}
}
执行结果:
2.3 条件查询
条件查询在实际的应用中是比较广泛的,通常使用条件查询过滤数据库返回的查询数据,因为一个表中的所有数据并一定对用户都是有意义的,在应用系统中,需要为用户显示具有价值的信息,所以条件查询在数据查询中占有非常重要的地位,后面讲解的大部分的高级查询也都是基于条件查询的。
HQL的条件查询与SQL语句一样都是通过where子句实现的,查询性别都为“男”的员工,HQL语句可以按照如下定义:
from Employee emp where emp.sex="男"
2.4 HQL参数绑定机制
参数绑定机制可以使查询语句和参数具体值相互独立,不但可以提高程序的开发效率,还可以有效地防止SQL的注入攻击。在JDBC中的PreparedStatement对象就是通过动态赋值的形式对SQL语句的参数进行绑定。在HQL中同样提供了动态赋值的功能,分别有两种不同的实现方法。
(1)利用顺序占位符“?”替代具体参数。
session = HibernateUtil.getSession(); //获取Session
String hql = "from Employee emp where emp.sex=?0"; //查询HQL语句
Query q = session.createQuery(hql); //执行查询操作
q.setParameter(0,"男"); //为占位符赋值
List<Employee> employeeList = q.list(); //将返回的对象转换为List集合
(2)利用引用占位符“:parameter”替代具体参数。
session = HibernateUtil.getSession(); //获取Session
String hql = "from Employee emp where emp.sex=:sex"; //查询HQL语句
Query q = session.createQuery(hql); //执行查询操作
q.setParameter("sex","男"); //为占位符赋值
List<Employee> employeeList = q.list(); //将返回的对象转换为List集合
2.5 排序查询
在SQL中通过order by子句和asc、desc关键字实现查询结果集的排序操作,asc是正序排列,desc是降序排列。在HQL查询语言同样提供了此功能,用法与SQL语句类似,只是排序的条件参数换成了实体对象的属性。
按员工信息按照年龄的倒序排列:
from Employee emp order by emp.age desc
员工信息按照ID的正序序排列:
from Employee emp order by emp.id asc
2.6 聚合函数的应用
在HQL查询语言中,支持SQL中常用的聚合函数,如sum、avg、count、max、min等等,其使用方法与SQL中基本相同。
HQL语句是计算所有的员工的平均年龄。
select avg(emp.age) from Employee emp
HQL语句是查询所有员工中年龄最小的员工信息。
select min(emp.age) from Employee emp
【示例】使用聚合函数,获取雇员平均年龄、最大年龄、最小年龄信息。
package com.pjb.main;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.query.Query;
import java.util.Iterator;
import java.util.List;
/**
* 使用聚合函数
* @author pan_junbiao
**/
public class GetEmpData
{
public static void main(String[] args)
{
Session session = null;// 实例化session对象
try {
session = HibernateUtil.getSession();// 获得session对象
String hql = "select avg(emp.age),max(emp.age),min(emp.age) from Employee emp"; // 聚合函数
Query q = session.createQuery(hql);// 执行查询操作
List emplist = q.list();
Iterator it = emplist.iterator();//使用迭代器输出返回的对象数组
Object[] results = (Object[])it.next();
System.out.println("平均年龄: " + results[0]);
System.out.println("最大年龄: " + results[1]);
System.out.println("最小年龄: " + results[2]);
} catch (HibernateException e) {
e.printStackTrace();
} finally {
HibernateUtil.closeSession();// 关闭session
}
}
}
执行结果:
2.7 分组方法
在HQL查询语言中,使用group by子句进行分组操作,其使用习惯与SQL语句相同,在HQL中同样可以在group by子句中使用having语句,但是在前提需要底层数据库的支持,例如MySQL数据库就是不支持having语句的。
【示例】分组统计男女员工的人数。
package com.pjb.main;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.query.Query;
import java.util.Iterator;
import java.util.List;
/**
* 分组统计男女员工的人数
* @author pan_junbiao
**/
public class EmpGroupBy
{
public static void main(String[] args)
{
Session session = null;// 实例化session对象
try {
session = HibernateUtil.getSession();// 获得session对象
String hql = "select emp.sex,count(*) from Employee emp group by emp.sex";// 条件查询HQL语句
Query q = session.createQuery(hql);// 执行查询操作
List emplist = q.list();
Iterator it = emplist.iterator();//使用迭代器输出返回的对象数组
while(it.hasNext()) {
Object[] results = (Object[])it.next();
System.out.print("员工性别: " + results[0] + "————");
System.out.println("人数: " + results[1]);
}
} catch (HibernateException e) {
e.printStackTrace();
} finally {
HibernateUtil.closeSession();// 关闭session
}
}
}
执行结果:
2.8 联合查询
联合查询是进行数据库多表操作时必不可少的操作之一,例如在SQL中熟知的连接查询方式:内连接查询(inner join)、左连接查询(left outer join)、右连接查询(right outer join)和全连接查询(full join),在HQL查询语句中也支持联合查询的这种方式。
【示例】通过HQL的左连接查询公民信息和其关联的身份证信息。
session = HibernateInitialize.getSession();// 获得session对象
String hql = "select peo.id,peo.name,peo.age,peo.sex,c.idcard_code from People peo left join peo.idcard c";// 条件查询HQL语句
Query q = session.createQuery(hql);// 执行查询操作
list = q.list();
2.9 子查询
子查询也是应用比较广泛的查询方式之一,在HQL中也支持这种方式,但是前提条件是底层数据库支持子查询,在HQL中的一个子查询必须被圆括号()包起来,例如:
from Employee emp where emp.age>(select avg(age) from Employee)
上面的HQL语句是查询大于员工平均年龄的员工信息。
【示例】利用子查询获取ID值最小的员工信息。
package com.pjb.main;
import com.pjb.entity.Employee;
import com.pjb.hibernate.HibernateUtil;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.query.Query;
import java.util.Iterator;
import java.util.List;
/**
* 利用子查询获取ID值最小的员工信息
* @author pan_junbiao
**/
public class QueryMinID
{
public static void main(String[] args)
{
Session session = null;// 实例化session对象
try {
session = HibernateUtil.getSession();// 获得session对象
String hql = "from Employee emp where emp.id = (select min(id) from Employee)"; //HQL语句
Query q = session.createQuery(hql);// 执行查询操作
List<Employee> emplist = q.list();
Employee employee = emplist.get(0);
System.out.println("ID值最小的员工为:"+employee.getName());
System.out.println("其ID值为:"+employee.getId());
} catch (HibernateException e) {
e.printStackTrace();
} finally {
HibernateUtil.closeSession();// 关闭session
}
}
}
执行结果: