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

原文地址:https://www.cnblogs.com/cpaulyz/p/12401609.html