• Hibernate 笔记(二) 数据关系


    目录:

    • 查询数据的三种方式(HQL/Criteria/SQL)
    • 多表关系(多对一/一对多/多对多)
    • 各种概念
      • get和load
      • 两种Session
      • 查询总数的方式
      • 乐观锁

    查询数据

    HQL(Hibernate Query Languag)

    HQL(Hibernate Query Language)是hibernate专门用于查询数据的语句,有别于SQL,HQL 更接近于面向对象的思维方式。

    比如使用的是类的名字Product,而非表格的名字product_

    以模糊查询为例子:

            // 模糊查询 HQL
            Query q  = s.createQuery("from Product p where p.name like ?");
            String name = "iphone";
            q.setParameter(0,"%"+name+"%");
            List<Product> list = q.list();
            for (Product p :list){
                System.out.println(p.getName());
            }
    

    注:“%”是省略的意思,有点像正则表达式

    Criteria

    使用Criteria进行数据查询。
    与HQL和SQL的区别是Criteria 完全是 面向对象的方式在进行数据查询,将不再看到有sql语句的痕迹

     String name = "iphone";
            Criteria c= s.createCriteria(Product.class);
            c.add(Restrictions.like("name", "%"+name+"%"));
    //        c.add(Restrictions.like("id", 8));
            List<Product> ps = c.list();
            for (Product p : ps) {
                System.out.println(p.getName());
            }
    

    Restrictions.like()里的第二个参数,在数据库里是什么类型的就要传什么类型的

    比如要查询id为8的,应该用c.add(Restrictions.like("id", 8));而非c.add(Restrictions.like("id", 8+""));

    SQL语句

    使用Session的createSQLQuery方法执行标准SQL语句

    因为标准SQL语句有可能返回各种各样的结果,比如多表查询,分组统计结果等等。 不能保证其查询结果能够装进一个Product对象中,所以返回的集合里的每一个元素是一个对象数组。 然后再通过下标把这个对象数组中的数据取出来。

            // 用SQL语句
            String name = "iphone";
            String sql = "select * from product_ p where p.name like '%"+name+"%'";
            Query q= s.createSQLQuery(sql);
            List<Object[]> list= q.list();
            for (Object[] os : list) {
                for (Object filed: os) {
                    System.out.print(filed+"	");
                }
                System.out.println();
            }
    

    多表关系

    多对一

    一个Product对应一个Category
    一个Category对应多个Product
    所以Product和Category是多对一的关系

    方法:

    在Product类中多一个标识Catagory的成员变量

    	Category category;
        public Category getCategory() {
            return category;
        }
        public void setCategory(Category category) {
            this.category = category;
        }
    

    并在Product.hbm.xml中加上(这句话指定product的cid列关联category的自增长主键)

    <many-to-one name="category" class="Category" column="cid" />
    

    测试一下代码

            Category c =new Category();
            c.setName("c2");
            s.save(c);
            for (int i = 6; i <10 ; i++) {
                Product p = (Product) s.get(Product.class, i);
                p.setCategory(c);
                s.update(p);
            }
    

    1580364844844

    1580364863723

    原理:

    多对一,在“一”的上面加上一个值为为“多”的id的column来标识。

    一对多

    一个Product对应一个Category
    一个Category对应多个Product
    所以Category和Product是一对多的关系

    方法:

    在Catagory类加入Product的Set

        Set<Product> products;
        public Set<Product> getProducts() {
            return products;
        }
        public void setProducts(Set<Product> products) {
            this.products = products;
        }
    

    配置Category.hbm.xml

    <set name="products" lazy="false">
         <key column="cid" not-null="false" />
         <one-to-many class="Product" />
    </set>
    
    • <set 用于设置一对多(多对多也是他)关系,也可以用list,设置稍复杂点,这里使用简单的set来入门。
    • name="products" 对应 Category类中的products属性
    • lazy="false" 表示不使用延迟加载。关于延迟加载,请参考关系的延迟加载
    • <key column="cid" not-null="false" /> 表示外键是cid,可以为空
    • <one-to-many class="Product" /> 表示一对多所对应的类是Product

    测试一下

            // 设置多对一的category
            Category c = (Category) s.get(Category.class, 6);
            Set<Product> ps = c.getProducts();
            for (Product p : ps) {
                System.out.println(p.getName());
            }
    

    1580365247818

    原理

    和多对一一样,是通过“多”的ID去找”一“中的相应column的值,然后返回(通过xml设置的)

    多对多

    一种Product可以被多个User购买
    一个User可以购买多种Product
    所以Product和User之间的关系是多对多 many-to-many
    要实现多对多关系,必须有一张中间表 user_product 用于维护 User和Product之间的关系

    详见https://how2j.cn/k/hibernate/hibernate-many-to-many/42.html

    原理:

    无法通过在原本表上添加id来实现了

    必须多一个中间表,来记录两个”多“的id

    1580365423687

    级联

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

    各种概念

    两种获取方式

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

    1. 延迟加载
    2. 对于id不存在的时候的处理

    延迟加载

    load方式是延迟加载,只有属性被访问的时候才会调用sql语句
    get方式是非延迟加载,无论后面的代码是否会访问到属性,马上执行sql语句

    ID不存在的情况

    都通过id=500去获取对象

    1. get方式会返回null
    2. load方式会抛出异常

    两种Session方式

    https://how2j.cn/k/hibernate/hibernate-session/51.html

    查询总数

    方法一:

    如果知道HQL的话,可以用

    public static void main(String[] args) {
            SessionFactory sf = new Configuration().configure().buildSessionFactory();
            Session s = sf.openSession();
            s.beginTransaction();
      
            String name = "iphone";
             
            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);
            s.getTransaction().commit();
            s.close();
            sf.close();
        }
    

    但是我一开始是不知道HQL的,比较熟悉MySQL的语言,所有试了一下用另一个方法也可以

    方法二:

    public static void main(String[] args) {
            SessionFactory sf = new Configuration().configure().buildSessionFactory();
            Session s = sf.openSession();
            s.beginTransaction();
      
            String name = "iphone";
             
            // 查询总数
            String name = "iphone";
            Query q =s.createSQLQuery("select count(*) from product_ p where p.name like ?");
            q.setString(0, "%"+name+"%");
            long total= (Long) q.uniqueResult();
            System.out.println(total);
    
            s.getTransaction().commit();
            s.close();
            sf.close();
        }
    

    神奇的是,他竟然报错了

    Exception in thread "main" java.lang.ClassCastException: class java.math.BigInteger cannot be cast to class java.lang.Long (java.math.BigInteger and java.lang.Long are in module java.base of loader 'bootstrap')

    但是把获取到的q.uniqueResult();直接打印出来,结果没有问题。

    即改成

    public static void main(String[] args) {
            SessionFactory sf = new Configuration().configure().buildSessionFactory();
            Session s = sf.openSession();
            s.beginTransaction();
      
            String name = "iphone";
             
            // 查询总数
            String name = "iphone";
            Query q =s.createSQLQuery("select count(*) from product_ p where p.name like ?");
            q.setString(0, "%"+name+"%");
            System.out.println(q.uniqueResult());
    
            s.getTransaction().commit();
            s.close();
            sf.close();
        }
    

    乐观锁

    和线程锁的概念有点像,用来处理脏数据

    故意创造一个场景来制造脏数据。

    1. 通过session1得到id=1的对象 product1
    2. 在product1原来价格的基础上增加1000
    3. 更新product1之前,通过session2得到id=1的对象product2
    4. 在product2原来价格的基础上增加1000
    5. 更新product1
    6. 更新product2
    public class TestHibernate {
     public static void main(String[] args) {
         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();
     }
    }
    

    结果为

    产品原本价格是: 7000.0
    Hibernate: select product0_.id as id0_0_, product0_.name as name0_0_, product0_.price as price0_0_, product0_.cid as cid0_0_ from product_ product0_ where product0_.id=?
    Hibernate: update product_ set name=?, price=?, cid=? where id=?
    Hibernate: update product_ set name=?, price=?, cid=? where id=?
    经过两次价格增加后,价格变为: 8000.0

    于是为了避免这种情况,我们可以增加一个version字段,用于版本信息控制。这就是乐观锁的核心机制。

    比如session1获取product1的时候,version=1。 那么session1更新product1的时候,就需要确保version还是1才可以进行更新,并且更新结束后,把version改为2。

    设置的方法:

    1. 在Product上添加version变量,修改xml

      <?xml version="1.0"?>
      <!DOCTYPE hibernate-mapping PUBLIC
              "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
              "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
       
      <hibernate-mapping package="com.how2java.pojo">
          <class name="Product" table="product_">
              <id name="id" column="id">
                  <generator class="native">
                  </generator>
              </id>
              <!--version元素必须紧挨着id后面  -->
              <version name="version" column="ver" type="int"></version>
              <property name="name" />
              <property name="price" />
              <many-to-one name="category" class="Category" column="cid" />
              <set name="users" table="user_product" lazy="false">
                  <key column="pid" />
                  <many-to-many column="uid" class="User" />
              </set>
          </class>
       
      </hibernate-mapping>
      

      注意:version元素必须紧跟着id后面,否则会出错。

    2. 在Product类添加int version和getter、setter,并且在SQL中添加column

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

    再次运行代码

    设置完成后,再运行一遍,发现报错了。通过这种方法就可以保证数据的一致性

    Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.how2java.pojo.Product#1]
    

    原理

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

    1580309303994

  • 相关阅读:
    【转载】有效防止百度移动搜索转码
    jquery 弥补ie6不支持input:hover状态
    解决 IE6 position:fixed 固定定位问题
    png-24在ie6中的几种透明方法
    Chrome调试小技巧
    html select美化模拟jquery插件select2.js
    响应式设计的十个基本技巧
    colspan和rowspan
    【转】为什么整个互联网行业都缺前端工程师?
    设计模式之桥接模式
  • 原文地址:https://www.cnblogs.com/cpaulyz/p/12401609.html
Copyright © 2020-2023  润新知