您的位置:首页 > 其它

hibernate简介

2014-07-11 14:47 1186 查看

前言

Hibernate(冬眠)

开源免费的持久层框架

www.hibernate.com

Hibernate之父 Kavin King

类似持久层框架

Ibatis TopLink

实现:

软件的核心是数据

数据存在形式:一种是存在内存中,瞬时的

另一种存在硬盘上的,持久的

Object RelationShipMapping

ORM(Object RelationShip Mapping)

类 表

对象 记录

属性 字段

标识属性 主键

搭建框架

下载jar包,熟悉包结构层次

doc 文档

eg 实例程序

etc 配置文件目录

grammar HQL文件目录

lib 依赖包

src 源文件

test 测试程序

1.新建工程,导入JAR包

核心包 hibernate*.jar

lib下所有jar包

数据库驱动包

2.拷贝配置文件

hibernate.cfg.xml Hibernate配置文件

*.hbm.xml
映射文件

书写测试程序

1.建表


//读取配置文件

//调用config()方法读取hibernate.cfg.xml

Configuration config = new Configuration().configure();

//导出创建数据库表

SchemaExport export = new SchemaExport(config);

//导出执行脚本到控制台

export.create(true, true);

2.对象到数据库表中记录的转换

//加载配置文件
Configuration config = new Configuration().configure();
//创建SessionFactory对象
SessionFactory sf = config.buildSessionFactory();
//获取session,session是一个封装了Connection对象的并且加入缓存机制的对象
Session session = sf.openSession();
try{
User u = new User();
u.setName("xiaohei");
u.setPassword("123456");
u.setAge(5);
//执行事务处理,开启事务
session.beginTransaction();
//将对象持久化到数据库
session.save(u);
//关闭事务
session.getTransaction().commit();
}catch(Exception e){
//发生异常回滚事务
session.getTransaction().rollback();
e.printStackTrace();
}finally{
try{
session.close();
}catch(Exception e){
e.printStackTrace();
}
}


Session对象中的API

将对象保存到数据库

session.save(Object);

将数据库 中的记录查询转换成对象返回

session.get(Class,主键);

将数据库 中的记录查询转换成对象返回

session.load(Class,主键);

在执行delete/update操作时建议先通过主键将目标对象查询

返回目标对象之后在做对应操作

删除对应记录

session.delete(Object);

修改对应记录

session.update(Object);

数据库中存在则进行修改操作,不存在则进行插入操作

session.saveOrUpdate(Object);

清除session中所有缓存

session.clear();

清除缓存中特定对象

session.evict(Object);

发送缓存中的内容

session.flush();

对象是否缓存

session.contains(Object);

持久化对象的生命周期

持久化对象的状态

1.瞬时态(transient状态)

当前对象没有纳入到session的管理,数据库中没有与之对应的记录

2.持久态(persistent状态)

当前对象有纳入到session的管理,数据库中有与之对应的记录

3.离线态(detached状态)

当前对象没有纳入到session的管理,但是数据库中有与之对应记录

对象关系

关联关系,组合关系,继承关系

关联关系:多对一关联关系,一对多关联关系,一对一关联关系,多对多关联关系

1.多对一关联关系映射(n:1)

单向:

在多的一端持有一的一端的引用(Student.java)

private Classes clazz;

映射文件中(Student.hbm.xml)

<!-- 配置多对一的关联关系 ,在多的一端持有一的一端的引用 -->

<many-to-one name="clazz" class="com.zhongx.hibernate.day2.Classes" ></many-to-one>

在Student对应表中会生成一个外键字段,引用Classes对应表的主键

在保存学生对象时默认必须先保存引用的班级对象否则会抛出异常:

TransientObjectException

可以通过在多对一标签中设置cascade属性来实现级联操作

<many-to-one name="clazz" class="com.zhongx.hibernate.day2.Classes" cascade="all"/>

cascade属性: none 不包含级联

all 添加,修改,删除都有级联操作

save-update 级联保存与更新

delete 只级联删除

2.一对多的关联关系映射(1:n)

单向:

在一的一端持有多的一端的引用(Classes.java)

private Set<Student> students;

映射文件中(Classes.hbm.xml)

<!-- 配置set集合的映射 -->

<set name="students" cascade="" >

<!-- 配置外键字段名称 -->

<key column="fcs"></key>

<!-- 设置一对多映射 -->

<one-to-many class="com.zhongx.hibernate.day2.Student" />

</set>

在Student对应表中会生成一个外键字段,引用Classes对应表的主键

3.一对一关联关系映射(1:1)

一对一主键关联映射

单向:

在一端持有另一端的引用 (IdCard.java)

private Person person;

映射文件中(IdCard.hbm.xml)

<!-- constrained属性声明当前表的从属关系 -->

<one-to-one name="person" constrained="true"></one-to-one>

进一步声明当前IdCard对象对应的表他的主键同时作为外键引用Person中对应的主键

<id name="id">

<!-- 配置主键的生成策略 -->

<generator class="foreign">

<!--配置主键引用自Person表中的主键-->

<param name="property">person</param>

</generator>

</id>

双向:

在另一端(Person.java)

private IdCard idCard;

映射文件(Person.hbm.xml)

<one-to-one name="idCard"></one-to-one>

一对一唯一外键关联

双向:

在一端

private IdCard idCard;

另一端

private Person person;

通过many2one标签来映射唯一外键,会在对应表中生成外键字段

<many-to-one name="idCard" unique="true" column="pi_fk" cascade="all"></many-to-one>

在另一端的映射文件中

<one-to-one name="person" property-ref="idCard"></one-to-one>

在双向关联过程中设置property-ref="idCard" 来设置当前表中的对象所引用的对应表的外键字段

4.多对多关联关系

数据库中存储结构

通过中间表的形式来维护多对多的关系

中间表:

联合主键,主键分别为两个关联表的主键

正文

Hibernate

1.主要内容

1. 引入

2. 安装配置

3. 核心接口

4. 集合映射

5. 关联映射

6. 继承映射(没讲)

7. HQL和Criteria

8. 事务

9. 缓存

10. 其他

2.什么是hibernate

Hibernate是轻量级java EE应用的“持久化”解决方案,它不仅管理java类到数据库表的映射,还提供数据查询和获取数据的方法,可以大幅度缩短使用jdbc处理数据持久化的时间。

目前主流的数据库依旧是关系型数据库,而java是OOP,当两者结合在一起使用的时候就比较麻烦,Hibernate就减少了这个问题的困扰,它完成对象模型和关系型数据库的映射使得开发者可以完全采用面向对象的方式来开发应用程序。

Hibernate充当了面向对象的程序设计语言和关系型数据库之间的桥梁,Hibernate允许程序开发者采取面向对象的方式来操作关系型数据库,有了Hibernate的支持,使得java EE应用的OOA,OOD, OOP成为一整体。

3. ORM

ORM的全称是Object/Relation Mapping,对象/关系数据库映射,它是一种规范,它表示“完成面向对象的编程语言到关系型数据库的映射”,当使用ORM完成映射后,即可利用面向对象语言的简单易用,又可以用关系型数据库的技术优势。

1. ORM把对数据库的操作转换成对对象的操作,由ORM来负责转成对应的SQL操作。

2. ORM是应用程序和数据库之间的桥梁。

3.1 目前流行的 ORM 产品

1. Apache OJB

2. Cayenne

3. Jaxor

4. Hibernate

5. MyBatis,以前叫iBatis

6. jRelationalFramework

7. mirage

8. SMYLE

9. TopLink

4.Hibernate概述

Hibernate是一个 面向java环境的对象/关系型数据库映射的工具。用来把对象模型表示的对象映射到关系型数据库中去。Hibernate的目标是释放开发者通常的数据持久化相关的编程任务。

它不仅仅管理java类到数据库的映射,还提供数据多种方式的查询方法,可以大大减少使用JDBC处理数据的时间

相较于其它ORM的优势:

1. 开源和免费

2. 可扩展性

3. 轻量级封装

4. 开发者活跃

4.1 Hibernate的下载安装

下载小节省略,强调此次教学使用的hibernate的版本为hibernate.3.6.0.Final即可。

下载完后将解压后中的hibernate3.jar,和lib路径下的required,jpa子目录下所有的jar包拷贝到工程的类加载路径中,此外还包括数据库驱动包。



上面的图是使用hibernate时候所需要的jar包。

4.2hello world

1. 建立工程,拷贝相关的jar包

2. 配置hibernate.cfg.xml。

3. 开发PO类,配置hbm.xml配置文件。

4. 执行CURD操作。

注意:第一步不在此版述。

4.2.1配置hibernate.cfg.xml

<!DOCTYPE hibernate-configuration PUBLIC

"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<property name="connection.driver_class">

<![CDATA[com.mysql.jdbc.Driver]]>

</property>

<property name="connection.url">

<![CDATA[jdbc:mysql://127.0.0.1:3306/hibernate?useUnicode=true&characterEncoding=utf8]]>

</property>

<property name="connection.username"><![CDATA[root]]></property>

<property name=" connection.password"><![CDATA[mysql]]></property>

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQLInnoDBDialect]]>

</property>



<!-- 指定连接池里最大连接数 -->

<property name="hibernate.c3p0.max_size">20</property>

<!-- 指定连接池里最小连接数 -->

<property name="hibernate.c3p0.min_size">1</property>

<!-- 指定连接池里连接的超时时长 -->

<property name="hibernate.c3p0.timeout">5000</property>

<!-- 指定连接池里最大缓存多少个Statement对象 -->

<property name="hibernate.c3p0.max_statements">100</property>

<property name="hibernate.c3p0.idle_test_period">3000</property>

<property name="hibernate.c3p0.acquire_increment">2</property>

<property name="hibernate.c3p0.validate">true</property>

<property name="hbm2ddl.auto"><![CDATA[update]]></property>

<property name="show_sql"><![CDATA[true]]></property>

<property name="hibernate.format_sql">true</property>

<property name="hibernate.use_sql_comments">true</property>



<mapping resource="com/hbm/methodo/User.bhm.xml" />

<ssion-factory>

</hibernate-configuration>

上面是基于mysql的hibernate.cfg.xml配置信息,

绿色部分的配置信息是jdbc的配置信息。

红色部分是C3P0链接池的配置信息。

栗红色的是其它配置信息。

黑丝的是映射文件配置信息。

注意:映射文件是开完POJO类,后才添加到hibernate.cfg.xml里面来的。

4.2.2开发POJO类

POJO类是用来和关系型数据库映射的对象,以后的对数据库的操作可以转换成对POJO对象的操作。

Hibernate持久化类的要求:

1. 要求提供无参的构造函数。(不提供还行运行也不报错)。

2. 提供一个标示符,这个属性通常映射数据库表中的主键字段。

3. 为每个属性提供setter,getter方法,hibernate默认采用属性方式来访问持久化类的属性。

4. 使用非final的类,运行时生成代理是hibernate的一个重要的功能,持久化类没有实现任何借口的话,hibernate使用javassist生成代理(以前版本是使用CGLIB来生成代理)。(是final的时候行运行也不报错javassist, CGLIB这两种代理的不同???)。

5. 重新equals(), hashCode()方法,如果需要把持久化对象放入Set的时候且要进行关联的时候推荐这么做。

public class Person implements Serializable {

private int personId;

private String name;

private int age;

getter() setter()………….

}

4.2.3 配置hbm.xml

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.csuinfosoft.hb3.vo">

<class name="Person" table="t_person"dynamic-update="true"

dynamic-insert="true">

<id name="personId" column="person_id">

<generator class="native" />

</id>

<property name="name" column="person_name">

</property>

<property name="age" column="person_age">

</property>

</class>

</hibernate-mapping>

这个配置文件的是怎么配置会在以后的课程中详细介绍。

4.2.4 curd开发

下面的代码只往数据库里面添加一行数据,这种DAO的写法,比以前的传统的JDBC操作简便了不少,与此同时配置文件却比以前多。

4.2.4.1 Hibernate持久化步骤

1. 开发持久化类,映射文件。

2. 获取Configuration对象。

3. 获取SessionFactory对象。

4. 获取Session对象,打开事务。

5. 使用面向对象的方法来操作数据库。

6. 提交事务,关闭session,关闭SessionFactory。

public static void save(Person p) {

Configuration conf = new Configuration().configure();

SessionFactory sessionFactory = conf.buildSessionFactory();

Session session = sessionFactory.openSession();

Transaction trancation = session.beginTransaction();

session.save(p);

trancation.commit();

session.close();

sessionFactory.close();

}

下面是hibernate生成的表结构,运行的时候hibernate会自动给我们创建表,也就是设计好PO类后,就不用设计数据库表了。

SQL> desc t_person

Name Type Nullable Default Comments

----------- ------------------ -------- ------- --------

PERSON_ID NUMBER(10)

NAME VARCHAR2(255 CHAR) Y

AGE NUMBER(10) Y

PERSON_NAME VARCHAR2(255 CHAR) Y

PERSON_AGE NUMBER(10) Y

5.Hibernate体系结构



通过上图能够发现HIbernate需要一个hibernate.properties文件,该文件用于配置Hibernate和数据库连接的信息。还需要一个XML文件,该映射文件确定了持久化类和数据表、数据列之间的想对应关系。

除了使用hibernate.properties文件,还可以采用另一种形式的配置文件:*.cfg.xml文件。在实际应用中,采用XML配置文件的方式更加广泛,两种配置文件的实质是一样的。

Hibernate全面解决方案架构图



6.hibernate配置

Hibernate的配置比较多,也相对比较繁琐,这里主要要讲hibernate.cfg.xml和bhm.xml的配置,这里只讲最基本的配置,有关关联,缓存,事物,锁的配置会在以后的课程中相继说明。

6.1hibernate.cfg.xml

Hibernate的描述文件可以是一个properties属性文件,也可以是一个xml文件。下面讲一下Hibernate.cfg.xml的配置。

这个配置文件可以配置JDBC, 链接池,映射,缓存,锁,统计,过滤器等配置信息。

注意:这些配置都是写在<session-factory>节点中。

<!DOCTYPE hibernate-configuration PUBLIC

"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<property name="hibernate.connection.driver_class">

<![CDATA[com.mysql.jdbc.Driver]]>

</property>

<property name="hibernate.connection.url">

<![CDATA[jdbc:mysql://127.0.0.1:3306/hibernate?useUnicode=true&characterEncoding=utf8]]>

</property>

<property name="hibernate.connection.username"><![CDATA[root]]></property>

<property name="hibernate.connection.password"><![CDATA[mysql]]></property>

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQLInnoDBDialect]]>

</property>



<!-- 指定连接池里最大连接数 -->

<property name="hibernate.c3p0.max_size">20</property>

<!-- 指定连接池里最小连接数 -->

<property name="hibernate.c3p0.min_size">1</property>

<!-- 指定连接池里连接的超时时长 -->

<property name="hibernate.c3p0.timeout">5000</property>

<!-- 指定连接池里最大缓存多少个Statement对象 -->

<property name="hibernate.c3p0.max_statements">100</property>

<property name="hibernate.c3p0.idle_test_period">3000</property>

<property name="hibernate.c3p0.acquire_increment">2</property>

<property name="hibernate.c3p0.validate">true</property>



<property name="hbm2ddl.auto"><![CDATA[update]]></property>

<property name="show_sql"><![CDATA[true]]></property>

<property name="hibernate.format_sql">true</property>

<!-- <property name="hibernate.use_sql_comments">true</property> -->



<mapping resource="com/hbm/methodo/User.bhm.xml" />

<ssion-factory>

</hibernate-configuration>

6.1.1 JDBC配置

上面绿色部分为JDBC的配置信息,包括“驱动”,“URL”,“用户名”,“密码”这几个都不做说明跟以前配置的一样,要注意的是这些属性都是配置在<property>元素中,且<property>的name属性是指定了的,不能随意定义,value是对应配置项的值(具体配置请参考$hibernate\project\etc\hibernate.properties)

<property name="hibernate.connection.driver_class">

<![CDATA[com.mysql.jdbc.Driver]]>

</property>

<property name="hibernate.connection.url">

<![CDATA[jdbc:mysql://127.0.0.1:3306/hibernate?useUnicode=true&characterEncoding=utf8]]>

</property>

<property name="hibernate.connection.username"><![CDATA[root]]></property>

<property name="hibernate.connection.password"><![CDATA[mysql]]></property>

这里要强调的是”dialect”的配置,首先为什么有这个配置项,由于hibernate封装了底层的JDBC操作,这样有个问题出在,那就是不同的数据库(oracle,mysql mssql)这些数据库在某些操作上存在差异,比如分页,mysql,oracle就不同,怎么样来消除差异呢,hibernate就是通过”dialect”来实现的,这个名字取的很形象,现在主流的关系型数据来说他们有共同的语言那就是sql标准,但是不同的数据库有自己的特殊语法规则,就好比不同的地方有自己的方言一样,我们通过指定方言就可以来弥补在各个数据库上都兼容的问题。

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQLInnoDBDialect]]></property>

下面是hibernate所支持的所有数据的DIALECT。



6.1.2链接池配置

链接池我们以前已经使用过用的是apache的DPCP链接池,hibernate对数据库的操作也是基于数据库链接池的,hibernate支持多种数据库链接池,不支持DPCP(C3P0,Proxool)这里我们使用C3P0链接池(具体配置请参考$hibernate\project\etc\hibernate.properties)。

<!-- 指定连接池里最大连接数 -->

<property name="hibernate.c3p0.max_size">20</property>

<!-- 指定连接池里最小连接数 -->

<property name="hibernate.c3p0.min_size">1</property>

<!-- 指定连接池里连接的超时时长 -->

<property name="hibernate.c3p0.timeout">5000</property>

<!-- 指定连接池里最大缓存多少个Statement对象 -->

<property name="hibernate.c3p0.max_statements">100</property>

6.1.3映射文件配置

<mapping resource="com/hbm/method/vo/User.bhm.xml" />

这里要强调的是每个“持久化类”都要在Hibernate.cfg.xml中配置,如果不配置的话,又不使用注解的话hibernate是无法识别的,这个跟配置servlet一样,如果不在web.xml里面配置的话,即使写了servlet也无法使用的。

6.1.4hbm2ddl.auto

hbm2ddl.auto

create

会根据你的model类来生成表,但是每次运行都会删除上一次的表,重新生成表,哪怕第二次没有任何改变。

create-drop

根据model类生成表,但是SessionFactory一关闭,表就自动删除。

update

最常用的属性,也根据model类生成表,即使表结构改变了,表中的数据仍然存在,不会删除以前的数据。

validate

只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

6.2映射文件

上面说了每个PO(persistobject)类都要在Hibernate.cfg.xml中配置,下面来学习下这个映射文件怎么配置。持久化类的名称默认情况下是*.hbm.xml。

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.csuinfosoft.hb3.vo">

<class name="Person" table="t_person" dynamic-update="true"

dynamic-insert="true">

<id name="personId" column="person_id">

<generator class="native" />

</id>

<property name="name" column="person_name">

</property>

<property name="age" column="person_age">

</property>

</class>

</hibernate-mapping>

映射文件的根元素为<hibernate-mapping>元素,改元素下有多个<class>元素,每个<class>对应一个持久化类。

6.2.1 <hibernate-mapping>

配置项

用途

package

(可选)指定包名的前缀,如果隐射文件中没有类的包名,就用这个。

auto-import

(可选-默认为 true):指定我们是否可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。

default-lazy

(可选-默认为 true)是否延迟加载,默认是true

default-access

(可选-默认为 property):Hibernate用来访问属性的策略。

hibernate-mapping属性里面配置的在这个映射文件里面是全局的,我们可以在里面的配置覆盖它,最典型的就是关于“延迟加载”。

6.2.2.<class>

一个<class>元素就代表一个持久化类,一个映射文件里面可以有多个<class>元素,但是一般情况下一个持久化类对应一个映射文件。

<class name="Person" table="t_person" dynamic-update="true"

dynamic-insert="true">

<id name="personId" column="person_id">

<generator class="native" />

</id>

<property name="name" column="person_name"></property>

<property name="age" column="person_age"></property>

</class>

下面是<class>里面比用的比较多的几个属性,其中name, table这两个属性,lazy的设置会覆盖<hibernate-mapping>中的default-lazy。

配置项

用途

name

(可选):持久化类的 Java 全限定名。

table

(可选-默认是类的非全限定名):对应的数据库表名。

lazy

(可选-默认为 true)是否延迟加载,默认是true

dynamic-update

(可选,默认为 false):指定用于 UPDATE 的 SQL 将会在运行时动态生成,并且只更新那些改变过的字段。

dynamic-insert

(可选,默认为 false):指定用于INSERT 的 SQL 将会在运行时动态生成,并且只插入那些改变过的字段。

6.2.2.1<id>

<id>表示的是这个“持久化对象”的主键,通过<id>指定属性名称,类型,以及在数据库表中对应的列名。可以通过其子标签<generator>指定主键的生成策略。

配置项

用途

name

持久化对象的主键属性名称。

type

主键的类型。

column

数据库表中的主键名称。

6.2.2.1.1<generator>
通过<generator>来设置主键的生成策略,这个会在下面6.2.3章节中讲解。

6.2.2.2<property>

<property>属性,对应持久化类里面的一个属性和数据库表中的一个字段,通过配置可以指定<property>的属性,设置各个字段的信息。

配置项

用途

name

属性名称

type

(可选):属性的类型

column

(可选):属性名在数据库表中对应的列名称

length

(可选):属性的长度

unique

(可选):唯一约束

not-null

(可选):为空约束

formula

(可选): 一个SQL表达式,定义了这个计算(computed) 属性的值。计算属性没有和它对应的数据库字段。

<property name="fullContent"

formula="(select concat(e.title, e.content) from t_event e where e.event_id=event_id)" />

6.2.2.2.1Hibernate 数据库字段映射类型
Java基本类型的Hibernate映射类型



Java时间和日期类型的Hibernate映射



Java大对象类型的Hibernate映射类型



6.2.2.2.2<column>
<property>有一个子节点<column>,它用来精力度的控制表的定义。

<property name="name">

<column name="s_name" not-null="true" unique="true" index="name_index" length="20"></column>

</property>

<property name="age">

<column name="s_age" sql-type="int" length="3" check="s_age>10"></column>

</property>

name

列名

length

列的长度

not-null

是否为空

unique

是否唯一

unique-key

为多个字段设置唯一约束(不讲)

index

建立索引

sql-type

设定字段的SQL类型

check

约束,注意:mysql会忽略约束,就是说check约束不起作用,oracle可以。

Mysql使用: show index fromtblname; 查看索引

注意:<property>有属性type,而<column>有属性 sql-type,这里的运行机制是这样的,hbm2dll会根据<property>的type属性来推断SQL类型,也就是说是跨数据库的,比如:如果是mysql的话字段为varchar类型,如果是oracle的话是varchar2。如果指定了sql-type的话,字段类型就定死了,失去了跨平台的特性了。

6.2.3 主键生成策略

由于hibernate是将对数据库的操作转换成了对对象的操作,但是其底层还是JDBC,这就有一个问题,数据库表中是有主键的。所以我们必须要指定主键是怎么生成。

通过<class>的子元素<generator>来设置主键的生成策略,hibernate里面的主键生成策略有许多种,这里只介绍几种用的最多的。

配置项

用途

assigned

主键由应用逻辑产生,数据交由Hibernate保存时,主键值已经设置完成,无需Hibernate干预(不推荐这么做)。

hilo

通过hi/lo算法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态。

seqhilo

与hilo类似,通过hilo算法实现主键生成机制,只是主键历史状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle。

increment

用于为 long, short 或者 int 类型生成 唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。在集群下不要使用。

sequence

采用数据库提供的sequence机制生成主键,如Oracle Sequence。

native

