Hibernate 配置详解(8)
hibernate.generate_statistics
这个配置大家应该都很熟悉,用于开启Hibernate统计信息,便于对Hibernate相关性能调试提供数据依据。在开发过程当中,可以把这个选项设置为true。
下面主要再来看看Statistics的一些用法:
首先看看Statistics的体系结构:可以通过SessionFactory.getStatistics()方法得到Statistics对象,然后就可以通过Statistics得到相关的细节的统计信息:
其中:
Statistics:针对某一个SessionFactory的统计信息,是一个比较笼统的信息统计。该对象还有一些重要的方法是用于得到针对某一些具体特性的统计信息对象。
EntityStatistics:针对某一个具体实体类型的统计信息,可以统计到该实体类型的insert,load,update,get,乐观锁失败等操作个数;
CollectionStatistics:针对某一个实体集合关系(比如one2many,many2many)的统计信息;可以统计到这个集合关系的加载次数,发送的SQL次数,删除和修改次数;
QueryStatistics:针对某一个具体的HQL/SQL查询的统计信息;可以统计到这个HQL/SQL的查询次数,查询缓存情况,查询时间等信息;
SecondLevelCacheStatistics:针对某一个二级缓存区域(region),得到该缓存区域的命中数,缓存对象个数,内存占用大小等信息;
NaturalIdCacheStatistics:针对某一个二级缓存区域(region),得到该缓存区域对自然主键(naturalId)缓存的存放个数,丢失个数,执行时间,存放数据个数等信息。
对于EntityStatistics、CollectionStatistics、QueryStatistics、SecondLevelCacheStatistics和NaturalIdCacheStatistics这几个对象来说,要获得具体的对象实例,都需要通过Statistics对象上面的方法来获取,下面分别举例说明:
比如对于Employee和Department对象来说,假设是一个双向many2one/one2many关系,准备数据代码:
@Before public void save() { Department d = new Department(); d.setName("d1"); Department d2 = new Department(); d2.setName("d2"); Department d3 = new Department(); d3.setName("d3"); Employee e = new Employee(); e.setDept(d); Employee e2 = new Employee(); e2.setDept(d); Employee e3 = new Employee(); e3.setDept(d2); Employee e4 = new Employee(); e4.setDept(d2); Employee e5 = new Employee(); e5.setDept(d2); Session session = HibernateUtil.getInstance().getSession(); session.beginTransaction(); session.save(d); session.save(d2); session.save(d3); session.save(e); session.save(e2); session.save(e3); session.save(e4); session.save(e5); session.getTransaction().commit(); session.close(); }
分别保存了3个dept和5个emp,下面分别来看这几个特殊的统计对象:
1,EntityStatistics:
@Test public void testStatics() { Statistics st = HibernateUtil.getInstance().getSf().getStatistics(); EntityStatistics es = st .getEntityStatistics("cd.itcast.hibernate.day2.many2one.Department"); EntityStatistics es2 = st .getEntityStatistics("cd.itcast.hibernate.day2.many2one.Employee"); System.out.println(es); System.out.println(es2); }
控制台输出:
EntityStatistics[loadCount=0,updateCount=0,insertCount=3,deleteCount=0,fetchCount=0,optimisticLockFailureCount=0] EntityStatistics[loadCount=0,updateCount=0,insertCount=5,deleteCount=0,fetchCount=0,optimisticLockFailureCount=0]
简单说明:
1,通过Statistics对象的getEntityStatistics(String entityName)得到对应Entity的实体统计信息,如果实体在映射的时候配置了entityName,则直接使用该entityName(特别对于动态模型),如果实体没有单独配置entityName,则就是类的全限定名。当然,假如我们在做一个Hibernate监控应用的话,我们更多的情况是使用Statistics对象的getEntityNames()来得到所有的Hibernate管理的EntityName。
2,在EntityStatistics中,可以得到的具体信息有:
getDeleteCount:该实体删除个数
getInsertCount:该实体保存个数
getLoadCount:该实体加载次数(get/load总次数)
getUpdateCount:该实体更新次数
getFetchCount:该实体到数据库中查询的次数
getOptimisticFailureCount:该实体乐观锁失败个数
2,CollectionStatistics:
@Test public void testStatics2() { Session session = HibernateUtil.getInstance().getSession(); List<Department> depts = session.createQuery("FROM Department").list(); for (Department d : depts) { System.out.println(d.getEmps()); } session.close(); Statistics st = HibernateUtil.getInstance().getSf().getStatistics(); CollectionStatistics cs = st .getCollectionStatistics("cd.itcast.hibernate.day2.many2one.Department.emps"); System.out.println(cs); }
控制台输出:
CollectionStatistics[loadCount=3,fetchCount=3,recreateCount=3,removeCount=0,updateCount=0]
简单说明:
1,通过Statistics对象的getCollectionStatistics(String role)得到对应Entity的集合关系统计信息,需要根据具体的关系来得到。关系的意思很简单,就是表明类的全限定名+“.”+表关系的属性名(简单理解,就是那个set/list/map/bag配置的name名字)。当然,假如我们在做一个Hibernate监控应用的话,我们更多的情况是使用Statistics对象的getCollectionRoleNames()来得到所有的Hibernate管理的roleName。
2,在CollectionStatistics对象上,可以得到的具体信息有:
getLoadCount:得到这个关系加载的次数
getFetchCount:得到这个关系到数据库获取数据的次数
getRecreateCount:创建集合的次数
getRemoveCount:集合中有元素删除的次数
getUpdateCount:集合修改(即集合中数据增加)的次数
3,在上面的输出中,很明显能看到LoadCount和FetchCount相等,要提高性能,很明显需要减少LoadCount和FetchCount数量,那么就可以通过两种方式,第一种就是设置batch-fetch来减少FetchCount;第二种就是添加集合二级缓存减少LoadCount:
<collection-cache usage="read-write" collection="cd.itcast.hibernate.day2.many2one.Department.emps"/>
3,QueryStatistics:
@Test public void testStatics3() { Session session = HibernateUtil.getInstance().getSession(); session.createQuery("FROM Department").list(); session.createQuery("FROM Department").list(); session.createQuery("FROM Department").list(); session.createSQLQuery("SELECT * FROM DEPARTMENT").list(); List<Employee> es = session.createQuery("FROM Employee").list(); for (Employee e : es) { e.getDept().getEmps().size(); } session.close(); Statistics st = HibernateUtil.getInstance().getSf().getStatistics(); String[] queries = st.getQueries(); for (String q : queries) { System.out.println(q); QueryStatistics qs = st.getQueryStatistics(q); System.out.println(qs); } }
控制台输出:
FROM Employee QueryStatistics[cacheHitCount=0,cacheMissCount=0,cachePutCount=0,executionCount=1,executionRowCount=5,executionAvgTime=4,executionMaxTime=4,executionMinTime=4] FROM Department QueryStatistics[cacheHitCount=0,cacheMissCount=0,cachePutCount=0,executionCount=3,executionRowCount=9,executionAvgTime=8,executionMaxTime=23,executionMinTime=1] SELECT * FROM DEPARTMENT QueryStatistics[cacheHitCount=0,cacheMissCount=0,cachePutCount=0,executionCount=1,executionRowCount=3,executionAvgTime=8,executionMaxTime=8,executionMinTime=8]
简单说明:
1,通过Statistics对象的getQueryStatistics(String query)得到对应查询的统计信息,需要根据具体的查询语句来得到。当然,假如我们在做一个Hibernate监控应用的话,我们更多的情况是使用Statistics对象的getQueries()方法来得到执行到统计时刻在Hibernate中执行的所有查询。
2,注意,只有createQuery(即HQL)、createNamedQuery和createSQLQuery(即SQL)执行的查询才会在这个统计信息中反映出来,使用createCriteria或hibernate自动的延迟加载等查询是不会在这个统计信息中反映出来。
3,在QueryStatistics对象上面能够得到的统计信息有:
getExecutionCount:该查询执行的次数
getCacheHitCount:该查询在查询缓存中命中数量
getCachePutCount:该查询在查询缓存中存放的次数
getCacheMissCount:该查询在查询缓存中丢失的次数
getExecutionRowCount:该查询一共返回的ResultSet行数
getExecutionAvgTime:该查询执行的平均时间(ms)
getExecutionMaxTime:该查询执行的最长时间(ms)
getExecutionMinTime:该查询执行的最短时间(ms)
4,要提高查询性能,就是要想办法提高查询缓存命中率(CacheHitCount/ExecutionCount)和提高平均查询时间(ExecutionAvgTime),并且,如果CachePutCount过大,那么也说明该查询不适合缓存。
4,SecondLevelCacheStatistics
@Test public void testStatistics4() { Statistics st = HibernateUtil.getInstance().getSf().getStatistics(); String[] regions = st.getSecondLevelCacheRegionNames(); for (String region : regions) { SecondLevelCacheStatistics scs = st .getSecondLevelCacheStatistics(region); System.out.println(scs); } }
控制台输出:
itcast.cd.itcast.hibernate.day2.many2one.Department.emps SecondLevelCacheStatistics[hitCount=0,missCount=0,putCount=0,elementCountInMemory=3,elementCountOnDisk=0,sizeInMemory=6081] itcast.EMP SecondLevelCacheStatistics[hitCount=0,missCount=0,putCount=0,elementCountInMemory=0,elementCountOnDisk=0,sizeInMemory=0]
简单说明:
1,通过Statistics对象的getSecondLevelCacheStatistics(String regionName)得到对应缓存区域(region)的统计信息,需要根据具体的缓存区域名称来得到。当然,假如我们在做一个Hibernate监控应用的话,我们更多的情况是使用Statistics对象的getSecondLevelCacheRegionNames()方法来得到Hibernate中接入的所有缓存区域。
2,SecondLevelCacheStatistics上的统计信息:
getHitCount:得到该区域二级缓存命中次数
getMissCount:得到该区域二级缓存丢失次数
getPutCount:得到该区域二级缓存存放次数
getElementCountOnDisk:得到该区域二级缓存在磁盘上面的元素个数(如果允许了overFlowToDisk)
getSizeInMemory:得到该区域二级缓存在内存中的占用大小(byte)
Map getEntries():可以得到这个区域二级缓存中缓存的实体(注意这个实体不是真正的Employee或者Department对象,而是AbstractReadWriteEhcacheAccessStrategy.Lock对象,如果要得到更易读的缓存对象,需要设置hibernate.cache.use_structured_entries,这个配置后面会介绍)。
NaturalIdCacheStatistics
NaturalId代表自然主键,有别于代理主键<id>;在开发中使用的较少。要使用自然主键,需要修改一下Department的映射文件:
<class name="Department"> <cache usage="read-write" region="DEPT"/> <id name="id"> <generator class="native" /> </id> <natural-id> <property name="name" /> </natural-id> <set name="emps" lazy="extra" inverse="true"> <key column="DEPT_ID" /> <one-to-many class="Employee" /> </set> </class>
注意一点的就是,natural-id是额外的一个作为自然主键的列,他必须和id同时存在。Hibernate会为标记为自然主键的属性或多个属性添加UNIQUE约束。
要使用NaturalId,主要是在查询的时候:
@Test public void testStatistics5() { Session session = HibernateUtil.getInstance().getSession(); session.createCriteria(Department.class) .add(Restrictions.naturalId().set("name", "d1")) .setCacheable(true).setCacheRegion("EMP").uniqueResult(); session.close(); session = HibernateUtil.getInstance().getSession(); session.byNaturalId(Department.class).using("name", "d1").load(); session.close(); Statistics st = HibernateUtil.getInstance().getSf().getStatistics(); String[] regions = st.getSecondLevelCacheRegionNames(); for (String region : regions) { NaturalIdCacheStatistics ncs = st .getNaturalIdCacheStatistics(region); System.out.println(ncs); } }
简单解释一下:
1,要使用自然主键查询,一般有两种方式,一种方式是使用session新提供的byNaturalId方法,该方法需要传入查询对象的类型,并通过using(String propertyName,Object value)来设置要查询的自然主键属性及其值,然后再通过load()方法(立刻查询)或者byReference()方法(延迟加载)来完成对象的查询;第二种方式是通过createCriteria的Restrictions.naturalId约束来查询。
2,自然主键查询一般需要生成2条SQL:
SELECT id FROM DEPARTMENT WHERE name = ?
SELECT * FROM DEPARTMENT WHERE id = ?
即首先查询出符合自然主键的对象的ID,再通过得到的id去load对象。所以,一般情况下,使用自然主键查询对象,都需要开启二级缓存。
3,来看下输出:
NaturalIdCacheStatistics[hitCount=0,missCount=0,putCount=0,executionCount=0,executionAvgTime=0,executionMinTime=9223372036854775807,executionMaxTime=0,elementCountInMemory=1,elementCountOnDisk=0,sizeInMemory=2089]
注意就是在naturalIdCacheStatistics中保存的是NaturalId和对应的对象的ID值。
了解了以上这些内容,要做一个简单的全面监控Hibernate的模块就是很简单的事情了。