Hibernate琐碎知识总结
Cascade lazy
Hibernate是一种Java语言下的对象关系映射解决方案。 它是一种自由、开源的软件。它用来把对象模型表示的对象映射到基于SQL 的关系模型结构中去,为面向对象的领域模型到传统的关系型数据库的映射,提供了一个使用方便的框架。
Hibernate 不仅管理Java 类到数据库表的映射(包括从Java数据类型到SQL数据类型的映射),还提供数据查询和获取数据的方法,可以大幅度减少开发时人工使用SQL 和JDBC 处理数据的时间。
它的设计目标是将软件开发人员从大量相同的数据持久层相关编程工作中解放出来。无论是从设计草案还是从一个遗留数据库开始,开发人员都可以采用Hibernate。
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久化的重任。
了解了什么是Hibernate之后,那么我们就该关注,为什么要选择Hibernate来完成这项功能呢?下面我从应用程序需求角度来简单解释一下Hibernate流行的原因。
随着计算机应用软件的发展,应用程序逐渐由单层体系结构发展为多层体系结构。其中,三层结构是目前典型的一种应用软件结构,如图1-1。
1)、表述层:提供与用户交互的界面,如GUI、WEB界面等。
2)、业务逻辑层:负责各种业务逻辑,对数据的集中处理,直接完成对数据的CRUD操作。
3)、数据库层:负责存放管理应用的持久性业务数据。
分层的优点:恰当的分层能够提高软件的性能。
分层的缺点:软件分层越多,软件设计人员的要求就越高,同时设计难度加大,开发周期加长,测试成本变高。
三层结构的特点:所有下层向上层提供调用的接口,具体实现细节对上层透明。层与层之间存在自下而上的依赖关系,即上层会访问下层的API,但下层不依赖上层。
在上一节中我们看到了一个典型的三层应用模型,但是这种设计随着软件项目的扩大而变得不合理,今日的软件行业是日新月异,需求是千变万化,而一个应用对于数据存取又是必不可少的,那么在三层应用中,我们的应用随着我们需求改变,在改变业务逻辑同时也改变了数据的存取过程,而实际上大多数系统的变化只会涉及到其中一部分,这就好比是建立一了栋小楼后,突然现在需要再建一个车库,从而立即将整个小楼拆掉,再重新开始,其实大家都知道,这是不必要的,我们只要在小楼旁边建这就行了。但是如果我当初小楼设计不合理,没法添加脊梁,那么我们想法也就不能实现了,这种思想其实在软件工程中叫软件的耦合。在软件工程中我们提倡软件设计要高内聚,低耦合。所以在一个大型应用中,业务逻辑的实现与持久化过程应该是分开的,所以我们才有了持久层的出现。如图1-2。
下面我们再具体介绍一下持久层的概念以及其具体作用。
要搞明白什么是持久层,其实首先要明白什么是持久化?
持久化(Persistence),就是把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等。
那么,持久层(Persistence Layer),即专注于实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联。在图1-2中,分离出的持久层封装了数据访问细节,为业务逻辑层提供了面向对象的API。
增加持久化层提高了开发的效率,使软件的体系结构更加清晰,在代码编写和系统维护方面变得更容易。特别是在大型的应用里边,会更有利。同时,持久化层作为单独的一层,人们可以为这一层独立的开发一个软件包,让其实现将各种应用数据的持久化,并为上层提供服务。从而使得各个企业里做应用开发的开发人员,不必再来做数据持久化的底层实现工作,而是可以直接调用持久化层提供的API。
持久化层封装了数据的访问细节,为业务逻辑层提供了面向对象的API,完善的持久化层提供了一下目标:
1)、代码的可重用性高,能够完成所有的数据库访问操作。
2)、如果需要的话,能够支持多种数据库平台。
3)、相对独立,当前持久化层发生变化,不会影响上层的实现。
那么到底如何实现持久化层?对于复杂的数据模型,使用JDBC来完成数据的持久化层,需要软件开发人具备比较深的功底。这对于软件开发企业来说,无论从人力资源和时间资源上花费都比较高。对于企业软件开发人员,从头开发自己的持久层所付出的代价太多,基本上不可取的。
幸运的是,目前在持久化层,已经出了许多优秀的ORM框架,有的是商业性的,有的是开源的,Hibernate就是一种越来越受欢迎的开放源代码的ORM产品。Hibernate作为一种中间件,可以为任何一个需要访问关系型数据库的Java应用服务,作为中间件,Hibernate也具有另一个特性,就是透明性,对于使用Hibernate的开发人员来讲,无需关于它的内部实现细节,只要知道其各接口的调用方法就可以了。另外,Hibernate作为一个连接Java程序与关系数据库的桥梁,它具备较高的重用性,同时它也屏蔽了一些数据的底层实现差异,使用系统具备更高的扩展性和可移植性。图1-3显示了Hibernate的通用性。
能够实现持久化除了ORM技术,还有以下几种持久化技术:
1)、主动域对象模型:
它是在实现中封装了关系数据模型和数据访问细节的一种形式。在J2EE架构中,EJB组件分为会话EJB和实体EJB。会话EJB 通常实现业务逻辑,而实体EJB表示业务实体。实体EJB又分为两种:由EJB本身管理持久化,即BMP(Bean-Managed Persistence);由EJB容器管理持久化,即CMP(Container-Managed Persistence)。BMP就是主动域对象模式的一个例子,BMP 表示由实体EJB自身管理数据访问细节。主动域对象本身位于业务逻辑层,因此采用主动域对象模式时,整个应用仍然是三层应用结构,并没有从业务逻辑层分离出独立的持久化层。
2)、JDO模式:
Java Data Objects(JDO)是SUN公司制定的描述对象持久化语义的标准API。严格的说,JDO并不是对象-关系映射接口,因为它支持把对象持久化到任意一种存储系统中,包括 关系数据库、面向对象的数据库、基于XML的数据库,以及其他专有存储系统。由于关系数据库是目前最流行的存储系统,许多JDO的实现都包含了对象-关系映射服务。
3)、CMP模式:
在J2EE架构中,CMP(Container-Managed Persistence)表示由EJB容器来管理实体EJB的持久化,EJB容器封装了对象-关系的映射及数据访问细节。CMP和ORM的相似之处在于,两者都提供对象-关系映射服务,都把对象持久化的任务从业务逻辑中分离出来。区别在于CMP负责持久化实体EJB组件,而ORM负责持久化POJO,它是普通的基于Java Bean 形式的实体域对象。
一般把基于Java Bean形式的实体域对象称为POJO(Plain Old Java Object),意为又普通又古老的Java对象的意思。随着各种ORM映射工具的日趋成熟和流行,POJO又重现光彩,它和基于CMP的实体EJB相比,即简单又具有很高的可移植性,因此联合使用ORM映射工具和POJO,已经成为一种越来越受欢迎的且用来取代CMP的持久化方案。POJO的缺点就是无法做远程调用,不支持分布式计算。
上一讲我们已经讲到对于现在的大型企业应用的持久层多数都会选用一种ORM产品来完成需求,我们所学的Hibernate就是大多数据企业的首选ORM技术。这一讲我们一起来了解一下ORM的基本知识。
对象关系映射(Object Relational Mapper,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。
ORM是随着面向对象的软件开发方法发展而产生的。面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,是当今企业级应用开发环境中的主流开发方法,而关系数据库则是从数学理论发展而来的,是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。它们之间存在显著的区别,为了解决这个不匹配的现象,对象关系映射技术应运而生。因此,ORM系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
ORM的实现思想就是将关系数据库中表的数据映射成为对象,以对象的形式展现,这样开发人员就可以把对数据库的操作转化为对这些对象的操作。因此它的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
一般的ORM包括以下四部分:
1)、一个持久类对象进行CRUD操作的API;
2)、一个语言或API用来规定与类和类属性相关的查询;
3)、一个规定mapping metadata的工具;
4)、一种技术可以让ORM的实现同事务对象一起进行dirty checking,lazy association fetching以及其他的优化操作。
Apache OJB、Cayenne、Jaxor、Hibernate、Ibatis、Mybatis、SMYLE、TopLink
其中Hibernate 的轻量级 ORM 模型逐步确立了在 Java ORM 架构中领导地位,甚至取代复杂而又繁琐的 EJB 模型而成为事实上的 Java ORM 工业标准。而且其中的许多设计均被 J2EE 标准组织吸纳而成为最新 EJB 3.0 规范的标准,这也是开源项目影响工业领域标准的有力见证。
有些人学完了Hibernate,还在云里雾里,我觉得有那种结果的一个原因,大概就是所谓的“不识庐山真面目,只缘身在此山中”。通过前面的介绍,大家应该对Hibernate有一个大概的轮廓,后面的章节我们将深入Hibernate内部进行剖析,本章我们就先初步认识一下Hibernate的真面目。
要比较快,比较轻松的学习Hibernate框架,必须对Hibernate的整个体系有一个比较直观的认识。图1-4对Hibernate作了一个非常高层的概览,它展示了Hibernate通过配置文件来使用数据库和业务数据来为应用程序提供持久化服务(和持久化的对象)。
通过上图我们应该能比较好的了解Hibernate的灵活性实际上是通过配置文件来到达的。下面我们更细致地观察一下运行时的体系结构。挺不幸的,Hibernate是比较复杂的,提供了好几种不同的运行方式。我们展示一下两种极端情况。
如图1-5,轻型体系中,应用程序自己提供JDBC连接,并且自行管理事务。这种方式使用了Hibernate API的一个最小子集。
如图1-6,全面解决体系中,对于应用程序来说,所有的底层JDBC/JTA API都被抽象了,Hibernate会替你照管所有的细节。
应用程序可以通过Hibernate API直接访问数据库,Hibernate API中的API大致可以分为以下几类:
1. 用于配置Hibernate应用的接口,如:Configuration等。
2. 提供访问数据操作的接口,如:Session,Transaction,Query等。
3. 应用程序接收Hibernate内部发生的事件,并作出相应响应的回调接口,如:Interceptor,Lifecycle等。
4. 用于自身扩展Hibernate功能的接口,如:UserType,IdentifierGenerator等。
Hibernate内部封装了JDBC(Java Data Base Connectivity),JTA(Java Transaction API)和JNDI(Java Naming and Directory Interface)。所以Hibernate 的API比较全,也比较庞大。本节只对各接口的作用作简单介绍,更详细的应用会在稍后的章节中讲到。
Hibernate官方发布Hibernate的时,随着也给我们提供了相关的API帮助文档,整个Hibernate的API还是比较庞大的,但对于我们Hibernate应用开发人员来讲,并不难,我们只要搞明白与我们开发相关的常用接口就可以了。所有的Hibernate应用程序都会使用到如图1-7中的六个核心的接口。
这六个核心接口分别为:Session、SessionFactory、Transaction、Query、Criteria和Configuration。这6个核心类和接口在任何开发中都会用到。通过这些接口,不仅可以对持久化对象进行存取,还能够进行事务控制。
下面对这6个核心接口和类分别加以介绍。
Configuration 类的作用是对Hibernate 进行配置,以及对它进行启动。在Hibernate 的启动过程中,Configuration 类的实例首先定位映射文档的位置,读取这些配置,然后创建一个SessionFactory对象。虽然Configuration 类在整个Hibernate 项目中只扮演着一个很小的角色,但它是启动hibernate 时所遇到的第一个对象。
Configuration接口负责配置并启动Hibernate,创建SessionFactory对象。在Hibernate的启动的过程中,Configuration类的实例首先定位映射文档位置、读取配置,然后创建SessionFactory对象。
加载默认的hibernate的配置文件
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
加载指定hibernate的配置文件
sessionFactory=new AnnotationConfiguration().configure("hibernate.xml").buildSessionFactory();
SessionFactory接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
作用:主要用于产生Session的工厂(数据库连接池),当它产生一个Session时,会从数据库连接池取出一个连接,交给这个Session
Session session = sessionFactory.getCurrentSession();
并且可以通过这个Session取出这个连接
关注两个方法:
1)、 getCurrentSession():表示当前环境没有Session时,则创建一个,否则不用创建
2)、openSession(): 表示创建一个Session(3.0以后不常用),使用后需要关闭这个Session
两方法的区别:
①、openSession永远是每次都打开一个新的Session,而getCurrentSession不是,是从上下文找、只有当前没有Session时,才创建一个新的Session
②、OpenSession需要手动close,getCurrentSession不需要手动close,事务提交自动close
③、getCurrentSession界定事务边界
上下文:
所指的上下文是指hibernate配置文件(hibernate.cfg.xml)中的“current_session_context_class”所指的值:(可取值:jta|thread|managed|custom.Class)
<property name="current_session_context_class">thread</property>
常用的是:
①、thread:是从上下文找、只有当前没有Session时,才创建一个新的Session,主要从数据界定事务
②、jta:主要从分布式界定事务,运行时需要Application Server来支持(Tomcat不支持)
③、managed:不常用
④、custom.Class:不常用
在ssh2中的sessionFactory配置文件中应将hibernate.current_session_context_class设为 org.springframework.orm.hibernate3.SpringSessionContext(默认为此值),并应用spring管理事务。
如果为<prop key="hibernate.current_session_context_class">thread</prop> 则会报异常,
Session接口负责执行被持久化对象的CRUD操作 (CRUD的任务是完成与数据库的交流,包含了很多常见的SQL语句)。但需要注意的是Session对象是非线程安全的。同时,Hibernate的session不同于JSP应用中的HttpSession。这里当使用session这个术语时,其实指的是Hibernate中的session,而以后会将HttpSession对象称为用户session。
Query接口让你方便地对数据库及持久对象进行查询,它可以有两种表达方式:HQL语言或本地数据库的SQL语句。Query经常被用来绑定查询参数、限制查询记录数量,并最终执行查询操作。
Criteria接口与Query接口非常类似,允许创建并执行面向对象的标准化查询。值得注意的是Criteria接口也是轻量级的,它不能在Session之外使用。
Query和Criteria接口是Hibernate的查询接口,用于向数据库查询对象,以及控制执行查询的过程。Query实例包装了一个HQL查询语句,HQL查询语句和SQL查询语句有些相似,但HQL查询语句是面向对象的,它引用类句及类的属性句,而不是表句及表的字段句。Criteria接口完全封装了基于字符串的查询语句,比Query接口更加面向对象,Criteria接口擅长执行动态查询。
Transaction 接口是一个可选的API,可以选择不使用这个接口,取而代之的是Hibernate 的设计者自己写的底层事务处理代码。 Transaction 接口是对实际事务实现的一个抽象,这些实现包括JDBC的事务、JTA 中的UserTransaction、甚至可以是CORBA 事务。之所以这样设计是能让开发者能够使用一个统一事务的操作界面,使得自己的项目可以在不同的环境和容器之间方便地移植。
Transaction接口是Hibernate的数据库事务接口,负责事务相关的操作,它对底层的事务接口做了封装,底层事务接口包括:
1. JDBC API
2. JTA(Java Transaction API)
3. CORBA(Common Object Requet Broker Architecture)API
Hibernate应用可通过一致的Transaction接口来声明事务边界,这有助于应用在不同的环境容器中移植。尽管应用也可以绕过Transaction接口,直接访问底层的事务接口,这种方法不值得推荐,因为它不利于应用在不同的环境移植。但它是可选的,可发人员也可以设计编写自己的底层事务处理代码。
当一个对象发生了特定的事件(如加载、保存、更新、删除),Hibernate应用可以通过回调来响应事件。
回调接口按实现方式可分为:
Lifecycle接口和Validatable接口:由持久化类来实现接口。
Lifecycle接口,使持久化类的实例能响应被加载、保存、删除的事件。
Validatable接口,使持久化类的实例在被保存前进行数据验证。
Interceptor接口:不必由持久化类来实现接口。Interceptor实现类负责响应持久化类的实例被加载、保存、更新、删除的事件。
注:对于强迫持久化类实现约定接口来完成事件回调,由于会是Hibernate API渗透到持久化类中,会影响到持久化类的可移植性,不推荐大家使用。
Type接口,表示Hibernate映射类型,用于把域对象映射为数据库的关系数据。Hibernate为Type接口提供了各种各样的实现,它们代表不同的具体映射类型。如PrimitiveType类,映射Java基本类型。DateType类,映射Java日期类型。另外Hibernate还允许用户实现UserType和CompositeUserType接口的方式来定义客户化的映射类型。
Hibernate映射类型 |
Java类型 |
标准SQL类型 |
Integer / Int |
Integer / Int |
Integer |
long |
Long / long |
bigInt |
short |
short |
smallInt |
byte |
byte |
tinyInt |
Double |
Double |
Double |
Character |
Char/ character / string |
Char |
String |
String |
Varchar |
Boolean |
Boolean |
bit |
Yes_no |
Boolean |
Char(1) ( Y/ N) |
True_false |
Boolean |
Char(1)( T/F) |
|
||
Date |
Date |
Date |
Time |
Date |
Time |
Timestamp |
Date / timestamp |
Timestamp |
Calendar |
Calendar |
timestamp |
Calendar_date |
Calendar |
Date |
|
||
Binary |
Byte[] |
Varbinary / blob |
Text |
String |
Clob / text |
Serializable |
Serializable |
Varbinary / blob |
Hibernate提供了多种可配置的功能,允许用户选择适当的内置策略来完成需求,这个在后面的章节中你都会体会到,对于有些特殊的应用,可能Hibernate内置的解决方案不足以完成需要,没关系,Hibernate允许客户通过实现接口或继承特定类的方式,根据自己的需要来扩展Hibernate功能。达到客户定制的目的。
Hibernate的扩展点包括以下内容:
1. 定制主键的生成策略:IndentifierGenerator接口
2. 定制本地SQL方言:Dialect抽象类
3. 定制缓存机制:Cache接口、CacheProvider接口
4. 定制JDBC连接管理:ConnectionProvider接口
5. 定制事务管理:TransationFactory接口、Transation接口、TransationManagerLookup接口
6. 定制ORM策略:ClassPersister接口及其子接口
7. 定制属性访问策略:PropertyAccesser接口
8. 创建代理:ProxyFactory接口
9. 定制客户化映射类型:UserType接口、CompositeUserType接口。
对于以上的大多数接口,Hibernate至少都提供了一种实现,所以一般的Hibernate应用,使用Hibernate内置的功能实现即可满足要求。如果对现有的Hibernate实现满意,则可以通过扩展Hibernate来完成需求,所以这一点也是Hibernate做的比较吸引人的一点。
首先我们来看一个HelloWorld例子,从全局来看一下Hibernate开发的必须步骤。对于其中涉及一些Hibernate原理性的知识我们会在后继章节详细介绍。在这个例子中,你只要大概把握Hibernate应用开发的一个大致流程即可。
Hibernate是Java应用和关系数据库之间的桥梁,要在应用中使用Hibernate完成持久化,其实只要搭起一座桥梁就行,我们都知道,现实社会中建立一座桥梁是需要按照一定的工程步骤才能完成的,那么我们先来总体了解一下建立Hibernate这座桥梁的步骤又如何呢?
Hibernate应用开发的步骤可以总结为如下几点:
(1) 引入Hibernate框架支持
(2) 创建Hibernate启动的配置文件
(3) 创建持久化类
(4) 创建对象---关系映射文件,并将些映射文件交给Hibernate管理
(5) 通过Hibernate核心API编写访问数据库的代码
源码如下:(hibernate的配置信息)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
<property name="connection.username">root</property>
<property name="connection.password">a123</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<!--引用hbm.xml 实体对象与数据表之间的映射文件-->
<mapping resource="com/lee/model/Student.hbm.xml"/>
</session-factory>
</hibernate-configuration>
(持久化类)
public class Student {
private Integer id;
private String name;
private String gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", gender=" + gender
+ "]";
}
}
注:将此文件引入到 hibernate.cfg.xml配置文件中
(实体类与数据库表之间的映射文件)
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.lee.model">
<class name="Student" table="t_student">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<property name="gender" column="gender"/>
</class>
</hibernate-mapping>
public class StudentCRUD {
private static Configuration cfg = null;
private static SessionFactory sf = null;
private Session session=null;
@Before
public void beforeMethod(){
cfg = new Configuration().configure();
sf = cfg.buildSessionFactory();
}
@Test
public void Student_add(){
Student stu = new Student();
stu.setName("test_01");
stu.setGender("male");
try {
session = sf.openSession();
session.beginTransaction();
session.save(stu);
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
if(session!=null){
session.close();
}
}
}
@After
public void afterMethod(){
if(sf!=null){
sf.close();
}
}
}
(1) org.hibernate.cfg.Configuration类的作用:
读取hibernate配置文件(hibernate.cfg.xml或hiberante.properties)的.
new Configuration()默认是读取hibernate.properties 所以使用new Configuration().configure();来读取hibernate.cfg.xml配置文件
(2)一个数据库对应一个SessionFactory,SessionFactory是线程安全的。
(3) session只有在用时才建立connection,session还管理缓存。
Session是非线程安全的,所以一般一个请求对应一个session。
首先我们看一下JDBC完成一个持久化过程的代码:
public int insertByJdbc(Account account){
Connection con = null;
PreparedStatement ps = null;
java.sql.ResultSet rs = null;
try{
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/hibernate", "root", "a123");
con.setAutoCommit(false);
ps = con.prepareStatement("insert into account(name,password,balance,create_date) values (?,?,?,?)");
ps.setString(1, account.getName());
ps.setString(2, account.getPassword());
ps.setDouble(3, account.getBalance());
ps.setDate(4, new java.sql.Date(account.getCreateDate().getTime()));
ps.executeUpdate();
rs = ps.getGeneratedKeys();
while(rs.next()){
account.setId(rs.getInt(1));
}
con.commit();
}catch(Exception e){
try {
con.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(con!=null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return account.getId();
}
这段代码大家应该不陌生,一段很典型的JDBC操作。对比我们用Hibernate完成此功能的代码,不难发现Hibernate的诸多好处。
1. 从代码量上已经作了很大的精减,代码不再那么繁琐。
2. 不再需要拼写复杂的SQL语句,也不需要具体区分底层数据库的不同实现。
3. 对数据库的操作完成是面向对象的。甚至开发人员不再需要学习数据库,即可参与持久层的开发。
4. 当持久类或数据库表设计发生变化时,持久层并不需要改动,只需要修改映射文件即可。
5. 当进行数据库移植的时候,也只需要修改Hibernate配置文件。
6. 如果再结合设计模式进行设计,DAO层的代码会非常精减和干净。
注:从理论上讲,进行hibernate应用开发是不需要数据库知识的,但从软件公司的实际要求来讲,数据库技术是软件开发人员必须具备的基本能力,只不过hibernate对数据库操作封装的比较好,这方面的要求相对较低,但从长远来看,数据库技术非常重要。
对于HelloWorld应用中的hibernate.cfg.xml文件称为Hibernate的配置文件,它是Hibernate应用启动的不可缺少的一个文件,Hibernate配置文件主要用来配置数据库连接参数,例如数据库的驱动程序URL,用户名,密码等。
Hibernate提供了两种格式的配置文件:hibernate.properties和hibernate.cfg.xml。一般情况下,hibernate.cfg.xml是Hibernate的默认配置文件,classpath作为配置文件的默认存放位置。在实际应用中推荐大家使用xml的配置方式。
由于Hibernate是为了能在各种不同环境下工作而设计的,因此存在着大量的配置参数, 幸运的是多数配置参数都有比较直观的默认值,并且随Hibernate发布而一同分发了的配置样例hibernate.properties(位于etc/)来展示各种配置选项,同时也分发了一个hibernate.cfg.xml的配置模板,我们所需要做的仅仅是将这个配置模板复制到类路径 (classpath)下并做一些自定义的修改。
下面对hibernate.cfg.xml文件的常用属性进行说明。
|
属性 |
说明 |
必须 |
Connection.driver_class |
指定数据库驱动程序 |
Connection.url |
指定数据库的URL |
|
Connection.username |
指定数据库的用户名 |
|
Connection.password |
指定数据库的密码 |
|
Dialect |
指定数据库的方言,用于不同的数据库 |
|
可选 |
Show_sql |
在控制台数据SQL语句,便于调试 |
format_sql |
对输出的SQL语句格式化,提高可读性 |
|
Hbm2ddl.auto |
根据映射文件自动生成DDL语句 |
|
Mapping |
指定对象关系映射文件的存放位置 |
以上属性是前面的例子中提到过的,也是经常用到的,其实Hibernate配置文件的属性远不止这些,都可以在etc目录下的hibernate.properties文件中找到,其中都有相关说明和配置方法。在这里就不一一列举,以后用到了再查阅相关文档。
注:hibernate的配置文件的默认名是hibernate.cfg.xml,默认存放位置是classpath下。但这都不是必须的,可以根据需要来指定,但会给代码带来一些没必要的麻烦,如果没有特殊需要,建议遵循hibernate默认方式。
(1)、Hibernate方言:
你总是可以为你的数据库设置一个hibernate.dialect方言,它是net.sf.hibernate.dialect.Dialect 的一个子类。如果你不需要使用基于native或者sequence的主键自动生成算法,或者悲观锁定(使用Session.lock() 或 Query.setLockMode())的话,方言就可以不必指定。然而,假若你指定了一个方言,Hibernate会为上面列出的一些属性使用特殊默认值,省得你手工指定它们。
RDBMS |
方言 |
DB2 |
net.sf.hibernate.dialect.DB2Dialect |
MySQL |
net.sf.hibernate.dialect.MySQLDialect |
SAP DB |
net.sf.hibernate.dialect.SAPDBDialect |
Oracle (所有版本) |
net.sf.hibernate.dialect.OracleDialect |
Oracle 9 |
net.sf.hibernate.dialect.Oracle9Dialect |
Sybase |
net.sf.hibernate.dialect.SybaseDialect |
Sybase Anywhere |
net.sf.hibernate.dialect.SybaseAnywhereDialect |
Progress |
net.sf.hibernate.dialect.ProgressDialect |
Mckoi SQL |
net.sf.hibernate.dialect.MckoiDialect |
Interbase |
net.sf.hibernate.dialect.InterbaseDialect |
Pointbase |
net.sf.hibernate.dialect.PointbaseDialect |
PostgreSQL |
net.sf.hibernate.dialect.PostgreSQLDialect |
HypersonicSQL |
net.sf.hibernate.dialect.HSQLDialect |
Microsoft SQL Server |
net.sf.hibernate.dialect.SybaseDialect |
Ingres |
net.sf.hibernate.dialect.IngresDialect |
Informix |
net.sf.hibernate.dialect.InformixDialect |
FrontBase |
net.sf.hibernate.dialect.FrontbaseDialect |
(2)事务策略:
如果你希望使用Hibernate的Transaction API,你必须通过hibernate.transaction.factory_class属性指定一个Transaction实例的工厂类。 内置的两个标准选择是:
net.sf.hibernate.transaction.JDBCTransactionFactory 使用数据库(JDBC)事务
net.sf.hibernate.transaction.JTATransactionFactory 使用JTA(假若已经存在一个事务,Session会在这个上下文中工作,否则会启动一个新的事务。)
你也可以自行定义你的事务策略(比如说,一个CORBA事务服务)。
如果你希望在JTA环境中为可变数据使用JVM级别的缓存,你必须指定一个获取JTA TransactionManager的策略。
事务工厂类 |
Application Server |
net.sf.hibernate.transaction.JBossTransactionManagerLookup |
JBoss |
net.sf.hibernate.transaction.WeblogicTransactionManagerLookup |
Weblogic |
net.sf.hibernate.transaction.WebSphereTransactionManagerLookup |
WebSphere |
net.sf.hibernate.transaction.OrionTransactionManagerLookup |
Orion |
net.sf.hibernate.transaction.ResinTransactionManagerLookup |
Resin |
net.sf.hibernate.transaction.JOTMTransactionManagerLookup |
JOTM |
net.sf.hibernate.transaction.JOnASTransactionManagerLookup |
JOnAS |
net.sf.hibernate.transaction.JRun4TransactionManagerLookup |
JRun4 |
在应用程序中,用来实现业务问题实体的(如在电子商务应用程序中的Customer和Order)类就是持久化类,其实持久化类通常都是域模型中的实体类。如果这些持久化类遵循一些简单的规则,Hibernate能够工作得更好,这些规则也被称作简单传Java对象(POJO:Plain Old Java Object)编程模型。但是这些规则并不是必需的。实际上,Hibernate3对于你的持久化类几乎不做任何设想。那么Hiberante的持久化类又应该具备哪些要求呢?
Hibernate官方文档中已经给出了说明,我们大致可以总结出以下几点:
1. 持久化类应符合JavaBean规范,具有一些属性,和与之对应的set方法和get方法。set方法与get方法必须遵循特定的命名规则,set和get后紧跟相应的属性名,并将属性的第一个字母改为大写。
2. 必须提供无参的构造方法。因为Hibernate是通过反射调用持久类的无参的构造方法来实例化持久对象的。
3. 每个持久化类最好提供一个id属性,用来作为持久化类对象的唯一标识。在面向对象的术语中,这个id称作对象标识符(OID,Object Identifier),通常都是整数表示,但不是必须的。
4. 持久化类最好是public和protected的访问权限,不可以是private,默认或final修饰,否则Hibernate的一些特性将变得不可用,如懒加载特性。
注:hibernate并不要求持久化类必须实现java.io.serializable接口,如果你的应用是一个分布式应用程序或此持久化对象的实例需要存入在HTTPSession中,则该类必须实现序列化接口。
HelloWorld应用中的Student.hbm.xml我们就称之为对称关系映射文件,它的作用在于指定持久类的属性与关系数据中表的字段一一对应关系。
对象和关系数据库之间的映射通常是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以Java为中心,这意味着映射文档是按照持久化类的定义来创建的,而非表的定义。
Hibernate的映射文件也有一些约定俗成的要求(都不是必须的):
1. 一个持久化类对应一个映射文件
2. 映射文件名与类名相同
3. XML文件的命名形式为XXX.hbm.xml
4. 通常与持久化类放在相同目录下
下面对映射文件的常用配置进行说明:
(1)hibernate-mapping节点,映射文件根节点。
例如:<hibernate-mapping package="com.lee.model">
1. package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定的类名, 就使用这个作为包名。
2. schema (可选): 数据库schema的名称。
3. catalog (可选): 数据库catalog的名称。
4. default-cascade (可选 - 默认为 none): 默认的级联风格。
5. default-lazy (可选 - 默认为 true): 指定了未明确注明lazy属性的Java属性和集合类, Hibernate会采取什么样的默认加载风格。
6. auto-import (可选 - 默认为 true): 指定我们是否可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。
(2)class节点,用来定义一个持久化类。
例如:<class name="Student" table="t_student">
1. name (可选): 持久化类(或者接口)的Java全限定名。 如果这个属性不存在,Hibernate将假定这是一个非POJO的实体映射。
2. table (可选 - 默认是类的非全限定名): 对应的数据库表名。
3. schema (可选): 覆盖在根<hibernate-mapping>元素中指定的schema名字。
4. catalog (可选): 覆盖在根<hibernate-mapping>元素中指定的catalog名字。
5. dynamic-update (可选, 默认为 false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。
6. dynamic-insert (可选, 默认为 false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。
7. batch-size (可选,默认是1) 指定一个用于 根据标识符(identifier)抓取实例时使用的"batch size"(批次抓取数量)。
8. optimistic-lock(乐观锁定) (可选,默认是version): 决定乐观锁定的策略。
9. lazy (可选): 通过设置lazy="false", 所有的延迟加载(Lazy fetching)功能将被全部禁用(disabled)。
10. abstract (可选): 用于在<union-subclass>的继承结构 (hierarchies)中标识抽象超类。
(3)id节点,被映射的类必须定义对应数据库表主键字段。大多数类有一个JavaBean风格的属性, 为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。
例如:<id name="id" column="id"></id>
1. name (可选): 标识属性的名字。
2. column (可选 - 默认为属性名): 主键字段的名字。
3. type (可选): 标识Hibernate类型的名字。
(4)generator节点,可选的<generator>子元素是一个Java类的名字, 用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数, 用<param>元素来传递。
例如:<generator class="native"/>
(5)property节点,指定持久化类的属性。
例如:<property name="gender" column="gender"/>
1. name属性:指定持久化类的属性。
2. type属性:指定hibernate映射的数据类型。对应Java数据类型。
3. column属性:通过name属性指定其对应的数据库表的字段名。
4. length属性:通过name属性指定其对应的数据库表的字段名的长度。
5. not-null属性:通过name属性指定其对应的数据库表的字段名是否为空。
6. update:若为false则在更新数据时,不会更新该字段。默认为true.即更新。
7. insert:若为false则在插入数据时,不会插入该字段。默认为true.即插入。
public class StudentCRUD {
private static Configuration cfg = null;
private static SessionFactory sf = null;
private Session session=null;
@Before
public void beforeMethod(){
cfg = new Configuration().configure();
sf = cfg.buildSessionFactory();
}
@Test
public void Student_add(){
Student stu = new Student();
stu.setName("test_01");
stu.setGender("male");
try {
session = sf.openSession();
session.beginTransaction();
session.save(stu);
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
if(session!=null){
session.close();
}
}
}
@After
public void afterMethod(){
if(sf!=null){
sf.close();
}
}
}
分析上面这段典型的Hibernate代码,我们可以总结出进行Hiberante持久层开发一般步骤:
(1) 创建Configuration实例
(2) 加载Hibernate配置文件并取得SessionFacotroy实例
(3) 打开会话Session
(4) 开启本次事务
(5) 执行持久化操作
(6) 结束本次事务
(7) 关闭资源
其实进行Hibernate应用开发只要遵照上面一般步骤即可,实际上这段代码就是一个基本的Hibernate持久化操作,我们只需要稍加修改,它就可以成为我们Hibernate应用的一个开发模板。但对于一个应用而言,读取Hibernate配置文件和创建与数据库对应的SessionFactory只需要进行一次就可以了,没有必要在每个持久化操作中重复进行,而实际上完成对象持久化操作的核心接口只有Session接口,后面我们会优化我们这段代码。
看完上面的注释,其实我们对使用Hibernate的API进行持久化操作应该有一定的了解,下面我们再具体学习一下在这段代码中涉及到的几个Hibernate接口。
(1)configuration接口:
Configuration是Hibernate的入口,在新建一个Configuration的实例的时候,Hibernate会在classpath里面查找Hibernate的默认配置文件。它的作用是对Hibernate进行配置,以及对它进行启动,在Hibernate的启动过程中。
Configuration对象可以创建一个SessionFactory对象,当SessionFactory对象创建成功后,Configuration对象就没有用了,你可以简单地抛弃它,虽然Configuration接口在整个Hibernate项目中只扮演着一个很小的角色,但它是启动hibernate时你所遇到的第一个对象。
启动Hibernate方式如下:
//创建实例
Configuration config = new Configuration();
//加载hibernate默认的配置文件,如果你的Hibernate配置文件没有遵循Hibernate默认配置,则你需要调用configure(String path)方法来指定配置文件
Config.configure();
若使用Annotation则创建Configuration代码如下:
Configuration config = new AnnotationConfiguration();
(2)sessionFactory接口:
这里用到了一个设计模式――工厂模式,用户程序从工厂类SessionFactory中取得持久化操作接口Session的实例。令你感到奇怪的是SessionFactory并不是轻量级的,实际上它的设计者的意图是让它能在整个应用中共享。简单地来说,一个项目通常只需要一个SessionFactory就够了,因为随意地创建SessionFactory实例会占用大量内存空间。但是当你的项目要操作多个数据库时,那你必须为每个数据库指定一个SessionFactory。
获得数据存储源SessionFactory的对像的方式如下:
SessionFactory sessionFactory = config.buildSessionFactory();
其实启动Hiberante到创建SessionFactory,可以采用Java的链式编程方式一步到位:
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
SessionFactory在Hibernate中实际起到了一个缓冲区的作用,它缓冲了Hibernate自动生成的SQL语句和一些其它的映射数据,还缓冲了一些将来有可能重复利用的数据。
关于Hibernate的更详细的缓存问题我们会在专门的章节探讨。
(3)Session接口:
Session接口对于Hibernate开发人员来说是一个最重要的接口,Hibernate的持久化功能大多集中在Session接口中。然而在Hibernate中,实例化的Session是一个轻量级的类,创建和销毁它都不会占用很多资源。这在实际项目中确实很重要,因为在客户程序中,可能会不断地创建以及销毁Session对象,如果Session的开销太大,会给系统带来不良影响。但值得注意的是Session对象是非线程安全的,因此在你的设计中,最好是一个线程只创建一个Session对象。
创建和销毁Session实例对象的方式如下:
Session session = sessionFacotry.openSession();//创建session
session.close();//关闭session
关于Session接口更详细的用法我们会在下一节中介绍。
(4)Transaction接口:
Transaction接口是一个可选的API,你可以选择不使用这个接口,取而代之的是Hibernate的设计者自己写的底层事务处理代码。Transaction接口是对实际事务实现的一个抽象,这些实现包括JDBC的事务、JTA中的UserTransaction、甚至可以是CORBA 事务。之所以这样设计是能让开发者能够使用一个统一事务的操作界面,使得自己的项目可以在不同的环境和容器之间方便地移值。Hibernate默认的事务采用的是JDBC事务。
涉及Hibernate事务的代码如下:
Transaction trans = session.beginTransaction();//打开事务
trans.commit();//提交事务
trans.rollback();//回滚事务
关于Transaction更详尽的用法我们会在专门的章节探讨。
<property name="hbm2ddl.auto">update</property>
有四个值可以取:
Validate | update | create | create-drop
其实这个hibernate.hbm2ddl.auto参数的作用主要用于:自动创建|更新|验证数据库表结构。如果不是此方面的需求建议set value="none"。
(1)create:
每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
(2)create-drop :
每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
(3)update:
最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据 model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等 应用第一次运行起来后才会。
(4)validate :
每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
再说点“废话”:
当我们把hibernate.hbm2ddl.auto=create时hibernate先用hbm2ddl来生成数据库schema。
当我们把hibernate.cfg.xml文件中hbm2ddl属性注释掉,这样我们就取消了在启动时用hbm2ddl来生成数据库schema。通常 只有在不断重复进行单元测试的时候才需要打开它,但再次运行hbm2ddl会把你保存的一切都删除掉(drop)---- create配置的含义是:“在创建SessionFactory的时候,从schema中drop掉所有的表,再重新创建它们”。
注意,很多Hibernate新手在这一步会失败,我们不时看到关于Table not found错误信息的提问。但是,只要你根据上面描述的步骤来执行,就不会有这个问题,因为hbm2ddl会在第一次运行的时候创建数据库schema, 后续的应用程序重启后还能继续使用这个schema。假若你修改了映射,或者修改了数据库schema,你必须把hbm2ddl重新打开一次。
***********************************************************
这两天在整理Spring + JPA(Hibernate实现),从网上copy了一段Hibernate连接参数的配置。
<properties>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
结果在测试时,老是发现数据库表数据丢失。这个参数以前没怎么用,查了一圈其它的东东,最后才定位到这个上面。赶紧查了一下Hibernate的参数配置,解释如下:
hibernate.hbm2ddl.auto Automatically validate or export schema DDL to the database when the SessionFactory is created. With create-drop, the database schema will be dropped when the SessionFactory is closed explicitly. eg. validate | update | create | create-drop
其实这个参数的作用主要用于:自动创建|更新|验证数据库表结构。如果不是此方面的需求建议set value="none".
其它几个参数的意思,我解释一下:
validate 加载hibernate时,验证创建数据库表结构
create 每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。
create-drop 加载hibernate时创建,退出是删除表结构
update 加载hibernate自动更新数据库结构
以上4个属性对同一配置文件下所用有的映射表都起作用
(1)xml:
<class name="Student" table="t_student">
(2)annotation:
使用@Table(name=”t_student”)
/**
* @Entity 表示下面的这个Teacher是一个实体类
* @Table 表示映射到数据表中的表名,其中的name参数表示"表名称"
* @Id 表示主键Id,一般放在getXXX前面
*/
@Entity
@Table(name="t_teacher")
public class Teacher {
[……]
}
(1)xml :
<property name="gender" column="t_gender"/>
(2)annotation :
使用@column( name = “columnName” )
/**
* @Entity 表示下面的这个Teacher是一个实体类
* @Table 表示映射到数据表中的表名,其中的name参数表示"表名称"
* @Column 表示实体类成员属性映射数据表中的字段名,其中name参数指定一个新的字段名
* @Id 表示主键Id
*/
@Entity
@Table(name="t_teacher")
public class Teacher {
private int id;
private String name;
private String title;
//设置主键使用@Id
@Id
public int getId() {
return id;
}
@Column(name="t_name")//字段名与属性不同时
public String getName() {
return name;
}
……
(1)xml :
不写(就是不需要对这个成员属性进行映射)
(2)annotation :
使用@Transient注解就可以了
@Transient
public String getTitle() {
return title;
}
(1)xml :
使用Type属性指定hibernate类型
<property name="birthDate" type="date"/>
(2)annotation :
使用@temporal( value = temporalType ) 注解来表示时间和日期
其中TemporalType有三个值:
TemporalType.TIMESTAMP 表示yyyy-MM-dd HH:mm:ss
TemporalType.DATE 表示yyyy-MM-dd
TemporalType.TIME 表示HH:mm:ss
@Temporal(value=TemporalType.DATE)
public Date getBirthDate() {
return birthDate;
}
(1)xml :
映射有点麻烦,先要定义自定义类型,然后再使用这个定义的类型。
(2)annotation :
Annotation:使用@Enumerated(value=EnumType)来注解表示此成员属性为枚举映射到数据库
其中EnumType有二个值:
①EnumType.STRING 表示直接将枚举名称存入数据库
②EnumType.ORDINAL 表示将枚举所对应的数值存入数据库
<id>标签必须配置在<class>标签内第一个位置。由一个字段构成主键,如果是复杂主键<composite-id>标签
被映射的类必须定义对应数据库表主键字段。大多数类有一个JavaBeans风格的属性, 为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。
<id
name="propertyName" (1)
type="typename" (2)
column="column_name" (3)
unsaved-value="null|any|none|undefined|id_value" (4)
access="field|property|ClassName" (5)
node="element-name|@attribute-name|element/@attribute|.">
<generator class="generatorClass"/>
</id>
(1) name (可选): 标识属性的名字(实体类的属性)。
(2) type (可选): 标识Hibernate类型的名字(省略则使用hibernate默认类型),也可以自己配置其它hibernate类型(integer, long, short, float, double, character, byte, boolean, yes_no, true_false)
(2) length(可选):当type为varchar时,设置字段长度
(3) column (可选 - 默认为属性名): 主键字段的名字(省略则取name为字段名)。
(4) unsaved-value (可选 - 默认为一个切合实际(sensible)的值): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。 这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注) 但未再次持久化的实例区分开来。
(5) access (可选 - 默认为property): Hibernate用来访问属性值的策略。
如果 name属性不存在,会认为这个类没有标识属性。
unsaved-value 属性在Hibernate3中几乎不再需要。
还有一个另外的<composite-id>定义可以访问旧式的多主键数据。 我们强烈不建议使用这种方式。
<generator>元素,主键生成策略:
主键生成策略是必须配置
用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数, 用<param>元素来传递。
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>
所有的生成器都实现org.hibernate.id.IdentifierGenerator接口。 这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然, Hibernate提供了很多内置的实现。下面是一些内置生成器的快捷名字:
Increment:
用于为long, short或者int类型生成 唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。 在集群下不要使用。
Identity:
对DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的内置标识字段提供支持。 返回的标识符是long, short 或者int类型的。 (数据库自增)
Sequence:
在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的标识符是long, short或者 int类型的。(数据库自增)
UUID:
用一个128-bit的UUID算法生成字符串类型的标识符, 这在一个网络中是唯一的(使用了IP地址)。UUID被编码为一个32位16进制数字的字符串,它的生成是由hibernate生成,一般不会重复。
UUID包含:IP地址,JVM的启动时间(精确到1/4秒),系统时间和一个计数器值(在JVM中唯一)。 在Java代码中不可能获得MAC地址或者内存地址,所以这已经是我们在不使用JNI的前提下的能做的最好实现了。
native :
根据底层数据库的能力选择identity, sequence 或者hilo中的一个。(数据库自增)
Assigned:
让应用程序在save()之前为对象分配一个标示符。这是 <generator>元素没有指定时的默认生成策略。(如果是手动分配,则需要设置此配置)
select :
通过数据库触发器选择一些唯一主键的行并返回主键值来分配一个主键。
foreign :
使用另外一个相关联的对象的标识符。通常和<one-to-one>联合起来使用。
使用@GeneratedValue(strategy=GenerationType)注解可以定义该标识符的生成策略
Strategy有四个值:
1)、AUTO - 可以是identity column类型,或者sequence类型或者table类型,取决于不同的底层数据库.
相当于native
2)、TABLE - 使用表保存id值
3)、IDENTITY - identity column
4)、SEQUENCE - sequence
注意:auto是默认值,也就是说没有后的参数则表示为auto
@Id
@GeneratedValue
public int getId() {
return id;
}
或
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public int getId() {
return id;
}
1. 对于mysql,使用auto_increment
2. 对于oracle使用hibernate_sequence(名称固定)
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
return id;
}
对DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的内置标识字段提供支持。 返回的标识符是long, short 或者int类型的。 (数据库自增)
注意:此生成策略不支持Oracle
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
public int getId() {
return id;
}
在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的标识符是long, short或者 int类型的。(数据库自增)
注意:此生成策略不支持MySQL
为Oracle指定定义的Sequence
a)、首先需要在实体类前面申明一个Sequence如下:
方法:@SequenceGenerator(name="SEQ_Name",sequenceName="SEQ_DB_Name")
参数注意:
SEQ_Name:表示为申明的这个Sequence指定一个名称,以便使用
SEQ_DB_Name:表示为数据库中的Sequence指定一个名称。
两个参数的名称可以一样。
@Entity
@SequenceGenerator(name="teacherSEQ",sequenceName="teacherSEQ_DB")
public class Teacher {
……
}
b)、然后使用@GeneratedValue注解
方法:@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="SEQ_Name")
参数:strategy:固定为GenerationType.SEQUENCE
Generator:在实体类前面申明的sequnce的名称
@Entity
@SequenceGenerator(name="teacherSEQ",sequenceName="teacherSEQ_DB")
public class Teacher {
private int id;
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="teacherSEQ")
public int getId() {
return id;
}
}
(复核主键)联合主键:多个字段构成唯一性
(1)实例场景:
// 核算期间
public class FiscalYearPeriod {
private int fiscalYear; //核算年
private int fiscalPeriod; //核算月
private Date beginDate; //开始日期
private Date endDate; //结束日期
private String periodSts; //状态
public int getFiscalYear() {
return fiscalYear;
}
public void setFiscalYear(int fiscalYear) {
this.fiscalYear = fiscalYear;
}
public int getFiscalPeriod() { return fiscalPeriod;}
public void setFiscalPeriod(int fiscalPeriod) {
this.fiscalPeriod = fiscalPeriod;
}
public Date getBeginDate() {return beginDate;}
public void setBeginDate(Date beginDate) { this.beginDate = beginDate; }
public Date getEndDate() {return endDate;}
public void setEndDate(Date endDate) { this.endDate = endDate; }
public String getPeriodSts() { return periodSts;}
public void setPeriodSts(String periodSts) {this.periodSts = periodSts;}
}
复合主键的映射,一般情况把主键相关的属性抽取出来单独放入一个类中。而这个类是有要求的:必需实现序列化接口(java.io.Serializable)(可以保存到磁盘上),为了确定这个复合主键类所对应对象的唯一性就会产生比较,对象比较就需要复写对象的hashCode()、equals()方法(复写方法如下图片),然后在类中引用这个复合主键类
(2)复核主键类:
必须实现java.io.serializable接口:
public class FiscalYearPeriodPK implements java.io.Serializable {
private int fiscalYear;//核算年
private int fiscalPeriod;//核算月
public int getFiscalYear() {
return fiscalYear;
}
public void setFiscalYear(int fiscalYear) {
this.fiscalYear = fiscalYear;
}
public int getFiscalPeriod() {
return fiscalPeriod;
}
public void setFiscalPeriod(int fiscalPeriod) {
this.fiscalPeriod = fiscalPeriod;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + fiscalPeriod;
result = prime * result + fiscalYear;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FiscalYearPeriodPK other = (FiscalYearPeriodPK) obj;
if (fiscalPeriod != other.fiscalPeriod)
return false;
if (fiscalYear != other.fiscalYear)
return false;
return true;
}
}
(3)实体类:
实体类中引用了复核主键类:
public class FiscalYearPeriod {
private FiscalYearPeriodPK fiscalYearPeriodPK;//引用 复合主键类
private Date beginDate;//开始日期
private Date endDate;//结束日期
private String periodSts;//状态
public FiscalYearPeriodPK getFiscalYearPeriodPK() {
return fiscalYearPeriodPK;
}
public void setFiscalYearPeriodPK(FiscalYearPeriodPK fiscalYearPeriodPK) {
this.fiscalYearPeriodPK = fiscalYearPeriodPK;
}
………………
(4)FiscalYearPeriod.hbm.xml映射文件:
<hibernate-mapping>
<class name="com.bjsxt.hibernate.FiscalYearPeriod" table="t_fiscal_year_period">
<composite-id name="fiscalYearPeriodPK">
<key-property name="fiscalYear"/>
<key-property name="fiscalPeriod"/>
</composite-id>
<property name="beginDate"/>
<property name="endDate"/>
<property name="periodSts"/>
</class>
</hibernate-mapping>
(5)创建数据库的SQL语句:
create table t_fiscalYearPeriod (
fiscalYear integer not null,
fiscalPeriod integer not null,
beginDate datetime,
endDate datetime,
periodSts varchar(255),
primary key (fiscalYear, fiscalPeriod)
)
(6)数据库表结构:
(7)数据存储过程:
session = HibernateUtils.getSession();
tx = session.beginTransaction();
FiscalYearPeriod fiscalYearPeriod = new FiscalYearPeriod();
//构造复合主键
FiscalYearPeriodPK pk = new FiscalYearPeriodPK();
pk.setFiscalYear(2009);
pk.setFiscalPeriod(11);
fiscalYearPeriod.setFiscalYearPeriodPK(pk);//为对象设置复合主键
fiscalYearPeriod.setEndDate(new Date());
fiscalYearPeriod.setBeginDate(new Date());
fiscalYearPeriod.setPeriodSts("Y");
session.save(fiscalYearPeriod);
下面是定义主键的几种方法:
1. 将组件类注解为@Embeddable,并将组件的属性注解为@Id
2. 将组件的属性注解为@EmbeddedId
3. 将类注解为@IdClass,并将该实体中所有属于主键的属性都注解为@Id
A)、将组件类注解为@Embeddable,并将组件的属性注解为@id
组件类:
@Embeddable
public class TeacherPK implements java.io.Serializable{
private int id;
private String name;
public int getId() {return id; }
public void setId(int id) { this.id = id;}
public String getName() { return name;}
public void setName(String name) { this.name = name;}
@Override
public boolean equals(Object o) { ……}
@Override
public int hashCode() { return this.name.hashCode(); }
}
将组件类的属性注解为@id
@Entity
public class Teacher {
private TeacherPK pk;
private String title;
@Id
public TeacherPK getPk() {
return pk;
}
}
B)、将组件的属性注解为@EmbededId
注意:只需要在实体类中表示复合主键属性前注解为@Entity,表示此主键是一个复合主键
注意了,复合主键类不需要任何的注解。
@Entity
public class Teacher {
private TeacherPK pk;
private String title;
@EmbeddedId
public TeacherPK getPk() {
return pk;
}}
C)、类注解为@IdClass,主键的属性都注解为@Id
需要将复合主键类建立好,不需要进行任何注解
在实体类中不需要进行复合主键类的引用
需要在实体类前面注解为@IdClass,并且指定一个value属性,值为复合主键类的class
需要在实体类中进行复合主键成员属性前面注解为@Id
如下:
@Entity
@IdClass(TeacherPK.class)
public class Teacher {
//private TeacherPK pk;//不再需要
private int id;
private String name;
@Id
public int getId() {return id; }
public void setId(int id) { this.id = id; }
@Id
public String getName() {return name;}
public void setName(String name) {this.name = name;
}}
作用:进行配置信息的管理
目标:用来产生SessionFactory
可以再Configure方法中指定hibernate配置文件,默认(不指定)是在classpath下加载hibernate.cfg.xml文件
①、加载默认的hibernate配置文件:
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
②、加载指定的hibernate配置文件:
sessionFactory=new AnnotationConfiguration().configure("hibernate.xml").buildSessionFactory();
只需要关注一个方法BuildSessionFactory( ).
作用:主要用于产生session的工厂(数据库连接池)
当它产生一个Session时,会从数据库连接池取出一个连接,交给这个Session
Session session = sessionFactory.getCurrentSession();
并且可以通过这个Session取出这个连接
关注两个方法:
getCurrentSession():表示当前环境没有Session时,则创建一个,否则不用创建
openSession(): 表示创建一个Session(3.0以后不常用),使用后需要关闭这个Session
两方法的区别:
①、openSession永远是每次都打开一个新的Session,而getCurrentSession不是,是从上下文找、只有当前没有Session 时,才创建一个新的Session
②、OpenSession需要手动close,而getCurrentSession不需要手动close,事务提交自动close
③、getCurrentSession界定事务边界
上下文:
所指的上下文是指hibernate配置文件(hibernate.cfg.xml)中的“current_session_context_class”所指的值:
(可取值:jta | thread | managed | custom.Class )
<property name="current_session_context_class">thread</property>
常用的是:
①、thread:是从上下文找、只有当前没有Session时,才创建一个新的Session,主要从数据界定事务
②、jta:主要从分布式界定事务,运行时需要Application Server来支持(Tomcat不支持)
③、managed:不常用
④、custom.Class:不常用
目的:管理一个数据库的任务单元。
session.save(Object)
session的save方法是向数据库中保存一个对象,这个方法产生对象的三种状态
session.delete(Object)
Object对象需要有ID
对象删除后,对象状态为Transistent状态
格式: Session.load(Class arg0, Serializable arg1) throws HibernateException
* arg0:需要加载对象的类,例如:User.class
* arg1:查询条件(实现了序列化接口的对象):例"4028818a245fdd0301245fdd06380001"字符串已经实现了序列化接口。如果是数值类类型,则hibernate会自动使用包装类,例如 1
* 此方法返回类型为Object,但返回的是代理对象。
* 执行此方法时不会立即发出查询SQL语句。只有在使用对象时,它才发出查询SQL语句,加载对象。
* 因为load方法实现了lazy(称为延迟加载、赖加载)
* 延迟加载:只有真正使用这个对象的时候,才加载(才发出SQL语句)
* hibernate延迟加载实现原理是代理方式。
* 采用load()方法加载数据,如果数据库中没有相应的记录,则会抛出异常对象不找到(org.hibernate.ObjectNotFoundException)
try {
session = sf.openSession();
session.beginTransaction();
User user = (User)session.load(User.class,1);
//只有在使用对象时,它才发出查询SQL语句,加载对象。
System.out.println("user.name=" + user.getName());
//因为此的user为persistent状态,所以数据库进行同步为龙哥。
user.setName("发哥");
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally{
if (session != null){
if (session.isOpen()){
session.close();
}
}
}
格式:Session.get(Class arg0, Serializable arg1)方法
* arg0:需要加载对象的类,例如:User.class
* arg1:查询条件(实现了序列化接口的对象):
例"4028818a245fdd0301245fdd06380001"字符串已经实现了序列化接口。如果是基数类型,则hibernate会自动转换成包装类,如 1
返回值:
此方法返回类型为Object,也就是对象,然后我们再强行转换为需要加载的对象就可以了。
如果数据不存在,则返回null;
注:执行此方法时立即发出查询SQL语句。加载User对象
加载数据库中存在的数据,代码如下:
try {
session = sf.openSession();
session.beginTransaction();
* 此方法返回类型为Object,也就是对象,然后我们再强行转换为需要加载的对象就可以了。
如果数据不存在,则返回null
* 执行此方法时立即发出查询SQL语句。加载User对象。
*/
User user = (User)session.get(User.class, 1);
//数据加载完后的状态为persistent状态。数据将与数据库同步。
System.out.println("user.name=" + user.getName());
//因为此的user为persistent状态,所以数据库进行同步为龙哥。
user.setName("龙哥");
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally{
if (session != null){
if (session.isOpen()){
session.close();
}
}
1. 不存在对应记录时表现不一样;(load抛异常,get返回null)
2. load返回的是代理对象,等到真正使用对象的内容时才发出sql语句,这样就要求在第一次使用对象时,要求session处于open状态,否则出错
3. get直接从数据库加载,不会延迟加载
get()和load()只根据主键查询,不能根据其它字段查询,如果想根据非主键查询,可以使用HQL
1. 用来更新detached对象,更新完成后转为为persistent状态(默认更新全部字段)
2. 更新transient对象会报错(没有ID)
3. 更新自己设定ID的transient对象可以(默认更新全部字段)
4. persistent状态的对象,只要设定字段不同的值,在session提交时,会自动更新(默认更新全部字段)
5. 更新部分更新的字段(更改了哪个字段就更新哪个字段的内容)
a)方法1:update/updatable属性
xml:设定<property>标签的update属性,设置在更新时是否参与更新
<property name="name" update="false"/>
注意:update可取值为true(默认):参与更新;false:更新时不参与更新
annotation:设定@Column的updatable属性值,true参与更新,false:不参与更新
@Column(updatable=false)
public String getTitle() {return title;}
注意:此种方法很少用,因为它不灵活
b )方法二:dynamic-update属性
注意:此方法目前只适合xml方式,JAP1.0 annotation没有对应的
在实体类的映射文件中的<class>标签中,使用dynamic-update属性,true:表示修改了哪个字段就更新哪个字段,其它字段不更新,但要求是同一个session(不能跨session),如果跨了session同样会更新所有的字段内容。
<class name="com.bjsxt.Student" dynamic-update="true">
代码:
@Test
public void testUpdate5() {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Student s = (Student)session.get(Student.class, 1);
s.setName("zhangsan5");
//提交时,会只更新name字段,因为此时的s为persistent状态
session.getTransaction().commit();
s.setName("z4");
Session session2 = sessionFactory.getCurrentSession();
session2.beginTransaction();
//更新时,会更新所有的字段,因为此时的s不是persistent状态
session2.update(s);
session2.getTransaction().commit();
}
如果需要跨session实现更新修改的部分字段,需要使用session.merget()方法,合并字段内容
@Test
public void testUpdate6() {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Student s = (Student)session.get(Student.class, 1);
s.setName("zhangsan6");
session.getTransaction().commit();
s.setName("z4");
Session session2 = sessionFactory.getCurrentSession();
session2.beginTransaction();
session2.merge(s);
session2.getTransaction().commit()}
这样虽然可以实现部分字段更新,但这样会多出一条select语句,因为在字段数据合并时,需要比较字段内容是否已变化,就需要从数据库中取出这条记录进行比较
c )、使用HQL(EJBQL)面向对象的查询语言(建议)
@Test
public void testUpdate7() {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Query q = session.createQuery(
"update Student s set s.name='z5' where s.id = 1");
q.executeUpdate();
session.getTransaction().commit();
}
在执行的时候hibernate会检查,如果对象在数据库中已经有对应的记录(是指主键),则会更新update,否则会添加数据save
清除session缓存
无论是load还是get,都会首先查找缓存(一级缓存,也叫session级缓存),如果没有,才会去数据库查找,调用clear()方法可以强制清除session缓存
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Teacher t = (Teacher)session.load(Teacher.class, 1);
System.out.println(t.getName());
session.clear();
Teacher t2 = (Teacher)session.load(Teacher.class, 1);
System.out.println(t2.getName());
session.getTransaction().commit();
注意:这样就会发出两条SELECT语句,如果把session.clear()去除,则只会发出一条SELECT语句,因为第二次load时,是使用session缓存中ID为1的对象,而这个对象已经在第一次load到缓存中 了。
在hibernate中也存在flush这个功能,在默认的情况下session.commit()之前时,其实执行了一个flush命令。
Session.flush功能:
1. 清理缓存( 将缓存和数据库同步);
2. 执行sql(确定是执行SQL语句(确定生成update、insert、delete语句等),然后执行SQL语句。)
Session在什么情况下执行flush:
默认在事务提交时执行;
注意:flush时,可以自己设定,使用session.setFlushMode(FlushMode)来指定。
session.setFlushMode(FlushMode);
FlushMode的枚举值:
1. FlushMode.ALWAYS:任务一条SQL语句,都会flush一次
2. FlushMode.AUTO :自动flush(默认)
3. FlushMode.COMMIT: 只有在commit时才flush
4. FlushMode.MANUAL:手动flush。
5. FlushMode.NEVER :永远不flush 此选项在性能优化时可能用,比如session取数据为只读时用,这样就不需要与数据库同步了
注意:设置flush模式时,需要在session开启事务之前设置。
1. 可以显示的调用flush;
2. 在执行查询前,如:iterate.
注:如果主键生成策略是uuid等不是由数据库生成的,则session.save()时并不会发出SQL语句,只有flush时才会发出SQL语句,但如果主键生成策略是native由数据库生成的,则session.save的同时就发出SQL语句。
例如:session.evict(user)
作用:从session缓存(EntityEntries属性)中逐出该对象
但是与commit同时使用,会抛出异常
session = HibernateUtils.getSession();
tx = session.beginTransaction();
User1 user = new User1();
user.setName("李四");
user.setPassword("123");
user.setCreateTime(new Date());
user.setExpireTime(new Date());
//利用Hibernate将实体类对象保存到数据库中,因为user主键生成策略采用的是uuid,所以调用完成save后,只是将user纳入session的管理,不会发出insert语句,但是id已经生成,session中的existsInDatabase状态为false
session.save(user);
session.evict(user);//从session缓存(EntityEntries属性)中逐出该对象
//无法成功提交,因为hibernate在清理缓存时,在session的临时集合(insertions)中取出user对象进行insert操作后需要更新entityEntries属性中的existsInDatabase为true,而我们采用evict已经将user从session中逐出了,所以找不到相关数据,无法更新,抛出异常。
tx.commit();
解决在逐出session缓存中的对象不抛出异常的方法:
在session.evict()之前进行显示的调用session.flush()方法就可以了。
session.save(user);
//flush后hibernate会清理缓存,会将user对象保存到数据库中,将session中的insertions中的user对象清除,并且会设置session中的existsInDatabase状态为false
session.flush();
session.evict(user);//从session缓存(EntityEntries属性)中逐出该对象
//可以成功提交,因为hibernate在清理缓存时,在Session的insertions中集合中无法找到user对象所以不会发出insert语句,也不会更新session中existsInDatabase的状态。
tx.commit();
使用new操作符初始化的对象不是立刻就持久的。它们的状态是瞬时的,也就是说它们没有任何跟数据库表相关联的行为,只要应用不再引用这些对象(不再被任何其它对象所引用),它们的状态将会丢失,并由垃圾回收机制回收
持久实例是任何具有数据库标识的实例,它有持久化管理器Session统一管理,持久实例是在事务中进行操作的----它们的状态在事务结束时同数据库进行同步。当事务提交时,通过执行SQL的INSERT、UPDATE和DELETE语句把内存中的状态同步到数据库中。
Session关闭之后,持久化对象就变为离线对象。离线表示这个对象不能再与数据库保持同步,它们不再受hibernate管理。
1. 有没有ID,(如果没有则是Transient状态)
2. ID在数据库中有没有
3. 在内存里有没有(session缓存)
Transient对象:随时可能被垃圾回收器回收(在数据库中没有于之对应的记录,应为是new初始化),而执行save()方 法后,就变为Persistent对象(持久性对象),没有纳入session的管理
内存中一个对象,没有ID,缓存中也没有
Persistent对象:在数据库中存在的对应的记录,纳入session管理。在清理缓存(脏数据检查)的时候,会和数据库同 步。
内存中有、缓存中有、数据库有(ID)
Detached对象:也可能被垃圾回收器回收掉(数据库中存在对应的记录,只是没有任何对象引用它是指session引用), 注引状态经过Persistent状态,没有纳入session的管理
内存有、缓存没有、数据库有(ID)
这里指的关系不是数据库的关系,而是对象与对象间的关系。
存在以下关系:
1.一对一:单向(主键、外键)、双向(主键、外键)
2.一对多:单向、双向
3.多对一:单向、双向
4.多对多:单向、双向
5.集合映射:list、set、map
6.继承关系映射:单表、多表、一张主表多张子表
7.组件映射:@Embeddable、@Embedded
有两种策略可以实现一对一的关联映射:
1. 主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来 维护它们之间的关系,仅通过表的主键来关联。
2. 唯一外键关联:外键关联,本来是用于多对一的配置,但是如果加上唯一的限制之后,也可以用来表示一对一关 联关系。
对象模型:
实体类:
/** 人-实体类 */
public class Person {
private int id;
private String name;
public int getId() {return id; }
public void setId(int id) { this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
/**身份证-实体类*/
public class IdCard {
private int id;
private String cardNo;
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getCardNo() { return cardNo;}
public void setCardNo(String cardNo) {this.cardNo = cardNo;}
}
1、说明:
人—-> 身份证号(Person