目录:
- 查询数据的三种方式(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);
}
原理:
多对一,在“一”的上面加上一个值为为“多”的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());
}
原理
和多对一一样,是通过“多”的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
级联
- 什么是级联? 简单的说,没有配置级联的时候,删除分类,其对应的产品不会被删除。 但是如果配置了恰当的级联,那么删除分类的时候,其对应的产品都会被删除掉。
- 四种级联
- all:所有操作都执行级联操作;
- none:所有操作都不执行级联操作;
- delete:删除时执行级联操作;
- save-update:保存和更新时执行级联操作;
- 级联通常用在one-many和many-to-many上,几乎不用在many-one上。
各种概念
两种获取方式
通过id获取Product对象有两种方式,分别是get和load
他们的区别分别在于
- 延迟加载
- 对于id不存在的时候的处理
延迟加载
load方式是延迟加载,只有属性被访问的时候才会调用sql语句
get方式是非延迟加载,无论后面的代码是否会访问到属性,马上执行sql语句
ID不存在的情况
都通过id=500去获取对象
- get方式会返回null
- 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();
}
乐观锁
和线程锁的概念有点像,用来处理脏数据
故意创造一个场景来制造脏数据。
- 通过session1得到id=1的对象 product1
- 在product1原来价格的基础上增加1000
- 更新product1之前,通过session2得到id=1的对象product2
- 在product2原来价格的基础上增加1000
- 更新product1
- 更新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。
设置的方法:
-
在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后面,否则会出错。
-
在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]
原理
- 假设数据库中产品的价格是10000,version是10
- session1,session2分别获取了该对象
- 都修改了对象的价格
- session1试图保存到数据库,检测version依旧=10,成功保存,并把version修改为11
- session2试图保存到数据库,检测version=11,说明该数据已经被其他人动过了。 保存失败,抛出异常