1、一级缓存session级别,二级缓存sessionFactory级别 --操作session缓存的三个方法 flush() refresh() clear()的区别和使用
在事务commit()之前,session会进行flush()操作,这个flush()操作,在对象与数据库记录不一致的情况下(一致则不发),会发sql语句,但仅仅是发语句,而不是执行语句,不会提交事务。可以理解为先列出来,等到commit的时候再执行语句。
refresh()操作会强制执行select语句,使数据库中的记录与session中的实体缓存保持一致
clear()就是直接清除session中的实体缓存,再用再查
数据库的隔离级别,mysql和oracle是不一样的 hibernate设置隔离级别的问题
2、四种状态 -- 把session看成是公司实体,OID看成是公司的员工号
•临时对象(Transient): -- 这就是等于新入职员工,还未到公司的情况。公司还没有给他分配工号,他也还没来公司报道。
–在使用代理主键的情况下, OID 通常为 null
–不处于 Session 的缓存中
–在数据库中没有对应的记录
•持久化对象(也叫”托管”)(Persist):-- 等于正式在公司里的员工
–OID 不为 null
–位于 Session 缓存中
–若在数据库中已经有和其对应的记录, 持久化对象和数据库中的相关记录对应
–Session 在 flush 缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
–在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
•删除对象(Removed) -- 被开除的员工
–在数据库中没有和其 OID 对应的记录
–不再处于 Session 缓存中
–一般情况下, 应用程序不该再使用被删除的对象
•游离对象(也叫”脱管”) (Detached):--- 等于离开公司到外地出差的员工,有员工号,但是没在公司里,在外面。
–OID 不为 null
–不再处于 Session 缓存中
–一般情况需下, 游离对象是由持久化对象转变过来的, 因此在数据库中可能还存在与它对应的记录
一些基础概念和代码
---------------------------
HELLO WORLD 基本代码:
//1. 创建一个 SessionFactory 对象
SessionFactory sessionFactory = null;
//1). 创建 Configuration 对象: 对应 hibernate 的基本配置信息和 对象关系映射信息
Configuration configuration = new Configuration().configure();
// 4.0 之前这样创建
// sessionFactory = configuration.buildSessionFactory();
//2). 创建一个 ServiceRegistry 对象: hibernate 4.x 新添加的对象
//hibernate 的任何配置和服务都需要在该对象中注册后才能有效.
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
//3).
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
//2. 创建一个 Session 对象
Session session = sessionFactory.openSession();
//3. 开启事务
Transaction transaction = session.beginTransaction();
//4. 执行保存操作
News news = new News("Java12345", "ATGUIGU", new Date(new java.util.Date().getTime()));
session.save(news);
//5. 提交事务
transaction.commit();
//6. 关闭 Session
session.close();
//7. 关闭 SessionFactory 对象
sessionFactory.close();
基本配置文件
<hibernate-configuration>
<session-factory>
<!-- 配置连接数据库的基本信息 -->
<property name="connection.username">root</property>
<property name="connection.password">1230</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql:///hibernate5</property>
<!-- 配置 hibernate 的基本信息 -->
<!-- hibernate 所使用的数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<!-- 执行操作时是否在控制台打印 SQL -->
<property name="show_sql">true</property>
<!-- 是否对 SQL 进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<!-- 指定关联的 .hbm.xml 文件 -->
<mapping resource="com/atguigu/hibernate/helloworld/News.hbm.xml"/>
</session-factory>
</hibernate-configuration>
---------------------------------------------
加载:根据特定的OID,把一个对象从数据库加载到内存中。
为了在系统中能够找到所需对象,需要为每一个对象分配一个唯一的标识号。在关系数据库中称之为主键,而在对象术语中,则叫做对象标识(Objectidentifier-OID).
持久化java类的要求:
•使用非 final 类:在运行时生成代理是 Hibernate 的一个重要的功能. 如果持久化类没有实现任何接口, Hibnernate 使用 CGLIB 生成代理. 如果使用的是 final 类, 则无法生成 CGLIB 代理.
•重写 eqauls 和 hashCode 方法: 如果需要把持久化类的实例放到 Set 中(当需要进行关联映射时), 则应该重写这两个方法
Configuration类
•Configuration 类负责管理 Hibernate 的配置信息。包括如下内容:
–Hibernate 运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,数据库连接池等(对应
hibernate.cfg.xml文件)。
–持久化类与数据表的映射关系(*.hbm.xml文件)
•创建 Configuration 的两种方式
–属性文件(hibernate.properties):
•Configuration cfg = new Configuration();
–Xml文件(hibernate.cfg.xml)
•Configuration cfg = new Configuration().configure();
–Configuration 的 configure 方法还支持带参数的访问:
•File file = new File(“simpleit.xml”);
•Configuration cfg = new Configuration().configure(file);
SessionFactory 接口
•针对单个数据库映射关系经过编译后的内存镜像,是线程安全的。
•SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息
•SessionFactory是生成Session的工厂
•构造 SessionFactory 很消耗资源,一般情况下一个应用中只初始化一个 SessionFactory 对象。
•Hibernate4 新增了一个 ServiceRegistry 接口,所有基于 Hibernate 的配置或者服务都必须统一向这个 ServiceRegistry 注册后才能生效
Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心,所有持久化对象必须在 session 的管理下才可以进行持久化操作。此对象的生命周期很短。Session 对象有一个一级缓存,显式执行 flush 之前,所有的持久层操作的数据都缓存在 session 对象处。相当于 JDBC 中的 Connection。
•flush:Session 按照缓存中对象的属性变化来同步更新数据库
•默认情况下 Session 在以下时间点刷新缓存:
–显式调用 Session 的 flush()方法
–当应用程序调用 Transaction 的 commit()方法的时, 该方法先 flush ,然后在向数据库提交事务
–当应用程序执行一些查询(HQL, Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先 flush 缓存,以保证查询结果能够反映持久化对象的最新状态
•flush 缓存的例外情况: 如果对象使用 native 生成器生成 OID, 那么当调用 Session 的 save() 方法保存对象时, 会立即执行向数据库插入该实体的 insert 语句. -- 非自增的情况 有待测试
•commit() 和 flush() 方法的区别:flush 执行一系列 sql 语句,但不提交事务;commit 方法先调用flush() 方法,然后提交事务. 意味着提交事务意味着对数据库操作永久保存下来。
•对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
–脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
–不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
–幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1
再次读取同一个表, 就会多出几行.
•数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
•一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱
•数据库提供的 4 种事务隔离级别: :看课件32页
•Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED
•Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ
•JDBC 数据库连接使用数据库系统默认的隔离级别. 在 Hibernate 的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:
–1. READ UNCOMMITED
–2. READ COMMITED
–4. REPEATABLE READ
–8. SERIALIZEABLE
•Hibernate 通过为 Hibernate 映射文件指定 hibernate.connection.isolation 属性来设置事务的隔离级别 ,一般可设置为 2
<!-- 设置 Hibernate 的事务隔离级别 -->
<property name="connection.isolation">2</property>
•persist() 和 save() 区别:
–当对一个 OID 不为 Null 的对象执行 save() 方法时, 会把该对象以一个新的 oid 保存到数据库中; 但执行 persist() 方法时会抛出一个异常.
•
get VS load:
/**
* get VS load:
*
* 1. 执行 get 方法: 会立即加载对象.
* 执行 load 方法, 若不适用该对象, 则不会立即执行查询操作, 而返回一个
代理对象
*
* get 是 立即检索, load 是延迟检索.
*
* 2. load 方法可能会抛出 LazyInitializationException 异常: 在需要初始化
* 代理对象之前已经关闭了 Session
*
* 3. 若数据表中没有对应的记录, Session 也没有被关闭.
* get 返回 null
* load 若不使用该对象的任何属性, 没问题; 若需要初始化了, 抛出异常.
*/
•Session 的 update()
•Session 的 update() 方法使一个游离对象转变为持久化对象, 并且计划执行一条 update 语句.
游离对象举例:
News news = (News) session.get(News.class,1);
session.close();
此时news就是一个游离对象:在内存中,有OID,但是没有托管的session
啥叫游离,就是离开公司在外出差的员工,还有员工号的,类比离开session 但是有OID的对象
此时,如果再新开一个session, 然后 session.update(news) ,明明news没有更改,还是会发sql语句,因为session已经变了。
News news = (News) session.get(News.class,1);
session.update(news);
这个时候是不会发送sql语句的。因为数据表记录和缓存的对象是一样的,在flush的时候会检查,发现一样的是不会发送sql语句的,不一样的时候才发送。
•若希望 Session 仅当修改了 News 对象的属性时, 才执行 update() 语句, 可以把映射文件中 <class> 元素的
select-before-update 设为 true. 该属性的默认值为 false
•当 update() 方法关联一个游离对象时, 如果在 Session 的缓存中已经存在相同 OID 的持久化对象, 会抛出异常
•当 update() 方法关联一个游离对象时, 如果在数据库中不存在相应的记录, 也会抛出异常.
在同一个Session里面,不能有两个ID相同的对象,举例如下:
News news = (News) session.get(News.class,1);
session.close();
News news2 = (News) session.get(News.class,1);
session2.update(news) // 注:session2是新开的session,省去新开过程
就会报错,显示session里面的该ID为1的对象不唯一
更新数据表里无记录的对象,也会报错,举例如下:
News news = (News) session.get(News.class,1);
session.close();
news.setId(100); // 假设数据表中没有id=100的记录
session2.update(news); // 注:session2是新开的session,省去新开过程
把游离对象的ID更改了,数据表里没有对应的记录了,再更新就会报错。
•Session 的 delete() 方法既可以删除一个游离对象, 也可以删除一个持久化对象,举例如下:
// 删除游离对象
News news = new News();
news.setId(1);
session.delete(news);
// 删除持久化对象
News news = session.get(News.class,1);
session.delete(news);
•Session 的 delete() 方法处理过程
–计划执行一条 delete 语句
–把对象从 Session 缓存中删除, 该对象进入删除状态.
•Hibernate 的 cfg.xml 配置文件中有一个 hibernate.use_identifier_rollback 属性, 其默认值为 false, 若把它设为 true, 将改变 delete() 方法的运行行为: delete() 方法会把持久化对象或游离对象的 OID 设置为 null, 使它们变为临时对象
<!-- 删除对象后, 使其 OID 置为 null -->
<property name="use_identifier_rollback">true</property>
说明如下:
News news = session.get(News.class,1); 1
session.delete(news); 2
System.out.println(news); 3
则运行结果是 先发select语句,对应1;再打印对象,对应3;再发删除语句,是指事务提交session flush时候,对应着2。要理解这个过程,也即session执行delete()方法,并不是马上就删除。
session.evit()是指从session缓存中移除持久化对象。-- 做测试,是否还能打印内存中的对象~~~~
Hibernate调用存储过程,是通过调用JDBC原生API实现的
session.doWork(new Work(){
@Override
public void execute(Connection connection) throws SQLException { System.out.println(connection); }
});
得到了connection,也可以在里面进行批量操作
c3p0数据库连接池
hibernate.c3p0.max_size: 数据库连接池的最大连接数
hibernate.c3p0.min_size: 数据库连接池的最小连接数
hibernate.c3p0.acquire_increment: 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
hibernate.c3p0.timeout: 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
hibernate.c3p0.idle_test_period: 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时. 连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
hibernate.c3p0.max_statements: 缓存 Statement 对象的数量
通过打印上面的session.doWork中的connection 即可以知道连接是否来自c3p0
fetch_size batch_size -- mysql不支持
•hibernate.jdbc.fetch_size:实质是调用 Statement.setFetchSize() 方法设定JDBC 的 Statement 读取数据的时候每次从数据库中取出的记录条数。
–例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会 1 次性把1万条取出来的,而只会取出 fetchSize 条数,当结果集遍历完了这些记录以后,再去数据库取 fetchSize 条数据。因此大大节省了无谓的内存消耗。Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。Oracle数据库的JDBC驱动默认的Fetch Size = 10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍之多,当fetchSize=100,性能还能继续提升20%,Fetch
Size继续增大,性能提升的就不显著了。并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持
•hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。batchSize 越大,批量操作时向数据库发送sql的次数越少,速度就越快。
–测试结果是当Batch Size=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,Batch Size = 50的时候,删除仅仅需要5秒!Oracle数据库batchSize=30 的时候比较合适。
•class 元素用于指定类和表的映射
–name:指定该持久化类映射的持久化类的类名
–table:指定该持久化类映射的表名, Hibernate 默认以持久化类的类名作为表名
–dynamic-insert: 若设置为 true, 表示当保存一个对象时, 会动态生成 insert 语句, insert 语句中仅包含所有取值不为 null 的字段. 默认值为 false
–dynamic-update: 若设置为 true, 表示当更新一个对象时, 会动态生成 update 语句, update 语句中仅包含所有取值需要更新的字段. 默认值为 false
–select-before-update:设置 Hibernate 在更新某个持久化对象之前是否需要先执行一次查询. 默认值为 false
–batch-size:指定根据 OID 来抓取实例时每批抓取的实例数.
–lazy: 指定是否使用延迟加载.
–mutable: 若设置为 true, 等价于所有的 <property> 元素的 update 属性为 false, 表示整个实例不能被更新. 默认为 true.
–discriminator-value: 指定区分不同子类的值. 当使用 <subclass/> 元素来定义持久化类的继承关系时需要使用该属性
unique: 设置是否为该属性所映射的数据列添加唯一约束.
update:指定该属性是否可以被修改
为flase时候表示不能被修改
–index: 指定一个字符串的索引名称. 当系统需要 Hibernate 自动建表时, 用于为该属性所映射的数据列创建索引, 从而加快该数据列的查询.
–length: 指定该属性所映射数据列的字段的长度
–scale: 指定该属性所映射数据列的小数位数, 对 double, float, decimal 等类型的数据列有效.
–formula:设置一个 SQL 表达式, Hibernate 将根据它来计算出派生属性的值.
<!-- 映射派生属性 -->
<property name="desc" formula="(SELECT concat(author, ': ', title) FROM NEWS n WHERE n.id = id)"></property>
•在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
•在没有设置 inverse=true 的情况下,父子两边都维护父子
关系
•在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
•在 1-N 关系中,若将 1 方设为主控方
–会额外多出 update 语句。
–插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
•无论 <class> 元素的 lazy 属性是 true 还是 false, Session 的get() 方法及 Query 的 list() 方法在类级别总是使用立即检索策略
•若 <class> 元素的 lazy 属性为 true 或取默认值, Session 的 load()方法不会执行查询数据表的 SELECT 语句, 仅返回代理类对象的实例, 该代理类实例有如下特征:
–由 Hibernate 在运行时采用 CGLIB 工具动态生成
–Hibernate 创建代理类实例时, 仅初始化其 OID 属性
–在应用程序第一次访问代理类实例的非 OID 属性时, Hibernate 会初始化代理类实例
lazy 仅对Load()方法有效
当lazy为true的时候,如下例子:
Customer customer = (Customer) session.load(Customer.class, 1);
System.out.println(customer.getClass()); // 打印出代理类
System.out.println(customer.getCustomerId()); // 打印出id值为1 ,但是不会发sql语句
System.out.println(customer.getCustomerName()); // 打印出属性值,会发sql语句
lazy 还可以设置为extra
<set> 元素有一个 batch-size 属性, 用来为延迟检索策略或立即检索策略设定批量检索的数量. 批量检索能减少 SELECT 语句的数目, 提高延迟检索或立即检索的运行性能.
List<Customer> customers = session.createQuery("FROM Customer").list();
System.out.println(customers.size()); //
假设size 为 5 那么,如果设置了batch-size 为 5 ,那么下面只会发一条语句,否则就会要检索五次,发5次sql语句
for(Customer customer: customers){
if(customer.getOrders() != null)
System.out.println(customer.getOrders().size());
}
•<set> 元素的 fetch 属性 -- 需要好好看下
•<set> 元素的 fetch 属性: 取值为 “select” 或 “subselect” 时,决定初始化 orders 的查询语句的形式; 若取值为”join”, 则决定 orders 集合被初始化的时机.默认值为 select
•当 fetch 属性为 “subselect” 时
–假定 Session 缓存中有 n 个 orders 集合代理类实例没有被初始化, Hibernate 能够通过带子查询的 select 语句, 来批量初始化 n 个 orders 集合代理类实例
–batch-size 属性将被忽略
–子查询中的 select 语句为查询 CUSTOMERS 表 OID 的 SELECT 语句
投影查询,需要Employee中有相应的构造方法
String hql = "SELECT new Employee(e.email, e.salary, e.dept) "
+ "FROM Employee e "
+ "WHERE e.dept = :dept";
Query query = session.createQuery(hql);
Department dept = new Department();
dept.setId(80);
List<Employee> result = query.setEntity("dept", dept)
.list();
for(Employee emp: result){
System.out.println(emp.getId() + ", " + emp.getEmail()
+ ", " + emp.getSalary() + ", " + emp.getDept());
}
EHCache 二级缓存
集合的二级缓存配置方法 ,比如 Customer类中有个Order类的集合
Customer 配置缓存 其内的order属性配置缓存 Order类配置缓存 要配三个
查询缓存: 默认情况下, 设置的缓存对 HQL 及 QBC 查询时无效的, 但可以通过以下方式使其是有效的
I. 在 hibernate 配置文件中声明开启查询缓存
<property name="cache.use_query_cache">true</property>
II. 调用 Query 或 Criteria 的 setCacheable(true) 方法
III. 查询缓存依赖于二级缓存
hibernate.inilize( xx ) xx为load的对象
要查询问题:
fetch = join
select
subselect
batch_size
-----------------------------------
1、内容多的大属性上,加上 fetch=FetchType.LAZY 延迟加载,否则全抓到session里 session容量太大,放内存里受不了
2、cascade 级联 增删改 的 ,需要哪个添哪个 。 fetch 关系到 查 的,抓取的
3、双向 一对多 多对一 的情况,关系的维护,放在多的那一方。咋维护?说白了就是在 多的里面 放上 一的 ID ,靠这个ID维护而已。
比如 Province 和 City , City 是多的一方:
1)则在City 里面 会有个 private Province province;的属性。靠这个属性来维护双方。这个属性在city的表里 可能是 province_id 的字段,意思就是靠这个字段来关系两者。这里province属性和province_id这个字段是对应等价的。
2)在Province一方,在 getCity()上加上 mappedby = "province" ,意思就指的 是 city表里的 province_id 是维护双方的!!同时加mappedby的一方,也可作为是被维护的一方的标志。
也就是说,谁的里面加了mappedby,那你这个类就是被维护的,没有权力维护,维护权力在对方那里。你没权力维护,那你的表里就没必要有对方的id字段了。。
4、 双向多对多的情况,在维护端的一方,比如 Teacher 和 Student , 假设 Student 是 维护端,那么 Teacher 里面 getStudent()那里加个mappedby,Student里面的getTeacher() 呢,要加上如下:
@ManyToMany
@JoinTable(name="xxx", reverseJoinColumns=@JoinColumn(name="teacher_id"), joinColumn....略 ) // 含双方ID的中间表,这里的reverseJoinColumns 指的是被维护端
getTeacher()