Hibernate
目前企业级应用一般均采用面向对象的开发方法,而内存中的对象数据不能永久存在,如想借用关系数据库来永久保存这些数据的话,无疑就存在一个对象-关系的映射过程。在这种情形下,诞生了许多解决对象持久化的中间件,其中开源的Hibernate由于其功能与性能的优越而备受Java程序员青睐。
1 ORM简介
1.2.hibernate普通属性延迟加载
@Lob
@Basic(fetch=FetchType.LAZY)
可真正使用的时候,它还是会被加载出来
需要进行字节码增强操作,详情如下:
http://java.chinaitlab.com/Hibernate/917279.html
对象-关系映射ORM(Object-Relation Mapping)是用来将对象和对象之间的关系对应到数据库表与表之间的关系的一种模式。
1.1 持久化及持久层
所谓持久化就是把内存中的数据同步保存到数据库(如关系数据库)或永久存储设备(如硬盘、磁带等)中,其原理如图1-1所示。
以前使用的JDBC操作(如新增记录、删除记录及修改记录)其实就是一个持久化的过程。所谓持久层就是专门负责持久化工作的逻辑层,由它统一与数据库层打交道。这样一来,便可以将以前的三层模型(表示层、业务逻辑层和数据库层)修改成四层模型(表示层、业务逻辑层、持久层和数据库层)。四层模型的内部关系如图所示。
从上图不难发现,持久层封装了所有与数据库有关的操作和细节,作为一个专业的持久层中间件(如Hibernate),除了具备基本的新增数据、删除数据、修改数据、查询数据功能之外,还必须提供连接管理、事务管理、性能管理、缓存管理、对象-关系映射等高级功能,以满足专业的开发需求。
1.2 JDBC的局限性
对于小型的应用开发而言,使用JDBC也许感觉还不错。但对于大型应用开发而言,单凭JDBC就显得有些力不从心了,例如,从上百张拥有几十个字段的数据表中取数据的话,可以想象要写多少个getXXX()语句完成数据读取工作,暂不说烦琐的代码,最让人头痛的还有高出错率和低复用性。
在当今多层体系结构的软件开发中,使用JDBC很难将持久层进行分离,负责业务逻辑代码编写的程序员必须密切关注数据库各表的结构关系,以确保其SQL语句工作的正常。如果引入Hibernate这样的持久层中间件的话,业务逻辑层的开发人员整天面对的就是一个又一个的对象而不必关心数据表,既有利于团队分工协作,又提高了软件产品的可移植性。
从易用性与高效性角度来说,JDBC在记录的批量操作、多表连接、表单级联方面表现并不优秀,Hibernate对此提供了自己的解决方案,使得与数据库层的交互既高效又稳定。
1.3 实体域模型与关系数据模型
模型是对现实的一种抽象。
实体域模型则是对真实世界中的物质实体(如学生、老师、商品等)的抽象,它由实体域对象组成,实体域对象用来代表这些真实世界中的物质实体。在Java EE应用中,实体域对象是指实体EJB或POJO(JavaBean形式的实体域对象),若无特别声明,本书所说的实体域对象默指POJO。
例如,在客户关系管理系统中城市实体类City.java为:
与上面City实体域对象对应的关系模型如表所示。
综上所述,实体域模型是面向对象的,而关系数据模型是面向关系型数据库的,它们之间的数据交换需要一个映射的过程。
例如,实体域对象City与关系数据表city之间可通过Hibernate的映射配置文件City.hbm.xml进行映射。
1.4 ORM中间件
大量的企业应用开发人员的实践表明,直接采用第三方提供的ORM中间件是可行之道。因为企业自行开发ORM中间件的话,不但对开发人员的专业知识要求相当高,而且产品质量与性能很难保证。目前,市面上除了几款商业化的ORM中间件(如TopLink、FrontierSuite等)外,优秀的开源ORM中间件(如Hibernate、IBATIS、JPOX等)也不少。
无论使用商业化的ORM中间件,还是使用开源的ORM中间件,我们都应该使业务逻辑层与ORM中间件保持相对独立,这有利于以后应用的升级与移植。
2 Hibernate简介
2.1 Hibernate相关包
从Hibernate网站上下载开发所需包,拷贝到tomcat的lib目录(所有项目可共享,推荐)或是你的项目lib目录下。
说明:
(1)hibernate3.jar:Hibernate的核心包,所以是必须的jar包.
(2)antlr-2.7.6.jar:一个语言转换工具 Hibernate 利用它实现 HQL 到 SQL的转换模板相关操作需要包。
(3)dom4j.jar:dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。这是必须使用的jar包,Hibernate用它来读写配置文件。 (4)commons-collections.jar:Apache Commons包中的一个,包含了一些Apache开发的集合类,功能比java.util.*强大。必须使用的jar包。
(5)javassist-3.12.0.GA.jar:是必须的.
(6)jta-1.1.jar:当使用JTA规范时,必须加入。JTA全称是 Java Transaction API (java 事务 API),一般情况下也是必须的.
(7)slf4j-api-1.6.1.jar:必须的,因为在Hibernate的核心包中多处用到了,比如onfiguration.class中用到了。
(8)slf4j-nop-1.6.1.jar:slf4j的实现类,所以也是必须的,注意实现类要与slf4j的版本要匹配。
(9)hibernate-jpa-2.0-api-1.0.1.Final.jar:Hibernate使用Annotation(注解)需加,所以说如果不用Annotation的话就不用加的.
(以上的包是Hibernate3.5以上的版本)
2.2 Hibernate开发体验
1)将hibernate相关包加入tomcat的lib目录下,创建一个web应用项目
2)在hibernate.cfg.xml(项目配置文件)中加入该实体类映射
3)创建一个实体类User.java
4)创建该实体类的配置文件User.hbm.xml
5)创建一个类实现session对象的创建
6)创建一个测试类,运行该项目
2.3 hibernate的体系结构
在分层体系架构中,Hibernate负责应用程序与数据库之间的数据交换,起ORM中间件的作用,具体关系如下图所示。
从上图可以看出,Hibernate与数据库的连接配置信息均封装到hibernate.properties或hibernate.cfg.xml文件中,对象-关系的映射工作依靠ORM映射文件进行,最终完成对象与关系间的平滑映射。
2.4 hibernate API
2.4.1 hibernate API
Hibernate作为ORM中间件出现,使得应用程序通过Hibernate的API就可以访问数据库。由于Hibernate只是对JDBC做了一个轻量级的封装(未完全封装),因此亦可绕过Hibernate,直接使用JDBC API来访问数据库。不过,作为面向对象的应用开发技术体系而言,笔者推荐尽量使用Hibernate API。
Hibernate API根据版本的不同,所属的包也不一样。Hibernate2.1的API位于net.sf.hibernate包中,而Hibernate 3.X的API则位于org.hibernate包中。其核心hibernate 3.x API的5个核心接口:
1、 Configuraction接口:配置Hibernate,启动Hibernate,创建SessionFactory对象。
2、 SessionFactory接口:初始化Hibernate,当数据存储源的代理,创建Session对象。
SessionFactory以下特点:
(1) 它是线程安全的,这意味着它的同一个实例可以被应用的多个线程共享
(2) 它是重量级的,这意味着不能随意创建或销毁它的实例。
因为sessionFactory需要一个很大的缓存,用来存放预定义的SQL语句以及映射元数据等。用户还可以为sessionFactory配置二级缓存。
3、 Session接口:负责保存、更新、删除、加载和查询对象。
Session接口是Hibernate应用使用最广泛的接口。Session也被称为持久化管理器,它提供了和持久化相关的操作。
Session有以下特点:
(1) 不是线程安全的,因此在设计软件架构时,应该避免多个线程共享同一个session实例。
(2) Session是轻量级的,因为它在创创建和销毁是不需要消耗太多的资源。
Session有一个缓存,被称为一级缓存,它存放被当前工作单元加载的对象。每个session实例都有自己的缓存,这个session实例的缓存只能被当前工作单元访问。
4、 Transaction:管理事务。
Transaction接口是Hibernate的数据库事务接口,它对数据库底层的事务接口进行封装,底层事务接口包括:
(1)JDBC API
(2)JTA(JavaTransaction API)
(3)CORBA(CommonObject Request Broker Architecture)API
5、 Query和Criteria接口:执行数据库查询。
Query和Criteria接口是Hibernate的查询接口,用于向数据库查询对象,以及控制执行查询的过程。Query实例包装了一个HQL(Hibernate Query Language)查询语句。HQL查询是面向对象,它引用类名及类的属性名。Criteria接口完全封装了基于字符串形式的查询语句,比Query接口更加面向对象,Criteria接口擅长于执行动态查询。
小知识:关于线程安全。如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。线程安全问题都是由全局变量及静态变量引起的(全局变量和静态变量是存放于堆中,对于堆中数据的读写是分为两步操作的)。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
小知识:JVM中堆、栈、方法区的基本概念。
栈(stack):每个方法被执行的时候都会创建一个stack frame用于储存局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈道出栈的过程。 如果线程所请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError,如果虚拟机栈可以动态扩展,当扩展时无法申请足够的内存时会抛出OutOfMemoryError。
堆(heap): 它是虚拟机管理的内存中最大的一块。它是被所有线程共享的一块区域,是虚拟机创建的。它是存放对象实例,几乎所有对象实例都在堆上分配。它是垃圾回收器管理的主要区域。当缺少内存时出现OutOfMemoryError。
方法区(method area):它和java堆一样,是各个线程共享的内存区域,它用于存储以被虚拟器加载的类信息/常量/静态变量/即时编译器编译后的代码等数据。如果它无法满足内存分配,将抛出OutOfMemoryError异常。运行时常量池:它是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述性信息外,还有一个是常量池(Constant Pool Table),用于存放编译器生成的各种字面量和符号引用,这部分内容会在类加载后存放到方法去的运行时常量池中。当缺少内存时出现OutOfMemoryError。
2.4.2 使用Hibernate操作数据库需要七个步骤
(1)读取并解析配置文件
Configuration conf = new Configuration().configure();
创建一个Configuration对象,并通过该对象的configure()方法加载Hibernate配置文件。
(2)读取并解析映射信息,创建SessionFactory
SessionFactory sf = conf.buildSessionFactory();
完成配置文件和映射文件的加载后,将得到一个包括所有Hibernate运行期参数的Configuration实例,通过Configuration实例的buildSessionFactory()方法可以构建一个唯一的SessionFactory,构建SessionFactory要放在静态代码块中,因为它只在该类被加载时执行一次。
(3)打开Session
Session session = sf.openSession();
一般的持久化方法(CRUD)都是通过Session来调用的。
Session是一个轻量级对象,通常将每个Session实例和一个数据库事务绑定,也就是每执行一个数据库事务,都应该先创建一个新的Session实例,在使用Session后,还需要关闭Session。
Session是非线程安全的。Session中包含了数据库操作相关的状态信息,如果多个线程同时使用一个Session实例进行CRUD,就很有可能导致数据存取的混乱,在hibernate中threadlocal类解决这个问题,线程局部变量(ThreadLocal)其实的功用非常简单, 就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有一个该变量。
ThreadLocal类本身不代表线程要访问的变量,这个类的成员变量才是。JDK1.5给ThreadLocal加了泛型功能,即ThreadLocal的泛型T才是要线程的本地变量。线程通过ThreadLocal的get和set方法去访问这个变量T。
小知识:获得session实例有两种方法:getSession()和getCurrentSession(),它们的区别:
(1)通过SessionFactory的方法getCurrentSession()获取当前线程中的Session,当调用时,hibernate将session绑定到当前线程,事务结束后,hibernate将session从当前线程中释放,并且关闭 session。当再次调用getCurrentSession()时,将得到一个新的session,并重新开始这一系列工作。
(2)通过SessionFactory创建Session实例时,代码如下。
Session session=sessionFactory.openSession();
创建Session后,就可以通过创建的Session进行持久化操作了,在创建Session实例后,不论是否执行事务,最后都需要关闭Session实例,释放Session实例占用的资源。即关闭Session实例:session.close();
(3)getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭;
(4)getCurrentSession () 使用当前的session,openSession() 重新建立一个新的session
(5)getCurrentSession本地事务(本地事务:jdbc)时 要在配置文件里进行如下设置
* 如果使用的是本地事务(jdbc事务)
<property name="hibernate.current_session_context_class">thread</property>
* 如果使用的是全局事务(jta事务)
<property name="hibernate.current_session_context_class">jta</property>
当 SessionFactory 启动时,Hibernate 会根据配置创建相应的 CurrentSessionContext,在 getCurrentSession() 被调用的时候,实际被执行的方法是
CurrentSessionContext.currentSession() 。在 currentSession() 执行时,如果当前 Session 为空,currentSession 会调用 SessionFactory 的 openSession。因此, 对于 Java EE 来说,getCurrentSession()是更好地获取 Session 的方法。
(4)开始一个事务(增删改操作必须,查询操作可选)
Transaction tx = session.beginTransaction();
Hibernate本身是不具备Transaction处理功能的,Hibernate的Transaction实际上是底层的JDBC Transaction或者JTA Transaction的封装,可通过hibernate.properties或者hibernate.cfg.xml中的配置JDBCTransaction或者是JTATransaction,默认情况下使用JDBCTransaction,如果你配置为: hibernate.transaction.factory_class =
net.sf.hibernate.transaction.JTATransactionFactory
将使用JTATransaction 。
(5)数据库操作
session.save(user);//或其它操作
(6)提交事务或者回滚事务
tx.commit(); 或者tx.rollback();
(7)关闭session
session.close();
2.5 应用hibernate API
在应用中使用Hibernate,首先必须进行Hibernate与数据库连接设置、连接池参数设置及ORM映射文件的创建,如图所示。
在使用了Hibernate的应用(简称"Hibernate应用")中,Hibernate的启动与内部分工协作的关系,如图所示。
综上所述,不难看出,Hibernate Web应用的开发一般经过以下几个步骤。
(1)创建数据库。
(2)将Hibernate所需的JAR包复制到WEB-INF/lib下。
(3)创建Hibernate的配置文件。
(4)利用Hibernate的第三方工具或Eclipse的有关插件从数据库中创建出相应的实体对象及其ORM映射文件。
(5)创建Hibernate的SessionFactory类。
(6)通过SessionFactory创建Session实例。
(7)通过创建的Session实例进行持久化对象的管理。
(8)通过创建的Transaction实例进行事务管理。
(9)通过创建的Query或Criteria实例实现数据库的查询。
2.5.1 配置Hibernate
配置Hibernate主要就是创建Hibernate的配置文件和SessionFactory类,Hibernate的配置文件可以是hibernate.properties或hibernate.cfg.xml(二者任选其一),由于hibernate.cfg.xml配置的便捷与完整性,使之成为Hibernate配置文件之首选。
下面是hibernate.cfg.xml的配置文件。
2.5.2创建sessionFactory
配置好hibernate.cfg.xml后,推荐将其保存到WEB-INF/classes下,接下来就可以创建自己的SessionFactory了,例如,MySessionFactory.java。
2.5.3调用sessionFactory
创建好sessionFactory,在应用中就可以调用Hibernate API进行持久化操作了,例如,SystemPart.java(第10行):
2.5.4 Hibernate的映射
Hibernate的映射配置文件是实体对象与数据库关系表之间相互转换的重要依据,一般而言,一个映射配置文件对应数据库中一个关系表,关系表之间的关联关系也在映射文件中进行配置。
目前,已经有很多的第三方工具(如Middlegen和MyEclipse等)能自动从数据库中导出相应的映射配置文件。
假设定义了一个用户类Users,对应数据库中有一个用户表users。
数据库关系表users与应用中的持久化类Users.java的映射文件Users.hbm.xml为:
小知识:static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用,但是不能在其他类中通过类名来直接引用,这一点很重要。实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用。
2.6 用户管理实例
案例[UserManagerDemo]
3 Hibernate的映射
3.1 Hibernate的基本映射数据类型
Hibernate的基本映射数据类型是Java基本类型与标准SQL类型相互转换的桥梁,其关系如图。
通过Hibernate的基本映射数据类型可以非常方便地将数据从一种形式转换成另一种形式,完成高质量的ORM任务。例如:
下面通过实例演示上述映射数据类型的用法。[案例HibernateDataMapDemo]
假如在Sql server数据库中有一张关系表ProductInfo,如下表所示。
则对应的持久化类应定义为:
相应的配置文件为Product.hbm.xml
小知识:BLOB数据。
文件类型包括ASCII和二进制,在SQL SERVER中存储的数据值主要是ASCII字符组成。ASCII字符组成的文件用记事本即可解析,但二进制文件则无法使用记事本进行正确解析和编辑。二进制数据包括图像和可执行文件汇编程序,BLOB即是指大型二进制文件。
二进制文件可存储在数据库或文件系统中,一般小些的二进制对象文件存储于数据库中。若是二进制对象较大,则放在文件系统中性能、碎片处理等方面会更好些。
SQL SERVER 2008中存储二进制数据的类型有:(1)Binary:文件大小固定,最大长度可达8000字节;(2) VarBinary(n): 文件大小可变,最大长度可达8000字节,(n指明最大文件长度);(3)VarBianry(max): 文件大小可变,不限最大长度。(4)image:最大长度为2GB(用于向下兼容,以后将取消)。读者可根据BLOB数据的容量大小来决定采用上面4个类型中的其中一个。
在hibernate中可通过注释方式将byte[]类型映射为blob类型
3.2 Hibernate的主键映射
3.2.1 hibernate中的内置主键
关系数据库中依靠主键来区分不同的记录,主键又有自然主键与代理主键之分。自然主键是指充当主键的字段本身具有一定的含义,是构成记录的组成部分,比如学生的学号,即该字段既为主键,同时又是学生记录的一个字段;代理主键则是充当主键的字段本身不具有业务含义,只起主键的作用,比如自动增长类型的ID号等。在Hibernate应用方案中,极力推荐使用代理主键。
在Hibernate应用中,Hibernate依靠对象标识符(OID)来区分不同的持久化对象。而对象标识符(OID)则可以通过Hibernate内置的标识生成器来产生。
下表列出了常用的Hibernate标识生成器。
标识生成器名称 |
描 述 |
assigned |
OID由业务逻辑程序负责产生,Hibernate只是负责持久化,常用于映射自然主键 |
hilo |
OID由Hibernate按照high/low算法产生,该算法需要从数据库的某个表的字段中读取high值 |
increment |
OID由Hibernate依递增方式产生,该算法依赖保存于当前应用实例中的一个最大值变量,当有多个应用实例需要访问数据库时难免出现重复的主键,应当谨慎使用 |
identity |
OID由底层数据库的自增主键生成机制产生,如MySQL的auto_increment类型主键与SQL Server的identity类型主键 |
sequence |
OID由底层数据库的sequence主键生成机制产生,如Oracle sequence |
native |
根据底层数据库对自动生成OID能力的支持,具体选择identity、sequence或hilo生成器来产生OID,常用于跨平台应用 |
根据所采用的数据库产品,来决定选用哪种Hibernate内置的标识生成器。果数据库产品为MySQL或SQL Server,则优先考虑identity生成器,如果是Oracle则可考虑sequence生成器,如果想提高应用的可移植性,开发跨平台的应用则选用native生成器。
3.2.2 hibernate中的主键映射
Hibernate的主键映射就是将数据库中的主键与持久化类的对象标识符(OID)进行映射,同时指定一款标识生成器。
例如,MySQL、Sybase、DB2或SQL Server数据库的主键映射:
DB2、Oracle、PostgreSQL、Interbase、McKoi或SAP DB数据库的主键映射:
跨平台应用中的主键映射:
自然主键的映射:
复合自然主键的映射:
在新增记录时,自然主键是需要显式赋值,而代理主键则无需显式赋值。下面是相应的代码:
在查询记录时,自然主键需要给出主键所含字段在实际记录中的值,而代理主键则只需给系统自动生成的序号即可,相应代码如下 :
3.3 Hibernate的实体映射
3.3.1 映射一对一实体关联关系
Hibernate有两种映射实体一对一关联关系的实现方式:共享主键方式和唯一外键方式。所谓共享主键方式就是限制两个数据表的主键使用相同的值,通过主键形成一对一映射关系。
共享主键方式:
例如,在电子商务应用中,通常将会员的登录账号信息和会员详细信息分别存放到两张不同的表中。
登录账号信息表login
字 段 名 称 |
数 据 类 型 |
主 键 |
自 增 |
允 许 为 空 |
描 述 |
ID |
int(4) |
是 |
|
|
ID号 |
LOGINNAME |
char(20) |
|
|
√ |
登录账号 |
LOGINPWD |
char(20) |
|
|
√ |
登录密码 |
会员详情表company
字 段 名 称 |
数 据 类 型 |
主 键 |
自 增 |
允 许 为 空 |
描 述 |
ID |
int(4) |
是 |
增1 |
|
ID号 |
COMPANYNAME |
varchar(100) |
|
|
√ |
公司名称 |
LINKMAN |
char(20) |
|
|
√ |
联系人 |
TELEPHONE |
char(20) |
|
|
√ |
联系电话 |
|
varchar(100) |
|
|
√ |
电子邮箱 |
会员详情表与登录账号表属于典型的一对一关联关系,可按共享主键方式进行。数据库表的关联关系如图。
说明:上图有误,login映射文件中描述该类的id采用的是company的外键,因此,login的主键是同时是主键和外键。
相应的持久化类之间的关联关系如图。
持久化类Login.java:
持久化类Company.java:
上面两个持久化类中互有一个对方类的属性对象。
login表与Login类的ORM映射文件Login.hbm.xml:
company表与Company类的ORM映射文件Company.hbm.xml:
说明:上例中login将company的主键以外键形式作为自己的主键,login是被company类所关联,即当company表中的记录删除,则login表中的记录会被执行相同操作。
测试用例:
测试用例运行分析。
(1)执行了addCompany()以后,Hibernate向数据库服务器提交的SQL语句为:
由于Company.hbm.xml中one-to-one 标签的cascade="all",所以当对Company进行了保存(save)操作后,与Company一对一关联的Login也同样会执行保存(save)操作。
(2)执行了loadCompany(1)以后,Hibernate向数据库服务器提交的SQL语句为:
在默认情况下,Hibernate对一对一关联采用迫切左外连接的检索策略。
唯一外键方式:
所谓唯一外键方式就是一个表的外键和另一个表的唯一主键对应形成一对一映射关系,这种一对一的关系其实就是多对一关联关系的一种特殊情况而已。例如,在电子商务应用中,客户详情表与地址表也属于典型的一对一关联关系,由于地址表的主键在会员详情表中以外键存在,因此可按唯一外键方式进行。
客户详情表client
字 段 名 称 |
数 据 类 型 |
主 键 |
自 增 |
允 许 为 空 |
描 述 |
ID |
int(4) |
是 |
增1 |
|
ID号 |
CLIENTNAME |
char(20) |
|
|
√ |
客户姓名 |
PHONE |
char(20) |
|
|
√ |
联系电话 |
|
varchar(100) |
|
|
√ |
电子邮箱 |
ADDRESS |
int(4) |
|
|
|
联系地址 |
地址表address
字 段 名 称 |
数 据 类 型 |
主 键 |
自 增 |
允 许 为 空 |
描 述 |
ID |
int(4) |
是 |
增1 |
|
ID号 |
PROVINCE |
varchar(40) |
|
|
√ |
省份 |
CITY |
varchar(40) |
|
|
√ |
城市 |
STREET |
varchar(100) |
|
|
√ |
街道 |
ZIPCODE |
char(10) |
|
|
√ |
邮编 |
客户详情表和地址表的一对一关联关系如图。
|
|
持久化类Client.java:
持久化类Address.java:
client表与Client类的ORM映射文件Client.hbm.xml:
其实到此为止,已经实现了从Client到Address的单向关联,如果要实现双向关联的话,还需要将Address.hbm.xml修改为:
测试用例:
测试用例运行分析。
(1)执行了addClient()以后,Hibernate向数据库服务器提交的SQL语句为:
由于Client.hbm.xml中many-to-one 标签的cascade="all",所以当对Client进行了保存(save)操作后,与Client一对一关联的Address也同样会执行保存(save)操作。
(2)执行了loadClient(1)以后,Hibernate向数据库服务器提交的SQL语句为:
由于Client.hbm.xml中many-to-one 标签的lazy="false",在装载Client后,会立即装载与Client一对一关联的Address。
小知识:LAZY(懒加载)与EAGER(立即加载)的区别。
例如,一个用户组(group)对应多个用户(user)。采用LAZY和EAGER方式加载的效果是:
EAGER:会将需要加载的类(user)以及其关联的对象(group)一起放入内存中。如果session关闭了仍能处理其关联的对象(group)。
LAZY: 只有当要访问关联的对象(user)时,才从数据库中取得。如果session关闭后被关联的对象不在内存中,此时是无法再取得被关联对象(group) 的。
3.3.2 映射多对一单向实体关联关系
实体之间的多对一单向关联关系是比较常见的一种关联关系,比如订单与客户、购物车与顾客之间的关系都属于多对一关联关系。这种多对一的关联关系在关系型数据库中通过外键参照是很容易实现的。
例如,在电子商务应用中,订单表与客户表的多对一关联如图。
客户表customer
字 段 名 称 |
数 据 类 型 |
主 键 |
自 增 |
允 许 为 空 |
描 述 |
ID |
int(4) |
是 |
增1 |
|
ID号 |
CNAME |
char(20) |
|
|
√ |
客户姓名 |
BANK |
varchar(40) |
|
|
√ |
联系电话 |
PHONE |
varchar(20) |
|
|
√ |
电子邮箱 |
订单表orders
字 段 名 称 |
数 据 类 型 |
主 键 |
自 增 |
允 许 为 空 |
描 述 |
ID |
int(4) |
是 |
增1 |
|
ID号 |
ORDERNO |
varchar(40) |
|
|
√ |
订单号 |
MONEY |
varchar(40) |
|
|
√ |
金额 |
CUSTOMER |
Int(4) |
|
|
√ |
客户 |
客户表和订单表的一对多关联关系如图。
持久化类Customer.java:
持久化类Orders.java:
customer表与Customer类的ORM映射文件Customer.hbm.xml:
orders表与Orders类的ORM映射文件Orders.hbm.xml:
由于Orders.hbm.xml中的lazy="false",在装载Orders后,会立即装载与Orders一对一关联的Customer。当通过DAO查询订单时,可以通过Orders.getCustomer().getCname()方法同时获取该订单对应的客户信息。
3.3.2 映射一对多单向实体关联关系
也可以将多对一的实体关联关系用一对多的实体关联关系来描述。
持久化类Customer.java:
持久化类Orders.java:
customer表与Customer类的ORM映射文件Customer.hbm.xml:
orders表与Orders类的ORM映射文件Orders.hbm.xml:
小知识:多对一与一对多关联的区别。
“单向一对多/多对一关系”只需在“一”/“多”方进行配置,单向多对一关联是最常见的单向关联关系。不管多对一还是一对多,都是在"多"的一端添加一个外键指向"一"的一端,只不过是多对一是在多的一端为其自己添加外键,而一对多则是在一的一端为多的一端添加外主键。例如:一个用户组(group)对应多个用户(user)。因多对一关联映射维护的关系为多到一的关系,当载入一个用户(user)时将会同时载入组(group)的信息。它的关系映射将写在多的一端(user):
<many-to-one name="group" column="relatedGroup" cascade="all"/>
此时它在多的一端(user)添加了一个外键“relateGroup”指向一的一端,即在多的一端通过group维护一的一端。
而一对多关联映射维护的关系为一到多的关系,当载入一个组(group)时,将会同时载入此组用户(user)的信息。它的关系映射将写在一的一端(group):
<set name="users" order-by="name">
<key column="relatedGroup"/>
<one-to-many class="com.dvnchina.hibernate.User"/>
</set>
此时,通过<key column="relatedGroup"/>在多的一端(user)添加了一个外键“relateGroup”指向一的一端,即在一的一端通过users维护多的一端。
总之,一对多和多对一的映射策略是一致的,都是通过在"多"的一端添加一个外键指向"一"的一端,只是站的角度不同。
3.3.3 映射多对一双向实体关联关系
多对一双向是最常见的双向关联关系。在电子商务应用中,经常会有这样的需求:根据给定的客户,得到该客户的所有订单;根据给定的订单,得到该订单的所属客户。对于这种双向关联的情况,在Hibernate应用中,也有人叫多对一双向关联,只是叫法不同而已,都是通过映射一对多双向关联关系来实现的。
不管是多对一单向关联还是(一对多)多对一双向关联,在关系型数据库中的表现方式都是一样的,均通过外键参照来实现如图。
只是在持久化类与ORM映射文件中单向关联与双向关联存在一些区别而已,如图。
持久化类Customer.java:
持久化类Orders.java:
customer表与Customer类的ORM映射文件Customer.hbm.xml:
orders表与Orders类的ORM映射文件Orders.hbm.xml:
小知识:多对一双向关联与一对多、多对一的关系。
当只需要从一方获取另一方的数据时 就使用单向关联,双方都需要获取对方数据时,就使用双向关系。例如:部门--人员,使用人员时,如果只需要获取对应部门信息(user.getdeptarment()),而不需要从部门的人员信息时,就配置成单向。多对一单向关联只在多的一方进行配置,而双向多对一关联就是在多方和一方都进行配置,并在“一”方通过属性inverse="true"设置控制关系反转。双向多对一关联实际上是“多对一”与“一对多”关联的组合。
小知识:cascade和inverse的使用。[案例OneToManyMapDemo]
inverse 有两个值 true ,false(默认值) ;如果设置为true 则表示当前对象不负责将级联对象的状态变化同步到数据库 ;设置false则相反。
cascade 有五个选项 分别是: all ,delete ,none,save-update,delete-orphan ;
all : 所有情况下均进行关联操作。
none:所有情况下均不进行关联操作。这是默认值。
save-update:在执行save/update/saveOrUpdate时进行关联操作。
delete:在执行delete时进行关联操作。
delete-orphan: 当save/update/saveOrUpdate时,相当于save-update ;当删除操作时,相当于delete ;
高效地作法:在“多”方设置Inverse=false, cascade=all,在“一”方设置inverse=true,这样的话,只要对多方直指插入或是删除多方对象就行了,一旦多方对象删除则会级联删除一方对象。
注意:
(1)<one-to-many>、<many-to-one>元素中无inverse,但有cascade属性
(2)<set>元素可设置inverse和cascade属性
(3)在一对多单向关系中,只能<set>中设置inverse=“false”(可不设,默认即为false),即由“many”方来进行关联关系的维护
(4)在多对多关系中,建议只设置其中一方inverse=“false”,或双方都不设置
(5)在多对一和多对多中,通常不设置cascade,而一对多或是一对一中则需要看需求而定,是否设置cascade
3.3.4 映射一对多双向实体自身关联关系
在电子商务应用中,通常用一张表来存放所有的商品分类(见表4-16),分类之间的层级关系是典型的自身一对多双向关联关系,也同样通过外键参照来实现。从某种意义上讲,一对多双向自身关联关系只是一对多双向关联关系的一种特殊情况,因为,参与关联的双方都是同一个持久化类,也即自身。
商品分类表goodscate
字 段 名 称 |
数 据 类 型 |
主 键 |
自 增 |
允 许 为 空 |
描 述 |
ID |
int(4) |
是 |
增1 |
|
ID号 |
CATE_NO |
varchar(40) |
|
|
√ |
类别编号 |
CATE_NAME |
varchar(60) |
|
|
√ |
类别名称 |
PARENT |
varchar(20) |
|
|
√ |
所属大类的类别 |
商品分类表goodscate的一对多关联关系的自身外键参照实现如图4-21所示。
|
|
相应的持久化类Goodscate的一对多双向自身关联关系如图。
|
|
持久化类Goodscate.java:
goodscate表与Goodscate类的ORM映射文件Goodscate.hbm.xml:
|
|
新增处理相应代码片段如下。
3.3.5 映射多对多双向实体关联关系 [案例ManyToManyMapDemo]
在电子商务应用中,多对多的关联关系也非常普遍,诸如订单与商品、商品与购物车之间的关系均属于多对多的关联关系。多对多关联在关系数据库中不能直接实现,还必须依赖一张连接表用于保存这种关联关系。
下面以订单表与商品表为例来说明多对多单向关联关系的映射,订单表与商品表的连接表为selecteditems。
订单表与商品表之间的多对多单向关联通过连接表来实现,如图。
|
|
持久化类订单到商品之间的多对多单向关联关系如图。
|
|
持久化类Orders.java:
持久化类Items.java:
items表与Items 类的ORM映射文件Items.hbm.xml:
orders表与Orders类的 ORM映射文件Orders.hbm.xml:
items表与Items类的ORM映射文件Items.hbm.xml:
说明:上面两个映射文件中应有一inverse=false才行,此例中全部写为true是不对的
几种关联关系的描述方法总结:
关系类型 |
关联描述 |
一对一 |
主表 被关联表 |
一对多 |
|
多对一 |
|
多对多 |
|
3.4 Hibernate的操作持久化对象
3.4.1 实体对象的生命周期
实体对象的生命周期在Hibernate应用中是一个很关键的概念,所谓的实体对象的生命周期就是指实体对象由产生到被GC回收的一段过程。实体对象生命周期有三种状态。
(1)自由状态/临时状态(Transient)
所谓的Transient状态,即实体对象在内存中自由存在,与数据库中的记录无关,通常是我们的J2EE中VO(普通的java对象(POJO)),并没有被纳入Hibernate的实体管理容器。
Test test = new Test();
test.setName("energykk");
//此时的test对象处于Transient(自由状态)并没有被Hibernate框架所管理
特性:
①不在Session的缓存中,不与任何的Session实例相关联。
②在数据库中没有与之相对应的记录。
(2)持久状态(Persistent)
何谓 Persistent? 即实体对象已经处于被Hibernate实体管理容器所管理的状态。这种状态下这个实体对象的引用将被纳入Hibernate实体管理容器所管理。处于Persistent状态的实体对象,对它的变更也将被固化到数据库中。在J2EE中通常指的是一个PO(持久对象)。
Transaction tr = session.beginTransaction();
session.save(test);
//此时的test对象已经处于Persistent(持久状态)它被Hibernate纳入实体管理容器
tr.commit();
Transaction tr2=session.beginTransaction();
test.setName("xukai");
//在这个事务中我们并没有显示的调用save()方法但是由于persistent状态的对象将会自动的固化到时数据库中,因此此时正处在persistent状态的test对象的变化也将自动补充同步到数据库中
tr2.commit();
处于Persistent状态的实体可以简单的理解为:如果一个实体对象与session发生了关联,并且处于session的有效期内,那么这个实体对象就处于Persistent状态。
特性
①在Session的缓存中,与Session实例相关联。
②在数据库中存在与之相对应的记录。
3.游离状态(Detached)/脱管状态
处于Persistent状态的实体对象,其对应的session关闭以后,那么这个实体就处于Detached状态。可以认为session对象就是一个Persistent的宿主,一旦这个宿主失效,那么这个实体就处于Detached状态。
session.close();
//与test对象关联的session被关闭,因此此时的test对象进入Detached(游离状态)
session2 = HibernateSessionFactory.getSession();
Transaction tr3 = session2.beginTransaction();
session2.update(test);
//此时正处于Detached状态的test对象由于再次借助与session2被纳入到Hibernate的实体管理容器所以此时的test对象恢复到Persistent状态
test.setName("jjjj");
tr3.commit();
session2.close();
特性
①不在Session的缓存中,不与任何的Session实例相关联。
②在数据库中存在与之相对应的记录。(前提条件是没有其他Session实例删除该条记录)。
小知识:Transient 与Detached两个状态的实体区别。
Transient状态的实体与Detached状态的实体都与Hibernate的实体管理容器没有关系。但处于Transient状态的只有一个Name的属性,此时的test对象所包含的数据信息仅限于此,与数据库中的记录没有任何瓜葛。但是处于Detached状态的实体已经不止包含Name这个属性,还被赋予了主键也就是通常POJO里的id属性,由于id是主键,它可以确定数据库表中的一条唯一的记录,那么,处于Detached状态的实体就能与数据库表中拥有相同id的记录相关联。简而言之,Transient状态的实体缺乏与数据库表记录之间的联系,而Detached状态的试题恰恰相反。只不过是脱离了session这个数据库操作平台而已。
3.4.1 操作持久化实体的方法
1.beginTranscation()方法
通过使用session的beginTranscation()方法,可以开辟一个事务管理单元,对业务代码进行事务管理,若该事务管理单元出现异常,则对已提交的事务进行回滚。典型用法:
2.close()方法
Session的close()方法用于关闭当前Session实例,断开该Session实例的JDBC连接及清除其缓存,同时位于该Session缓存中的持久化对象将由持久化状态变为游离状态。
3.delete()方法
Session的delete(Object object)用于删除数据库中与指定对象object所对应的记录。典型用法:
4.get()方法
Session的get(Class entityClass, Serializable id)方法,用于从数据库加载指定的持久化对象到Session缓存中,若该记录不存在则返回null。典型用法:
5.load()方法
Session的load(Class entityClass, Serializable id)方法,用于从数据库加载指定的持久化对象到Session缓存中,如果指定记录不存在则招聘异常。
小知识:关于get()和load()方法的区别。
hibernate中get方法和load方法的根本区别在于:如果你使用load方法,hibernate认为该id对应的对象(数据库记录)在数据库中是一定存在的,所以它可以放心的使用,它可以放心的使用代理来延迟加载该对象。在用到对象中的其他属性数据时才查询数据库,但是万一数据库中不存在该记录,那没办法,只能抛异常,所说的load方法抛异常是指在使用该对象的数据时,数据库中不存在该数据时抛异常,而不是在创建这个对象时。由于session中的缓存对于hibernate来说是个相当廉价的资源,所以在load时会先查一下session缓存看看该id对应的对象是否存在,不存在则创建代理。所以如果你知道该id在数据库中一定有对应记录存在就可以使用load方法来实现延迟加载。
对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查数据库,数据库中没有就返回null。
对于第2点,虽然好多书中都这么说:“get()永远只返回实体类”,但实际上这是不正确的,get方法如果在session缓存中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是原先的代理对象,而不是实体类对象,如果该代理对象还没有加载实体数据(就是id以外的其他属性数据),那么它会查询二级缓存或者数据库来加载数据,但是返回的还是代理对象,只不过已经加载了实体数据。
3。get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;反而load方法创建时首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库。
总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。
6.save()方法
Session的save(Object obj)方法用于将一个临时对象加载到Session缓存中,使之变为持久化对象,当Session缓存被清理时,向数据库发送一条insert语句,在数据库中新增一条与该持久化对象相应的记录。典型用法:
7.update()方法
Session的update(Object obj)方法用于将一个处于游离状态的对象加载到Session缓存中,与一个具体的Session实例关联,使之变为持久化对象,当Session缓存被清理时,向数据库发送一条update语句,在数据库中更新与之对应的记录内容。典型用法:
8.saveOrUpdate()方法
Session的saveOrUpdate (Object obj)方法将根据指定对象的状态进行相应的处理。如果指定对象为临时对象,则该方法相当于save(Object obj)方法的作用;如果指定对象为游离对象,则该方法相当于update(Object obj)方法;如果指定对象为持久化对象,则直接返回。
3.5 注释(Annotation)
传统的Hibernate的配置依赖于外部 XML 文件:数据库映射被定义为一组 XML 映射文件,并且在启动时进行加载。在项目中因此会出现大量的 Hibernate 映射文件。在最新的几个Hibernate版本中,出现了一种基于 Java 5 注释的更为巧妙的新方法。借助新的 Hibernate Annotation 库,即可一次性地分配所有旧映射文件(即按照开发者的想法来定义),将注释直接嵌入到项目中的 Java 类中,并提供一种强大及灵活的方法来声明持久性映射。
Annotation提供了一条与程序元素关联任何信息的途径。Annotation类似于修饰符一样,可用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中,这些信息被存储在annotation的“name=value”结构对中。
Annotation类型是一种接口,能够通过java反射API的方式提供对其信息的访问。需要注意的是:Annotaion不能影响程序代码的执行,无论增加、删除annotation,代码都始终如一的执行。Java语言解释器在工作时,会忽略这些Annotation,因此,只有通过某种配套的工具才会对annotation类型中的信息进行访问和处理。
小知识:注释的相关概念。
元数据:是关于数据的数据,是在编程语言上下中添加到方法、字段、类和包之外的额外信息,元注释是元数据的一种表示方式,也称为注释或注解,可用于创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件(hibernate注释即起到这种作用)。在Java中有内置和自定义两种,如内置有@override(方法重写)、@Deprecated(过时)、@SupressWarnings(关闭警告信息)等。
下面通过一个自定义注释来理解注释的使用机制。定义一个注释Now,代码如下:
(1)@Target里面的 ElementType是用来指定Annotation类型可以用在哪一些元素上的,包括TYPE(类型),METHOD(方法),FIELD(字段),PARAMETER(参数)等。其中TYPE是指可以用在Class,Interface等类型上。
(2)@Retention中的RetentionPolicy是指Annotation中的信息保留方式,分别是SOURCE、CLASS 和RUNTIME(默认)。SOURCE表示该Annotation类型的信息只会保留在程序源码里,源码如果经过了编译之后,Annotation的数据就会消失,不会保留在编译好的.class文件里面。ClASS表示该Annotation类型的信息保留在程序源码里,同时也会保留在编译好的.class文件里面,但在执行时不会把这一些信息加载到虚拟机(JVM)中去。RUNTIME表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去。
(3)@Documented表示生成javadoc文档时,也将该Annotation写入帮助文档中。
另外,再定义一个权限annotation,之后编写一个测试程序使用上面所定义的注释接口类。
[案例DefineAnnotationDemo]
小知识:Java反射机制。
1.通常情况下,使用Java类需要在编译时确定,并由JVM加载、实例化,之后才能使用。而Java反射机制则在运行时才确定所需的类,即在程序运行过程中加载、探知、自审,这种伤使用在编译期未确定的类,即这个特性被称为反射。Java 的反射机制知道类的基本结构,这种对 Java 类结构探知的能力,被称为 Java 类的“自审”
Java中类的加载有三种方式:
1)命令行启动应用时候由JVM初始化加载
2)通过Class.forName()方法动态加载(类装载的并被初始化,即执行静态块)
3)通过ClassLoader.loadClass()方法动态加载(类装载但不被初始化)
其中,Class.forName("xx.xx",true,CALL Class.class.getClassLoader()),第二个参数(bool)表示装载类的时候是否初始化该类,即调用类的静态块的语句及初始化静态成员变量。ClassLoader.loadClass()其实与Class.forName("xx.xx",false,loader)是一致的,只是loader.loadClass("xx.xx")执行的是更底层的操作。第二种方式即是通过反射方式加载类的。例:Class c = Class.forName("TestClass"); //通过反射加载类TestClass
TestClass object = (TestClass)c.newInstance()//实例化TestClass
2.与反射机制相关类
Class:当一个类被加载以后, Java 虚拟机就会自动产生一个 Class 对象。通过这个 Class 对象我们就能获得加载到虚拟机当中这个 Class 对象对应的 方法、成员以及构造方法的声明和定义等信息。
反射API:用于获取在当前 Java 虚拟机中的类、接口或者对象信息。包括:一个对象的类信息,一个类的访问修饰符、成员、方法、构造方法以及超类的信息,属于一个接口的常量和方法声明。还可以创建一个直到程序运行期间才知道名字的类的实例;获取并设置一个对象的成员(该成员可能在程序运行期间才知道);检测一个在运行期间才知道名字的对象的方法。
[案例DefineAnnotationDemo]
由上例可知,类的全路径是在程序运行的时候,由用户输入的,而虚拟机事先并不知道所要加载类的信息,这就是利用反射机制来对用户输入的类全路径对类自身的一个自审,从而探知该类所拥有的方法和属性。
jcreate等开发工具中,编写程序时jcreate工具所提供的帮助功能,写出类时按鼠标右键即可得到相应的方法和属性,与上例的原理是相同的。
3.使用反射机制的步骤
1)获得需操作类的 java.lang.Class 对象
若已得到类的实例,则使用Class c = 对象名 .getClass();
若在编译期知道类的名字,你可以使用如下的方法:Class c = java.awt.Button.class;
或者 Class c = Integer.TYPE;
若类名在编译期不知道, 但是在运行期可以获得,则使用下面的方法:Class c = Class.forName(strg);
2)调用诸如 getDeclaredMethods 的方法
3)使用反射 API来操作这些信息
使用hibernate注释,需要有Hibernate 3.2和Java 5的支持,hibernate注释分为两个部分:逻辑映射注释和物理映射注释,通过逻辑映射注释可用描述对象模型,类之间的关系等等,而物理映射注释则描述了物理的schema,表,列,索引等等。下面列出常用Annotation的用法说明。
1、@Entity(name="EntityName")
必须,name为可选,对应数据库中一的个表
2、@Table(name="",catalog="",schema="")
可选,通常和@Entity配合使用,只能标注在实体的class定义处,表示实体对应的数据库表的信息
Name:可选,表示表的名称。默认情况下表名和实体名称一致,只有在不一致的情况下才需要指定表名
Catalog:可选,表示Catalog名称,默认为Catalog("")。
Schema:可选,表示Schema名称,默认为Schema("")。
3、@id
必须,@id定义了映射到数据库表的主键的属性,一个实体只能有一个属性被映射为主键。置于getXxxx()前。
4、@GeneratedValue(strategy=GenerationType,generator="")
可选
Strategy:表示主键生成策略,有AUTO、INDENTITY、SEQUENCE 和 TABLE 4种,分别表示让ORM框架自动选择、根据数据库的Identity字段生成、根据数据库表的Sequence字段生成,以及根据一个额外的表生成主键,默认为AUTO。
Generator:表示主键生成器的名称,这个属性通常和ORM框架相关,例如,Hibernate可以指定uuid等主键生成方式。
5、@Column
可选,@Column描述了数据库表中该字段的详细定义,这对于根据JPA注解生成数据库表结构的工具非常有用。
name:表示数据库表中该字段的名称,默认情形属性名称一致
nullable:表示该字段是否允许为null,默认为true
unique:表示该字段是否是唯一标识,默认为false
length:表示该字段的大小,仅对String类型的字段有效
insertable:表示在ORM框架执行插入操作时,该字段是否应出现INSETRT语句中,默认为true
updateable:表示在ORM框架执行更新操作时,该字段是否应该出现在UPDATE语句中,默认为true。对于一经创建就不可以更改的字段,该属性非常有用,如对于birthday字段。
columnDefinition:表示该字段在数据库中的实际类型。通常ORM框架可以根据属性类型自动判断数据库中字段的类型,但是对于Date类型仍无法确定数据库中字段类型究竟是DATETIME还是TIMESTAMP。此外,String的默认映射类型为VARCHAR,如果要将String类型映射到特定数据库的BLOB或TEXT字段类型,该属性非常有用。
示例:
@Column(name="BIRTH",nullable="false",columnDefinition="DATE")
public String getBithday() {
return birthday;
}
6、@Basic(fetch=FetchType,optional=true)
可选,@Basic表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的getXxxx()方法,默认即为@Basic。
fetch: 表示该属性的读取策略,有EAGER和LAZY两种,分别表示主动获取和延迟加载,默认为EAGER。
Optional:表示该属性是否允许为null,默认为true。
示例:
@Basic(optional=false)
public String getAddress() {
return address;
}
7、@Transient
可选,@Transient表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。
如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则ORM框架默认其注解为@Basic。
示例:
//根据birth计算出age属性
@Transient
public int getAge() {
return getYear(new Date()) - getYear(birth);
}
8、@ManyToOne(fetch=FetchType,cascade=CascadeType)
可选,@ManyToOne表示一个多对一的映射,该注解标注的属性通常是数据库表的外键
optional:是否允许该字段为null,该属性应该根据数据库表的外键约束来确定,默认为true。
fetch:表示获取策略,默认为FetchType.EAGER
cascade:表示默认的级联操作策略,可以指定为ALL(包含所有级联)、PERSIST(当调用persist()方法时会级联保存)、MERGE(当调用merge()方法时会级联更新)、REFRESH(级联刷新:当多个用户同时操作一个实体,为了获取实时的数据,可在使用实体数据前调用refresh()方法)和REMOVE(当调remove()方法时会级联删除,)中的若干组合,默认为无级联操作
targetEntity:表示该属性关联的实体类型.该属性通常不必指定,ORM框架根据属性类型自动判断targetEntity.
示例:
//订单Order和用户User是一个ManyToOne的关系
//在Order类中定义(为user属性的get方法定义)
@ManyToOne()
@JoinColumn(name="USER_ID")
public User getUser() {
return user;
}
9、@JoinColumn
可选,@JoinColumn和@Column类似,这里描述的不是一个简单字段,而是一个关联字段,例如,描述一个@ManyToOne的字段:
name:该字段的名称。由于@JoinColumn描述的是一个关联字段,如ManyToOne,则默认的名称由其关联的实体决定。
例如,实体Order有一个user属性来关联实体User,则Order的user属性为一个外键,
其默认的名称为实体User的名称+下划线+实体User的主键名称
示例:
见@ManyToOne
10、@OneToMany(fetch=FetchType,cascade=CascadeType)
可选,@OneToMany描述一个一对多的关联,该属性应该为集体类型,在数据库中并没有实际字段。
fetch:表示获取策略,默认为FetchType.LAZY,因为关联的多个对象通常不必从数据库预先读取到内存。
cascade:表示级联操作策略,对于OneToMany类型的关联非常重要,通常该实体更新或删除时,其关联的实体也应当被更新或删除
例如,实体User和Order是OneToMany的关系,则实体User被删除时,其关联的实体Order也应该被全部删除
示例:
@OneTyMany(cascade=ALL)
@JoinColumn(name = “一的一方的ID“);
public List getOrders() {
return orders;
}
11、@OneToOne(fetch=FetchType,cascade=CascadeType)
可选,@OneToOne描述一个一对一的关联
fetch:表示获取策略,默认为FetchType.LAZY
cascade:表示级联操作策略
示例:
@OneToOne(fetch=FetchType.LAZY)
public Blog getBlog() {
return blog;
}
12、@ManyToMany
可选,@ManyToMany 描述一个多对多的关联。多对多关联上是两个一对多关联,但是在ManyToMany描述中,中间表是由ORM框架自动处理
targetEntity:表示多对多关联的另一个实体类的全名,例如:package.Book.class
mappedBy:表示多对多关联的另一个实体类的对应集合属性名称,定义了属性名称所属类为双向关系的维护端
示例:
Goods实体表示商品,Order实体表示订单,为了描述订单所购买的商品,可以在Goods和Order之间建立ManyToMany关联。利用ORM工具自动生成的表除了GOODS和ORDERS表外,还自动生成了一个GOODS_ORDERS表,用于实现多对多关联。
//多对多的一方
//负责将级联的对象同步到数据库
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "GOODS_ORDER", joinColumns = {@JoinColumn(name = "GOODS_ID", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "ORDER_ID", referencedColumnName ="id")})
public Set<Order> getOrder() {
return order;
}
//多对多的另一方
@ManyToMany(fetch = FetchType.LAZY,targetEntity = com.Entity.Goods.class,mappedBy="order")
public Set<Goods> getGoods() {
return goods;
}
说明:两个实体间相互关联的属性必须标记为@ManyToMany,并相互指定targetEntity属性,需要注意的是,有且只有一个实体的@ManyToMany注解需要指定mappedBy属性,指向targetEntity的集合属性名称
13、@MappedSuperclass
可选,@MappedSuperclass可以将超类的JPA注解传递给子类,使子类能够继承超类的JPA注解
示例:
@MappedSuperclass
public class Employee() {
....
}
@Entity
public class Engineer extends Employee {
.....
}
@Entity
public class Manager extends Employee {
.....
}
14、@Embedded
可选,@Embedded将几个字段组合成一个类,并作为整个Entity的一个属性。
例如:User包括id、name、city、street、zip属性。
如果想要将city、street、zip属性映射为Address对象,则User对象将具有id、name和address这三个属性。Address对象必须定义为@Embededable
示例:
15、Hibernate验证注解
包括四个部分:
注解
适用类型
说明
示例
(1)@Pattern
String
例:通过正则表达式来验证字符串
@attern(regex=”[a-z]{6}”)
(2)@Length
String
例:验证字符串的长度
@length(min=3,max=20)
(3)@Email
String
例:验证一个Email地址是否有效
(4)@Range
Long
例:验证一个整型是否在有效的范围内
@Range(min=0,max=100)
(5)@Min
Long
例:验证一个整型必须不小于指定值
@Min(value=10)
(6)@Max
Long
例:验证一个整型必须不大于指定值
@Max(value=20)
(7)@Size
集合或数组
例:集合或数组的大小是否在指定范围内
@Size(min=1,max=255)
说明:为了强化EJB3的能力,Hibernate提供了与其自身特性相吻合的特殊注解。org.hibernate.annotations包中包含了所有的这些注解扩展。例如:@OnDelete(action=OnDeleteAction.CASCADE)
定义于多方(被连接的子类):在删除时使用SQL级联删除,而非通常的Hibernate删除机制,即删除一方时会级联删除多方有外键关联的所有记录。
Customer(n):Room(1)
Customer实体类:
测试用例,删除Room将级联删除相关Customer
Annotation(注释)的应用实例:
Customer(n):Room(1)中的实体和hibernate配置。
Customer实体类代码:
Room实体类代码:
hibernate.cfg.xml
3.6 Hibernate的查询HQL
Hibernate支持强大且易于使用的面向对象查询语言(HQL)。 如果希望通过编程的方式创建查询,Hibernate提供了完善的按条件(Query By Criteria, QBC)以及按样例(Query By Example, QBE)进行Hibernate查询的功能。 你也可以用原生SQL(native SQL)描述Hibernate查询,Hibernate额外提供了将结果集(result set)转化为对象的支持。
一、查询的基本步骤
(1)获得session,创建事务transaction
(2)HQL和原生SQL(native SQL)查询需要创建一个Query对象
(3)调用 Query对象的相应方法(如list())实现查询的功能,并返回结果集
(4)利用迭代器Iterator遍历查询结果集
二、查询语法
(1)from子句(查询所有记录)
语法:from 实体类名 //返回实体对象集合
例如:from Customer,这里的实体类名不需要写类的全名,auto-import可以缺省自动引入。
(2)带条件的查询
语法:from 实体类名 where 属性名1=:name And 属性名2=? //返回符合条件的实体对象集合
例如:from Customer where name=?
也可以直接使用SQL语句,但返回的是属性,而非实体对象,如下面代码返回的是t_student实体类对象的所有属性。
(3)连接查询(内连、外连和交叉连接)
◆ 内连查询(inner join):把两个实体类对应的表中共有的部分查出来
◆ 左外连接(left join):把左边的部分全查出来
◆ 右外连接(right join):把右边的部分全查出来
三、查询实例:
(1)查询语句
(2)查询结果遍历
如果返回的是属性而非对象时,遍历时返回的每个属性对应一个object对象。
Object[] objs = (Object[])diter.next()
(3)查询结果
查询结果可以在select从句中指定类的属性,甚至可以调用SQL统计(aggregate)函数。 属性或统计结果被认定为"标量(Scalar)"的结果(而不是持久(persistent state)的实体)。
3.1、类型转换
经过createSQLQuery查询出来的结果集中,Long类型会变成BigDecimal类型,这种类型要先转化为String之后才能转为Long。
(4)绑定参数
接口Query提供了对命名参数(named parameters)和JDBC风格的问号(?)参数两种绑定方法。 不同于JDBC的是,Hibernate对参数从0开始计数。命名参数(named parameters)在查询字符串中是形如:name的标识符。使用命名参数(named parameters)的优点是: 命名参数(named parameters)与其在查询串中出现的顺序无关,它们可在同一查询串中多次出现。
也可以使用setParameter()方法来填充参数:
传多个参数时:
也可利用库函数做为参数值
(5)分页查询
如果需要指定结果集的范围(希望返回的最大行数/或开始的行数),可使用Query接口提供的方法: setFirstResult()表示从0开始;setMaxResults()表示每页显示多少条数据
(6)聚合函数
查询语句中可以使用的聚合属性:
支持的聚集函数有:
· avg(...), sum(...), min(...), max(...)
· count(*)
· count(...), count(distinct ...), count(all...)
(7)表达式(Expressions)
where从句中的表达式允许你使用SQL中的很多东西:
· 数学运算符: +, -, *, /
· 二元比较运算符: =, >=, < =, < >, !=, like
· 逻辑操作符: and, or, not
· 字符串连接符: ||
· SQL函数,如: upper() and lower()
· 圆括号: ( )
· in, between, is null,is not null
· JDBC输入参数: ?
· 指定的参数::name, :start_date, :x1
· in和between:
和否定形式的(negated forms):
· 也可以使用特殊的属性size或size()函数来测试集合的大小:
对于有索引的集合,你可以使用特殊属性minIndex和maxIndex来引用最小索引和最大索引。
表达式中的[]的内部可以是一个算术表达式:
HQL为one-to-many关联和值的集合提供了内置的index()函数:
一个综合查询的例子:
3.7 Hibernate的检索策略
Hibernate的检索策略包括类级别检索策略和关联级别检索策略。检索策略有三种:
什么是延迟检索:
1)将不执行select 查询,而是创建Grage类的代理类实例,会初始化其OID属性(但包含原来的所有属性和方法)
2)访问Grade类的OID属性时不会查询数据库
3)第一次访问Grade类的非OID 属性时,查询数据库
3.7.1 检索策略
一、类级别检索策略
类级别检索策略有立即检索和延迟检索,hibernate3.x默认的检索策略是延迟检索。对于Session的检索方式,类级别检索策略仅适用于load方法;也就说,对于get、query检索,持久化对象都会被立即加载而不管lazy是false还是true。
二、关联级别检索策略
关联级别检索策略,要给它配置属性fetch,以orders和customers为例:
在映射文件中,用set元素来配置一对多关联关系,set元素有lazy和fetch属性
1、lazy:主要决定orders集合被初始化的时机,既到底是在加载customer对象时被初始化还是在程序访问orders集合时被初始化
2、fetch:当fetch取值为select或subselect时,决定初始化orders的查询语句的形式;若取值为join(在HQL查询中配置文件中设置的join方式是不起作用的),则决定orders集合属性被初始化的时机
fetch的主要用途:在查询Parent对象的时候,默认只有Parent的内容,并不包含childs的信息,如果在Parent.hbm.xml里设置lazy="false"的话才同时取出关联的所有childs内容.
问题是我既想要Hibernate默认的性能又想要临时的灵活性该怎么办? 就可以用Fetch解决,在HQL查询中使用迫切左外连接即可。
3、若把fetch设置为join,lazy属性将被忽略
1)当fetch的值为join时,不管lazy的属性值为什么,采用的策略都是迫切左外联接检索
Hibernate: selectcustomers0_.id as id0_1_, customers0_.realName as realName0_1_,customers0_.pass as pass0_1_, customers0_.sex as sex0_1_, customers0_.petNameas petName0_1_, customers0_.email as email0_1_, customers0_.rdate as rdate0_1_,orders1_.cid as cid0_3_, orders1_.id as id3_, orders1_.id as id1_0_,orders1_.number as number1_0_, orders1_.address as address1_0_, orders1_.phoneas phone1_0_, orders1_.odate as odate1_0_, orders1_.sum as sum1_0_,orders1_.status as status1_0_, orders1_.cid as cid1_0_ from pros.customerscustomers0_ left outer join pros.Orders orders1_ on customers0_.id=orders1_.cidwhere customers0_.id=?
2)当fetch的值为select时,采用的策略主要根据lazy的取值决定
Lazy=true
Hibernate: selectcustomers0_.id as id0_0_, customers0_.realName as realName0_0_, customers0_.passas pass0_0_, customers0_.sex as sex0_0_, customers0_.petName as petName0_0_,customers0_.email as email0_0_, customers0_.rdate as rdate0_0_ frompros.customers customers0_ where customers0_.id=?
Lazy=false
Hibernate: selectcustomers0_.id as id0_0_, customers0_.realName as realName0_0_,customers0_.pass as pass0_0_, customers0_.sex as sex0_0_, customers0_.petNameas petName0_0_, customers0_.email as email0_0_, customers0_.rdate as rdate0_0_from pros.customers customers0_ where customers0_.id=?
Hibernate: select orders0_.cidas cid0_1_, orders0_.id as id1_, orders0_.id as id1_0_, orders0_.number asnumber1_0_, orders0_.address as address1_0_, orders0_.phone as phone1_0_,orders0_.odate as odate1_0_, orders0_.sum as sum1_0_, orders0_.status as status1_0_,orders0_.cid as cid1_0_ from pros.Orders orders0_ where orders0_.cid=?
Lazy=extra
Hibernate: select customers0_.id as id0_0_,customers0_.realName as realName0_0_, customers0_.pass as pass0_0_,customers0_.sex as sex0_0_, customers0_.petName as petName0_0_,customers0_.email as email0_0_, customers0_.rdate as rdate0_0_ frompros.customers customers0_ where customers0_.id=?
3) 当fetch的值为subselect时,lazy的属性值同样分为false、true和extra三种情况,采用的检索策略为嵌套子查询(检索多个customer对象时)lazy属性决定检索策略
三、两种策略使用比较
1、立即检索策略
优点:
对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象
缺点:
1) select语句的数目太多,需要频繁地访问数据库,会影响检索性能。如果需要查询n个Customer对象,那么必须执行n+1次select查询语句。这种检索策略没有利用sql的连接查询功能
2) 在应用逻辑只需要访问Customer对象,而不需要访问Order对象的场合,加载Order对象完全是多余的操作,这些多余的Order对象白白浪费了许多内存空间
优先考虑使用的场合:
a. 类级别
b. 应用程序需要立即访问的对象
c. 使用了二级缓存
2、延迟检索策略
优点:
由应用程序决定需要加载哪些对象,可以避免执行多余的select,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。
缺点:
应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。
优先考虑使用的场合
(1)一对多或者多对多关联
(2)应用程序不需要立即访问或者根本不会访问的对象
3、迫切左外连接检索策略
优点:
(1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象
(2)使用了外连接,select语句少
缺点:
(1)可能会加载应用程序不需要访问的对象,浪费内存
(2)复杂的数据库表连接也会影响检索性能
优先考虑使用的场合:
(1)多对一
(2)需要立即访问的对象
(3)数据库有良好的表连接性能
四、一对多关联检索实例
班级grade(1):学生student(n)
Session session = HibernateSessionFactory.getSession();
Query query = session.cerateQuery("from Grade");
List<Grade> list = query.list();
一对多关联检索策略
1)延迟检索:
2)立即检索:
因为 select * from Grade 查到了包括班级的三条记录,所以下面立即跟了三知查询语句。
一共产生了1+n 条查询语句。
减少执行select 语句的条数, 从而减少访问数据里的次数,提高软件性能。
3)fetch的作用:
用来说明加载其关联对象Student时,查询语句的形式是什么
fetch 关键字取值为 join时,对HQL 查询是不起作用的。
另外
1)join属性仅适用于立即查询而不适用于延迟查询
2)当fetch 取值为join 时,lazy 属性的值将被忽略
对于检索策略,需要根据实际情况进行选择。对于立即检索和延迟检索,它们的优点在于select语句简单(每张表一条语句)、查询速度快,缺点在于关联表时需要多条select语句,增加了访问数据库的频率。因此在选择立即检索和延迟检索时,可以考虑使用批量检索策略来减少select语句的数量(配置batch-size属性。
对于迫切左外连接检索,优点在于select较少,但缺点是select语句的复杂度提高,多表之间的关联会是很耗时的操作。另外,配置文件是死的,程序是活的,可以根据需要在程序里显示的指定检索策略(可能经常需要在程序中显示指定迫切左外连接检索)。为了清楚检索策略的配置效果如何,可以配置show_sql属性查看程序运行时Hibernate执行的sql语句。
五、hibernate3.x的检索策略
1) 使用load()方法,一对一,一对多,多对一,多对多检索默认采用的都是延迟检索
2)使用get()方法,一对多、多对一、多对多检索默认采用的都是立即检索
一对一检索默认采用的都是左外连接
四、常见问题
4.1、对象名‘XXX‘无效
在使用Session.createSQLQuery(sql);时,sql语句中表名必须加上 数据库名.dbo.表名。
6.hibernate 注释解释
hibernate注释
注解映射必须满足两大条件:Hibernate3.2以上版本和JSEE 5。
@Entity 类注释,所有要持久化的类都要有
Java代码
@Entity
public class Org implements java.io.Serializable {
}
@Entitypublic class Org implements java.io.Serializable {}
@Id 主键
Java代码
@Id
@GeneratedValue
private String orgId;
private String orgName;
@Id @GeneratedValue private String orgId; private String orgName;
@Column(name="...") 该属性对应表中的字段是什么,没有name表示一样
@Table 对象与表映射
@UniqueConstraint 唯一约束
@Version 方法和字段级,乐观锁用法,返回数字和timestamp,数字为首选
@Transient 暂态属性,表示不需要处理
@Basic 最基本的注释。有两个属性:fetch是否延迟加载,optional是否允许null
@Enumerated 枚举类型
@Temporal 日期转换。默认转换Timestamp
@Lob 通常与@Basic同时使用,提高访问速度。
@Embeddable 类级,表可嵌入的
@Embedded 方法字段级,表被嵌入的对象和@Embeddable一起使用
@AttributeOverrides 属性重写
@AttributeOverride 属性重写的内容和@AttributeOverrides一起嵌套使用
@SecondaryTables 多个表格映射
@SecondaryTable 定义辅助表格映射和@SecondaryTables一起嵌套使用
@GeneratedValue 标识符生成策略,默认Auto
表与表关系映射
@OneToOne:一对一映射。它包含五个属性:
targetEntity:关联的目标类
Cascade:持久化时的级联操作,默认没有
fetch:获取对象的方式,默认EAGER
Optional:目标对象是否允许为null,默认允许
mappedBy:定义双向关联中的从属类。
单向:
@JoinColumn:定义外键(主表会多一字段,做外键)
@OneToMany:一对多映射;@ManyToOne:多对一映射
单向一对多:
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="book_oid")/**book:表;oid:book表的主键;无name会按此规则自动生成*/
单向多对一:
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="author_oid")
关联表格一对多:
@OneToMany(cascade=CascadeType.ALL)
@JoinTable(")})
双向一对多或多对一:
不需要多一张表,只是使用mappedBy:使用在One一方,值为One方类名表示Many的从属类。
// Constructors
...
// Property accessors
...
}
@Entitypublic class Org implements java.io.Serializable { // Fields @Id @GeneratedValue private String orgId; private String orgName; @OneToMany(mappedBy = "org") private List<Department> departments; // Constructors... // Property accessors...}
Java代码
@Entity
public class Department implements java.io.Serializable {
// Fields
@Id
@GeneratedValue
private String id;
private String name;
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="org_orgId")
private Org org;
@OneToMany(mappedBy = "department")
private List<Employee> employees;
// Constructors
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public Org getOrg() {
return org;
}
public void setOrg(Org org) {
this.org = org;
}
/** default constructor */
.
.
.
}
@Entitypublic class Department implements java.io.Serializable { // Fields @Id @GeneratedValue private String id; private String name; @ManyToOne(fetch=FetchType.EAGER) @JoinColumn(name="org_orgId") private Org org; @OneToMany(mappedBy = "department") private List<Employee> employees; // Constructors public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } public Org getOrg() { return org; } public void setOrg(Org org) { this.org = org; } /** default constructor */ . . . }
Java代码
@Entity
public class Employee implements java.io.Serializable {
// Fields
@Id
@GeneratedValue
private String employeeId;
private String employeeName;
private String passWord;
private Integer age;
private Integer sex;
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="department_id")
private Department department;
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
/** default constructor */
...
// Property accessors
...
}
@Entitypublic class Employee implements java.io.Serializable { // Fields @Id @GeneratedValue private String employeeId; private String employeeName; private String passWord; private Integer age; private Integer sex; @ManyToOne(fetch=FetchType.EAGER) @JoinColumn(name="department_id") private Department department; public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; } /** default constructor */ ... // Property accessors ...}
双向多对多:@ManyToMany.单向多对多这里不在赘述(没有太多实际意义)
这个比较简单,看下代码就明白了:
Java代码
@Entity
public class Book implements java.io.Serializable {
@Id
private int id;
private String name;
private float money;
@ManyToMany(cascade = CascadeType.ALL)
private List<Author> authors;
public List<Author> getAuthors() {
return authors;
}
public void setAuthors(List<Author> authors) {
this.authors = authors;
}
...
}
@Entitypublic class Book implements java.io.Serializable { @Id private int id; private String name; private float money; @ManyToMany(cascade = CascadeType.ALL) private List<Author> authors; public List<Author> getAuthors() { return authors; } public void setAuthors(List<Author> authors) { this.authors = authors; } ...}
Java代码
@Entity
public class Author implements java.io.Serializable {
@Id
private int id;
private String name;
private int age;
@ManyToMany(mappedBy="authors")
private List<Book> books;
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
...
}
@Entitypublic class Author implements java.io.Serializable { @Id private int id; private String name; private int age; @ManyToMany(mappedBy="authors") private List<Book> books; public List<Book> getBooks() { return books; } public void setBooks(List<Book> books) { this.books = books; } ...}
需要注意的是:注释最好加在属性上,不要加在get方法上,那样做有时候就会出错。比如:@ManyToMany的时候就会报错!
注意import javax.xx.Entity ,而不是org.hibernate.xx.Entity。
Descn属性不存在于数据库中,用@Transient 注明
------------------------------------------
1,需要: Hibernate库文件,Hibernate Annotations库,ejb3-persstence.jar(Java 持久化API)
sessionFactory=new AnnotationConfiguration().buildSessionFactory();
------------------------------------------
2,<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="annotatedClasses">
<list>
<value>com.onjava.modelplanes.domain.PlaneType</value>
</list>
</property>
</bean>
------------------------------------------
1,@Entity
@Table(name = "teacher_info")
@IdClass(UUIDHexGenerator.class)
public class UserMember implements java.io.Serializable
2,@entity通过getters/setters方法访问,或直接访问他的成员变量。
@Entity(access = AccessType.PROPERTY)
@Entity(access = AccessType.FIELD)
------------------------------------------
映射标识符
1,@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private String id;
2,@Id(generate=GeneratorType.SEQUENCE, generator='SEQ_STORE')
3,@Id(generate=GeneratorType.IDENTITY)
------------------------------------------
映射属性
1,@Transient
2,@Column(name="PLANE_ID", length=80, nullable=true)
3,@Basic(fetch = FetchType.LAZY)
4,@Serialized 凡标识@Serialized的属性将被序列化
public Country getCountry() { ... }
5,@Lob标识了存储对象可能是个CLOB或者BLOB。
@Lob(type=LobType.CLOB)
public String getFullText(){return fullText;}
@Lob(type = LobType.BLOB)
public byte[] getFullCode() {return fullCode;}
@Version 定义乐观锁机制使用
------------------------------------------
关联关系:
一、一对一:
1,@OneToOne(mappedBy = "address")
public User getUser() {
return user;
}
1、两边都定义了@OneToOne,但都没有定义mappedBy,则user和address表都会生成到对方的外键,双方都是这个关系的拥有者。
2、两边都定义了@OneToOne,如果user定义了mappedBy,则在address表生成到user的外键,address是这个关系的拥有者;如果address定义
了mappedBy,则在user表生成到address的外键,user是这个关系的拥有者。
二、一对多,多对一:
2,@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
3,@OneToMany(mappedBy="planeType",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@OrderBy("name")
public List<ModelPlane> getModelPlanes() {
return modelPlanes;
}
其中定义mappedBy的是@OneToMany,也就是说One这一方是关系的拥有者。Many一方的表中生成到关联类的外键。
三、@ManyToMany
private Set authors = new HashSet<Author>();
@ManyToMany
public Set<Author> getAuthors(){
return authors;
}
private Set books = new HashSet<Book>();
@ManyToMany(mappedBy="authors")
public Set<Book> getBooks(){
return books;
}
@ManyToMany会生成中间表,具体表名和字段可以通过@AssociationTable来定义,默认的就可以了,同样关系的非拥有者,需要定义mappedBy属性。
------------------------------------------
命名查询
你也可以通过注解,利用@NameQueries和@NameQuery注解,如下:
@NamedQueries(
{
@NamedQuery(name="planeType.findAll",query="select p from PlaneType p" ),
@NamedQuery(name="planeType.delete",query="delete from PlaneType where id=:id" )
}
)
------------------------------------------
内嵌对象(组件)
@Embedded({
@AttributeOverride(name='iso2', column = @Column(name='bornIso2') ),
@AttributeOverride(name='name', column = @Column(name='bornCountryName') )
})
Country bornIn;
...
}
@Embeddable(access = AccessType.FIELD)
public class Address implements Serializable {
String city;
Country nationality;
}
@Embeddable
public class Country implements Serializable {
private String iso2;
private String name;
public String getIso2() { return iso2; }
public void setIso2(String iso2) { this.iso2 = iso2; }
@Column(name='countryName')
public String getName() { return name; }
public void setName(String name) { this.name = name; }
...
}
------------------------------------------------------------------------------------------------------------
自定义的主键生成策略
@javax.persistence.GeneratedIdTable(
name='GEN_TABLE',
table = @Table(name='GENERATOR_TABLE'),
pkColumnName = 'key',
valueColumnName = 'hi'
)
@javax.persistence.TableGenerator(
name='EMP_GEN',
tableName='GEN_TABLE',
pkColumnValue='EMP',
allocationSize=20
)
@javax.persistence.SequenceGenerator(
name='SEQ_GEN',
sequenceName='my_sequence'
)
package org.hibernate.test.metadata;
school和userMember是一对多关系
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "school_info")
public class SchoolInfo implements java.io.Serializable {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;//hibernate的uuid机制,生成32为字符串
@Column(name = "actcodeId", updatable = false, nullable = true, length = 36)
private String actcodeId;
@Formula("select COUNT(*) from school_info")
private int count;
@Temporal(TemporalType.TIMESTAMP)//不用set,hibernate会自动把当前时间写入
@Column(updatable = false, length = 20)
private Date createTime;
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;// 刚开始我默认insertable=false,但会读取出错提示如下:
// Value '0000-00-00' can not be represented as java.sql.Timestamp
// mappedBy="school"就相当于inverse=true,(mappedBy指定的是不需要维护关系的一端)
// 应该注意的是mappedBy值对应@ManyToOne标注的属性,我刚开始写成"schoolId",让我郁闷了好一会
@OneToMany(mappedBy = "school", cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = UserMember.class)
// 用范性的话,就不用targetEntity了
private List users = new ArrayList();
}
@GeneratedValue(strategy=GenerationType.AUTO)我们常用的自增长机制,我这里采用的是hibernate的uuid生成机制.
需要注意的是import javax.xx.Entity ,而不是org.hibernate.xx.Entity。
郁闷的是我上面用到@Formula,生成的sql竟然是'select COUNT(*) from school_info as formula0_ from school_info schoolinfo0_,当然不能执行了,寻求正解中~!!!!!!!!!
UserMember.java(前面引入的包已经贴过了,下面就不贴了)
@Entity
@Table(name = "teacher_info")//实体类和数据库表名不一致时,才用这个
public class UserMember implements java.io.Serializable {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
@Column(updatable = false, nullable = false, length = 20)
private String logonName;
@Temporal(TemporalType.TIMESTAMP)
@Column(updatable = false, length = 20)
private Date createTime;
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;
@ManyToOne(cascade = { CascadeType.MERGE })
@JoinColumn(name = "schoolId")
private SchoolInfo school;
//注意该类就不用声明schoolId属性了,如果不用@JoinColumn指明关联的字段,hibernate默认会是school_id.