持久化对象的状态
Hibernate持久化对象存在三种状态:
瞬时态 transient:(临时态) 不存在持久化标识OID,尚未与Hibernate Session关联对象,被认为是瞬时状态,失去引用将被JVM回收。特点:无持久化标识OID,未与Session关联。
持久态 persistent:存在持久化标识OID,与当前Session有关联,并且相关联的Session没有关闭,并且事务未提交。特点:存在持久化标识OID,与Session关联。*****注意 持久态对象具有自动更新数据库的能力。
托管态 detached:存在持久化标识OID,但没有与当前session关联,托管状态发送改变 Hibernate不能检测到。特点:存在持久化标识OID,未与Session关联。
测试Hibernate中持久化对象的状态:
对象的状态总结:
持久化对象状态转换:
三种状态的获得与转换如下:
瞬时态:
*获得:
Book book= new Book();
*瞬时—>持久
* save(book);
* save() / saveOrUpdate();
*瞬时—>脱管
* book.setId(1);
持久态:
*获得:
Book book = (Book)session.get(Book.class, 1);
*持久—>瞬时
* delete(book);
*特殊状态:删除态.(被删除的状态,不建议去使用)
*持久—>脱管
* session.close();
* close() / clear() / evict();
托管态:
*获得:
Book book = new Book();
book.setId(1);
*脱管—>持久
* session.update();
* update() / saveOrUpdate() / lock();
*脱管—>瞬时
* book.setId(null);
注意:持久态对象有自动更新数据库的能力;
Hibernate的一级缓存(Session缓存)
什么是缓存:
* 缓存将数据库/硬盘上文件中的数据,放入到缓存中(就是内存中的一片空间)。当再次使用的时候,可以直接从内存中获得。
缓存的好处:
* 提升程序运行的效率。缓存技术是Hibernate的一个优化的手段。
Hibernate分成两个基本的缓存:
- 一级缓存:Session级别的缓存.一级缓存与Session的生命周期是一致的。自带的,不可卸载。
- 二级缓存:SessionFactory级别的缓存,不是自带的。
深入理解Hibernate中的Session缓存:
- 在Session接口的实现中包含一系列的Java集合,这些Java集合构成了Session缓存。只要Session实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。
- 当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清理,该对象仍然处于生命周期中。当试图get()、load()对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库。没有再查询数据库。
- Session能够在某些时间点,按照缓存中对象的变化来执行相关的SQL语句,来同步更新数据库,这一过程被称为刷出缓存(flush)。
默认情况下Session在以下时间点刷出缓存:
- 当应用程序调用Transaction的commit()方法时,该方法先刷出缓存(session.flush() ),然后再向数据库提交事务(tx.commit() )。
- 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态。
- 手动调用Session的flush()方法。
注意:flush与commit是有区别的,区别如下:
- flush()方法进行清理缓存的操作,执行一系列的SQL语句,但不会提交事务;commit()方法会先调用flush()方法,然后提交事务.
- 提交事务意味着对数据库所做的更新会永久保持下来
- 所谓清理,是指Hibernate 按照持久化象的状态来同步更新数据库
- Flush()后只是将Hibernate缓存中的数据提交到数据库,如果这时数据库处在一个事物当中,则数据库将这些SQL语句缓存起来,当Hibernate进行commit时,会告诉数据库,你可以真正提交了,这时数据才会永久保存下来,也就是被持久化了.
- commit针对事物的
- flush针对缓存的
- 同步到数据库中后只要没有commit还是可以rollback的
Hibernate快照区:
当session加载了customer对象后,会为customer对象的值类型的属性复制一份快照。当刷出缓存时,通过比较对象的当前属性和快照,来判断对象的哪些属性发生了变化
向一级缓存中存入数据的时候,放入一级缓存区和缓存快照区,当更新了一级缓存的数据的时候,事务一旦提交,会比对一级缓存和快照区,如果数据一致,不更新,如果数据不一致,自动更新数据库。
Hibernate管理一级缓存:
一级缓存是与session的生命周期相关的.session生命周期结束,一级缓存就结束了。
- clear() / evict() / flush() / refresh() 管理一级缓存
- clear() :清空一级缓存中的所有对象。
- evict(Object obj) :清空一级缓存中的某个对象。
- flush() :刷出缓存。
- refresh(Object obj):将快照区的数据重新覆盖了一级缓存的数据。
清理session的缓存,测试flush和clear、evict方法的使用
session的flush方法让缓存的数据刷出到数据库
session的clear方法清空缓存数据
执行过程:调用session的flush方法后会立即发出查询语句,然后将id为1的customer对象分别复制一份到session的快照区与缓存区。获取customer2时会首先查找比对缓存区中的对象 发现已经存在了OID为1的Customer对象,不再查询数据库,立即返回缓存区中的对应对象。最后调用session的flush方法,会将一级缓存区中的所有对象清除,获取customer3时,先上session一级缓存区中进行查找,没有查找到,再向数据库发出SQL语句进行查询。
session的evict方法清空指定对象一级缓存数据,使对象变为离线
执行过程:第一次获取customer对象 会发出SQL语句进行查询(因为在Session缓存区中没有找到),从数据库查询成功后,还是先将customer各复制一份到Session的缓存区与快照区。调用Session的evict方法,会将customer对象从Session的缓存区与快照区中清除,再次获取时 就会发出SQL查询语句。
refresh刷新一级缓存
当session.load 加载Customer对象后,修改city为 武汉,调用refresh方法更新一级缓存,此时设置的武汉重新被数据表中记录覆盖
一级缓存的刷出时机
清理session的缓存(设置缓存的flush模式)
session.setFlushMode(FlushMode.AUTO);
FlushMode:
常量:
* ALWAYS :每次查询的时候都会刷出;手动调用flush;事务提交的时候。
* AUTO :默认值。有些查询会刷出;手动调用flush;事务提交的时候。
* COMMIT :在事务提交的时候,手动调用flush的时候。
* MANUAL :只有在手动调用flush的时候才会刷出。
****** 严格程度:MANUAL > COMMIT > AUTO > ALWAYS
ALWAYS和AUTO的区别:当hibernate缓存中的对象被改动之后,会被标记为脏数据(即与数据库不同步了)。当 session设置为FlushMode.AUTO时,hibernate在进行查询的时候会判断缓存中的数据是否为脏数据,是则刷数据库,不是则不刷, 而always是只要执行查询都直接刷新,不进行任何判断。很显然auto比always要高效得多
看如下代码图:
会不会执行更新到数据库?
是不会更新到数据库的,因为设置了MANUAL模式,只有在手动调用flush的时候才会刷出缓存。
操纵持久化对象的方法
操作持久化对象 —save()
Session的save()方法保存一条记录,使一个瞬时态的对象转变为持久态对象。
Session的save()方法可以完成以下操作:
- 把瞬时态对象加入Session缓存中,使它进入持久化状态。
- 选用映射文件指定的标识符生成器,为持久化对象分配唯一的OID,在使用代理主键的情况下,setId()方法为瞬时对象设置的OID是无效的。
- 计划执行一条insert语句,把Customer对象当前的属性值组装到insert语句中。
Hibernate通过持久化对象的OID来维持它和数据库相关记录的对应关系,当Customer对象处于持久化状态时,不允许程序随意修改它的ID。
操纵持久化对象—update()
Session的update方法使一个托管对象转变为持久化对象,并且计划执行一条update语句。
以上默认情况下 不管Customer对象c的内容是否发生改变,都会执行更新操作。
若希望Session仅当修改了Customer对象的属性时,才执行update()语句,可以把映射文件中 元素标签上的属性值select-before-update(更新之前先查询)设置为true,该属性的默认值是false。
当update()方法关联一个托管对象时,如果在Session的缓存中已经存在相同OID的持久化对象,就会抛出异常。
执行过程:执行evict(c);方法后对象c变为托管态的游离对象。查询获得c1对象并存入session缓存中,更新游离对象c时会从缓存区中查找和c1相同的对象(注意:缓存即内存,在缓存中查找相同对象是按照对象在内存中的地址进行比对的 因为c和c1的内存地址不同,c会保存到session缓存区中,这时Hibernate检查出缓存区中两个内存地址不同的对象拥有相同的OID时就会抛出异常,因为Hibernate是按照OID进行比对的,是不允许缓存区中存在两个内存地址不同而OID相同的对象的)
抛出异常的原因是:两个不同的对象拥有相同的OID。
当update()方法关联一个托管对象时,如果在数据库中不存在相应的记录,也会抛出异常。
操作持久化对象—saveOrUpdate()
saveOrUpdate():
该方法同时包含save方法和update方法,如果参数是瞬时对象就调用save方法,如果是脱管对象就调用update方法,如果是持久化对象就直接返回。
判断对象为临时对象的标准:
- 标准一:Java对象的OID为null
- 标准二:映射文件中为标签设置了unsaved-value属性,并且Java对象的OID的取值与这个unsaved-value属性值匹配。
根据以上判断临时对象的标准 id=null为临时对象。但可以定义属性id为int id;
* 此时id的默认值为0而不是null。如果将id设置为1,而此时应该在中设置unsaved-value的值为除了1之外的值。此时应该执行更新id为1这条记录的操作(如果所设置的id值在数据库表中不存在则抛出异常)
* 如果我们要执行的是插入操作。这时,可以在标签上设置属性unsaved-value=1,与在JavaBean中定义的int id=1对应,2个值相等则执行插入操作。其中unsaved-value=0(默认值)
即saveOrUpdate的执行流程如下:
首先判断要操作的对象是否有id值 如果id值存在说明是要么是查询出来的持久化对象要么是脱管状态或瞬时态的对象。如果是持久态的对象,则直接返回 ,如果不是持久态的对象首先查找这个对象所属类对应的hbm.xml中标签上的unsaved-value属性 如果对象的id值与unsaved-value的值相同 则判断此对象为瞬时态对象 执行save的插入操作,如果对象的id值不与unsaved-value的的相同 则执行update更新操作,如果在数据库对应表中查询到id值相同的记录则更新这条记录。如果没有查询到 则抛出异常(因为update是不允许更新数据库中不存在的数据的)。如果id不存在则一定为瞬时态的对象,直接执行save的插入操作。
示例如下:
如果参数是瞬时对象就用save方法
如果是脱管对象就用update方法
如果是持久化对象就直接返回
操纵持久化对象-get() load()
都可以根据给定的 OID 从数据库中加载一个持久化对象
区别:当数据库中不存在与 OID 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null
两者采用不同的延迟检索策略
操纵持久化对象-delete
Session的delete()方法既可以删除一个托管对象,也可以删除一个持久化对象
如果参数是持久化对象,就执行一个delete语句,若为托管对象,先使游离对象被session关联。
计划执行一条delete语句。
把对象从Session缓存中删除,使该对象进入脱管状态。刷新缓存后,该对象从数据库中删除。
Hibernate关联关系映射
系统设计中的三种实体关系
实体之间有三种关系:
* 一对多:
* 一个用户,生成多个订单,每一个订单只能属于一个用户。
*建表原则:在多的一方创建一个字段,作为外键指向一的一方的主键。
* 多对多:
*一个学生可以选择多门课程,一个课程可以被多个学生选择。
*建表原则:创建中间表,中间表至少有两个字段,分别作为外键指向多方的主键。
*一对多:(特殊,使用最少)
*一个公司只能有一个注册地址,一个注册地址只能被一个公司使用(如果没有特殊的需求,可以建同一张表)
*建表原则:
*唯一外键:一对一的双方,假设任意一方是多的关系,需要在多的一方创建一个字段,作为外键 指向一的 一方的主键。但是在外键添加一个unique。
*主键对应:一对一的双方通过主键进行关联。
Hibernate关联关系映射:
Hibernate是采用Java对象关系来描述数据表实体间的关系
建立多对一关系映射
客户和订单是典型的一对多关联关系
在Order.hbm.xml配置
- name:设定待映射的持久化类的属性名称,即关联对象的属性的名称。
- column:设定和持久化类的属性对应的表的外键。
- class:关联对象类的全路径。
- not-null:是否允许为空。
建立一对多关系映射
仍然采用客户和订单系统
在Customer.hbm.xml添加元素
- name:设定Customer中集合类型属性名称。
- 中的column:用来描述一对多多的一方的外键名称。
- not null:设置外键字段非空约束。
- :设置一对多映射关系。
- class:设置映射关联目标类的全路径。
注意:一对多是具有双向性的,如果需要在一的一方持有多方的引用,则在一的一方类中添加多方类型的集合引用。在一方的hbm.xml映射文件中添加 标签 在标签中添加子标签和。如果在多方的类中需要引用一方,则在多方对应的类中添加一方的对象引用,然后在多方类对应的hbm.xml映射文件中添加标签。如果都需要引用,则双方全部配置即可。注意:一方和多方的关系是靠多方对应表中指向一方主键的外键来指定的。如果需要指定多个一对多或多对一,即一方中有多个集合(都存放多方的对象),则需要在一方的映射文件中设置多个集合 或在多方的映射表中设置多个标签。注意一个标签的子标签中属性column(多方的表中的外键名称)是和标签中的column一一对应相同的。即多方类中的多个一方对象的引用和一方类中的包含多方的多个集合以及一方映射表中中子标签的column属性值和多方映射文件中的column属性值是一一对应相同的。
—— (一一对应)
—— 中属性column="外键名称" (对应相同)
一对多保存操作
建立双向关联,保存客户 同时也要保存订单。
一对多保存操作——级联保存
采用级联保存操作 cascade="save-update"
保存多方级联保存相关联的一方
注意:如果在多方(Order)对应的关系映射中没有设置cascade=true属性 那么只保存了Order对象,是无法保存其关联的一方对象(Customer)的,并且会抛出org.hibernate.TransientObjectException异常,表示无法保存没有与Session关联的瞬时态对象。如果想在保存多方对象(order)时级联保存关联的一方对象(customer) 需要在多方对应的映射文件的实体关系标签上设置级联属性 cascade="save-update"即可。
保存一方,级联保存相关联的多方。
测试保存Customer对象,将瞬时态的Order对象关联到Customer对象
如果想只保存一方,级联保存或更新关联的多方, 则需要在一方对应的映射文件中的集合标签上设置cascade属性.
一对多保存——对象导航
在配置文件中需要配置2端cascade="save-update"
1、order1关联到customer而customer没有关联到order1。
2、customer关联到order2,order3 而order2和order3没有关联到customer
问题1:session.save(order1) 插入几条数据?
会插入4条记录,因为是双向级联的,保存order1时 会级联保存order1相关联的customer对象,插入两条记录,在级联保存customer对象时 customer会级联保存customer相关联的对象order2和order3 又会插入两条记录 所以一共会插入4条记录。
问题2:session.save(customer);会插入几条记录?
会插入三条记录,在保存customer时 会级联保存order2和order3。由于保存的对象中没有关联到order1,所以无法级联保存order1。
问题3:session.save(order2)插入几条记录?
只会插入一条记录,在保存order2时,由于order2没有关联任何对象。所以只能保存自身。
一对多删除操作——级联删除
默认情况下,Hibernate是不会级联删除的,假设只删除一方(customer),并不会删除多方(customer关联的所有order) 只是将与customer关联的order对应表中指向customer的外键置为null。
如果想删除一方时 也同时删除相关联的多方或者删除多方时也删除相关联的一方,就需要设置级联删除,采用级联删除操作 cascade="delete"
删除多方(订单)时级联删除对应的一方(客户)
会发现删除订单后 对应的客户信息也被删除了,如果想在删除某个多方时级联删除对应的一方,需要在多方对应的映射文件中的标签上设置级联删除属性 cascade="delete"
删除一方(客户)时级联删除对应的多方(对应的所有订单)
发现删除客户数据时,对应的订单数据也会被删除。如果想删除一方时,级联删除多方(对应的所有订单)需要在一方对应的映射文件中的集合属性上添加级联删除属性cascade="delete"
从查询的客户对象中移除订单对象,订单对象是否删除?
发现从客户对应的所有订单中移除某个订单,被移除的订单被删除。原因是从查询的客户对象中移除某个订单,会把被移除订单中指向客户的外键置为null,这时被移除的订单就成了"孤儿", 一旦在客户的映射文件的集合标签上添加了孤儿删除的属性即cascade="delete-orphan" Hibernate就会在移除订单后 将外键为null的订单级联删除。
cascade属性的配置
在对象-关系映射文件中,用于映射持久化类之间关联关系的元素,、和都有一个cascade属性,它用于指定如何操作与当前对象关联的其他对象。
cascade有以下属性值:
总结:
none :不使用级联
dave-update :保存或更新的时候级联
delete :删除的时候级联
all :除了孤儿删除以外的所有级联.
delete-orphan :孤儿删除(孤子删除).
* 仅限于一对多.只有一对多时候,才有父子存在.认为一的一方是父亲,多的一方是子方.
* 当一个客户与某个订单解除了关系.将外键置为null.订单没有了所属客户,相当于一个孩子没有了父亲.将这种记录就删除了.
all-delete-orphan :包含了孤儿删除的所有的级联.
处理双向维护的多余SQL问题
双向维护会产生多余的SQL语句
上面的代码为什么产生两条update语句?如何产生一条语句呢,完成上面的功能呢?
Hibernate: update orders set order_number=?, price=?, customer_id=? where id=?
Hibernate: update orders set customer_id=? where id=?
更改订单表id=6的customer_id=3更改为4
* 使用session缓存的监控功能剖析产生两条update语句的原因:
解决方法:设置inverse属性(单向维护)
通过inverse属性来设置由双方关联的哪一方来维护表和表之间的外键关系。inverse=false的为主动方,inverse=true的为被动方。由主动方负责维护关联关系(配置inverse="true":在哪一端配置,那么那一端就放弃了外键的维护权)
一般情况下一的一方去放弃。
注意:cascade和inverse是有很大区别的
cascade:操作关联对象。
inverse:控制外键的维护。
示例如下:
在一的一方(Customer)设置inverse="true" 由集合端来管理关系
在Customer.hbm.xml中的上配置 cascade="save-update" inverse="true"
发现客户能存到数据库,订单也能存到数据库是因为在Customer的映射关系文件中的集合上设置了cascade="save-update" 级联保存或更新。但是级联保存的订单指向Customer主键的外键属性为null,是因为设置了inverse="true" 即Customer放弃了外键的维护权。
一对多关联中的父子关系
所谓父子关系:
是指父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联。
解除关联关系——父子关系
当customer.hbm.xml的元素的cascade属性取值为all-delete-orphan,
Hibernate会按照如下方式处理customer对象:
1.当保存或更新customer对象时,级联保存或更新所有关联的order对象,
相当于save-update.
2.当删除customer对象时,级联删除所有的order对象,相当于delete。
3.删除不再和customer对象关联的所有order对象。
当关联双方存在父子关系时,就可以把父方的cascade属性设为all-delete-orphan.
注意:父子关系只是针对于一对多而言
多对多关联关系映射
多对多的实体关系模型也是很常见的,比如学生和课程的关系。一个学生可以选修多门课程,一个课程可以被多名学生选修。在关系型数据库中对于多对多关联关系的处理一般采用中间表的形式,将多对多的关系转化成两个一对多的关系。
- 双向 n-n 关联需要两端都使用集合属性
- 双向n-n关联必须使用中间表
- 集合属性应增加 key 子元素用以映射外键列, 集合元素里还应增加many-to-many子元素关联实体类
- 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名. 两个集合元素 set 的 table 元素的值必须指定,而且必须相同。
- set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,
- 其中,key中的column属性和 many-to-many 中的column属性分别指定本持久化类和关联类在连接表中的外键列名,
- 因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a.
具体的类与映射关系的设置如下:
测试保存:
注意:之所以能保存成功,是因为在其中的任意一方(student)对应的标签上设置的inverse="true"
也就是说双向的多对多必须有一方放弃主键的维护。若双方没有任何一方放弃主键的维护,则会抛出约束异常,是因为双方维护会产生多余的SQL,这时多余的SQL就是向中间表里插入两次相同的外键记录作为中间表的联合主键,所以会抛出约束冲突异常。
解除1号学生和1号课程的关联关系
改变1号学生和2号学生课程的关联关系,改为1号学生和1号课程。
删除1号课程
会抛出异常:org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
这个异常表示的是约束冲突异常 因为在中间表中还有外键指向到c1的主键。
如果想解决异常 :
方式一: 级联删除,在Course对应的标签上添加级联删除的属性。这样一来删除了c1就会级联删除与c1相关联的所有students。如果Student中也设置了级联删除的属性,在级联删除与c1相关的students时 也会级联删除students相关联的所有courses。因此很少使用级联删除。
方式二:在代码中进行双向解除关联。会将中间表中对应的联合主键记录给删除。基本上都是使用这种方式。
删除1号课程的同时,要把1号和2号学生删除
设置多个多对多:
打个比方:一个QQ联系人和是多对多的关系,一个有多个联系人,一个联系人有多个。但是一个联系人相关联的又分为两种已经加入的是一个集合,正在审核加入的是另一集合。这种关系要设置两个多对多集合(注意:切不可设置flag标志进行区别)设置要在双方对应的映射文件中各设置两个集合,分别指定不同的中间表,才可以完成这种多对多映射。
映射一对一主键双向关联
一对一关联指两个表之间的记录时一一对应的关系。分为两种:唯一外键关联和主键关联。
一对一方式一:唯一外键关联
比如一家公司(Company)和它所在的地址(Address)。在业务逻辑中要求一家公司只有唯一的地址,一个地址也只有一家公司。 下图表现为外键关联关系。
对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键一端,增加 many-to-one 元素。为 many-to-one元素增加 unique=“true” 属性来表示为1-1关联,并用name属性来指定关联属性的属性名
另一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
具体设置配置如下:
测试唯一性(会报错)
一对一方式二:主键关联
一对一的另一种解决方式就是主键关联,在这种关联关系中,要求两个对象的主键必须保持一致,通过两个表的主键建立关联关系,无须外键参与。
基于主键的映射策略:指一端的主键生成器使用foreign策略,表明根据"对方"的主键来生成自己的主键,自己并不能独立生成主键。子元素指定使用当前持久化类的那个属性作为"对方"。
采用foreign主键生成策略的一端增加one-to-one元素映射关联属性,其one-to-one还应增加constrained="true"属性,另一端(company)增加one-to-one元素映射关联属性。
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键
具体设置和映射如下:
测试保存
注意:无论是那种FlushMode, 在事务中查询到的数据都是更改后最新的数据,并未持久化到数据库。除了MANUAL必须手动flush后commit才能持久化到数据库,别的模式不管是否手动调用flush方法,最后commit时 都会持久化到数据库。举例如下:session.save():实体被纳入session缓存,session计划执行sql,此时查询不会有结果(无插入sql的执行);
session.flush():session“清理”缓存,发送执行sql,但不提交事务,此时在同一session中执行查询可以查询到结果(处于同一个数据库会话,尽管该会话的事物尚未提交,数据库中无真实的数据,此时查询到的应该是数据库本身事务机制的缓存),但在另外的查询中不会有结果(比如在native mysql中,因为不处于同一个数据库会话中);
commit():发送执行sql并提交事务;事务完成
实例演示代码如下:
其中demo1测试持久化对象的状态与一级缓存。demo2测试一对多的关系。demo3测试多对多的关系。
HibernateUtils
package cn.test.hibernate3.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* Hibernate抽取工具类
*
*/
public class HibernateUtils {
private static Configuration configuration;
private static SessionFactory sessionFactory;
static {
configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
}
public static Session openSession() {
return sessionFactory.openSession();
}
public static void main(String[] args) {
openSession();
}
}
log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=off, stdout
Book
package cn.test.hibernate3.demo1;
public class Book {
private Integer id;
private String name;
private String author;
private Double price;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String toString() {
return "Book [id=" + id + ", name=" + name + ", author=" + author
+ ", price=" + price + "]";
}
}
Book.hbm.xml
HibernateTest1
package cn.test.hibernate3.demo1;
import java.util.List;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.test.hibernate3.demo1.Book;
import cn.test.hibernate3.utils.HibernateUtils;
/**
* Hibernate的测试类
*
*/
public class HibernateTest1 {
@Test
// 区分持久化对象的三种状态
public void demo1() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
// 向数据库中保存一本图书
Book book = new Book(); // 瞬时态:没有唯一OID,没有与Session关联
book.setName("Hibernate开发");
book.setAuthor("孙XX");
book.setPrice(65d);
session.save(book);
// 3.事务提交
tx.commit();
// 4.释放资源
session.close();
book.setName("Struts2开发"); // 托管态:有唯一的标识,没有与Session关联
}
@Test
// 测试持久态的对象自动更新数据库
public void demo2() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
// 获得一个持久态的对象
Book book = (Book) session.get(Book.class, 1);
book.setName("Struts2开发");
// 3.事务提交
tx.commit();
// 4.释放资源
session.close();
}
@Test
// 证明一级缓存的存在
public void demo3() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
// save方法可以向一级缓存中存放数据
/*
* Book book=new Book(); book.setName("JQuery"); book.setAuthor("张XX");
* book.setPrice(45d);
*
* Integer id=(Integer) session.save(book);
*
* Book book2=(Book) session.get(Book.class, id);
*
* System.out.println(book2);
*/
// 分别用get执行两次查询
Book book1 = (Book) session.get(Book.class, 1); // 马上发生SQL查询
System.out.println(book1);
Book book2 = (Book) session.get(Book.class, 1);// 不发生SQL,因为使用一级缓存的数据
System.out.println(book2);
// 3.事务提交
tx.commit();
// 4.释放资源
session.close();
}
@Test
// 深入理解一级缓存的结构:快照区
public void demo4() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
// 获得一个持久态的对象
Book book = (Book) session.get(Book.class, 1);
book.setName("Spring3开发");
// 3.事务提交
tx.commit();
// 4.释放资源
session.close();
}
@Test
// 一级缓存的管理:clear/evict()
public void demo5() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
// 获得一个持久态的对象
Book book1 = (Book) session.get(Book.class, 1);
Book book2 = (Book) session.get(Book.class, 2);
System.out.println(book1);
System.out.println(book2);
// session.clear(); // 清空一级缓存区域
session.evict(book1); // 清空一级缓存的某个区域
Book book3 = (Book) session.get(Book.class, 1);
Book book4 = (Book) session.get(Book.class, 2);
System.out.println(book3);
System.out.println(book4);
// 3.事务提交
tx.commit();
// 4.释放资源
session.close();
}
@Test
// 一级缓存的管理:flush()刷出缓存
// 默认情况下 如果没有手动调用flush方法,会在commit时隐式的调用flush方法 发出SQL语句
// 默认情况下 如果手动的调用了flush方法,会在flush的时候发出SQL语句 commit的时候就不再发出
public void demo6() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
Book book = (Book) session.get(Book.class, 1);
book.setName("Hibernate3开发");
session.flush(); // 发出update语句
// 3.事务提交
tx.commit();
// 4.释放资源
session.close();
}
@Test
// 一级缓存的管理:refresh() 将快照区的数据,覆盖了一级缓存的数据
public void demo7() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
Book book = (Book) session.get(Book.class, 1);
book.setName("PHP开发");
session.refresh(book);
// 3.事务提交
tx.commit();
// 4.释放资源
session.close();
}
@Test
// 一级缓存的输出时机(了解)
public void demo8() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// session.setFlushMode(FlushMode.COMMIT); // 事务提交
session.setFlushMode(FlushMode.MANUAL); // 只有手动调用flush的时候才会发送
// 2.开启事务
Transaction tx = session.beginTransaction();
Book book = (Book) session.get(Book.class, 1);
book.setName("JavaScript开发");
List list = session.createQuery("from Book").list();
System.out.println(list);
Book book2 = (Book) session.get(Book.class, 1); // 不发送SQL,从一级缓存中获取数据
System.out.println(book2);
// 3.事务提交
tx.commit();
session.flush();
// 4.释放资源
session.close();
}
@Test
// save方法
public void demo9() {
// 1.创建Session
Session session = HibernateUtils.openSession();
// 2.开启事务
Transaction tx = session.beginTransaction();
Book book = new Book(); // 瞬时态对象
book.setName("网页平面设计");
book.setAuthor("王XX");
book.setPrice(32d);
session.save(book); // 持久态
// 3.提交事务
tx.commit();
// 4.释放资源
session.close();
}
@Test
// update方法:更新一条记录,将托管对象转成持久对象
public void demo10(){
// 1.创建Session
Session session=HibernateUtils.openSession();
// 2.开启事务
Transaction tx=session.beginTransaction();
Book book=new Book(); // 瞬时态
book.setId(1); // 托管态
book.setName("MyBatis开发");
// 默认情况下,每次update对象都会执行update操作
/* 如果在Book对应的hbm映射文件的class标签上设置了select-before-update="true"
* 则只有当更新的对象和数据库中对应的数据不一致时才会发出更新语句update
* */
session.update(book); // 持久态
// 3.提交事务
tx.commit();
// 4.释放资源
session.close();
}
@Test
// saveOrUpdate方法:保存或更新一条记录
// 如果对象时瞬时,执行save操作,如果对象是脱管,执行update操作
// 如果设置的id是数据库中没有保存的.可以在映射文件的上设置unsaved-value="-1"执行保存操作
public void demo11(){
// 1.创建Session
Session session=HibernateUtils.openSession();
// 2.开启事务
Transaction tx=session.beginTransaction();
/*
Book book = new Book();//瞬时态.
book.setName("MySQL数据库");
session.saveOrUpdate(book);// 执行保存.
Book book = new Book();//瞬时态.
book.setId(1); // 脱管态:
book.setName("Java大全");
session.saveOrUpdate(book);// 执行更新.
*/
Book book = new Book();//瞬时态.
book.setId(-1); // 脱管态:
book.setName("Spring大全");
session.saveOrUpdate(book);// 执行保存.
// 3.提交事务
tx.commit();
// 4.释放资源
session.close();
}
}
Customer
package cn.test.hibernate3.demo2;
import java.util.HashSet;
import java.util.Set;
/**
* 客户的实体
*
*/
public class Customer {
private Integer cid;
private String cname;
// 一个客户有多个订单
private Set orders = new HashSet();
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public Set getOrders() {
return orders;
}
public void setOrders(Set orders) {
this.orders = orders;
}
}
Order
package cn.test.hibernate3.demo2;
/**
* 订单的实体
*
*/
public class Order {
private Integer oid;
private String addr;
// 订单属于某一个客户.放置一个客户的对象
private Customer customer;
public Integer getOid() {
return oid;
}
public void setOid(Integer oid) {
this.oid = oid;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
Customer.hbm.xml
Order.hbm.xml
HibernateTest2
package cn.test.hibernate3.demo2;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.test.hibernate3.demo2.Customer;
import cn.test.hibernate3.demo2.Order;
import cn.test.hibernate3.utils.HibernateUtils;
/**
* 一对多测试 为防止干扰,每次测试之前drop掉数据库所有表 在测试删除操作时,drop全部表后,再运行demo1 重新在数据库生成数据
*/
public class HibernateTest2 {
@Test
// 双方都不配置级联的情况下
// 向客户表中插入一个客户,在订单表中插入两个订单
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 定义一个客户
Customer customer = new Customer();
customer.setCname("郭浩");
// 定义两个订单
Order order1 = new Order();
order1.setAddr("西三旗中腾");
Order order2 = new Order();
order2.setAddr("西三旗金燕龙");
// 建立关系
order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
session.save(customer);
session.save(order1);
session.save(order2);
tx.commit();
session.close();
}
@Test
// 保存客户和订单的时候,是否可以只保存其中一方?
// 会报异常:一个持久态的对象关联了一个瞬时态的对象
// org.hibernate.TransientObjectException: object references an unsaved
// transient instance - save the transient instance before flushing:
// cn.test.hibernate3.demo2.Order
public void demo2() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 定义客户
Customer customer = new Customer();
customer.setCname("李小龙");
// 定义订单
Order order = new Order();
order.setAddr("五道口");
order.setCustomer(customer);
customer.getOrders().add(order);
// 保存的时候只保存一方
session.save(customer);
tx.commit();
session.close();
}
@Test
// 保存客户级联保存订单—— 一方设置级联多方
// 集合时客户的关联订单对象的集合.所以在Customer.hbm.xml中上配置一个属性:cascade="save-update"
public void demo3() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 定义客户:
Customer customer = new Customer();
customer.setCname("郭浩");
// 定义订单:
Order order = new Order();
order.setAddr("西三旗中腾建华");
order.setCustomer(customer);
customer.getOrders().add(order);
// 保存的时候只保存一方:
session.save(customer);
tx.commit();
session.close();
}
@Test
// 保存订单级联保存客户——在多方设置级联保存一方
// 在Order.hbm.xml中配置cascade属性:级联保存
public void demo4() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 定义客户:
Customer customer = new Customer();
customer.setCname("郭浩");
// 定义订单:
Order order = new Order();
order.setAddr("西三旗中腾建华");
order.setCustomer(customer);
customer.getOrders().add(order);
// 保存的时候只保存一方:
session.save(order);
tx.commit();
session.close();
}
@Test
// 测试对象的导航关系——目前是双向级联保存或更新关系
public void demo5() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 定义一个客户
Customer customer = new Customer();
customer.setCname("武藤兰");
// 定义三个订单
Order order1 = new Order();
order1.setAddr("西三旗");
Order order2 = new Order();
order2.setAddr("上地");
Order order3 = new Order();
order3.setAddr("五道口");
order1.setCustomer(customer);
customer.getOrders().add(order2);
customer.getOrders().add(order3);
// session.save(order1); // 共发送4条insert语句
// session.save(customer);// 共发送3条insert语句
session.save(order2); // 共发送1条insert语句
tx.commit();
session.close();
}
@Test
// 删除一个客户
// 默认情况下(即都不配置级联删除的情况下),将外键置为null,删除数据记录
// 执行完毕后发现数据库中OID为1的customer被删除,customer有关的所有订单都没有被删除,但是这些订单指向被删除customer主键的外键都被置为null
public void demo6() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 删除方式有两种
// 但当涉及到对象关联关系时,只使用先查询再删除的情况
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
tx.commit();
session.close();
}
@Test
// 级联删除:删除客户的时候级联删除订单
// 在Customer.hbm.xml的标签上配置级联删除的属性cascade="delete"
public void demo7() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 级联删除:先查询再删除的方式
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
tx.commit();
session.close();
}
@Test
// 级联删除:删除订单的时候,级联删除客户
// 在Order.hbm.xml的标签上设置级联删除属性cascade="delete"
public void demo8() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Order order = (Order) session.get(Order.class, 1);
session.delete(order);
tx.commit();
session.close();
}
@Test
// 孤儿删除
// 在Customer.hbm.xml中上配置属性cascade="delete-orphan"
// 如果默认情况下
// 即不配置级联孤儿删除,一的一方(Customer)从集合中移除了若干的多的一方,并不会删除被移除的对象,只是将指向它的外键置为null而已
// 一旦在对应集合上配置了级联孤儿删除,当从集合中移除对象时,外键置为了null Hibernate会查找外键为null的多的一方 并将其删除
public void demo9() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 让1号客户和1号订单解除关系
Customer customer = (Customer) session.get(Customer.class, 1);
Order order = (Order) session.get(Order.class, 1);
customer.getOrders().remove(order);
tx.commit();
session.close();
}
@Test
// 双向维护:自动更新数据库,产生多余的SQL
// 双方默认都有外键的维护能力.必须让其中的一方放弃外键的维护权(一般的情况下都是一的一方放弃)
// Hibernate: update orders set addr=?, cno=? where oid=?
// Hibernate: update orders set cno=? where oid=?
public void demo10() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 让1号客户和1号订单解除关系
Customer customer = (Customer) session.get(Customer.class, 1);
Order order = (Order) session.get(Order.class, 2);
customer.getOrders().add(order);
order.setCustomer(customer);
tx.commit();
session.close();
}
@Test
// 区分cascade和inverse
// 在Customer.hbm.xml中的上配置cascade="save-update" inverse="true"
public void demo11() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();
customer.setCname("张三");
Order order = new Order();
order.setAddr("西三旗");
customer.getOrders().add(order);
// 客户是否存到数据库:存
// 订单是否存到数据库:存 cascade="save-update".外键是null.
session.save(customer);
tx.commit();
session.close();
}
}
Course
package cn.test.hibernate3.demo3;
/**
* 课程的实体类
*/
import java.util.HashSet;
import java.util.Set;
public class Course {
private Integer cid;
private String cname;
// 一个课程被多个学生选择
private Set students = new HashSet();
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public Set getStudents() {
return students;
}
public void setStudents(Set students) {
this.students = students;
}
}
Student
package cn.test.hibernate3.demo3;
import java.util.HashSet;
import java.util.Set;
/**
* 学生的实体类
*
*/
public class Student {
private Integer sid;
private String sname;
// 一个学生选择多门课程
private Set courses = new HashSet();
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Set getCourses() {
return courses;
}
public void setCourses(Set courses) {
this.courses = courses;
}
}
Course.hbm.xml
package cn.test.hibernate3.demo3;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.test.hibernate3.demo3.Course;
import cn.test.hibernate3.demo3.Student;
import cn.test.hibernate3.utils.HibernateUtils;
public class HibernateTest3 {
@Test
// 多对多的学生退选.
// Hibernate: delete from stu_cour where sno=? and cno=?
// 这种从集合中移除另一方在多对多中可行的 即使不配置级联删除也会直接从中间表中删除对应的记录
// 但是如果不配置级联删除 就直接session.delete(student)去删除有课程关联的学生 是会抛出异常的因为中间表中还有一个外键约束
public void demo4() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 查询一号学生
Student student = (Student) session.get(Student.class, 1);
Course course = (Course) session.get(Course.class, 2);
student.getCourses().remove(course);
tx.commit();
session.close();
}
@Test
// 级联删除:在多对多中很少使用.
// 删除:删除学生同时删除学生关联选课
public void demo3() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Student student = (Student) session.get(Student.class, 3);
session.delete(student);
tx.commit();
session.close();
}
@Test
// 级联操作:级联保存:保存学生关联课程
// 在Student.hbm.xml中配置上 cascade="save-update"
public void demo2() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 创建学生:
Student student1 = new Student();
student1.setSname("王五");
// 创建课程:
Course course1 = new Course();
course1.setCname("PHP语言");
student1.getCourses().add(course1);
course1.getStudents().add(student1);
session.save(student1);
tx.commit();
session.close();
}
@Test
// 保存学生和课程.为学生选择一些课程:
// 如果没有任何一方放弃主键的维护,不只是多余SQL的问题 会抛出异常,因为往中间表内插入两条相同的联合主键是不允许的
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 创建学生:
Student student1 = new Student();
student1.setSname("张三");
Student student2 = new Student();
student2.setSname("李四");
// 创建课程:
Course course1 = new Course();
course1.setCname("Java语言");
Course course2 = new Course();
course2.setCname("Android语言");
// 张三选1号和2号课
student1.getCourses().add(course1);
student1.getCourses().add(course2);
course1.getStudents().add(student1);
course2.getStudents().add(student1);
student2.getCourses().add(course1);
course1.getStudents().add(student2);
// 执行保存:
session.save(student1);
session.save(student2);
session.save(course1);
session.save(course2);
tx.commit();
session.close();
}
}
hibernate.cfg.xml
com.mysql.jdbc.Driver
jdbc:mysql:///hibernate3_day02
root
root
org.hibernate.dialect.MySQLDialect
true
true
false
update
org.hibernate.connection.C3P0ConnectionProvider
5
20
120
3000