zoukankan      html  css  js  c++  java
  • hibernate(三)多对多、加载策略、懒加载、HQL查询、整合c3p0、乐观锁悲观锁

    录:

    1、多对多关系
    2、加载策略(优化查询)
      2.1、类级别加载策略
      2.2、关联级别加载策略--一对多(查询集合时间时使用,比如查询Customer下面的Set<Order>)
      2.3、关联级别加载策略--多对一(查询外键所在的对象,即查询Order时使用)
      2.4、批量查询加载策略
      2.5、加载策略-总结
    3、查询总结
    4、HQL详解
    5、HQL查询--表连接
    6、HQL的命名查询
    7、QBC查询(了解)
    8、整合c3p0连接池
    9、事务&Hibernate中指定隔离级别
    10、悲观锁
    11、乐观锁

    1、多对多关系    <--返回目录

      创建实体类Student.java

    public class Student{
        private Integer id;
        private String name;
        private Set<Course> courses = new HashSet<>();
    }


      创建实体类Course.java

    public class Course{
        private Integer id;
        private String name;
        private Set<Student> students = new HashSet<>();
    }


      映射文件Student.hbm.xml

    <class name="com.oy.domain.Student" table="t_student">
        <id name="id" column="id">
            <generator class="native"></generator>
        </id>
        <property name="name" column="name"></property>
        <set name="courses" table="t_student_course">
            <key column="sid" />
            <many-to-many class="com.oy.domain.Course" column="cid" />
        </set>
    </class>


      映射文件Course.hbm.xml

    <class name="com.oy.domain.Course" table="t_course"  >
        <id name="id" column="id"    >
            <generator class="native"></generator>
        </id>     
        <property name="name" column="name" ></property>
        
        <set name="students" table="t_student_course" inverse="true" >
            <key column="cid" ></key>
            <many-to-many class="Student" column="sid"  ></many-to-many>
        </set>
    </class>

      测试代码

    @Test
    public void fun1() {
        Session session = HibernateUtils.openSession();
        Transaction ts = session.beginTransaction();
        // ==============================================
        Student s = new Student();
        s.setName("张无忌");
        
        Course c1 = new Course();
        c1.setName("乾坤大挪移");
        
        Course c2 = new Course();
        c2.setName("九阳神功");
                
        s.getCourses().add(c1);
        s.getCourses().add(c2);
        
        session.save(c1);
        session.save(c2);
        session.save(s);
        // ==============================================
        ts.commit(); // 事务提交,这里使用insert into语句
        session.close();
    }

      控制台打印

    Hibernate: 
        insert 
        into
            t_course
            (name) 
        values
            (?)
    Hibernate: 
        insert 
        into
            t_course
            (name) 
        values
            (?)
    Hibernate: 
        insert 
        into
            t_student
            (name) 
        values
            (?)
    Hibernate: 
        insert 
        into
            t_student_course
            (sid, cid) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            t_student_course
            (sid, cid) 
        values
            (?, ?)
    虚拟机关闭!释放资源
    View Code

      数据库记录

      t_student 表

      t_course 表

      t_student_course 表

      操作:
            - inverse: 是否要放弃维护外键关系
            - cascade: 是否需要级联操作 (5个)
            - 注意: 配置级联删除时,要小心,双方都配置级联删除, 任意删除一条记录, 整个关系链数据都会被删除

    2、加载策略(优化查询)    <--返回目录

      策略种类:
              延迟加载:等到使用的时候才会加载数据
              立即加载:不管使用不使用,都会立刻将数据加载
      策略的应用:
              类级别的加载策略
              关联级别的加载策略

    2.1、类级别加载策略    <--返回目录

      类级别加载策略:
        get() 方法,立即查询数据库,将数据初始化
        load() 方法,hbm 文件中,class 元素的 lazy 属性决定类级别 load 方法的加载策略,lazy 默认为 true
          1) lazy=true(默认): 先返回一个代理对象,使用代理对象的属性时,才去查询数据库
          2) lazy=false: 与 get() 一致,会立即加载数据


      类级别懒加载测试
            - lazy:<class>元素属性,默认为 true,即使用懒加载
            - 映射文件 Customer.hbm.xml:<class name="Customer" table="t_customer">  lazy 没有设置,默认为 true

    Customer c = (Customer)session.load(Customer.class, 1);//断点发现,运行此行代码后,不打印sql语句
    System.out.println(c.getName());//运行此行代码,打印sql语句

           
            - 映射文件Customer.hbm.xml:<class name="Customer" table="t_customer" lazy="false">

    Customer c = (Customer)session.load(Customer.class, 1);//断点发现,运行此行代码后,打印sql语句

    2.2、关联级别加载策略--一对多(查询集合时间时使用,比如查询Customer下面的Set<Order>)    <--返回目录

      在查询有关联关系的数据时,加载一方的数据是否需要将另一方立即查询出。
      默认: 关联的数据,在使用时才会加载。


      lazy:<set>元素属性,默认为true,即使用懒加载
           映射文件Customer.hbm.xml:<set name="orders" lazy="true">   lazy没有设置,默认为true

    Customer c = (Customer)session.get(Customer.class, 1);//get()方法立即加载,查询Customer,但是不会查询Customer下面的Order
    for(Order o: c.getOrders){  //c.getOrders执行后,会查询Customer下面的所有Order
        System.out.println(o.getName());
    }

                  
            映射文件 Customer.hbm.xml:<set name="orders" lazy="false">

    Customer c = (Customer)session.get(Customer.class, 1);//查询Customer,并且查询Customer下面的所有Order


      <set lazy="">: 是否对set数据使用懒加载
              - true:(默认值) 对集合使用才加载
              - false: 集合将会被立即加载
              - extra: 极其懒惰,如果使用集合时,之调用size方法查询数量, Hibernate会发送count语句,只查询数量.不加载集合内数据.
          
      <set fetch="">:<set>元素的属性,决定加载集合使用的sql语句种类
              - select: (默认值) 普通select查询
              - join: 表链接语句查询集合数据
              - subselect: 使用子查询 一次加载多个Customer的订单数据
          
            fetch        lazy    结论
         ------------------------------------------------------------------------------------------
            select      true    默认值, 会在使用集合时加载,普通select语句
            select        false    立刻使用select语句加载集合数据
            select        extra    会在使用集合时加载,普通select语句,如果只是获得集合的长度,会发送Count语句查询长度.
            
            join        true    查询集合时使用表链接查询,会立刻加载集合数据
            join        false    查询集合时使用表链接查询,会立刻加载集合数据
            join        extra    查询集合时使用表链接查询,会立刻加载集合数据
            
            subselect    true    会在使用集合时加载,子查询语句
            subselect    false    会在查询用户时,立即使用子查询加载客户的订单数据
            subselect   extra    会在使用集合时加载,子查询语句,如果只是获得集合的长度,会发送Count语句查询长度

    2.3、关联级别加载策略--多对一(查询外键所在的对象,即查询Order时使用)    <--返回目录

      <many-to-one lazy="">
              - false: 关闭懒加载;加载订单时,会立即加载客户
              - proxy: 看客户对象的类加载策略来决定
              - no-proxy: 不做研究


      <many-to-one fetch="">
              - select:(默认值)使用普通select加载
              - join:使用表链接加载数据

        fetch            lazy            结果
        ---------------------------------------------------------------------------------------
        select            false          加载订单时,立即加载客户数据.普通select语句加载客户.
        select            proxy         类加载策略为:lazy=false 同上
                                               lazy=true    加载订单时,先不加载客户数据.使用客户数据时才加载
        join            false             使用表链接查询订单以及对应客户信息.lazy属性无效
        join            proxy            使用表链接查询订单以及对应客户信息.lazy属性无效
        ---------------------------------------------------------------------------------------


      映射文件 Order.hbm.xml <many-to-one lazy="false" fetch="select">

    Order o = (Order)session.get(Order.class, 1);//关闭懒加载;加载订单时,立即加载客户数据
    System.out.println(o.getCustomer().getName());

    2.4、批量查询加载策略    <--返回目录

      查询所有客户,遍历客户,打印客户下的订单

    List<Customer> list = session.createQuery("from Customer").list();
    for(Customer c: list){
        (System.out.println(c.getOrders().size());//每循环一次,发送一条sql语句,加载一个客户的order的集合
    }

      <set name="orders" batch-size="2">

    List<Customer> list = session.createQuery("from Customer").list();
    for(Customer c: list){
        (System.out.println(c.getOrders().size());//一次加载两个客户的order集合
    }

      总结batch-size:决定一次加载几个对象的集合数据. in 条件加载多个用户的订单

    2.5、加载策略-总结    <--返回目录

        * 表连接件检索用到比较少
        * 延迟检索和立即检索的优缺点
        * 如何解决延迟加载的缺点:
            缺点描述:在dao层有一个方法Customer getCustomer(int id),
            return session.load(Customer.class, id);return的是查询出的Customer对象代理对象
            中间没有使用查询出的Customer对象的属性,此时由于使用延迟加载,该return结果没有初始化。
        * 解决方案1:设置lazy=false关闭懒加载
        * 解决方案2:在Service层获得在页面要用到的属性=>在Service层中确保数据已经加载
           

    3、查询总结    <--返回目录

      HIbernate查询分类:
        1)根据OID检索:get/load()
        2)对象视图检索:customer.getOrders()
        3)sql语句:createSqlQuery
        4)HQL语句:createQuery
        5)criteria查询:createcriteria

    4、HQL详解    <--返回目录

      要用到的表t_order                表t_customer
        -------------------             ------------------
        oid        oname    cid              cid       cname
        -------------------             ------------------
        1        香皂    1                  1        zs
        2        肥皂    1                   2        ls     
        3        蜡烛    2                   3        ww
        4        手电筒    2
        5        胶带    2
        -------------------

      HQL查询所有

    Query query = session.createQuery("from Customer");
    //Query query = session.createQuery("from Customer c");//指定别名为c
    //Query query = session.createQuery("select c from Customer c");//其他写法
    List<Customer> list = query.list();

      选择查询(查询对象的某几个属性)

    Query query = session.createQuery("select c.name from Customer c");//c.name为对象属性名,
    List list = query.list();
    System.out.println(list);//[tom,jerry]
    
    Query query = session.createQuery("select c.id, c.name from Customer c");
    List<Object[]> list = query.list();
    for(Object[] objs: list){
        System.out.println(Arrays.toString(objs));//[1, tom] [2,jerry]
    }

      投影查询:选择查询的基础上,把查询结果封装到对象中

    Query query = session.createQuery("select new Customer(c.id, c.name) from Customer c");//Customer对象要有含参构造方法
    List<Customer> list = query.list();

      排序:asc升序,desc降序

    Query query = session.createQuery("from Customer c order by c.id desc");

      分页

    Query query = session.createQuery("from Customer c");
    query.setFirstResult(0);//从哪个索引开始取数据,firstResutl=(当前页数-1)*每页记录数
    query.setMaxResults(10);//查询多少条数据
    List<Customer> list = query.list();

      绑定参数

    Query query = session.createQuery("from Customer c where c.id=?");
    query.setInteger(0,2);//0表示第一个?
    Customer c = (Customer)query.uniqueResult();
    另一种方式:
    Query query = session.createQuery("from Customer c where c.id=haha");
    query.setInteger("haha",2);

      聚合函数

    Query query = session.createQuery("select count(*) from Customer c");
    Object count = query.uniqueResult();
    
    select avg(c.id) from Customer c
    select sum(c.id) from Customer c
    select max(c.id) from Customer c
    select min(c.id) from Customer c

      分组

    Query query = session.createQuery("select o.customer, count(o) from Order o group by o.customer having count(o)>2");
    List<Object[]> list = query.list();
    for(Object[] objs: list){
        System.out.println(Arrays.toString(objs));//[Customer对象, 3] 
    }

      

    5、HQL查询--表连接    <--返回目录

      表连接:交叉连接(笛卡尔积)、内连接(去笛卡尔积)、左外连接、右外连接

      隐式内连接(mysql方言)--在笛卡尔积基础上过滤无效数据。

      显示内连接(标准,建议使用)-- inner join

    //Query query = session.createQuery("from Customer c, Order o where o.customer=c");//隐式内连接(mysql方言)
    Query query = session.createQuery("from Customer c inner join c.orders");//显示内连接
    List<Object[]> list = query.list();
    System.out.println(Arrays.toString(list.get(1)));//[Customer [cid=1, cname=zs, orders=[Order [oid=2, oname=肥皂], 
                                       //Order [oid=1, oname=香皂]]], Order [oid=2, oname=肥皂]]
    for(Object[] objs: list){  //遍历打印
    System.out.println(Arrays.toString(objs));
    }

      打印

    [Customer [cid=1, cname=zs, orders=[Order [oid=2, oname=肥皂], Order [oid=1, oname=香皂]]], Order [oid=1, oname=香皂]]
    [Customer [cid=1, cname=zs, orders=[Order [oid=2, oname=肥皂], Order [oid=1, oname=香皂]]], Order [oid=2, oname=肥皂]]
    [Customer [cid=2, cname=ls, orders=[Order [oid=5, oname=胶带], Order [oid=4, oname=手电筒], Order [oid=3, oname=蜡烛]]], Order [oid=3, oname=蜡烛]]
    [Customer [cid=2, cname=ls, orders=[Order [oid=5, oname=胶带], Order [oid=4, oname=手电筒], Order [oid=3, oname=蜡烛]]], Order [oid=4, oname=手电筒]]
    [Customer [cid=2, cname=ls, orders=[Order [oid=5, oname=胶带], Order [oid=4, oname=手电筒], Order [oid=3, oname=蜡烛]]], Order [oid=5, oname=胶带]]

      

      内连接(迫切):查询结果List<Customer>;二非迫切查询List<Object[]>

    Query query = session.createQuery("from Customer c inner join fetch c.orders");
    List<Customer> list = query.list();
    
    for(Customer c: list){
        System.out.println(c);
    }

      打印

    Customer [cid=1, cname=zs, orders=[Order [oid=1, oname=香皂], Order [oid=2, oname=肥皂]]]
    Customer [cid=1, cname=zs, orders=[Order [oid=1, oname=香皂], Order [oid=2, oname=肥皂]]]
    Customer [cid=2, cname=ls, orders=[Order [oid=4, oname=手电筒], Order [oid=5, oname=胶带], Order [oid=3, oname=蜡烛]]]
    Customer [cid=2, cname=ls, orders=[Order [oid=4, oname=手电筒], Order [oid=5, oname=胶带], Order [oid=3, oname=蜡烛]]]
    Customer [cid=2, cname=ls, orders=[Order [oid=4, oname=手电筒], Order [oid=5, oname=胶带], Order [oid=3, oname=蜡烛]]]

      注意:以后实体类中重写toString()方法:包含外键的类toString()中不要包含代表外键的属性!!!
                否则,在使用内连接查询后,打印结果抛异常:java.lang.StackOverflowError

      左外连接:from Customer c left outer join fetch c.orders //会打印出[Customer [cid=3, cname=ww, orders=[]], null]
      右外连接: from Customer c right outer join fetch c.orders

    6、HQL的命名查询    <--返回目录

      映射文件Customer.hbm.xml

    <hibernate-mapping>
        <class>
            <!--局部配置-->
            <query name="bcd"><![CDATA[from Order]]</query>
        </class>
        <!--全局配置-->
        <query name="abc"><![CDATA[from Customer]]</query>
    </hibernate-mapping>

      找到局部配置的HQL语句

    Query query = session.getNamedQuery("com.oy.domain.Cumtomer.bcd");//表示从Cumtomer.hbm.xml找名称为bcd的HQL语句

      找全局配置的HQL语句

    Query query = session.getNamedQuery("Cumtomer.abc");

    7、QBC查询(了解)    <--返回目录

      QBC查询结束criteria查询
      只能单表查询(不能表连接查询)

    8、整合c3p0连接池    <--返回目录

        1) 导包
        2) 在hibernate.cfg.xml中配置

    <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
    <property name="hibernate.c3p0.max_size">2</property>  

      
        设置参数:不设置的化,有默认值

    #hibernate.c3p0.max_size 2
    #hibernate.c3p0.min_size 2
    #hibernate.c3p0.timeout 5000
    #hibernate.c3p0.max_statements 100
    #hibernate.c3p0.idle_test_period 3000
    #hibernate.c3p0.acquire_increment 2
    #hibernate.c3p0.validate false


      如果连接池参数设置不好,可能会降低系统效率

    9、事务&Hibernate中指定隔离级别    <--返回目录

      事务的特性ACID:
              - 原子性:整体
              - 一致性:数据
              - 隔离性:并发
              - 持久性:结果
      改变Hibernate连接数据库的事务隔离级别

    <property name="hibernate.connection.isolation">4</>

        1:  读未提交
        2:读已提交
        4:可重复读
        8:串行化

    10、悲观锁    <--返回目录

      悲观锁:数据库提供实现。防止别人跟我抢着修改数据,分为读锁和写锁

        读锁/共享锁:在读取过程中,不希望别人修改,并且自己也不会修改,我们可以给读取的数据加上读锁

               select * from t_user lock in share mode

        写锁/排他锁:我不仅要读还要对数据进行修改,我就可以为读取的数据加上写锁

               select * from t_user for update

      hibernate 中使用悲观锁

    @Test
    public void fun1() {
        Session session = HibernateUtils.openSession();
        Transaction ts = session.beginTransaction();
        // ==============================================
        Student s = (Student) session.get(Student.class, 1, LockOptions.UPGRADE);
        System.out.println(s);
        // ==============================================
        ts.commit(); // 事务提交,这里使用insert into语句
        session.close();
    }

      打印

    Hibernate: 
        select
            student0_.id as id0_0_,
            student0_.name as name0_0_ 
        from
            t_student student0_ 
        where
            student0_.id=? for update

      演示写锁

       debug 模式调试

     

       另一事务提交,写锁被释放,该事务可以读取到数据了

    11、乐观锁    <--返回目录

      乐观锁:人为来控制的锁,需要自己实现

      乐观锁实现:通过字段 version实现,修改前判断 version,只有大于数据库表的version 才能修改成功

      hibernate 中 乐观锁的实现

      实体类中添加 version 字段

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

      实体类对应的映射文件添加

      测试

    @Test
    public void fun1() {
        Session session = HibernateUtils.openSession();
        Transaction ts = session.beginTransaction();
        // ==============================================
        Student s = (Student) session.get(Student.class, 1);
        s.setName("jack1");
        System.out.println(s);
        // ==============================================
        ts.commit(); // 事务提交,这里使用insert into语句
        session.close();
    }

      数据库实体类对应的表自动添加了字段 version。每次修改成功后 version 增1

      断点测试乐观锁的生效

      程序中查出version=2,如果程序update成功,会将 version 加1。为了演示乐观锁的生效,手动修改数据库表中该记录version+1

      手动将 version 改为 3

       程序往下执行

       报错(乐观锁起作用了)

    org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction 
    (or unsaved-value mapping was incorrect): [com.oy.domain.Student#1]

    ---

  • 相关阅读:
    Android安全-代码安全1-ProGuard混淆处理
    快速提高Android开发效率的Web工具
    Android采用ListView实现数据列表显示2-使用SimpleAdapter进行数据绑定
    Java中Enum方法toString与ordinal方法
    视一:Web前端开发之HTML+CSS
    100-days: thirty-seven
    css 权重
    100-days: thirty-six
    100-days: thirty-five
    100-days: thirty-four
  • 原文地址:https://www.cnblogs.com/xy-ouyang/p/13123522.html
Copyright © 2011-2022 走看看