Hibernate

ORM(Object/Relation Mapping):对象/关系映射

ORM的思想:将关系数据库中表中的记录映射成为对象,以对象的形式展现,

程序员可以把对数据库的操作转化为对对象的操作

ORM采用元数据来描述对象-关系映射细节,元数据通常采用XML格式,并且存放在专门的对象-关系文件中

 

Hibernate配置文件中(hibernate.cfg.xml文件)

<id name=”” type=”integer”>

<!—指定持久化类的OID和表的主键的映射,指定独享标识符生成器,负责为OID生成唯一标识符 -->

         <column name=”ID” />

         <generator class=”native” />

</id>

 

//配置映射文件的时候指定的是目录结构,而不是包名

<mapping resource=”com/atguigu/hibernate/….hbm.xml” />

 

Hibernate配置文件的两个配置项

Hbm2ddl.auto:该属性可帮助程序员实现正向工程,即由java代码生成数据库脚本,进而生成具体的表结构,取值   create|update|create-drop|validate

Create:会根据.hbm.xml文件来生成数据表但每次隐形都会删除上一次的表,重新生成表,哪怕两次没有任何改变

Create-drop:会根据.hbm.xml文件生成表,但是SessionFactory一关闭,表自动删除。

Update:最常用的属性,也会根据.hbm.xml文件盒数据库中对应的数据表的结构不同,Hibernate 将更新数据表结构,但不会删除已有的行和列。、

Validate:会和数据库中的表进行比较,若.hbm.xml文件中的列在数据表中不存在,则抛出异常。

Format_sql :是否将SQL转化为格式良好的SQL,取值 true|false.

 

 

在测试文件中

//创建一个SessionFactory对象

SessionFactory sessionFactory = null;

//创建Configuration对象,对应hibernate的基本配置信息和对象关系映射信息

Configuration configuration = new Configuration().configure();

//4.0之前这样创建

//sessionFactory = configuration.buildSessionFactory();

//创建一个ServiceRegistry对象,hibernate 4.*新添加的对象

//hibernate 的任何配置和服务都需要在该对象中注册后才有效。

ServiceRegistry serviceRegistry =

new ServiceRegistryBuilder().applySettings(configuration.getProperties())

                                                                              .buildServiceRegistry();

 

sessionFactory = configuration.buildSessionFactory(serviceRegistry);

//创建一个Session对象

Session session = sessionFactory.openSession();

//开启事务

Transaction transaction = session.beginTransaction();

//执行保存操作

News news = new News(“Java”,”ATGUIGU”,new Date(new java.util.Date().getTime()));

//提交事务

Transaction.commit();

//关闭Session

Session.close();

//关闭SessionFactory

sessionFactory.close();

 

 

持久化Java类的特点

  1. 提供一个无参的构造器:使Hibernate可以使用 Constructor.newInstance()来实例化持久化类(利用反射机制)
  2. 提供一个标识属性(identifier property):通常映射为数据库表的主键字段,如果没有该属性,一些功能将不起作用,例如:session.saveOrUpdate()
  3. 为类的持久化字段声明访问方法(get/set):Hibernate对JavaBeans风格的属性实行持久化
  4. 使用非final类:在运行时生成代理是Hibernate的一个重要的功能,如果持久化类没有实现任何借口,Hibernate使用cglib生成代理,如果使用的是final类,则无法生成CGLIB代理
  5. 重写equals 和hashCode方法:如果需要把吃接话类的实力方法Set中(当需要进行关联映射时),则应该重写这两个方法

 

 

Session接口:

Session 是应用程序对数据库之间交互操作的一个单线程对象,是Hibernate运作的中心,

所有的持久化操作必须在session的管理下才可以进行持久化操作。

 

Session类的方法:

获得持久化的方法:get() load()

持久化对象都得保存,更新和删除

Save() , update(), saveOrUpdate()  , delete()

开启事务:beginTransaction()

管理Session的方法: isOpen() , flush() , clear() , evict() , close()等

 

Session接口是Hibernate向应用程序提供的操作数据库的主要的接口,它提供了基本的保存,更新,删除和加载Java对象的方法。

 

Session具有一个缓存,位于缓存的对象称之为持久化对象,它和数据库中的相关记录对应,Session能够在某些时间点,按照缓存中对象的变化来执行相关的SQL语句,来同步更新数据库,这一过程被称为刷新缓存(flush)

站在持久化的角度,Hibernate把对象分为4种状态:持久化状态,临时状态,游离状态,删除状态,Session的特定方法能使对象从一个状态转换带另一个状态

 

 

开发中不能把Session和Transaction作为成员变量,因为可能存在并发问题

 

Session.flush()的时机

session缓存中对象的状态(包括对象的属性)跟数据库中对应的记录是否一致,不一致的话会发送对应的sql语句让其保持一致 ,可能是update,delete,select,insert 等等

 

 

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;

 

public class HibernateTest {

    private SessionFactory sessionFactory;

    private Session session;

    private Transaction transaction;

   

    @Before

    public void init(){

       Configuration conf = new Configuration().configure();

       ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(conf.getProperties()).buildServiceRegistry();

       sessionFactory = conf.buildSessionFactory(serviceRegistry);

       session = sessionFactory.openSession();

       transaction = session.beginTransaction();

    }

    @Test

    public void test(){

       News news = (News)session.get(News.class, 1);

       news.setAuthor("SUN");

    }

    @Test

    public void testSessionFlush(){

       News news = new News("Java","SUN",new Date());

       session.save(news);

    }

    /**

     * flush:使数据表中的记录和Session缓存中的对象的状态保持一致,威客保持一致,

     * 可能会发送对应的sql语句。(看session缓存中的对象状态和数据库中的是否一模一样)

     * 1.在Transaction的commit()方法中:先调用session的flush方法,再提交事务

     * 2.flush()方法可能会发送sql语句,但不会提交事务。

     * 3.注意:在未提交事务或显示调用session.flush方法之前,也有可能会进行flush()操作,

     * 1).执行HQL 或 QBC 查询,会先进行 flush()操作,得到数据库表的最新记录

     * 2).若记录的ID是由底层数据库使用自增的方式生成的,则在调用save()方法后,

     *      就会立即发送INSERT 语句,因为save方法后,必须保证对象的ID是存在的.

     *     如果是Hibernate生成的ID的话,则在commit()的时候发送SQL语句

     */

   

   /**
  * refresh():会强制发送SELECT 语句,以使Session缓存中对象的状态和数据表中对应的记录
  * 保持一致,但有的时候refresh以后查的结果不是最新的(如mysql是因为mysql事务的隔离级别的问题),
  * 是因为数据库本身事务隔离级别的问题(mysql默认事务隔离级别是可重复读)

  如果用的是mysql数据库:默认的事物隔离级别是可重复读需要显示设置事物的提交方式,才能在refresh的时候

  提前查出数据库中实时的数据
  */

    @Test

    public void testRefresh(){

       News enws = (News)session.get(News.class, 1);

       System.out.println(news);

      

       session.refresh(news);

       System.out.println(news);

      

    }

    @After

    public void destroy(){

       transaction.commit();

       session.close();

       sessionFactory.close();

    }

}

 

Hibernate设置数据库中事务的隔离级别

<property name=”connection.isolation” >2</property>

 

对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的事务隔离机制,就会导致各种并发问题:

         脏读: 对于两个事务T1,T2, T1读取了已经被T2更新但是没有被提交的字段之后,若T2回滚,T1读取的内容就是临时且无效的。

         不可重复读:对于两个事务 ,T1,T2  T1读取了一个字段,然后T2更新了该字段后,T1再次读取同一个字段,值就不同了。

         幻读:对于两个事务T1,T2, T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行之后,如果T1再次读取同一个表,就会多出几行。

数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使他们不会相互影响,避免各种并发问题。

一个事务与其他事务隔离程度的程度称为隔离级别,数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱

 

数据库的隔离级别

数据库提供了4中事务隔离级别:

READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更,脏读,不可重复读和幻读的问题都会出现问题仍然可能出现

READ COMMITED(读已提交数据):只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读

REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值 ,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读仍然。

SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新和山吃操作,所有并发问题都可以避免但性能十分低下。

Oracle支持的2中事务隔离级别:READ COMMITED  ,  SERIALIZABLE ,Oracle默认的事务隔离级别为: READ COMMITED

Myaql支持4种事务隔离级别,Mysql默认的事务隔离级别为REPEATABLE READ

 

JDBC数据库连接使用数据库系统默认的隔离级别,在Hibernate的配置文件中可以显示的设置隔离级别,每一个隔离级别都对应一个整数

-1.READ UNCOMMITED

2.READ COMMITED

4.REPEATABLE DEAD

8.SERIALZEABLE

 

Hibernate 通过为Hibernate映射文件指定

Hibernate.connection.isolation属性来设置事务的隔离级别

 

 

 

Session 的save()方法

Session的save()方法完成以下操作:

把News对象加入到Session缓存中,使它进入持久化状态。

选用映射文件指定额标示符生成器,为持久化对象分配唯一的OID,在使用代理主键的情况下,setId()方法为News对象设置OID是无效的。

计划执行一条insert语句:在flush缓存的时候

Hibernate通过持久化对象的OID来维持它和数据库相关记录的对应关系,当News对象处于持久化状态时,不允许程序随意修改它的ID

 

 

 

Persist()和save()区别:

当对一个OID不为null的对象执行save()方法时,会把该对象以一个新的oid保存到数据库中,但执行persist()方法时会抛出一个异常。

 

如果new出来的对象没有设置ID的话其是临时对象,如果设置了id的话 其就为游离对象了

 

Hibernate与触发器协同工作

Hibernate与数据库中的触发器协同工作时,会造成两类问题

  1. 触发器使Session的缓存中的持久化对象与数据库中对应的数据不一致:触发器运行在数据库中,它执行的操作对Session是透明的。
  2. Session的update()方法盲目地激发触发器:无论游离对象的属性是否发生变化,都会执行update语句,而update语句会激发数据库中相应的触发器。

解决方案:

在执行完Session的相关操作后,立即调用Session的flush()和refresh()方法,迫使session的缓存与数据库同步(refresh()方法重新从数据库中加载对象);

  refresh():会强制发送SELECT 语句,以使Session缓存中对象的状态和数据表中对应的记录保持一致

在映射文件的<class>元素中设置select-before-update属性:当Session的update或saveOrUpdate()方法更新一个游离对象时,会先执行select语句,获得当前游离对象在数据库中的最新数据(即把在数据库中存在的游离对象转换为持久化对象),只有在不一致的情况下才会执行update语句。

 

 

public class HibernateTest {

    private SessionFactory sessionFactory;

    private Session session;

    private Transaction transaction;

   

    @Before

    public void init(){

       Configuration conf = new Configuration().configure();

       ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(conf.getProperties()).buildServiceRegistry();

       sessionFactory = conf.buildSessionFactory(serviceRegistry);

       session = sessionFactory.openSession();

       transaction = session.beginTransaction();

    }

    @Test

    public void test(){

       News news = (News)session.get(News.class, 1);

       news.setAuthor("SUN");

    }

    @Test

    public void testSessionFlush(){

       News news = new News("Java","SUN",new Date());

       session.save(news);

    }

    /**

     * flush:使数据表中的记录和Session缓存中的对象的状态保持一致,为了保持一致,

     * 可能会发送对应的sql语句。(看session缓存中的对象状态和数据库中的是否一模一样)

     * 1.在Transaction的commit()方法中:先调用session的flush方法,再提交事务

     * 2.flush()方法可能会发送sql语句,但不会提交事务。

     * 3.注意:在未提交事务或显示调用session.flush方法之前,也有可能会进行flush()操作,

     * 1).执行HQL 或 QBC 查询,会先进行 flush()操作,得到数据库表的最新记录

     * 2).若记录的ID是由底层数据库使用自增的方式生成的,则在调用save()方法后,

     *      就会立即发送INSERT 语句,因为save方法后,必须保证对象的ID是存在的.

     *     如果是Hibernate生成的ID的话,则在commit()的时候发送SQL语句

     */

   

    /**

     * refresh():会强制发送SELECT 语句,以使Session缓存中对象的状态和数据表中对应的记录

     * 保持一致,但有的时候refresh以后查的结果不是最新的(如mysql),是因为,

     * 数据库本身事务隔离级别的问题

     */

    @Test

    public void testRefresh(){

       News news = (News)session.get(News.class, 1);

       System.out.println(news);

      

       session.refresh(news);

       System.out.println(news);

      

    }

    /**

     * persist():也会执行insert操作

     *

     * 和save()的区别:

     * 在persist方法之前,若对象已经有id了,则不会执行insert ,而抛出异常

     */

    public void testPersist(){

       News news  = new News();

       news.setTitle("cc");

       news.setAuthor("cc");

       news.setDate(new Date());

       news.setId(1101);

      

       session.persist(news);

    }

   

   

   

    /**

     * save()方法

     * 1)使一个临时对象变为持久态对象

     * 2)为对象分配ID

     * 3)在flush缓存时会发送一条Insert语句

     * 4)在save方法之前设置的id是无效的

     * 5)持久化对象的ID是不能被修改的(ts.commit之前)!否则会报错

     */

    @Test

    public void testSave(){

       News news  = new News();

       news.setTitle("cc");

       news.setAuthor("cc");

       news.setDate(new Date());

       news.setId(100);

       System.out.println(news);

       session.save(news);

       news.setId(1000);//报错

      

    }

    /**

     * get vs load

     * 1.执行get 方法:会立即加载对象,而执行load方法,若不使用该对象,则不会立即执行查询操作,

     *    而返回一个代理对象。

     *    get是立即检索,load是延迟检索

     * 2.若数据表中没有对应的记录,且session也没有被关闭

     *  get 返回null

     *  load 若不使用该对象的任何属性,没问题;若需要初始化了,抛出异常

     * 3.load方法可能会抛出 LazyInitializationException 异常:在需要初始化代理对象

     *   之前,已经关闭了session 可能会抛出这个异常

     */

    @Test

    public void testLoad(){

 

       News news = (News) session.load(News.class, 1);

       System.out.println(news.getClass().getName());//打印的是代理对象的名称

       System.out.println(news);

    }

   

    @Test

    public void testGet(){

       News news = (News) session.get(News.class, 1);

       System.out.println(news);

    }

    /**

     * update:

     * 1.若更新一个持久化对象的属性不需要显示的调用 update 方法(如果显示的调用也没有错),因为在调用

     * Transaction的commit方法时 会先执行session的flush方法。

     * 2.更新一个游离对象到数据库当中,需要显示的调用session的update方法,

     * 可以把一个游离对象,转换成为一个持久化对象(自我总结:修改游离态对象的任何属性是不会报错的)

     * 需要注意:

     * 1.无论要更新的游离对象和数据库表的记录是否一致,都会发送update语句

     *      (但持久化对象如果和数据库表中记录一致时不会发送update语句不一样时才会发)

     *      如何能让update方法不再盲目的触发update语句呢?在.hbm.xml文件的

     *      class节点设置一个属性,select-before-update=“true”就可以了,即在更新之前再查一下

     *      (默认为false),但看到了两个select语句,但通常不需要设置该属性。除非你要与触发器协同工作,、

     *      如果没有触发器,则不需要设置它

     * 2. 若数据表中没有对应的记录,但还调用了updata方法,则抛出异常

     * (如果对于数据库表中不存在的对象,即对象的OID在数据库表中没有相应id记录与之对应,调用updata方法的话会抛出异常)

     */

    @Test

    public void testUpdate(){

       News news = (News) session.get(News.class, 1);

      

       transaction.commit();

       session.close();

       session = sessionFactory.openSession();

       transaction= session.beginTransaction();

       news.setId(111);//游离对象时可以改id的 ,

       news.setAuthor("Oracle");

    }

    /**

     * 3.当update()方法关联一个游离态对象时,

     * 如果session的缓存中已经存在相同的OID的持久化对象,会抛出异常,

     * 因为在session缓存中不能有两个 OID 相同的对象

     */

    @Test

    public void testUpdate2(){

       News news = (News) session.get(News.class, 1);

      

       transaction.commit();

       session.close();

       session = sessionFactory.openSession();

       transaction= session.beginTransaction();

      

       News news2 = (News) session.get(News.class, 1);

       session.update(news);//会报错

    }

   

    /**

     * 注意:

     *  1.若OID不为空 ,但数据表中还没有和其对应的记录,会抛出一个异常

     *  2.了解:OID值 等于id 的upsaved-value属性值得的对象,也会被认为是一个游离对象

     *     如果数据库中没有对应的记录(即记录的id没有与OID对应的),则会执行一个insert语句

     */

    public void testSaveOrUpdate(){

       News news = new News("FF","ff",new Date());

       news.setId(1);

       session.saveOrUpdate(news);

       //新创建的对象(即OID为null)

       //控制台会打印insert语句

       //如果手动为新创建的对象设置与数据库中记录相同的id会打印update语句 ,

       //更新数据库中的信息

    }

    /**

     * delete:执行删除操作,只要OID(游离对象(包括新创建的对象)

     * 或持久化对象的OID)和数据表中一条记录对应,就会准备执行delete操作

     * 若OID在数据库表中没有对应的记录,则抛出异常。

     *

     * 如果删除对象后,不能对该对象进行update 和select操作 ,因为它的删除操作是在

     * session.flush期间完成的

     * 可以通过设置hibernate配置文件的一个属性hibernate.use_identifier_rollback

     * 为true使删除对象后,把它的OID置空为null

     * <!--删除对象后使其OID置为null-->

     * <property name="use_identifier_rollback" >true</property>

     */

    @Test

    public void testDelete(){

//     News news = new News();

//     news.setId(1);

      

       News news = (News) session.get(News.class, 1);

       session.delete(news);

       //<property name="use_identifier_rollback" >true</property>

       //如果在hibernate配置文件中设置了该配置,还没删除对象呢

       //但是已经设置该对象的OID为空了

       System.out.println(news);

    }

    /**

     *  evict:从session缓存中把持久化对象移除

     */

    public void testEvict(){

       News news1 = (News) session.get(News.class, 1);

       News news2 = (News) session.get(News.class, 2);

      

       news1.setTitle("aa");

       news2.setTitle("bb");

      

       session.evict(news1);//在数据库中看不到数据的更新

       //打印语句只有两个select 和一个update

    }

 

    /**

     * 使用Hibernate调用存储过程

     */

    @Test

    public void testDoWork(){

       session.doWork(new Work(){

           public void execute(Connection connection) throws SQLException {

              System.out.println(connection);

              //调用存储过程(原生的connection)

             

           }

       });

    }

 

    @After

    public void destroy(){

       transaction.commit();

       session.close();

       sessionFactory.close();

    }

}