Hibernate中的对象状态
在Hibernate中持久化对象具有三种状态: 瞬时态, 持久态, 游离态.
瞬时态: 对象没有与Hibernate产生关联(transient,session中没有缓存), 数据库中也没有对应记录
=> 对象无id, 没有关联
持久态: 对象与Hibernate产生关联(persistent, session中有缓存), 数据库中会存在记录
=> 对象存有id, 有关联
游离态: 对象没有与Hibernate产生关联(detached,session中没有缓存), 数据库中有记录
=> 对象有id, 没有关联
持久化对象状态之间的相互转化
- 瞬时(无id, 无关联)=>持久(有id, 有关联) save操作:
修改id, 与session的关联状态
让瞬时对象拥有id, 并且与Hibernate产生关联
//u此时没有id, 没有与Hibernate产生关联
User u = new User(); //瞬时态
u.setName("tom"); //瞬时态
u.setPassword("123"); //瞬时态
//save操作, Hibernate根据id生成策略, 为u生成id
//然后存入数据库, 与Hibernate产生关联, 变为持久态
session.save(u);//持久态
注: 只有Hibernate的主键生成策略变为assigned才能手动设置id, 否则报错
- **瞬时(无id, 无关联)=>游离(有id, 无关联) **
修改id即可
//设置Hibernate主键生成策略为assigned
User u = new User();
u.setId(1);
- 持久(有id, 有关联)=>瞬时(无id, 无关联)
修改id与关联状态即可
1.直接通过session的get查询操作获取持久化对象
关闭session切断关联, 再通过setId(null)修改id就变为瞬时态
User u = (User) session.get(User.class, 1); //持久态
session.close();
u.setId(null); //瞬时态
2.get查询到的对象, 使用evict方法切断user与session的联系,
再设置id为null变为瞬时态
User u = session.get(User.class ,1);//持久态
session.evict(u);
u.setId(null); //瞬时态
- 持久(有id, 有关联)=>游离(有id, 无关联)
获得持久对象, 然后切断持久对象与session的联系
User u =(User) session.get(User.class, 1);
session.close();
//或者 session.evict(u);
- 游离(有id, 无关联)=>瞬时(无id, 无关联)
设置游离对象id为null
User u = (User) session.get(User.class, 1);
session.evict(u);
u.setId(null);
- 游离(有id, 无关联)=>持久(有id, 有关联)
重新将游离对象写入数据库, 使得与session产生联系
User u = (User) session.get(User.class, 1);
session.evict(u);
session.update(u); //写入数据库,变为持久态
注: 持久态的时候不允许修改持久化对象的id: u.setId(2), 修改直接报错, 对持久化对象直接进行修改:u.setName(“C”), 那么Hibernate将会执行update操作, 将修改的持久化对象的数据同步到数据库
一级缓存
一级缓存(session缓存), 用于存储持久化对象 , 内部储存结构是一个Map, 当需要使用持久化对象的时候, Hibernate会优先从缓存中获取, 当session关闭, 一级缓存销毁.
- 快照
快照: 缓存的复制品, 存放在session中
快照主要用来与缓存中数据进行比较, 判断缓存中数据是否发生变化
缓存中数据发生变化:
Hibernate就会执行update, insert操作
缓存中数据没有发生变化:
说明数据库中数据与缓存中数据相同,
Hibernate就会避免主动执行SQL的update, insert, 减少资源浪费
- 缓存执行过程:
查询(select):
session首次执行查询, 发送select到数据库, 将查询的结果封装放入session缓存,
当再次执行查询操作, Hibernate会先去Session缓存中查找,
没找到, 再去数据库中查找.
更新(update):
使用get/load执行查询操作后, 持久化对象放入Session缓存中,
然后对持久化对象update, 事务提交的时候将执行如下操作:
先将Session缓存中修改后的对象与快照中数据进行比较,
二者数据不相同的时候执行update, 数据更新到数据库中
二者数据相同, 那么不执行任何操作.
插入(insert):
使用快照中数据与缓存中数据进行比较,
根据比较结果判断是否继续执行insert操作
- 缓存刷新时机:
//查询表中所有数据, 查询后的结果会覆盖缓存中数据
List<User> list = session.createQuery("from User").list();
//执行flush, 刷新缓存中数据,
session.flush();
- 缓存导致的问题:
每次获取持久化对象, Hibernate优先去缓存中查找, 一定程度上提高了SQL的执行效率.
缓存的存在, 出现的一个问题就是: 如果Hibernate获得持久化对象后, 数据库中数据又出现了修改, 当再次对该持久化对象进行操作的时候, Hibernate会优先从缓存中获得持久化对象, 导致数据库与Hibernate中数据不一致. 当出现这种问题的时候, 建议JDBC操作
注: 避免将相同的对象放入缓存中, 谨记缓存是一个Map, 看如下操作:
User u1 = session.get(User.class, 1);//缓存中放入u1
session.evict(u1);//变为游离态, 缓存中不存在
User u2 = session.get(User.class, 1);//获得id=1的持久化对象,缓存中存在
session.update(u1);//u1重新变为持久态, 缓存中存在
//u1, u2同时存在缓存中, 将会报错
session缓存常用API
- evict(Object o): 将指定对象从session缓存中移除
- clear(): 清除session缓存中所有的对象
- refresh(Object o):
强制刷新指定对象, 使持久化对象数据与数据库中数据一致, 一定程度上避免session缓存产生的数据不一致问题;
对o对象重新执行SQL - flush():
对比快照与缓存中的数据, 确保数据一致, 然后将缓存中数据提交到数据库, 类似于commit, 数据不一致的时候刷新缓存中的数据
对象的操作
- save操作细节:
当执行save的时候, 对象会从瞬时态=>持久态, 事务提交后将持久化对象存入数据库中
执行save操作, 先根据映射文件中的主键生成策略生成主键.
Hibernate将生成的主键赋值给瞬时态对象, 若该对象有id, 那么就会覆盖原有的id
最后执行insert, 将瞬时态对象变为持久化对象
- persist操作:
persist操作与save一样, 他们二者的区别在: persist会检查对象主键, save不会检查对象主键
例如:
//u.setId(9); //save操作的时候Hibernate会自动进行主键生成,设置id无效
//persist会检查Bean的id
//发现与Hibernate主键生成策略不符,
//报org.hibernate.PersistentObjectException: detached entity passed to persist: com.demo.User异常
///将主键生成策略改为assigned或不设置主键,将不报错
u.setName("O");
u.setPassword("66");
//session.save(u);
session.persist(u);
- update操作细节:
游离对象=>持久对象, 对持久对象属性修改后, 使用save, 执行的是update, 而非insert
在映射文件的class标签中设置select-before-update=“true”, 那么执行update就会执行如下操作:
User u = new User();
u.setId(1); u.setName("P"); u.setPassword("pp");
session.update(u);
//当执行这样的操作的时候, 先执行select操作, 然后比较查询结果
//与查询结果一直, 那么就不执行update
- saveOrUpdate:
该方法就是save与update的结合, session.saveOrUpdate(u); 如果u存在id, 那么执行select, 然后再执行update, 没有id, 执行insert
HQL, SQL, Criteria与缓存的联系
下面通过例子说明:
体现一个问题: HQL都会执行select操作,将获取的list与缓存中的数据进行比较
//如果相同, 每次获取缓存中的封装对象
/*List<User> list1 = session.createQuery("from User").list();
List<User> list2 = session.createQuery("from User").list();
List<User> list3 = session.createQuery("from User").list();
for(User u:list1){
System.out.println(u);
}
System.out.println(list1.hashCode()+"--"+list2.hashCode()+"--"+list3.hashCode());*/
//原生的SQL操作与HQL一致
/*List<User> list1 = session.createSQLQuery("select * from t_User").addEntity(User.class).list();
List<User> list2 = session.createSQLQuery("select * from t_User").addEntity(User.class).list();
List<User> list3 = session.createSQLQuery("select * from t_User").addEntity(User.class).list();
for(User u:list1){
System.out.println(u);
}
System.out.println(list1.hashCode()+"--"+list2.hashCode()+"--"+list3.hashCode());*/
//criteria操作同上
/*List<User> list1 = session.createCriteria(User.class).list();
List<User> list2 = session.createCriteria(User.class).list();
List<User> list3 = session.createCriteria(User.class).list();
for(User u:list1){
System.out.println(u);
}
System.out.println(list1.hashCode()+"--"+list2.hashCode()+"--"+list3.hashCode());*/
通过程序的运行观察执行的SQL语句, 以及list对象的hashCode, 发现每次执行批量查询(HQL, SQL, Criteria)都会select * from t_user, 然后将查询的结果集与Session缓存中的数据进行比较.
说明:Hibernate把第一次执行的结果集放入缓存区, 在后面的查询中, 尽管Hibernate发送了SQL语句, 但是使用的数据依旧是缓存中的数据, 这个时候使用get操作的时候, 获取的数据也是从缓存区中得到
多表设计
表中存在的三种关系: 多对多, 一对多, 一对一
-
数据库描述上述关系:
在数据库中所有的关系都需要通过外键进行约束. -
Bean对象描述上述的关系:
一对多:客户与订单
class Customer{
//使用set描述客户订单列表
private Set<Order> orderSet;
}
class Order{
//多个商品订单属于一个客户
private Customer customer;
}
多对多: 学生与课程
class Student{
private Set<Course> courseSet;
}
class Course{
private Set<Student> StudentSet;
}
一对一: 学生与学生证
class Student{
private StudentCard sc;
}
class StudentCard{
private Student s;
}
Hibernate的一对多关系实现
一对多操作的时候, 维护一个对象的时候会自动维护另一方的关系; 例如 Customer referenced Order, 当删除Order的时候,Hibernate会先update商品表中所有的外键为null, 然后再执行删除订单操作, 我们就不用显式修改商品表中的外键, 维护商品与订单之间的关系
测试类:
//消费者:
public class Customer {
private Integer cid;
private String cname;
private Set<Order> orderSet = new HashSet<Order>();
//get/set方法就不写了
}
//订单
public class Order {
private Integer oid;
private String price;
private Customer customer;
//get/set方法就不写了
}
- Customer.hbm.xml与Order.hbm.xml编写
Customer.hbm.xml
<hibernate-mapping>
<class name="com.test.Customer" table="t_customer">
<id>
<generator class="native"></generator>
</id>
<property name="cname"></property>
<!--配置一对多关系标签-->
<set name="ordertest" cascade="save-update">
<key column="customer_id"></key>
<one-to-many class="com.test.Order"/>
</set>
</class>
</hibernate-mapping>
Order.hbm.xml
<hibernate-mapping>
<class name="com.test.Order" table="t_order">
<id name="oid">
<generator class="native"></generator>
</id>
<property name="price"></property>
<many-to-one name="customer" class="com.test.Customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
在Customer.hbm.xml中:
set标签用于确定容器(用于装备Order)
name属性: 确定对象属性名
cascade属性: 设置Customer与Order的级联操作
inverse属性: 将关系的维护翻转给对方, 默认值false(我维护这个关系)
key标签确定Customer主键名
one-to-many标签确定从表Order
cascade详细级联操作: 级联操作就是, 当A与B绑定好关系后, 就比如Customer的set已经存储了B, 当A执行save的时候, B也会自动执行save操作, 少写session.save(B)的代码, 同样的也可以执行级联删除, 当A删除了, B也跟着自动删除
注: 级联操作并不会维护关系
cascade的取值如下:
save-update:级联保存与修改
A保存,同时保存B
在程序中修改A中的B, 对应到数据库中B将会级联修改
delete:删除A,同时删除B,AB都不存在
删除过程中, 如果A在维护关系,那么A还会去处理外键,对外键设置为null,然后执行删除.
如果设置了inverse为true,A不去维护关系,A删除,B就删除,A不去update外键,减少了SQL操作
delete-orphan:孤儿删除,解除关系,同时将B删除,A存在的。
接触B与A的关系, 将B从A的集合内移除, B此时没有引用对象, 就自动delete
如果需要配置多项,使用逗号分隔。<set cascade="save-update,delete">
all : save-update 和 delete 整合
all-delete-orphan : 三个整合
此处注明: 千万不要A设置了级联删除,然后B也设置了级联删除
当删除B对象的时候, 由于级联删除, B会select所有A, 然后删除A, 但是A又触发级联删除, A会select所有的B, 最终删除所有的B, 以及所有的A, 就因为删除了一个B导致了如此严重的问题, 这个一定要避免!!!
在Order.hbm.xml中:
many-to-one标签中
name属性: 确定属性名称
class属性: 确定参照的类
column属性: 确定Order表参照Customer表的外建名
往数据库中保存Customer与Order:
Customer c = new Customer();
c.setName("tom");
Order o1 = new Order();
o1.setName("o1");
Order o2 = new Order();
o2.setName("o2");
//往c中添加Order信息, 维护关系
c.getOrderSet().add(o1);//Customer去维护, 执行update
c.getOrderSet().add(o2);//执行update
//往Order对象中添加Customer, 维护关系
o1.setCustomer(c);//Order去维护, 在insert中修改cid的值
o2.setCustomer(c);
//保存到数据库
session.save(c);
session.save(o1);
session.save(o2);
执行上面的代码, Hibernate执行3次insert, 2次update, 需要注意的是在c, o1, o2 insert的过程中, 就已经在维护关系(对Order表的cid外键进行设置), 但是后面又对Order表执行了2次update, 产生的问题就是重复
所以通过上面的代码也可以看出, 当维护关系的时候只需要维护一方, 另一方的关系就能得到维护
同理, 执行delete操作: session.delete©; 执行这条语句的时候, Hibernate会先将o1, o2的cid设置为null, 然后再对c进行delete, 从Customer的角度维护关系, 但是Customer不去维护关系的时候, 就需要遍历Customer的orderSet, 将所有的Order对象setCustomer(null)主动切断与Customer的关系, 设置所有Order的外键为null
总结: 设置一对多关系下, 可以只让一方维护关系, 另一方不维护, 放弃维护关系的对象就是–非外键所在的对象, 就比如上面的操作, 让Order 维护关系, Customer不去维护关系, 这种一方去维护关系也可以使用set标签中的inverse属性, 使得关系的维护交给对方