Configuration cfg = new Configuration().configure() ;//加载配置文件默认路径为”/hibernate-cfg.xml”
SessionFactory factory = cfg.buildSessionFactory() ;
Session session = factory.openSession() ;
1、关于Session的管理的注意事项:(session的actionQueue和persistenceContext是重点)
User u = new User () ;
u.setName("小强3") ;
Transaction t =session.beginTransaction() ;
session.save(u) ;
u.setName("小二强3") ;
t.commit() ;
发生如下代码
1、Hibernate: insert into User (name, password, createTime, expireTime, id) values (?, ?, ?, ?, ?)
2、Hibernate: update User set name=?, password=?, createTime=?, expireTime=? where id=?
观察发现: save以后,又手动改了u的name ,没有再进行保存commit时仍然进行了update,说明此时的u对象被Session对象管理着(放在一个map里),commit后逐一检查是否更改(实际采用listener监听,扫描listener数组,观察是否发生改变),若更改则进行保存 .
然后再进行了一次测试 (接着上面的)
User user = new User() ;
user.setId(u.getId()) ;
user.setName(u.getName()) ;
user.setPassword("111") ;
session.save(user) ;
//session.update(user) ; //update也是一样的异常
然后发生如下错误:
a different object with the same identifier value was already associated with the session: [com.pqh.entity.User#5a437a293eef6ef9013eef6efb5e0001]
(一开始以为 session管理是记录实体的id,后来看源代码发现是map)此时知道了 new出来的对象 ,即使id相同 ,也无法对应同一条记录 (u所对应的那条数据库记录),但实际上 这两个User对象 的key是相同的,都是[com.pqh.entity.User#5a437a293eef6ef9013eef6efb5e0001]
(可能是先通过key取出对象 ,再进行地址的比较判断是不是同一个对象)
Session.get(object o) 和Session.load(object o) 的区别
Get 是发出sql 查询,而load(lazy加载)是不发出sql去查询
而是用cglib生成一个继承User的代理类
只有在真正使用的时候 才发出sql去加载
2、Detached 状态注意点:
Detached 状态的对象 ,原先处于Session管理中,然后因为session.close ()或clear() 被清除
但是此时可以对该对象进行update ,saveOrUpdate(推测是在sessionFactory中保留了一些信息进行user对象的验证)
若对detached对象进行update,可以修改原来的数据
但若对detached对象进行save操作 ,会存进一条新的数据( 而被session管理的对象则会更新,因为save内部其实调用了saveOrUpdate方法)
User u = new User () ;
Transaction t =session.beginTransaction() ;
session.save(u) ;
u.setName("小二强3") ;
t.commit() ;
session.close() ;
Session session2 = factory.openSession() ;
session2.beginTransaction() ;
session2.save(u) ;
u.setName("小强3") ;
session2.getTransaction().commit() ;
而把session2.save(u) ; 改成session2.update(u) ; 只会
3、对于hibernate 的oid <id>
Hibernate 3中 ,
<id name="id">
<generator class="org.hibernate.id.IncrementGenerator"/>
</id>
该字0段仅对hibernate程序有效 ,在mysql中不会自增,insert时不指定id则出错
4、对于manyToOne 等标签的一个小注意,
如user的外键(在hibernate中变成外键的类对象)为group ,
save一个user时 (在没有级联时)如果group为trasient状态(即也是刚new出来,还没存的)
则会出错 ,要先存group类
而manyToOne 中先指定了casecade= “all” 、”save-update” 、”delete”、”none”的前2个,则不用再先保存group
如果是用已有的groupid insert一个user ,先要查询出group 设进user中,再进行插入,否则会新建group 或出错(把groupid设成数据库中一个grou记录的id时,因为不是被session管理的group,即使id相同,也会出错(新建了重复id的group))
5、one2many 和many2one标签 在数据库添加的外键是一样的
比如在user中设置了<manytoone class=”group” column=”groupid”>
这样会在user表中添加一个groupid 的字段
而只是用onetomany标签,在group中添加
<set name=”users”>
<key column=”groupid” />
<onetomany class=”User” />
<set/>
一样是在user表中加了一个groupid字段 ,两个标签的区别就是可以在group中取到set<User> 而数据库表是没区别的
6、many2many
例子:Student 和course表是多对多的
单向:
只在student中加入
<set name="courses" table="t_students_courses">
<key column="studentid"></key>
<many-to-many class="com.pqh.entity.Course"column="courseid">
</many-to-many>
</set>
生成的表
双向:
几乎相同(两个类中都有对方的set , 其中table名一定要一样t_students_courses,不然会多生成出一个中间表)
7、Session flush :
作用:
1、清理缓存(save ,update 等操作后,在session中有一个临时集合存储对象,还有一个map 用来管理对象 ,flush就是移除临时集合里的对象 ,而evict是移除map里被管理的对象)
2、发送sql (在移除临时集合的同时,发送sql到数据库,但还没有commit)
3、修改EntityEntries中sentinel中的ExistsInDatabase为true
临时集合:
管理的map(对象在collectionEntries中):
数据库的隔离级别 :
Read uncommitted
Read -committed // 其他数据库常用 ()
Repeatable read //mysql 默认 (可重复读)
Serializable
对于id 的native 和uuid
Native:
(Mysql为自增),此时用session.save 就发送了sql
Uuid :
Session.save 还不发送sql ,直到session.flush 调用才发送sql (commit方法会调用session.flush)
如果我们数据库的隔离级别为未提交读( Read uncommitted ),我们可以看到flush后的数据 ,(在commit之前)
8、Hibernate lazy 策略:
Class上的lazy策略 ,load方法得到一个cglib生成的代理实体类(假设是user)
,在getName等方法调用以后 会向数据库发送sql取得相应的实体对象(getId不行,因为查询时id已经自己设进去了,不会再去数据库中查询)
若是在session关闭以后才调用getName等方法去触发sql,就会发生异常
Collection上的lazy策略, load方法得到group对象 ,group对象有set<User>
但直到调用
Set<User> users = group.getUsers() ; //在该方法调用以后才发出了查询全部数据的SQL ,说明集合是支持lazy加载的
Lazy =false 标签
1、 Class上的lazy ,把lazy设为false :
<class name="com.pqh.entity.Groups" lazy="false">
在调用load方法以后立刻发出了sql ,但只加载了普通属性,set 仍然没有加载(many2one one2one 对应的对象也都没有加载)
2、集合上的lazy设为false以后 :
<set name="users" lazy="false">
<key column="groupid"></key>
<one-to-many class="com.pqh.entity.User" />
</set>
在load group类时 立刻把set<User> 加载进来了
3、集合上的lazy设为extra以后 :
<set name="users" lazy="extra">
<key column="groupid"></key>
<one-to-many class="com.pqh.entity.User" />
</set>
其他方面和lazy =true 相似 ,但调用users.size() 时,
发出的sql是 select count(*) from user where user.id = ?
而不是直接把所有的user 都加载进来 , 效率会比lazy =true 高一些
(但是访问任意一个users中的user对象 ,仍然会把所有的user都加载进来)
4、把many2one one2one 的lazy
设为false以后 :不会进行延迟加载
设为proxy以后 :进行延迟加载 (和普通的lazy = true相同)
Noproxy 一般不用
注意 :
对class(group) 设置Lazy = false 以后 ,不会再生成class的代理类,
那么这对类内部的其他属性(如set) 设置的lazy=true有影响吗??
(由图得 :其中的set<User>因为设置了lazy=true , 所以仍然生成了代理类presistentSet ,没有影响)
9、继承映射:
1、每棵继
承树映射成一张表
t_animal
id | name | sex | weight | height | type |
1 | 猪猪 | true | 100 | P | |
2 | 鸟鸟 | false | 50 | B |
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.bjsxt.hibernate">
<class name="Animal" table="t_animal" lazy="false">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="type" type="string"/>
<property name="name"/>
<property name="sex"/>
<subclass name="Pig" discriminator-value="P">
<property name="weight"/>
</subclass>
<subclass name="Bird" discriminator-value="B">
<property name="height"/>
</subclass>
</class>
</hibernate-mapping>
多态查询:在hibernate加载数据的时候能鉴别出正真的类型(instanceOf)
1、get支持多态查询
2、load只有在lazy=false,才支持多态查询
而用load(lazy=true)查询animal时,得到的结果即使是pig (type =p ),但是 animal对象 instanceof Pig == false ,因为该instance是animal的代理子类 ,字节码是不同的
3、hql支持多态查询
2、每个继承类映射成一张表
<hibernate-mapping package="com.bjsxt.hibernate">
<class name="Animal" table="t_animal">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="sex"/>
<joined-subclass name="Pig" table="t_pig">
<key column="pid"/>
<property name="weight"/>
</joined-subclass>
<joined-subclass name="Bird" table="t_bird">
<key column="bid"/>
<property name="height"/>
</joined-subclass>
</class>
</hibernate-mapping>
共生成3张表 ,t_animal t_pig ,t_bird , 后2着的主键分别为pid和bid,是t_animal 的id的外键
3、每个类映射成一张表(子类中加入父类的字段,这时最好设置父类的abstract=true)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.bjsxt.hibernate">
<class name="Animal" abstract="true">
<id name="id">
<generator class="assigned"/>
</id>
<property name="name"/>
<property name="sex"/>
<union-subclass name="Pig" table="t_pig">
<property name="weight"/>
</union-subclass>
<union-subclass name="Bird" table="t_bird">
<property name="height"/>
</union-subclass>
</class>
</hibernate-mapping>
设置abstract=true以后得到2张表(不设置则为3张,但父表是没用的)
字表中直接有父表的字段,不用再像第2种生成方式那样通过join id =id 得到完整的一条记录
此时要注意:分成2张表以后 <generator class="assigned"/>,也可以是uuid,但不能是native ,虽然在数据库中native是可用的,但在hibernate的session中, 若是native ,则插入一个pig 和一个bird数据 ,两个类会都得到id=1 ,这样会出错(同是animal的子类 ,(但这个解释仍然不是非常理解))
10、 复合主键映射 (composite 和component映射相似,但前者是主键,后者是普通字段)
11、 Map ,List 等集合的映射 (生成了其他表)
12 、悲观锁 :
加锁以后读取一条记录,在事务完成前,别的事务不能再读取该记录了 。
在数据库中是select * from XX for update;
在hibernate中使用方法是 :
load (XX.class ,id ,LockModel.UPGRADE) ;
使用悲观所以后,load的lazy会失效
13、乐观锁 :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pqh.entity.Student" optimistic-lock="version"> <!--添加该字段表示乐观锁的控制方式为版本控制 -->
<id name="id">
<generator class="native"/>
</id>
<version name="version"></version> <!--加入一个版本的字段 该字段由hibernate自动进行维护 -->
<property name="st_name" />
<property name="st_classNumber" />
</class>
</hibernate-mapping>
public class Student {
private int id ;
private String st_name ;
private int st_classNumber ;
// ....getter setter .....
}
测试:
线程1取得了一条Student数据并修改,但没有提交
此时线程2也取得该数据 修改并提交 ,再对1进行提交 ,
此时出现如下错误
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.pqh.entity.Student#1]
因为原先 \
Version 为0 。两线程取得的version都为0 线程2修改以后,version变为1 ,而线程1又提交了version为0的数据,此时hibernate可以判断出线程1的记录为脏数据,所以出错
14、hibernate cascade=true 的设置
<hibernate-mapping>
<class name="com.pqh.entity.Groups">
<id name="id">
<generator class="org.hibernate.id.IncrementGenerator"/>
</id>
<property name="name"/>
<set name="users" >
<key column="groupid" /> <!-- 告诉hibernate groups的id字段要设到user哪个字段上去 -->
<one-to-many class="com.pqh.entity.User" />
</set>
</class>
</hibernate-mapping>
public class Groups {
private int id ;
private String name ;
private Set<User> users ;
}
public class User {
private int id;
private String name;
private Groups group ;
}
<hibernate-mapping>
<class name="com.pqh.entity.User">
<id name="id">
<generator class="org.hibernate.id.IncrementGenerator"/>
</id>
<property name="name"/>
<many-to-one name="group" column="groupid" cascade="all">
</many-to-one>
</class>
</hibernate-mapping>
测试:
在many2one 标签上设置了cascade以后 ,
1、save User对象时,会同时save trasient状态的group ,
2、save Group对象时 ,会出错,object references an unsaved transient instance
说明 cascade是单向的,若想group和user 能互相保存trasient状态的对方,many2one 和one2many上都要设置cascade=true
15、Hibernate HQL
首先,query是支持lazy策略的
List list = session.createQuery(" from User u where u.id=0").list() ;
User u= (User) list.get(0);
System.out.println(u.getBelonggroup().getName());
查询得一个User对象,其中的belonggroup是代理类,
hibernate查询语言hql
在hql中关键字不区分大小写,但是属性和类名区分大小写
1、简单属性查询【重要】
* 单一属性查询,返回结果集属性列表,元素类型和实体类中相应的属性类型一致
* 多个属性查询,返回的集合元素是对象数组,数组元素的类型和对应的属性在实体类中的类型一致
数组的长度取决与select中属性的个数
* 如果认为返回数组不够对象化,可以采用hql动态实例化Student对象
参见:SimplePropertyQueryTest.java
2、实体对象查询【重要】
* N + 1问题,在默认情况下,使用query.iterate查询,有可以能出现N+1问题
所谓的N+1是在查询的时候发出了N+1条sql语句
1: 首先发出一条查询对象id列表的sql
N: 根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的sql语句
* list和iterate的区别?
* list每次都会发出sql语句,list会向缓存中放入数据,而不利用缓存中的数据
* iterate:在默认情况下iterate利用缓存数据,但如果缓存中不存在数据有可以能出现N+1问题
参见:SimpleObjectQueryTest1.java/SimpleObjectQueryTest2.java
3、条件查询【重要】
* 可以采用拼字符串的方式传递参数
* 可以采用 ?来传递参数(索引从0开始)
* 可以采用 :参数名 来传递参数
* 如果传递多个参数,可以采用setParamterList方法
* 在hql中可以使用数据库的函数,如:date_format
参见:SimpleConditionQueryTest.java
4、hibernate也支持直接使用sql进行查询
参见:SqlQueryTest.java
5、外置命名查询
* 在映射文件中采用<query>标签来定义hql
* 在程序中采用session.getNamedQuery()方法得到hql查询串
参见:Student.hbm.xml、NameQueryTest.java
<query name="getUserByID">
<![CDATA[
from User u where u.id =?
]]>
</query>
6、查询过滤器
* 在映射文件中定义过滤器参数
* 在类的映射中使用这些参数
* 在程序中启用过滤器
参见:Student.hbm.xml、FilterQueryTest.java
7、分页查询【重要】
* setFirstResult(),从0开始
* setMaxResults,每页显示多少条数据
参见:PageQueryTest.java
//每页3条记录
for (int i =0 ;i<5; i++){
List<User> list = session.createQuery("from User ").setFirstResult(i*3)
.setMaxResults(3).list() ;
System.out.println("第"+(i+1)+"页");
Iterator<User> iter = list.iterator() ;
while (iter.hasNext()){
User u = iter.next();
System.out.println(u.getName()+ ","+u.getCreateTime());
}
}
8、对象导航查询,在hql中采用 . 进行导航【重要】
参见:ObjectNavQueryTest.java
9、连接查询【重要】
* 内连
* 外连接(左连接/右连接)
参见:JoinQueryTest.java
10、统计查询【重要】
参见:StatQueryTest.java
11、DML风格的操作(尽量少用,因为和缓存不同步)
参见:DMLQueryTest.java
session.createQuery("update Student s set s.name=? where s.id < ?")
.setParameter(0, "李四")
.setParameter(1, 5)
.executeUpdate();
16、Hibernate 一级缓存
一级缓存很短和session的生命周期一致,一级缓存也叫session级的缓存或事务级缓存
那些方法(的查询)支持一级缓存:
* get()
* load()
* iterate(查询实体对象)
如何管理一级缓存:
* session.clear(),session.evict()
如何避免一次性大量的实体数据入库导致内存溢出
* 先flush,再clear
如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具
Evict(object ) 会清除object 的缓存 ,
Clear () 会清除当前没在使用的缓存(EntityEntries) ,
Flush() 会对比actionQueue和EntityEntries 中对象的区别,从而进行数据库更新,设置ExistsInDatabase =true ;
17、Hibernate 二级缓存
二级缓存的配置和使用:
* 将echcache.xml文件拷贝到src下
* 开启二级缓存,修改hibernate.cfg.xml文件
<property name="hibernate.cache.use_second_level_cache">true</property>
* 指定缓存产品提供商,修改hibernate.cfg.xml文件
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
* 指定那些实体类使用二级缓存(两种方法)
* 在映射文件中采用<cache>标签
* 在hibernate.cfg.xml文件中,采用<class-cache>标签
二级缓存是缓存实体对象的
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3308/hibernatetest</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 指定缓存产品提供商 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<mapping resource="com/pqh/entity/Groups.hbm.xml"/>
<mapping resource="com/pqh/entity/User.hbm.xml"/>
<class-cache class="com.pqh.entity.User" usage="read-only"/>
</session-factory>
</hibernate-configuration>
用User 和Groups 两个对象,用两个Session查询同一个id的User时,
u =(User) session.get(User.class, 0) ;
System.out.println("name = "+u.getName());
System.out.println(" group ="+u.getBelonggroup().getName());
第二次读取 :虽然在u.getName时没有再发sql,但getBelonggroup时发出了查询group的sql ,(可以说明,如果只配置了User,那么User的one2many ,one2one等映射的对象,不会被缓存起来 )
session和二级缓存的交互 : (默认为CacheMode.Normal)
session.setCacheMode(CacheMode.GET) ;//表示该session的get、load操作只从二级缓存中读,不会写进二级缓存
CacheMode.PUT CacheMode.IGNORE CacheMode.NORMAL