SSH Chapter 04 Hibernate入门 笔记
Hibernate 知识体系图:
本章目标:
- 理解类和表的映射关系
- 掌握单表的增删改
- 掌握按主键查询
- 理解持久化对象的状态及其转换
技术内容:
前面我们已经学习了SQL Server
,MySQL
,Oracle
数据库,以及JDBC
标准,MyBatis
框架,从本章开始我们将学习持久化层的另一个框架产品一一Hibernate
,使用Hibernate
可以方便的完成持久化操作.
本章我们将学习搭建Hibernate
开发环境,编写Hibernate
配置文件及映射文件,以及使用Hibernate
完成对数据库单表的增,删,改,查操作.最后还将了解Hibernate
中持久化对象的三种状态及其相互转换的规律.
1 . Hibernate 介绍及其环境搭建
1.1 Hibernate 框架简介
Hibernate是一个开发源代码的对象关系映射框架,它对JDBC进行非常轻量级的对象封装,使得程序员可以随心所欲地使用对象编程思维来操纵数据库。Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序中使用,也可以在Servlet或JSP的Web应用中。
总之,可以简单的理解为Hibernate是基于JDBC技术基础上衍生而来,并在此基础上使得由原来直接操纵数据库变成直接操作映射数据表后生成的Java类,从而实现对象编程思维来操纵数据库。
Hibernate官网:http://hibernate.org/
;
http://sourceforge.net/projects/hibernate/files/hibernate-orm/5.4.2.Final/
1.2 Hibernate 框架的优缺点及使用场合
1 . Hibernate 框架的优点:
(1)、对象化。Hibernate
可以让开发人员以面向对象的思想来操作数据库。jdbc只能通过SQL语句将元数据传送给数据库,进行数据操作。而Hibernate
可以在底层对元数据和对象进行转化,使得开发者只用面向对象的方式来存取数据即可。
(2)、更好的移植性。Hibernate
使用xml或JPA的配置以及数据库方言等等的机制,使得Hibernate
具有更好的移植性,对于不同的数据库,开发者只需要使用相同的数据操作即可,无需关心数据库之间的差异。而直接使用JDBC就不得不考虑数据库差异的问题。
(3)、开发效率高。Hibernate
提供了大量的封装(这也是它最大的缺点),很多数据操作以及关联关系等都被封装的很好,开发者不需写大量的sql语句,这就极大的提高了开发者的开发效率。
(4)、缓存机制的使用。Hibernate
提供了缓存机制(session缓存,二级缓存,查询缓存),对于那些改动不大且经常使用的数据,可以将它们放到缓存中,不必在每次使用时都去查询数据库,缓存机制对提升性能大有裨益。
2 . Hibernate 框架的缺点:
(1)、由于对持久层封装过于完整,导致开发人员无法对SQL进行优化,无法灵活使用JDBC的原生SQL,Hibernate
封装了JDBC,所以没有JDBC直接访问数据库效率高。要使用数据库的特定优化机制的时候,不适合用Hibernate
.
(2)、框架中使用ORM原则,导致配置过于复杂,一旦遇到大型项目,比如300张表以上,配置文件和内容是非常庞大的,另外,DTO(数据传输对象)多如牛毛,性能和维护问题也随之而来
(3)、如果项目中各个表中关系复杂,表之间的关系很多,在很多地方把lazy
都设置false
,会导致数据查询和加载很慢,尤其是级联查询的时候。
(4)、Hibernate
在批量数据处理时有弱势,对于批量的修改,删除,不适合用Hibernate,这也是ORM框架的弱点
1.3 Hibernate 与 MyBatis 的比较:
相同点:
(1) Hibernate与MyBatis都是通过某个对象先读取并解析配置文件 ( 注意:MyBatis中对应的对象是:SqlSessionFactoryBuilder,而Hibernate中对应的对象为:Configuration) 的方式 , 由配置文件生成SessionFactory(MyBatis中对应的对象是:SqlSessionFactory),由SessionFactory 生成Session(MyBatis中对应的对象是:SqlSession),由Session来开启执行事务和SQL(Structured Query Language,结构化查询语言)语句
(2) Hibernate和MyBatis都支持JDBC(Java DataBase Connectivity,java数据库连接)和JTA(Java Transaction API,Java事务API(Application Programming Interface,应用程序编程接口))事务处理
(3) 基于ORM(Object Relational Mapping, 对象关系映射)思想解决了entity和数据库的映射问题
不同点:
(1) SQL方面:MyBatis
通过mapper.xml
维护映射结果,程序员手动编写sql
相比Hibernate
自动生成hql(hibernate sql)
更加灵活,sql
调优更加容易(Hibernate
因为更好的封装性,开发效率提高的同时,sql
语句调优要更费力,当然可以手动修改sql
来优化,但是同时也会影响开发效率);Hibernate
的hql
数据库移植性更好,体现在强壮性。Hibernate
在级联删除的时候效率低;数据量大, 表多的时候,基于关系操作会变得复杂
(2) 缓存方面:MyBatis
和Hibernate
都可以使用第三方缓存,而Hibernate
相比MyBatis
有更好的二级缓存机制;
一句话总结:
Mybatis:小巧、方便、高效、简单、直接、半自动化
Hibernate:强大、方便、高效、复杂、间接、全自动化
1.4 Hibernate 环境搭建:
Hibernate开发步骤:
- 下载并部署需要的项目jar文件
- 创建持久化类
- 创建对象-关系映射文件
- 创建Hibernate配置文件
- 通过Hibernate API编写访问数据库的代码
1. 下载需要的jar文件
在IDEA
中创建Maven
项目,导入如下依赖:
<!--hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.2.Final</version>
</dependency>
<!--hibernate默认使用 slf4j打印日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--ojdbc -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.1.0</version>
</dependency>
<!-- 若是mysql 数据库,需要加入mysql驱动 -->
<!--<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>-->
注意:
在Maven项目里是不能直接使用<dependency>
导入Oracle
相关的jar
包,需要手动添加到Maven本地仓库.我们在下载Oracle数据库的时候是自带了ojdbc的jar包的.Oracle 的ojdbc jar包所在的位置是:D:\app\Administrator\product\11.2.0\dbhome_1\jdbc\lib
下,cmd
窗口使用maven命令将ojdbc6打包,命令如下:
mvn install:install-file -Dfile=ojdbc6.jar -Dpackaging=jar -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.1.0
解释一下上面的命令:
-DgroupId、-DartifactId、-Dversion就是dependency的groupId、artifactId、version标签
不过version是当前所下载数据库的版本号
-Dfile就是我们第二步执行的将jar所拷贝到的目录,就是将该jar包加入Maven本地仓库
maven安装jar包到本地仓库的命令是:
mvn install:install-file -Dfile=jar包路径 -DgroupId=xxxx -DartifactId=xxxx -Dversion=xxx -Dpackaging=jar
Hibernate默认使用slf4j记录日志,如果使用log4j记录日志时 , 需要在pom.xml
文件中加入以下依赖:
<!--可以启用log4j2的日志依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
并在resources目录下创建log4j.properties 文件,内容 ,可参考源码目录\project\etc
目录下的log4j.properties文件 , 内容如下:
#
# Hibernate, Relational Persistence for Idiomatic Java
#
# License: GNU Lesser General Public License (LGPL), version 2.1 or later.
# See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
#
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info,stdout
log4j.logger.org.hibernate=info
#log4j.logger.org.hibernate=debug
### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug
### log just the SQL
#log4j.logger.org.hibernate.SQL=debug
### log JDBC bind parameters ###
#log4j.logger.org.hibernate.type=info
#log4j.logger.org.hibernate.type=debug
### log schema export/update ###
#log4j.logger.org.hibernate.tool.hbm2ddl=debug
### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug
### log cache activity ###
#log4j.logger.org.hibernate.cache=debug
### log transaction activity
#log4j.logger.org.hibernate.transaction=debug
### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug
### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
可参考hibernate-release-5.4.2.Final\project\etc
目录下的log4j.properties
文件 , 如图所示:
2. 创建 Hibernate 配置文件 hibernate.cfg.xml
Hibernate
配置文件主要用于配置数据库连接和Hibernate
运行时所需的各种特性.
在工程的resources
目录下添加Hibernate
配置文件(可在project/etc
目录下找到该示例文件),默认文件名为hibernate.cfg.xml
,或者在源码\documentation\quickstart\html_single\basic\src\test\resources
下找到示例文件hibernate.cfg.xml
.如图所示:
该文件需要配置数据库连接信息和Hibernate
的参数。
示例1:
<!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>
<!-- mysql连接字符串 -->
<!--<property name="connection.url">
jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8
</property>-->
<!-- Oracle连接字符串 -->
<property name="connection.url">
jdbc:oracle:thin:@localhost:1521/orcl
</property>
<!-- Oracle用户密码 -->
<property name="connection.username">scott</property>
<property name="connection.password">tiger</property>
<!-- MySQL用户密码 -->
<!--<property name="connection.username">root</property>-->
<!--<property name="connection.password">root</property>-->
<!-- hibernate预设的连接池设定 通常用于测试环境
发布到生产环境当中的时候,一般都是使用中间件的连接池-->
<!--<property name="connection.pool_size">1</property>-->
<!-- Oracle jdbc 驱动类 -->
<property name="connection.driver_class">
oracle.jdbc.driver.OracleDriver
</property>
<!-- MySQL jdbc 驱动类 -->
<!--<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>-->
<!-- MySQL 5 对应的方言类,以匹配其平台特性 -->
<!--<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>-->
<!-- Oracle 11g 对应的方言类,以匹配其平台特性 -->
<property name="dialect">
org.hibernate.dialect.Oracle10gDialect
</property>
<-- 如果是在一个单独的需要进行JDBC连接的java application中运行hibernate,则这样设置 -->
<!--<property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>-->
<!-- 指定org.hibernate.context.CurrentSessionContext.currentSession()方法所得到的Session由谁来跟踪管理.thread指Session由当前执行的线程来跟踪管理. -->
<property name="current_session_context_class">thread</property>
<!-- 显示Hibernate持久化操作所生成的SQL -->
<property name="show_sql">true</property>
<!-- 将SQL脚本进行格式化后再输出 -->
<property name="format_sql">true</property>
<!-- 指定实体类的所有的映射文件
映射文件位置,注意文件名必须包含其相对于classpath的全路径-->
<mapping resource="cn/hibernatedemo/entity/Dept.hbm.xml"/>
<!--<mapping resource="cn/smbms/pojo/Dept.hbm.xml"/>-->
</session-factory>
</hibernate-configuration>
其中几个常用的参数作用如下:
-
connection.url
: 表示数据库URL
, Oracle的URL
:jdbc:oracle:thin:@localhost:1521/orcl
.其中jdbc:oracle:thin:@
是固定写法,localhost
是IP
地址,1521
是端口号,orcl
是数据库的实例名,12c以后可以使用pdb数据库 -
connection.username
: 表示数据库用户名. -
connection.password
: 表示数据库用户密码 -
connection.driver_class
: 表示数据库驱动.oracle.jdbc.driver.OracleDriver
是Oracle
数据库的驱动类. -
dialect
: 配置Hibernate数据库方言,Hibernate可针对特殊的数据库进行优化. -
current_session_context_class
: 指定org.hibernate.context.CurrentSessionContext.currentSession()
方法所得到的Session
由谁来跟踪管理.thread
指Session
由当前执行的线程来跟踪管理. -
show_sql
: 是否把Hibernate
运行时的SQL语句输出到控制台,项目编码期间设置为true便于调试,项目部署完毕设置为false加快程序运行 -
format_sql
: 是否优化在日志和控制台输出的SQL语句,如果设置为true,在Hibernate运行输出到控制台的SQL语句排版清晰,更便于阅读。建议设置为true.
提示:
因为Hibernate
的配置属性较多,可查阅资料或者查看官网.
完成了Hibernate
的配置文件hibernate.cfg.xml
,接下来就要准备持久化类和映射文件了.
备注:
关于数据库连接参数的配置可参考源码位置\project\etc
目录下的hibernate.properties
文件,如图所示:
3. 创建持久化类和映射文件
持久化类是指其实例状态需要被Hibernate
持久化到数据库中的类. 在应用设计中,持久化类通常对应需求中的业务实体.
Hibernate
对持久化类的需求很少,它鼓励采用POJO
编程模式来实现持久化类,与POJO
类配合完成持久化工作是Hibernate
最期望的工作模式.Hibernate
要求持久化类必须具有一个无参数的构造方法.
下面首先以Oracle中scott
用户的部门表为例,定义部门持久化类,添加一个无参构造方法.
注意:该类实现了java.io.Serializable
接口,这不是Hibernate
要求的,为了在将持久化类用于数据传输等用途时,能够对其实例正确执行序列化操作,建议实现该接口.
示例2:
部门持久化类Dept.java
的代码,如下:
package cn.hibernatedemo.entity;
import java.io.Serializable;
/**
* 部门信息
*/
public class Dept implements Serializable {
//使用slf4j记录日志
//private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
private Byte deptNo;
private String deptName;
private String location;
@Override
public String toString() {
return "Dept{" +
"deptNo=" + deptNo +
", deptName='" + deptName + '\'' +
", location='" + location + '\'' +
'}';
}
public Byte getDeptNo() {
return deptNo;
}
public void setDeptNo(Byte deptNo) {
this.deptNo = deptNo;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
Dept
持久化类有一个deptNo
属性,用来唯一标识Dept
类的每个实例.deptNo
属性又称为id
属性.在Hibernate
中,这个id
被称为对象标识符(Object Identifier , OID)
,一个Dept
实例和DEPT
表中的一条记录对应.
创建持久化类后,还要"告诉"
Hibernate
,持久化类Dept
映射到数据库的哪个表,以及哪个属性对应到数据库表的哪个字段,这些都要在Dept
类的映射文件Dept.hbm.xml
中配置.
文件内容可参考源码目录documentation\quickstart\html_single\basic\src\test\java\org\hibernate\tutorial\hbm
的hbm的文件.如图所示:
示例3:
Dept.hbm.xml
的代码如下:
注意: 在Hibernate
中,映射文件通常与对应的持久化类同名,并以 “.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,每一个hbm.xml文件都有唯一的一个根元素,包含一些可选的属性
package:指定一个包前缀,如果在映射文档中(就是在class标签的那么属性里没有指定全类名)没有指定全限定的类名,就使用这个作为包名,-->
<hibernate-mapping package="cn.hibernatedemo.entity">
<!--class 元素用于指定类和表的映射
name:指定该持久化类映射的持久化类的类名
table:指定该持久化类映射的表名-->
<class name="Dept" table="DEPT">
<!--id标签:在对象-关系映射文件中, 元素用来设置对象标识符-->
<id name="deptNo" column="DEPTNO">
<!--generator 子元素用来设定标识符生成器.
assigned:主键由应用程序负责生成,无须Hibernate参与.这是没有<generator>元素时的默认生成策略.
increment:对类型为long,short或int的主键,以自动增长的方式生成主键的值.主键按顺序递增,增量为1.由hibernate生成,hibernate先查询主键的最大值,然后在此基础上+1作为插入操作时的主键生成策略
identity:采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL 中的主键生成机制。
sequence:采用数据库提供的 sequence 机制生成主键。如 Oralce 中的Sequence,属性为的name为sequence_name。
native:由 Hibernate 根据使用的数据库自行判断采用何种主键生成策略,即由使用的数据库生成主键的值,指定name属性为sequence_name的时候使用序列
-->
<generator class="assigned"/>
</id>
<!-- property:定义持久化类中的属性和数据库表中的字段对应关系.常用属性如下:
name:表示持久化类属性的名称,和属性的访问器相匹配.
type:表示持久化类属性的类型.
column:表示持久化类属性对应的数据库表字段的名称,也可以在子元素column中指定-->
<property name="deptName" type="string" column="DNAME"/>
<property name="location" type="string" >
<!--column元素:用于指定其父元素代表的持久化类属性,所对应的数据库表中的字段.其常用属性如下:
name:表示字段名称
length:表示字段长度
not-null:设定是否不能为null,设置为true表示不能为null-->
<column name="LOC"/>
</property>
</class>
</hibernate-mapping>
经验: 如果表名或字段名是数据关键字,或包含空格等特殊字符,可以使用反单引号( ` ) 进行约束.
**为了避免不必要的错误,建议使用反单引号( ` )对数据表名和字段名统一进行约束 **
示例3中Dept.hbm.xml
定义了Dept
类到数据库表DEPT
的映射.其中个元素含义如下:
- class:元素用于指定类和表的映射,常用属性如下:
- name:指定该持久化类映射的持久化类的类名
- table:指定该持久化类映射的表名
- id:在对象-关系映射文件中, 元素用来设置对象标识符
- name:表示持久化类属性的名称,和属性的访问器相匹配
- type:表示持久化类属性的类型
- column:表示持久化类属性对应的数据库表字段名称,也可以在子元素column中执行.
注意:这里所指的持久化类属性的名称,是指符合JavaBean名称规范的属性名称,即通过getter和setter访问器得到的默认属性名称.如无特别说明,书中涉及的属性名称均属此类
- generator : id元素的子元素,用来指定主键的生成策略.常用的属性及子元素如下:
- class:属性用来指定具体主键策略
- param 元素用来传递参数.示例3使用的主键生成策略是assigned,不需要配置param元素
常用的主键生成策略如下:
-
assigned
: 此方式由自己在应用程序中手动指定,不推荐使用 -
increment
,适用于 short 、int 、long 类型的主键,这是 Hibernate 的自动增长机制,原理其实很简单。它发送的是一条这样的 sql 语句select max(id) from xx
,找到最大的主键,然后让 id + 1 ,再作为下一条记录的主键。也就是说该策略要求数据库也要支持自增长,比如 Oracle 就是不行的。而且使用该方式存在线程安全问题,如果两个线程同时读取到的是同一个 id ,这就会造成混乱,典型的脏数据问题。 -
identity
,适用于 short 、int 、long 类型的主键,它采用的是数据库提供的主键自动增长机制,所以只能用在支持自增长机制的数据库中,比如 MySQL、MSSqlServer ,Oracle 是不支持主键自增长的。 -
sequence
,适用于序列的方式生成主键,比如 Oracle 数据库就支持这种方式,MySql 就不支持。 -
native
,表示此 POJO 对象的对象标识符OID 由具体使用的数据库生成。**POJO 的标识符类型只能是整数类型,比如 short 、int 、long 类型的数据。**一般默认用这个作为主键生成策略!要注意它并不是一种具体的主键生成策略,它是根据核心配置文件中的方言来动态决定调用那种生成策略,比如identity
和sequence
。
- property : 定义持久化类中的属性和数据库表中的字段对应关系.常用属性如下:
- name:表示持久化类属性的名称,和属性的访问器相匹配.
- type:表示持久化类属性的类型.
- column:表示持久化类属性对应的数据库表字段的名称,也可以在子元素column中指定
- column元素:用于指定其父元素代表的持久化类属性,所对应的数据库表中的字段.其常用属性如下:
- name:表示字段名称
- length:表示字段长度
- not-null:设定是否不能为null,设置为true表示不能为null
若是IDEA
的maven项目,需要在本项目的pom.xml
文件加入以下内容:
<build>
<resources>
<resource>
<!--filtering是否开启替换标签,若文件中有类似${key}这样的配置,
就会根据maven的配置进行覆盖,让其使用真实值来填写
true表示开启替换,false表示不开启替换,无此标签表示不开启替换-->
<filtering>false</filtering>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
2 . 使用 Hibernate 完成 持久化操作
Hibernate
操作数据如图:
使用Hibernate操作数据库包括7个步骤:
-
读取并解析配置文件以及映射文件
Configuration configuration = new Configuration().configure();
根据默认位置的
Hibernate
配置文件中的信息,构建Configuration
对象.Configuration
负责管理Hibernate
的配置信息. -
根据配置文件和映射问中的信息,创建
SessionFactory
对象.SessionFactory sessionFactory = configuration.buildSessionFactory();
Configuration
对象会根据当前的数据库配置信息,构造SessionFactory
对象.SessionFactory
对象一旦构造完毕,Configuration
对象的任何变更将不会影响已经创建的SessionFactory
对象.如果Hibernate
配置信息有改动,那么就需要基于改动后的Configuration
对象重新构建一个SessionFactory
对象. -
打开
Session
.Session session = sessionFactory.getCurrentSession();//或者使用sessionFactory.openSession()
SessionFactory
负责创建Session
对象.
Session
是Hibernate
持久化操作的基础.Session
作为贯穿Hibernate
持久化管理器的核心,提供了众多持久化方法,如save(),delete(),update(),get(),load()
等.通过这些方法,即可透明的完成对象的增,删,改,查(CURD). -
开始一个事务.
session.beginTransaction();
-
执行数据库操作.
session.save(user);//保存操作
-
结束事务.
session.getTransaction().commit();//提交事务
或者
session.getTransaction().rollback();//回滚事务
-
如果是通过
SessionFactory
的openSession()
方法获取Session
对象,则需关闭session
.session.close();
如果在
Hibernate
配置文件中将参数current_session_context_class
设置为thread
,并采用SessionFactory
对象的getCurrentSession()
方法获取Session
对象,则不需要执行session.close()
方法,通过这种方式获得的Session
对象会在关联的事务结束(提交或回滚)时自动关闭.
示例4:
Hibernate5
之后也可以使用如下代码进行测试:
//可以使用slf4j记录日志
//private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
//创建服务注册对象
StandardServiceRegistry registry = new
StandardServiceRegistryBuilder()
.configure() // configures settings from hibernate.cfg.xml
.build();
//根据服务注册对象创建一个元数据资源集,同时构建元数据对象并调用相应的方法生成SessionFactory对象
SessionFactory sessionFactory =
new MetadataSources
(registry).buildMetadata().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Dept dept = new Dept();
dept.setDeptno(50);
dept.setDname("CODE");
dept.setLoc("CAL");
session.save(dept)
session.getTransaction().commit();
经验:在项目开发过程中,通常使用工具类管理SessionFactory
和Session
,参考代码如下:
/**
* Hibernate工具类
*/
public class HibernateUtil {
private static SessionFactory sessionFactory;
private HibernateUtil(){}
static{
//创建服务注册对象
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure() // configures settings from hibernate.cfg.xml
.build();
try {
//根据服务注册对象创建一个元数据资源集,
//同时构建元数据对象并调用相应的方法生成SessionFactory对象
sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory();
}
catch (Exception e) {
// The registry would be destroyed by the SessionFactory, but we had trouble building the SessionFactory
// so destroy it manually.
StandardServiceRegistryBuilder.destroy( registry );
}
}
public static Session currentSession(){
return sessionFactory.getCurrentSession();
}
}
SessionFactory接口获得Session(会话)实例有两种方式,一种是通过openSession(),另外一种是通过getCurrentSession()。这里讨论获得Session两种方式的区别。
1、openSession() 是获取一个新的session;而getCurrentSession() 是获取和当前线程绑定的session,换言之,在同一个线程中,我们获取的session是同一个session,这样可以利于事务控制。
2、使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置:
如果使用的是本地事务(jdbc事务)
<property name="hibernate.current_session_context_class">thread</property>
如果使用的是全局事务(jta事务)
<property name="hibernate.current_session_context_class">jta</property>
3、通过 getCurrentSession() 获取的session在commit或rollback时会自动关闭;通过openSession()获取的session则必须手动关闭。
4、通过getCurrentSession() 获取sesssion进行查询需要事务提交;而通过openSession()进行查询时可以不用事务提交。
在开发中如何选择openSession()和 getCurrentSession():
- 如果需要在同一线程中,保证使用同一个Session,则使用getCurrentSession()
- 如果在一个线程中,需要使用不同的Session,则使用opentSession()
说明一下jdbc和jta方式事务管理的区别:
JDBC事务由Connnection管理,也就是说,事务管理实际上是在JDBC Connection
中实现。事务周期限于Connection的生命周期之内
JTA 事务管理则由 JTA 容器实现,JTA 容器对当前加入事务的众多Connection 进
行调度,实现其事务性要求。JTA的事务周期可横跨多个JDBC Connection生命周期。
2.1 使用Hiberante
实现按主键查询
在进行修改或删除操作时,应先加载对象,然后再执行修改或删除操作.Hibernate
提供了两种方法按照主键加载对象:get()
和load()
方法.
- Object get(Class clazz,Serializable id)
- Object load(Class clazz,Serializable id)
虽然两个方法都能加载对象,但是它们是由区别的.下面以部门表为例.通过示例来讲讲解它们的区别:
1.load()方法:
当使用load方法来得到一个对象时,此时hibernate
会使用延迟加载的机制来加载这个对象,即:
==当我们使用session.load()
方法来加载一个对象时,此时并不会发出sql语句,当前得到的这个对象其实是一个代理对象,这个代理对象只保存了实体对象的id值,只有当我们要使用这个对象,得到其它属性时,这个时候才会发出sql语句,从数据库中去查询我们的对象。==示例如下:
示例5:
Session session = HibernateUtil.currentSession();
session.beginTransaction();
/** 通过load的方式加载对象时,会使用延迟加载机制,此时并不会发出sql语句,只有当我们需要使用的时候才会从数据库中去查询
*/
Dept dept = session.load(Dept.class,(byte)10);
我们看到,如果我们仅仅是通过load来加载我们的Dept对象,此时从控制台我们会发现并不会从数据库中查询出该对象,即并不会发出sql语句,但如果我们要使用该对象时:
Session session = HibernateUtil.currentSession();
session.beginTransaction();
/** 通过load的方式加载对象时,会使用延迟加载机制,此时并不会发出sql语句,只有当我们需要使用的时候才会从数据库中去查询
*/
Dept dept = session.load(Dept.class,(byte)10);
System.out.println(dept);
此时我们看到控制台会发出了sql查询语句,会将该对象从数据库中查询出来:
Hibernate:
select
dept0_.DEPTNO as DEPTNO1_0_0_,
dept0_.DNAME as DNAME2_0_0_,
dept0_.LOC as LOC3_0_0_
from
DEPT dept0_
where
dept0_.DEPTNO=?
Dept{deptNo=10, deptName='ACCOUNTING', location='NEW YORK'}
这个时候我们可能会想,那么既然调用load()
方法时,并不会发出sql语句去从数据库中查出该对象,那么这个Dept
对象到底是个什么对象呢?
其实这个Dept
对象是我们的一个代理对象,这个代理对象仅仅保存了deptNo
这个属性:
Session session = HibernateUtil.currentSession();
session.beginTransaction();
/** 通过load的方式加载对象时,会使用延迟加载机制,此时并不会发出sql语句,只有当我们需要使用的时候才会从数据库中去查询
*/
Dept dept = session.load(Dept.class,(byte)10);
System.out.println(dept.getDeptNo());
Console:10
我们看到,如果我们只打印出这个Dept
对象的deptNo
值时,此时控制台会打印出该deptNo
值,但是同样不会发出sql语句去从数据库中去查询。
这就印证了我们的这个Dept
对象仅仅是一个保存了deptNo
的代理对象,但如果我需要打印出Dept
对象的其他属性值时,这个时候会不会发出sql语句呢?答案是肯定的:
Session session = HibernateUtil.currentSession();
session.beginTransaction();
/** 通过load的方式加载对象时,会使用延迟加载机制,此时并不会发出sql语句,只有当我们需要使用的时候才会从数据库中去查询
*/
Dept dept = session.load(Dept.class,(byte)10);
System.out.println(dept.getDeptNo());
//如果此时要得到dept其他属性,则会从数据库中查询
System.out.println(dept.getDeptName());
此时我们看控制台的输出:
10
Hibernate:
select
dept0_.DEPTNO as DEPTNO1_0_0_,
dept0_.DNAME as DNAME2_0_0_,
dept0_.LOC as LOC3_0_0_
from
DEPT dept0_
where
dept0_.DEPTNO=?
ACCOUNTING
2.get()方法:
相对于load()
的延迟加载方式,get()
就直接的多,当我们使用session.get()
方法来得到一个对象时,不管我们使不使用这个对象,此时都会发出sql语句去从数据库中查询出来:
示例6:
Session session = HibernateUtil.currentSession();
session.beginTransaction();
/**通过get方法来加载对象时,不管使不使用该对象,都会发出sql语句,从数据库中查询
*/
Dept dept = session.load(Dept.class,(byte)10);
此时我们通过get
方式来得到Dept
对象,但是我们并没有使用它,但是我们发现控制台会输出sql的查询语句:
Hibernate:
select
dept0_.DEPTNO as DEPTNO1_0_0_,
dept0_.DNAME as DNAME2_0_0_,
dept0_.LOC as LOC3_0_0_
from
DEPT dept0_
where
dept0_.DEPTNO=?
因此我们可以看到,使用load
的加载方式比get
的加载方式性能要好一些,因为load
加载时,得到的只是一个代理对象,当真正需要使用这个对象时再去从数据库中查询。
3.使用get和load时的一些小问题
当了解了load
和get
的加载机制以后,我们此时来看看这两种方式会出现的一些小问题:
①如果使用get
方式来加载对象,当我们试图得到一个id不存在的对象时,此时会报NullPointException
的异常:
Session session = HibernateUtil.currentSession();
session.beginTransaction();
/**当通过get方式试图得到一个id不存在的user对象时,此时会报NullPointException异常
*/
Dept dept = session.get(Dept.class,(byte)15);
System.out.println(dept.getDeptNo());
System.out.println(dept.getDeptName());
此时我们看控制台的输出信息,会报空指针的异常:
Hibernate:
select
dept0_.DEPTNO as DEPTNO1_0_0_,
dept0_.DNAME as DNAME2_0_0_,
dept0_.LOC as LOC3_0_0_
from
DEPT dept0_
where
dept0_.DEPTNO=?
java.lang.NullPointerException...
这是因为通过get
方式我们会去数据库中查询出该对象,但是这个id
值不存在,所以此时Dept
对象是null
,所以就会报NullPointException
的异常了。
②如果使用load
方式来加载对象,当我们试图得到一个id
不存在的对象时,此时会报ObjectNotFoundException
异常:
Session session = HibernateUtil.currentSession();
session.beginTransaction();
/** 当通过load方式试图得到一个deptNo不存在的Dept对象时,此时会报ObjectNotFoundException异常
*/
Dept dept = session.load(Dept.class,(byte)15);
System.out.println(dept.getDeptNo());
//如果此时要得到dept其他属性,则会从数据库中查询
System.out.println(dept.getDeptName());
我们看看控制台的输出:
15
Hibernate:
select
dept0_.DEPTNO as DEPTNO1_0_0_,
dept0_.DNAME as DNAME2_0_0_,
dept0_.LOC as LOC3_0_0_
from
DEPT dept0_
where
dept0_.DEPTNO=?
org.hibernate.ObjectNotFoundException:..
为什么使用load
的方式和get
的方式来得到一个不存在的对象报的异常不同呢??
其原因还是因为load
的延迟加载机制,使用load
时,此时的Dept
对象是一个代理对象,仅仅保存了当前的这个id
值,当我们试图得到该对象的deptName
属性时,这个属性其实是不存在的,所以就会报出ObjectNotFoundException
这个异常了。
③org.hibernate.LazyInitializationException
异常:
示例7:
DeptDao.java
代码如下:
/**
* 部门数据访问层
*/
public class DeptDao {
public Dept loadDept(Serializable deptNo){
return HibernateUtil.currentSession().load(Dept.class,deptNo);
}
}
DeptBiz.java
代码如下:
/**
* 部门业务层
*/
public class DeptBiz {
private DeptDao deptDao = new DeptDao();
public Dept findDeptById(Byte id){
Transaction transaction = null;
Dept dept = null;
try {
transaction = HibernateUtil.currentSession().beginTransaction();
dept = deptDao.loadDept(id);
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return dept;
}
}
测试类中方法如下:
private DeptBiz deptBiz = new DeptBiz();
@Test
public void testFindDeptById(){
Dept dept = deptBiz.findDeptById(new Byte("10"));
System.out.println(dept.getDeptNo());
System.out.println(dept.getDeptName());
}
模拟了一个DeptBiz
这样的对象,然后我们在测试用例里面来通过load加载一个对象,此时我们发现控制台会报LazyInitializationException
异常
org.hibernate.LazyInitializationException: could not initialize proxy [cn.hibernatedemo.entity.Dept#10] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:169)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
这个异常是什么原因呢??
还是因为load
的延迟加载机制,当我们通过load()
方法来加载一个对象时,此时并没有发出sql
语句去从数据库中查询出该对象,当前这个对象仅仅是一个只有id
的代理对象,我们还并没有使用该对象,但是此时我们的session
已经关闭了,所以当我们在测试用例中使用该对象时就会报LazyInitializationException
这个异常了。
所以以后我们只要看到控制台报LazyInitializationException
这种异常,就知道是使用了load
的方式延迟加载一个对象了,解决这个的方法有两种,一种是将load
改成get
的方式来得到该对象,另一种是在表示层
来开启我们的session
和关闭session
。
至此,hibernate
的两种加载方式get
和load
已经分析完毕!!!
2.2 使用Hibernate
实现数据库的 增 删 改 操作
1. 使用Hibernate
实现增加部门记录
示例8:
封装BaseDao.java
, 用来获取session
,代码如下:
/**
* Dao层基类
*/
public class BaseDao {
protected Session currentSession(){
return HibernateUtil.currentSession();
}
}
DeptDao.java
中的关键代码如下:
public void save(Dept dept){
currentSession().save(dept);//保存指定的Dept对象
}
DeptBiz.java
中的关键代码如下:
/**
* 部门业务层
*/
public class DeptBiz {
private DeptDao deptDao = new DeptDao();
public void addNewDept(Dept dept){
Transaction transaction = null;
try {
transaction = deptDao.currentSession().beginTransaction();
deptDao.save(dept);//调用dao保存Dept对象的数据
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
}
}
测试方法代码如下:
@Test
public void testAddNewDept(){
//构建测试数据
Dept dept = new Dept();
dept.setDeptNo((byte)11);
dept.setDeptName("测试部");
dept.setLocation("东区");
DeptBiz deptBiz = new DeptBiz();
deptBiz.addNewDept(dept);
}
2. 使用Hibernate
实现部门的修改和删除
示例9:
DeptDao.java
中的关键代码如下:
public Dept load(Serializable id){
return currentSession().load(Dept.class,id);
}
DeptBiz.java
中关键代码如下:
public void updateDept(Dept dept){
Transaction transaction =
null;
try {
transaction =
deptDao.currentSession().beginTransaction();
Dept deptToUpdate = deptDao.load(dept.getDeptNo());
//更新部门数据
deptToUpdate.setDeptName(dept.getDeptName());
deptToUpdate.setLocation(dept.getLocation());
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
}
测试代码如下:
@Test
public void testUpdateDept(){
//构建测试数据
Dept dept = new Dept();
dept.setDeptNo((byte)11);
dept.setDeptName("质管部");
dept.setLocation("东区");
DeptBiz deptBiz = new DeptBiz();
deptBiz.updateDept(dept);
}
示例10:
Dept.java
中的关键代码如下:
//删除指定的Dept对象
public void delete(Dept dept){
currentSession().delete(dept);
}
DeptBiz.java
中关键代码如下:
public void deleteDept(Byte id) {
Transaction transaction =
null;
try {
transaction =
deptDao.currentSession().beginTransaction();
//通过load方法加载要删除的部门对象
Dept deptToDelete = deptDao.load(id);
deptDao.delete(deptToDelete);//删除部门数据
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
}
测试方法代码如下:
@Test
public void testDeleteDept(){
deptBiz.deleteDept((byte)11);
}
3 . Hibernate 中 Java对象的三种状态
3.1 Java对象的三种状态
当应用通过调用Hibernate API
与框架发生交互时,需要从持久化的角度关注应用对象的生命周期。持久化生命周期是Hibernate
中的一个关键概念,正确地理解生命周期,可以更好地了解Hiberate
的实现原理,掌握Hibernte
的正确用法。Hibernate
框架通过Session
来管理Java
对象的状态,在持久化生命周期中,Java
对象存在着如下三种状态。
1 . 瞬时状态( Transient)
瞬时状态又称临时状态.通过new创建对象后,对象并没有立刻持久化,它并未与数据库中的数据有任何的关联,此时Java
对象的状态为瞬时状态。
Session
对于瞬时状态的Java
对象是一无所知的,当对象不再被其他对象引用时,它的所有数据也就丢失了,对象将会被Java
虚拟机按照垃圾回收机制处理。
2 . 持久状态( Persistent)
当对象与Session
关联,被Session
管理时,它就处于持久状态。处于持久状态的对象拥有数据库标识(数据库中的主键值)。那么,对象是什么时候与Session
发生关联的呢?有两种方式:
第一种,通过Session
的查询接口,或者get()
方法,或者load()
方法从数据库中加载对象的时候,加载的对象是与数据库表中的一条记录关联的,此时对象与加载它的Session
发生关联;
第二种,瞬时状态的对象,通过调用Session
的save()
方法或saveOrUpdate()
方法时,Java
对象也与Session
发生关联。对于处于持久状态的对象,Session
会持续跟踪和管理它们,如果对象的内部状态发生了任何变更,Hibernate
会选择合适的时机(如事务提交时)将变更固化到数据库中。
3 . 游离状态( Detached)
处于持久状态的对象,脱离与其关联的Session
的管理后,对象就处于游离状态。处于游离状态的对象,Hibernate
无法保证对象所包含的数据与数据库中的记录一致,因为Hibernate
已经无法感知对该对象的任何操作。
Session
提供了两个方法(update()
、merge()
),将处于游离状态的对象,与—个新的Session
发生关联。这时,对象的状态就从游离状态重新转换为持久状态。
三种状态的区别:
-
是否在Session对象管理下,如果在,则一定是持久态,否则是临时态或者游离态。
-
是否持有OID,没有OID(或者OID为空或者OID在数据库里没有对应的记录)的是临时状态,有OID的是持久化状态或者游离状态。
临时状态(Transient) | 持久化状态(Persistent) | 游离状态(Detached) | |
---|---|---|---|
是否处于Session缓存中 | × | √ | × |
数据库中是否有对应记录 | × | √ | √ |
是否有OID | × | √ | √ |
3.2 三种状态之间的转换
在Hibemate应用中,不同的持久化操作会导致对象状态的改变。下图描述了对象状态的转换:
使用new
关键字构建对象,该对象的状态是瞬时状态。
1. 瞬时状态转为持久状态
使用Session
对象的save()
或saveOrUpdate()
方法保存对象后,该对象的状态由瞬时状态转换为持久状态。
使用Session
对象的get()
或load()
方法获取对象,该对象的状态是持久状态。
案例演示如下:
/**
* 演示三种状态对象之间的转换 临时转持久
*/
@Test
public void test_03(){
try(Session session = HibernateUtil.currentSession()){
Transaction tx = session.beginTransaction();
//临时状态的对象
Dept dept = new Dept();
dept.setDeptName("研发部");
dept.setDeptNo(50);
dept.setLocation("东区");
//hibernate对于此状态对象只有一种管理手段 就是调用save方法
//将它转换为持久状态的对象
session.save(dept);
//此后都为持久状态对象 但凡属性发生一丝一毫的变化
// hibernate都会调用其相应的方法对此对象进行管理
dept.setLocation("西区");
tx.commit();
}
}
2. 持久状态转为瞬时状态
执行Session
对象的delete()
方法后,对象由原来的持久状态变为瞬时状态,因为此时该对象没有与任何的数据库数据关联。
演示案例如下:
/**
* 演示三种状态对象之间的转换 持久转临时
*/
@Test
public void test_05(){
try(Session session = HibernateUtil.currentSession()){
Transaction tx = session.beginTransaction();
//持久状态的对象
Dept dept = session.get(Dept.class,50);
session.delete(dept);//持久转临时
//临时状态对象,因此即使该对象的属性发生变化
//hibernate也不会理会的
dept.setLocation("西区");
tx.commit();
}
}
3 . 持久状态转为游离状态
执行了Session
对象的evict()
–清除单个对象、clear()-
-清除所有对象或close()
方法,对象由原来的持久状态转为游离状态。
举例说明如下:
/**
* 演示三种状态对象之间的转换 持久转游离
*/
@Test
public void test_06(){
try(Session session = HibernateUtil.currentSession()){
Transaction tx = session.beginTransaction();
//持久状态的对象
Dept dept = session.get(Dept.class,50);
session.clear();//持久转游离
//游离状态对象
//此后该对象发生的任何变化 都跟hibernate无关
dept.setLocation("西区");
tx.commit();
}
}
4. 游离状态转为持久状态
重新获取Session
对象,执行Session
对象的update()
或saveOrUpdate()
方法,对象由游离状态转为持久状态,该对象再次与Session对象相关联。
举例说明如下:
/**
* 演示三种状态对象之间的转换 游离转持久
*/
@Test
public void test_04(){
try(Session session = HibernateUtil.currentSession()){
Transaction tx = session.beginTransaction();
//游离状态的对象(id必须在数据库中存在)
Dept dept = new Dept();
dept.setDeptNo(50);
//hibernate对于此状态对象只有多种管理手段
// 最经典的就是调用get或load方法
//将它转换为持久状态的对象 返回的对象为持久态对象
dept = session.get(Dept.class,dept.getDeptNo());
//此后都为持久状态对象 但凡属性发生一丝一毫的变化
// hibernate都会调用其相应的对此对象进行管理
dept.setLocation("东区");
tx.commit();
}
}
5. 游离状态转为瞬时状态
执行Session
对象的delete()
方法,对象由游离状态转为瞬时状态。
处于瞬时状态或游离状态的对象不再被其他对象引用时,会被Java虚拟机按照垃圾回收机制处理。
代码举例说明如下:
/**
* 演示三种状态对象之间的转换 游离转瞬时
*/
@Test
public void test_07(){
try(Session session = HibernateUtil.currentSession()){
Transaction tx = session.beginTransaction();
//游离状态的对象
Dept dept = new Dept();
dept.setDeptNo(50);
session.delete(dept);//游离转瞬时
dept.setDeptName("南区");
tx.commit();
}
}
4 . 脏检查及刷新缓存机制
Session
是Hibernate
向应用程序提供的操作数据库的主要接口,它提供了基本的保存,更新,删除和加载Java对象的方法,Session
具有一个缓存,可以管理和追踪所有持久化对象,对象和数据库中的相关记录对应,在某些时间点,Session
会根据缓存中对象的变化来执行相关SQL语句,将对象包含的变化数据更新到数据库中,这一过程成为刷新缓存.
换句话说:就是将数据库同步为Session
缓存一致,这一过程为刷新缓存.
4.1 脏检查
在Hibernate
,数据前后发生变化的对象,称为脏对象,如一下代码所示:
tx = session.beginTransaction();
//获取部门对象,dept对象处于持久状态
Dept dept = session.load(Dept.class,new Byte("10"));
//修改后,部门信息和之前不同,此时dept对象称为所谓的脏对象
dept.setDname("质管部");//脏对象
//提交事务
tx.commit();
Session
到底是如何进行脏检查的呢?
以上代码中dept
对象处于持久状态, 当dept
对象被加入到Session
缓存中时,Session
会为dept
对象的值类型的属性复制一份快照。
当Session
清理缓存时,会先进行脏检查,即比较dept
对象的当前属性与它的快照,来判断dept
对象的属性是否发生了变化,如果发生了变化,就称这个对象是“脏对象”,Session
会根据脏对象的最新属性来执行相关的SQL
语句,从而同步更新数据库。
4.2 刷新缓存机制
需要注意的是:当Session
缓存中对象的属性每次发生了变化,Session
并不会立即清理缓存和执行相关的SQL update语句,而是在特定的时间点才清理缓存,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,以便减少访问数据库的次数,从而提高应用程序的数据访问性能。
在默认情况下,Session
会在以下时间点清理缓存。
- 当应用程序调用
org.hibernate.Transaction
的commit()
方法的时候.
commit()
方法先清理缓存,然后再向数据库提交事务。Hibernate
之所以把清理缓存的时间点安排在事务快结束时,一方面是因为可以减少访问数据库的频率,还有一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间。 - 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,就会清理缓存,使得
Session
缓存与数据库已经进行了同步,从而保证查询结果返回的是正确的数据。 - 当应用程序显示调用
Session
的flush()
方法的时候。
5 . 更新数据的方法
Hibernate
中的Session
提供了多种更新数据的方法,如update()
,saveOrUpdate()
,merga()
方法.
(1) update() 方法:
用于将游离状态的对象恢复为持久状态,同时进行数据库的更新操作.当参数对象的OID
为null
时会报异常.
(2) saveOrUpdate() 方法:
同时包含了save()
与update()
方法的功能.如果入参是瞬时状态的对象,就调用save()
方法;如果入参是游离状态的对象,则调用update()
方法.
(3) merge() 方法:
首先从参数说明来看,merge()
的参数应该是一个处于托管状态的实例对象,而返回值则是一个持久化对象。
但是这里的参数并不是一定要是托管状态的对象,它还可以是瞬态和持久化的实例对象。正因如此,才使merge方法变得复杂化。
经代码检验从merge方法产生的效果来看,它和saveOrUpdate方法相似,因此虽然上面提到是因为参数状态的不同造成复杂化,但是这里我们并不打算分参数的不同状态来理解merge,而是根据参数有无id或id是否已经存在来理解merge。这样更容易理解,而且从执行他们两个方法而产生的sql语句来看是一样的。
-
参数实例对象没有提供id 或 提供的id在数据库中不存在:这时merge将执行插入操作,产生的sql语句如下:
Hibernate: select max(deptNo) from Dept Hibernate: insert into Dept (deptNo, dname, loc) values (?, ?, ?)
-
参数实例对象的id在数据库中已经存在,此时又有两种情况:
(1)如果对象有改动,则执行更新操作,产生sql语句有:
Hibernate: select Dept0_.deptNo as deptNo0_0_, Dept0_.dname as dname0_0_, Dept0_.loc as loc0_0_ from Dept Dept0_ where Dept0_.deptNo=? Hibernate: update Dept set dname=?, loc=? where deptNo=?
(2)如果对象未改动,则执行查询操作,产生的语句有:
Hibernate: select Dept0_.deptNo as deptNo0_0_, Dept0_.dname as dname0_0_, Dept0_.loc as loc0_0_ from Dept Dept0_ where Dept0_.deptNo=?
不管哪种情况,merge的返回值都是一个持久化的实例对象,但对于参数而言不会改变它的状态。
例如:修改11号部门的信息.
DEPT
数据表中11号部门的原有信息如下:
DEPTNO : 11 DNAME : 质管部 LOC : 西区
将该部门的信息修改为:
DEPTNO : 11 DNAME : 开发部 LOC : 西区
使用merge()
方法实现,如示例11所示:
映射文件Dept.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="cn.hibernatedemo.entity">
<class name="Dept" table="DEPT" dynamic-update="true">
<id name="deptNo" column="DEPTNO">
<!--<generator class="assigned"/>-->
<generator class="increment"/>
</id>
<property name="deptName" column="DNAME"/>
<property name="location" column="LOC"/>
</class>
</hibernate-mapping>
DeptDao.java
中的关键代码如下:
public Dept merge(Dept dept){
return (Dept) currentSession().merge(dept);
}
DeptBiz.java
关键代码如下:
public void mergeDept(Dept dept) {
Transaction transaction = null;
Dept persistenDept = null;
try {
transaction = deptDao.currentSession().beginTransaction();
//合并dept的数据或者保存dept的副本,返回持久态对象
persistenDept = deptDao.merge(dept);
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
}
测试方法关键代码如下:
public void testMergeDept(){
//构建测试数据
Dept dept = new Dept();
dept.setDeptNo((short)11);
dept.setDeptName("开发部");
dept.setLocation("西区");
DeptBiz deptBiz = new DeptBiz();
dept = deptBiz.mergeDept(dept);
System.out.println(dept);
}
运行以上程序,Hibernate
会执行以下SQL语句:
Hibernate:
select
dept0_.DEPTNO as DEPTNO1_0_0_,
dept0_.DNAME as DNAME2_0_0_,
dept0_.LOC as LOC3_0_0_
from
DEPT dept0_
where
dept0_.DEPTNO=?
Hibernate:
update
DEPT
set
DNAME=?
where
DEPTNO=?
可以看出,使用merge()
方法,Hibernate
会根据OID
加载对应的Dept
类对象.在Dept.hbm.xml
映射文件中,为<class>
标签配置属性:dynamic-update=true
,作用是只修改发生变化的属性。
综上所述:
如果当前Session
缓存中没有包含具有相同OID
的持久化对象(如打开Session
后的首次操作),可以使用update()
或者saveOrUpdate()
方法;
如果想随时合并对象的修改而不考虑Session
缓存中对象的状态,可以使用merge()
方法.
补充:
解决Oracle启动报错的方法:
一、错误:
C:\Users\Frank> sqlplus "/as sysdba"
SQL> startup
ORA-00119: invalid specification for system parameter LOCAL_LISTENER
ORA-00132: syntax error or unresolved network name 'LISTENER_ORCL'
二、办法
- cmd窗口以sys用户的方式登录 输入以下命令:
C:\Users\Frank> sqlplus "/as sysdba"
SQL> create pfile from spfile='D:\app\Frank\product\11.2.0\dbhome_1\database\spfileorcl.ora';
说明:D:\app\Frank\product\11.2.0\dbhome_1\database这个路径是你Oracle安装路径里的,改成自己的
- 修改文件:
在D:\app\Frank\product\11.2.0\dbhome_1\database 路径里找到 init<你的数据库name>.ora文件,我的数据库name是orcl,所以我的文件名是initorcl.ora,打开修改找到local_listener这行,修改为:
*.local_listener='(ADDRESS_LIST=(Address=(Protocol=tcp) (Host=your_hostname)(Port=1521)))'
说明:Host=your_hostname改成自己的,可以右击计算机选择属性进行查看进算机名就是。
- 输入以下命令:
SQL> create spfile from pfile='D:\app\Frank\product\11.2.0\dbhome_1\database\initorcl.ora';
说明:D:\app\Frank\product\11.2.0\dbhome_1\database这个路径是你Oracle安装路径里的,改成自己的
- 重启
SQL> startup