hibernate笔记(4)——各类概念

Hibernate的任何对数据有改动的操作,都应该被放在事务里面.  
在事务中的多个操作行为,要么都成功,要么都失败
hibernate中的事务由s.beginTransaction();开始
由s.getTransaction().commit();结束
本例子,执行了两个操作
第一个,删除id=8的产品,这个是会成功的
第二个,修改id=11的产品,使得其产品名称超过了数据库中设置的长度30,这个是会失败的。
因为这两个操作都是在一个事务中,而且第二个操作失败了,所以最后的结果是两个操作都没有生效
SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession() ;
s.beginTransaction();
Product p9 = (Product)s.get(Product. class, 9) ;
s.delete(p9) ;
Product p11 = (Product)s.get(Product. class, 11) ;
p11.setName( "长度超过30的字符串作为产品名称长度超过30的字符串作为产品名称长度超过30的字符串作为产品名称长度超过30的字符串作为产品名称") ;
s.update(p11) ;
s.getTransaction().commit();
s.close() ;
sf.close() ;

MYSQL 表的类型必须是INNODB才支持事务

hibernate中的 延迟加载 (lazyload)分 属性的延迟加载 关系的延迟加载  
属性的延迟加载:  
当使用 load 的方式来获取对象的时候,只有访问了这个对象的属性,hibernate才会到数据库中进行查询。否则不会访问数据库

在打印log1之前,是不会打印出sql语句的,只有在访问属性“getName()"的时候,才会访问数据库
SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession() ;
Product p = (Product)s.load(Product. class, 1 ) ;
System. out .println( "log1" ) ;
System. out .println(p.getName()) ;
System. out .println( "log2" ) ;
s.close() ;
sf.close() ;
结果:

延迟加载又叫lazyload,在one-many many-many的时候都可以使用关系的延迟加载
修改配置文件 Category.hbm.xml
<set name="products" lazy="false">
改为
<set name="products" lazy="true">
表示通过Category获取产品是 延迟加载的


执行15行的时候,只会查询Category表的信息,不会查询product_表
只有在执行18行,通过category取products的时候,才会进行对product_表的查询



什么是级联? 简单的说,没有配置级联的时候,删除分类,其对应的产品不会被删除。 但是如果配置了恰当的级联,那么删除分类的时候,其对应的产品都会被删除掉。
包括上一步说的删除用得级联,级联有4种类型:
all:所有操作都执行级联操作;
none:所有操作都不执行级联操作;
delete:删除时执行级联操作;  
save-update:保存和更新时执行级联操作;
级联通常用在one-many和many-to-many上,几乎不用在many-one上。

delete 级联
一对多 代码的基础之上,修改TestHibernate类为
Category c = (Category) s.get(Category.class, 3);
s.delete(c);

并且为Category.hbm.xml 加上  
<set name="products" cascade="delete" lazy="false">

运行代码就会发现,删除分类的时候,会把分类下对应的产品都删除掉,否则只会把产品对应的cid设置为空
SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession() ;
s.beginTransaction() ;
Category category = (Category)s.get(Category. class, 2 ) ;
s.delete(category) ;
s.getTransaction().commit() ;
s.close() ;
sf.close() ;



save-update 级联
一对多 代码的基础之上,修改TestHibernate类为
Category c = (Category) s.get(Category.class, 5);
Product p1 = new Product();
p1.setName("product_501");
Product p2 = new Product();
p2.setName("product_502");
Product p3 = new Product();
p3.setName("product_503");

并且为Category.hbm.xml 加上  
<set name="products" cascade="save-update" lazy="false">

运行代码就会发现,这3个 瞬时状态 的产品对象虽然没有添加到数据库里,但是在事务提交的时候,会把他们3个 持久化 。 如果没有cascade="save-update",就会报错

SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession() ;
s.beginTransaction() ;
Category category = (Category)s.get(Category. class, 3 ) ;
Product p1 = new Product() ;
p1.setName( "product_501" ) ;
Product p2 = new Product() ;
p2.setName( "product_502" ) ;
Product p3 = new Product() ;
p3.setName( "product_503" ) ;
category.getProductSet().add(p1) ;
category.getProductSet().add(p2) ;
category.getProductSet().add(p3) ;
s.getTransaction().commit() ;
s.close() ;
sf.close() ;



