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]

---

原文地址:https://www.cnblogs.com/xy-ouyang/p/13123522.html