先介绍几个知识点
session对象
Session为我们实现增删改查。
运载Java对象的工具。从Java到oracle。中途被OracelDialect翻译成了sql。
一个普通Java对象的生命周期:
创建的时候生命开始了,当没有任何一个变量指向这个对象的时候,Jvm就会将其回收。
User u=new User();
u=null;
对象生命周期:一个对象从被创建开始,到不再使用,被垃圾回收期回收为止。
一个Hibernate实体类产生的对象的生命周期:
一个持久化类的实例可能处于三种不同状态中的某一种。 这三种状态的定义则与所谓的持久化上下文(persistence context)有关:
瞬时状态 (Transient): 数据库和session里面都没有记录。
持久状态(Persistent) : 保存在session这个缓存里面,session也叫一级缓存,他是轻量级缓存,同时数据库里面也有对应的记录
脱管状态(Detached) :在数据库还有对应的记录,但是和session没有关系。
三种状态的解释
由new操作符创建,且尚未与Hibernate Session 关联的对象被认定为瞬时(Transient)的
瞬时(Transient)对象不会被持久化到数据库中,也不会被赋予持久化标识(identifier)
如果瞬时(Transient)对象在程序中没有被引用,它会被垃圾回收器(garbage collector)销毁
使用Hibernate Session可以将其变为持久(Persistent)状态
持久(Persistent)的实例在数据库中有对应的记录,并拥有一个持久化标识(identifier)
Hibernate会检测到处于持久(Persistent)状态的对象的任何改动,在当前操作单元(unit of work)执行完毕时将对象数据(state)与数据库同步(synchronize)
在默认情况下,Hibernate会在UPDATE中包含所有的列
如果只更新那些被修改的列,可以通过修改配置dynamic-update=”true”来实现
实例曾经与某个持久化上下文发生过关联,不过那个上下文被关闭了, 或者这个实例是被序列化(serialize)到另外的进程。 它拥有持久化标识,并且在数据库中存在一个对应的行
脱管(Detached)对象不在持久化管理之内,但对脱管对象的引用依然有效,对象可继续被修改
Detached状态的对象可以再次与某个Session实例相关联而成为Persistent对象
三种状态的转换
load()与get()区别
Session session=sf.openSession();
User u=(User)session.get(User.class, 2);
session.beginTransaction();
u.setUserAddress("shandong");
session.getTransaction().commit();
User u=(User)session.load(User.class, 2);
session.beginTransaction();
u.setUserAddress("shandong");
session.getTransaction().commit();
load是生成一个代理类,也就是采用懒加载机制,get方式直接去获取数据(懒加载就是一开始并不加载持久对象,而是加载代理类,等到要使用该对象的时候才真正加载)
get方式在数据库没有对应记录的时候返回null,load方式将会抛出异常。
生命周期转换
import static org.junit.Assert.*;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import db.group.entity.User;
import db.group.util.HibernateUtil;
public class HibernateDemo1 {
private Session session;
@Before
public void before(){
// 1.获取session
session= HibernateUtil.getSession();
}
@After
public void after(){
HibernateUtil.closeSession(session);
}
/**
* 1.将一个瞬时态对象通过save或者saveOrUpdate方法进入持久太
*/
@Test
public void testSave(){
User u=new User();
u.setUsername("张三");
u.setPassword("ok");
u.setAge(20);
Transaction tx = session.beginTransaction();
session.save(u);
tx.commit();
}
/**
* 2 通过id来查询id=1的user对象 */
@Test
public void testGet(){
// User user = (User)session.get(User.class, 1);
User user = (User)session.get(User.class,1);
session.get(User.class,1);
session.get(User.class,1);
session.get(User.class,1);
System.out.println(user.getUsername());
}
/**
* 3 通过id来查询id=1的user对象 */
@Test
public void testPersistence(){
// User user = (User)session.get(User.class, 1);
User user = (User)session.get(User.class,1);
user.setUsername("王五");
Transaction tx = session.beginTransaction();
tx.commit();
}
/**
* 4 持久太对象编程游离态 */
@Test
public void testPersistence2Detached(){
// User user = (User)session.get(User.class, 1);
User user = (User)session.get(User.class,1);//进入持久太
session.clear();
System.out.println(user.getUsername());
//回到持久太
session.saveOrUpdate(user);
}
/**
* 5 持久太对象编程游离态 */
@Test
public void testPersistence2Transient(){
// User user = (User)session.get(User.class, 1);
User user = (User)session.get(User.class,1);//进入持久太
Transaction tx = session.beginTransaction();
session.delete(user);
tx.commit();
}
}
论hibernate的缓存
什么是缓存?
缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质可以是内存、硬盘或者二者混合使用。
Session和sessionfactory
一级缓存:
Session 有一个内置的缓存,其中存放了被当前工作单元加载的对象。
每个Session 都有自己独立的缓存,且只能被当前工作单元访问。
二级缓存:
SessionFactory的外置的可插拔的缓存插件。其中的数据可被多个Session共享访问。
SessionFactory的内置缓存:存放了映射元数据,预定义的Sql语句。
private static SessionFactory sf;
static{
Configuration cfg=new Configuration();
Configuration config=cfg.configure().setNamingStrategy(new ImprovedNamingStrategy());
sf=config.buildSessionFactory();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
sf.openSession();
}
论查询缓存
那么什么是查询缓存呢?hibernate中提供了一级缓存、二级缓存用来提高单个记录查询的效率。查询缓存是为了提高批量查询的效率。何为批量查询呢?就是使用HQL/SQL语句,从数据库中查询多条满足条件的记录。也就是说:load和get缓存中的key是实体对象的主键值;查询缓存中的key则是hql/sql语句。我们以HQL语句为例进行介绍,SQL语句的查询稍后再进行测试。在hibernate中可以使用如下形式的代码,进行HQL查询。
Query query = hqlSession.createQuery("from Student where name='zhangsan111'");
List<Student> studentList = (List<Student>) query.list();
Iterator<Student> studentIterator = (Iterator<Student>) query.iterate();
在hibernate4.1.6版本中,为了使用查询缓存,需要进行两步操作:
1.在hibernate.cfg.xml中开启查询缓存,因为hibernate默认情况下会关闭查询缓存。配置方式如下:
<property name="hibernate.cache.use_query_cache">true</property>
2.在hibernate的Query对象中,通过代码设置开启查询缓存。代码如下:
Session hqlSession = sessionFactory.openSession();
Query query = hqlSession.createQuery("from Student where name='zhangsan111'");
query.setCacheable(true);
注意:经过上面的配置,我们开启了查询缓存,但是没有开启二级缓存。我们先编写单元测试代码,然后通过查看实际的运行结果,来分析查询缓存的原理。测试代码如下:
public class TestQueryCache
{
private SessionFactory sessionFactory = new Configuration().configure()
.buildSessionFactory();
private String testHql = "from Student where name='zhangsan111'";
@Test
// 如果开启了查询缓存,list第二次查询,直接获取缓存的主键id,然后根据id查询详情
public void testUseList()
{
testListQuery(sessionFactory);
System.out.println("-------list进行第二次查询------");
testListQuery(sessionFactory);
}
@Test
// 是否开启查询缓存,对iterator没有影响,它都会先根据hql语句查询id,再根据id查询详情
public void testUseIterator()
{
testIterateQuery(sessionFactory);
System.out.println("-------iterate进行第二次查询------");
testIterateQuery(sessionFactory);
}
@Test
public void testIteratorAndList1()
{
testIterateQuery(sessionFactory);
System.out.println("-------第一次使用iterate,第二次使用list查询------");
testListQuery(sessionFactory);
}
@Test
public void testIteratorAndList2()
{
testListQuery(sessionFactory);
System.out.println("-------第一次使用list,第二次使用iterate查询------");
testIterateQuery(sessionFactory);
}
private void testListQuery(SessionFactory sessionFactory)
{
Session hqlSession = sessionFactory.openSession();
Query query = hqlSession.createQuery(testHql);
query.setCacheable(true);
@SuppressWarnings("unchecked")
List<Student> studentList = (List<Student>) query.list();
for (Student student : studentList)
{
System.out.println("list语句测试query cache:" + student);
}
hqlSession.close();
}
private void testIterateQuery(SessionFactory sessionFactory)
{
Session hqlSession = sessionFactory.openSession();
Query query = hqlSession.createQuery(testHql);
query.setCacheable(true);
@SuppressWarnings("unchecked")
Iterator<Student> studentList = (Iterator<Student>) query.iterate();
while (studentList.hasNext())
{
System.out.println("iterate语句测试query cache:" + studentList.next());
}
hqlSession.close();
}
}
这段新手可以略过
数据库中的记录如下:
1. 2次list()执行结果分析
list()方法执行效果如下:
Hibernate:
select
student0_.id as id0_,
student0_.name as name0_,
student0_.age as age0_
from
Student student0_
where
student0_.name='zhangsan111'
list语句测试query cache:hibernate.Student@a09e41[id=1, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@9dd6e2[id=2, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@8698fa[id=3, name=zhangsan111, age=18]
-------list进行第二次查询------
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
list语句测试query cache:hibernate.Student@1751a9e[id=1, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@60b407[id=2, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@e391c4[id=3, name=zhangsan111, age=18]
第一次查询的时候,list()只发出了一条sql语句,查出了所有name='zhangsan111'的记录共有3条,这个很好理解,因为缓存中没有数据,所以list直接去数据库中进行了查询;第二次查询的时候,list发出了3条根据id去查询的sql语句。这是因为,第二次查询的hql语句跟第一次查询的时候完全相同。所以直接从查询缓存中,获取到满足条件的记录id值。之后根据id去student表中查询详情。可以看出:查询缓存,缓存的key是hql语句,缓存的value是满足hql语句的记录的主键值。也就是说,查询缓存,只是缓存数据库记录的主键值,并不会缓存记录的所有字段值。
2. 2次iterate()执行结果分析
iterate()方法执行效果如下:
Hibernate:
select
student0_.id as col_0_0_
from
Student student0_
where
student0_.name='zhangsan111'
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@61ec49[id=1, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@4e21db[id=2, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@1e3a0ec[id=3, name=zhangsan111, age=18]
-------iterate进行第二次查询------
Hibernate:
select
student0_.id as col_0_0_
from
Student student0_
where
student0_.name='zhangsan111'
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@1e4905a[id=1, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@1751a9e[id=2, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@182a033[id=3, name=zhangsan111, age=18]
可以发现:2次使用iterate进行查询,发出的sql完全相同。是否开启查询缓存,对iterate方式没有影响。无论如何,iterate()都会先查询出满足条件的id值,然后再根据id去数据查询记录的详情。
3. 先iterate后list执行结果分析
testIteratorAndList1()方法执行效果如下:
Hibernate:
select
student0_.id as col_0_0_
from
Student student0_
where
student0_.name='zhangsan111'
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@135daf[id=1, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@158f1fa[id=2, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@e9aa13[id=3, name=zhangsan111, age=18]
-------第一次使用iterate,第二次使用list查询------
Hibernate:
select
student0_.id as id0_,
student0_.name as name0_,
student0_.age as age0_
from
Student student0_
where
student0_.name='zhangsan111'
list语句测试query cache:hibernate.Student@cab854[id=1, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@10bbd42[id=2, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@14323d5[id=3, name=zhangsan111, age=18]
iterate方法,发出了4次sql查询语句,list发出了一次sql查询语句。可以得到结论:iterate不会将满足条件的id值放入查询缓存中,或者说iterate将满足条件的id值放入了查询缓存,但是list方法并没有使用。个人感觉:iterate方法不会将满足查询条件的id值放入查询缓存。因为如果iterate将id放入了查询缓存,list没有理由不去使用;而且通过2次iterate测试发现,查询缓存对iterate没有影响。
4. 先list后iterate执行结果分析
testIteratorAndList2()方法执行效果如下:
Hibernate:
select
student0_.id as id0_,
student0_.name as name0_,
student0_.age as age0_
from
Student student0_
where
student0_.name='zhangsan111'
list语句测试query cache:hibernate.Student@16daa9[id=1, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@14b9a74[id=2, name=zhangsan111, age=18]
list语句测试query cache:hibernate.Student@893969[id=3, name=zhangsan111, age=18]
-------第一次使用list,第二次使用iterate查询------
Hibernate:
select
student0_.id as col_0_0_
from
Student student0_
where
student0_.name='zhangsan111'
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@1add463[id=1, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@12a66ea[id=2, name=zhangsan111, age=18]
Hibernate:
select
student0_.id as id0_0_,
student0_.name as name0_0_,
student0_.age as age0_0_
from
Student student0_
where
student0_.id=?
iterate语句测试query cache:hibernate.Student@e580e1[id=3, name=zhangsan111, age=18]
结论2次list查询的效果,可以得出结论:list会将满足查询条件的记录的id放入查询缓存,但是iterate不会使用这些缓存的id。
5. 总结缓存
默认hibernate不会开启查询缓存,这是因为查询缓存只有在hql/hql语句语义完全一致的时候,才能命中。而实际查询场景下,查询条件、分页、排序等构成的复杂查询sql语句很难完全一致。可能是hibernate觉得命中率低,所以默认关闭了查询缓存。我们可以根据实际使用情况,决定是否开启查询缓存,唯一的原则就是命中率要尽可能的高。如果针对A表的查询,查询sql语句基本都是完全一致的情况,就可以针对A使用查询缓存;如果B表的查询条件经常变化,很难命中,那么就不要对B表使用查询缓存。这可能就是hibernate使用查询缓存的时候,既要在hibernate.cfg.xml中进行配置,也需要query.setCacheable(true)的原因。
查询缓存只对list有用,对iterate方式无用。iterate不会读也不会写查询缓存,list会读也会写查询缓存。查询缓存中的key是sql语句(这些sql语句会被hibernate解析,保证语义相同的sql,能够命中查询缓存),缓存的value是记录的主键值。
查询语句在session中的缓存情况:
list每次都是通过一条语句直接操作数据库取出所有的数据返回(并且将对象存入hibernate缓存);
iterator首先通过一条语句取出所有数据的id,然后通过id在hibernate的一级缓存中查找是否存在该对象,如果存在则直接取出,如果没有则再次发出一条sql语句通过id取得对象(并且加入到缓存中),这样如果所有的id在缓存中都没有的话就会出现n+1条sql语句的问题。
所以两者需要合理的结合使用,最大提高性能。
在查询返回list的方法中如果查询的是所有字段则查询结果直接缓存在session的一级缓存之中,也就是意味着如果此时做了任何修改都会在事务提交后同步到数据库。如果查询了部分字段则session内无缓存。
下一篇
说说hibernate的配置数据库连接池和log4j以及具体的hibernate关联映射是怎么映射的。