Hibernate 一, Hibernate 介绍: Hibernate 只是一个将持久化类与数据库表相映射的工具,每个持久化类实例均对应于数据库表中的一个数据行而已。用户只需直接使用面向对象的方法操作此持久化类实例,即可完成对数据库表数据的插入、删除、修改、读取等操作。 当然实际的 Hibernate 框架非常复杂,用分层的概念划分的话,它相当于在 业务逻辑处理层 和 数据库底层JDBC驱动之间的一层,即通常说的持久化层,而用户通过 XML 配置文件将具体的持久化类与数据库表映射起来。Hibernate 的实际过程还需依赖 SQL 语言和 JDBC 编程接口,但是 Hibernate 将原本分散的 JDBC 和 SQL 配合产生的接口变成了对象化的接口,定义了自己的基于面向对象设计的 HQL(Hibernate Query Language)查询语言,通过它生成实际的 SQL 语句传递到数据库执行的。 1) Configuration Configuration 类负责管理 Hibernate 运行时需要获取一些底层实现的基本配置信息,如:数据库 URL、数据库用户、数据库用户密码、数据库 JDBC 驱动类、数据库适配器(dialect,用于对特定数据库支持)等。 Hibernate 的配置文件为 hibernate.cfg.xml 或者 hibernate.properties,缺省在 CLASSPATH 路径下, 可调用如下进行初始化: Configuration config = new Configuration().configure(); 2) SessionFactory SessionFactory 负责创建 Session 实例,通过 Configuation 实例创建它: SessionFactory sessionFactory = config.buildSessionFactory(); 如果需要访问多个数据库,要分别为其创建对应的 SessionFactory 实例。 一个程序里面有一个SessionFactory就够用了,但是Session是线程不安的,所以一个线程需要一个Session SessionFactory 实例中保存了当前数据库配置的所有映射关系,同时也负责维护当前的二级数据缓存和 Statement Pool。一般是将查询结果放在二级缓存里.是session 缓存的副本; 3) Session 如果设置成线程安全的,不值得 Session 是 Hibernate 持久化操作的基础,提供了如save、update、delete等这些持久化操作。 Session 实例是由SessionFactory 创建的,并且是非线程安全的,如下: Session session = sessionFactory.openSession(); 创建了实例,就可以使用它完成持久层操作,如下: // 新增名为“tina”的用户记录 TUser user = new TUser(); user.setName("tina"); session.save(user); Session 具有一个缓存, 位于缓存中的对象出于持久化状态,它和数据库中的相关记录对应, Session能够在某些时间点,按照缓存中持久化对象的属性变化来同步更新数据库,这一过程被称为清理缓存. 理解Session: 首先理解,当应用程序通过new语句创建一个java对象时, jvm会为这个对象分配一块内存空间,只要这个对象被引用变量引用, 它就一直存在于内存中. 如果这个对象不被任何引用变量引用,它就结束生命周期, 此时jvm的垃圾回收器会在适当的时候回收它占用的内存. java的集合(list,set,map)的一个重要特征是: 集合中存放的是java对象的引用. 当向集合中添加一个对象时, 其实是把这个对象的引用添加到集合中;
如果希望一个java对象一直出于生命周期中, 就必须保证至少有一个变量引用它,或者在一个java集合中存放了这个对象的引用. 在session接口的实现类sessionImpl中定义了一系列的java集合, 这些java集合构成了session的缓存. 例如: // Map集合中的键对象代表持久化对象的OID,值对象代表持久化对象 Private final Map entitiesByKey; … EntitiesByKey.put(key,object);// 向session的缓存中加入一个持久化对象 … EntitiesByKey.remove(key);//从session的缓存中删除一个持久化对象 EntitiesByKey.clear(); 当session的save()方法持久化一个User对象时, user对象被加入到session的缓存中, 以后即使应应用程序中的引用变量不再引用user对象, 只要session的缓存还没有被清空, user 对象仍然处于生命周期中. 当session的load()方法试图从数据库中加载一个user对象时, session先判断缓存中是否已经存在这个user对象,如果存在, 就不需要再到数据库中检索. Session 的缓存两大作用: (1) 减少访问数据库的频率.应用程序从内存中读取持久化对象的速度显然比到数据库中查询数据的速度快多了, 因此session的缓存可以提高数据访问的性能; (2) 保证缓存中的对象与数据库中的相关记录保持同步.位于缓存中的对象被称为持久化对象.当缓存中持久化对象的状态发生了变化, session并不会立即执行相关的SQL语句, 这使得session能够把几条相关的SQL语句合并为一条SQL语句,以便减少访问数据库的次数,从而提高应用程序的性能. 例如以下程序代码对user的name属性修改了两次: T=session.beginTransaction(); User user=(User)session.load(User.class,new Long(1)); User.setName(“jack”); User.setName(“tom”); T.commit(); 当session清空缓存时, 只需执行一条update语句: Update t_user set name=”tom” where id=1; 注: 当session加载了user后, 会为user对象的值类型的属性复制一份快照.当session清理缓存时,通过比较user对象的当前属性与它的快照,session能够判断user对象的那些属性发生了变化. 在默认情况下, session会在下面的时间点清理缓存 1) 当应用程序调用commit()方法的时候,commit方法先清理缓存, 然后再向数据库提交事务; 2) 当应用程序显示调用session的flush()方法的时候. Session进行清理缓存的例外情况是, 如果对象使用native生成器来生成oid,那么当调用session的save方法保存对象时, 会立即执行向数据库中插入该实体的insert语句. 注意: session 的commit方法和flush方法的区别, flush方法进行清理缓存的操作,执行一系列SQL语句,但不会提交事务; commit方法会先调用flush方法,然后提交事务. 提交事务意味着对数据库所做的更新被永久保存下来. 默认 flush 的时机: 1)提交commit时会隐含的进行flush; 2)直接调用flush; 3)执行某些查询之前hibernate会自动进行flush; hibernate 在用oid 去查询时,不一定去数据库里查,先从session中查; 可以通过session的setFlushMode方法设置清理缓存的时间点; FlushMode.AUTO, FlushMode.COMMIT , FlushMode.NEVER, 用来约束session的查询方法, commit方法,flush方法是否清理缓存; 第一个是默认值, 保证在整个事务中,数据保持一致. Cmmit模式. 可以避免在执行session的查询方法时先清理缓存, 提高应用程序的性能. 二, 一对一, 一对多的关系 1, id的生成方式; 7种,看第二天的笔记; <id name="id" column="id"> <generator class="seqhilo"> <!-- 这时默认的, select hibernate_sequence.nextval from dual;--> <!--这里不指定参数的话同上, 也可以指定自定义的sequence--> <generator class="sequence"> <param name="sequence">person_seq</param> </generator> </id> 用的比较多的是native, sequence ; 2, 一对一关系, 唯一外键, 共享主键; 1) 唯一外键: 在Account.xml中: 账户表维护的是关系的主键; <one-to-one name="addr" property-ref="act" cascade="all"/> 在Address.xml中: 维护的是关系的外键, 代表多; <many-to-one name="act" column="fid" unique="true" /> fid 是Address表里的外键, 关联到Account的主键aid ; 唯一外键表结构: create table ln_address( oid number(20) primary key, postcode varchar2(20), city varchar2(20), street varchar2(30), fid number(20) not null unique, //fid 外键引用ln_account(aid) constraint account_address foreign key(fid) references ln_account(aid) ); create table ln_account( aid number(20) primary key, actNo varchar(100) not null, owner varchar(20) not null, balance number(20,2), ); 2) 共享主键: account的xml文件: 表示对方的主键就是外键 <one-to-one name="addr" cascade="all" > address 的xml文件: <id name="oid" column="oid"><!-- 既是主键又是外键;值是从它引用的表得到的;--> <generator class="foreign"> <!--表示主键引用的是别的表 --> <param name="property">account</param> </generator> </id> <one-to-one name="account" constrained="true" cascade="all"/> constrained="true";//acct所对应的类型的表,account表,对我现在的表形成了外键约束; //account表的主键对address表的主键形成了外键约束; 表结构: address 的主键又是外键 和 account 的值一致; create table ln_address( oid number(20) primary key, postcode varchar2(20), city varchar2(20), street varchar2(30), constraint account_address2 foreign key(oid) references ln_account(aid) ); create table ln_account( aid number(20) primary key, actNo varchar(100) not null, owner varchar(20) not null, balance number(20,2), ); 3, 一对多的关系: (1) user的xml文件:(一的一方,有一个account的 set集合) <set name="accounts" cascade="all" inverse="true"> <key column="fid"></key> <one-to-many class="Account"> </set> 1、集合里存的是PO,集合代表一对多;并且是关联属性; <one-to-many> 2、class="Account" ;说明集合中的PO类型;因为泛型只是编译时有效,虚拟机看不到,所以还要说明类型; 3、key 标签说明的是,关系的另一方(Account)表中与本表关联的外键字段的名字; 关系的维护权: 一对多的关系中, 在一的一方写上inverse="true"; 将关系的维护权放在多的一方; inverse="false"; // 默认的, User维护关系,存user时, 会先存account; 这时存account时,account有外键字段,先插入三条account语句,fid=null; 再插入user,并且要更新account的外键字段; inverse="true"; // User放弃维护关系,会先存user,user的oid先算出来, 然后再存account,fid就有值了; 1)、防止外键为空;把关系的维护交给多的一方; 2)、防止n+1次更新; 如果为false ; user.add(account); s.saveOrUpdate(user); 更新user时会把集合的元素更新一遍;再更新user; 如果为true; 存account时会先存user ,再把自己存上就行了; (2) account的xml文件:多的一方,有一个user属性,维护外键; <property name="actNo" unique="true" not-null="true">//自动建表时加上约束 <many-to-one name="user" column="fid" cascade="save-update"> cascade="save-update":帐户保存和更新时更新用户,但删帐户时不能删用户; 三, Hibernate应用中 java对象的状态 : transient:暂态,瞬态; 该对象和任何session无关,在数据库中没有它的记录; 刚new出来的对象;或已经删除的对象; persistent: 持久对象的持久态;该对象处于某session的管理中;在数据库中有记录;从数据库中取出的对象;通过save()方法同步到数据库中的对象 detached:游离态、脱管态;当前和任何session无关,但在数据库中有记录; session关闭后,就成了游离态了;update()同步到数据库中; 持久化类与持久化对象是不同的概念, 持久化类的实例可以处于临时状态, 持久化 状态和游离状态, 其中处于持久化状态的实例被称为持久化对象. 对象的状态有两种含义, 一种含义是指由对象的属性表示的数据, 一种含义是指临时状态, 持久化状态或游离状态之一. 应该根据上下文来辨别状态的具体含义.
get(); 如果找到对象就返回,否则返回空; load(); 如果找到对象就返回,否则抛异常 throw unrecoverableException; s.close(); 关闭session,对象变成游离的; clear(); 将session的缓冲区清空;对象变成游离的对象;内存中还有,但跟session已经无关了 evict(o1);将一个对象清出去,变成游离状态 update(), saveOrUpdate(); 使游离对象变成持久状态; saveOrUpdate()方法同时包含了save与update方法的功能, 如果传入的参数是临时对象,就调用save()方法, 如果传入的参数是游离对象,就调用update()方法, 如果传入的参数是持久化对象, 就直接返回. 那么, 它是如何判断一个对象处于临时状态还是游离状态呢? 如果满足以下情况之一, Hibernate 就把它当成临时对象: 1)Java对象的OID是null; 2)Java对象具有version属性并且取值为version 3)在映射文件中为id元素设置了unsaved-value属性,并且oid的取值与unsaved-value属性值匹配;这种情况指的的是id属性是long类型, 它的默认值是0, 此时需要显式设置id元素的unsaved-value=”0”; 4) 在映射文件为version属性设置了unsaved-value属性, 并且version属性取值与unsaved-value属性值匹配; delete()方法, 用于从数据库中删除与java对象对应的记录.如果传入的参数是持久化对象, session就计划执行一个delete语句, 如果传入的参数是游离对象, 先使游离对象被session关联,使他变为持久化对象,然后计划执行一个delete语句, 值得注意的是, session只有在清理缓存的时候才会执行delete语句. 此外只有在调用session的close方法时, 才会从session的缓存中删除该对象. 级联操作: 1、cascade cascade属性是设置级联操作的. 也就是在操作一端的数据如果影响到多端数据时会进行级联操作, cascade="none",cascade="save-update",cascade="delete",cascade="all" cascade="persist" cascade="delete-orphan",cascade属性的值常用的设置为以上五项: none就是不使用级联操作,默认级联是none。 save-update也就是只有对象保存操作(持久化操作) 或者是持久化对象的更新操作,才会级联操作关联对象(子对象)。 persist就只是将级联对象也持久化到数据库。 delete对持久化对象的删除操作时会进行级联操作关联对象(子对象)。 all 对持久化对象的所有操作都会级联操作关联对象(子对象)。 包含save-update, 和delete 行为; all-delete-orphan,从集合中删除时,同步将数据库中的记录删掉; 包含save-update, 和delete-orphan行为; delete 之后将表中表示关联的外键id置成null,不会将这条纪录也删除掉; delete-orphan 就不会留有空纪录,而是级联的把相关纪录删除掉。 四、get、load的具体区别: 如果你使用load方法,hibernate认为该id对应的对象(数据库记录)在数据库中是一定存在的,所以它可以放心的使用,它可以放心的使用代理来延迟加载该对象。在用到对象中的其他属性数据时才查询数据库,但是万一数据库中不存在该记录,那没办法,只能抛异常ObjectNotFoundException,所说的load方法抛异常是指在使用该对象的数据时,数据库中不存在该数据时抛异常,而不是在创建这个对象时。由于session中的缓存对于hibernate来说是个相当廉价的资源,所以在load时会先查一下session缓存看看该id对应的对象是否存在,不存在则创建代理。所以如果你知道该id在数据库中一定有对应记录存在就可以使用load方法来实现延迟加载。 对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查数据库,数据库中没有就返回null。 虽然好多书中都这么说:“get()永远只返回实体类”,但实际上这是不正确的,get方法如果在session缓存中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是原先的代理对象,而不是实体类对象,如果该代理对象还没有加载实体数据(就是id以外的其他属性数据),那么它会查询二级缓存或者数据库来加载数据,但是返回的还是代理对象,只不过已经加载了实体数据。 前面已经讲了,get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;反而load方法创建时首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库。 总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,就抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。 五:二级缓存: 在hibernate.cfg.xml中添加如下代码: <property name="hibernate.cache.provider_class"> NHibernate.Caches.SysCache.SysCacheProvider,NHibernate.Caches.SysCache</property> <property name="expiration">120</property> 注意: NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache还可以替换为NHibernate.Caches.Prevalence.PrevalenceCacheProvider, NHibernate.Caches.Prevalence,代表缓存的实现类,在NHibernate的安装目录的bin目录中有这样两个dll: NHibernate.Caches.SysCache.dll,NHibernate.Caches.Prevalence.dll用哪个就把哪个拷贝到应用程序的bin目录下。expiration代表缓存过期时间,单位为秒。 设置完后,还需要在对象的映射文件中配置二级缓存的策略,比如我在User.hbm.xml中如下配置,注意红色字体部分: <?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="NhibernateSample1.User,NhibernateSample1" table="Users" lazy="false"> <cache usage="read-write"/> <id name="Id" column="Id" unsaved-value="0"> <generator class="native" /> </id> <property name="Name" column="Name" type="string" length="64" not-null="true" unique="true"></property> <property name="Pwd" column="Pwd" type="string" length="64" not-null="true"></property> <many-to-one name="Role" class="NhibernateSample1.Role,NhibernateSample1" column="RoleID"></many-to-one> </class> </hibernate-mapping> 当然在利用缓存的时候,缓存不会知道另外一个进程存储的实体发生变化,应该自己建立一些策略来及时地更新缓存快照。而且当ISessionFactory销毁的时候,二级缓存也会随之销毁。 五, Hibernate 的数据类型: 配置文件中, type=” ”; |
|