美文网首页
Hibernate学习笔记(三) 一些概念

Hibernate学习笔记(三) 一些概念

作者: 吴忆松 | 来源:发表于2019-10-30 20:48 被阅读0次

    目录

    1.对象状态
    2.Hibernate的事务
    3.延迟加载(lazyload)
    4.级联
    5.缓存
    6.分页
    7.两种获取方式
    8.两种获得Session的方式
    9.N+1
    10.总数查询
    11.乐观锁
    12.C3P0连接池


    一、对象状态

    实体类对象在Hibernate中有三种状态:
    1.瞬时:指的是没有和hibernate发生任何关系,在数据库中也没有对应的记录,一旦JVM结束,这个对象也就消失了 。
    2.持久:指得是一个对象和hibernate发生联系,有对应的session,并且在数据库中有对应的一条记录。
    3.脱管:指的是一个对象虽然在数据库中有对应的一条记录,但是它所对应的session已经关闭了 。

    说白了,瞬时就是对象还没有放入session中,持久就是对象已经放入session中,但是事务还没有被提交,托管就是事务被提交了,session也关闭了。

            SessionFactory sf = new Configuration().configure().buildSessionFactory();
            Session s = sf.openSession();
            s.beginTransaction();
            Product p = new Product();
            p.setName("p1");
            System.out.println("此时p是瞬时状态");
            s.save(p);
            System.out.println("此时p是持久状态");
            s.getTransaction().commit();
            s.close();
            System.out.println("此时p是脱管状态");
            sf.close();
    

    二、Hibernate的事务

    Hibernate的任何对数据有改动的操作,都应该被放在事务里面。在事务中的多个操作行为,要么都成功,要么都失败 。
    Hibernate中的事务由s.beginTransaction()开始,由s.getTransaction().commit()结束。


    三、延迟加载(lazyload)

    Hibernate中的延迟加载分为两种:属性的延迟加载和关系的延迟加载。

    1.属性的延迟加载

    当使用load的方式来获取对象的时候,只有访问了这个对象的属性,hibernate才会到数据库中进行查询,否则不会访问数据库。

            Product p = (Product)s.load(Product.class, 1);  //用load获取了一个对象,这时候并没有访问数据库。
            System.out.println(p.getName());    //直到要获取这个对象的属性时,才去访问数据库。
    

    2.关系的延迟加载

    在一对多和多对多关系当中都可以用关系延迟加载。修改配置属性lazy为“true”即可。
    执行第一行的时候,只会查询Category表的信息,不会查询product_表。只有在执行第二行,通过category取products的时候,才会进行对product_表的查询 。

            Category c = (Category) s.get(Category.class, 1);
            System.out.println(c.getProducts());
    

    四、级联

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


    五、缓存

    1.一级缓存

    Hibernate默认是开启一级缓存的,一级缓存存放在session上 。
    在执行下面第一句代码的时候,session中是没有对应缓存对象的,所以会访问数据库。
    在执行下面第二句代码的时候,session中是有对应缓存对象的,所以读取缓存,不会访问数据库。

            Category c1 = (Category)s.get(Category.class, 1);
            Category c2= (Category)s.get(Category.class, 1);
    

    2.二级缓存

    Hibernate的二级缓存是在SessionFactory上 ,需要手动开启。
    在Hibernate.cfg.xml中开启二级缓存的配置Hibernate本身不提供二级缓存,都是使用第三方的二级缓存插件
    这里使用的是 EhCache提供的二级缓存。

            <property name="hibernate.cache.use_second_level_cache">true</property>
            <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
    

    下面是ehcache.xml的配置:

    <ehcache>
        <diskStore path="java.io.tmpdir"/>
        <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            />
    </ehcache>
    

    对于要进行二级缓存的实体类,进行配置:

            <cache usage="read-only" />
    

    这样,在同一个SessionFactory的不同Session中访问同一对象,也能读取缓存而不访问数据库。


    六、分页

    Hibernate使用Criteria来进行分页查询。
    c.setFirstResult(2); 表示从第3条数据开始
    c.setMaxResults(5); 表示一共查询5条数据

            String name = "iphone";
            Criteria c= session.createCriteria(Product.class);
            c.add(Restrictions.like("name", "%"+name+"%"));
            c.setFirstResult(2);
            c.setMaxResults(5);
             
            List<Product> ps = c.list();
            for (Product p : ps) {
                System.out.println(p.getName());
            }
    

    七、两种获取方式

    通过id获取对象有两种方式,分别是get和load ,他们的区别分别在于 :

    1. 延迟加载
      load方式是延迟加载,只有属性被访问的时候才会调用sql语句。
      get方式是非延迟加载,无论后面的代码是否会访问到属性,马上执行sql语句 。
    2. 对于id不存在的时候的处理
      get方式会返回null
      load方式会抛出异常

    八、两种获得Session的方式

    Hibernate有两种方式获得session,分别是: openSessiongetCurrentSession他们的区别在于

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

    九、N+1

    Hibernate有缓存机制,可以通过用id作为key把product对象保存在缓存中。N+1的意思就是,数据库里有100条记录,其中30条在缓存中也有。首先执行一条sql语句,去查询这100条记录,但是,只返回这100条记录的id。然后再根据id进行进一步查询。如果id在缓存中,就从缓存中获取product对象了,否则再从数据库中获取。
    当然,Hibernate也提供Query的查询方式。使用Query的list方法,就会所有的100条数据都从数据库中查询,而无视这30条缓存中的记录。


    十、总数查询

    调用Query对象的uniqueResult()方法,返回一个long型的数据,即查询总数。

            Query q =s.createQuery("select count(*) from Product p where p.name like ?");
            q.setString(0, "%"+name+"%");
            long total= (Long) q.uniqueResult();
            System.out.println(total);
    

    十一、乐观锁

    Hibernate使用乐观锁来处理脏数据问题。
    首先,不用乐观锁,会出现脏数据现象。

            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();
    

    经过两个线程各将价格增加1000后,价格却只增加了1000。这就是脏数据问题。
    这时候使用乐观锁来解决这个问题。
    1.修改配置文件Product.hbm.xml

    <version name="version" column="ver" type="int"></version>
    

    2.修改 Product.java,增加version属性

        int version;
        public int getVersion() {
            return version;
        }
        public void setVersion(int version) {
            this.version = version;
        }
    

    3.重新运行一样的代码,做同样的业务就会抛出异常,提示该行已经被其他事务删除或者修改过了,本次修改无法生效。这样就保证了数据的一致性。
    ① 假设数据库中产品的价格是10000,version是10。
    ② session1,session2分别获取了该对象。
    ③ 都修改了对象的价格。
    ④ session1试图保存到数据库,检测version依旧=10,成功保存,并把version修改为11。
    ⑤ session2试图保存到数据库,检测version=11,说明该数据已经被其他人动过了。 保存失败,抛出异常。


    十二、C3P0连接池

    建立数据库连接时比较消耗时间的,所以通常都会采用数据库连接池的技术来建立多条数据库连接,并且在将来持续使用,从而节约掉建立数据库连接的时间 .
    Hibernate本身是提供了数据库连接池的,但是Hibernate官网也不推荐使用他自带的数据库连接池。一般都会使用第三方的数据库连接池。C3P0是免费的第三方的数据库连接池,并且有不错的表现。
    1.导入C3P0的jar包
    2.增加c3p0连接池的配置

            <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>   
    

    3.注意不能开启二级缓存

    相关文章

      网友评论

          本文标题:Hibernate学习笔记(三) 一些概念

          本文链接:https://www.haomeiwen.com/subject/sssavctx.html