all和none级联
all 就是 delete+save-update
none 就是没有,默认就是none

hibernate默认是开启一级缓存的,一级缓存存放在session上
两次获取id=1的category对象
第一次通过id=1获取对象的时候,session中是 没有对应缓存对象 的,所以会在"log1"后出现sql查询语句。
第二次通过id=1获取对象的时候,session中 有对应的缓存对象 ,所以在"log2"后不会出现sql查询语句
所以总共会看到一次SQL语句出现
SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession() ;
System. out .println( "log1" ) ;
Product p1 = (Product)s.get(Product. class, 1 ) ;
System. out .println( "log2" ) ;
Product p2 = (Product)s.get(Product. class, 1 ) ;
System. out .println( "log3" ) ;
s.close() ;
sf.close() ;
结果:


Hibernate的一级缓存是在Session上,二级缓存是在SessionFactory上
创建了两个Session
在第一个Session里
第一次获取id=1的Category   执行SQL语句
第二次获取id=1的Category, 不会 执行SQL语句,因为有 一级缓存
在第二个Session里
获取id=1的Category, 执行SQL语句,因为在第二个Session,没有缓存该对象。  
所以总共会看到两条SQL语句。
hibernate.cfg.xml 中增加对二级缓存的配置
在hibernate.cfg.xml中开启二级缓存的配置
hibernate本身不提供二级缓存,都是使用第三方的二级缓存插件
这里使用的是 EhCache提供的二级缓存
< property   name = "hibernate.cache.use_second_level_cache" >true</ property >
   <propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
在src目录下,创建一个ehcache.xml用于EHCache的缓存配置
< ehcache >
     < diskStore   path = "java.io.tmpdir" />
     < defaultCache
         maxElementsInMemory = "10000"
         eternal = "false"
         timeToIdleSeconds = "120"
         timeToLiveSeconds = "120"
         overflowToDisk = "true"
         />
</ ehcache >
设置HBM
对于要进行二级缓存的实体类,进行配置,增加 <cache usage="read-only" />

< hibernate-mapping   package = "com.how2java.pojo" >
     < class   name = "Category"   table = "category_" >
         < cache   usage = "read-only"   />
         < id   name = "id"   column = "id" >
             < generator   class = "native" >
             </ generator >
         </ id >
         < property   name = "name"   />
 
         < set   name = "products"   lazy = "true" >
             < key   column = "cid"   not-null = "false"   />
             < one-to-many   class = "Product"   />
         </ set >
                  
     </ class >       
</ hibernate-mapping >

通过id获取Product对象有两种方式,分别是 get load  
他们的区别分别在于  
1. 延迟加载  
2. 对于id不存在的时候的处理
load方式是延迟加载,只有属性被访问的时候才会调用sql语句
get方式是非延迟加载,无论后面的代码是否会访问到属性,马上执行sql语句
对于id不存在的对象的处理
都通过id=500去获取对象  
1. get方式会返回null  
2. load方式会抛出异常

Hibernate有两种方式获得session,分别是:  
openSession getCurrentSession  
他们的区别在于  
1. 获取的是否是同一个session对象  
openSession每次都会得到一个新的Session对象  
getCurrentSession在同一个线程中,每次都是获取相同的Session对象,但是在不同的线程中获取的是不同的Session对象  
2. 事务提交的必要性  
openSession只有在增加,删除,修改的时候需要事务, 查询时不需要  
getCurrentSession是 所有操作都必须放在事务中进行 ,并且 提交事务后,session就自动关闭 ,不能够再进行关闭  

Hibernate有缓存机制,可以通过用id作为key把product对象保存在缓存中  
同时hibernate也提供Query的查询方式。假设数据库中有100条记录,其中有30条记录在缓存中,但是使用Query的的list方法,就会所有的100条数据都从数据库中查询,而 无视这30条缓存中 的记录  
N+1是什么意思呢,首先执行一条sql语句,去查询这100条记录,但是,只返回这100条记录的ID  
然后再根据id,进行进一步查询。  
如果id在缓存中,就从缓存中获取product对象了,否则再从数据库中获取
Hibernate使用Iterator实现N 1
首先通过Query的iterator把所有满足条件的Product的 id 查出来
然后再通过it.next()查询每一个对象
如果这个对象在缓存中,就直接从缓存中取了
否则就从数据库中获取
N+1中的1,就是指只返回id的SQL语句,N指的是如果在缓存中找不到对应的数据,就到数据库中去查
SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession() ;
Query q = s.createQuery("from Product p where p.name like ?");
q.setString(0, "%pa%");
Iterator<Product> it = q.iterate();
while (it.hasNext()) {
Product p = it.next();
System.out.println(p.getName());
}
s.close() ;
sf.close() ;