由Hibernate根据数据库适配器中的定义,自动采用identity、hilo、sequence的其中一种作为主键生成方式。

UUID

IP 地址、JVM 的启动时间(精确到 1/4 秒)、系统时间和一个计数器值(在 JVM 中唯一),来生成一个32位的字符串,这个这个字符串在网络中是唯一的,它不与数据库发生关联,它可以保证全球任何两张表不重复,如果系统插入很频繁的可以考虑它。

identity

采用数据库提供的主键生成机制,如SQL Server, MySQL中的自增长主键生成机制。

select

通过数据库触发器选择一些唯一主键的行并返回主键值来分配一个主键。

foreign

使用另外一个相关联的对象的标识符。它通常和 <one-to-one>联合起来使用

推荐使用native,如果是orale这种使用序列的话推荐使用sequence,如果是mysql这种自增的外键的话推荐使用identity。

Hibernate还有许多的配置,会在以后的学习中逐个的讲解。

6.2.4HibernateUtil初级版

我们写helloworld的时候的代码是不怎么规范的,且代码的重用性比较低,一也没有进行异常处理。

/**

* HiberanteUtil初级班

*/

public final class HiberanteUtilPrimary {

/*

* 构造函数私有

*/

private HiberanteUtilPrimary() {

}

private static SessionFactory seessionFactory = null;

// 确保值运行一次

static {

try {

// 采用默认的hibernate.cfg.xml来启动一个Configuration的实例

Configuration c = new Configuration().configure();

// 由Configuration的实例来创建一个SessionFactory实例

seessionFactory = c.buildSessionFactory();

} catch (Throwable ex) {

System.err.println("Initial SessionFactory creation failed." + ex);

throw new ExceptionInInitializerError(ex);

}

}

//获取session

public static Session getSession() {

return seessionFactory.openSession();

}

}

注意异常的处理,或者不写,但是一定要处理,这点一定要注意。

public static void save(Object obj) {

Session session = null;

Transaction transaction = null;

try {

session = HiberanteUtilPrimary.getSession();

transaction = session.beginTransaction();

session.save(obj);

transaction.commit();

} catch (HibernateException e) {

if (transaction != null) {

transaction.rollback();

}

throw e;

} finally {

if (session != null && session.isOpen()) {

session.close();

}

}

}

这种也可以,因为catch不写也是可以的,如果发生异常,就不会提交事务,默认情况下是会事务回滚的。

public static void save(Object obj) {

Session session = null;

Transaction transaction = null;

try {

session = HiberanteUtilPrimary.getSession();

transaction = session.beginTransaction();

session.save(obj);

transaction.commit();

}

finally {

if (session != null && session.isOpen()) {

session.close();

}

}

}

7.核心接口

Hibernate的核心接口几乎在任何实际开发中都会用到。通过这些接口,不仅可以存储和获得持久对象,并且能够进行事务控制,还可以进行查询。



7.1核心接口简介

Configuration接口:Configuration接口负责配置并启动Hibernate,创建SessionFactory对象。在Hibernate的启动的过程中,Configuration类的实例首先定位映射文档位置、读取配置,然后创建SessionFactory对象。

SessionFactory接口:SessionFactroy接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。

Session接口:Session接口负责执行被持久化对象的CRUD操作(CRUD的任务是完成与数据库的交流,包含了很多常见的SQL语句。)。但需要注意的是Session对象是非线程安全的。同时,Hibernate的session不同于JSP应用中的HttpSession。这里当使用session这个术语时,其实指的是Hibernate中的session,而以后会将HttpSesion对象称为用户session。

Transaction接口:Transaction接口负责事务相关的操作。它是可选的,可发人员也可以设计编写自己的底层事务处理代码。

Query和Criteria接口:Query和Criteria接口负责执行各种数据库查询。它可以使用HQL语言或SQL语句两种表达方式。

7.2 Configuration接口

Configuration 接口负责管理Hibernate 的配置信息。它包括如下内容:

Hibernate运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,数据库连接池等。Hibernate映射文件(*.hbm.xml)。

Hibernate配置的两种方法:

属性文件(hibernate.properties)

调用代码:Configurationconfig = new Configuration();

Xml文件(hibernate.cfg.xml)

调用代码:Configurationconfig = new Configuration().configure();

在hibernate中其实可以不用配置文件也就是说不用hibernate.properties和hibernate.cfg.xml,怎么实现我们不讲,有兴趣可以去网上查资料。

7.3 SessionFactory接口

SessionFactory是数据库编译后的内存镜像,通常情况下,整个应用只有唯一的一个会话工厂(一个数据库对应一个SessionFactory,如果应用有多个数据库的话就要用多个SessionFactory),在应用程序的多个线程间共享,所以它是线程安全的。一般情况下在应用初始化时被创建。应用程序可以从SessionFactory(会话工厂)里获得Session(会话)实例。

Configuration conf = new Configuration().configure();

SessionFactory sessionFactory = conf.buildSessionFactory();

上面的代码展示了代码中SessionFactory实例过程,它是通过Configuration.buildSessionFactory()获得的。

注意:可以把SessionFactory等价于DriverManager。

7.4 Transaction接口

Transaction接口是对实际事务实现的一个抽象,这些实现包括JDBC事务或者JTA等。这样设计允许开发人员能够使用一个统一的事务操作接口使得自己的项目可以在不同的环境和容器(Container)之间方便地迁移。

Configuration conf = new Configuration().configure();
SessionFactory sessionFactory = conf.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction trancation = session.beginTransaction();
…………………
trancation.commit();

7.5 Session接口

在Hibernate session是对Connection的封装,一个Session对应了一个Connection(与传统意义上的Web层的HttpSession没什么关系), Session是Hibernate持久化操作的基础。提供了众多持久化方法,如:save,update,delete等。提供了透明地完成了对象的“增删查改”操作(CRUD)。但是Session是线程不安全的,不能多个线程使用同一个Session。它与Connection一样,用完一定要关闭。它的使用规则是“尽量晚的获得,尽量早的释放。”

Configuration config=new Configuration().configure();

SessionFactory sf= config.buildSessionFactory();

Session session = sf.openSession();

上面的代码是展示session是怎么获得的,它是从SessionFactory获取的。

注意:暂时不使用Session.getCurrentSession()。这时候使用它是会报错的。

7.5.1 Session方法

要进行持久化操作的话,就得调用Session对象中的方法,来执行对应的增删改查。

7.5.1.1 save()

save()方法完成对象的持久化操作。

Serializable save(Object
object)throws
HibernateException

其实这个方法执行后并没有真正把手插入到数据库中,hibernate只把其放入到缓存中(或者内存,或者一级缓存中)然后改变了PO对象的状态,save()的返回值是PO的主键。

public static void testSave() {
SessionFactory sf = null;
Session s = null;
Transaction t = null;
try {
Configuration conf = new Configuration().configure();
sf = conf.buildSessionFactory();
s = sf.openSession();
t = s.beginTransaction();
// /////////////////////////////
Person p = new Person();
p.setName("testSave=" + new Date().getTime());
p.setAge(9);
s.save (p);
System.out.println("1.p.getPersonId()="+ p.getPersonId());
t.commit();
System.out.println("2. p.getPersonId()=" + p.getPersonId());
} catch (HibernateException e) {
if (t != null) {
t.rollback();
}
throw e;
} finally {
if (s != null) {
s.close();
}
if (sf != null) {
sf.close();
}
}
}
下面是控制台打印的语句,从下面的打印语句可以看出,首先打印的是获取序列,以便在插入数据时候当作主键使用,打印insertSQL语句是在打印输出语句之后,也就是说save()后没有立即插入数据库,此时“持久化对象”的“主键”已经有值了。从打印来看是在事物提交的时候才插入数据库。

Hibernate:
select
hibernate_sequence.nextval
from
dual
save()=34, id=34
Hibernate:
insert
into
t_person
(person_name, person_age, person_id)
values
(?, ?, ?)
111111111
222222222
看下面的代码,这个代码和上面的差不多只是在调用save()后修改了PO对象的name属性,从打印的后台结果可发现,在最后打印了一个条update语句,查看数据库的值也发现先插入的”hibernate”没有,只有一条”提交前修改”一条,这个也就是说在session关闭之前对PO的任何修改都会更新到数据库。

Configuration conf = new Configuration().configure();
SessionFactory sessionFactory = conf.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction trancation = session.beginTransaction();
Person p = new Person();
p.setName("hibernate");
p.setAge(9);
Serializable s = session.save(p);
System.out.println("save()=" + s + ", id=" + p.getPersonId());
p.setName("提交前修改");
trancation.commit();
session.close();
sessionFactory.close();


Hibernate:
select
hibernate_sequence.nextval
from
dual
save()=37, id=37
Hibernate:
insert
into
t_person
(person_name, person_age, person_id)
values
(?, ?, ?)
Hibernate:
update
t_person
set
person_name=?
where
person_id=?
7.5.1.1.1 dynamic-insert
插入数据的时候会有这种情况,也就是某些字段没有值,在某人情况下插入的SQL 语句也会有值,如果这些没有值的字段不在SQL体现的话可以把dynamic-insert的值设置成true。

下面的测试中没有设置SEX,以前的SQL语句如下面,sex即使没有值也在SQL语句中出现了,其实这种是可以避免的。

Student s = new Student();
s.setName("test1234");
//s.setSex("女");
s.setAge(18);
s.setQq("345306321");
s.setBirthday(new Date());
session.save(s);

insert
into
t_student
(s_name, s_sex, s_age, s_qq, s_birthday, s_id)
values
(?, ?, ?, ?, ?, ?)
如果将dynamic-insert="true"在看SQL语句,就没有SEX了。

<class name="Student" table="t_student" dynamic-insert="true">
insert
into
t_student
(s_name, s_age, s_qq, s_birthday, s_id)
values
(?, ?, ?, ?, ?)

7.5.1.2 persist()

persist()与save()区别, 不讲,从代码上面无法区分其差异。

public static void testPersist() {
SessionFactory sf = null;
Session s = null;
Transaction t = null;
try {
Configuration conf = new Configuration().configure();
sf = conf.buildSessionFactory();
s = sf.openSession();
t = s.beginTransaction();
Person p = new Person();
p.setName("testPersist=" + new Date().getTime());
p.setAge(9);
s.persist(p);
System.out.println("1. p.getPersonId()=" +p.getPersonId());
t.commit();
System.out.println("2.p.getPersonId()=" + p.getPersonId());
} catch (HibernateException e) {
if (t != null) {
t.rollback();
}
throw e;
} finally {
if (s != null) {
s.close();
}
if (sf != null) {
sf.close();
}
}
}

7.5.1.3 get(),load()

get() load()这两个方法都是通过ID去数据库查询数据。

Object load(Classclazz,Serializable
id) throws
HibernateException
Object get(Class
clazz,Serializable id)throwsHibernateException
get(), load()方法的区别:

load是懒加载方式,用的时候才去数据库查询,前提是session没有关闭,也就是如果我们查询完后,session又关闭了,又作为返回值返回给调用它的方法,在方法里面获取PO里面对象的时候会报错。而get()不会。

注意:如果PO类定义成final类型的话,懒加载会失效!

<hibernate-mapping package="com.csuinfosoft.hb3.vo" default-lazy="false">
<class name="Person" table="t_person" lazy="false">
注意:load()的时候不能判断获取到的对象为空,因为不可能为空,即使查不到也不会为空,因为懒加载的时候是直接new出这个对象然后返回。

s = sf.openSession();
t = s.beginTransaction();
Person p1 = (Person) s.load(Person.class, 148);
if (p1 == null) {
System.out.println("load(148) 查询不到");
}
// System.out.println("1. load() p.getPersonId()=" + p1.toString());
// Person p2 = (Person) s.get(Person.class, 48);
// System.out.println("1. get() p.getPersonId()=" + p2.toString());
// Hibernate.initialize(p1);
t.commit();

7.5.1.4 delete()

删除对象

void delete(Object object)throwsHibernateException

7.5.1.5 update()

更新对象,当对象在“脱管”状态下的时候就需要调用update()来更新。如果是“持久”态的时候是不需要调用的,因为修改PO类的属性hibernate会检测到,会自动更新到数据库(不是立即,默认在commit之后)

void update(Object object) throwsHibernateException
7.5.1.5.1 dynamic-update
类似于7.5.1.1.1 dynamic-insert,更新的时候只更新有变化的字段。

7.5.1.6saveOrUpdate()

这个考虑在7.6 对象状态后讲解。

当不知道对象的状态,就是说不知道PO对象是“瞬时”,还是“脱管”的状态的时候,这个时候就用saveOrUpdate()来实现,由hibernate来决定是save()还是update()。hibernate会根据ID来判断(int型的时候判断是否等于0,string型的时候判断是否为null,如果要自定义的话,比如-1表示“瞬时态”的话看下面配置unsaved-value="-1"),瞬时状态的时候是没有ID的,脱管状态是有ID的。saveOrUpdate()执行后,PO的状态变成“持久态”了,而merge()执行后对象的状态还是“脱管”。

<id name="addressId" column="address_id" unsaved-value="-1">
<generator class="identity" />
</id>
void saveOrUpdate(Object object) throwsHibernateException

7.5.1.7 merge()(不讲)

使用merge()的时候,实体的状态不会变成持久态,且它的返回值是持久化的对象。

t = s.beginTransaction();
Person p = new Person();
p.setName("merge....");
p.setAge(19);
Person _p = (Person) s.merge(p);
System.out.println(_p);
// 使用merge()的话,实体不会变成持久态,所以下面的修改,不会反应到数据库
p.setName("修改了?");
t.commit();
1. 如果对象处于瞬态,我所说的游离态是指该对象的id值为空,当调用merge方法的时候,就会直接执行插入操作。

2. 如果对象是脱管:就是说数据库有一行数据对应,且ID不为空,这样的话只会执行一条查询语句。

3. 如果是持久态:如果再调用merge,那么hibernate就会先判断该记录是否被修改,没有则什么也不干,修改了就update

7.6对象状态

Hibernate的PO对象有3种状态分别为:瞬时,持久,脱管(游离)。

1. 瞬时(transient):数据库中没有数据与之对应,超过作用域会被“JVM垃圾回收器”回收,一般是new出来且与session没有关联的对象。

2. 持久(persistent):数据库中有数据与之对应,当前与session有关联,并且相关联的session没有关闭,事务没有提交;持久对象状态发生改变,在事务提交时会影响到数据库(hibernate能检测到)。

3. 脱管(detached):数据库中有数据与之对应,但当前没有session与之关联;托管对象状态发生改变,hibernate不能检测到。(脱离session管理)

要衡量对象是什么状态的话,可以基于两个方面来考虑,是否被session管理,是否在数据库里面有数据与之对应。



7.7 HibernateUtil工具类

这个类是不允许继承,也不能被实例化所以这个类是final型的,且是构造方法私有。

在static代码库里面构建Configuration和SessionFactory对象。(之所以用static代码块,在于在只运行一次。)

