SSH Chapter 04 Hibernate 入门

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/;

Hibernate的5.4.2 Jar包下载地址

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来优化,但是同时也会影响开发效率);Hibernatehql数据库移植性更好,体现在强壮性。Hibernate在级联删除的时候效率低;数据量大, 表多的时候,基于关系操作会变得复杂

(2) 缓存方面:MyBatisHibernate都可以使用第三方缓存,而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&amp;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>

其中几个常用的参数作用如下:

  1. connection.url : 表示数据库URL, Oracle的URL:jdbc:oracle:thin:@localhost:1521/orcl.其中jdbc:oracle:thin:@是固定写法,localhostIP地址,1521是端口号,orcl是数据库的实例名,12c以后可以使用pdb数据库

  2. connection.username : 表示数据库用户名.

  3. connection.password : 表示数据库用户密码

  4. connection.driver_class : 表示数据库驱动.oracle.jdbc.driver.OracleDriverOracle数据库的驱动类.

  5. dialect : 配置Hibernate数据库方言,Hibernate可针对特殊的数据库进行优化.

  6. current_session_context_class : 指定org.hibernate.context.CurrentSessionContext.currentSession()方法所得到的Session由谁来跟踪管理.threadSession由当前执行的线程来跟踪管理.

  7. show_sql : 是否把Hibernate运行时的SQL语句输出到控制台,项目编码期间设置为true便于调试,项目部署完毕设置为false加快程序运行

  8. 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元素

常用的主键生成策略如下:

  1. assigned: 此方式由自己在应用程序中手动指定,不推荐使用

  2. increment,适用于 short 、int 、long 类型的主键,这是 Hibernate 的自动增长机制,原理其实很简单。它发送的是一条这样的 sql 语句select max(id) from xx,找到最大的主键,然后让 id + 1 ,再作为下一条记录的主键。也就是说该策略要求数据库也要支持自增长,比如 Oracle 就是不行的。而且使用该方式存在线程安全问题,如果两个线程同时读取到的是同一个 id ,这就会造成混乱,典型的脏数据问题。

  3. identity,适用于 short 、int 、long 类型的主键,它采用的是数据库提供的主键自动增长机制,所以只能用在支持自增长机制的数据库中,比如 MySQL、MSSqlServer ,Oracle 是不支持主键自增长的。

  4. sequence,适用于序列的方式生成主键,比如 Oracle 数据库就支持这种方式,MySql 就不支持。

  5. native,表示此 POJO 对象的对象标识符OID 由具体使用的数据库生成。**POJO 的标识符类型只能是整数类型,比如 short 、int 、long 类型的数据。**一般默认用这个作为主键生成策略!要注意它并不是一种具体的主键生成策略,它是根据核心配置文件中的方言来动态决定调用那种生成策略,比如 identitysequence

  • 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个步骤:

  1. 读取并解析配置文件以及映射文件

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

    根据默认位置的Hibernate配置文件中的信息,构建Configuration对象.Configuration负责管理Hibernate的配置信息.

  2. 根据配置文件和映射问中的信息,创建SessionFactory对象.

    SessionFactory sessionFactory = configuration.buildSessionFactory();
    

    Configuration对象会根据当前的数据库配置信息,构造SessionFactory对象.SessionFactory对象一旦构造完毕,Configuration对象的任何变更将不会影响已经创建的SessionFactory对象.如果Hibernate配置信息有改动,那么就需要基于改动后的Configuration对象重新构建一个SessionFactory对象.

  3. 打开Session.

    Session session = sessionFactory.getCurrentSession();//或者使用sessionFactory.openSession()
    

    SessionFactory负责创建Session对象.
    SessionHibernate持久化操作的基础.Session作为贯穿Hibernate持久化管理器的核心,提供了众多持久化方法,如save(),delete(),update(),get(),load()等.通过这些方法,即可透明的完成对象的增,删,改,查(CURD).

  4. 开始一个事务.

    session.beginTransaction();
    
  5. 执行数据库操作.

    session.save(user);//保存操作
    
  6. 结束事务.

    session.getTransaction().commit();//提交事务
    

    或者

    session.getTransaction().rollback();//回滚事务
    
  7. 如果是通过SessionFactoryopenSession()方法获取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();

经验:在项目开发过程中,通常使用工具类管理SessionFactorySession,参考代码如下:

/**
 * 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时的一些小问题

当了解了loadget的加载机制以后,我们此时来看看这两种方式会出现的一些小问题:

①如果使用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的两种加载方式getload已经分析完毕!!!

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发生关联;

第二种,瞬时状态的对象,通过调用Sessionsave()方法或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 . 脏检查及刷新缓存机制

SessionHibernate向应用程序提供的操作数据库的主要接口,它提供了基本的保存,更新,删除和加载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会在以下时间点清理缓存。

  1. 当应用程序调用org.hibernate.Transactioncommit()方法的时候.
    commit()方法先清理缓存,然后再向数据库提交事务。Hibernate之所以把清理缓存的时间点安排在事务快结束时,一方面是因为可以减少访问数据库的频率,还有一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间。
  2. 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,就会清理缓存,使得Session缓存与数据库已经进行了同步,从而保证查询结果返回的是正确的数据。
  3. 当应用程序显示调用Sessionflush()方法的时候。

5 . 更新数据的方法

Hibernate中的Session提供了多种更新数据的方法,如update(),saveOrUpdate(),merga()方法.

(1) update() 方法:

用于将游离状态的对象恢复为持久状态,同时进行数据库的更新操作.当参数对象的OIDnull时会报异常.

(2) saveOrUpdate() 方法:

同时包含了save()update()方法的功能.如果入参是瞬时状态的对象,就调用save()方法;如果入参是游离状态的对象,则调用update()方法.

(3) merge() 方法:

首先从参数说明来看,merge()的参数应该是一个处于托管状态的实例对象,而返回值则是一个持久化对象。

但是这里的参数并不是一定要是托管状态的对象,它还可以是瞬态和持久化的实例对象。正因如此,才使merge方法变得复杂化。

经代码检验从merge方法产生的效果来看,它和saveOrUpdate方法相似,因此虽然上面提到是因为参数状态的不同造成复杂化,但是这里我们并不打算分参数的不同状态来理解merge,而是根据参数有无id或id是否已经存在来理解merge。这样更容易理解,而且从执行他们两个方法而产生的sql语句来看是一样的。

  1. 参数实例对象没有提供id 或 提供的id在数据库中不存在:这时merge将执行插入操作,产生的sql语句如下:

    Hibernate: select max(deptNo) from Dept     
    
    Hibernate: insert into Dept (deptNo, dname, loc) values (?, ?, ?)
    
  2. 参数实例对象的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' 

二、办法

  1. 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安装路径里的,改成自己的

  1. 修改文件:
    在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改成自己的,可以右击计算机选择属性进行查看进算机名就是。

  1. 输入以下命令:
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安装路径里的,改成自己的

  1. 重启
SQL> startup
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值