返回满足条件的总数
SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession() ;
Query q = s.createQuery("select count(*) from Product p where p.name like ?");
q.setString(0, "%ip%");
long total = (long)q.uniqueResult();
System.out.println(total);
s.close() ;
sf.close() ;

Hibernate使用乐观锁来处理脏数据问题
首先看不使用乐观锁的情况
故意创造一个场景来制造脏数据。

1. 通过session1得到 id=1 的对象 product1
2. 在product1原来价格的基础上增加1000
3. 更新product1之前,通过session2得到 id=1 的对象product2
4. 在product2原来价格的基础上增加1000
5. 更新product1
6. 更新product2

最后结果是product的价格只增加了1000,而不是2000
SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s1 = sf.openSession() ;
Session s2 = sf.openSession() ;
s1.beginTransaction() ;
s2.beginTransaction() ;
Product p1 = (Product)s1.get(Product. class, 1 ) ;
System. out .println( "产品原本价格是:" +p1.getPrice()) ;
p1.setPrice(p1.getPrice()+ 1000 ) ;

Product p2 = (Product)s2.get(Product. class, 1 ) ;
p2.setPrice(p2.getPrice()+ 1000 ) ;

s1.update(p1) ;
s2.update(p2) ;
s1.getTransaction().commit() ;
s2.getTransaction().commit() ;
Product p = (Product)s1.get(Product. class, 1 ) ;
System. out .println( "经过两次价格增加后,价格变为;" +p.getPrice()) ;
s1.close() ;
s2.close() ;
sf.close() ;



修改配置文件 Product.hbm.xml
<version name="version" column="ver" type="int"></version>

增加一个version字段,用于版本信息控制。这就是乐观锁的核心机制。
比如session1获取product1的时候,version=1。 那么session1更新product1的时候,就需要确保version还是1才可以进行更新,并且更新结束后,把version改为2。


修改 Product.java
增加version属性
private int version ;
public int getVersion () {
return version ;
}

public void setVersion ( int version) {
this . version = version ;
}
重新运行一样的代码
做同样的业务就会抛出异常,提示该行已经被其他事务删除或者修改过了,本次修改无法生效。
这样就保证了数据的一致性。

原理
1. 假设数据库中产品的价格是10000,version是10
2. session1,session2分别获取了该对象
3. 都修改了对象的价格
4. session1试图保存到数据库,检测version依旧=10,成功保存,并把version修改为11
5. session2试图保存到数据库,检测version=11,说明该数据已经被其他人动过了。 保存失败,抛出异常


建立数据库连接时比较消耗时间的,所以通常都会采用数据库连接池的技术来建立多条数据库连接,并且在将来持续使用,从而节约掉建立数据库连接的时间  
hibernate本身是提供了数据库连接池的,但是hibernate官网也不推荐使用他自带的数据库连接池。  
一般都会使用第三方的数据库连接池  
C3P0是免费的第三方的数据库连接池,并且有不错的表现  
导入c3po-0.9.1.jar,并在hiberna.cfg.xml加入以下
         < property   name = "hibernate.connection.provider_class"
             org.hibernate.connection.C3P0ConnectionProvider 
         </ property
         < property   name = "hibernate.c3p0.max_size" >20</ property
         < property   name = "hibernate.c3p0.min_size" >5</ property
         < property   name = "hibernate.c3p0.timeout" >50000</ property
         < property   name = "hibernate.c3p0.max_statements" >100</ property
         < property   name = "hibernate.c3p0.idle_test_period" >3000</ property
         <!-- 当连接池耗尽并接到获得连接的请求,则新增加连接的数量 -->
         < property   name = "hibernate.c3p0.acquire_increment" >2</ property
         <!-- 是否验证,检查连接 -->
         < property   name = "hibernate.c3p0.validate" >false</ property

记得要把上一步的二级缓存的相关配置去掉
<!--     <cache usage="read-only" />-->

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值