hibernate总结二

xiaoxiao2026-01-02  3

Hibernate总结二: Criteria查询: 接口,它是特定的持久化类的查询,面向对象,是传统sql的对象化表示,让开发人员可以用对象的方式来对数据库进行操作,他仅仅针对单个类查询。 Session是用来制造criteria实例的工厂 Criteria criteria = session.createCriteria(MyTest.class); 怎么构建一个criteria查询: 第一步:创建criteria对象 第二步:设置条件,criterion对象来封装,用Restictions获取条件 第三步:添加条件 add 第四步:执行查询 实战演练: package com.lovo.criteria; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Restrictions; import com.lovo.po.Pet; import com.lovo.po.User; import junit.framework.TestCase; /** * criteria查询 * @author xieyongbing * */ public class Test extends TestCase { private SessionFactory sessionfactory; private Configuration cfg; public void setUp() throws Exception { cfg = new Configuration().configure(); this.sessionfactory = cfg.buildSessionFactory(); } public void tearDown() throws Exception { this.sessionfactory.close(); } public void testCriteria(){ Session session = this.sessionfactory.openSession(); // Criteria criteria = session.createCriteria(User.class); //创建criteria对象 // //添加查询 add,criterion来充当条件 // Criterion criterion = Restrictions.eq("id", 1); //创建查询条件,一般来说,我们用Restictions获取条件 List list = criteria.list(); //什么条件也不给,将返回所有的对象 // criteria.add(criterion); //添加条件 // List list = criteria.list(); //执行查询 // System.out.println(list.size()); // session.close(); /** * 用一个方法链格式来写 * Restrictions.eq("id", 1) 这其中的id是你实体类的属性,后面是你的值 */ // Session session = this.sessionfactory.openSession(); // List list = session.createCriteria(User.class) // .add(Restrictions.eq("id", 1)) // .list(); // System.out.println(list.size()); // session.close(); /** * 两个条件查询 * 简单查询 * Restrictions.or() */ // Session session = this.sessionfactory.openSession(); // List list = session.createCriteria(User.class) // .add(Restrictions.or(Restrictions.eq("password", "1234"),Restrictions.eq("name", "谢永兵"))) // .list(); // System.out.println(list.size()); // session.close(); /** * 离散查询,即离线查询 DetachedCriteria * 好处:先构建一个离散查询对象,它没有和session关联,需要时关联。在web层构造查询条件。构建一个 * 离散的查询对象,传回持久层,与session关联。会带来web层与持久层耦合了 *(Restrictions.between("age", 12, 5) 12 和 5 的参数是从客户端传来的 */ Integer low = 1; Integer hi = 2; //创建一个离散的criteria对象 DetachedCriteria dc = DetachedCriteria.forClass(User.class); //添加条件 dc.add(Restrictions.between("id", low, hi)); //构建一个criteria对象,和session关联 // 一般来说我们应该,将下面的分离在另外一个方法中,这里我就不演示了 Criteria criteria = dc.getExecutableCriteria(session); List list = criteria.list(); System.out.println(list.size()); } } Hibernate 缓存处理: 什么是缓存: 就是数据库的数据在内存中的临时容器 位于数据库与数据库访问层中间,实际上就是为了性能的提高,查询速度的增快 缓存的分类: 一级缓存: 即在但前事物范围内的数据库缓存,它是基于session的生命周期实现的 二级缓存:即在某个应用中或应用中某个独立数据库访问子集中的共享缓存,此缓存可由多 个事物共享,hibernate中由sessfactory实现。 就是相当作用域当中的 servletcontext 上下文 分布式缓存: 即在多个应用实例,多个jvm间共享的缓存 一级缓存实例: /** * 一级缓存实例 * */ Session session = this.sessionfactory.openSession(); User u1 = (User)session.get(User.class, 2); System.out.println(u1.getName()); User u2 = (User)session.get(User.class, 2); System.out.println(u2.getName()); //session.clear();内存清除session session.close(); 控制台打印的查询语句: 什么时候使用二级缓存: 第一:数据不会被第三方修改 第二:数据大小在可以接受的范围之内 第三:数据更新频率比较低 第四:同一数据可能会被频繁引用 第五:非关键数据需要共享的数据 查询缓存: 查询缓存,就是在内存建立空间用来保存上次查询结果,下次再进行同样的查询时,就不用再从数据库查找结果,大大的提高速度。 不过内存和数据库的数据没有完全同步,所以不适用于多个程序共同访问同一数据表的的情况;这样的话,数据表的数据已经被其他程序修改,有可能内存里存在的数据是旧数据,这就是所谓的脏数据。 而且Hibernate和数据库的查询缓存,在对一个数据表记录进行修改时,会把有关这个表的全部查询缓存清空,以保证减少脏读问题。 所以查询缓存只适用于非多服务器同时访问的数据库,而且读取大大多于修改操作的数据表上 配置一: hibernate.cfg.xml文件中增加 <!--开启二级缓存--> <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property> <property name="hibernate.cache.use_query_cache">true</property> 配置二: 工程项目src文件下新建一个ehcache.xml文件,其内容为 <!--开启二级缓存--> <?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStore path="java.io.tmpdir" /> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="180" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> </ehcache> 配置三: 为了缓存某类的对象,其hbm文件中需添加<cache usage="read-only"/>属性例如: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Mapping file autogenerated by MyEclipse - Hibernate Tools --> <hibernate-mapping> <class name="com.vogue.bbsphoto.entity.Forum" table="cdb_forums"> <cache usage="read-only"/> <id name="ID" column="fid" unsaved-value="null"> <generator class="increment" /> </id> <property name="name" column="name" type="string" /> <property name="type" column="type" type="string" /> </class> </hibernate-mapping> 配置四: 为了使用查询缓存,Query必须设置cacheable为true,query.setCacheable(true); 例如dao父类中用于hql查询的方法修改后为: /** * 执行hql语句的查询 * @param sql * @return */ public List executeQuery(String hql){ List list = new ArrayList(); Session session = HibernateSessionFactory.currentSession(); Transaction tx = null; Query query = session.createQuery(hql); query.setCacheable(true); try { tx = session.beginTransaction(); list = query.list(); tx.commit(); } catch (Exception ex) { ex.printStackTrace(); HibernateSessionFactory.rollbackTransaction(tx); } finally { HibernateSessionFactory.closeSession(); } return list; } 实验结果: 一验证对象缓存 执行如下测试方法 public void testLoadForum(){ //第一次加载 long l1 = System.currentTimeMillis(); Forum res = (Forum)fDAO.get(Forum.class,new Long(3)); System.out.println(System.currentTimeMillis() - l1); System.out.println(res.getName()); //第二次加载 long l2 = System.currentTimeMillis(); res = (Forum)fDAO.get(Forum.class,new Long(3)); System.out.println(System.currentTimeMillis() - l2); System.out.println(res.getName()); }输出结果:可以看到先后两次加载id为3的(Forum)对象,但是只执行了一条sql log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: select forum0_.fid as fid0_, forum0_.name as name2_0_, forum0_.type as type2_0_ from cdb_forums forum0_ where forum0_.fid=? 1703 功能版区 0 功能版区 二验证查询结果缓存 执行如下测试方法 public void testGetAllForum() { //第一次查询所有论坛记录 long l1 = System.currentTimeMillis(); List res = fDAO.getAllForum(); System.out.println(System.currentTimeMillis() - l1); System.out.println(res.size()); //第二次查询所有论坛记录 long l2 = System.currentTimeMillis(); res = fDAO.getAllForum(); System.out.println(System.currentTimeMillis() - l2); System.out.println(res.size()); }输出结果:先后两次查询所有论坛记录但,仍然只执行了一条sql log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: select forum0_.fid as fid, forum0_.name as name2_, forum0_.type as type2_ from cdb_forums forum0_ where (1=1 )and(forum0_.type='forum' ) order by forum0_.fid 1765 14 0 14 补充一下:当要缓存的对象处于级联关系中时。如果和他存在级联关系的对象都有属性 <cache usage="read-only"/>那么,在第一次get后该对象所处的对象图中的所有对象都会保存到hibernate的二级缓存中,在第二次get该对象时,直接从二级缓存中找到所有级联的对象;如果其中某个级联对象没有<cache usage="read-only"/>属性,则不会被保存到二级缓存中,以后每次get时仍然会执行sql去数据库中找该级联对象。 二级缓存: 1启用Hibernate二级缓存 Hibernate二级缓存分为两部分,class缓存和查询缓存,其获取对象的方式有所不同,但两者也有联系,查询缓存必须以class缓存为基础才能起作用,否则只会使效率更低。 我们这里使用的二级缓存是通过ehcache第三方插件实现的。 1.1配置Hibernate.cfg.xml 启用class缓存: <property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> 启用查询缓存: <property name="hibernate.cache.use_query_cache">true</property> 1.2配置Spring框架中的hibernate 启用class缓存: <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop> 启用查询缓存: <prop key="hibernate.cache.use_query_cache">true</prop> 1.3配置ehcache Ehcache配置文件为ehcache.xml,默认配置为: <ehcache> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="true" /> </ehcache> 其中各项内容的含义为: 1 diskStore:代表当二级缓存对象数据在内存中溢出,如果需要写入文件系统时的文件目录。 2 defaultCache:默认的calss缓存配置,如果某个对象没有其专有的配置时,ehcache一律启用默认配置。 3 maxElementInMemory:对象在内存中可存放的最大数量。 4 eternal:表示对象永不过期,如果选true则5,6两项无效。 5 timeToIdleSeconds:对象的空闲状态过期时间,单位为秒,0为可以无限制空闲。 6 timeToLiveSeconds:对象存在的最长时间,单位为秒(注意,如果该项比5项要小,则第5项无意义),0为永不过期。 7 overflowToDisk:当对象在内存中的数量超过maxElementInMemory值时,如果该项为true,则ehcahe会把对象数据写入diskStore项指定的目录。 如果需要对某个具体对象进行单独配置时,可以加上一组cache配置,例如: <cache name="com.juyee.mp.bean.SysCodelist" maxElementsInMemory="10000" eternal="true" timeToIdleSeconds="1800" timeToLiveSeconds="0" overflowToDisk="true" /> 另外还有两个特殊的cache配置: <cache name="org.hibernate.cache.UpdateTimestampsCache" maxElementsInMemory="5000" eternal="true" timeToIdleSeconds="1800" timeToLiveSeconds="0" overflowToDisk="true"/> <cache name="org.hibernate.cache.StandardQueryCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="0" overflowToDisk="true"/> 这两个cache配置对应查询缓存,具体作用如下(摘用别人的描述): “当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢? hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面UpdateTimestampsCache所指定的缓存配置里面。 当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。 可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。” 当然,如果没有这两个配置,则ehcache将为查询缓存启用默认配置。 2如何使用class缓存 2.1对象缓存 Class缓存的作用主要是在内存中保存某个具体对象,当用户第一次通过get、iterator方式取出对象时,系统会先从class缓存中去找,如果没有再通过sql语句去数据库中查找相关记录,并将查询到的对象放入内存中。 实例:对某个对象使用二级缓存,只需要在该对象的hbm文件中配置即可 <cache usage="read-write"/> 我们注意到其中usage这个选项,它有多个选择,对应不同的含义,经常有这两种种: 1 read-only:只对缓存中的对象进行读操作。 2 read-write:当对象被update时,缓存中和数据库中一同被修改(缓存不支持事务回滚)。 2.2关联缓存 目前我们仅仅实现了对一个对象的缓存,那如何对该对象的关联对象集合进行缓存呢? 实例:对某个对象的关联对象集合的二级缓存,需要在该对象的hbm文件中set配置进行修改 <set name="children" lazy="true" order-by="treeid asc"> <cache usage="read-write"/> <key column="PARENTID"/> <one-to-many class="SysCodelist"/> </set> 注意: 1 只对one-to-many有效,而且仅仅缓存的是关联对象的id集合,如果需要实现完全缓存,则需要对关联的对象也配置成使用二级缓存。 2 集合缓存是独立的,不受关联对象添加、删除的影响,如果要修改集合内容,必须对这个集合本身进行修改,例如:codelist.getChildren().add()。 3如何使用查询缓存 查询缓存,目的是为了将通过list()方法的查询结果存入缓存中,并实现对语句的缓存,如果下次用户在使用这条语句进行查询时,将直接从缓存中获取对象数据。 这里要注意的是,查询缓存必须配合class缓存使用,如果只启用查询缓存,不对查询对象启用二级缓存,则会大大降低查询效率。 因为,当第一次通过启用查询缓存的session进行语句查询时,系统只执行一次数据库查询将所有的记录取出,并将对象存入class缓存,语句及id集合存入查询缓存;而当用户第二次查询该语句时,系统将先执行去查询缓存中查找,取出所有符合条件的id集合,如果这时候该对象的class缓存没启用或在class缓存中已过期,系统将根据id,一个个去数据库load,实际上是进行了1+N次查询。 实际上,在我们系统中,并不是对所有对象都要进行二级缓存,而spring框架中提供的hibernate方法,虽然有getHibernateTemplate().setCacheQueries(),但该方法影响的是全局的配置,一旦启用,将会对不需要缓存的查询造成不良影响。 于是,我自己在hibernateService()中添加了一个方法: public List findByCachedQuery(final String hql) { return (List) getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Query queryObject = session.createQuery(hql); queryObject.setCacheable(true); if (getHibernateTemplate().getQueryCacheRegion() != null) { queryObject.setCacheRegion(getHibernateTemplate().getQueryCacheRegion()); } return queryObject.list(); } }, true); } 这样,将只在session范围内启用查询缓存,一旦该session结束了,那么查询缓存也将回复默认配置。 注意:使用时只支持hql。 4注意事项 在使用二级缓存时,注意,所有对数据库的修改都必须走hibernate,如果从其他系统来或使用sql语句来修改数据库相关记录,那么将对二级缓存的数据不会造成影响,换句话说,缓存中的对象数据将和数据库中的不一致。 Hibernate 提供的四种缓存同步策略: Read-only : 只读,应用于不会发生改变的数据 Constrict-read-writer : 非严格读写,不保证缓存数据与数据库数据的一致性 Read-writer : 严格可读写缓存,用于数据同步很严格的情况 Transcational :事务性缓存 Hibernate 延迟加载: 延迟加载: 延迟加载机制是为了避免一些无谓的性能开销而提出来 的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。在Hibernate中提供了对实体对象的延迟加载以及对集合的延迟加载,另外在 Hibernate3中还提供了对属性的延迟加载。下面我们就分别介绍这些种类的延迟加载的细节。 A、实体对象的延迟加载: 如果想对实体对象使用延迟加载,必须要在实体的映射配置文件中进行相应的配置,如下所示: <hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user” lazy=”true”> …… </class> </hibernate-mapping> 通过将class的lazy属性设置为true,来开启实体的延迟加载特性。如果我们运行下面的代码: User user=(User)session.load(User.class,”1”);(1) System.out.println(user.getName());(2) 当 运行到(1)处时,Hibernate并没有发起对数据的查询,如果我们此时通过一些调试工具(比如JBuilder2005的Debug工具),观察此 时user对象的内存快照,我们会惊奇的发现,此时返回的可能是User$EnhancerByCGLIB$$bede8986类型的对象,而且其属性为 null,这是怎么回事?还记得前面我曾讲过session.load()方法,会返回实体对象的代理类对象,这里所返回的对象类型就是User对象的代 理类对象。在Hibernate中通过使用CGLIB,来实现动态构造一个目标对象的代理类对象,并且在代理类对象中包含目标对象的所有属性和方法,而且 所有属性均被赋值为null。通过调试器显示的内存快照,我们可以看出此时真正的User对象,是包含在代理对象的 CGLIB$CALBACK_0.target属性中,当代码运行到(2)处时,此时调用user.getName()方法,这时通过CGLIB赋予的回 调机制,实际上调用CGLIB$CALBACK_0.getName()方法,当调用该方法时,Hibernate会首先检查 CGLIB$CALBACK_0.target属性是否为null,如果不为空,则调用目标对象的 getName方法,如果为空,则会发起数据库查询,生成类似这样的SQL语句:select * from user where id=’1’;来查询数据,并构造目标对象,并且将它赋值到CGLIB$CALBACK_0.target属性中。 这样,通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以 实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才 会返回实体类的代理类对象。 B、 集合类型的延迟加载: 在 Hibernate的延迟加载机制中,针对集合类型的应用,意义是最为重大的,因为这有可能使性能得到大幅度的提高,为此Hibernate进行了大量的 努力,其中包括对JDK Collection的独立实现,我们在一对多关联中,定义的用来容纳关联对象的Set集合,并不是java.util.Set类型或其子类型,而是 net.sf.hibernate.collection.Set类型,通过使用自定义集合类的实现,Hibernate实现了集合类型的延迟加载。为了 对集合类型使用延迟加载,我们必须如下配置我们的实体类的关于关联的部分: <hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> ….. <set name=”addresses” table=”address” lazy=”true” inverse=”true”> <key column=”user_id”/> <one-to-many class=”com.neusoft.entity.Arrderss”/> </set> </class> </hibernate-mapping> 通过将<set>元素的lazy属性设置为true来开启集合类型的延迟加载特性。我们看下面的代码: User user=(User)session.load(User.class,”1”); Collection addset=user.getAddresses(); (1) Iterator it=addset.iterator(); (2) while(it.hasNext()){ Address address=(Address)it.next(); System.out.println(address.getAddress()); } 当程序执行到(1)处时,这时并不会发起对关联数据的查询来加载关联数据,只有运行到(2)处时,真正的数据读取操作才会开始,这时Hibernate会根据缓存中符合条件的数据索引,来查找符合条件的实体对象。 这里我们引入了一个全新的概念——数据索引,下面我们首先 将接一下什么是数据索引。在 Hibernate中对集合类型进行缓存时,是分两部分进行缓存的,首先缓存集合中所有实体的id列表,然后缓存实体对象,这些实体对象的id列表,就是 所谓的数据索引。当查找数据索引时,如果没有找到对应的数据索引,这时就会一条select SQL的执行,获得符合条件的数据,并构造实体对象集合和数 据索引,然后返回实体对象的集合,并且将实体对象和数据索引纳入Hibernate的缓存之中。另一方面,如果找到对应的数据索引,则从数据索引中取出 id列表,然后根据id在缓存中查找对应的实体,如果找到就从缓存中返回,如果没有找到,在发起select SQL查询。在这里我们看出了另外一个问题,这个问题可能会对性能产生影响,这就是集合类型的缓存策略。如果我们如下配置集合类型: <hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> ….. <set name=”addresses” table=”address” lazy=”true” inverse=”true”> <cache usage=”read-only”/> <key column=”user_id”/> <one-to-many class=”com.neusoft.entity.Arrderss”/> </set> </class> </hibernate-mapping> 这里我们应用了<cache usage=”read-only”/>配置,如果采用这种策略来配置集合类型,Hibernate将只会对数据索引进行缓存,而不会对集合中的实体对象进行缓存。如上配置我们运行下面的代码: User user=(User)session.load(User.class,”1”); Collection addset=user.getAddresses(); Iterator it=addset.iterator(); while(it.hasNext()){ Address address=(Address)it.next(); System.out.println(address.getAddress()); } System.out.println(“Second query……”); User user2=(User)session.load(User.class,”1”); Collection it2=user2.getAddresses(); while(it2.hasNext()){ Address address2=(Address)it2.next(); System.out.println(address2.getAddress()); } 运行这段代码,会得到类似下面的输出: Select * from user where id=’1’; Select * from address where user_id=’1’; Tianjin Dalian Second query…… Select * from address where id=’1’; Select * from address where id=’2’; Tianjin Dalian 我们看到,当第二次执行查询时,执行了两条对 address表的查询操作,为什么会这样?这是因为当第一次加载实体后,根据集合类型缓存策略的配置,只对集合数据索引进行了缓存,而并没有对集合中的 实体对象进行缓存,所以在第二次再次加载实体时,Hibernate找到了对应实体的数据索引,但是根据数据索引,却无法在缓存中找到对应的实体,所以 Hibernate根据找到的数据索引发起了两条select SQL的查询操作,这里造成了对性能的浪费,怎样才能避免这种情况呢?我们必须对集合类型中的实体也指定缓存策略,所以我们要如下对集合类型进行配置: <hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> ….. <set name=”addresses” table=”address” lazy=”true” inverse=”true”> <cache usage=”read-write”/> <key column=”user_id”/> <one-to-many class=”com.neusoft.entity.Arrderss”/> </set> </class> </hibernate-mapping> 此时Hibernate会对集合类型中的实体也进行缓存,如果根据这个配置再次运行上面的代码,将会得到类似如下的输出: Select * from user where id=’1’; Select * from address where user_id=’1’; Tianjin Dalian Second query…… Tianjin Dalian 这时将不会再有根据数据索引进行查询的SQL语句,因为此时可以直接从缓存中获得集合类型中存放的实体对象。 C、 属性延迟加载: 在 Hibernate3中,引入了一种新的特性——属性的延迟加载,这个机制又为获取高性能查询提供了有力的工具。在前面我们讲大数据对象读取时,在 User对象中有一个resume字段,该字段是一个java.sql.Clob类型,包含了用户的简历信息,当我们加载该对象时,我们不得不每一次都要 加载这个字段,而不论我们是否真的需要它,而且这种大数据对象的读取本身会带来很大的性能开销。在Hibernate2中,我们只有通过我们前面讲过的面 性能的粒度细分,来分解User类,来解决这个问题(请参照那一节的论述),但是在Hibernate3中,我们可以通过属性延迟加载机制,来使我们获得 只有当我们真正需要操作这个字段时,才去读取这个字段数据的能力,为此我们必须如下配置我们的实体类: <hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> …… <property name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/> </class> </hibernate-mapping> 通 过对<property>元素的lazy属性设置true来开启属性的延迟加载,在Hibernate3中为了实现属性的延迟加载,使用了类 增强器来对实体类的Class文件进行强化处理,通过增强器的增强,将CGLIB的回调机制逻辑,加入实体类,这里我们可以看出属性的延迟加载,还是通过 CGLIB来实现的。CGLIB是 Apache的一个开源工程,这个类库可以操纵java类的字节码,根据字节码来动态构造符合要求的类对象。根据上面的配置我们运行下面的代码: String sql=”from User user where user.name=’zx’ ”; Query query=session.createQuery(sql); (1) List list=query.list(); for(int i=0;i<list.size();i++){ User user=(User)list.get(i); System.out.println(user.getName()); System.out.println(user.getResume()); (2) } 当执行到(1)处时,会生成类似如下的SQL语句: Select id,age,name from user where name=’zx’; 这时Hibernate会检索User实体中所有非延迟加载属性对应的字段数据,当执行到(2)处时,会生成类似如下的SQL语句: Select resume from user where id=’1’; 这时会发起对resume字段数据真正的读取操作。 属性加载应该注意的问题: 我们在影射文件中加上 lazy = true; <class name="Document"> <id name="id"> <generator class="native"/> </id> <property name="name" not-null="true" length="50"/> <property name="summary" not-null="true" length="200" lazy="true"/> <property name="text" not-null="true" length="2000" lazy="true"/> </class> 属性的延迟加载需要使用运行时的字节设备来处理,如果你的持久化类还没有被这个设备处理。hibernate 会忽略这个设置,我们要导入一个包: cglib 要想使用此字节设备处理持久化类,使用如下的Ant 任务。 <target name="instrument" depends="compile"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"> <classpath path="${jar.path}"/> <classpath path="${classes.dir}"/> <classpath refid="lib.class.path"/> </taskdef> <instrument verbose="true"> <fileset dir="${testclasses.dir}/org/hibernate/auction/model"> <include name="*.class"/> </fileset> </instrument> </target> 当我在使用ant时候,我们的java实体bean讲发生改变,如果我们在修改的时候不运行ant,,,java实体bean不会发生改变 一、 saveorUpdate与unsaved-value 到底是sava还是update Hibernate需要判断被操作的对象究竟是一个已经持久化过的持久对象还是临时对象。 1).主键Hibernate的id generator产生 <id name="id" type="java.lang.Long"> <column name="ID" precision="22" scale="0" /> <generator class="increment" /> </id> Project project = new Project(); project.setId(XXX); this.projectDao.saveOrUpdate(project); 1、默认unsaved-value="null" 主键是对象类型,hebernate判断project的主键是否位null,来判断project是否已被持久化 是的话,对project对象发送save(project), 若自己设置了主键则直接生成update的sql,发送update(project),即便数据库里没有那条记录。 主键是基本类型如int/long/double/ 自己设置unsaved-null="0"。 所以这样的话save和update操作肯定不会报错。 2、unsaved-value="none", 由于不论主键属性为任何值,都不可能为none,因此Hibernate总是对project对象发送update(project) 3、unsaved-value="any" 由于不论主键属性为任何值,都肯定为any,因此Hibernate总是对project对象发送save(project),hibernate生成主键。 Hibernate文档中写到 saveOrUpdate()完成了如下工作: 如果对象已经在这个session中持久化过了,什么都不用做 如果对象没有标识值,调用save()来保存它 如果对象的标识值与unsaved-value中的条件匹配,调用save()来保存它 如果对象使用了版本(version或timestamp),那么除非设置unsaved-value="undefined",版本检查会发生在标识符检查之前. 如果这个session中有另外一个对象具有同样的标识符,抛出一个异常 2).主键由自己来赋值 <id name="id" type="java.lang.Long"> <column name="ID" precision="22" scale="0" /> <generator class="assigned" /> </id> Project project = new Project(); project.setId(XXX); this.projectDao.saveOrUpdate(project); 1、默认unsaved-value="null" 这时有所不同,hibernate会根据主键产生一个select,来判断此对象是否已被持久化 已被持久化则update,未被持久化则save。 2、unsaved-value="none",update对象,同上 3、unsaved-value="any" ,save对象, 如果自己自己设置的ID在数据库中已存在,则报错。 二、save与update操作 显式的使用session.save()或者session.update()操作一个对象的时候,实际上是用不到unsaved-value的 在同一Session,save没什么可说得 update对象时, 最直接的更改一个对象的方法就是load()它,保持Session打开,然后直接修改即可: Session s =… Project p = (Project) sess.load(Project.class, id) ); p.setName(“test”); s.flush(); 不用调用s.update(p);hibernate能察觉到它的变化,会自动更新。当然显示调用的话也不会错 Hibernate文档中写到 update()方法在下列情形下使用: 程序在前面的session中装载了对象 对象被传递到UI(界面)层 对该对象进行了一些修改 对象被传递回业务层 应用程序在第二个session中调用update()保存修改 三、delete操作 删除时直接自己构造一个project即可删除 this.projectDao.delete(preojct); 以前删除我是这样写的 public void deleteProject(String id) { Project project = (Project) this.projectDao.get(Project.class, id); if (project != null) { this.projectDao.delete(project); } 即这样也是可以的 Project project = new Project(); project.setId(id); this.projectDao.delete(project). 如果有级联关系,需要把级联的子类也构造出来add进去,同样可以删除。 Hibernate 事物处理机制: Hibernate是对JDBC的轻量级对象封装,Hibernate本身是不具备Transaction处理功能的,Hibernate的 Transaction实际上是底层的JDBC Transaction的封装,或者是JTA Transaction的封装,下面我们详细的分析: Hibernate可以配置为JDBCTransaction或者是JTATransaction,这取决于你在hibernate.properties中的配置: #hibernate.transaction.factory_class net.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_class net.sf.hibernate.transaction.JDBCTransactionFactory 如果你什么都不配置,默认情况下使用JDBCTransaction,如果你配置为: hibernate.transaction.factory_class net.sf.hibernate.transaction.JTATransactionFactory 将使用JTATransaction 不管你准备让Hibernate使用JDBCTransaction,还是JTATransaction,我的忠告就是什么都不配,将让它保持默认状态,如下: #hibernate.transaction.factory_class net.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_class net.sf.hibernate.transaction.JDBCTransactionFactory 在下面的分析中我会给出原因。 一、JDBC Transaction 看看使用JDBC Transaction的时候我们的代码例子: Session session = sf.openSession(); Transaction tx = session.beginTransactioin(); ... session.flush(); tx.commit(); session.close(); 这是默认的情况,当你在代码中使用Hibernate的Transaction的时候实际上就是JDBCTransaction。那么JDBCTransaction究竟是什么东西呢?来看看源代码就清楚了: Hibernate2.0.3源代码中的类 net.sf.hibernate.transaction.JDBCTransaction: public void begin() throws HibernateException { ... if (toggleAutoCommit) session.connection().setAutoCommit(false); ... } 这是启动Transaction的方法,看到 connection().setAutoCommit(false) 了吗?是不是很熟悉? 再来看 public void commit() throws HibernateException { ... try { if ( session.getFlushMode()!=FlushMode.NEVER ) session.flush(); try { session.connection().commit(); committed = true; } ... toggleAutoCommit(); } 这 是提交方法,看到connection().commit() 了吗?下面就不用我多说了,这个类代码非常简单易懂,通过阅读使我们明白Hibernate的Transaction都在干了些什么?我现在把用 Hibernate写的例子翻译成JDBC,大家就一目了然了: Connection conn = ...; <--- session = sf.openSession(); conn.setAutoCommit(false); <--- tx = session.beginTransactioin(); ... <--- ... conn.commit(); <--- tx.commit(); (对应左边的两句) conn.setAutoCommit(true); conn.close(); <--- session.close(); 看 明白了吧,Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫无神秘可言,只不过在Hibernate 中,Session打开的时候,就会自动conn.setAutoCommit(false),不像一般的JDBC,默认都是true,所以你最后不写 commit也没有关系,由于Hibernate已经把AutoCommit给关掉了,所以用Hibernate的时候,你在程序中不写 Transaction的话,数据库根本就没有反应。 二、JTATransaction 如果你在EJB中使用Hibernate,或者准备用JTA来管理跨Session的长事务,那么就需要使用JTATransaction,先看一个例子: javax.transaction.UserTransaction tx = new InitialContext().lookup("javax.transaction.UserTransaction"); Session s1 = sf.openSession(); ... s1.flush(); s1.close(); ... Session s2 = sf.openSession(); ... s2.flush(); s2.close(); tx.commit(); 这 是标准的使用JTA的代码片断,Transaction是跨Session的,它的生命周期比Session要长。如果你在EJB中使用 Hibernate,那么是最简单不过的了,你什么Transaction代码统统都不要写了,直接在EJB的部署描述符上配置某某方法是否使用事务就可 以了。 现在我们来分析一下JTATransaction的源代码, net.sf.hibernate.transaction.JTATransaction: public void begin(InitialContext context, ... ... ut = (UserTransaction) context.lookup(utName); ... 看清楚了吗? 和我上面写的代码 tx = new Initial Context?().lookup("javax.transaction.UserTransaction"); 是不是完全一样? public void commit() ... ... if (newTransaction) ut.commit(); ... JTATransaction的控制稍微复杂,不过仍然可以很清楚的看出来Hibernate是如何封装JTA的Transaction代码的。 但 是你现在是否看到了什么问题? 仔细想一下,Hibernate Transaction是从Session中获得的,tx = session.beginTransaction(),最后要先提交tx,然后再session.close,这完全符合JDBC的 Transaction的操作顺序,但是这个顺序是和JTA的Transactioin操作顺序彻底矛盾的!!! JTA是先启动Transaction,然后启动Session,关闭Session,最后提交Transaction,因此当你使用JTA的 Transaction的时候,那么就千万不要使用Hibernate的Transaction,而是应该像我上面的JTA的代码片断那样使用才行。 总结: 1、在JDBC上使用Hibernate 必须写上Hibernate Transaction代码,否则数据库没有反应。此时Hibernate的Transaction就是Connection.commit而已 2、在JTA上使用Hibernate 写JTA的Transaction代码,不要写Hibernate的Transaction代码,否则程序会报错 3、在EJB上使用Hibernate 什么Transactioin代码都不要写,在EJB的部署描述符里面配置 |---CMT(Container Managed Transaction) | |---BMT(Bean Managed Transaction) | |----JDBC Transaction | |----JTA Transaction -------------------------------------------------------------------------------- 提问: javax.transaction.UserTransaction tx = new InitialContext().lookup("javax.transaction.UserTransaction"); Session s1 = sf.openSession(); ... s1.flush(); s1.close(); ... Session s2 = sf.openSession(); ... s2.flush(); s2.close(); tx.commit(); s1不关闭,使用s2进行操作的代码中使用s1可不可以(我觉得这样更加节约资源,不需要反复的连接、关闭) 但sf.opengSession()时,并没有setAutoCommit(false),我想问的是,如果不编写任何事务代码,如: Session s = sf.openSession(); ...... s.close(); 数据库会不会有反应(此时应该是默认AutoCommit为true)。 不会有反应。在sf.openSession() 创建Session实例的时候,就已经调用了conn.setAutoCommit(false)了。 另外,我想问一下: 1. s.flush()是不是必须的 2. s.close()是不是一定要关闭 -------------------------------------------------------------------------------- 回答: s.flush不是必须的,s.close()会调用一次s.flush() s.close()正常情况下应该关闭,除非你是用ThreadLocal管理Session。 s1不关闭,使用s2进行操作的代码中使用s1可不可以(我觉得这样更加节约资源,不需要反复的连接、关闭) 在这个例子中看不出来JTA的作用。 假设 Class A { find() { Session s1 = sf.openSession(); ... s1.flush(); s1.close(); } } Class B { find() { Session s2 = sf.openSession(); ... s2.flush(); s2.close(); } } Main { tx = ...; A.find(); B.find(); tx.commit(); } Hibernate连接池: 用Hibernate自带的连接池性能不高,而且还存在BUG,因此官方推荐使用c3p0或Proxool连接池。 1.Hibernate默认连接池 <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory > <!—JDBC驱动程序--> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <!-- 连接数据库的URL--> <property name="connection.url"> jdbc:mysql://localhost:3306/schoolproject </property> <property name="connection.useUnicode">true</property> <property name="connection.characterEncoding">UTF-8</property> <!--连接的登录名--> <property name="connection.username">root</property> <!—登录密码--> <property name="connection.password"></property> <!--是否将运行期生成的SQL输出到日志以供调试--> <property name="show_sql">true</property> <!--指定连接的语言--> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <!--映射Student这个资源--> <mapping resource="com/wqbi/model/pojo/student.hbm.xml" /> </session-factory> </hibernate-configuration> 2.C3P0连接配置 <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory > <!—JDBC驱动程序--> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <!-- 连接数据库的URL--> <property name="connection.url"> jdbc:mysql://localhost:3306/schoolproject </property> <property name="connection.useUnicode">true</property> <property name="connection.characterEncoding">UTF-8</property> <!--连接的登录名--> <property name="connection.username">root</property> <!--登录密码--> <property name="connection.password"></property> <!-- C3P0连接池设定--> <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider </property> <property name="hibernate.c3p0.max_size">20</property> <property name="hibernate.c3p0.min_size">5</property> <property name="hibernate.c3p0.timeout">120</property> <property name="hibernate.c3p0.max_statements">100</property> <property name="hibernate.c3p0.idle_test_period">120</property> <property name="hibernate.c3p0.acquire_increment">2</property> <!--是否将运行期生成的SQL输出到日志以供调试--> <property name="show_sql">true</property> <!--指定连接的语言--> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <!--映射Student这个资源--> <mapping resource="com/wqbi/model/pojo/student.hbm.xml" /> </session-factory> </hibernate-configuration> 3.proxool连接池 (1) 先写proxool的配置文件,文件名:proxool.xml(一般放在与hibernate.cfg.xml文件在同一个目录中)本例配置的是MYSQL数据库,数据库的名字为schoolproject <?xml version="1.0" encoding="UTF-8"?> <!-- the proxool configuration can be embedded within your own application's. Anything outside the "proxool" tag is ignored. --> <something-else-entirely> <proxool> <!--连接池的别名--> <alias>DBPool</alias> <!--proxool只能管理由自己产生的连接--> <driver-url> jdbc:mysql://localhost:3306/schoolproject?useUnicode=true&characterEncoding=UTF8 </driver-url> <!—JDBC驱动程序--> <driver-class>com.mysql.jdbc.Driver</driver-class> <driver-properties> <property name="user" value="root"/> <property name="password" value=""/> </driver-properties> <!-- proxool自动侦察各个连接状态的时间间隔(毫秒),侦察到空闲的连接就马上回 收,超时的销毁--> <house-keeping-sleep-time>90000</house-keeping-sleep-time> <!-- 指因未有空闲连接可以分配而在队列中等候的最大请求数,超过这个请求数的 用户连接就不会被接受--> <maximum-new-connections>20</maximum-new-connections> <!-- 最少保持的空闲连接数--> <prototype-count>5</prototype-count> <!-- 允许最大连接数,超过了这个连接,再有请求时,就排在队列中等候,最大的 等待请求数由maximum-new-connections决定--> <maximum-connection-count>100</maximum-connection-count> <!-- 最小连接数--> <minimum-connection-count>10</minimum-connection-count> </proxool> </something-else-entirely> (2)配置hibernate.cfg.xml文件 <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory > <property name="hibernate.connection.provider_class"> org.hibernate.connection.ProxoolConnectionProvider </property> <property name="hibernate.proxool.pool_alias">DBPool</property> <property name="hibernate.proxool.xml">proxoolconf.xml</property> <!--是否将运行期生成的SQL输出到日志以供调试--> <property name="show_sql">true</property> <!--指定连接的语言--> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <!--映射Student这个资源--> <mapping resource="com/wqbi/model/pojo/student.hbm.xml" /> </session-factory> </hibernate-configuration> (1) hibernate.connection.provider_class定义Hibernate的连接加载类,这里Proxool连接池是用这个,不同的连接池有不同的加载类,可以查阅Hibernate文档获取相关信息 (2) hibernate.proxool.pool_alias这里就是用我们上面提到的连接池的别名 (3) hibernate.proxool.xml是向Hibernate声明连接池的配置文件位置,可以用相对或绝对路径,用相对路径时要注意一定在要Path范围内!不然会抛出异常。 (4) dialect是声明SQL语句的方言 (5) show_sql定义是否显示Hibernate生成的SQL语言,一般在调试阶段设为true,完成后再改成false,这样有利于调试。 (6) <mapping >资源文件映射 4.JNDI连接池,数据源已经由应用服务配置好(如Web服务器),Hibernate需要做的只是通过JNDI名查找到此数据源。应用服务器将连接池对外显示为JNDI绑定数据源,它是javax.jdbc.Datasource类的一个实例。只要配置一个Hibernate文件,如: hibernate.connection.datasource=java:/comp/env/jdbc/schoolproject //JNDI名 hibernate.transaction.factory_class = org.hibernate.transaction.JTATransactionFactory hibernate.transaction.manager_loopup_class = org.hibernate.transaction.JBossTransactionManagerLookup hibernate.dialect=org.hibernate.dialect.MySQLDialect
转载请注明原文地址: https://www.6miu.com/read-5041946.html

最新回复(0)