1、对象的三种状态
2、session.save()方法
3、与session建立关联的对象的id,不允许修改
4、Hibernate一级缓存(更深层次理解hibernate中对象的操作)
5、Hibernate一级缓存细节问题
6、session.save()方法和persist()方法的区别
7、session的其他API
8、映射文件表达多对一(或一对多)关系
9、多对一关系-多表操作
10、多对一关系的维护--inverse 属性
11、cascade级联操作
1、对象的三种状态 <--返回目录
对象的三种状态
1)瞬时态(或临时态): 没有与Hibernate产生关联,与数据库中的记录没有产生关联(有关联就是与数据库中的 id 有关联)
2)持久态: 与Hibernate有关联,与数据库有关联(对象有 id)
3)游离态(或托管态): 与Hibernate没有关联,与数据库有关联(对象有 id)
如何判断:
1)对象有 id <==> 与数据库有关联
2)使用 session 操作过 对象 <==> 与 Hibernate 有关联
HibernateUtils
package com.oy.helloworld; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtils { // 会话工厂,整个程序只有一份。 private static SessionFactory sf; static { // 1 加载配置 Configuration config = new Configuration().configure(); // 2 获得工厂 sf = config.buildSessionFactory(); // 3 关闭虚拟机时,释放SessionFactory Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { System.out.println("虚拟机关闭!释放资源"); sf.close(); } })); } // 获得一个新的session public static Session openSession() { return sf.openSession(); } // 获得当前线程中绑定session public static Session getCurrentSession() { return sf.getCurrentSession(); } }
测试代码
public class TestHelloWorld { @Test public void fun1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); // =========================================== User u = new User(); // 临时态 u.setUsername("tom"); // 临时态 u.setPassword("333"); // 临时态 // save 方法会使用主键生成策略,为 User 指定 id session.save(u); // 持久态: 与Hibernate有关联,与数据库有关联(对象有 id) // =========================================== session.getTransaction().commit(); // 持久态 session.close(); // 游离态 } }
@Test public void fun2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); // =========================================== User u = (User) session.get(User.class, 1); // 持久状态 u.setUsername("jerry"); session.update(u); // 多余代码,Hibernate会自动将持久化状态对象的变化同步到数据库中,无需该代码 // =========================================== session.getTransaction().commit();// 持久状态,打印update语句 session.close(); // 游离状态 }
2、session.save()方法 <--返回目录
session.save()方法作用:获得 id
1)当主键生成策略为 identity 主键自增长时,该方法会通过 insert 语句来获得 id
2)当主键生成策略为 increment,该方法会使用语句 select max(id) from t_user; (使用断点查看,是否生成该 sql 语句),然后事务提交时,才生成 insert 语句
3)当主键生成策略为 assigned 时,在 session.save() 之前没有手动设置id,报错。
关于save()方法的测试
package com.oy.test; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.SQLQuery; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.criterion.Restrictions; import org.junit.Test; import com.oy.domain.User; public class Demo1 { @Test /** * 主键生成策略increment:数据库自己生成。先从数据库总查询最大的ID值,将ID值加1; * 当主键生成策略为increment,save()方法会使用语句select max(uid) from t_user来给user设置uid; * 由于这里没有使用事务,最后没有使用insert语句,数据库没有添加记录; */ public void fun1() { // 1.读取配置文件 Configuration conf = new Configuration().configure(); // 2.根据配置创建Factory SessionFactory sessionFactory = conf.buildSessionFactory(); // 3.获得操作数据库的session对象 Session session = sessionFactory.openSession(); // 4.操作数据库 // 增 User user = new User(); user.setUname("小张1"); user.setPassword("123"); System.out.println("save前:" + user);//save前:User [uid=0, uname=小张1, password=123] session.save(user);//当主键生成策略为increment,该方法会使用语句select max(uid) from t_user //由于这里没有insert语句,后面也没有 System.out.println("save后:" + user);//save后:User [uid=7, uname=小张1, password=123] // 5.关闭资源 session.close(); System.out.println("session关闭后:" + user);//session关闭后:User [uid=7, uname=小张1, password=123] sessionFactory.close(); } @Test /** * 主键生成策略increment:数据库自己生成。先从数据库总查询最大的ID值,将ID值加1; * 当主键生成策略为increment,save()方法会使用语句select max(uid) from t_user来给user设置uid; * 由于这里使用事务,事务提交时使用insert语句添加记录。 */ public void fun2() { // 1.读取配置文件 Configuration conf = new Configuration().configure(); // 2.根据配置创建Factory SessionFactory sessionFactory = conf.buildSessionFactory(); // 3.获得操作数据库的session对象 Session session = sessionFactory.openSession(); // 4.开启事务 Transaction ts = session.beginTransaction(); // 增 User user = new User(); user.setUname("小张1"); user.setPassword("123"); System.out.println("save前:" + user);//save前:User [uid=0, uname=小张1, password=123] session.save(user);//当主键生成策略为increment,该方法会使用语句select max(uid) from t_user //这里没有insert语句 System.out.println("save后:" + user);//save后:User [uid=8, uname=小张1, password=123] //5.提交事务 ts.commit(); //事务提交,这里执行insert into语句 // 6.关闭资源 session.close(); System.out.println("session关闭后:" + user);//session关闭后:User [uid=8, uname=小张1, password=123] sessionFactory.close(); } @Test /** * 主键生成策略是identity */ public void fun3() { // 1.读取配置文件 Configuration conf = new Configuration().configure(); // 2.根据配置创建Factory SessionFactory sessionFactory = conf.buildSessionFactory(); // 3.获得操作数据库的session对象 Session session = sessionFactory.openSession(); // 4.操作数据库 // 增 User user = new User(); user.setUname("小张2"); user.setPassword("123"); System.out.println("save前:" + user);//save前:User [uid=0, uname=小张2, password=123] session.save(user);//由于主键生成策略是identity,使用insert语句获取uid; //使用insert语句获取uid,然后给user设置uid; //由于使用了insert语句,save()方法后数据已经持久化到数据库中了; System.out.println("save后:" + user);//save后:User [uid=7, uname=小张2, password=123] // 5.关闭资源 session.close(); System.out.println("session关闭后:" + user);//session关闭后:User [uid=7, uname=小张2, password=123] sessionFactory.close(); } }
3、与session建立关联的对象的id,不允许修改 <--返回目录
Session session = HibernateUtils.openSession(); session.beginTransaction(); User u = (User)session.get(User.class, 1); u.setId(3); //报错,因为与session建立关联的对象的id,不允许修改 session.commint(); session.close();
4、Hibernate一级缓存(更深层次理解hibernate中对象的操作) <--返回目录
缓存:Hibernate中也存在缓存. Hibernate中存在的缓存也是用来提高效率.
Hibernate中存在两种缓存:
- 线程级别的缓存. Session缓存 (现在要学习的)
- 进程级别的缓存. Hibernate 二级缓存.
session缓存: 就是session对象中存在的缓存.缓存中存在的是(持久化)对象.
证明session缓存的存在
@Test public void fun2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); // =========================================== User u1 = (User) session.get(User.class, 1);// 发送select语句,从数据库取出记录封装成User对象 // 持久化状态对象=>存到缓存中 User u2 = (User) session.get(User.class, 1);// 再次查询时,会从缓存中查找,不会发送select语句 // =========================================== session.getTransaction().commit();// 持久状态,打印update语句 session.close(); // 游离状态 }
问题:一级缓存以什么形式存在的?
Map形式存在,Map<串行化Serializable, Object>
缓存中的快照: 在从数据库取得数据时,会将数据一式两份,一份作为缓存中的对象,一份作为快照。 在session提交时作为对比。
一级缓存执行原理
5、Hibernate一级缓存细节问题 <--返回目录
持久化状态本质:就是存在缓存中的对象,就是持久化状态
保存对象时可以使用save()方法和persist()方法
- 区别:有区别
- save()方法来自hibernate
- persist()方法来自JPA接口,persist就是持久化的意思
HQL语句批量查询时,查询结果是否会进入缓存?
- 会。
List<User> userList = session.createQuery("from User").list();//查询所有,并把查询出的结果封装成对象放入缓存 User u = (User)session.get(User.class, 1);//若缓存中有id=1的对象,直接从缓存中取User对象;缓存没有找到,使用select where id=1查询数据库
HQL查询是否会使用一级缓存?
- 批量查询时不会。
案例1:
// 批量查询,不使用一级缓存 List<User> userList1 = session.createQuery("from User").list();//打印select语句 List<User> userList2 = session.createQuery("from User").list();//也会打印select语句
案例2:
// 批量查询,不使用一级缓存 Query query1 = session.createQuery("from com.oy.domain.User where username='张三'"); User u1 = (User)query1.uniqueResult();//执行hql语句,返回一个User对象 Query query2 = session.createQuery("from com.oy.domain.User where username='张三'"); User u2 = (User)query2.uniqueResult();//执行hql语句,返回一个User对象
案例3:
// 非批量查询,使用一级缓存
Query query3 = session.createQuery("from com.oy.domain.User where id=2"); User u3 = (User)query3.uniqueResult();//执行hql语句,返回一个User对象 Query query4 = session.createQuery("from com.oy.domain.User where id=2"); User u4 = (User)query4.uniqueResult();//不执行hql语句
案例4:
List<User> userList1 = session.createQuery("from User").list();//打印select语句 Query query3 = session.createQuery("from com.oy.domain.User where uid=2"); User u3 = (User)query3.uniqueResult();//执行hql语句,返回一个User对象 Query query4 = session.createQuery("from com.oy.domain.User where uid='2"); User u4 = (User)query4.uniqueResult();//不执行hql语句
案例5:
Query query3 = session.createQuery("from com.oy.domain.User where uid=2"); User u3 = (User)query3.uniqueResult();//执行hql语句,返回一个User对象 User u = (User)session.get(User.class, 2);//不打印sql语句,直接从缓存中取
原生SQL语句查询时,查询结果是否会进入缓存?
- 如果有这句query.addEntity(User.class),会。否则不会。
criteria查询,会将查询结果放入一级缓存,但是查询不会使用一级查询。与HQL一样的。
6、session.save()方法和persist()方法的区别 <--返回目录
- 主键生成策略为native,数据库主键设置自增
- persist()方法体现的是持久化;persist()的理念是将对象完整的持久化,也包括对象的id;
在保存之前设置了id,那么将会使用设置的id进行insert,但是主键策略是由数据库来维护,所以产出矛盾,所以抛出异常。
User user = new User();
user.setId(99);
session.persist(user); //这里报错
- session.save()方法:如果保存之前设置了id,那么该id也被认为是无效的id。
User user = new User();
user.setId(99);
session.savet(user); //这里不报错
7、session的其他API <--返回目录
* session.evict(user): 将user对象从session缓存中移除
* session.clear(): 将session缓存中所有对象移除
* session.refresh(user): 强制刷新缓存中的对象,发送seclet语句,将数据库中数据更新到session缓存。
该方法可以用来解决缓存与数据库数据不同步的问题,但不是一种好的解决方案。
* session.flush():对比快照,立刻将session缓存中对象提交(执行update语句)
* saveOrUpdate方法:
- 数据库为代理主键,设置主键自增,主键生成策略为native:主键为空,save;主键非空,update
- 数据库为自然主键,主键生成策略为assigned: 必须手动设置id,然后根据id查询,查不到save,查到update
8、映射文件表达多对一(或一对多)关系 <--返回目录
订单表 t_order(多) 客户表 t_customer(一)
oid oname cid(外键) cid cname
mysql 建表 sql
create database test default character set utf8; use test; create table t_order( oid int primary key auto_increment, oname varchar(40), cid int )ENGINE=InnoDB DEFAULT CHARSET=utf8; create table t_customer( cid int primary key auto_increment, cname varchar(40) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
实体类Order.java
public class Order{ private Integer oid;//订单id private String oname;//订单名称 private Customer customer;//外键 }
实体类Customer.java
public class Customer{ private Integer cid;//客户id private String cname;//客户名称 private Set<Order> orders = new HashSet<Order>(); }
先配置Order==>t_order的映射 Order.hbm.xml文件
<class name="com.oy.domain.Order" table="t_order"> <id name="oid" column="oid"> <generator class="native"></generator> </id> <property name="oname" column="oname"></property> <many-to-one name="customer" column="cid" class="com.oy.domain.Customer"></many-to-one> </class>
然后配置Customer==>t_customer的映射 Customer.hbm.xml文件
<class name="com.oy.domain.Order" table="t_order"> <id name="cid" column="cid"> <generator class="native"></generator> </id> <property name="cname" column="cname"></property> <set name="orders"> <key column="cid"></key> <one-to-many class="com.oy.domain.Order"></one-to-many> </set> </class>
在hibernate.cfg.xml配置文件中引入映射文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">123456</property> <property name="show_sql">true</property> <!--操作数据库时,向控制台打印sql语句--> <property name="format_sql">true</property> <!--打印sql语句前,优化sql语句--> <property name="hbm2ddl.auto">update</property> <!--是否自动生成表结构--> <property name="hibernate.connection.autocommit">true</property> <!--事务自动提交--> <!-- 添加映射文件 --> <mapping resource="com/oy/domain/Customer.hbm.xml"/> <mapping resource="com/oy/domain/Order.hbm.xml"/> </session-factory> </hibernate-configuration>
9、多对一关系-多表操作 <--返回目录
demo 源码见:链接:https://pan.baidu.com/s/12nwIyodOhVCEemaAyPVPtQ 提取码:tp9w
增(下面代码是按照如下顺序:先持久化无外键所在对象,后持久化有外键的对象)
Customer customer = new Customer(); customer.setCname("张三1"); session.save(customer);//没这句报错 //org.hibernate.TransientObjectException: object references an unsaved transient //instance - save the transient instance before flushing: com.oy.domain.Customer Order o1= new Order(); o1.setOname("牙膏1"); o1.setCustomer(customer); session.save(o1);
增:session.save(customer); 移到后面也是可以的
Customer customer = new Customer(); customer.setCname("张三1"); //session.save(customer); Order o1= new Order(); o1.setOname("牙膏1"); o1.setCustomer(customer); session.save(customer);//移到后面也是可以的 session.save(o1); //session.save(customer);//移到这是也是可以的
控制台打印结果:
Hibernate: insert into t_customer (cname) values (?) Hibernate: insert into t_order (oname, cid) values (?, ?) 虚拟机关闭!释放资源
给已有的 Customer 新增 Order
@Test public void fun2() { Session session = HibernateUtils.openSession(); Transaction ts = session.beginTransaction(); // ============================================== Customer customer = (Customer) session.get(Customer.class, 1); Order o1 = new Order(); o1.setOname("手机"); Order o2 = new Order(); o2.setOname("电脑"); customer.getOrders().add(o1); customer.getOrders().add(o2); session.save(o1); session.save(o2); session.save(customer); // 如果 Customer.hbm.xml 配置了 inverse=true, 这句可以注释掉,并且不会打印 Update 语句 // ============================================== ts.commit(); // 事务提交,这里使用insert into语句 session.close(); }
打印结果:
Hibernate: select customer0_.cid as cid0_0_, customer0_.cname as cname0_0_ from t_customer customer0_ where customer0_.cid=? Hibernate: select orders0_.cid as cid0_1_, orders0_.oid as oid1_, orders0_.oid as oid1_0_, orders0_.oname as oname1_0_, orders0_.cid as cid1_0_ from t_order orders0_ where orders0_.cid=? Hibernate: insert into t_order (oname, cid) values (?, ?) Hibernate: insert into t_order (oname, cid) values (?, ?) Hibernate: update t_order set cid=? where oid=? Hibernate: update t_order set cid=? where oid=? 虚拟机关闭!释放资源
10、多对一关系的维护--inverse 属性 <--返回目录
外键维护的放弃,只能有非外键所在的的对象来放弃,即t_customer放弃维护关系
在Customer.hbm.xml表中配置:t_customer放弃维护关系
<set name="orders" inverse="true">
inverse: 是否将关系的维护反转给对方,默认false;true表示放弃维护关系
当inverse="false"时,删除t_customer表中记录,成功,但是会把引用该客户的外键cid设置为null;
当inverse="true"时,删除t_customer表中记录,报错。这时候怎么办?
List<Order> orders = customer.getOrders(); for(Order o: orders) { o.setCustomer(null); // 设置订单不属于任何Customer }
11、cascade级联操作 <--返回目录
cascade: 级联操作
<!-- 表达一对多关系中的集合 name:集合的属性名称 inverse: 是否将关系的维护反转给对方. 默认值: false true: 在Customer 中 放弃维护外键关系 cascade: 级联操作 save-update:级联保存,级联修改. 保存A时,同时保存B. delete:删除A,同时删除B,AB都不存在 delete-orphan:孤儿删除,解除关系,同时将B删除,A存在的。 如果需要配置多项,使用逗号分隔。<set cascade="save-update,delete"> all: save-update 和 delete 整合 all-delete-orphan: 三个整合 --> <set name="orders" inverse="false" cascade="all-delete-orphan" > <!-- key 用来描述外键 column : 外键的值 --> <key column="cid" ></key> <!-- one-to-many 表达, Customer 与orders 的关系是一对多 class: 表达关联的另一方的完整类名 --> <one-to-many class="Order" /> </set>
级联删除要注意:不要在两表之间都使用级联删除,否则会全部删除数据!!!
---