hibernate
注意:在开发中,建议使用代理主键。
generator
属性,hibernate 默认使用该主键生成策略。但不建议使用这种方式,尽量要减少手动对主键的操作。实体类经过 hibernate 操作转换成持久化类,下面还是使用实体类说明规则。
关于这三种状态的理解,可以结合下面的 curd 操作和一级缓存来理解。
实体类的代码
package cc.wenshixin.entity;
public class Notice {
private int id; // 公告序号
private String title; // 公告标题
private String content; // 公告内容
private String people; // 发布人
private String date; // 发布日期
public Notice()
{
}
public Notice(String title, String content, String people, String date) {
super();
this.title = title;
this.content = content;
this.people = people;
this.date = date;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getPeople() {
return people;
}
public void setPeople(String people) {
this.people = people;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
@Override
public String toString() {
return "Notice [id=" + id + ", title=" + title + ", content=" + content + ", people=" + people + ", date=" + date
+ "]";
}
}
hibernate 自定义的工具类,方便操作 hibernate。
package cc.wenshixin.utility;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtility {
private static Configuration cfg = null;
private static SessionFactory sessionFactory = null;
//静态代码块
static
{
//加载核心配置文件
cfg = new Configuration().configure();
sessionFactory = cfg.buildSessionFactory();
}
/*提供方法返回sessionFactory*/
public static SessionFactory getSessionFactory()
{
return sessionFactory;
}
/*提供于本地线程绑定的session方法*/
public static Session getSession()
{
return sessionFactory.getCurrentSession();
}
}
下面的操作都是使用JUnit测试工具测试的代码。
增加操作让持久化类从瞬时态变为持久态。
@Test
public void testSave()
{
//得到sessionFactory对象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得session对象
Session session = sessionFactory.openSession();
//开启事务
Transaction tx = session.beginTransaction();
/*执行curd操作*/
Notice notice = new Notice("实验室开放", "同学们课外可自由选择实验", "admin", "2017-10-1");
session.save(notice);
//执行事务
tx.commit();
//关闭session和sessionFactory
session.close();
sessionFactory.close();
}
hibernate 的删改操作都是基于查询操作实现的。
@Test
public void testGet()
{
//得到sessionFactory对象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session
Session session = sessionFactory.openSession();
//开启事务
Transaction tx = session.beginTransaction();
//执行查询操作
Notice notice = session.get(Notice.class, 2);
//这里要先重写toString()方法
System.out.println(notice.toString());
//提交事务
tx.commit();
//关闭session和sessionFactory
session.close();
sessionFactory.close();
}
下面展示了两种方式来删除一条记录,但建议使用第一种,先查询后删除的方式,应该避免第二种直接设置主键对应属性值的方式。
@Test
public void testDelete()
{
//得到sessionFactory对象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session对象
Session session = sessionFactory.openSession();
//开启事务
Transaction tx = session.beginTransaction();
//执行删除操作
//第一种方法
//Notice notice = session.get(Notice.class, 3);
//第二种方法
//Notice notice = new Notice();
//notice.setId(2);
session.delete(notice);
//提交事务
tx.commit();
//关闭session和sessionFactory
session.close();
sessionFactory.close();
}
先得到持久态的对象,再对这个对象进行操作。
@Test
public void testUpdate()
{
//得到sessionFactory对象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session对象
Session session = sessionFactory.openSession();
//开启事务
Transaction tx = session.beginTransaction();
//执行更新操作
Notice notice = session.get(Notice.class, 2);
notice.setTitle("我改变了");
session.update(notice);
//执行事务
tx.commit();
//关闭session和sessionFactory
session.close();
sessionFactory.close();
}
saveOrUpdate()方法是更具持久化对象的状态来做增加或者更新操作的,对象如果是瞬时态,那么执行事务就做增加操作,如果对象是托管态,那么执行事务就做更新操作,但此时要注意,更新操作要把持久化类的所有属性都设置值,否则没有设置属性值的字段为null,下面的代码就会产生这种情况,所以不推荐使用托管态修改数据表种的记录。
@Test
public void testSaveOrUpdate()
{
//得到sessionFactory对象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session对象
Session session = sessionFactory.openSession();
//开启事务
Transaction tx = session.beginTransaction();
//执行增加或更新操作
//Notice notice = new Notice("新的公告", "公告内容", "admin", "2017-10-9");
Notice notice = new Notice();
notice.setId(4);
notice.setPeople("admin");
session.saveOrUpdate(notice);
//提交事务
tx.commit();
//关闭session和sessionFactory
session.close();
sessionFactory.close();
}
Notice notice = new Notice(); //瞬时态
notice。setId(2); //托管态
注意:由于持久化态对象的值改变,其实不用调用 update()方法或者 saveOrUpdate()方法,在执行完事务后就可以自动更新数据库的(在一级缓存中会解释自动更新),但是还是建议把方法加上,便于阅读代码。
首先我们要明白什么是缓存,数据库本身其实就是一个文件系统,并且我们知道使用流的方式操作文件效率不高,所以我们把数据放到内存里面,这样就可以直接读取内存里面的数据,提高读取的效率。
hibernate 框架提供了很多的优化方式,一级缓冲就是优化方式之一。hibernate 还有二级缓存,但现在已经不适用了,使用 redis技术来代替了。
hibernate 的一级缓存就是指 session 缓存,session 缓冲就是一块内存空间,用来存放相互管理的 java 对象,在使用 hibernate 查询对象时,先根据对象的 OID(唯一标识符)去一级缓存中查找,如果找到就直接从一级缓存中取出使用,不用再去数据库查询了,这样就提高了查询效率,如果一级缓存中没有,就要去数据库中查询,然后把查到的数据信息放到一级缓存中。hibernate 的一级缓存的作用就是减少对数据库的访问。
Notice notice1 = session.get(Notice.class, 1);
System.out.println(notice1);
Notice notice2 = session.get(Notice.class, 1);
System.out.println(notice2);
//比较的是对象的指向的地址是否一样
System.out.println(notice1==notice2);
连续执行查询操作,观察控制台的输出,发现只出现了一次查询的 sql 语句,这就说明第二次的查询不是在数据库中查询得到的,而是直接从 hibernate 的一级缓存中取的,并且比较两个对象引用的地址也是true。
验证一级缓存
在前面我们说持久化类改变属性值后,不需使用 update()方法就可以自动更新数据库里面的记录,我们需要指导 hibernate 一级缓存的内部结构。在执行完查询操作后,把查询到的数据放到缓冲区,并且复制一份数据到快照区,直接通过 set 方法改变持久化对象的属性值,也会改变缓冲区里面的内容,在提交事务时比较缓冲区和快照区里面的数据是否一致,如果不一致,就更新数据库中的记录,并更新快照区中的数据。快照区的作用就是确保一级缓存中的数据和数据库中的数据一致。
持久化类自动更新
hibernate 是 jdbc 的轻量级封装,hibernate 的事务处理就是数据库的事务处理。
在数据库操作上,一项事务是由一条或多条操作数据库的 sql 语句组成的一个不可分割的工作单元。只有当事务中的所有操作都正常完成,整个事务才会被提交到数据库中。如果事务中由一项操作没有完成,则整个事务就会被回滚。事务简单理解起来就是,一组逻辑上的操作,组成这组操作的各个单元,要么一起成功,要么一起失败,具有统一性。
事务有很严格的定义,需要同时满足下面的四个特性,这四个特性通常称之为 ACID 特性。
在实际应用中,数据库是要被多个用户共同访问的,在多个事务同时使用相同的数据时,可能会发生并发的问题。
为了避免上面所说的事务并发问题发生,所以在标准的 SQL 规范中,定义了四个事务隔离级别,不同的隔离级别对事务的处理是不同的。
事务的隔离级别是由数据库提供的,但并不是所有数据库都支持四种隔离级别的。在使用数据库时,隔离级别越高,安全性越高,性能越低。在实际的开发中,不会选择最高或者最低的隔离级别,使用数据库默认的即可。
在 hibernate 中,可以通过代码来操作管理事务,如通过 Transaction tx = session.beginTransaction();
开启一个事务,持久化操作后,通过 tx.commit();
提交事务,如果事务出现异常,要通过 tx.rollback();
操作来撤销事务(回滚事务)。
除了在代码中对事务开启,提交和回滚操作外,还可以在 hibernate 的配置文件中对事务进行配置。在配置文件中,可以设置事务的隔离级别。其具体的配置方法是在 hibernate.cfg.xml 文件中的 property
标签中进行的。配置方法:<property name="hibernate.connection.isolation">4</property>
,并且我们在进行正真的事务管理时,需要考虑到事务的应用场景,事务的控制不应该放在 DAO 层,而应该放在 Service 层调用多个 DAO 实现一个业务逻辑的操作。其实最主要的是如何保证在 Service 中开启事务时使用的 Session 对象和 DAO 中多个操作使用的是同一个 Session 对象。
事务处理的层
下面有两种解决办法。
第二种方式时最优的方案,而且具体的实现,hibernate 已经在内部完成了,我们只需要配置一下。hibernate5 种提供了三种管理 Session 对象的方法。
在 hibernate 的配置文件中,hibernate.current_session_context_class 属性用于指定 Session 管理方式,可选值有:1. tread,Session 对象的生命周期与本地线程绑定;2. jta,Session 对象的生命周期与 JTA 事务绑定;managed,hibernate 委托程序来管理 Session 对象的生命周期。在这里我们选择 tread 值,在 hibernate.cfg.xml 中进行配置:<property name="hibernate.current_session_context_class">thread</property>
,并且在 hibernate 中提供了 getCurrentSession()方法来创建一个 Session 和本地线程 TreadLocal 绑定的方法。
/*提供于本地线程绑定的session方法*/
public static Session getSession()
{
return sessionFactory.getCurrentSession();
}
hibernate 提供的这个与本地线程绑定的 Session 可以不用关闭,当线程执行结束后,就会自动关闭了。
下面给出事务操作的规范代码写法。
代码结构如下:
try {
开启事务
提交事务
}catch() {
回滚事务
}finally {
关闭
}
@Test
public void testTx1()
{
Session session = null;
Transaction tx = null;
try{
//得到与本地线程绑定的 Session
session = HibernateUtility.getSession();
//开启事务
tx = session.beginTransaction();
//添加操作
Notice notice = new Notice("本地线程绑定", "规范操作", "admin", "2017-10-8");
session.save(notice);
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
tx.rollback();
} finally {
//不需要关闭session
}
}
//下面的代码只是上面代码的对比
@Test
public void testTx2()
{
SessionFactory sessionFactory = null;
Session session = null;
Transaction tx = null;
try{
sessionFactory = HibernateUtility.getSessionFactory();
//不是与本地线程绑定的 Session,类似于单例模式。
session = sessionFactory.openSession();
//开启事务
tx = session.beginTransaction();
//添加操作
Notice notice = new Notice("本地线程绑定", "规范操作", "admin", "2017-10-8");
session.save(notice);
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
tx.rollback();
} finally {
//需要关闭session
session.close();
sessionFactory.close();
}
}
在前面,我们只进行了简单的 curd 操作,对于查询操作,hibernate 还有几种不同的 API 可以选择使用,在这里先简单介绍一下,在后面还会详细叙述。
使用 query 对象,不需要写 sql 语句,但要写简单的 hql(hibernate query language,hibernate 的查询语言) 语句。
hql 和 sql 语句的区别:
hql语句的写法:from 实体类的名称
。
Query 对象的使用:
示例代码如下:
@Test
//查询表中所有数据
public void testQuery1()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Query<Notice> query = session.createQuery("from Notice");
List<Notice> list = query.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
@Test
//有条件的查询
public void testQuery2()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Query<Notice> query = session.createQuery("from Notice where title=?");
query.setString(0, "实验室开放");
List<Notice> list = query.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
使用 criteria 对象,不需要写语句,直接调用方法来实现。
criteria 对象的使用:
示例代码如下:
@Test
//查询表中所有数据
public void testCriteria1()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Notice.class);
List<Notice> list = criteria.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
@Test
//有条件的查询
public void testCriterial2()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Notice.class);
criteria.add(Restrictions.eq("title", "实验室开放"));
List<Notice> list = criteria.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
从名字就可以看出是和 sql 有关的,直接写 sql 语句,底层 hibernate 调用的是 sql 语句实现的。
SQLQuery 对象
示例代码如下:
@Test
public void testSQLQuery()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM notice_content");
List<Object[]> list = sqlQuery.list();
for(Object[] objects : list)
{
System.out.println(Arrays.toString(objects));
}
}