public finalclass HibernateUtil {

private HibernateUtil() {
}

public static final SessionFactory sf;

// ThreadLocal可以隔离多个线程的数据共享,因此不再需要对线程同步
// 相当于一个没有KEY的线程,可以把当前线程当成KEY,放在它里面的东西在同一个线程都有效
public static final ThreadLocal<Session> session = new ThreadLocal<Session>();

static {
try {
// 采用默认的hibernate.cfg.xml来启动一个Configuration的实例
Configuration c = new Configuration().configure();
// 由Configuration的实例来创建一个SessionFactory实例
sf = c.buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static Session currentSession() throws HibernateException {
Session s = session.get();
// 如果该线程还没有Session,则创建一个新的Session
if (s == null) {
s = sf.openSession();
// 将获得的Session变量存储在ThreadLocal变量session里
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = session.get();
if (s != null && s.isOpen()) {
s.close();
}
session.set(null);
}
public static void closeSessionFactory() throws HibernateException {
if (sf != null && !sf.isClosed()) {
sf.close();
}
}
// 不使用catch会把异常向外抛
public static void save(Object obj) {
Session s = null;
Transaction t = null;
try {
s = HibernateUtil.currentSession();
t = s.beginTransaction();
s.save(obj);
t.commit();
} finally {
if (s != null && s.isOpen()) {
s.close();
}
}
}
public static void update(Object obj) {
Session s = null;
Transaction t = null;
try {
s = HibernateUtil.currentSession();
t = s.beginTransaction();
s.update(obj);
t.commit();
} finally {
if (s != null && s.isOpen()) {
s.close();
}
}
}
public static void delete(Object obj) {
Session s = null;
Transaction t = null;
try {
s = HibernateUtil.currentSession();
t = s.beginTransaction();
s.delete(obj);
t.commit();
} finally {
if (s != null && s.isOpen()) {
s.close();
}
}
}
public static Object getById(Class<?> clazz, Serializable id) {
Session s = null;
try {
s = HibernateUtil.currentSession();
return s.get(clazz, id);
} finally {
if (s != null && s.isOpen()) {
s.close();
}
}
}
}

7.8 getCurrentSession()

getCurrentSession创建的session会和绑定到当前线程,而openSession不会。

getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭。

这里getCurrentSession本地事务(本地事务:jdbc)时 要在配置文件里进行如下设置:

如果使用的是本地事务(jdbc事务)

<property name="hibernate.current_session_context_class">thread</property>
如果使用的是全局事务(jta事务)

<property name="hibernate.current_session_context_class">jta</property>

8.集合映射

集合属性是非常常见的,例如每个人的考试成绩,就是典型的map结构,没门功课对应一个成绩。或者间断的集合属性。

集合大致有两种,第一种是单纯的集合属性,如List,Set或者数组。还有一种是Map结构的集合属性,每个属性的值都有对应的KEY,VALUE。

Hibernate要求持久化集合字段必须声明为接口。实际接口可以是java.util.Set, java.util.Collection, java.util.List, java.util.Map,java.util.Map, java.util.SortedSet, java.util.SortedMap。也可以是自定义类型。

集合映射的元素大致有如下:

1. list: 用于映射List集合属性。

2. set: 用于映射Set集合属性。

3. map: 用于映射Map集合属性。

4. array: 用于映射数组集合属性。

5. primitive-array: 专门用于映射基本数据类型的数组。

6. bag: 用于隐射无序集合。

8.1 List集合映射

List是有序集合,因此持久化到数据时候也必须增加一列来表示集合元素的次序。

Person类有一个集合属性schools,该属性用来对应多个学校。

8.1.1List集合映射代码

PersonList.java

public class PersonList {
private Integer id;
private String name;
private int age;
// 集合属性,保留该对象关联的学校
private List<String> schools = new ArrayList<String>();
}
PersonList.hbm.xml

<!-- 映射List集合属性 -->
<list name="schools" table="t_school_list">
<!-- 外键关联 -->
<key column="person_id" />
<!-- 映射集合属性数据表的集合索引列 -->
<index column="list_order" />
<!-- 映射保存集合元素的数据列 -->
<element column="school_name" type="string" />
</list>

8.1.2 保存测试

PersonList p = new PersonList();
p.setName("TestList");
p.setAge(1);
List<String> schools = new ArrayList<String>();
schools.add("小学");
schools.add("初中");
schools.add("高中");
schools.add("大学");
p.setSchools(schools);
s.save(p);
t.commit();
List集合映射保存分析

PersonList.java映射后的表”t_person_list”的表结构。



”t_person_list”表数据。



List<String> schools映射后的表”t_school_list”的表结构。



”t_school_list”表的数据。这里要注意的是”list_order”,为排序字段。



下面是hibernate产生的sql语句,从语句可以看出先查询序列,获得主键的值,先持久化主类”PersonList”,然后持久化”schools”List集合属性。



注意:”t_school_list”表的主键是”person_id, list_order”的联合主键。

Hibernate产生的sql语句如下:

Hibernate:
select
hibernate_sequence.nextval
from
dual
Hibernate:
insert
into
t_person_list
(person_name, person_age, person_id)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_list
(person_id, list_order, school_name)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_list
(person_id, list_order, school_name)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_list
(person_id, list_order, school_name)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_list
(person_id, list_order, school_name)
values
(?, ?, ?)

8.1.3 删除测试

通过先查询然后删除,这里可以正常删除,也产生delete的SQL语句,这点和SET不一样,且还没有设置级联操作。

Student s = (Student) HibernateUtil.getById(PersonList.class, 7);

HibernateUtil.delete(s);

8.1.4 查询测试

这里的结论和8.3.5里面一样。

session = HibernateUtil.getSession();

PersonList s = (Student) session.get(PersonList.class, 8);

System.out.println(s.getName());

8.2数组集合映射(不讲)

Hibernate对数组的处理和List处理方式非常相似。实际上List和数组非常像。它的区别就是List长度可变,数组不可变。

8.2.1数组集合映射代码

PersonArray.java

public class PersonArray {
private Integer id;
private String name;
private int age;
// 集合属性,保留该对象关联的学校
private String schools[] = null;
}
PersonArray.hbm.xml

<!-- 映射List集合属性 -->
<array name="schools" table="t_school_array">
<!-- 外键关联 -->
<key column="person_id" />
<!-- 映射集合属性数据表的集合索引列 -->
<index column="list_order" />
<!-- 映射保存集合元素的数据列 -->
<element column="school_name" type="string" />
</array>
Hibernate产生的sql语句如下:

Hibernate:
select
hibernate_sequence.nextval
from
dual
Hibernate:
insert
into
t_person_array
(person_name, person_age, person_id)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_array
(person_id, list_order, school_name)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_array
(person_id, list_order, school_name)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_array
(person_id, list_order, school_name)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_array
(person_id, list_order, school_name)
values
(?, ?, ?)

8.2.2 数组集合映射分析

PersonArray.java映射后的表”t_person_array”的表结构。



”t_person_array”表数据。



String schools[]映射后的表”t_school_array”的表结构。



”t_school_array”表的数据。这里要注意的是”list_order”,为排序字段。



这个跟list映射一样也是联合主键。



8.3 Set集合映射

Set集合属性的映射与List有点不同,但因为Set是无序,不可重复的集合。与List相同的是,Set集合同样需要使用<key>元素来映射“外键列”,以保证关联。与List一样只能用Set接口,不能用实现类。

8.3.1 Set集合映射配置

PersonSet.java

public class Student {
private List<String> images;
}
PersonSet.hbm.xml

<set name="images" table="t_student_images">
<!-- 外键关联 -->
<key column="s_id" not-null="true" />
<!-- set为无序,不重复,索引不需要 -->
<!-- 映射保存集合元素的数据列 -->
<element column="image" not-null="true" type="string" />
</set>

8.3.2 测试保存

Student s = new Student();
s.setName("test c3p0");
s.setSex("女");
s.setQq("123456");
s.setAge(22);
s.setBirthday(new Date());
Set<String> images = new HashSet<String>();
images.add("1.jpg");
images.add("2.jpg");
s.setImages(images);
HibernateUtil.save(s);
Set集合映射保存分析

Hibernate产生的sql语句如下:

Hibernate:
select
hibernate_sequence.nextval
from
dual
Hibernate:
insert
into
t_student
(s_name, s_sex, s_age, s_qq, s_birthday, s_id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
t_student_images
(s_id, image)
values
(?, ?)
Hibernate:
insert
into
t_student_images
(s_id, image)
values
(?, ?)

8.3.3 测试删除

8.3.5 查询测试

这里测试查询Student的时候,是否同时查询与之关联的images。

session = HibernateUtil.getSession();
// 获得事务
t = session.beginTransaction();
Student s = (Student) session.get(Student.class, 1);
System.out.println(s.getName());
t.commit();
运行时候发现只查询了t_student,而没有查询t_student_images,也就是说默认情况下是延迟加载,这也是从性能上考虑的。当做如下配置的时候:

<set name="images" table="t_student_images"lazy="false">
运行时候发现t_student,t_student_images都已经查询,这也说明默认情况下lazy="true"。

注意:这里不要写s.toString(),由于重写了Student的toString(),会使用反射读取里面所有的值,导致看不出效果。

8.3.4作业

Person可以有多学校,小学到大小。

8.4 bag元素映射(不讲)

Bag元素既可以是list又可以是set,甚至可以是Collection集合,不管是哪种集合,bag都会映射成无序集合。集合属性对应的表没有主键。

Bag元素只要<key>元素来映射关联的外键列,使用<element>来映射集合属性的元素列。

8.4.1Bag集合映射代码

public class PersonBag {
private Integer id;
private String name;
private int age;
// 集合属性,保留该对象关联的学校
private Collection<String> schools = new ArrayList<String>();
}
s = HibernateUtil.currentSession();
t = s.beginTransaction();
PersonBag p = new PersonBag();
p.setName("Test Array");
p.setAge(10);
Collection<String> schools = new ArrayList<String>();
schools.add("小学");
schools.add("初中");
schools.add("高中");
schools.add("大学");
p.setSchools(schools);
s.save(p);
t.commit();
Hibernate:
select
hibernate_sequence.nextval
from
dual
Hibernate:
insert
into
t_person_bag
(person_name, person_age, person_id)
values
(?, ?, ?)
Hibernate:
insert
into
t_school_bag
(person_id, school_name)
values
(?, ?)
Hibernate:
insert
into
t_school_bag
(person_id, school_name)
values
(?, ?)
Hibernate:
insert
into
t_school_bag
(person_id, school_name)
values
(?, ?)
Hibernate:
insert
into
t_school_bag
(person_id, school_name)
values
(?, ?)

8.4.2 bag集合映射分析



8.5 map集合映射

映射一个map

8.5.1 Map集合映射代码

public class Student {
private Map<String, Float> scores = null;
}
配置文件

<map name="scores" table="t_student_score">
<key column="s_id" />
<map-key column="subject" type="string" />
<element column="grade" type="float" />
</map>
s = HibernateUtil.currentSession();
t = s.beginTransaction();
PersonMap p = new PersonMap();
p.setName("Test map");
p.setAge(10);
Map<String, Float> scores = new HashMap<String, Float>();
scores.put("chinese", 100f);
scores.put("english", 100f);
scores.put("math", 100f);
p.setScores(scores);
s.save(p);
t.commit();

8.5.2 Map集合映射分析



9.Hibernate关联映射

客观世界中的对象很少有孤立存在的,例如老师,往往与被授课的学生存在关联关系,如果已经得到某个老师的实例,那么应该可以直接获取该老师对应的全部学生。返回来,如果已经得到一个学生的实例,也应该可以访问该学生对应的老师。这种实例之间的相互访问就是管理关系。

Hibernate提供的映射关系很多, 比较复杂,也很容易忘记。熟悉这些映射,需要大量的实践才能掌握。常用的有“一对一,多对一”

Hibernate关系映射分类:

单向关联

1. 一对一外键单向关联

2. 一对一主键单向关联

3. 一对一连接表单向关联

4. 一对多外键单向关联

5. 一对多连接表单向关联

6. 多对一外键单向关联

7. 多对一连接表单向关联

8. 多对多单向关联

双向关联

1. 一对一外键双向关联。

2. 一对一主键双向关联

3. 一对一连接表双向关联

4. 一对多外键双向关联

5. 一对多连接表双向关联

6. 多对多双向关联

9.1单向关联映射

单向关联映射介绍:“一对一”外键单向关联,“一对一”主键单向关联,“一对一”连接表单向关联,“一对多”外键单向关联,“一对多”连接表单向关联,“多对一”外键单向关联,“多对一”连接表单向关联,“多对多”单向关联。

9.1.1 一对一单向外键关联

事实上,单向一对一与N对一的实质是相同的,一对一是N对一的特例,它们的配置也相似,只需要将many-to-one元素增加unique=”true”的属性,用于表示N的一端必须唯一的,在N的一端增加唯一约束。

9.1.1.1一对一单向外键关联实例1(一)

用户信息与用户详细信息这是一个典型的一对一,这里说的是单向,所以就存在是从User 到UserDetail还是UserDetail到User这两种情况。

从下图可以发现,外键是建立在UserDetail里面,它引用了User实体中的主键作为外键,所以这里来说User是主表。从模型上来讲User是主对象UserDetil是从属对象。

从配置文件上来看有问题,因为外键是建立在UserDetail中,所以在外键的配置必须在UserDetail这一端,由于又是单向的导致User这段无法保持对UserDetail的引用,



9.1.1.1.1实体
从数据库模型上看是User是主对象,UserDetail是从对象,但是从实体模型上看,UserDetail是主对象,而User是从对象,这个导致的问题是在User中引用不到UserDetail,但是UserDetail又能引用到User,我各人觉得这样在现实中是不行的。

要避免这问题,得双向配置才能避免。

User.java
public class User implements Serializable {
private int id;
private String name;
private String password;
private UserDetail userDetail;
}
UserDetail.java
public class UserDetail implements Serializable {
private int userDetailId;
private String nickName;
private String sex;
private String address;
private String email;
private Date birthday;
}
9.1.1.1.2配置文件
在有”外键”的一端使用many-to-one,另一端用one-to-one。原因在于一对一本质上是多对一的多的一端是唯一的特例,但是加了外键,其本质上还是多对一,所以加外键的这端是使用<many-to-one unique=”true” />。

也就是当多个User对应一个UserDetail的User变成了唯一,也就成了一对一了。

User.bhm.xml

<class name="User" table="t_user">
<id name="id" column="user_id">
<generator class="native" />
</id>
<property name="name" column="user_name" type="string"
length="30" not-null="true" />
<property name="password" column="user_pwd" type="string"
length="30" not-null="true" />
<many-to-one name="userDetail" column="user_detail_id"
cascade="all" unique="true" />
</class>
UserDetail.bhm.xml

<class name="UserDetail" table="t_user_detail">
<id name="id" column="user_detail_id">
<generator class="native" />
</id>
<property name="age" column="user_age" type="int" />
<property name="sex" column="user_sex" type="string" length="4" />
<property name="address" column="user_address" type="string"
length="40" />
</class>
9.1.1.1.3测试保存
User user = new User();
user.setName("test");
user.setPassword("123445");

UserDetail userDetail = new UserDetail();
userDetail.setSex("male");
userDetail.setAge(20);
userDetail.setAddress("hunan changsha");

user.setUserDetail(userDetail);

session.save(userDetail);
session.save(user);
这样保存也可以,只是它会产生update t_user的语句,原因在于插入User的时候UserDetail的ID还没有,所以要更新。

session.save(user);
session.save(userDetail);
上面的保存可以通过,下面的保存不可以,也就是只保存User,不保存Userdetail。

session.save(user);
//session.save(userDetail);
通过配置下面的级联也可以实现值保存User,不保存Userdetail。

<many-to-one name="userDetail" column="user_detail_id"unique="true"cascade="save-update" />
9.1.1.1.4分析
T_USER表的结构

CREATE TABLE `t_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(30) NOT NULL,
`user_pwd` varchar(30) NOT NULL,
`user_detail_id` int(11) DEFAULT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `user_detail_id` (`user_detail_id`),
KEY `FKCB63CCB6F07F5343` (`user_detail_id`),
CONSTRAINT `FKCB63CCB6F07F5343` FOREIGN KEY (`user_detail_id`) REFERENCES `t_user_detail` (`user_detail_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
T_USER_DETAIL表的结构
CREATE TABLE `t_user_detail` (
`user_detail_id` int(11) NOT NULL AUTO_INCREMENT,
`user_age` int(11) DEFAULT NULL,
`user_sex` varchar(4) DEFAULT NULL,
`user_address` varchar(40) DEFAULT NULL,
PRIMARY KEY (`user_detail_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
9.1.1.1.5测试删除

session.delete(session.get(User.class, 1));
<many-to-one name="userDetail" column="user_detail_id"unique="true" cascade="save-update,delete" />
如果配置了级联删除,上面的删除是删除User对象,从删除结果来看,它会级联删除了与之关联的UserDetail对象。不配置的话UserDetail不会删除。

delete
from
t_user
where
user_id=?
delete
from
t_user_detail
where
user_detail_id=?
如果删除UserDetail的时候,会发现报错了,这个我们在双向中讲解。

session.delete(session.get(UserDetail.class, 2));

9.1.1.2一对一单向外键关联实例2(二)

(这里使用的是双向,不是单向)

这里使用“客户”与“地址”信息作为实例,每个客户有一个地址。

9.1.1.2.1实体
Customer.java
public class Customer implements Serializable {
private int id;
private String name;
private Address address;

}

Address.java
public class Address implements Serializable {
private int id;
private String provice;
private String city;
private int zipCode;
private Customer customer;
}
9.1.1.2.2配置文件
Customer.hbm.xml
<class name="Customer" table="t_customer">
<id name="id" column="customer_id">
<generator class="native" />
</id>
<property name="name" column="c_name" type="string" length="40"
not-null="true" />
<many-to-one name=" address " column="address_id"
class="Address" cascade="all"unique="true" />
</class>
这里使用<many-to-one>的特例情况来表示一对一,也就是“当多对一的多的这一段设置成唯一后,就成了一对一”。

name

表示Customer中要持久化的属性,这里为address。

unique

这里设置成true表示:每一个customer对象都有唯一的Address,也就是customer与address一对一关联关系。

cascade

这里设置成all,表示保存,更新与删除customer的时候会“级联”保存,更新,删除address对象。

class

表示customer中要持久化的属性address的类型。

Address.hbm.xml

<class name="Address" table="t_address">
<id name="id" column="address_id">
<generator class="native" />
</id>
<property name="provice" column="provice_name" type="string"
length="30" not-null="true" />
<property name="city" column="city_name" type="string"
length="30" not-null="true" />
<property name="zipCode" column="zip_code" type="int" length="6"
not-null="true" />
<one-to-one name="customer" class="Customer"property-ref="address" />
</class>
name

前面已经经过

class

前面已经经过

property-ref

值为“address”,表明建立address到customer的关联。

9.1.1.2.3保存测试
这个的实体模型中主对象是UserDetail,从对象是User,所以先保存主对象UserDetail。

Customer c = new Customer();

c.setName("蒋阳");

Address a = new Address();

a.setProvice("湖南省");

a.setCity("益阳市");

a.setZipCode(413513);

// 确定双方的关系

c.setAddress(a);

a.setCustomer(c);

CREATE TABLE `t_customer` (

`customer_id` int(11) NOT NULL AUTO_INCREMENT,

`c_name` varchar(40) NOT NULL,

`address_id` int(11) DEFAULT NULL,

PRIMARY KEY (`customer_id`),

UNIQUE KEY `address_id` (`address_id`),

KEY `FKDACDF34952B59B88` (`address_id`),

CONSTRAINT `FKDACDF34952B59B88` FOREIGN KEY (`address_id`) REFERENCES `t_address` (`address_id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

CREATE TABLE `t_address` (

`address_id` int(11) NOT NULL AUTO_INCREMENT,

`provice_name` varchar(30) NOT NULL,

`city_name` varchar(30) NOT NULL,

`zip_code` int(11) NOT NULL,

PRIMARY KEY (`address_id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

9.1.1.2.4级联操作
通过设置cascade=”all”后可以进行级联操作

// 只保存customer,不用保存address也可以

session.save(c);

//session.save(a);

// 删除Customer的时候会自动删除与之关联的Address对象。

session.delete(session.get(Customer.class, 1));

9.1.2一对一主键单向关联(不讲)

一对一的关联可以基于主键关联,但基于主键关联的持久化类不能拥有自己的主键生成策略。它的主键由关联的类来生成。另外,增加one-to-one元素来关联属性,必须为元素增加”constrained=true”属性。



9.1.2.1一对一单向主键关联实例

实例模型是User中有一个UserDetail中的引用,User为主表,UserDetail为从表。但是从下面的数据库模型来看UserDetail为主表,User为从表。也就是说User的主键是依赖于USerDetail的主键。

9.1.2.1.1 实体
User.java User中有UserDetail的引用。
public class User implements Serializable {

private int userId;

private String userName;

private String password;

private UserDetail detail;

}

UserDetail.java
public class UserDetail implements Serializable {

private int userDetailId;

private String nickName;

private String sex;

private String address;

private String email;

private Date birthday;

}

9.1.2.1.2 配置文件
User.hbm.xml

<hibernate-mapping package="com.hb.query.relate.one2one.dan.pk">

<class name="User" table="t_user">

<id name="userId" column="user_id">

<generator class="foreign">

<param name="property">detail</param>

</generator>

</id>

<property name="userName" column="user_name" />

<property name="password" column="password" />

<one-to-one name="detail" constrained="true" />

</class>

</hibernate-mapping>

9.1.2.1.4 分析
T_USER_DETAIL表结构



T_USER_DETAIL表主外键情况



T_USER表结构



T_USER表主外键情况



从表结构看出T_USER使用T_USER_DETAIL表的主键做外键。

9.1.2.1.5 查询
查询User的时候的也可以查询到他的昵称

s = HibernateUtil.currentSession();
User user = (User) s.get(User.class, id);
System.out.println("name=" + user.getUserName() + ", detail=" + user.getDetail().getNickName());
Hibernate:
select
user0_.user_id as user1_0_0_,
user0_.user_name as user2_0_0_,
user0_.password as password0_0_
from
t_user user0_
where
user0_.user_id=?
Hibernate:
select
userdetail0_.user_detail_id as user1_1_0_,
userdetail0_.nick_name as nick2_1_0_,
userdetail0_.sex as sex1_0_,
userdetail0_.address as address1_0_,
userdetail0_.email as email1_0_,
userdetail0_.birthday as birthday1_0_
from
t_user_detail userdetail0_
where
userdetail0_.user_detail_id=?

9.1.2.1.5.1 join查询
查询的时候发现有两个SQL语句来完成查询,这样的话,效率是有问题的,可以通过配置合并成一个查询,通过使用fetch="join"来配置,这个不配置的时候是使用fetch="select",这个也类似于懒加载,在不知道你要使用从对象的时候默认是不查询的,如果使用了”join”就会不管3721的通过关联查询。

<one-to-one name="detail" constrained="true" fetch="join" />

9.1.3一对一连接表单向关联(不讲)

这种也比较少见。

9.1.3.1一对一单向连接表关联实例

一个人(Person)对应一个地址(Address),在Person里面增加一个Address的引用。

9.1.3.1.1实体
Person11Tbl.java

public class Person11Tbl implements Serializable {

private int personId;

private String name;

private int age;

private Address11Tbl address;

}

Address11Tbl.java

public class Address11Tbl implements Serializable {

private int addressId;

private String addressDetail;

}

9.1.3.1.2配置文件
Person11Tbl.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.unidirectional.one2one.tbl">

<class name="Person11Tbl" table="t_person_11_tbl">

<id name="personId" column="person_id">

<generator class="native" />

</id>

<property name="name" column="name" type="string" length="20"

not-null="true" />

<property name="age" column="age" type="int" length="3"

not-null="true" />

<!-- 使用join元素显示的确定连接表 -->

<join table="t_join_un_11_tbl">

<key column="person_id" />

<!-- 映射1-1关联属性,其中unique="true"属性确定为"1-1" -->

<many-to-one name="address" unique="true">

<column name="address_id" />

</many-to-one>

</join>

</class>

</hibernate-mapping>

Address11Tbl.hbm.xml配置文件完全跟以前一样,省略。

9.1.3.1.4 分析
T_PERSON_11_TBL表与T_ADDRESS_11_TBL表结构也以前一样这里不在做过多的说明。说一下关联表"t_join_un_11_tbl"



"t_join_un_11_tbl"的“主外键”情况



Hibernate产生的sql语句(先插入address,然后插入person)

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: insert into t_address_11_tbl (address_detail, address_id) values (?, ?)

Hibernate: insert into t_person_11_tbl (name, age, person_id) values (?, ?, ?)

Hibernate: insert into t_join_un_11_tbl (address_id, person_id) values (?, ?)

先插入person,后插入address,然后更新person,因为先插入person的时候还没有address_id,所以会有两条插入语句和一条更新语句。

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: insert into t_person_11_tbl (name, age, person_id) values (?, ?, ?)

Hibernate: insert into t_join_un_11_tbl (address_id, person_id) values (?, ?)

Hibernate: insert into t_address_11_tbl (address_detail, address_id) values (?, ?)

Hibernate: update t_join_un_11_tbl set address_id=? where person_id=?

9.1.4 一对多外键单向关联

这个用的也用的比较多。

9.1.4.1一对多外键单向关联实例(二)

一个customer对应多个订单。

9.1.4.1.1 实体
Customer.java

public class Customer implements Serializable {

private int id;

private String name;

private Set<Order> orders;

}

Order.java

public class Order implements Serializable {

private int id;

private Date orderDate;

}

9.1.4.1.2配置文件
Customer.hbm.xml

<hibernate-mapping package="com.csuinfosoft.oneton">

<class name="Customer" table="t_customer">

<id name="id" column="customer_id" length="5">

<generator class="native" />

</id>

<property name="name" column="c_name" type="string" length="20"

not-null="true" update="true" />

<set name="orders" cascade="save-update">

<key column="customer_id" />

<one-to-many class="Order" />

</set>

</class>

</hibernate-mapping>

name

设定带映射的持久化类的属性名,这里为Customer里面的orders属性。

cascade

表示级联操作,这里的值是save-update,表示级联保存与更新。

set

表示Customer类的orders属性是java.util.Set类型。

key

表示orders表通过外键customer_id参考Customer表。

Order.hbm.xml

<hibernate-mapping package="com.csuinfosoft.oneton">

<class name="Order" table="t_orders">

<id name="id" column="order_id" length="5">

<generator class="native" />

</id>

<property name="orderDate" column="order_date" type="date" />

</class>

</hibernate-mapping>

9.1.4.1.4分析
Hibernate产生的sql语句如下:先有3条查询序列的select语句,因为又会3条insert语句。先插入两条Order,此时的Customer还没有插入所以customer_id为空,这也就是在插入Customer后产生两条更新Customer语句。

Hibernate: insert into t_customer (c_name) values (?)

Hibernate: insert into t_orders (order_date) values (?)

Hibernate: insert into t_orders (order_date) values (?)

Hibernate: update t_orders set customer_id=? where order_id=?

Hibernate: update t_orders set customer_id=? where order_id=?

这样插入的话,也会产生两条update语句。这是为什么呢???

由于这只设置了客户与订单的关联,没有设置订单与客户的关联,当插入的时候无法确定该订单属于哪个用户,所以在后面,才更新订单的外键,这个会在一对多双向时候讲解,怎么避免这种问题。

session.save(c);

session.save(o1);

session.save(o2);

9.1.4.1.5 级联保存与更新
如果不设置 cascade="save-update"的话,下面的操作,值保存Customer,不保存Order的时候会报错。这点在9.1.6.1.6中讲过。

Customer c = new Customer();

c.setName("sinoyang");

Order o1 = new Order();

o1.setOrderDate(new Date());

Order o2 = new Order();

o2.setOrderDate(new Date());

Set<Order> orders = new HashSet<Order>();

orders.add(o1);

orders.add(o2);

c.setOrders(orders);

session.save(c);

// session.save(o1);

// session.save(o2);

9.1.4.1.6 级联删除(这里可以不讲,双向再讲)
我们删除客户,不删除订单,我们来看下执行的效果。

session.delete(session.get(Customer.class, 1));

从SQL语句上来看,它把外键更新成空,然后在删除客户。从表中的数据来看也是的。

Hibernate:

update

t_order

set

c_id=null

where

c_id=?

Hibernate:

delete

from

t_customer

where

c_id=?

上面的删除可是可以,但是如果想删除客户的同时也把关联的订单删除,就需要级联删除了。Cascade的属性配置成delete-orphan,delete都可以删除。

<set name="orders" cascade="delete">

从SQL语句来看,这里首先把order的外键更新从空,然后按ID去删除订单,然后在删除客户。这里一对多单向关联有很多地方比较的难以理解,所以不推荐单独使用,最好配置成双向。

Hibernate:

update

t_order

set

c_id=null

where

c_id=?

Hibernate:

delete

from

t_order

where

o_id=?

Hibernate:

delete

from

t_order

where

o_id=?

Hibernate:

delete

from

t_customer

where

c_id=?

如果既要“级联保存,更新”,又要“级联删除”,可以使用下面的配置,或者 使用all。

<set name="orders" cascade="save-update, delete">

9.1.4.1.7 查询分析
这里有一个问题就是,查询customer的时候会不会查询与之关联的Order。

Customer c = (Customer) session.get(Customer.class, 2);

System.out.println(c.getName());

以上面的代码来运行,我们分析后台打印的sql语句如下:从打印的sql语句可以看出啊,只查了customer,没有查与之关联的Order。

select

customer0_.customer_id as customer1_1_0_,

customer0_.c_name as c2_1_0_

from

t_customer customer0_

where

customer0_.customer_id=?

如果查询customer的时候,顺便查询与之关联的order的时候可以这么配置

<set name="orders" cascade="save-update, delete-orphan"lazy="false">

下面的sql语句可以看出来, 先查询t_customer,然后再查询t_orders

select

customer0_.customer_id as customer1_1_0_,

customer0_.c_name as c2_1_0_

from

t_customer customer0_

where

customer0_.customer_id=?

select

orders0_.customer_id as customer3_1_1_,

orders0_.order_id as order1_1_,

orders0_.order_id as order1_0_0_,

orders0_.order_date as order2_0_0_

from

t_orders orders0_

where

orders0_.customer_id=?

这里有没有发现一个问题查询客户,然后查询客户与之关联的订单的时候用了两个SQL语句,这与我们一般开发是不同的,我们一般会使用一个关联查询就可以做到,这里我们可以做如下配置达到这目的

<set name="orders" table="t_order" cascade="save-update" lazy="false"

fetch="join">

从后台代码SQL语句来看,它使用一个左外连接达到这个目的,理论上讲一个SQL肯定比两个快。

select

customer0_.c_id as c1_5_1_,

customer0_.c_name as c2_5_1_,

orders1_.c_id as c3_5_3_,

orders1_.o_id as o1_3_,

orders1_.o_id as o1_4_0_,

orders1_.order_date as order2_4_0_

from

t_customer customer0_

left outer join

t_order orders1_

on customer0_.c_id=orders1_.c_id

where

customer0_.c_id=?

9.1.4.2一对多外键单向关联实例(订单与订单项)

在现实中一个订单中可能有多个订单项,这是典型的一对多。

9.1.4.2.1 实体
Order.java

/**

* 订单,一个订单可能有多个订单项

*/

public class Order implements Serializable {

private int orderId;

private int userId;

private Date createDate;

private Set<OrderItem> orderItems = new HashSet<OrderItem>();

}

OrderItem.java

/**

* 订单项

*/

public class OrderItem implements Serializable {

private int orderItemId;

private int bookId;

private float price;

private Date createDate;

}

9.1.4.2.2配置文件
Order.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.unidirectional.one2many.order"

auto-import="false">

<class name="Order" table="t_order">

<id name="orderId" column="order_id">

<generator class="native" />

</id>

<property name="userId" type="int" />

<set name="orderItems" cascade="all">

<key column="order_id" />

<one-to-many class="OrderItem" />

</set>

</class>

</hibernate-mapping>

OrderItem.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.unidirectional.one2many.order"

auto-import="false">

<class name="OrderItem" table="t_order_item">

<id name="orderItemId" column="orderitem_id">

<generator class="native" />

</id>

<property name="bookId" column="book_id" type="int" />

<property name="price" column="price" type="float" />

<property name="createDate" column="create_date" type="date" />

</class>

</hibernate-mapping>

9.1.4.2.4分析
Order.java映射后的T_ORDER表



T_ORDER表的数据



OrderItem.java映射后的T_ORDER_ITEM表



T_ORDER_ITEM表的主外键



T_ORDER_ITEM表的数据,从这个表中的数据可以看出,T_ORDER_ITEM中有一个ORDER_ID它是T_ORDER表的主键。



Hibernate产生的数据如下:首先是11条序列查询语句,然后是插入一条订单数据,然后插入10条订单项。

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: insert into t_order (userId, order_id) values (?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: insert into t_order_item (book_id, price, create_date, orderitem_id) values (?, ?, ?, ?)

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

Hibernate: update t_order_item set order_id=? where orderitem_id=?

9.1.5一对多连接表单向关联(不讲)

这种一对多使用的不是很多,使用连接表来实现这种关联,一对一的时候也提到过这种情况,通过产生一个新表来设置两个表的关联关系。

9.1.5.1一对多连接表单向关联实例

一个人(Person)可以对应多个地址(Address),比如:家庭地址,工作地址。

9.1.5.1.1实体
Person1nTbl.java

public class Person1nTbl implements Serializable {

private int personId;

private String name;

private int age;

private Set<Address1nTbl> addresses = new HashSet<Address1nTbl>();

}

Address1nTbl.java

public class Address1nTbl implements Serializable {

private int addressId;

private String addressDetail;

}

9.1.5.1.2配置文件
Person1nTbl.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.unidirectional.one2many.tbl">

<class name="Person1nTbl" table="t_person_1n_tbl">

<id name="personId" column="person_id">

<generator class="native" />

</id>

<property name="name" column="name" type="string" length="20"

not-null="true" />

<property name="age" column="age" type="int" length="3"

not-null="true" />

<set table="t_join_un_1n_tbl" name="addresses">

<key column="person_id" />

<many-to-many unique="true" class="Address1nTbl">

<column name="address_id" />

</many-to-many>

</set>

</class>

</hibernate-mapping>

9.1.5.1.4分析
Person1nTbl, Address1nTbl映射成的表T_PERSON_1N_TBL , T_ADDRESS_1N_TBL不在介绍,和以前一样。

T_JOIN_UN_1N_TBL的表结构如下:

T_JOIN_UN_1N_TBL的表的主外键如下:PERSON_ID,ADDRESS_ID是联合主键;PERSON_ID, ADDRESS_ID又分别是外键。

Hibernate产生的sql语句如下:

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: insert into t_person_1n_tbl (name, age, person_id) values (?, ?, ?)

Hibernate: insert into t_address_1n_tbl (address_detail, address_id) values (?, ?)

Hibernate: insert into t_address_1n_tbl (address_detail, address_id) values (?, ?)

Hibernate: insert into t_join_un_1n_tbl (person_id, address_id) values (?, ?)

Hibernate: insert into t_join_un_1n_tbl (person_id, address_id) values (?, ?)

9.1.6多对一外键单向关联(一)

多对一,多个订单对应于一个客户。

9.1.6.1多对一外键单向关联实例

Order与Customer来说就是典型的多对一关联,也就是说多个订单对应一个客户,而一个订单不能属于多个用户。

9.1.6.1.1实体
Customer.java

public class Customer implements Serializable {

private int id;

private String name;

getter(),setter()…………

}

Order.java

public class Order implements Serializable {

private int id;

private Date orderDate;

private Customer customer;

getter(),setter()…………

}

9.1.6.1.2配置文件
Customer.hbm.xml

<class name="Customer" table="t_customer">

<id name="id" column="customer_id" length="5">

<generator class="native" />

</id>

<property name="name" column="c_name" type="string" length="20"not-null="true" update="true" />

</class>

Order.hbm.xml

<class name="Order" table="t_orders">

<id name="id" column="order_id" length="5">

<generator class="native" />

</id>

<property name="orderDate" column="order_date" type="date" />

<!-- order 到 customer 多对一单向关联 -->

<!—解释,参考下面 -->

<many-to-one name="customer" column="customer_id"

class="com.csuinfosoft.n1.Customer" not-null="false" lazy="false" />

</class>

name

设定待映射的持久化类的属性名,这里为Order中的Customer属性customer。

column

设定和持久化类的属性对应的表的外键,此处为Oder表用的外键customer_id。

class

设定映射属性的持久化类的属性的类型,此处设定customer属性的类型Customer。

not-null

如果为true,表示customer属性不可以为空,该属性默认为false。

lazy

默认值是:proxy,表示关联的customer对象使用延迟加载的策略。 如果值为false,表示hibernate从数据库中加载Order的时候,时候立即自动的加载与之关联的Customer对象。

9.1.6.1.3 测试代码
先保存客户,在保存订单。

// //////////////////////////

Customer c = new Customer();

c.setName("sinoyang");

Order o1 = new Order();

o1.setOrderDate(new Date());

Order o2 = new Order();

o2.setOrderDate(new Date());

o1.setCustomer(c);

o2.setCustomer(c);

// //////////////////////////

s.save(c);

s.save(o1);

s.save(o2);

9.1.6.1.4保存分析
Customer.java映射后的表t_customer表

CREATE TABLE `t_customer` (

`customer_id` int(11) NOT NULL AUTO_INCREMENT,

`c_name` varchar(20) NOT NULL,

PRIMARY KEY (`customer_id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

Order映射后的表t_orders的结构:

CREATE TABLE `t_orders` (

`order_id` int(11) NOT NULL AUTO_INCREMENT,

`order_date` date DEFAULT NULL,

`customer_id` int(11) DEFAULT NULL,

PRIMARY KEY (`order_id`),

KEY `FK7757B510F3431627` (`customer_id`),

CONSTRAINT `FK7757B510F3431627` FOREIGN KEY (`customer_id`) REFERENCES `t_customer` (`customer_id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

Hibernate产生的sql语句如下:

Hibernate: insert into t_customer (c_name) values (?)

Hibernate: insert into t_orders (order_date, customer_id) values (?, ?)

Hibernate: insert into t_orders (order_date, customer_id) values (?, ?)

这里的顺序是先插入customer,然后在插入order,也就是“先插主表,再插从表”。

如果我们设置的not-null="true"且是先插订单,在插入客户的话,如下所示:

session.save(o1);

session.save(o2);

session.save(c);

当运行的时候会出现如下异常:

org.hibernate.PropertyValueException: not-null property references a null or transient value: com.csuinfosoft.hibernate.pojo.Order.customer

这个异常很好理解not-null="true"表示order的属主customer不能为空,而先插入订单,的时候还没有插入customer,所以报错。

做如下的修改:not-null="false"

<many-to-one name="customer" column="customer_id"

class="com.csuinfosoft.n1.Customer" not-null="false" lazy="false" />

先插入Order,再插入Customer。

session.save(o1);

session.save(o2);

session.save(c);

Hibernate产生的sql语句如下:

Hibernate: insert into t_orders (order_date, customer_id) values (?, ?)

Hibernate: insert into t_orders (order_date, customer_id) values (?, ?)

Hibernate: insert into t_customer (c_name) values (?)

Hibernate: update t_orders set order_date=?, customer_id=? where order_id=?

Hibernate: update t_orders set order_date=?, customer_id=? where order_id=?

这里打印的sql语句里面可以看出,先插入t_orders,再插入t_customer,然后最后打印了两条update语句。打印这两条update语句的原因 是插入t_orders的时候,还没有customer_id,所以插入完成后,去更新customer_id。这也是为什么配置not-null="false"的原因,如果是true的话,会报错,因为不允许customer_id为空。

至于not-null的值到底是true,还是false的话,得由实际业务来决定,在这个例子中,订单是由每个用户发起的,所以这里应该把not-null设置为true。

9.1.6.1.5查询分析
这里讨论这种情况,查询order的时候,是否同时可以查询到与之关联的customer,这由lazy=""属性值确定,它有”false”,”proxy”两个值。

<many-to-one lazy="proxy" />

通过ID查询Order

Order o = (Order) session.get(Order.class, 1);

System.out.println(o.getOrderDate());

Sql语句如下:

select

order0_.order_id as order1_1_0_,

order0_.order_date as order2_1_0_,

order0_.customer_id as customer3_1_0_

from

t_orders order0_

where

order0_.order_id=?

表示使用了延迟加载。

如果改成:

<many-to-one lazy="false" />

打印的sql语句如下:

select

order0_.order_id as order1_1_0_,

order0_.order_date as order2_1_0_,

order0_.customer_id as customer3_1_0_

from

t_orders order0_

where

order0_.order_id=?

select

customer0_.customer_id as customer1_0_0_,

customer0_.c_name as c2_0_0_

from

t_customer customer0_

where

customer0_.customer_id=?

这表明这个时候,没使用延迟加载,查询Order的时候会自动的查询与之关联的Customer。

9.1.6.1.6 级联保存与更新
当hibernate持久化一个临时对象的时候,默认情况下,它不会自动持久化所关联的其它的零时对象。如果希望hibernate持久化Order的时候,同时持久化与之关联的Customer的时候在<many-to-one>的cascade属性设置成”save-update”,cascade的默认值是”none”。

这里只保存订单,不保存与之关联的customer

Customer c = new Customer();

c.setName("sinoyang");

Order o1 = new Order();

o1.setOrderDate(new Date());

Order o2 = new Order();

o2.setOrderDate(new Date());

o1.setCustomer(c);

o2.setCustomer(c);

//session.save(c);

session.save(o1);

session.save(o2);

运行的话会包下面的错。

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.csuinfosoft.n1.Customer

设置级联操作

<many-to-one name="customer" column="customer_id"class="Customer" not-null="false" lazy="false"cascade="save-update" />

此时在运行的话,就不会报错。

cascade的属性,除了可以是delete-orphan,还可以是create、update、delete、all等等。all代表除delete-orphan以外的所有属性值,当设置cascade为all以后,对父表记录的增加、修改操作,会影响到“子表”的相关记录。如果是多个的话可以用逗号分开cascade="save-update,delete"

9.1.6.1.7 级联删除
如果目前只是多对一单向映射,所以当删除一端的时候,是不行的,会报错,我们会在一对多的时候讲怎么删除。

9.1.6.2多对一外键单向关联实例(注解实现)

DeptVo.java

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

@Entity(name = "t_dept_annotation")

public class DeptVo implements Serializable {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

@Column(name = "dept_id")

private int id;

@Column(name = "dept_name", length = 10, nullable = false, unique = true)

private String deptName;

}

EmpVo.java

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.JoinColumn;

import javax.persistence.ManyToOne;

@Entity(name = "t_user_annotation")

public class EmpVo implements Serializable {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

@Column(name = "emp_id", length = 5)

private int id;

@Column(name = "emp_name", length = 20, nullable = false, unique = true)

private String name;

@Column(name = "emp_sex", length = 3)

private String sex;

@Column(name = "emp_age", length = 3)

private int age;

@Column(name = "emp_birthday")

private Date birthday;

@ManyToOne

@JoinColumn(name = "dept_id")

private DeptVo dept;

}

使用注解的就不需要hbm.xml配置文件,要配置实体类,用下面的方式。

<mapping class="com.csuinfosoft.n1.DeptVo" />

<mapping class="com.csuinfosoft.n1.EmpVo" />

9.1.7多对一连接表单向关联(不讲)

9.1.8多对多单向关联

多对多,一学生可以又多个老师,老师地址可以有多个学生。

9.1.8.1多对多单向关联实例

多对多,一学生可以又多个老师,老师可以有多个学生。

9.1.8.1.1 实体
Teachernn.java

public class Teachernn implements Serializable {
private int teacherId;
private String teacherName;
}
Studentnn.java
public class Studentnn implements Serializable {
private int studentId;
private String name;
private int age;
private Set<Teachernn> teacheres = new HashSet<Teachernn>();
}
9.1.8.1.2 配置文件
Teachernn.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.unidirectional.many2many"
auto-import="false">
<class name="Teachernn" table="t_teacher_nn">
<id name="teacherId" column="teacher_id">
<generator class="native" />
</id>
<property name="teacherName" column="teacher_name"
not-null="true" type="string" />
</class>
</hibernate-mapping>
Studentnn.hbm.xml
<hibernate-mapping package="com.csuinfosoft.hb3.unidirectional.many2many"
auto-import="false">
<class name="Studentnn" table="t_student_nn">
<id name="studentId" column="student_id">
<generator class="native" />
</id>
<property name="name" column="name" type="string" length="20"not-null="true" />
<property name="age" column="age" type="int" length="3"
not-null="true" />
<!-- 映射集合属性 -->
<set name="teacheres" table="t_join_nn">
<!-- column="student_id" 确定表关联到连接表的外键列名 -->
<key column="student_id" />
<!-- column="teacher_id" 确定person表关联到teacher对象的id在连接表的列名中 -->
<many-to-many column="teacher_id" class="Teachernn" />
</set>
</class>
</hibernate-mapping>
9.1.8.1.4 级联操作
不设置级联的是不保存学生或者不保存老师都是不能保存成功的!下面设置了级联保存,可以实现只插入学生,不插入老师,也可以实现插入。

cascade="save-update"
// session.save(t1);
// session.save(t2);

session.save(s1);
session.save(s2);

级联删除,在双向时候考虑,并且不要用。

9.1.8.1.5注意
多对多用的不是很多的,也包括一对多,从模型上来说,是没问题的,但是从真正的应用中会有问题,这种情况主要是指数据量,很大的时候,会关联查询很大的数据,会有很大的性能问题。

9.2双向关联

9.2.1一对一双向外键关联

一对一双向关联,需要在两个实体中添加所要关联的实体的引用。外键可以存放在任何一端,(这点要视具体的业务逻辑)需要在存放外键的一端添加<many-to-one unique=”true” />元素,另一端加<one-to-one>。

9.2.1.1一对一双向关联实例

以上面单向的User与UserDetail为例,实体模型是:User是主对象,UserDetail是从对象,数据库模型是:UserDetail使用User的主键作为外键,使得User是主表,UserDetail是从表。



9.2.1.1.1实体
User.java

public class User implements Serializable {
private int userId;
private String userName;
private String password;
private UserDetail detail;
}
UserDetail.java
public class UserDetail implements Serializable {
private int userDetailId;
private String nickName;
private String sex;
private String address;
private String email;
private Date birthday;
private User user;
}
9.2.1.1.2配置文件
User.hbm.xml

<many-to-one name="userDetail" column="user_detail_id"
unique="true" cascade="save-update,delete" />
unique

unique="true" 表示这个user_detail_id为不可重复,这样就成了1对1,或者说userDetail只能对应一个user,且就是当前的这个user。

UserDetail.hbm.xml

<one-to-one name="user"property-ref="userDetail" />
property-ref

property-ref="user"表示属性引用:当前的User和UserDetail怎么确定关系,也就是說當前的userId要與UserDetail.hbm.xml中<many-to-one unique="true">中的知名的user_id一样。或者说UserDetail中的user与property-ref="user"是同一个就表示UserDetail是这个user的。

注意:property-ref="user"不配置好像也可以,这点不是很好解释。

session.save(user);
session.save(userDetail);
上面先保存user,在保存detail 也是可以的,但是会产生SQL语句。

update
t_user
set
user_name=?,
user_pwd=?,
user_detail_id=?
where
user_id=?
原因在于user引用了detail的主键作为外键,所以插入user的时候还没有外键,当detail插入完后在更新user。

9.2.1.1.4 级联操作
9.2.1.1.4.1 级联保存
如果只保存user,或者只保存detail的话可不可以呢?这里会导致报错或者只插入一个值,要避免的话可以配置级联。

session.save(user);
session.save(userDetail);

//session.save(user);
session.save(userDetail);
配置保存更新级联

<many-to-one name="userDetail" column="user_detail_id"
unique="true"cascade="save-update " />

<one-to-one name="user" property-ref="userDetail"cascade="save-update" />
9.2.1.1.4.2 级联删除
这里讨论先删除user,或者先删除detail,能否把关联的也删除。

session.delete(session.get(User.class, 1));
先删除User,运行时候会报错。

session.delete(session.get(UserDetail.class, 1));
先删除Detail,运行也会报错。

配置删除级联

<many-to-one name="userDetail" column="user_detail_id"
unique="true"cascade="save-update,delete" />

<one-to-one name="user" property-ref="userDetail"cascade="save-update,delete" />
配置后能正常删除。

9.2.1.1.5查询???
这里查询User是否同时查询Detail,或者查询Detail是否查询User,这里不讨论,这里讨论关联查询时候的问题。

查询User,然后通过User获取UserDetail对象。

User u = (User) session.get(User.class, 6);
System.out.println(u.getUserDetail().getAge());
会产生3个SQL语句, 不好解释。即使两端都配置了join=”fetch”产生SQL都无法解释。

这里查询Detail同时获取关联的User,即使不配做join=”fetch”也是通过左外连接查询。
UserDetail ud = (UserDetail) session.get(UserDetail.class, 5);
System.out.println(ud.getUser().getName());

9.2.2一对多外键双向关联(三)

one-to-many外键双向关联总结: 需在one的一方设置set集合元素,在many多的一方设置many-to-one元素,在一对多的关联中推荐用这种双向的关联,而不是单向的关联。

其实一对多双向关联也叫多对一双向关联,只是习惯叫一对多双向关联。

9.2.2.1一对多外键双向关联实例

还是以用户与订单为实例,一个人可以有多个订单,而一个订单只属于一个人。这个实例其实是 9.1.6与9.1.4的结合。

9.2.2.1.1实体
Customer.java

public class Customer implements Serializable {

private int id;

private String name;

private Set<Order> orders;

}

Order.java

public class Order implements Serializable {

private int id;

private Date orderDate;

private Customer customer;

}

9.2.2.1.2配置文件
Customer.hbm.xml
<class name="Customer" table="t_customer">

<id name="id" column="customer_id" length="5">

<generator class="native" />

</id>

<property name="name" column="c_name" type="string" length="20"

not-null="true" update="true" />

<set name="orders" cascade="save-update, delete-orphan" lazy="true"

inverse="true">

<key column="customer_id" />

<one-to-many class="Order" />

</set>

</class>

Order.hbm.xml

<class name="Order" table="t_orders">

<id name="id" column="order_id" length="5">

<generator class="native" />

</id>

<property name="orderDate" column="order_date" type="date" />

<many-to-one name="customer" column="customer_id"

class="com.csuinfosoft.oneton.Customer" not-null="false" lazy="false"cascade="save-update" />

</class>

9.2.2.1.3 保存测试
先保存订单,再保存客户。

Customer c = new Customer();

c.setName("sinoyang");

Order o1 = new Order();

o1.setOrderDate(new Date());

Order o2 = new Order();

o2.setOrderDate(new Date());

o1.setCustomer(c);

o2.setCustomer(c);

Set<Order> orders = new HashSet<Order>();

orders.add(o1);

orders.add(o2);

c.setOrders(orders);

session.save(o1);

session.save(o2);

session.save(c);

先保存客户,再保存订单。

session.save(c);

session.save(o1);

session.save(o2);

如果不设置级联操作的话,下面呃操作会报错。

session.save(c);

//session.save(o1);

//session.save(o2);

//session.save(c);

session.save(o1);

session.save(o2);

设置级联保存。

cascade="save-update"

上面的测试中都产生了update语句,这里要怎么避免呢?可以使用下面的方式。

在“一”的一端加上这个属性,表示这段放弃关系的维护,从而减少update语句,9.3.1中详细介绍。

inverse="true"

9.2.2.1.4 级联保存更新
只保存customer对象,而不保存Order这时候也可以进行保存,通过配置级联可以到的。

<set name="orders"cascade="save-update, delete-orphan" lazy="true"inverse="true">

session.save(c);

// session.save(o1);

// session.save(o2);

9.2.2.1.5 查询分析
这里要讨论的是查询Customer的时候,能不能查询到与之关联的Order,或者查询Order的时候查询与之关联的Customer。这个已经在9.1.6,9.1.4中讨论过。

9.2.2.1.6注解实现
这里以部门与雇员的一对多双向关联,使用注解来实现。

Emp.java

@Entity(name = "t_emp_annotation")

public class Emp implements Serializable {

@Id

@Column(name = "emp_id")

@GeneratedValue(strategy = GenerationType.AUTO)

private int id;

@Column(name = "e_name", length = 20, nullable = false, unique = true)

private String name;

@Column(name = "e_salary")

private float salary;

@ManyToOne(targetEntity = Dept.class, cascade = CascadeType.ALL)

@JoinColumn(name = "dept_id")

private Dept dept;

}

Dept.java

@Entity(name = "t_dept_annotation")

public class Dept implements Serializable {

@Id

@Column(name = "dept_id")

@GeneratedValue(strategy = GenerationType.AUTO)

private int id;

@Column(name = "d_name", length = 20, nullable = false, unique = true)

private String name;

@OneToMany(targetEntity = Emp.class, cascade = CascadeType.ALL)

@JoinColumn(name = "dept_id")

private Set<Emp> emps;

}

<mapping class="com.csuinfosoft.homework.annotation.Dept" />

<mapping class="com.csuinfosoft.homework.annotation.Emp" />

保存测试

Emp e1 = new Emp();

e1.setName("蒋阳");

e1.setSalary(800.0f);

Emp e2 = new Emp();

e2.setName("肖高翔");

e2.setSalary(2800.0f);

Dept d1 = new Dept();

d1.setName("研发部门");

Set<Emp> emps = new HashSet<Emp>();

emps.add(e1);

emps.add(e2);

e1.setDept(d1);

e2.setDept(d1);

d1.setEmps(emps);

session.save(d1);

9.2.3多对多双向连接表关联(一)

多对多双向连接表关联需要在两端都使用Set集合属性,两端都增加对集合属性的访问。这种关联关系只能使用连接表的形式来实现。

9.2.3.1多对多双向连接表关联实例

多对多,一学生可以又多个老师,老师可以有多个学生。



9.2.3.1.1实体
Student.java

public class Student implements Serializable {

private int id;

private String name;

private String sex;

private Set<Teacher> teachers;

}

Teacher.java

public class Teacher implements Serializable {

private int id;

private String name;

private String sex;

private Set<Student> students;

}

9.2.3.1.2配置文件
Student.hbm.xml
<class name="Student" table="t_student">

<id name="id" column="s_id">

<generator class="native" />

</id>

<property name="name" column="s_name" type="string" length="30"

not-null="true" />

<property name="sex" column="s_sex" />

<set name="teachers" cascade="save-update" table="t_teacher_student">

<key column="s_id" />

<many-to-many column="t_id" class="Teacher" />

</set>

</class>

cascade
设置为:save-update,表示保存或者更新Student的时候会级联保存更新与之关联的Teacher对象。
注意:这里应该设置成save-update,不允许设置成all与delete,因为:假如删除一个Student对象时候会删除关联的Teacher对象,这时候这个老师可能与其他Student关联,所以这种删除是有问题的,违背数据库参照完整性。
table
表示的是Teacher与Student的关联表。
key
key的column_id指定t_teacher_student表中,参照的是t_student的主键,这里是s_id。
many-to-many
class属性指定的是teachers属性的类型这里是Teacher,column_id指定关联表t_teacher_student表中,参照的是t_teacher表中的主键t_id。
Teacher.hbm.xml
<class name="Teacher" table="t_teacher">

<id name="id" column="t_id">

<generator class="native" />

</id>

<property name="name" column="t_name" type="string" length="30"

not-null="true" />

<property name="sex" column="t_sex" />

<set name="students" cascade="save-update" inverse="true" table="t_teacher_student">

<key column="t_id" />

<many-to-many column="s_id" class="Student" />

</set>

</class>

这里的配置跟Student中其实是差不多的只强调的是inverse="true",这个一定要配置,也就是说多对多双向的时候一定要在一端设置它,不然会报错,原因在于两边都在维护关系,也就是在关联表中插入数据是一样的,就会导致报错。

9.2.3.1.4 级联操作
这里配置好 级联保存与级联更新,这里要不要配置级联删除,上面已经解释了。

9.2.3.1.5 查询
关于是否是延迟加载,这里不在讨论。

9.2.3.1.6 问题
给ID等于2的人添加一个学生

Student s1 = new Student();

s1.setName("冯建霞");

s1.setSex("女");

s1.setAge(23);

s1.setQq("123456789");

s1.setBirthday(new Date());

Teacher teacher = (Teacher) session.get(Teacher.class, 2);

Set<Teacher> teachers = new HashSet<Teacher>();

teachers.add(teacher);

s1.setTeachers(teachers);

session.save(s1);

9.2.4一对一双向主键关联(三)

考虑User与UserDetail是一对一关联关系,这时候可以考虑主键映射方式,也就是user_detail_id既是“主键”又是参考User 的“外键”,也就是说UserDetail与User共享主键。



9.2.4.1 实体

User.java

public class User implements Serializable {
private int id;
private String name;
private String password;
private UserDetail userDetail;
}
UserDetail.java

public class UserDetail implements Serializable {
private int id;
private int age;
private String sex;
private String address;
private User user;
}

9.2.4.2 配置文件

User.bhm.xml
<class name="User" table="t_user">
<id name="id" column="user_id">
<generator class="native" />
</id>
<property name="name" column="user_name" type="string"
length="30" not-null="true" />
<property name="password" column="user_pwd" type="string"
length="30" not-null="true" />
<one-to-one name="userDetail" class="UserDetail" cascade="save-update,delete" />
</class>
UserDetail.hbm.xml
<class name="UserDetail" table="t_user_detail">
<id name="id" column="user_detail_id">
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<property name="age" column="user_age" type="int" />
<property name="sex" column="user_sex" type="string" length="4" />
<property name="address" column="user_address" type="string"
length="40" />
<one-to-one name="user" class="User" constrained="true" cascade=" save-update,delete"/>
</class>
<generator class="foreign">

表示使用外键作为该表的主键,也就是这个表没有自己的组件生产方式。

<param name="property">user</param>

引用那个外键作为左键呢?使用user。

constrained="true"

用它来建立外键约束。

如果不设置,constrained="true"运行不会报错,但是外键没有建立,这里要注意。

9.2.4.3 分析

CREATE TABLE `t_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(30) NOT NULL,
`user_pwd` varchar(30) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
CREATE TABLE `t_user_detail` (
`user_detail_id` int(11) NOT NULL,
`user_age` int(11) DEFAULT NULL,
`user_sex` varchar(4) DEFAULT NULL,
`user_address` varchar(40) DEFAULT NULL,
PRIMARY KEY (`user_detail_id`),
KEY `FKCAEEAFFAFCB7DB6C` (`user_detail_id`),
CONSTRAINT `FKCAEEAFFAFCB7DB6C` FOREIGN KEY (`user_detail_id`) REFERENCES `t_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
从生成的表结构可以看出,t_user_detail中的user_detail_id既是主键,又是参考了t_user表中的user_id的外键,也就是说t_user_detail和t_user共享主键。

9.2.4.4 测试保存

User user = new User();
user.setName("test");
user.setPassword("123445");

UserDetail userDetail = new UserDetail();
userDetail.setSex("male");
userDetail.setAge(20);
userDetail.setAddress("hunan changsha");

user.setUserDetail(userDetail);
userDetail.setUser(user);

session.save(user);
session.save(userDetail);
产生SQL语句如下:

insert intot_user(user_name, user_pwd) values (?, ?)
insert into t_user_detail (user_age, user_sex, user_address, user_detail_id) values (?, ?, ?, ?)
这里要注意的是,无论是先保存User,还是先保存Userdetail,都可以正常的保存,并且,它的顺序都是先保存User,然后Userdetail,不用设置级联保存也可以。

9.2.4.5级联操作

不设置级联的时候,下面的第一个值保存User,第二个可以实现保存。

session.save(user);
//session.save(userDetail);

//session.save(user);
session.save(userDetail);
由于两边都设置了cascade="save-update",所以下面的保存都可以。

session.save(user);
//session.save(userDetail);

//session.save(user);
session.save(userDetail);
在不配置级联删除的时候,删除User不会删除关联的UserDetail,删除UserDetail不会删除关联的User

设置下面级联删除cascade="save-update,delete",下面的两种删除都可以实现删除与之关联的。

session.delete(session.get(User.class, 1));
session.delete(session.get(UserDetail.class, 2));

9.3关联映射其它

9.3.1 inverse

Inverse表示“是否放弃维护关联关系”(在Java里两个对象产生关联时,对数据库表的影响),在one-to-many和many-to-many的集合定义中使用,inverse=”true”表示该对象不维护关联关系;该属性的值一般在使用有序集合时设置成false(注意hibernate的缺省值是false,一般放在一的这段,而不是多的那端,这就好比让老师不去记住那些学生是他教的,但是学生却知道自己的老师)。

one-to-many维护关联关系就是更新外键。many-to-many维护关联关系就是在中间表增减记录。

9.3.1.1一对多双向实例

以一对多双向关联来作为实例:一个部门有多个雇员,一个雇员只能属于一个部门。

9.3.1.1.1实体
Department.java,一个部门有多个雇员。

public class Department implements Serializable {
private int deptId;
private String deptName;
private Set<Employee> emps = new HashSet<Employee>();
}
Employee.java一个雇员只能属于一个部门。

public class Employee implements Serializable {
private int empId;
private String empName;
private String sex;
private int empAge;
private float salary;
private Department dept;
}
9.3.1.1.2配置文件
Department.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.inverse"
auto-import="false">
<class name="Department" table="t_dept_inverse">
<id name="deptId" column="dept_id">
<generator class="native" />
</id>
<property name="deptName" column="dept_name" />
<set name="emps">
<key column="dept_id" />
<one-to-many class="Employee" />
</set>
</class>
</hibernate-mapping>
Employee.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.inverse"
auto-import="false">
<class name="Employee" table="t_emp_inverse">
<id name="empId" column="emp_id">
<generator class="native" />
</id>
<property name="empName" column="emp_name" />
<property name="sex" column="sex" />
<property name="empAge" column="emp_age" />
<property name="salary" column="salary" />
<many-to-one name="dept" class="Department">
<column name="dept_id" />
</many-to-one>
</class>
</hibernate-mapping>
9.3.1.1.3代码测试
分别测试保存,与查询。

9.3.1.1.3.1保存测试
// ///////////////////////////////////////////
Department d = new Department();
d.setDeptName("研发部");
// ///////////////////////////////////////////
Employee e1 = new Employee();
e1.setEmpName("李强");
e1.setSex("男");
e1.setEmpAge(22);
e1.setSalary(3500.00f);
e1.setDept(d);
// ///////////////////////////////////////////
Employee e2 = new Employee();
e2.setEmpName("姚淑漫");
e2.setSex("女");
e2.setEmpAge(22);
e2.setSalary(3500.00f);
e2.setDept(d);
// ///////////////////////////////////////////
Set<Employee> emps = new HashSet<Employee>();
emps.add(e1);
emps.add(e2);
d.setEmps(emps);
// ///////////////////////////////////////////
9.3.1.1.3.2保存分析
实体所产生的数据库表,与这些表的主外键就不分析了,以前都提到过,这里主要分析所打印的sql语句。

首先是3条序列查询语句,然后是插入部门信息,然后是插入雇员信息,然后更新雇员的部门信息。

Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual

Hibernate: insert into t_dept_inverse (dept_name, dept_id) values (?, ?)

Hibernate: insert into t_emp_inverse (emp_name, sex, emp_age, salary, dept_id, emp_id) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into t_emp_inverse (emp_name, sex, emp_age, salary, dept_id, emp_id) values (?, ?, ?, ?, ?, ?)
Hibernate: update t_emp_inverse set dept_id=? where emp_id=?
Hibernate: update t_emp_inverse set dept_id=? where emp_id=?

注意:上面加粗的那两条更新语句是多余的,也是inverse的用处。

如果先插入雇员,在插入部门信息,这样操作不会报错的,但是,打印的sql语句有所不同了,从下面的sql语句可以发现,一共与四条update语句。分析一下这四条update语句:

1. 红色的两条update语句是由于e1.setDept(d);, e2.setDept(d);如果将其注释掉的话,那那条更新雇员信息的红色update语句就没有了。

2. 两条黑色update语句是由于修改雇员的部门ID所产生的,可以这么理解(现招聘雇员,此时雇员的所属部门还没建立,此时的部门ID肯定为空,然后才建立部门,然后再将员工的部门ID修改,所以最下面多了两条update语句。)如果注释掉d.setEmps(emps)这两天sql语句不会有。

s.save(e1);
s.save(e2);
s.save(d);

Hibernate: insert into t_emp_inverse (emp_name, sex, emp_age, salary, dept_id, emp_id) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into t_emp_inverse (emp_name, sex, emp_age, salary, dept_id, emp_id) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into t_dept_inverse (dept_name, dept_id) values (?, ?)
Hibernate: update t_emp_inverse set emp_name=?, sex=?, emp_age=?, salary=?, dept_id=? where emp_id=?
Hibernate: update t_emp_inverse set emp_name=?, sex=?, emp_age=?, salary=?, dept_id=? where emp_id=?

Hibernate: update t_emp_inverse set dept_id=? where emp_id=?
Hibernate: update t_emp_inverse set dept_id=? where emp_id=?
到这里大家可能发现了,一个问题:下面的语句分别是在两端来确定实体之间的关系,它们都会去更新Emp中的外键dept_id,也就是说只要其中的一个运行就已经确定关系了,第二次运行产生的update语句是多余的。解决方案是在部门这段设置一下,不让它来维护与雇员的关系。一般情况下是放在一对多的一这端,这里就是部门这端(这里的理解是可以打个比方,让老师记住学生很费劲,他是一对多,而让学生记住老师是容易的。)这时候就可以使用inverse了,inverse表示它放弃对关系的维护,这样操作的话,效率会高些

e1.setDept(d);

e2.setDept(d);

d.setEmps(emps);

使用inverse达到性能最佳,解决方案如下:在部门一端配置inverse=”true”,先把部门保存,再保存雇员信息,此时就不会产生update语句去更新员工的部门ID,(通知部门它有哪些员工的时候”d.setEmps(emps)”它也不会去管理这种关系)以达到性能最佳。

<set name="emps"inverse="true">

<key column="dept_id" />

<one-to-many class="Employee" />

</set>

s.save(d);

s.save(e1);

s.save(e2);

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: select hibernate_sequence.nextval from dual

Hibernate: insert into t_dept_inverse (dept_name, dept_id) values (?, ?)

Hibernate: insert into t_emp_inverse (emp_name, sex, emp_age, salary, dept_id, emp_id) values (?, ?, ?, ?, ?, ?)

Hibernate: insert into t_emp_inverse (emp_name, sex, emp_age, salary, dept_id, emp_id) values (?, ?, ?, ?, ?, ?)

9.3.1.2 inverse其它

考虑inverse的局限性,考虑下面的配置把Set改成List变成有序的,此时代码运行不会报错,但是”list_order”列为空,原因在于inverse放弃了关系的维护,导致它不会记录顺序,所以顺序为空。所以inverse只能在Set这种无序集合中使用,List,Array等都是有序的,是不能使用的。

<set name="emps" inverse="true">

<key column="dept_id" />

<one-to-many class="Employee" />

</set>

<list name="empsBak" inverse="true">

<key column="dept_id" />

<list-index column="order_col" />

<one-to-many class="Employee" />

</list>



注意:在多的一端是无法放弃对关系的维护的<many-to-one>因为它就根本没有inverse这个属性,因为多的这端维护关系是效率比较高的。

9.3.1.3 多对多双向实例

一学生和老师的这种双向多对多的来做一个实例,如下面代码,学生与老师都维护之间的关系。此时运行会报错(违反主键约束)这个原因在于,它与一对多不同,一对多是用外键来实现关系的维护,而多对多不同,使用链接表来实现,一对多的时候大不了重复更新外键,多对多的会会重复插入到关联表中,这也就是它为什么报错的原因。

解决方案是有两种:
1. 让学生或者老师随便那端放弃关系的维护,下面配置随便配置一个
<set name="teachers" table="t_join_nn_b" cascade="all" inverse="true">

<set name="students" table="t_join_nn_b" cascade="all" inverse="true">

2. 在写代码的时候一端不要维护关系
// 设置学生与老师的关系

s1.setTeachers(teachers);

s2.setTeachers(teachers);

//设置老师与学生的关系

t1.setStudents(students);

t2.setStudents(students);

9.3.2 cascade

Casade用来说明当对主对象进行某种操作时是否对其关联的从对象也作类似的操作,常用的

none
默认:不做任何操作
all
表示所以
save-update
保存或者更新的时候进行级联操作
delete
删除的时候进行级联操作
delete-orphan(one-to-many)
把“一对多”的一的这端的清空,会删除多的一端
如果要多个的话可用逗号隔开,比如:”save-update,delete”。
一般对many-to-one,many-to-many是不设置级联,在<one-to-one>和<one-to-many>中才设置级联。

9.3.2.1cascade保存实例

先看这个场景:还是以部门与雇员的这种双向的关系来看,设置好部门与雇员的信息,和雇员与部门的信息后,只保存部门,不保存雇员,运行的时候会有个问题,部门保存了,但是雇员没有保存。

9.3.2.1.1实体
DepartmentCascade.java

/**

* 部门

*/

public class DepartmentCascade implements Serializable {

private int deptId;

private String deptName;

private Date createDate;

private Set<EmployeeCascade> emps = new HashSet<EmployeeCascade>();

}

EmployeeCascade.java

/**

* 雇员表

*/

public class EmployeeCascade implements Serializable {

private int empId;

private String empName;

private String sex;

private int empAge;

private float salary;

private Date hireDate;

private DepartmentCascade dept;

}

9.3.2.1.2配置文件
这两个配置文件和以前的基本一样,只是分别添加了两个时间字段,这里不做描述。

DepartmentCascade.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.cascade"

auto-import="false">

<class name="DepartmentCascade" table="t_dept_cascade">

<id name="deptId" column="dept_id">

<generator class="native" />

</id>

<property name="deptName" column="dept_name" />

<property name="createDate" column="create_date" />

<set name="emps" inverse="true" cascade="all">

<key column="dept_id" />

<one-to-many class="EmployeeCascade" />

</set>

</class>

</hibernate-mapping>

EmployeeCascade.hbm.xml

<hibernate-mapping package="com.csuinfosoft.hb3.cascade"

auto-import="false">

<class name="EmployeeCascade" table="t_emp_cascade">

<id name="empId" column="emp_id">

<generator class="native" />

</id>

<property name="empName" column="emp_name" />

<property name="sex" column="sex" />

<property name="empAge" column="emp_age" />

<property name="salary" column="salary" />

<property name="hireDate" column="hire_date" />

<many-to-one name="dept" class="DepartmentCascade">

<column name="dept_id" />

</many-to-one>

</class>

</hibernate-mapping>

9.3.2.1.4分析
从上面的测试代码中的加粗的代码可以发现,设置了雇员与部门之间的关系,设置了部门与雇员的关系,但是保存的时候,发现雇员信息没有保存

Hibernate产生的sql语句如下:只插入了部门,没有雇员信息

Hibernate: select hibernate_sequence.nextval from dual
Hibernate: insert into t_dept_cascade (dept_name, create_date, dept_id) values (?, ?, ?)

查看表数据发现确实没有雇员信息



从DepartmentCascade类中发现,同是字段,基本数据类型的字段,ID,NAME,DATE都已经插入,但是集合字段Set<EmployeeCascade>却没有插入,这个的原因是在于,对已集合字段它在默认情况下,级联操作是不做任何操作的。这时候就需要cascade属性来实现这种级联操作。

实现方式:在DepartmentCascade.hbm.xml添加

<set name="emps" inverse="true"cascade="all">
<key column="dept_id" />
<one-to-many class="EmployeeCascade" />
</set>
Hibernate产生的sql语句

Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: insert into t_dept_cascade (dept_name, create_date, dept_id) values (?, ?, ?)
Hibernate: insert into t_emp_cascade (emp_name, sex, emp_age, salary, hire_date, dept_id, emp_id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into t_emp_cascade (emp_name, sex, emp_age, salary, hire_date, dept_id, emp_id) values (?, ?, ?, ?, ?, ?, ?)

数据库数据如下,也正常。



9.3.2.2cascade删除实例

如果要实现删除部门的时候也删除部门下所以的员工,这是典型的级联删除,要怎么实现了?

s = HibernateUtil.currentSession();
t = s.beginTransaction();
DepartmentCascade d = (DepartmentCascade) s.load(DepartmentCascade.class, 4);
s.delete(d);
t.commit();
<set name="emps" inverse="true"cascade="save-update,delete">
<key column="dept_id" />
<one-to-many class="EmployeeCascade" />
</set>
如果这么写的话,级联删除就可以实现,但是要注意的是,级联删除一定要慎重,这个要视你的需求来定,比如把部门删除掉,不一定会把雇员删掉,会把她转到其他部门去,这点一定要慎重。

9.4映射总结

10.继承关系映射

说道OOP就不得不说到继承关系,看下面的模型,雇员有两个子类,一个是Skiller技能型雇员,一个是 Sales销售型雇员。



10.1继承实例一

10.1.1实体

Skiller和Seller都是Employee的子类。Department这个类省略,和一起一样。

/**
* 雇员表
*/
public class Employee implements Serializable {
private int empId;
private String empName;
private String sex;
private int empAge;
private float salary;
private Date hireDate;
private Department dept;
}
/**
* 技术员工
*/
public class Skiller extends Employee {
private String skill;
}
/**
* 销售员工
*/
public class Seller extends Employee {
private String sell;
}

10.1.2配置文件

<hibernate-mapping package="com.csuinfosoft.hb3.inherit"
auto-import="false">
<class name="Employee" table="t_emp_inherit"discriminator-value="0">
<id name="empId" column="emp_id">
<generator class="native" />
</id>
<!-- 标示是哪个子类的字段 -->
<discriminator column="type" type="string" />
<property name="empName" column="emp_name" />
<property name="sex" column="sex" />
<property name="empAge" column="emp_age" />
<property name="salary" column="salary" />
<property name="hireDate" column="hire_date" />
<many-to-one name="dept" class="Department">
<column name="dept_id" />
</many-to-one>
<!-- 子类 -->
<subclass name="Skiller" discriminator-value="1">
<property name="skill" />
</subclass>
<!-- 子类 -->
<subclass name="Seller" discriminator-value="2">
<property name="sell" />
</subclass>
</class>
</hibernate-mapping>



10.1.4分析

查询结果可以看出来,TYPE的值分别是0, 1, 2,TYPE==1的时候SKILL有值,TYPE==2的时候SELL有值。



Hibernate产生的sql语句。

Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: insert into t_dept_inherit (dept_name, create_date, dept_id) values (?, ?, ?)
Hibernate: insert into t_emp_inherit (emp_name, sex, emp_age, salary, hire_date, dept_id, type, emp_id) values (?, ?, ?, ?, ?, ?, '0', ?)
Hibernate: insert into t_emp_inherit (emp_name, sex, emp_age, salary, hire_date, dept_id, skill, type, emp_id) values(?, ?, ?, ?, ?, ?, ?, '1', ?)
Hibernate: insert into t_emp_inherit (emp_name, sex, emp_age, salary, hire_date, dept_id, sell, type, emp_id) values(?, ?, ?, ?, ?, ?, ?, '2', ?)

10.1.5 查询实例

查询的时候通过load(),这里要注意的是Employee.class。

s = HibernateUtil.currentSession();
Employee e = (Employee) s.load(Employee.class, 12);
System.out.println(e.getEmpName());
System.out.println(e.getClass().getName());

Hibernate产生的sql语句:从sql语句可以看出如果是Employee.class的话它只通过雇员ID来查询。

Hibernate: select employee0_.emp_id as emp1_45_0_, employee0_.emp_name as emp3_45_0_, employee0_.sex as sex45_0_, employee0_.emp_age as emp5_45_0_, employee0_.salary as salary45_0_, employee0_.hire_date as hire7_45_0_, employee0_.dept_id as
dept8_45_0_, employee0_.skill as skill45_0_, employee0_.sell as sell45_0_, employee0_.type as type45_0_ from t_emp_inherit employee0_where employee0_.emp_id=?
姚淑漫
com.csuinfosoft.hb3.inherit.Employee_$$_javassist_27

看看下面的写法:

s = HibernateUtil.currentSession();
Skiller e = (Skiller) s.load(Skiller.class, 12);
System.out.println(e.getEmpName());
System.out.println(e.getClass().getName());
Hibernate产生的sql语句可以看出不仅通过ID,还通过TYPE来查询。

Hibernate: select skiller0_.emp_id as emp1_45_0_, skiller0_.emp_name as emp3_45_0_, skiller0_.sex as sex45_0_, skiller0_.emp_age as emp5_45_0_, skiller0_.salary as salary45_0_, skiller0_.hire_date as hire7_45_0_, skiller0_.dept_id as dept8_45_0_,
skiller0_.skill as skill45_0_ from t_emp_inherit skiller0_ where skiller0_.emp_id=? and skiller0_.type='1'
姚淑漫
com.csuinfosoft.hb3.inherit.Skiller_$$_javassist_30

10.1.6 缺点分析

从这个的查询值就可以发现,这种集成映射的缺点:用这种的话会有空值出现,这种跟数据库设计原则相违背。特别是当子类过多的时候,这个问题更加严重,所以说这种方式不是很合理的。



10.2继承实例二

由于上面的方式的弊端很大,我做出修改,把关系模型修改成下面的:它将不同的字段存放在不同的表中,这么做,查询的代价是比较大的,牵涉到多表查询。



10.2.1实体

People.java

public class People implements Serializable {
private int peopleId;
private String name;
}
Chinese.java

public class Chinese extends People implements Serializable {
private String language;
}

American.java

public class American extends People implements Serializable {
private String color;
}

10.2.2配置文件

<hibernate-mapping package="com.csuinfosoft.hb3.inherit.fk"
auto-import="false">
<class name="People" table="t_people_inherit">
<id name="peopleId" column="people_id">
<generator class="native" />
</id>
<property name="name" column="name" />
<joined-subclass name="Chinese" table="t_chinese">
<key column="people_id" />
<property name="language" />
</joined-subclass>
<joined-subclass name="American" table="t_american">
<key column="people_id" />
<property name="color" />
</joined-subclass>
</class>
</hibernate-mapping>

10.2.3

T_AMIRCAN表主外键情况,从这里看出使用PEOPLE作为主键,有作为外键。



T_CHINESE表主外键情况,从这里看出使用PEOPLE作为主键,有作为外键。



下面是T_PEOPLE_INHERIT, T_CHINESE, T_AMERICAN这三个表的数据情况,可以看出T_CHINESE, T_AMERICAN存储的是各个子类的特有属性字段。这种解决方案在由多个字段的时候会出现多个表。



Hibernate产生sql语句如下:

Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: insert into t_people_inherit (name, people_id) values (?, ?)
Hibernate: insert into t_people_inherit (name, people_id) values (?, ?)
Hibernate: insert into t_chinese (language, people_id) values (?, ?)
Hibernate: insert into t_people_inherit (name, people_id) values (?, ?)
Hibernate: insert into t_american (color, people_id) values (?, ?)

10.2.3查询测试代码

public static Object query(Class<?> clazz, int id) {
Session s = null;
Object obj = null;
try {
s = HibernateUtil.currentSession();
obj = s.get(clazz, id);
} catch (HibernateException e) {
throw e;
} finally {
if (s != null && s.isOpen()) {
s.close();
}
}
return obj;
}
首先查询父类,也就是People,这种的查询会很复杂,效率不是很高。

People p = (People) query(People.class, 1);
System.out.println(p.toString());
这个查询语句会很复杂,因为查了父类,所以它会对T_PEOPLE_INHERIT, T_CHINESE, T_AMERICAN这三个表的关联查询,如果子类很多的话,效率会非常的低。因为它不确定是哪种类型的,这个sql语句不必深究。

select
people0_.people_id as people1_47_0_,
people0_.name as name47_0_,
people0_1_.language as language48_0_,
people0_2_.color as color49_0_,
case
when people0_1_.people_id is not null then 1
when people0_2_.people_id is not null then 2
when people0_.people_id is not null then 0
end as clazz_0_
from
t_people_inherit people0_
left outer join
t_chinese people0_1_
on people0_.people_id=people0_1_.people_id
left outer join
t_american people0_2_
on people0_.people_id=people0_2_.people_id
where
people0_.people_id=?
com.csuinfosoft.hb3.inherit.fk.People@103c29b[peopleId=1,name=我是一个人]
查询特定的类型,比如查询Chinese,这种情况下回比较简单,且效率比较高

Chinese c = (Chinese) query(Chinese.class, 2);
System.out.println(c.toString());
Hibernate:
select
chinese0_.people_id as people1_47_0_,
chinese0_1_.name as name47_0_,
chinese0_.language as language48_0_
from
t_chinese chinese0_
inner join
t_people_inherit chinese0_1_
on chinese0_.people_id=chinese0_1_.people_id
where
chinese0_.people_id=?
com.csuinfosoft.hb3.inherit.fk.Chinese@27cd63[language=中文,peopleId=2,name=我是中国人]

10.2.4缺点分析

这种方式在关系模型上,比较的好,但是其查询效率比第一个低,至于用哪个得按实际情况来决定。

10.3继承实例三(不讲)

11. Myeclipse对hibernate的支持

11.1myeclipse配置 DataBase Explorer







如果你能看到test数据库下的表的话,表示配置DataBase Explorer试图已经成功了。
这样的话就可以在myeclipse进行数据的查询,当做数据库的客户端来用。



10.2 myeclipse 的反向映射

使用myeclipse对hibernate的支持完成数据库到实体类的反向映射。

12.2.1 建立工程

要使用myeclipse的中的hibernate的反向映射的话首先得建立一个工程,这里以web工程为例,建立WEB工程这里不在描述。

12.2.2 给工程添加hibernate支持







在DB Driver选择我们先前配置的mysql



指定SessionFactory工具类放哪里?放com.hb3.manager中。



会提供hibernate.cfg.xml的配置向导,使得配置文件不用纯手写,可以配置



我们写的HibernateUtil的其实是HibernateSessionFactory的简写版。

public class HibernateSessionFactory {
private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
private static Configuration configuration = new Configuration();
private static org.hibernate.SessionFactory sessionFactory;
private static String configFile = CONFIG_FILE_LOCATION;
static {
try {
configuration.configure(configFile);
sessionFactory = configuration.buildSessionFactory();
} catch (Exception e) {
System.err.println("%%%% Error Creating SessionFactory %%%%");
e.printStackTrace();
}
}
private HibernateSessionFactory() {
}
public static Session getSession() throws HibernateException {
Session session = (Session) threadLocal.get();
if (session == null || !session.isOpen()) {
if (sessionFactory == null) {
rebuildSessionFactory();
}
session = (sessionFactory != null) ? sessionFactory.openSession() : null;
threadLocal.set(session);
}
return session;
}
public static void rebuildSessionFactory() {
try {
configuration.configure(configFile);
sessionFactory = configuration.buildSessionFactory();
} catch (Exception e) {
System.err.println("%%%% Error Creating SessionFactory %%%%");
e.printStackTrace();
}
}
public static void closeSession() throws HibernateException {
Session session = (Session) threadLocal.get();
threadLocal.set(null);
if (session != null) {
session.close();
}
}
public static org.hibernate.SessionFactory getSessionFactory() {
return sessionFactory;
}
public static void setConfigFile(String configFile) {
HibernateSessionFactory.configFile = configFile;
sessionFactory = null;
}
public static Configuration getConfiguration() {
return configuration;
}
}

12.2.3数据库表到实体映射

选择表后右键”hibernate reverse engineering”,打开反向映射。









12 HQL

HQL(Hibernate Query Language),面向对象的查询语言,与SQL不同,HQL中查的是对象而不是和表,并且支持多态;HQL主要通过Query来操作。

12.1 查询

HQL的查询有多种方式,我们下面一一列举

12.1.1 无参查询

不带参数的查询,这里要注意的是SELECT可以不写,BookVo是类名,不是表名。

12.1.1.1查询全部字段

String hql = "from BookVo as b";
hql1 = "select b from BookVo as b where b.typeName=?";
Query query = session.createQuery(hql);
List<BookVo> list = (List<BookVo>) query.list();
for (BookVo b : list) {
System.out.println(b.toString());
}

12.1.1.2查询一个字段

String hql1 = "from BookVo as b";
Query query1 = session.createQuery(hql1);
List<String> list1 = (List<String>) query1.list();
for (String s : list1) {
System.out.println(s.toString());
}

12.1.1.3查询多个字段

查询多个字段的话,返回的是Object数组。

// 查询多个字段
String hql = "select b.bookName, b.bookPrice from BookVo as b where b.bookId=82";
Query query = session.createQuery(hql);
Object result[] = (Object[])query.uniqueResult();
for(Object s : result){
System.out.println(s);
}

12.1.2带参数的查询

这种的写法和JDBC里面的差不多,这里要注意的是下标是从0开始的,这个与JDBC不同。

// 第一种方式
String hql1 = "from BookVo as b where b.typeName=?";
Query query1 = session.createQuery(hql1);
query1.setString(0, "计算机/网络");
List<BookVo> list1 = (List<BookVo>) query1.list();
for (BookVo b : list1) {
System.out.println(b.toString());
}
下面这种方式是HQL中特有的,看如下代码,使用”:xxx”这种站位符来实现,这种比上面的好处是很明显的,在字段多的时候,这种特别适合使用。

下面的写法是简写,这种代码十分紧凑,推荐这么写。

String hql2 = "from BookVo as b where b.typeName=:typeName and b.bookPrice>:bookPrice";
Query query2 = session.createQuery(hql2);
query2.setString("typeName", "计算机/网络");
query2.setDouble("bookPrice", 50);
List<BookVo> list2 = (List<BookVo>) query2.list();
for (BookVo b : list2) {
System.out.println("typeName=" + b.getTypeName() + "bookPrice=" + b.getBookPrice());
}

// 上面的代码可以简写成
List<BookVo> list2 = session.createQuery(hql2).setString("typeName", "计算机/网络").setDouble("bookPrice", 50).list();
注意:query.uniqueResult(), query.list(),如果确定结构只有一条的话就使用uniqueResult(),如果是多个结果集,又使用它会报异常。

12.1.3 分页查询

以前使用jdbc分页的会,会是一件比较麻烦的事,不同数据库之间还有差异。如果使用hibernate分页的话,会是一件很方便的事。

Hibernate分页的话,会牵涉到Query的两个方法:

setFirstResult(int firstResult)

设置返回的记录从第几条开始,从零开始

setMaxResult(int maxResults)

设置本次查询返回的记录数

List<BookVo> list2 = session.createQuery(hql2).setString("typeName", "计算机/网络").setDouble("bookPrice", 50)
.setFirstResult(0).setMaxResults(10).list();
for (BookVo b : list2) {
System.out.println("typeName=" + b.getTypeName() + "bookPrice=" + b.getBookPrice());
}
确实分页就这么简单。

select
*
from
( select
bookvo0_.book_id as book1_50_,
bookvo0_.book_name as book2_50_,
bookvo0_.book_url as book3_50_,
bookvo0_.isbn as isbn50_,
bookvo0_.author as author50_,
bookvo0_.publish as publish50_,
bookvo0_.publish_date as publish7_50_,
bookvo0_.pages as pages50_,
bookvo0_.book_price as book9_50_,
bookvo0_.mini_price as mini10_50_,
bookvo0_.type_name as type11_50_,
bookvo0_.pic_url as pic12_50_,
bookvo0_.counts as counts50_
from
TB_BOOK bookvo0_
where
bookvo0_.type_name=?
and bookvo0_.book_price>? )
where
rownum <= ?

课堂作业

1. 查询雇员ID为7839的雇员信息。

2. 查询全部的部门信息。

3. 查询所有工作为“CLERK”的雇员信息。

4. 查询薪水在2000到3000范围内的雇员名字。

5. 查询所有的SALESMAN,且薪水大于1500的雇员信息。

6. 查询雇员表里面的第5位,到10位的雇员。

12.1.4关联与连接

当程序需要从多个数据表中取得数据时,SQL语句将会考虑使用多表查询,hibernate使用关联映射来处理底层数据之间的连接,一旦我们提供了正确的关联映射后,当程序通过hibernate进行持久化访问时,将可以利用hibernate的关联来进行连接。

我们使用oracle数据库中的 EMP, DEPT表的数据来作为实例。

12.1.4.1 实例

12.1.4.1.1 实体
EmpVo.java

public class EmpVo implements Serializable {
private Long empno;
private String ename;
private String job;
private Long mgr;
private Date hiredate;
private Long sal;
private Long comm;
private DeptVo dept;
}
DeptVo.java

public class DeptVo implements Serializable {
private Long deptno;
private String dname;
private String loc;
private Set<EmpVo> emps = new HashSet<EmpVo>();
}
12.1.4.1.2配置文件
EmpVo.hbm.xml
<hibernate-mapping package="com.hb.query.vo">
<class name="EmpVo" table="emp">
<id name="empno" column="empno">
<generator class="native" />
</id>
<property name="ename" column="ename" length="10" />
<property name="job" column="job" length="9" />
<property name="mgr" column="mgr" not-null="false" />
<property name="hiredate" column="hiredate" />
<property name="sal" column="sal" />
<property name="comm" column="comm" not-null="false" />
<many-to-one name="dept" class="DeptVo" cascade="all">
<column name="deptno" />
</many-to-one>
</class>
</hibernate-mapping>
DeptVo.hbm.xml
<hibernate-mapping package="com.hb.query.vo">
<class name="DeptVo" table="dept">
<id name="deptno" column="deptno">
<generator class="native" />
</id>
<property name="dname" column="dname" length="14" />
<property name="loc" column="loc" length="13" />
<set name="emps" table="emp" cascade="all" inverse="false">
<key column="deptno" />
<one-to-many class="EmpVo" />
</set>
</class>
</hibernate-mapping>
12.1.4.1.3 测试代码
测试代码

public static void testQuery() {
Session session = null;
try {
session = HibernateUtil.currentSession();
String hql = "from EmpVo as e where e.empno=7369";
List<EmpVo> list = session.createQuery(hql).list();
for (EmpVo b : list) {
System.out.println(b.toString());
}
} catch (HibernateException e) {
throw e;
} finally {
if (session != null && session.isOpen()) {
session.close();
}
}
}

12.1.4.1.4注意
注意:

1. <hibernate-mapping package="com.hb.query.vo">千万不要加 auto-import="false",如果加了这个的话使用HQL查询的时候会出现错误。

2. 由于EMP表的COMM属性会有为NULL的字段,建立的EmpVo类,中comm属性不能用long或者int 得用Long, Integer。

12.1.4.2查询

12.1.4.2.1配置
查询之前先把EMP, DEPT的关联关系确定好,从EMP到DEPT的关系是 双向多对一。

在Emp.hbm.xml中加入多对一的配置:

<many-to-one name="dept" class="DeptVo" cascade="all">
<column name="deptno" />
</many-to-one>
在Dept.hbm.xml中加入一对多的配置:

<set name="emps" table="emp" cascade="all">
<key column="deptno" />
<one-to-many class="EmpVo" />
</set>
在两端都配置后这种关联关系就成了双向了,可以通过EMP所属于的DEPT,或者通过DEPT,找到这个DEPT所属的EMP。

12.1.4.2.2测试代码
String hql = "from DeptVo d where d.deptno=10";
Query query = session.createQuery(hql);
List<DeptVo> list = (List<DeptVo>) query.list();
for (DeptVo b : list) {
System.out.println(b.toString());
}
12.1.4.2.3内连接
查询dname='SALES'的雇员信息,以及他的部门信息,下面的HQL语句写了三种,都可以,只讲上面两种。

String hql="select e from EmpVo e inner join e.dept where d.dname='SALES'";
hql="select e from EmpVo e,DeptVo d where e.dept=d and d.dname='SALES'";
hql="select e from EmpVo e,DeptVo d where e.dept.deptno=d.deptno and d.dname='SALES'";
List<EmpVo> list = session.createQuery(hql).list();
for (EmpVo e : list) {
System.out.println("EmpVo=" + e.getEname());
System.out.println("DeptVo=" + e.getDept().getDname());
}
迫切连接

hql = "select e from Emp as e inner join fetch e.dept as d where d.name='SALES'";
上面代码所对应的SQL语句如下两种方式
SELECT e.* FROM
emp e INNER JOIN dept d
ON
e.deptno = d.deptno
WHERE d.dname='SALES'
AND e.sal>2000;
SELECT e.* FROM
emp e, dept d
WHERE
e.deptno = d.deptno
AND d.dname='SALES'
AND e.sal>2000;
12.1.4.2.4 交叉连接(不讲)
GrossJOIN连接不讲。

12.1.4.2.5自连接
查询每个雇员和他的直属上级的名称的SQL语句如下:

select worker.ename ||'''s manager is '|| manager.ename

from EMP worker, EMP manager
where worker.mgr=manager.empno;
HQL的实现如下:

String hql = "select w.ename, m.ename from EmpVo w, EmpVo m WHERE w.mgr=m.empno";
List<Object[]> list = session.createQuery(hql).list();
for (Object[] b : list) {
System.out.println(b[0] + "'s manager is " + b[1]);
}
12.1.4.2.6左外连接
看下面的左外连接SQL语句DEPT左外连接emp表,也就是说当dept连接emp的时候dept表里面的是一定要有的,即使和emp不关联,dept的数据也要输出。

查询每个员工的部门名字

select d.dname, e.ename
from dept d left join emp e
on d.deptno = e.deptno
1 ACCOUNTING CLARK
2 ACCOUNTING KING
3 ACCOUNTING MILLER
4 RESEARCH JONES
5 RESEARCH FORD
6 RESEARCH ADAMS
7 RESEARCH SMITH
8 RESEARCH SCOTT
9 SALES WARD
10 SALES TURNER
11 SALES ALLEN
12 SALES JAMES
13 SALES BLAKE
14 SALES MARTIN
15 OPERATIONS
这里注意的是:查询的是DeptVo对象然后通过关联获取这个部门下面的所有的雇员,如果不用distinct会出现很多重复,这个很好解释,会返回15个DeptVo但是这个DeptVo里面的雇员都一样所有要去重。

session = HibernateUtil.currentSession();
String hql = "select distinct d from DeptVo as d left outer join d.emps as e";
List<DeptVo> list = session.createQuery(hql).list();
System.out.println("###########################");
for (DeptVo d : list) {
System.out.println("部门名=" + d.getDname());
Set<EmpVo> emps = d.getEmps();
for(Iterator<EmpVo> iter=emps.iterator();iter.hasNext();) {
System.out.println(" 雇员=" + iter.next().getEname());
}
System.out.println("###########################");
}
分析:从SQL语句可以看出它首先查询所有的部门,然后按照部门ID,一个一个的查询会有多个查询SELECT的查询语句,这样的查询效率肯定不高,hibernate里面有办法解决,有待于下面要讲的“迫切查询”。

12.1.4.2.7右外连接
准备,在EMP表里面添加一条没有部门的员工信息。
右外连接SQL语句
select d.dname, e.ename
from dept d right join emp e
on d.deptno = e.deptno
右外连接HQL写法,这里一定要注意从EmpVo里面取DeptVo的时候一定要判断为空,因为一人是没有部门的,不判断的结果很明显,会报错。
session = HibernateUtil.currentSession();
String hql = "select distinct e from DeptVo as d right outer join d.emps as e";
List<EmpVo> list = session.createQuery(hql).list();
for (EmpVo e : list) {
DeptVo d = e.getDept();
String deptName = d != null ? d.getDname() : "没有部门";
System.out.println("雇員:" + e.getEname() + " 屬於:" + deptName);
}
输出结果
雇員:SCOTT 屬於:RESEARCH
雇員:TURNER 屬於:SALES
雇員:FORD 屬於:RESEARCH
雇員:MARTIN 屬於:SALES
雇員:SMITH 屬於:RESEARCH
雇員:JONES 屬於:RESEARCH
雇員:jiang 屬於:没有部门
雇員:ADAMS 屬於:RESEARCH
雇員:JAMES 屬於:SALES
雇員:WARD 屬於:SALES
雇員:BLAKE 屬於:SALES
雇員:CLARK 屬於:ACCOUNTING
雇員:MILLER 屬於:ACCOUNTING
雇員:ALLEN 屬於:SALES
雇員:KING 屬於:ACCOUNTING
12.1.4.2.8迫切查询
12.1.4.2.8.1分析
我们来分析一下上面的左外连接

select distinct d from DeptVo as d left outer join d.emps as e

它的查询步骤是这样的:

1. 首先查询部门信息。

2. 然后按照每个部门的ID去分别查询。

从查询可以看出一共有4个部分外加第一个查询一个5个查询语句。

select

distinct deptvo0_.deptno as deptno1_,

deptvo0_.dname as dname1_,

deptvo0_.loc as loc1_

from

dept deptvo0_

left outer join

emp emps1_

on deptvo0_.deptno=emps1_.deptno

###########################

部门名=RESEARCH

select

emps0_.deptno as deptno1_1_,

emps0_.empno as empno1_,

emps0_.empno as empno0_0_,

emps0_.ename as ename0_0_,

emps0_.job as job0_0_,

emps0_.mgr as mgr0_0_,

emps0_.hiredate as hiredate0_0_,

emps0_.sal as sal0_0_,

emps0_.comm as comm0_0_,

emps0_.deptno as deptno0_0_

from

emp emps0_

where

emps0_.deptno=?

###########################

部门名=OPERATIONS

select

emps0_.deptno as deptno1_1_,

emps0_.empno as empno1_,

emps0_.empno as empno0_0_,

emps0_.ename as ename0_0_,

emps0_.job as job0_0_,

emps0_.mgr as mgr0_0_,

emps0_.hiredate as hiredate0_0_,

emps0_.sal as sal0_0_,

emps0_.comm as comm0_0_,

emps0_.deptno as deptno0_0_

from

emp emps0_

where

emps0_.deptno=?

###########################

部门名=ACCOUNTING

select

emps0_.deptno as deptno1_1_,

emps0_.empno as empno1_,

emps0_.empno as empno0_0_,

emps0_.ename as ename0_0_,

emps0_.job as job0_0_,

emps0_.mgr as mgr0_0_,

emps0_.hiredate as hiredate0_0_,

emps0_.sal as sal0_0_,

emps0_.comm as comm0_0_,

emps0_.deptno as deptno0_0_

from

emp emps0_

where

emps0_.deptno=?

###########################

部门名=SALES

select

emps0_.deptno as deptno1_1_,

emps0_.empno as empno1_,

emps0_.empno as empno0_0_,

emps0_.ename as ename0_0_,

emps0_.job as job0_0_,

emps0_.mgr as mgr0_0_,

emps0_.hiredate as hiredate0_0_,

emps0_.sal as sal0_0_,

emps0_.comm as comm0_0_,

emps0_.deptno as deptno0_0_

from

emp emps0_

where

emps0_.deptno=?

###########################

12.1.4.2.8.2实例
要实现迫切查询的话在原有左外连接上的改动很小:加一个”fetch”就可以了。

String hql = "select distinct d from DeptVo as d left outer join
fetch
d.emps as e";
List<DeptVo> list = session.createQuery(hql).list();
System.out.println("###########################");
for (DeptVo d : list) {
System.out.println("部门名=" + d.getDname());
Set<EmpVo> emps = d.getEmps();
for(Iterator<EmpVo> iter=emps.iterator();iter.hasNext();) {
System.out.println(" 雇员=" + iter.next().getEname());
}
System.out.println("###########################");
}
查询结果如下:从下面的查询结果可以看出来,只有一个SQL就可以了。确实也只要一个SQL就可以查询出来,一个查询语句,肯定比上面的5个查询语句查询的效率要高。

Hibernate:
select
distinct deptvo0_.deptno as deptno1_0_,
emps1_.empno as empno0_1_,
deptvo0_.dname as dname1_0_,
deptvo0_.loc as loc1_0_,
emps1_.ename as ename0_1_,
emps1_.job as job0_1_,
emps1_.mgr as mgr0_1_,
emps1_.hiredate as hiredate0_1_,
emps1_.sal as sal0_1_,
emps1_.comm as comm0_1_,
emps1_.deptno as deptno0_1_,
emps1_.deptno as deptno1_0__,
emps1_.empno as empno0__
from
dept deptvo0_
left outer join
emp emps1_
on deptvo0_.deptno=emps1_.deptno
###########################
部门名=ACCOUNTING
雇员=KING
雇员=CLARK
雇员=MILLER
###########################
部门名=RESEARCH
雇员=SMITH
雇员=ADAMS
雇员=FORD
雇员=SCOTT
雇员=JONES
###########################
部门名=SALES
雇员=TURNER
雇员=ALLEN
雇员=WARD
雇员=JAMES
雇员=MARTIN
雇员=BLAKE
###########################
部门名=OPERATIONS
###########################

12.1.5 聚合函数

HQL也支持在属性上使用聚合函数。这种支持与SQL中完全一样。

函数名

作用

avg

计算属性的平均值

count

计算对象的数量

max

计算属性的最大值

min

计算属性的最小值

sum

计算属性的和

实例,这些都比较简单:

String hql = "select count(*), max(sal), min(sal), avg(sal), sum(sal) from EmpVo";

Object[] ret = (Object[]) s.createQuery(hql).uniqueResult();

if (ret.length == 5) {

System.out.println("总数:" + ret[0]);

System.out.println("最高工资:" + ret[1]);

System.out.println("最低工资:" + ret[2]);

System.out.println("平均工资:" + ret[3]);

System.out.println("总工资" + ret[4]);

}

12.1.6 分组与排序

返回的结果集可以根据实体,或者实体的属性来进行排序的,排序的其它方面与SQL一样。

分组也和SQL基本一致。

查询部门的平均工资,然后按平均工资的降序排

String hql = "select e.dept.deptno, avg(e.sal) as avgSal from EmpVo e group by e.dept order by avgSal desc";

List<Object[]> list = (List<Object[]>) s.createQuery(hql).list();

for (Object[] obj : list) {

System.out.println("dept:" + obj[0] + ", avg(sal)=" + obj[1]);

}

12.1.7 表达式

HQL的表达式练练很丰富,不仅支持SQL的表达式还支持一些其它的表达式比如EJB的。

数学运算符

+, -, *, /等

比较运算符

=, >, >=, <, <=, <>, !=, like

逻辑运算符

and, or, not, in, not in, between, is null, is not null,is empty, is not empty

字符串链接

value1||value2, concat(value1, value2)

EJB-EQ3.0

Substring(),trim(),lower(),upper(),length(),locate(), abs(),sqrt(),big_length(),coalesce(),nullif()

时间函数

current_date(),current_time(),current_timestamp(), second(),minute(),hour(),day(),month(),year()

测试between and, is not null

// 使用between and 查询

String between = "select e.sal from EmpVo as e where e.sal between 500 and 1000";

List<Long> list = (List<Long>) s.createQuery(between).list();

for (Long sal : list) {

System.out.println("salary=" + sal);

}

// 测试 is null, is not null

String isNotNull = "select e.ename, e.comm from EmpVo as e where e.comm is not null";

List<Object[]> notNull = (List<Object[]>) s.createQuery(isNotNull).list();

for (Object[] obj : notNull) {

System.out.println("ename=" + obj[0] + ", comm=" + obj[1]);

}

查询哪些部门有佣金。

String hql = "select distinct d.name from Dept as d, Emp as e where e.dept=d and e.comm is not null";

List<Object> result = (List<Object>)session.createQuery(hql).list();

for(Object obj : result) {

System.out.println(obj);

}

12.1.8子查询

子查询是指嵌入到在其它SQL语句中的查询语句,也就嵌套查询,按照返回子查询返回的结果可以将子查询分为单行子查询,多行子查询,多列子查询

12.1.8.1单行子查询

单行子查询是指返回一行数据的子查询。挡在WHERE子句中引用当行子查询是使用比较运算符(=, !=, >, >=, <, <=)。

实例:查询显示与ALLEN同部门的所有其他员工的姓名,工资,部门

select e.ename, e.sal, e.deptno

from emp e

where e.deptno=(

select e.deptno from emp e where e.ename='ALLEN'

)

and e.ename != 'ALLEN';

// 返回单行子查询

String hql = "select e.ename, e.dept.deptno from EmpVo as e where e.dept.deptno=(select e.dept.deptno from EmpVo as e where e.ename='ALLEN') and e.ename!='ALLEN'";

List<Object[]> list = (List<Object[]>) s.createQuery(hql).list();

for (Object[] obj : list) {

System.out.println("ename=" + obj[0] + ", deptno=" + obj[1]);

}

ename=WARD, deptno=30

ename=MARTIN, deptno=30

ename=BLAKE, deptno=30

ename=TURNER, deptno=30

ename=JAMES, deptno=30

12.1.8.2多行子查询

多行子查询是指返回多行的数据的子查询,在where条件中使用的时候得使用IN, ALL, ANY

运算符
含义
IN
匹配子查询中任何一个值即可。

ALL
必须符合子查询结果集的全部值。

ANY
只需要符合子查询结果的任意一个值即可。

12.1.8.2.1 多行子查询之IN
查询与部门ID为10有相同工作的人员的信息(也就是JOB一样的)。

select e.ename, e.job, e.deptno

from emp e

where e.job in (

select distinct job

from emp e

where e.deptno=10

);

// 返回多行子查询

String hql = "select e.ename, e.dept.deptno, e.job from EmpVo as e where e.jobin(select distinct _e.job from EmpVo as _e where _e.dept.deptno=10)";

List<Object[]> list = (List<Object[]>) s.createQuery(hql).list();

for (Object[] obj : list) {

System.out.println("ename=" + obj[0] + ", deptno=" + obj[1] + ", job=" + obj[2]);

}

ename=JONES, deptno=20, salary=2975

ename=SCOTT, deptno=20, salary=3000

ename=KING, deptno=10, salary=5000

ename=FORD, deptno=20, salary=3000

12.1.8.2.2多行子查询之ALL
课堂作业:查询高于部门ID为30所以雇员工资的姓名,工资,部门

select e.ename, e.sal, e.deptno

from emp e

where e.sal > all (

select e.sal

from emp e

where e.deptno=30

);

或者

select e.ename, e.job, e.deptno

from emp e

where e.sal > (

select max(e.sal)

from emp e

where e.deptno=30

);

String hql = "select e.ename, e.dept.deptno, e.sal from EmpVo as e where e.sal > all (select _e.sal from EmpVo as _e where _e.dept.deptno=30)";

List<Object[]> list = (List<Object[]>) s.createQuery(hql).list();

for (Object[] obj : list) {

System.out.println("ename=" + obj[0] + ", deptno=" + obj[1] + ", salary=" + obj[2]);

}

12.1.8.2.2多行子查询之ANY(不讲)
查询高于部门ID为30任意雇员的工资的雇员名,工资,部门号,这种情况下得使用ANY

select e.ename, e.sal, e.deptno

from emp e

where e.sal > any (

select e.sal

from emp e

where e.deptno=30

);

// 返回多行子查询

String hql = "select e.ename, e.dept.deptno, e.sal from EmpVo as e where e.sal > any (select _e.sal from EmpVo as _e where _e.dept.deptno=30)";

List<Object[]> list = (List<Object[]>) s.createQuery(hql).list();

for (Object[] obj : list) {

System.out.println("ename=" + obj[0] + ", deptno=" + obj[1] + ", salary=" + obj[2]);

}

ename=KING, deptno=10, salary=5000
ename=FORD, deptno=20, salary=3000
ename=SCOTT, deptno=20, salary=3000
ename=JONES, deptno=20, salary=2975
ename=BLAKE, deptno=30, salary=2850
ename=CLARK, deptno=10, salary=2450
ename=ALLEN, deptno=30, salary=1600
ename=TURNER, deptno=30, salary=1500
ename=MILLER, deptno=10, salary=1300
ename=WARD, deptno=30, salary=1250
ename=MARTIN, deptno=30, salary=1250
ename=ADAMS, deptno=20, salary=110

12.1.8.3多列子查询(不讲)

实例:查询与SMITH部门和岗位完全一样的所有雇员。

select e.ename, e.job, e.deptno

from emp e

where (e.deptno, e.job) = (

select e.deptno, e.job

from emp e

where e.ename='SMITH'

);

// 返回多列子查询

String hql = "select e.ename, e.dept.deptno, e.job from EmpVo as e where (e.dept.deptno, e.job) = (select _e.dept.deptno, _e.job from EmpVo as _e where _e.ename='SMITH')";

List<Object[]> list = (List<Object[]>) s.createQuery(hql).list();

for (Object[] obj : list) {

System.out.println("ename=" + obj[0] + ", deptno=" + obj[1] + ", salary=" + obj[2]);

}

ename=SMITH, deptno=20, salary=CLERK

ename=ADAMS, deptno=20, salary=CLERK

12.1.8.4相关子查询

对于普通子查询的时候来说,子查询只执行一次,而对于相关子查询来说,每处理一行SQL语句的数据都会执行一次相关子查询。

实例:显示工资高于部门平均工资的雇员名称,工资和部门号。

select e.ename, e.sal, e.deptno

from emp e

where e.sal > (

select avg(inEmp.sal)

from emp inEmp

where inEmp.deptno=e.deptno

);

// 返回多列子查询

String hql = "select e.ename, e.dept.deptno, e.sal from EmpVo as e where e.sal > (select avg(_e.sal) from EmpVo as _e where _e.dept.deptno=e.dept.deptno)";

List<Object[]> list = (List<Object[]>) s.createQuery(hql).list();

for (Object[] obj : list) {

System.out.println("ename=" + obj[0] + ", deptno=" + obj[1] + ", salary=" + obj[2]);

}

// 查询结果

ename=ALLEN, deptno=30, salary=1600

ename=JONES, deptno=20, salary=2975

ename=BLAKE, deptno=30, salary=2850

ename=SCOTT, deptno=20, salary=3000

ename=KING, deptno=10, salary=5000

ename=FORD, deptno=20, salary=3000

12.1.9多态查询

如果查询Object的话,hibernate会查询所有已经映射了的表,这点可以说明hibernate对多态查询的支持。

String hql = "from java.lang.Object as o";

List<Object> list = (List<Object>) s.createQuery(hql).list();

for(Object o : list){

System.out.println(o.toString());

}

课堂作业

1. 列出至少有5个员工的所有部门。

select dname from dept where deptno in(select deptno from emp group by deptno having count(*)>4)

2. 列出薪金比“SMITH”多的所有员工。

select ename from emp where sal>(select sal from emp where ename ='SMITH');

3. 列出在部门“SALES”(销售部)工作的员工的姓名。

select ename from emp where deptno=(select deptno from dept where dname like'SALES');

4. 列出受雇日期早于其直接上级的所有员工。

select ename from emp a where HIREDATE<(select HIREDATE from emp where empno=a.mgr);

5. 列出薪金高于在部门30工作的所有员工的薪金的员工姓名和薪金。

select sal,ename from emp where sal>(select max(sal) from emp where deptno=30);

6. 列出在每个(每个是关键字,对此group by)部门工作的员工数量、平均工资和平均服务期限。

select deptno,count(*),avg(a.sal),avg(sysdate-HIREDATE) from emp a group by deptno;

7. 列出各种工作的最低工资。

select job,min(sal) from emp group by job ;

8. 列出经理人的名字。

select e.ename from emp e where e.empno in (select distinct e1.mgr from emp e1);

9. 列出薪金水平处于第四位的雇员信息。

select * from (select rownum grade,ename,sal from (select e.empno,e.ename,e.sal from emp e order by e.sal desc )) where grade=4;

10. 查询各部门薪水最高的员工。

select * from emp e, (select deptno,max(sal) sal from emp group by deptno) s where e.sal=s.sal;

11. 查询最先受雇的职员信息。

select * from emp where hiredate=(select min(hiredate) from emp);

12. 查询平均工资大于1900元的部门的工资总额,职员人数和平均工资。

select avg(sal),count(*),sum(sal) ,deptno from emp group by deptno having avg(sal)>1900;

13. 查询员工名正好为6个字符的员工的信息。

select * from emp where length(ename)= 6;

14. 查询员工名字中不包含字母“S”员工。

select * from emp where ename not like '%S%';

15. 查询员工姓名的第2个字母为“M”的员工信息。

select * from emp where ename like '_M%';

16. 查询人数最多的部门信息。

select * from dept where deptno in (

select deptno from (

select count(*) count, deptno from emp group by deptno) where count in (

select max(count) from (

select count(*) count,deptno from emp group by deptno

)

)

);

12.2DML操作

使用HQL进行UPDATE, DELETE等操作。如果要更新或者删除的话得调用executeUpdate()。

12.2.1 update 操作

下面的代码是简写,只列举核心代码

/**
* 按照ID修改雇员的薪水
* @param empvo
* @param salary
*/
String hql = "UPDATE EmpVo AS e SET e.sal=:sal WHERE e.empno=:no";
int ret = session.createQuery(hql).setInteger("no", 8888).executeUpdate();
t.commit();
Assert.assertEquals(ret > 0, true);
Emp e = (Emp) session.get(Emp.class, 8888);
Assert.assertEquals(e.getSal(), 10000);

12.2.2 delete操作

下面的代码是简写,只列举核心代码

String hql = "delete Emp as e where e.sal=:sal";
int ret = session.createQuery(hql).setInteger("sal", 10000).executeUpdate();
t.commit();
Assert.assertEquals(ret > 0, true);

Emp e = (Emp) session.get(Emp.class, 8888);
Assert.assertEquals(e, null);

12.3 HQL命名查询

我们上面的查询语句都是写在代码里面的这样照成一个问题,如果要修改查询的话就得修改源代码,这样会有一定的局限性。

看下hibernate中的命名查询的实现方式:

1. 首先把查询语句写到你要查询的对应实体的映射文件中,注意一定要写在<class>标签之外,和</hibernate-mapping>之前,如果写在<class>中是会报异常的。

<query name="queryEmpBySalary">
<![CDATA[select e from EmpVo as e where e.sal>:sal]]>
</query>
2. 代码中调用,注意调用的方法是getNamedQuery(),设置查询返回值和以前的代码一样。

session = HibernateUtil.currentSession();
List<EmpVo> list =session.getNamedQuery("queryEmpBySalary").setInteger("sal", salary).list();
for (EmpVo b : list) {
System.out.println(b.toString());
}

12.4 SQL查询

Hibernate是支持SQL查询的,这个与HQL查询的不同之处在于SQL查询返回的是SQLQuery类,它是Query的实现类。

通过SQL查询实现查询用户与用户详细信息

String sql = "select e.user_name, d.nick_name from t_user e join t_user_detail d on e.user_id=d.user_id and e.user_id=1";
SQLQuery query = s.createSQLQuery(sql);
List<Object[]> obj = (List<Object[]>) query.list();
for (Object[] o : obj) {
System.out.println(o[0] + ", " + o[1]);
}
如果使用select * 这种查询的话,可以按照下面的写。

String sql = "select * from emp e";
List<Emp> emps = (List<Emp>) session.createSQLQuery(sql).addEntity(Emp.class).list();
for (Emp e : emps) {
System.out.println(e.getName());
}
注意:addScalar()方法,它给里面的字段添加返回类型。

String sql = "select max(e.sal) as maxWeigth from emp e";
int maxWeigth = (Integer) session.createSQLQuery(sql).addScalar("maxWeigth", StandardBasicTypes.INTEGER).uniqueResult();
System.out.println(maxWeigth);
指定的类型在下面的类中:

org.hibernate.type.StandardBasicTypes
org.hibernate.Hibernate //这个也可以,只是已经过时了。
统计各个部门的人数,按照部门人数的降序排列

String sql = "select deptno, count(deptno) depts from emp e group by deptno order by depts desc";
List<Object[]> results = (List<Object[]>)session.createSQLQuery(sql).list();
for (Object[] result : results) {
System.out.println(result[0] + ", " + result[1]);
}
按照书的类别来统计,按每种类别书的降序排列。

String sql = "select count(type_name) as counts ,type_name from tb_book where type_name is not null group by type_name order by counts desc";
SQLQuery query = session.createSQLQuery(sql);
//query.addScalar("counts", StandardBasicTypes.INTEGER);
//query.addScalar("type_name", StandardBasicTypes.STRING);
List<Object[]> list = (List<Object[]>) query.setFirstResult(0).setMaxResults(5).list();
for (Object[] array : list) {
System.out.println("类别=" + array[1] + ", 数量=" + array[0]);
}

12.4.1 SQL命名查询

HQL支持命名查询,SQL与支持命名查询的。使用命名的SQL查询还可以将SQL语句放在配置文件中配置,从而提高程序的解耦,命名SQL查询的一个作用是可以用于调用存储过程。

12.4.1.1 简单查询

查询特定字段

session = HibernateSessionFactory.getSession();
int maxWeigth = (Integer) session.getNamedQuery("queryMaxSalary").uniqueResult();
System.out.println(maxWeigth);
<sql-query name="queryMaxSalary">
<![CDATA[select max(e.sal) as maxWeigth from emp e]]>
</sql-query>

12.4.1.2 整个实体查询

注意:在使用SQL查询整个实体的时候,这里要注意的是要用“<return class="com.hb.query.vo.EmpVo" />”来指明返回的数据类型。

List<EmpVo> emps = (List<EmpVo>) session.getNamedQuery("queryAllEmp").list();
for (EmpVo e : emps) {
System.out.println(e.toString());
}

<sql-query name="queryAllEmp">
<return alias="e" class="com.hb.query.vo.EmpVo" />
<![CDATA[select * from emp as e]]>
</sql-query>

12.4.1.3 调用存储过程(不讲)

调用存储过程还有如下需要注意的地方:

1. 因为存储过程本身完成了查询的全部操作,所以调用存储过程进行的查询无法使用setFirstResult()/setMaxResults()进行分页。

2. 存储过程只能返回一个结果集,如果存储过程返回多个结果集,Hibernate将仅处理第一个结果集,其他将被丢弃。

3. 如果在存储过程里设定SET NOCOUNT ON,将有更好的性能表现。当然也可以没有该设定。

12.4.1.3.1 无参存储过程
CREATE PROCEDURE get_all_emp()
begin
select * from emp;
end
<sql-query name="getAllEmp" callable="true">
<return class="com.hb.query.vo.EmpVo" />
{ call get_all_emp() }
</sql-query>

List<EmpVo> emps = (List<EmpVo>) session.getNamedQuery("getAllEmp").list();
for (EmpVo e : emps) {
System.out.println(e.toString());
}
12.4.1.3.2 入参存储过程
CREATE PROCEDURE get_emp_by_id(in id int)
begin
select * from emp where empid=id;
end
<sql-query name="getEmpById" callable="true">
<return class="com.hb.query.vo.EmpVo" />
{ call get_emp_by_id(?) }
</sql-query>

List<EmpVo> emps = (List<EmpVo>) session.getNamedQuery("getEmpById").setInteger(0, id).list();
for (EmpVo e : emps) {
System.out.println(e.toString());
}
12.4.1.3.3 出参存储过程
12.4.1.3.4 SQL查询设置返回值类型
这里要注意的是,不设置返回的字段的类型的话,这段代码是无法运行的,红色代码就是设置类型。

String sql = "select * from tb_book where book_id=:id";

// 创建SQLQuery对象

SQLQuery query = session.createSQLQuery(sql);

// 查询

// addEntity(Class)设置返回数据的类型

BookVo book = (BookVo) query.addEntity(BookVo.class).setInteger("id", bookId).uniqueResult();

System.out.println(book.toString());

//按照书的类别进行统计,然后按书的数量降序排列

session = HibernateSessionFactory.getSession();

String sql = "select count(type_name) as counts ,type_name from tb_book where type_name is not null group by type_name order by counts desc";

// 创建SQLQuery对象

// addScalar()设置SQL查询返回的数据的类型

// 有几个返回字段,就要设置几个字段。

SQLQuery query = session.createSQLQuery(sql);

query.addScalar("counts", StandardBasicTypes.INTEGER);

query.addScalar("type_name", StandardBasicTypes.STRING);

List<Object[]> list = (List<Object[]>) query.setFirstResult(0).setMaxResults(maxResults).list();

for (Object[] array : list) {

System.out.println("类别=" + array[1] + ", 数量=" + array[0]);

}

12.5.条件查询 Criteria

基于对象的查询,这种查询里面不会出现HQL语句,也不会出现SQL语句。

12.5.1无条件查询

Criteria criteria = session.createCriteria(EmpVo.class);
List<EmpVo> emps = (List<EmpVo>) criteria.list();
for (EmpVo vo : emps) {
System.out.println(vo.getEname());
}

12.5.2带条件的查询

通过工作来查询员工

Criteria criteria = session.createCriteria(EmpVo.class);
criteria.add(Restrictions.eq("job", job));
List<EmpVo> emps = (List<EmpVo>) criteria.list();
for (EmpVo vo : emps) {
System.out.println("ename=" + vo.getEname() + " ,job=" + vo.getJob());
}
通过薪水的区间来查询员工

Criteria criteria = session.createCriteria(EmpVo.class);
criteria.add(Restrictions.between("sal", begin, end));
List<EmpVo> emps = (List<EmpVo>) criteria.list();
for (EmpVo vo : emps) {
System.out.println("ename=" + vo.getEname() + " ,sal=" + vo.getSal());
}
Criteria criteria = session.createCriteria(EmpVo.class);
criteria.add(Restrictions.gt("sal", begin));
criteria.add(Restrictions.lt("sal", end));
分页查询

Criteria criteria = session.createCriteria(EmpVo.class);
criteria.setFirstResult(0);
criteria.setMaxResults(5);
List<EmpVo> emps = (List<EmpVo>) criteria.list();
for (EmpVo vo : emps) {
System.out.println("ename=" + vo.getEname() + " ,sal=" + vo.getSal());
}
模糊查询

Criteria criteria = session.createCriteria(EmpVo.class);
criteria.add(Restrictions.like("ename", ename));
List<EmpVo> emps = (List<EmpVo>) criteria.list();
for (EmpVo vo : emps) {
System.out.println("ename=" + vo.getEname());
}
方法链查询

Criteria criteria = session.createCriteria(BookVo.class).add(Restrictions.between("miniPrice", 20d, 50d)).add(Restrictions.eq("typeName", "计算机/网络")).add(Restrictions.like("bookName", "%java%")).setFirstResult(0)
.setMaxResults(10);
List<BookVo> books = (List<BookVo>) criteria.list();
for (BookVo vo : books) {
System.out.println("bookName=" + vo.getBookName());
}
排序:查詢计算机/网络,然后按价格降序排列

Criteria criteria = session.createCriteria(BookVo.class).add(Restrictions.eq("typeName", "计算机/网络")).addOrder(Order.desc("miniPrice")).setFirstResult(0).setMaxResults(10);
List<BookVo> books = (List<BookVo>) criteria.list();
for (BookVo vo : books) {
System.out.println("bookName=" + vo.getBookName() + ", typeName=" + vo.getTypeName() + ", miniPrice="+ vo.getMiniPrice());
}
or的组合查询,书类别为“计算机/网络”或者书的价格是400~500之间
Criteria criteria = session.createCriteria(BookVo.class);
LogicalExpression or = Restrictions.or(Restrictions.eq("typeName", "计算机/网络"), Restrictions.between("miniPrice", 400d,500d));
criteria.add(or);
List<BookVo> books = (List<BookVo>) criteria.setFirstResult(0).setMaxResults(10).list();
for (BookVo vo : books) {
System.out.println("bookName=" + vo.getBookName() + ", typeName=" + vo.getTypeName() + ", miniPrice="+ vo.getMiniPrice());
}
函数
Object maxPrice = session.createCriteria(BookVo.class).setProjection(Projections.max("miniPrice")).uniqueResult();
分组
List<String> types = session.createCriteria(BookVo.class).setProjection(Projections.groupProperty("typeName")).setFirstResult(0).setMaxResults(10).list();
for (String t : types) {
System.out.println(t);
}

13.事务

数据库事务(DatabaseTransaction) ,是由一系列操作序列构成的程序执行单元,这些操作要么都做,要么都不做,是一个不可分割的工作单位。必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。

1. 原子性(Atomic)

事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。

2. 一致性(Consistency)

数据库的事务不能破坏关系型数据库的完整性以及业务逻辑上的一致性。

3. 隔离性(Isolation)

多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

4. 持久性(Durability)

已被提交的事务对数据库的修改应该永久保存在数据库中。

13.1事务隔离级别(不讲)

在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。

读未提交(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

称为读提交(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

可重复读取(RepeatableRead):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

Mysql的事务隔离级别设置。

// 设置当前的事务隔离级别

set transaction isolation level read committed;

// 设置全局的事务隔离级别

set global transaction isolation level read committed;

13.2 hibernate中的事务

Hibernate封装了JDBC和JTA,所以我们可以通过hibernate的API来声明事务。

只要在hibernate.cfg.xml中配置hibernate.transaction.factory_class属性即可。JDBCTransactionFactory也是hibernate默认的默认值。

<property name="hibernate.transaction.factory_class">

<![CDATA[org.hibernate.transaction.JDBCTransactionFactory]]>

</property>

<property name="hibernate.transaction.factory_class">

<![CDATA[org.hibernate.transaction.JTATransactionFactory]]>

</property>

注意:具体配置参见hibernate.properties315行

备注:JTA(Java Transaction API)是事务服务的J2EE解决方案。本质上,它是描述事务接口的J2EE模型的一部分,开发人员直接使用该接口或者通过J2EE容器使用该接口来确保业务逻辑能够可靠地运行。

在hibernate中JDBC,JTA事务的区别是,如果是跨数据库的事务用JTA,一个数据库的情况下用JDBC就可以了。

13.3悲观锁

悲观锁是指应用程序中显示的为数据加锁,悲观锁假定当前事务操作的数据资源时,肯定还有有其它事务同时访问该数据资源,为了避免当前事务的操作不受干扰,先锁定资源,尽管“悲观锁”能够防止丢失更新和不可以重复读取这类的并发问题,但是它会影响并发的性能,因此应该很谨慎的使用悲观锁。

Hibernate的加锁模式有:

类型

作用

LockMode.NONE

不加锁

LockMode.WRITE

在Insert和Update记录的时候会加锁

LockMode.READ

在读取记录的时候会加锁

LockMode.UPGRADE

利用数据库的for update子句加锁。

LockMode.PESSIMISTIC_WRITE

和UPGRADE一样只是UPGRADE为deprecated。

LockMode. UPGRADE_NOWAIT

Oracle的特定实现,利用Oracle的for update nowait子句实现加锁。

13.3.1悲观锁实例

13.3.1.1配置文件

<property name="hibernate.transaction.factory_class">

<![CDATA[org.hibernate.transaction.JDBCTransactionFactory]]>

</property>

<property name="current_session_context_class">

<![CDATA[thread]]>

</property>

<property name="hibernate.connection.isolation">2</property>

13.3.1.2测试代码

Emp e = (Emp) session.get(Emp.class, 7369, LockOptions.UPGRADE);

13.3.1.3分析

select

emp0_.empno as empno0_0_,

emp0_.ename as ename0_0_,

emp0_.job as job0_0_,

emp0_.mgr as mgr0_0_,

emp0_.hiredate as hiredate0_0_,

emp0_.sal as sal0_0_,

emp0_.comm as comm0_0_,

emp0_.deptno as deptno0_0_

from

emp emp0_

where

emp0_.empno=? for update

13.4乐观锁

乐观锁假定当前事务操作数据的时候,不一定会有其它事务同时访问该数据资源。Hibernate中使用版本控制来避免可能出现的并发问题。

一般情况下通过配置数据库的事务隔离级别为“ReadCommitted”,然后在加上乐观锁来达到“性能”与“数据一致性”之间的平衡。

13.4.1实例1

以下使用Integer Version来实现

public class Person implements Serializable {
private int personId;
private String name;
private int age;
// 用来记录版本号
private Integer version;
}
<hibernate-mapping package="com.hb.query.transaction">
<class name="Person" table="t_person" lazy="true"
optimistic-lock="version">
<id name="personId" column="person_id">
<generator class="native" />
</id>
<version name="version" column="version" />
<property name="name" column="person_name">
</property>
<property name="age" column="person_age">
</property>
</class>
</hibernate-mapping>
Session session1 = null;
Transaction t1 = null;
Session session2 = null;
Transaction t2 = null;
try {
// 第一个事务
session1 = HibernateMysqlUtil.getSession();
t1 = session1.beginTransaction();
Student s1 = (Student) session1.get(Student.class, 1);
// 第二个事务
session2 = HibernateMysqlUtil.getSession();
t2 = session2.beginTransaction();
System.out.println("t2.isActive()=" + t2.isActive());
Student s2 = (Student) session2.get(Student.class, 1);
// 更新
s1.setName("先下手");
s2.setName("后下手");
// 提交事物的顺序是先提交第二个,然后是第一个。
t2.commit();
t1.commit();
} catch (Exception e) {
e.printStackTrace(); //这些不要回滚事务,会有异常。
} finally {
HibernateMysqlUtil.closeSession(session1);
HibernateMysqlUtil.closeSession(session2);
}
注意:这里首先插入一个Person,然后呢调用更新方法,这个方法意思获取两个Session,然后开启两个Transaction。先让一个事物1先更新,事务2慢更新,然后事务2先提交,事务1慢提交,如果不配置乐观锁的时候,执行是没问题的,但是这样照成了更新被覆盖,设置乐观锁后,会包异常,不会让慢提交的事务提交。

这里会有两个问题:

1. 当重复执行这个代码的时候不会报异常,且只有一个更新语句,这个的原因是:调用持久太的SET方法的话,如果内容与数据库里面一样的话,不会产生UPDATE,这点算是hibernate只能方面,也就是说这种情况下,不会有两个事务同时提交,所有不会报异常。

2. 这里获取Session的话要注意:不能用ThreadLocal的实现方式了,这样的话会有问题,这个的原因是在于使用ThreadLocal获取Session的话,它保证同一个Thread中都是一个Session,这样的话,当你再次打开事务的时候肯定有问题,所有会报。

Exception in thread "main" org.hibernate.TransactionException:
Transaction not successfully startedat
org.hibernate.transaction.JDBCTransaction.rollback(JDBCTransaction.java:179)
at com.hbm3.transaction.Lock.optimismLock(Lock.java:100)
at com.hbm3.transaction.Lock.main(Lock.java:119)

13.4.2实例2

使用“时间戳”的话也可以,但是时间戳的精度是有限的。

public class Person implements Serializable {
private int personId;
private String name;
private int age;
private Date timestamp;
}
<class name="Person" table="t_person"optimistic-lock="version">
<id name="personId" column="person_id">
<generator class="native" />
</id>
<timestamp name="timestamp" />
<property name="name" column="person_name" />
<property name="age" column="person_age" />
</class>

14.缓存

缓存在现实开发中用的很多,这个的因为在于,去数据库查询的效率是比较低的,即使有数据库连接池,查询数据库也要向数据库发送查询请求,数据库要解析请求,从数据库查询数据,包装数据,在返回,jdbc在解析,这种都是耗时的。

Hibernatet提供了两级缓存,第一级缓存是Session缓存,属于事务级缓存,第一级缓存是必需的,不允许且无法删除。第二级缓存是一个可插拔的缓存插件,它有SessionFactory缓存的,属于应用级缓存,默认情况下二级缓存不打开的。

14.1模拟缓存

下面的模拟一下缓存,把数据放到Map里面,第一次查询的时候去数据库查询,当再次查询的时候就去Map里面去取。这样做会大大的提高效率。

这里计算时间的话,可能看不出什么效果。

/**
* 模拟缓存
* @author sinoyang
*/
public class MyCache {

private static Map<String, BookVo> cache = new HashMap<String, BookVo>();

/**
* 通过ID获取
* @param id
* @return
*/
public static BookVo getBookById(int id) {

long begin = System.currentTimeMillis();
// key
String key = BookVo.class.getName() + id;
// 从缓存获取。
BookVo vo = cache.get(key);
// 如果从缓存中获取不到的话,就去数据库查
if (vo == null) {
System.out.println("缓存中没有,去数据库获取!");
vo = (BookVo) getBookInDBById(id);
// 放入缓存
cache.put(key, vo);
} else {
System.out.println("缓存中获取!");
}
long end = System.currentTimeMillis();
System.out.println((end - begin) + "毫秒");

return vo;
}

/**
* 从数据库获取
* @param id
* @return
*/
public static BookVo getBookInDBById(int id) {

Session s = null;
Transaction t = null;
BookVo vo = null;
try {
s = HibernateSessionFactory.getSession();
t = s.beginTransaction();
vo = (BookVo) s.get(BookVo.class, id);
t.commit();

return vo;
} catch (HibernateException e) {
if (t != null && t.isActive()) {
t.rollback();
}
throw e;
} finally {
if (s != null && s.isOpen()) {
s.close();
}
}
}

public static void main(String[] args) {
getBookById(1000);
getBookById(1000);
}
}
下面的执行结果可以发现,第二次查询去缓存中取的时候效率大大提高了。

缓存中没有,去数据库获取!
1485毫秒
缓存中获取!
0毫秒

14.2 一级缓存

当应用程序调用Session的save(), update(), saveOrUpdate(), load(), get()方法,以及调用Query的list()等方法的时候,如果缓存中不存在这个对象,hibernate会把这个对象放入一级缓存。清理一级缓存的时候会根据缓存中对象的状态同步到数据库。

Session提供两个方法管理一级缓存:

evict(Object obj): 从缓存中清除指定的对象。

clear():清空缓存中的所有对象。

下面的代码通过get()查询,然后通过断言的测试发现,测试用例可以通过,这说明,即使调用两次,也只返回的同一个对象,这时候就用了一级缓存,从后台打印的sql语句也可以发现确实只打印了一个sql语句

当运行evict()方法的时候发现运行后b1 == b2的结果是false,因为evict()把b1从session中清除了,下面的代码contains()方法的时候来检测一级缓存中是否存在,由于evict()把b1从session中清除了后打印的值是false,b2则是true。

Book b1 = (Book) s.get(Book.class, 1000);

s.evict(b1);

Book b2 = (Book) s.get(Book.class, 1000);

System.out.println(b1 == b2);

System.out.println(s.contains(b1));

System.out.println(s.contains(b2));

t.commit();

clear()会把清理全部的一级缓存,所以下面的两个contains()都是false。

Book b1 = (Book) s.get(Book.class, 1000);

//s.evict(b1);

Book b2 = (Book) s.get(Book.class, 1000);

System.out.println(b1 == b2);

s.clear();

System.out.println(s.contains(b1));

System.out.println(s.contains(b2));

问题

考虑:使用get()方式查询数据,已经使用HQL查询相同的数据的时候,HQL查询能否使用缓存呢???

get(), load()可以从缓存中拿数据。

14.3 二级缓存

一级缓存有局限,比如放入缓存的东西可以无限制,直到内存溢出,再个它存在的周期很短。一般是一个session里面,这时候就有了二级缓存。

Hibernate中没有实现缓存,它的缓存是交由第三方来实现的。Hibernate支持的缓存有多种。

14.3.1二级缓存类型

Hibernate支持如下几种缓存

org.hibernate.cache.EhCacheProvider

org.hibernate.cache.EmptyCacheProvider

org.hibernate.cache.HashtableCacheProvider

org.hibernate.cache.TreeCacheProvider

org.hibernate.cache.OSCacheProvider

org.hibernate.cache.SwarmCacheProvider

缓存类型:

类型

作用

read-only

只读缓存:如果你的应用程序只需读取一个持久化类的实例,而无需对其修改,那么就可以对其进行只读缓存。这是最简单,也是实用性最好的方法。
nonstrict-read-write

不严格的读写缓存:如果应用程序只偶尔需要更新数据(也就是说,两个事务同时更新同一记录的情况很不常见),也不需要十分严格的事务隔离,那么比较适合使用“非严格读/写”缓存策略。
read-write

读写缓存:如果应用程序需要更新数据,那么使用读/写缓存比较合适。

transactional

事务性缓存:Hibernate 的事务缓存策略提供了全事务的缓存支持,例如对 JBoss TreeCache 的支持。这样的缓存只能用于 JTA 环境中。
缓存类型

只读型

非严格读写型

读写型

事务型

EHCache
支持
支持
支持

OSCache
支持
支持
支持

SwarmCache
支持
支持


JBossCache
支持


支持

14.3.2二级缓存配置

配置包括:拷贝JAR包。开启二级缓存,配置缓存类型,指定哪些类需要缓存。

所需JAR包。

slf4j-api-1.5.11.jar

backport-util-concurrent-3.1.jar

commons-logging-1.1.1.jar

配置

<!—开启二级缓存 -->

<property name="hibernate.cache.use_second_level_cache ">true</property>

<!-- 缓存类型 -->

<property name="hibernate.cache.provider_class">

<![CDATA[org.hibernate.cache.EhCacheProvider]]>

</property>

<!-- 设置哪些类需要缓存 -->

<!—这个配置一定要写在映射文件配置之后 -->

<class-cache usage="read-only" class="com.hb.query.vo.BookVo"/>

<!—或者 -->

<hibernate-mapping package="com.hb.query.vo">

<class name="BookVo" table="TB_BOOK" lazy="true">

<cache usage="read-only" />

<id name="bookId" column="book_id">

<generator class="native" />

</id>

</class>

</hibernate-mapping>

14.3.2.1 EHCACHE缓存配置

把ehcache.xml配置文件拷贝到SRC下,把ehcache所需的jar包拷贝到lib目录下。

<ehcache>

<diskStore path="java.io.tmpdir"/>

<defaultCache

maxElementsInMemory="10000"

eternal="false"

timeToIdleSeconds="120"

timeToLiveSeconds="120"

overflowToDisk="true"

/>

<cache name="com.hb.query.vo.BookVo"

maxElementsInMemory="10000"

eternal="false"

timeToIdleSeconds="300"

timeToLiveSeconds="600"

overflowToDisk="true"

/>

14.3.2.1.1EHCACHE配置
diskStore

指定一个文件目录,当EHCACHE把数据写到硬盘的时候,将把数据写到这个目录下。

defaultCache

设定默认缓存

cache

设定具体的二级缓存

name

设置缓存的名字,它的取值是类的完整路径,也就是包名加类名。

maxElementsInMemory

设置在内存中缓存对象的最大数目

eternal

如果是true,表示对象永远不过期,会忽略timeToIdleSeconds,timeToLiveSeconds,默认值是false

timeToIdleSeconds

设定允许对象处于闲置状态的最大时间,单位秒,当对象闲置时间超过配置时间,这个对象就会过期,如果对象过期,会从缓存中清除。只有eternal为false时候才有效,如果值是0,表示对象无限期处于空虚。

timeToLiveSeconds

设置对象在内存中的最大缓存时间,单位为秒,如果超过这个时间回被缓存清除,只有eternal为false时候才有效,如果值为0,表示无限期有效,且timeToLiveSeconds大于timeToIdleSeconds时候才有效。

overflowToDisk

如果是true表示在内存中的缓存数目大于了maxElementsInMemory,会把溢出的对象写入到diskStore配置的本地文件里面。

14.3.2.2 OSCACHE缓存配置(不講)

14.3.3二级缓存测试

还是以上面的查询书本信息为例,连续调用两次的时候,分别在配置缓存,和不配置缓存的情况下。

14.3.3.1测试代码

public static BookVo getBookInDBById(int id) {

Session s = null;

Transaction t = null;

BookVo vo = null;

try {

s = HibernateSessionFactory.getSession();

t = s.beginTransaction();

vo = (BookVo) s.get(BookVo.class, 1000);

t.commit();

return vo;

} catch (HibernateException e) {

if (t != null && t.isActive()) {

t.rollback();

}

throw e;

} finally {

if (s != null && s.isOpen()) {

s.close();

}

}

}

public static void main(String[] args) {

getBookInDBById(1000);

getBookInDBById(1000);

}

14.3.3.2测试结果分析

从运行结果来看,如果不配置缓存的话,会产生两个select查询语句,如果打开缓存,之后有一个select语句。

14.3.3.3 测试配置

打开缓存统计信息,便于我们的分析与统计缓存信息。

<property name=hibernate.generate_statistics>true</property>

根据ID查询书本信息

public static Book getBookById(int id) throws Exception {

Session s = null;

Transaction t = null;

try {

s = HibernateSessionFactory.getSession();

t = s.beginTransaction();

Book b = (Book) s.get(Book.class, 51);

System.out.println(b.getBookName());

t.commit();

return b;

} catch (Exception e) {

if (t != null) {

t.rollback();

}

e.printStackTrace();

throw e;

}

}

@Test

@SuppressWarnings("rawtypes")

public void testSecondCache() throws Exception {

getBookById(51);

getBookById(51);

SessionFactory sf = HibernateSessionFactory.getSessionFactory();

Statistics stat = sf.getStatistics();

Map map = stat.getSecondLevelCacheStatistics("com.csuinfosoft.vo.Book").getEntries();

System.out.println("Statistics=" + stat);

System.out.println("map=" + map);

long hit = stat.getSecondLevelCacheHitCount();

long put = stat.getSecondLevelCachePutCount();

long miss = stat.getSecondLevelCacheMissCount();

System.out.println("put=" + put + ", hit=" + hit + ", miss=" + miss);

}

获取详细的二级缓存信息

// 二级缓存放入的次数
System.out.println("put="+statiistics.getSecondLevelCachePutCount());
// 二级缓存命中的次数
System.out.println("hit="+statiistics.getSecondLevelCacheHitCount());
// 二级缓存没有命中的次数
System.out.println("miss="+statiistics.getSecondLevelCacheMissCount());

put=1
hit=1
miss=1

14.3.3.4清理二级缓存

SessionFactory中有以下两个方法来清理缓存,其中第一个会清理所以的指定类型的缓存,第二个会清除指定类型,指定ID的缓存。

void evict(Class persistentClass) throwsHibernateException
void evict(Class persistentClass,Serializable
id)throws
HibernateException
由于SessionFactory.evict()已经过时,所以使用下面的方式来清理二级缓存。

Book b = (Book) s.get(Book.class, id);
System.out.println(b.getBookName());
HibernateSessionFactory.getSessionFactory().getCache().evictEntityRegion(Book.class);
运行结果如下:两次都没有命中。

put=2, hit=0, miss=2

15.其它

16.BUG摘录

16.1 ORA-00928: 缺失 SELECT 关键字

16.1.1异常描述

此错误出现在hibernate一对多双向关联的时候,此错误在mysql中不会出现。

Caused by: java.sql.BatchUpdateException: ORA-00928: 缺失 SELECT 关键字

at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:342)

at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10720)

at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)

at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)

at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)

16.1.2错误所在

此错误在于,在hibernate映射文件配置字段的时候使用了hibernate的关键字”date”。

下面“标红加粗“的就是问题所在,使用date作为字段名,且没有设置column,默认情况下表的字段叫”date”,这个关键字在oracle中是不能使用的。

<class name="Order" table="t_order_test">

<id name="orderId">

<generator class="native" />

</id>

<property name="date" />

<set name="items" inverse="true">

<key column="orderId" />

<one-to-many class="OrderItem" />

</set>

</class>

16.1.3解决方案

解决方案如下:

<property name="date"column="order_date"/>

16.2 java.sql.SQLException:Column '' not found.

16.2.1异常描述

Hibernate:

select

count(type_name) as counts ,

type_name

from

tb_book

where

type_name is not null

group by

type_name

order by

counts desc limit ?

Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not execute query

at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:92)

at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)

at org.hibernate.loader.Loader.doList(Loader.java:2536)

at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276)

at org.hibernate.loader.Loader.list(Loader.java:2271)

at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:316)

at org.hibernate.impl.SessionImpl.listCustomQuery(SessionImpl.java:1842)

at org.hibernate.impl.AbstractSessionImpl.list(AbstractSessionImpl.java:165)

at org.hibernate.impl.SQLQueryImpl.list(SQLQueryImpl.java:157)

at com.hbm3.sql.TestSqlQuery.queryBookTypeCountDesc(TestSqlQuery.java:60)

at com.hbm3.sql.TestSqlQuery.main(TestSqlQuery.java:77)

Caused by: java.sql.SQLException: Column '' not found.

at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:910)

at com.mysql.jdbc.ResultSet.findColumn(ResultSet.java:955)

at com.mysql.jdbc.ResultSet.getBigDecimal(ResultSet.java:1226)

at com.mchange.v2.c3p0.impl.NewProxyResultSet.getBigDecimal(NewProxyResultSet.java:3602)

at org.hibernate.type.descriptor.sql.DecimalTypeDescriptor$2.doExtract(DecimalTypeDescriptor.java:62)

at org.hibernate.type.descriptor.sql.BasicExtractor.extract(BasicExtractor.java:64)

at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:253)

at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:249)

at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:234)

at org.hibernate.loader.custom.CustomLoader$ScalarResultColumnProcessor.extract(CustomLoader.java:505)

at org.hibernate.loader.custom.CustomLoader$ResultRowProcessor.buildResultRow(CustomLoader.java:451)

at org.hibernate.loader.custom.CustomLoader.getResultColumnOrRow(CustomLoader.java:348)

at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:639)

at org.hibernate.loader.Loader.doQuery(Loader.java:829)

at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)

at org.hibernate.loader.Loader.doList(Loader.java:2533)

... 8 more

16.2.2错误所在

没有给返回值设置类型也就是下面被注释的地方。

session = HibernateSessionFactory.getSession();

String sql = "select count(type_name) as counts ,type_name from tb_book where type_name is not null group by type_name order by counts desc";

// 创建SQLQuery对象

// addScalar()设置SQL查询返回的数据的类型

// 有几个返回字段,就要设置几个字段。

SQLQuery query = session.createSQLQuery(sql);

//query.addScalar("counts", StandardBasicTypes.INTEGER);

//query.addScalar("type_name", StandardBasicTypes.STRING);

List<Object[]> list = (List<Object[]>) query.setFirstResult(0).setMaxResults(maxResults).list();

for (Object[] array : list) {

System.out.println("类别=" + array[1] + ", 数量=" + array[0]);

}

16.2.3解决方案

query.addScalar("counts", StandardBasicTypes.INTEGER);

query.addScalar("type_name", StandardBasicTypes.STRING);

16.3 Table'test.studentvo' doesn't exist

使用的环境:

数据库

Mysql6.0

Hibernate

hibernate-distribution-3.6.0.Final

<!-- 配置数据信息 -->

<property name="connection.driver_class"><![CDATA[com.mysql.jdbc.Driver]]></property>

<property name="connection.url"><![CDATA[jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8]]></property>

<property name="connection.username"><![CDATA[root]]></property>

<property name="connection.password"><![CDATA[mysql]]></property>

<!-- 配置C3P0 -->

<!-- 配置C3P0最大的链接数 -->

<property name="c3p0.max_size"><![CDATA[2]]></property>

<!-- 配置C3P0最小的链接数 -->

<property name="c3p0.min_size"><![CDATA[1]]></property>

<!-- 链接超时时间,单位:毫秒 -->

<property name="c3p0.timeout"><![CDATA[5000]]></property>

<!-- 数据库 方言 -->

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQLInnoDBDialect]]></property>

<!-- 指定建表方式 -->

<property name="hbm2ddl.auto"><![CDATA[create]]></property>

<!-- 是否显示sql语句 -->

<property name="show_sql"><![CDATA[true]]></property>

<!-- 是否格式sql语句 -->

<property name="format_sql"><![CDATA[true]]></property>

<!-- 是否给sql语句加注释 -->

<property name="use_sql_comments"><![CDATA[true]]></property>

<!-- 注册实体类 -->

<mapping resource="com/csuinfosoft/vo/StudentVo.hbm.xml" />

16.3.1 异常描述

22:27:50.982 [main] DEBUG o.h.util.JDBCExceptionReporter - could not insert: [com.csuinfosoft.vo.StudentVo] [/* insert com.csuinfosoft.vo.StudentVo */ insert into StudentVo (name, sex, age, address, telephone, qq) values (?, ?, ?, ?, ?, ?)]

com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: Table 'test.studentvo' doesn't exist

at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:936) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2870) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1573) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1665) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.Connection.execSQL(Connection.java:3124) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1149) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1400) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1314) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1299) ~[mysql-connector-java-5.0.3-bin.jar:na]

at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105) ~[c3p0-0.9.1.jar:0.9.1]

at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:94) ~[hibernate3.jar:3.6.0.Final]

at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:57) ~[hibernate3.jar:3.6.0.Final]

at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2327) [hibernate3.jar:3.6.0.Final]

at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2834) [hibernate3.jar:3.6.0.Final]

at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71) [hibernate3.jar:3.6.0.Final]

at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:320) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:129) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50) [hibernate3.jar:3.6.0.Final]

at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93) [hibernate3.jar:3.6.0.Final]

at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:713) [hibernate3.jar:3.6.0.Final]

at org.hibernate.impl.SessionImpl.save(SessionImpl.java:701) [hibernate3.jar:3.6.0.Final]

at org.hibernate.impl.SessionImpl.save(SessionImpl.java:697) [hibernate3.jar:3.6.0.Final]

at com.csuinfosoft.test.TestHibernate.main(TestHibernate.java:63) [bin/:na]

22:27:50.982 [main] WARN o.h.util.JDBCExceptionReporter - SQL Error: 1146, SQLState: 42S02

22:27:50.982 [main] ERROR o.h.util.JDBCExceptionReporter - Table 'test.studentvo' doesn't exist

16.3.2 错误所在

Mysql6的话,不能使用这个方言。应该使用MySQL5InnoDBDialect

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQLInnoDBDialect]]></property>

<!-- 指定建表方式 -->

16.3.3解决方案

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQL5InnoDBDialect]]></property>

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQL5Dialect]]></property>

<property name="dialect"><![CDATA[org.hibernate.dialect.MySQLDialect]]></property>



总结

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: