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类的特点
- 提供一个无参的构造器:使Hibernate可以使用 Constructor.newInstance()来实例化持久化类(利用反射机制)
- 提供一个标识属性(identifier property):通常映射为数据库表的主键字段,如果没有该属性,一些功能将不起作用,例如:session.saveOrUpdate()
- 为类的持久化字段声明访问方法(get/set):Hibernate对JavaBeans风格的属性实行持久化
- 使用非final类:在运行时生成代理是Hibernate的一个重要的功能,如果持久化类没有实现任何借口,Hibernate使用cglib生成代理,如果使用的是final类,则无法生成CGLIB代理
- 重写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与数据库中的触发器协同工作时,会造成两类问题
- 触发器使Session的缓存中的持久化对象与数据库中对应的数据不一致:触发器运行在数据库中,它执行的操作对Session是透明的。
- 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();
}
}