Hibernate---Session讲解
Session概述
- Session 接口是 Hibernate 向应用程序提供的操纵数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载 Java 对象的方法.
- Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应. Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为刷新缓存(flush)
- 站在持久化的角度, Hibernate 把对象分为 4 种状态: 持久化状态, 临时状态, 游离状态, 删除状态. Session 的特定方法能使对象从一个状态转换到另一个状态.
- Session缓存
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期
Session 缓存可减少 Hibernate 应用程序访问数据库的频率。
- 编写一个测试程序
package mao.shu.vo;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Date;
public class NewsTest {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init(){
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
this.sessionFactory = configuration.buildSessionFactory(serviceRegistry);
this.session = this.sessionFactory.openSession();
this.transaction = this.session.beginTransaction();
}
@Test
public void add(){
News vo = new News(1,"测试2","程序测试2",new Date(),200.5);
this.session.save(vo);
}
@Test
public void get(){
News vo = (News) this.session.get(News.class,1);
System.out.println(vo);
News temp = (News) this.session.get(News.class,1);
System.out.println(temp);
}
@After
public void Destroy(){
this.transaction.commit();
this.session.close();
this.sessionFactory.close();
}
}
- 其中的get()方法之中,进行了查询操作,想news表中查询id为1的数据,并输出查询到的数据
- get()方法之中对id为1的数据进行了两次查询查询并输出打印操作,那么应该控制台会输出两次select的语句
- 但是发现控制台只输出了一次查询语句.
- 这是因为当Session对象的生命周期还没有结束的时候,在查询相同的数据对象,会现在缓存之中寻找,如果有找到就直接使用缓存中的数据.
- 操作Session缓存
- flush:Session 按照缓存中对象的属性变化来同步更新数据库
默认情况下 Session 在以下时间点刷新缓存:
- 显式调用 Session 的 flush() 方法
- 当应用程序调用 Transaction 的 commit()方法的时, 该方法先 flush ,然后在 向 数据库提交事务
- 当应用程序执行一些查询(HQL, Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先 flush 缓存,以保证查询结果能够反映持久化对象的最新状态
- 测试flush()方法
@Test
public void testFlush(){
News vo = (News) this.session.get(News.class,1);
vo.setInfo("flush()方法测试");
this.session.flush();//调用flush()方法
System.out.println(vo);
}
- 控制台输出结果
- 可以发现当调用了修改了缓存之中的对象,在调用了flush()方法之后,会执行update语句以来保证缓存之中的数据和数据库之中的数据保持同步.
- 数据库之中保存的数据
commit() 和 flush() 方法的区别:flush 执行一系列 sql 语句,但不提交事务;commit 方法先调用flush() 方法,然后提交事务. 意味着提交事务意味着对数据库操作永久保存下来。
-
注意: 若在为提交事务的时候或显示的调用session.flush()方法之前,也有可能会进行flush()操作.
- 执行HQL或QBC查询,会先进行flush()操作,已得到数据表的最新记录
- 若记录的id是由底层数据库使用自增的方式生成的,则在调用save方法之后,就会立即发送INSERT语句,因为save方法后,必须保证对象的id是存在.
-
测试reflush()
@Test
public void testReFlush(){
News vo = (News) this.session.get(News.class,1);
/*使用断点,暂停程序,暂停程序的时候修改数据库中的数据,查看最后输出的内容是否有变化*/
this.session.refresh(vo);
System.out.println(vo);
}
-
在暂停的时候修改数据库中的info数据
-
但是会发现最后输出的结果并不是修改过的数据
- 这是因为在Mysql数据库之中默认的隔离级别为 “REPETABLE READ” 可重读的,
- 可以通过在Hibernate的映射文件之中设置mysql的隔离级别
- 在hibernate.cfg.xml文件之中设置数据库的隔离级别.
<!--设置数据库的隔离级别-->
<property name="connection.isolation">2</property>
- 重新断点修改数据
- 最后输出最新的数据
- reflush()会强制发送SELECT语句,以使Session缓存中的状态和数据表中对应的记录保持一致.
Session核心方法1
- 测试save()方法
- save()方法最大的特点就是将一个临时对象变为持久化对象
- 为对象分配ID
@Test
public void testSave(){
News vo = new News();
vo.setTitle("testSave()");
vo.setInfo("测试Save");
vo.setPrice(55.0);
System.out.println(vo);
this.session.save(vo);
System.out.println(vo);
}
- 在flush()缓存时会发送一条INSERT语句.
- 在save()执行前,设置ID的方法是无效的.
@Test
public void testSave(){
News vo = new News();
vo.setTitle("testSave()");
vo.setInfo("测试Save");
vo.setPrice(55.0);
//在save()方法执行前设置ID
vo.setId(12345);
System.out.println(vo);
this.session.save(vo);
System.out.println(vo);
}
- 持久化对象的ID是不能够被修改的.
@Test
public void testSave(){
News vo = new News();
vo.setTitle("testSave()");
vo.setInfo("测试Save");
vo.setPrice(55.0);
System.out.println(vo);
this.session.save(vo);
//在save()方法执行过后修改id
vo.setId(12345);
System.out.println(vo);
}
-
如果在save()方法执行过后再次修改id值,就会出现要给异常
-
测试Presisit()方法
-
persist()方法也会执行INSERT操作.
-
在presist()方法执行之前,如果对象已经有ID值,则不会执行INSERT,而会抛出一个异常.
@Test
public void testPersist(){
News vo = new News();
vo.setTitle("testSave()");
vo.setInfo("测试Save");
vo.setPrice(55.0);
vo.setId(12345);
System.out.println(vo);
this.session.persist(vo);
System.out.println(vo);
}
- 测试get()方法和load()方法
- 执行get()方法会立即加载对象,而执行load方法,若不使用该对象,则不会立即执行查询操作,而返回一个代理对象.
get 是立即检索,load 是延迟检索
@Test
public void testGet(){
News getVo = (News)this.session.get(News.class,1);
System.out.println(getVo.getClass().getName());
}
- testGet()方法的执行结果
@Test
public void testLoad(){
News loadVo = (News)this.session.load(News.class,1);
System.out.println(loadVo.getClass().getName());
}
- tstLoad()方法的执行结果,可以发现使用load()方法得到的对象在没有使用的情况下是一个代理对象,这是因为使用load()方法得到的对象,在没有使用任何属性的情况下,不会立即加载,而是加载到内存之中,在有需要的时候载家在对象.
- 若查询一个数据表中没有的记录,而且Session也没有被关闭,同时需要使用对象的时候.
- 使用get方法查询会 返回null
- 使用load方法查询 :若不是用该对象的任何属性没有问题,若需要初始化了,抛出异常.
@Test
public void testGet(){
News getVo = (News)this.session.get(News.class,1888);
System.out.println(getVo.getClass().getName());
}
- 使用get()方法直接抛出空指针异常
@Test
public void testLoad(){
News loadVo = (News)this.session.load(News.class,1888);
System.out.println(loadVo.getClass().getName());
}
- 使用load()方法不会出现异常
3. 在需要初始化代理对象之前若关闭Session. load方法可能会抛出 异常.
@Test
public void testLoad(){
News loadVo = (News)this.session.load(News.class,1888);
this.session.close();//在使用loadVO类之前关闭session
System.out.println(loadVo);
}
- 抛出的异常:org.hibernate.LazyInitializationException: could not initialize proxy - no Session(无法初始化代理,没有Session)
Session核心方法2
- update()方法
- update()方法相当于sql语句之中的Update语句
当修改一个持久化对象的时候不需要现实调用update()方法,在执行transaction.commit()方法时会先执行flush()方法.就会自动执行update语句.
-
如果更新一个游离对象需要使用的调用update()方法
-
update()方法可以将一个游离对象变为一个持久化对象.
-
需要注意的问题:
- 无论要更新的游离对象是否与数据表中的记录是否一致,都会发送Update语句.
@Test
public void testUpdate(){
News temp =new News();
temp.setId(1);
temp.setTitle("update()方法测试");
temp.setPrice(550.2);
temp.setInfo("testUpdate");
temp.setPubDate(new Date());
this.session.update(temp);
}
2. 如何能让update方法不再盲目的触发update语句?在.hbm.xml文件的class节点设置一个select-before-update=“true”()但通常不使用该属性
<class name="News" table="NEWS" select-before-update="true">
- 如果数据表中没有对应的记录,但还调用了update方法,会抛出异常.
- 当前数据库的内容
@Test
public void testUpdate(){
News temp =new News();
temp.setId(12345);//当修改一个数据库之中不存在的数据,就会出现异常
temp.setTitle("update()方法测试");
temp.setPrice(550.2);
temp.setInfo("testUpdate");
temp.setPubDate(new Date());
this.session.update(temp);
}
4. 当update()方法关联一个游离对象时,如果在Session的缓存之中已经存在了相同的OID的持久化对象.会出现异常,因为在Session缓存中,不能够同时存在两个OID相同的对象.
@Test
public void testUpdate(){
//使用get()方法得到一个持久化对象,此时这个对象会保存在session的缓存之中
News vo = (News) this.session.get(News.class,1);
//创建一个于vo的id相同的对象
News temp = new News();
temp.setId(vo.getId());
//使用update()方法关联这个temp对象
//由于此时的temp对象和vo对象的id都是相同的
//并且vo对象已经保存在了session之中
//所以当使用update()方法的时候,就会出现在同一session之中存在两个id相同的对象
this.session.update(temp);
}
- saveOrUpdate()方法
Session 的 saveOrUpdate() 方法同时包含了 save() 与 update() 方法的功能
- 如果OID不为null,但数据表中还没有和其对应的记录,会抛出一个异常.
@Test
public void testSaveOrUpdate(){
News news = new News();
news.setId(123456);//此时数据库之中不存在此id的数据
this.session.saveOrUpdate(news);//如果调用saveOrUpdate()方法就会出现异常
}
判定对象为临时对象的标准
- Java 对象的 OID 为 null
- 映射文件中为 设置了 unsaved-value 属性, 并且 Java 对象的 OID 取值与这个 unsaved-value 属性值匹配
<id name="id" column="ID" unsaved-value="123456">
- delete()方法
- 只要OID和数据表中的一条记录对应,就执行delete操作
@Test
public void testDelete(){
News news = new News();
news.setId(1);//删除数据库之中,id为1的数据
this.session.saveOrUpdate(news);
}
- 使用delete()方法删除后的数据是奖对应的id那一条数据的其他属性都设置为nnull
- 如果OID在数据表中没有对应的记录,抛出异常.
@Test
public void testDelete(){
News news = new News();
news.setId(123);//删除一个数据库之中不存在的数据
this.session.saveOrUpdate(news);
}
Hibernate 的 cfg.xml 配置文件中有一个 hibernate.use_identifier_rollback 属性, 其默认值为 false, 若把它设为 true, 将改变 delete() 方法的运行行为: delete() 方法会把持久化对象或游离对象的 OID 设置为 null, 使它们变为临时对象
<!--改变delet()方法的行为--> <property name="use_identifier_rollback">true</property>
- evict()方法
- 作用:从session缓存中把指定的持久化对象移除.
@Test
public void testEvict(){
//使用load()方法将数据库中的一个对象将在到缓存之中
News vo1 = (News)this.session.load(News.class,4);
//使用evict()方法将缓存中的对象清除
this.session.evict(vo1);
//当要使用该对象的时候,系统无法从缓存之中初始化该对象,就会出现异常
System.out.println(vo1);
}
- doWork()方法
Work 接口: 直接通过 JDBC API 来访问数据库的操作
Session 的 doWork(Work) 方法用于执行 Work 对象指定的操作, 即调用 Work 对象的 execute() 方法. Session 会把当前使用的数据库连接传递给 execute() 方法.@Test public void testWork(){ Work work = new Work() { @Override public void execute(Connection connection) throws SQLException { String sql = "SELECT id FROM news;"; PreparedStatement ps = connection.prepareStatement(sql); ResultSet resultSet = ps.executeQuery(); while(resultSet.next()){ System.out.println(resultSet.getInt(1)); } } }; }