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.多对多关联关系
数据库中存储结构
通过中间表的形式来维护多对多的关系
中间表:
联合主键,主键分别为两个关联表的主键
正文
Hibernate1.主要内容
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 OJB2. 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> |
绿色部分的配置信息是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(); } |
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> |
<property name="dialect"><![CDATA[org.hibernate.dialect.MySQLInnoDBDialect]]></property> |
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" /> |
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> |
6.2.1 <hibernate-mapping>
配置项 | 用途 |
package | (可选)指定包名的前缀,如果隐射文件中没有类的包名,就用这个。 |
auto-import | (可选-默认为 true):指定我们是否可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。 |
default-lazy | (可选-默认为 true)是否延迟加载,默认是true |
default-access | (可选-默认为 property):Hibernate用来访问属性的策略。 |
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> |
配置项 | 用途 |
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 | 数据库表中的主键名称。 |
通过<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)" /> |
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可以。 |
注意:<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>联合起来使用 |
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(); } } } |
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等价于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.getCurrentSession()。这时候使用它是会报错的。
7.5.1 Session方法
要进行持久化操作的话,就得调用Session对象中的方法,来执行对应的增删改查。7.5.1.1 save()
save()方法完成对象的持久化操作。Serializable save(Object object)throws HibernateException |
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(); } } } |
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 |
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=? |
插入数据的时候会有这种情况,也就是某些字段没有值,在某人情况下插入的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 (?, ?, ?, ?, ?, ?) |
<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 |
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"> |
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.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(); |
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.javapublic class PersonList { private Integer id; private String name; private int age; // 集合属性,保留该对象关联的学校 private List<String> schools = new ArrayList<String>(); } |
<!-- 映射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(); |
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.javapublic class PersonArray { private Integer id; private String name; private int age; // 集合属性,保留该对象关联的学校 private String schools[] = null; } |
<!-- 映射List集合属性 --> <array name="schools" table="t_school_array"> <!-- 外键关联 --> <key column="person_id" /> <!-- 映射集合属性数据表的集合索引列 --> <index column="list_order" /> <!-- 映射保存集合元素的数据列 --> <element column="school_name" type="string" /> </array> |
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.javapublic class Student { private List<String> images; } |
<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); |
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(); |
<set name="images" table="t_student_images"lazy="false"> |
注意:这里不要写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集合映射
映射一个map8.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; } |
public class UserDetail implements Serializable { private int userDetailId; private String nickName; private String sex; private String address; private String email; private Date birthday; } |
在有”外键”的一端使用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> |
<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> |
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); |
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" /> |
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 |
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 |
session.delete(session.get(User.class, 1)); |
<many-to-one name="userDetail" column="user_detail_id"unique="true" cascade="save-update,delete" /> |
delete from t_user where user_id=? delete from t_user_detail where user_detail_id=? |
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; } |
public class Address implements Serializable { private int id; private String provice; private String city; private int zipCode; private Customer customer; } |
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> |
name | 表示Customer中要持久化的属性,这里为address。 |
unique | 这里设置成true表示:每一个customer对象都有唯一的Address,也就是customer与address一对一关联关系。 |
cascade | 这里设置成all,表示保存,更新与删除customer的时候会“级联”保存,更新,删除address对象。 |
class | 表示customer中要持久化的属性address的类型。 |
<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的关联。 |
这个的实体模型中主对象是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 |
通过设置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; } |
public class UserDetail implements Serializable { private int userDetailId; private String nickName; private String sex; private String address; private String email; private Date birthday; } |
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> |
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=? |
查询的时候发现有两个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; } |
public class Address11Tbl implements Serializable { private int addressId; private String addressDetail; } |
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> |
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 (?, ?) |
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; } |
public class Order implements Serializable { private int id; private Date orderDate; } |
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表。 |
<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> |
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=? |
由于这只设置了客户与订单的关联,没有设置订单与客户的关联,当插入的时候无法确定该订单属于哪个用户,所以在后面,才更新订单的外键,这个会在一对多双向时候讲解,怎么避免这种问题。
session.save(c); session.save(o1); session.save(o2); |
如果不设置 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); |
我们删除客户,不删除订单,我们来看下执行的效果。
session.delete(session.get(Customer.class, 1)); |
Hibernate: update t_order set c_id=null where c_id=? Hibernate: delete from t_customer where c_id=? |
<set name="orders" cascade="delete"> |
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=? |
<set name="orders" cascade="save-update, delete"> |
这里有一个问题就是,查询customer的时候会不会查询与之关联的Order。
Customer c = (Customer) session.get(Customer.class, 2); System.out.println(c.getName()); |
select customer0_.customer_id as customer1_1_0_, customer0_.c_name as c2_1_0_ from t_customer customer0_ where customer0_.customer_id=? |
<set name="orders" cascade="save-update, delete-orphan"lazy="false"> |
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=? |
<set name="orders" table="t_order" cascade="save-update" lazy="false" fetch="join"> |
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>(); } |
/** * 订单项 */ public class OrderItem implements Serializable { private int orderItemId; private int bookId; private float price; private Date createDate; } |
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> |
<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> |
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>(); } |
public class Address1nTbl implements Serializable { private int addressId; private String addressDetail; } |
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> |
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()………… } |
public class Order implements Serializable { private int id; private Date orderDate; private Customer customer; getter(),setter()………… } |
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> |
<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对象。 |
先保存客户,在保存订单。
// ////////////////////////// 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); |
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 |
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: 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 (?, ?) |
如果我们设置的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="false"
<many-to-one name="customer" column="customer_id" class="com.csuinfosoft.n1.Customer" not-null="false" lazy="false" /> |
session.save(o1); session.save(o2); session.save(c); |
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=? |
至于not-null的值到底是true,还是false的话,得由实际业务来决定,在这个例子中,订单是由每个用户发起的,所以这里应该把not-null设置为true。
9.1.6.1.5查询分析
这里讨论这种情况,查询order的时候,是否同时可以查询到与之关联的customer,这由lazy=""属性值确定,它有”false”,”proxy”两个值。
<many-to-one lazy="proxy" /> |
Order o = (Order) session.get(Order.class, 1); System.out.println(o.getOrderDate()); |
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" /> |
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=? |
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.javaimport 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; } |
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; } |
<mapping class="com.csuinfosoft.n1.DeptVo" /> <mapping class="com.csuinfosoft.n1.EmpVo" /> |
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; } |
public class Studentnn implements Serializable { private int studentId; private String name; private int age; private Set<Teachernn> teacheres = new HashSet<Teachernn>(); } |
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> |
<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> |
不设置级联的是不保存学生或者不保存老师都是不能保存成功的!下面设置了级联保存,可以实现只插入学生,不插入老师,也可以实现插入。
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; } |
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; } |
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。 |
<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的。 |
session.save(user); session.save(userDetail); |
update t_user set user_name=?, user_pwd=?, user_detail_id=? where user_id=? |
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" /> |
这里讨论先删除user,或者先删除detail,能否把关联的也删除。
session.delete(session.get(User.class, 1)); |
session.delete(session.get(UserDetail.class, 1)); |
配置删除级联
<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()); |
这里查询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; } |
public class Order implements Serializable { private int id; private Date orderDate; private Customer customer; } |
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> |
<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> |
先保存订单,再保存客户。
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语句,9.3.1中详细介绍。
inverse="true" |
只保存customer对象,而不保存Order这时候也可以进行保存,通过配置级联可以到的。
<set name="orders"cascade="save-update, delete-orphan" lazy="true"inverse="true"> |
session.save(c); // session.save(o1); // session.save(o2); |
这里要讨论的是查询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; } |
@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; } |
public class Teacher implements Serializable { private int id; private String name; private String sex; private Set<Student> students; } |
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。 |
<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> |
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.javapublic class User implements Serializable { private int id; private String name; private String password; private UserDetail userDetail; } |
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> |
<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" | 用它来建立外键约束。 |
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 |
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); |
insert intot_user(user_name, user_pwd) values (?, ?) insert into t_user_detail (user_age, user_sex, user_address, user_detail_id) values (?, ?, ?, ?) |
9.2.4.5级联操作
不设置级联的时候,下面的第一个值保存User,第二个可以实现保存。session.save(user); //session.save(userDetail); |
//session.save(user); session.save(userDetail); |
session.save(user); //session.save(userDetail); |
//session.save(user); session.save(userDetail); |
设置下面级联删除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>(); } |
public class Employee implements Serializable { private int empId; private String empName; private String sex; private int empAge; private float salary; private Department dept; } |
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> |
<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.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); // /////////////////////////////////////////// |
实体所产生的数据库表,与这些表的主外键就不分析了,以前都提到过,这里主要分析所打印的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=? |
如果先插入雇员,在插入部门信息,这样操作不会报错的,但是,打印的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=? |
e1.setDept(d); e2.setDept(d); |
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"> |
// 设置学生与老师的关系 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) | 把“一对多”的一的这端的清空,会删除多的一端 |
一般对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>(); } |
/** * 雇员表 */ 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; } |
这两个配置文件和以前的基本一样,只是分别添加了两个时间字段,这里不做描述。
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> |
<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> |
从上面的测试代码中的加粗的代码可以发现,设置了雇员与部门之间的关系,设置了部门与雇员的关系,但是保存的时候,发现雇员信息没有保存
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: 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: 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: 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.javapublic class People implements Serializable { private int peopleId; private String name; } |
public class Chinese extends People implements Serializable { private String language; } |
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 p = (People) query(People.class, 1); System.out.println(p.toString()); |
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 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()); } |
下面的写法是简写,这种代码十分紧凑,推荐这么写。
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(); |
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; } |
public class DeptVo implements Serializable { private Long deptno; private String dname; private String loc; private Set<EmpVo> emps = new HashSet<EmpVo>(); } |
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> |
<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> |
测试代码
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(); } } } |
注意:
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> |
<set name="emps" table="emp" cascade="all"> <key column="deptno" /> <one-to-many class="EmpVo" /> </set> |
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()); } |
查询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'"; |
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; |
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; |
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]); } |
看下面的左外连接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 |
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("###########################"); } |
12.1.4.2.7右外连接
准备,在EMP表里面添加一条没有部门的员工信息。
右外连接SQL语句
select d.dname, e.ename from dept d right join emp e on d.deptno = e.deptno |
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.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=? ########################### |
要实现迫切查询的话在原有左外连接上的改动很小:加一个”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("###########################"); } |
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 查询 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 | 只需要符合子查询结果的任意一个值即可。 |
查询与部门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 |
课堂作业:查询高于部门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]); } |
查询高于部门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) |
select ename from emp where sal>(select sal from emp where ename ='SMITH'); |
select ename from emp where deptno=(select deptno from dept where dname like'SALES'); |
select ename from emp a where HIREDATE<(select HIREDATE from emp where empno=a.mgr); |
select sal,ename from emp where sal>(select max(sal) from emp where deptno=30); |
select deptno,count(*),avg(a.sal),avg(sysdate-HIREDATE) from emp a group by deptno; |
select job,min(sal) from emp group by job ; |
select e.ename from emp e where e.empno in (select distinct e1.mgr from emp e1); |
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; |
select * from emp e, (select deptno,max(sal) sal from emp group by deptno) s where e.sal=s.sal; |
select * from emp where hiredate=(select min(hiredate) from emp); |
select avg(sal),count(*),sum(sal) ,deptno from emp group by deptno having avg(sal)>1900; |
select * from emp where length(ename)= 6; |
select * from emp where ename not like '%S%'; |
select * from emp where ename like '_M%'; |
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> |
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]); } |
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()); } |
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()); } |
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.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()); } |
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> |
备注: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); } |
这里会有两个问题:
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():清空缓存中的所有对象。 |
当运行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(); |
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" /> |
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> |
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 |
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> |
总结
相关文章推荐
- 史上最简单的Hibernate入门简介
- Hibernate缓存简介及领域对象的三种状态
- Hibernate核心接口简介
- 史上最简单的Hibernate入门简介 - 飞鸟的专栏 - CSDNBlog
- 史上最简单的Hibernate入门简介
- Hibernate注释简介
- Hibernate底层技术简介 JDBC编程
- Hibernate API简介
- Hibernate核心接口简介【转】
- 框架基础之Hibernate简介
- 博为峰JavaEE技术文章 ——MyBatis Hibernate 简介
- 博为峰JavaEE技术文章 ——MyBatis Hibernate 简介
- Hibernate 工作原理--简介
- hibernate简介(Session,几种状态,方法······等)概括全面,经典
- 史上最简单的Hibernate入门简介[Zone Yan.]最新版...网上的多数已经过期
- JavaEE经典试题(四) Hibernate简介
- Hibernate中Restrictions类的方法简介(转)
- Hibernate 一 JDBC简介
- Hibernate-Validation使用简介
- 史上最简单的Hibernate入门简介