JAVA Hibernate DAY02
1 验证一级缓存
1.1 问题
设计几个测试案例,以验证一级缓存的存在及特性。
1.2 方案
- 用同一个Session查询同一条数据2次,如果只查询一次数据库,则验证了一级缓存的存在。
- 用2个不同的Session,分别查询同一条数据,如果查询2次数据库,则验证了一级缓存是Session独享的。
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建项目
复制项目HibernateDay01,粘贴并将项目名改为HibernateDay02。
步骤二:写测试案例代码
在com.tarena.test包下创建测试类TestFirstCache,在这个测试类中分别写出方案中提到的2个测试方法,用以验证一级缓存的存在及特性,代码如下:
- package com.tarena.test;
- import org.hibernate.Session;
- import org.junit.Test;
- import com.tarena.entity.Emp;
- import com.tarena.util.HibernateUtil;
- public class TestFirstCache {
-
- /**
- * 用同一个Session查询同一条数据2次,
- * 如果只查询一次数据库,则验证了一级缓存的存在。
- */
- @Test
- public void test1() {
- Session session = HibernateUtil.getSession();
- Emp e1 = (Emp) session.get(Emp.class, 321);
- System.out.println(e1.getName());
- System.out.println("----------------");
- Emp e2 = (Emp) session.get(Emp.class, 321);
- System.out.println(e2.getName());
- session.close();
- }
- /**
- * 用2个不同的Session,分别查询同一条数据,
- * 如果查询2次数据库,则验证了一级缓存是Session独享的。
- */
- @Test
- public void test2() {
- Session session1 = HibernateUtil.getSession();
- Emp e1 = (Emp) session1.get(Emp.class, 321);
- System.out.println(e1.getName());
- Session session2 = HibernateUtil.getSession();
- Emp e2 = (Emp) session2.get(Emp.class, 321);
- System.out.println(e2.getName());
- session1.close();
- session2.close();
- }
-
- }
步骤三:测试
分别执行以上2个方法,根据控制台输出的结果,根据其SQL语句数量可以判断出查询执行的次数,进而验证一级缓存的存在及特性。
test1()执行后,控制台输出结果如下图,可以看出第二次查询并没有真正访问数据库,验证了一级缓存的存在:
图-1
test2()执行后,控制台输出结果如下图,可以看出第二次查询访问了数据库,验证了一级缓存是Session独享的。
图-2
1.4 完整代码
下面是本案例的完整代码。
其中TestFirstCache完整代码如下:
2 管理一级缓存
2.1 问题
掌握一级缓存管理的2种方式:
- 使用evict方法,从一级缓存中移除一个对象。
- 使用clear方法,将一级缓存中的对象全部移除。
设计出案例,来使用并验证一级缓存管理方法。
2.2 方案
设计2个案例,使用同一个Session查询同一条数据2次,由于一级缓存的存在,第二次查询时将从一级缓存中取数,而不会查询数据库。
那么,如果在第二次查询之前将数据从缓存中移除,第二次查询时就会访问数据库。在这两个案例中,我们分别使用evict和clear方法将数据从缓存中移除。
2.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:在TestFirstCache中增加测试案例代码
在TestFirstCache中增加2个方法,均使用同一个Session查询同一条数据2次,在第二次查询之前,分别使用evice和clear方法移除缓存数据,代码如下:
- package com.tarena.test;
- import org.hibernate.Session;
- import org.junit.Test;
- import com.tarena.entity.Emp;
- import com.tarena.util.HibernateUtil;
- public class TestFirstCache {
-
- /**
- * 用同一个Session查询同一条数据2次,
- * 如果只查询一次数据库,则验证了一级缓存的存在。
- */
- @Test
- public void test1() {
- Session session = HibernateUtil.getSession();
- Emp e1 = (Emp) session.get(Emp.class, 321);
- System.out.println(e1.getName());
- System.out.println("----------------");
- Emp e2 = (Emp) session.get(Emp.class, 321);
- System.out.println(e2.getName());
- session.close();
- }
- /**
- * 用2个不同的Session,分别查询同一条数据,
- * 如果查询2次数据库,则验证了一级缓存是Session独享的。
- */
- @Test
- public void test2() {
- Session session1 = HibernateUtil.getSession();
- Emp e1 = (Emp) session1.get(Emp.class, 321);
- System.out.println(e1.getName());
- Session session2 = HibernateUtil.getSession();
- Emp e2 = (Emp) session2.get(Emp.class, 321);
- System.out.println(e2.getName());
- session1.close();
- session2.close();
- }
-
- /**
- * 验证缓存管理的方法evict
- */
- @Test
- public void test3() {
- Session session = HibernateUtil.getSession();
- Emp e1 = (Emp) session.get(Emp.class, 321);
- System.out.println(e1.getName());
- session.evict(e1);
- Emp e2 = (Emp) session.get(Emp.class, 321);
- System.out.println(e2.getName());
- session.close();
- }
-
- /**
- * 验证缓存管理的方法clear
- */
- @Test
- public void test4() {
- Session session = HibernateUtil.getSession();
- Emp e1 = (Emp) session.get(Emp.class, 321);
- System.out.println(e1.getName());
- session.clear();
- Emp e2 = (Emp) session.get(Emp.class, 321);
- System.out.println(e2.getName());
- session.close();
- }
-
- }
步骤二:测试
分别执行这两个方法,并观察控制台,会发现都只输出了一次SQL,验证了这2个方法可以有效的管理一级缓存。
test3()执行后,控制台输出结果如下图,可以看出第二次查询访问了数据库,验证了evice管理一级缓存是有效的。
图-3
test4()执行后,控制台输出结果如下图,可以看出第二次查询访问了数据库,验证了clear管理一级缓存是有效的。
图-4
2.4 完整代码
下面是本案例的完整代码。
其中TestFirstCache完整代码如下:
3 验证持久态对象的特性
3.1 问题
设计出案例,验证持久态对象的特性:
- 持久态对象存在于一级缓存中。
- 持久态对象可以自动更新至数据库。
- 持久态对象自动更新数据库的时机是session.flush()。
3.2 方案
设计3个案例,分别验证持久态对象的3个特性:
- 新增一条数据,在新增后该数据对象为持久态的,然后根据对象的ID再次查询数据。执行时如果控制台不重新输出SQL则验证了持久态对象存在于一级缓存。
- 新增一条数据,在新增后该对象为持久态的,然后修改这个对象的任意属性值,并提交事务。在执行时,如果发现数据库中的数据是修改后的内容,则验证了持久态对象可以自动更新至数据库。
- 查询一条数据,该数据对象为持久态的,然后修改对象的任意属性值,再调用session.flush()方法,并且不提交事务。如果执行时控制台输出更新的SQL,则验证了一级缓存对象更新至数据库的时机为session.flush()。
3.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建验证对象持久性测试类
在com.tarena.test包下,创建一个测试类TestPersistent,在其中写出验证对象持久性的3个测试方法,用以验证对象持久性的3个特性,代码如下:
- package com.tarena.test;
- import java.sql.Date;
- import java.sql.Timestamp;
- import org.hibernate.HibernateException;
- import org.hibernate.Session;
- import org.hibernate.Transaction;
- import org.junit.Test;
- import com.tarena.entity.Emp;
- import com.tarena.util.HibernateUtil;
- public class TestPersistent {
-
- /**
- * 持久态对象存在于一级缓存中
- */
- @Test
- public void test1() {
- Emp e = new Emp();
- e.setName("唐僧");
- e.setAge(29);
- e.setMarry(false);
- e.setSalary(12000.00);
- e.setBirthday(
- Date.valueOf("1983-10-20"));
- e.setLastLoginTime(
- new Timestamp(System.currentTimeMillis()));
- Session session = HibernateUtil.getSession();
- Transaction ts = session.beginTransaction();
- try {
- session.save(e);
- ts.commit();
- } catch (HibernateException e1) {
- e1.printStackTrace();
- ts.rollback();
- }
-
- Emp emp = (Emp) session.get(Emp.class, e.getId());
- System.out.println(emp.getId() + " " + emp.getName());
-
- session.close();
- }
- /**
- * 持久态对象可以自动更新至数据库
- */
- @Test
- public void test2() {
- Emp e = new Emp();
- e.setName("孙悟空");
- e.setAge(29);
- e.setMarry(false);
- e.setSalary(12000.00);
- e.setBirthday(
- Date.valueOf("1983-10-20"));
- e.setLastLoginTime(
- new Timestamp(System.currentTimeMillis()));
- Session session = HibernateUtil.getSession();
- Transaction ts = session.beginTransaction();
- try {
- session.save(e);
- e.setName("猪八戒");
- ts.commit();
- } catch (HibernateException e1) {
- e1.printStackTrace();
- ts.rollback();
- }
- session.close();
- }
- /**
- * 持久态对象自动更新数据库的时机
- */
- @Test
- public void test3() {
- Session session = HibernateUtil.getSession();
- Emp e = (Emp) session.load(Emp.class, 201);
- e.setName("太上老君");
- session.flush(); //同步但未提交事务
- session.close();
- }
-
- }
步骤二:测试
分别执行上述3个测试方法,观察控制台输出的SQL,看是否与方案中描述的一致。
test1()执行后,控制台输出结果如下图,可以看出后面的查询并没有再次访问数据库,验证了持久态对象是存在于一级缓存中的。
图-5
test2()执行后,控制台输出结果如下图,可以看出Hibernate自动执行了一次更新,验证了持久态对象会自动更新至数据库。
图-6
EMP表查询结果如下图,可以看出最后一条数据的名称的确被更新为猪八戒。
图-7
test3()执行后,控制台输出结果如下图,可以看出Hibernate自动执行了一次更新,验证了自动更新的时机是session.flush()。
图-8
EMP表查询结果如下图,可以看出最后一条数据的名称并没有更新为太上老君,说明session.flush只是触发更新,并没有提交事务。
图-9
3.4 完整代码
本案例的完整代码如下所示:
TestPersistent完整代码如下:
4 验证延迟加载
4.1 问题
设计案例,验证session.load()方法和query.iterate()方法在查询时是采用延迟加载机制的。
4.2 方案
- 验证session.load()的延迟加载,可以先查询某条EMP数据,然后输出一个分割线,在分割线的后面再使用这个对象,输出它的一些属性值。在运行时,如果查询EMP的SQL输出在分割线的后面,则验证了该方法是采用延迟加载机制的。
- 验证query.iterate()的延迟加载,可以先查询出全部的EMP数据,然后输出一个分割线,在分割线的后面再遍历查询结果并输出每个对象的一些属性值。在运行时,如果查询EMP的SQL输出在分割线的后面,则验证了该方法是采用延迟加载机制的。
4.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建验证延迟加载的测试类
在com.tarena.test包下,创建一个测试类TestLazy,并在这个类中写2个测试方法,分别验证session.load()和query.iterate()方法是采用延迟加载机制的,代码如下:
- package com.tarena.test;
- import java.util.Iterator;
- import org.hibernate.Query;
- import org.hibernate.Session;
- import org.junit.Test;
- import com.tarena.entity.Emp;
- import com.tarena.util.HibernateUtil;
- public class TestLazy {
-
- /**
- * 验证load方法是延迟加载的
- */
- @Test
- public void test1() {
- Session session = HibernateUtil.getSession();
- // load方法并没有触发访问数据库
- Emp emp = (Emp) session.load(Emp.class, 321);
- System.out.println("-----------------");
- // 使用emp对象时才真正访问数据库
- System.out.println(emp.getName());
- session.close();
- }
- /**
- * 验证iterate方法是延迟加载的
- */
- @Test
- public void test2() {
- String hql = "from Emp";
- Session session = HibernateUtil.getSession();
- Query query = session.createQuery(hql);
- // iterate方法访问了数据库,但只查询了ID列
- Iterator<Emp> it = query.iterate();
- System.out.println("-----------------");
- while (it.hasNext()) {
- Emp emp = it.next();
- // 使用emp对象时才将其他列全部加载
- System.out.println(emp.getName());
- }
- session.close();
- }
-
- }
步骤二:测试
分别执行上面的2个测试方法,根据控制台输出的结果,判断session.load()和query.iterate()方法是否采用延迟加载机制。
test1()方法执行后,控制台输出结果如下图,可以看出查询SQL是输出在分割线后面的,也就是在使用emp对象时才触发了数据库的访问,验证了延迟加载的存在。
图-10
test2()方法执行后,控制台输出结果如下图,可以看出查询EMP表全部内容的SQL输出在分割线的后面,也就是在使用emp对象时才触发的数据库访问,验证了延迟加载的存在。
图-11
4.4 完整代码
本案例的完整代码如下所示:
5 在NETCTOSS中使用延迟加载
5.1 问题
请将NETCTOSS中资费模块DAO的实现,改用Hibernate中延迟加载的方法。
5.2 方案
将CostDaoImpl中的findById方法的实现,改为session.load()。
为了避免session提前关闭导致延迟加载出现问题,需要在findById方法中不关闭session,而是自定义拦截器,在拦截器中调用完action之后关闭session。自然地,资费模块的action都应该引用这个拦截器。
5.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:使用延迟加载方法
将CostDaoImpl中的findById方法中,session.get()改为session.load(),代码如下:
- package com.netctoss.dao;
- import java.util.List;
- import org.hibernate.HibernateException;
- import org.hibernate.Query;
- import org.hibernate.Session;
- import org.hibernate.Transaction;
- import com.netctoss.entity.Cost;
- import com.netctoss.util.HibernateUtil;
- /**
- * 当前阶段学习重点是Struts2,对于DAO的实现就模拟实现了。
- * 同学们可以使用JDBC/MyBatis自行实现该DAO。
- */
- public class CostDaoImpl implements ICostDao {
- @Override
- public List<Cost> findAll() {
- String hql = "from Cost";
- Session session = HibernateUtil.getSession();
- Query query = session.createQuery(hql);
- List<Cost> list = query.list();
- session.close();
- return list;
- }
- @Override
- public void delete(int id) {
- Cost cost = new Cost();
- cost.setId(id);
- Session session = HibernateUtil.getSession();
- Transaction ts = session.beginTransaction();
- try {
- session.delete(cost);
- ts.commit();
- } catch (HibernateException e) {
- e.printStackTrace();
- ts.rollback();
- } finally {
- session.close();
- }
- }
- @Override
- public Cost findByName(String name) {
- // 模拟根据名称查询资费数据,假设资费表中只有一条名为tarena的数据
- if("tarena".equals(name)) {
- Cost c = new Cost();
- c.setId(97);
- c.setName("tarena");
- c.setBaseDuration(99);
- c.setBaseCost(9.9);
- c.setUnitCost(0.9);
- c.setDescr("tarena套餐");
- c.setStatus("0");
- c.setCostType("2");
- return c;
- }
- return null;
- }
- @Override
- public Cost findById(int id) {
- Session session = HibernateUtil.getSession();
- Cost cost = (Cost) session.load(Cost.class, id);
- session.close();
- return cost;
- }
- }
步骤二:测试
重新部署项目,并重启tomcat,访问资费修改页面,效果如下图:
图-12
可以看出要修改的数据,只有ID显示正确,其他字段都为空。原因是我们的findById方法中直接关闭了session,而该方法返回的对象是在JSP中使用的,在使用时session已经关闭,由于延迟加载机制的存在,导致了这个问题的发生。要想解决这个问题,我们需要继续如下的步骤。
步骤三:重构HibernateUtil,使用ThreadLocal管理Session
重构HibernateUtil,引入ThreadLocal来管理Session。ThreadLocal对象是与线程有关的工具类,它的目的是将管理的对象按照线程进行隔离,以保证一个线程只对应一个对象。
由于后面我们要在拦截器中关闭连接,因此需要准确的取出DAO中使用的连接对象,为了便于实现在一个线程(一次客户端请求,就是一个线程)中,不同的代码位置获取同一个连接,那么使用ThreadLocal来管理session就再合适不过了。
注意,引入ThreadLocal管理session,不仅仅是在创建session时将其加入到ThreadLocal中,在关闭session时也需要将其从ThreadLocal中移除,因此HibernateUtil中还需要提供一个关闭session的方法。
重构以后,HibernateUtil代码如下:
- package com.netctoss.util;
- import org.hibernate.Session;
- import org.hibernate.SessionFactory;
- import org.hibernate.cfg.Configuration;
- public class HibernateUtil {
- private static SessionFactory sessionFactory;
- /**
- * 使用ThreadLocal管理Session,可以保证一个线程中只有唯一的一个连接。
- * 并且我们在获取连接时,它会自动的给我们返回当前线程对应的连接。
- */
- private static ThreadLocal<Session> tl =
- new ThreadLocal<Session>();
-
- static {
- // 加载Hibernate主配置文件
- Configuration conf = new Configuration();
- conf.configure("/hibernate.cfg.xml");
- sessionFactory = conf.buildSessionFactory();
- }
- /**
- * 创建session
- */
- public static Session getSession() {
- // ThreadLocal会以当前线程名为key获取连接
- Session session = tl.get();
- // 如果取到的当前线程的连接为空
- if(session == null) {
- // 使用工厂创建连接
- session = sessionFactory.openSession();
- // ThreadLocal会以当前线程名为key保存session
- tl.set(session);
- }
- return session;
- }
-
- /**
- * 关闭session
- */
- public static void close() {
- // ThreadLocal会以当前线程名为key获取连接
- Session session = tl.get();
- // 如果取到的当前线程的连接不为空
- if(session != null) {
- // 关闭session
- session.close();
- // 将当前线程对应的连接从ThreadLocal中移除
- tl.remove();
- }
- }
-
- public static void main(String[] args) {
- System.out.println(getSession());
- close();
- }
- }
步骤四:创建保持session在视图层开启的拦截器
在com.netctoss.interceptor包下,创建一个拦截器OpenSessionInViewInterceptor,在拦截方法中,先调用action和result,之后再关闭本次访问线程对应的session,代码如下:
- package com.netctoss.interceptor;
- import com.netctoss.util.HibernateUtil;
- import com.opensymphony.xwork2.ActionInvocation;
- import com.opensymphony.xwork2.interceptor.Interceptor;
- /**
- * 保持Session在视图层开启的拦截器,主要作用
- * 是在执行完JSP之后再统一关闭session。
- */
- public class OpenSessionInViewInterceptor
- implements Interceptor {
- @Override
- public void destroy() {
- }
- @Override
- public void init() {
- }
- @Override
- public String intercept(ActionInvocation ai)
- throws Exception {
- // 调用action和result
- ai.invoke();
- /*
- * result会把请求转发到页面,因此调用result,
- * 就相当于调用JSP,因此此处的代码是在JSP之后执行。
- * */
- HibernateUtil.close();
- return null;
- }
- }
步骤五:注册并引用拦截器
在struts.xml中,注册并引用这个拦截器,代码如下:
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
- "http://struts.apache.org/dtds/struts-2.1.7.dtd">
- <struts>
-
- <!-- 公共的包,封装了通用的拦截器、通用的result -->
- <package name="netctoss" extends="json-default">
- <interceptors>
- <!-- 登录检查拦截器 -->
- <interceptor name="loginInterceptor"
- class="com.netctoss.interceptor.LoginInterceptor"/>
- <!-- 保持session开启拦截器 -->
- <interceptor name="openSessionInterceptor"
- class="com.netctoss.interceptor.OpenSessionInViewInterceptor"/>
- <!-- 登录检查拦截器栈 -->
- <interceptor-stack name="loginStack">
- <interceptor-ref name="loginInterceptor"/>
- <interceptor-ref name="openSessionInterceptor"/>
- <!-- 不要丢掉默认的拦截器栈,里面有很多Struts2依赖的拦截器 -->
- <interceptor-ref name="defaultStack"/>
- </interceptor-stack>
- </interceptors>
- <!-- 设置action默认引用的拦截器 -->
- <default-interceptor-ref name="loginStack"/>
- <!-- 全局的result,包下所有的action都可以共用 -->
- <global-results>
- <!-- 跳转到登录页面的result -->
- <result name="login" type="redirectAction">
- <param name="namespace">/login</param>
- <param name="actionName">toLogin</param>
- </result>
- </global-results>
- </package>
-
- <!--
- 资费模块配置信息:
- 一般情况下,一个模块的配置单独封装在一个package下,
- 并且以模块名来命名package的name和namespace。
- -->
- <package name="cost" namespace="/cost" extends="netctoss">
- <!-- 查询资费数据 -->
- <action name="findCost" class="com.netctoss.action.FindCostAction">
- <!--
- 正常情况下跳转到资费列表页面。
- 一般一个模块的页面要打包在一个文件夹下,并且文件夹以模块名命名。
- -->
- <result name="success">
- /WEB-INF/cost/find_cost.jsp
- </result>
- <!--
- 错误情况下,跳转到错误页面。
- 错误页面可以被所有模块复用,因此放在main下,
- 该文件夹用于存放公用的页面。
- -->
- <result name="error">
- /WEB-INF/main/error.jsp
- </result>
- </action>
- <!-- 删除资费 -->
- <action name="deleteCost"
- class="com.netctoss.action.DeleteCostAction">
- <!-- 删除完之后,重定向到查询action -->
- <result name="success" type="redirectAction">
- findCost
- </result>
- <result name="error">
- /WEB-INF/main/error.jsp
- </result>
- </action>
- <!-- 打开资费新增页 -->
- <action name="toAddCost">
- <result name="success">
- /WEB-INF/cost/add_cost.jsp
- </result>
- </action>
- <!-- 资费名唯一性校验 -->
- <action name="checkCostName"
- class="com.netctoss.action.CheckCostNameAction">
- <!-- 使用json类型的result把结果输出给回调函数 -->
- <result name="success" type="json">
- <param name="root">info</param>
- </result>
- </action>
- <!-- 打开修改页面 -->
- <action name="toUpdateCost"
- class="com.netctoss.action.ToUpdateCostAction">
- <result name="success">
- /WEB-INF/cost/update_cost.jsp
- </result>
- <result name="error">
- /WEB-INF/main/error.jsp
- </result>
- </action>
- </package>
-
- <!-- 登录模块 -->
- <package name="login" namespace="/login" extends="struts-default">
- <!--
- 打开登录页面:
- 1、action的class属性可以省略,省略时Struts2
- 会自动实例化默认的Action类ActionSupport,
- 该类中有默认业务方法execute,返回success。
- 2、action的method属性可以省略,省略时Struts2
- 会自动调用execute方法。
- -->
- <action name="toLogin">
- <result name="success">
- /WEB-INF/main/login.jsp
- </result>
- </action>
- <!-- 登录校验 -->
- <action name="login" class="com.netctoss.action.LoginAction">
- <!-- 校验成功,跳转到系统首页 -->
- <result name="success">
- /WEB-INF/main/index.jsp
- </result>
- <!-- 登录失败,跳转回登录页面 -->
- <result name="fail">
- /WEB-INF/main/login.jsp
- </result>
- <!-- 报错,跳转到错误页面 -->
- <result name="error">
- /WEB-INF/main/error.jsp
- </result>
- </action>
- <!-- 生成验证码 -->
- <action name="createImage" class="com.netctoss.action.CreateImageAction">
- <!-- 使用stream类型的result -->
- <result name="success" type="stream">
- <!-- 指定输出的内容 -->
- <param name="inputName">imageStream</param>
- </result>
- </action>
- </package>
-
- </struts>
步骤六:重构资费DAO实现类,去掉关闭session
重构CostDaoImpl,将session关闭的代码注释掉,统一由拦截器关闭。重构后代码如下:
- package com.netctoss.dao;
- import java.util.List;
- import org.hibernate.HibernateException;
- import org.hibernate.Query;
- import org.hibernate.Session;
- import org.hibernate.Transaction;
- import com.netctoss.entity.Cost;
- import com.netctoss.util.HibernateUtil;
- /**
- * 当前阶段学习重点是Struts2,对于DAO的实现就模拟实现了。
- * 同学们可以使用JDBC/MyBatis自行实现该DAO。
- */
- public class CostDaoImpl implements ICostDao {
- @Override
- public List<Cost> findAll() {
- String hql = "from Cost";
- Session session = HibernateUtil.getSession();
- Query query = session.createQuery(hql);
- List<Cost> list = query.list();
- // session.close();
- return list;
- }
- @Override
- public void delete(int id) {
- Cost cost = new Cost();
- cost.setId(id);
- Session session = HibernateUtil.getSession();
- Transaction ts = session.beginTransaction();
- try {
- session.delete(cost);
- ts.commit();
- } catch (HibernateException e) {
- e.printStackTrace();
- ts.rollback();
- } finally {
- // session.close();
- }
- }
- @Override
- public Cost findByName(String name) {
- // 模拟根据名称查询资费数据,假设资费表中只有一条名为tarena的数据
- if("tarena".equals(name)) {
- Cost c = new Cost();
- c.setId(97);
- c.setName("tarena");
- c.setBaseDuration(99);
- c.setBaseCost(9.9);
- c.setUnitCost(0.9);
- c.setDescr("tarena套餐");
- c.setStatus("0");
- c.setCostType("2");
- return c;
- }
- return null;
- }
- @Override
- public Cost findById(int id) {
- Session session = HibernateUtil.getSession();
- Cost cost = (Cost) session.load(Cost.class, id);
- // session.close();
- return cost;
- }
- }
步骤七:测试
重新部署项目,并启动tomcat,访问资费修改功能,效果如下图,可以看出使用了OpenSessionInViewInterceptor之后,的确解决了session提前关闭引发的延迟加载问题:
图-13
5.4 完整代码
以下是本案例的完整代码。
其中CostDaoImpl完整代码如下:
HibernateUtil完整代码如下:
OpenSessionInViewInterceptor完整代码如下:
struts.xml完整代码如下:
6 使用一对多关联映射
6.1 问题
使用一对多关联映射,在查询账务账号时,自动查询出它对应的全部业务账号。
6.2 方案
一对多关联映射开发步骤:
- 账务账号与业务账号具有一对多关系,他们的关系字段是service.account_id。
- 在账务账号中追加集合属性,用于封装它对应的一组业务账号。
- 在账务账号映射关系文件中配置此集合属性。
6.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:创建账务账号实体类
创建账务账号实体类Account,代码如下:
- package com.tarena.entity;
- import java.sql.Date;
- public class Account {
- private Integer id;
- private Integer recommenderId;
- private String loginName;
- private String loginPassword;
- private String status;
- private Date createDate;
- private Date pauseDate;
- private Date closeDate;
- private String realName;
- private String idcardNo;
- private Date birthdate;
- private String gender;
- private String occupation;
- private String telephone;
- private String email;
- private String mailaddress;
- private String zipcode;
- private String qq;
- private Date lastLoginTime;
- private String lastLoginIp;
-
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public Integer getRecommenderId() {
- return recommenderId;
- }
- public void setRecommenderId(Integer recommenderId) {
- this.recommenderId = recommenderId;
- }
- public String getLoginName() {
- return loginName;
- }
- public void setLoginName(String loginName) {
- this.loginName = loginName;
- }
- public String getLoginPassword() {
- return loginPassword;
- }
- public void setLoginPassword(String loginPassword) {
- this.loginPassword = loginPassword;
- }
- public String getStatus() {
- return status;
- }
- public void setStatus(String status) {
- this.status = status;
- }
- public Date getCreateDate() {
- return createDate;
- }
- public void setCreateDate(Date createDate) {
- this.createDate = createDate;
- }
- public Date getPauseDate() {
- return pauseDate;
- }
- public void setPauseDate(Date pauseDate) {
- this.pauseDate = pauseDate;
- }
- public Date getCloseDate() {
- return closeDate;
- }
- public void setCloseDate(Date closeDate) {
- this.closeDate = closeDate;
- }
- public String getRealName() {
- return realName;
- }
- public void setRealName(String realName) {
- this.realName = realName;
- }
- public String getIdcardNo() {
- return idcardNo;
- }
- public void setIdcardNo(String idcardNo) {
- this.idcardNo = idcardNo;
- }
- public Date getBirthdate() {
- return birthdate;
- }
- public void setBirthdate(Date birthdate) {
- this.birthdate = birthdate;
- }
- public String getGender() {
- return gender;
- }
- public void setGender(String gender) {
- this.gender = gender;
- }
- public String getOccupation() {
- return occupation;
- }
- public void setOccupation(String occupation) {
- this.occupation = occupation;
- }
- public String getTelephone() {
- return telephone;
- }
- public void setTelephone(String telephone) {
- this.telephone = telephone;
- }
- public String getEmail() {
- return email;
- }
- public void setEmail(String email) {
- this.email = email;
- }
- public String getMailaddress() {
- return mailaddress;
- }
- public void setMailaddress(String mailaddress) {
- this.mailaddress = mailaddress;
- }
- public String getZipcode() {
- return zipcode;
- }
- public void setZipcode(String zipcode) {
- this.zipcode = zipcode;
- }
- public String getQq() {
- return qq;
- }
- public void setQq(String qq) {
- this.qq = qq;
- }
- public Date getLastLoginTime() {
- return lastLoginTime;
- }
- public void setLastLoginTime(Date lastLoginTime) {
- this.lastLoginTime = lastLoginTime;
- }
- public String getLastLoginIp() {
- return lastLoginIp;
- }
- public void setLastLoginIp(String lastLoginIp) {
- this.lastLoginIp = lastLoginIp;
- }
- }
步骤二:创建账务账号映射关系文件
创建账务账号映射关系文件Account.hbm.xml,代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="com.tarena.entity.Account" table="ACCOUNT">
- <id name="id" type="integer" column="id">
- <generator class="sequence">
- <param name="sequence">ACCOUNT_SEQ</param>
- </generator>
- </id>
- <property name="recommenderId"
- type="integer" column="RECOMMENDER_ID"/>
- <property name="loginName"
- type="string" column="LOGIN_NAME"/>
- <property name="loginPassword"
- type="string" column="LOGIN_PASSWD"/>
- <property name="status"
- type="string" column="STATUS"/>
- <property name="createDate"
- type="date" column="CREATE_DATE"/>
- <property name="pauseDate"
- type="date" column="PAUSE_DATE"/>
- <property name="closeDate"
- type="date" column="CLOSE_DATE"/>
- <property name="realName"
- type="string" column="REAL_NAME"/>
- <property name="idcardNo"
- type="string" column="IDCARD_NO"/>
- <property name="birthdate"
- type="date" column="BIRTHDATE"/>
- <property name="gender"
- type="string" column="GENDER"/>
- <property name="occupation"
- type="string" column="OCCUPATION"/>
- <property name="telephone"
- type="string" column="TELEPHONE"/>
- <property name="email"
- type="string" column="EMAIL"/>
- <property name="mailaddress"
- type="string" column="MAILADDRESS"/>
- <property name="zipcode"
- type="string" column="ZIPCODE"/>
- <property name="qq"
- type="string" column="QQ"/>
- <property name="lastLoginTime"
- type="date" column="LAST_LOGIN_TIME"/>
- <property name="lastLoginIp"
- type="string" column="LAST_LOGIN_IP"/>
-
- </class>
- </hibernate-mapping>
步骤三:创建业务账号实体类
创建业务账号实体类Service,代码如下:
- package com.tarena.entity;
- import java.sql.Date;
- public class Service {
- private Integer id;
- private Integer accountId;
- private String unixHost;
- private String osUserName;
- private String loginPassword;
- private String status;
- private Date createDate;
- private Date pauseDate;
- private Date closeDate;
- private Integer costId;
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public Integer getAccountId() {
- return accountId;
- }
- public void setAccountId(Integer accountId) {
- this.accountId = accountId;
- }
- public String getUnixHost() {
- return unixHost;
- }
- public void setUnixHost(String unixHost) {
- this.unixHost = unixHost;
- }
- public String getOsUserName() {
- return osUserName;
- }
- public void setOsUserName(String osUserName) {
- this.osUserName = osUserName;
- }
- public String getLoginPassword() {
- return loginPassword;
- }
- public void setLoginPassword(String loginPassword) {
- this.loginPassword = loginPassword;
- }
- public String getStatus() {
- return status;
- }
- public void setStatus(String status) {
- this.status = status;
- }
- public Date getCreateDate() {
- return createDate;
- }
- public void setCreateDate(Date createDate) {
- this.createDate = createDate;
- }
- public Date getPauseDate() {
- return pauseDate;
- }
- public void setPauseDate(Date pauseDate) {
- this.pauseDate = pauseDate;
- }
- public Date getCloseDate() {
- return closeDate;
- }
- public void setCloseDate(Date closeDate) {
- this.closeDate = closeDate;
- }
- public Integer getCostId() {
- return costId;
- }
- public void setCostId(Integer costId) {
- this.costId = costId;
- }
- }
步骤四:创建业务账号映射关系文件
创建业务账号映射关系文件Service.hbm.xml,代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="com.tarena.entity.Service" table="SERVICE">
- <id name="id" type="integer" column="id">
- <generator class="sequence">
- <param name="sequence">SERVICE_SEQ</param>
- </generator>
- </id>
- <property name="accountId"
- type="integer" column="ACCOUNT_ID"/>
- <property name="unixHost"
- type="string" column="UNIX_HOST"/>
- <property name="osUserName"
- type="string" column="OS_USERNAME"/>
- <property name="loginPassword"
- type="string" column="LOGIN_PASSWD"/>
- <property name="status"
- type="string" column="STATUS"/>
- <property name="createDate"
- type="date" column="CREATE_DATE"/>
- <property name="pauseDate"
- type="date" column="PAUSE_DATE"/>
- <property name="closeDate"
- type="date" column="CLOSE_DATE"/>
- <property name="costId"
- type="integer" column="COST_ID"/>
- </class>
- </hibernate-mapping>
步骤五:声明映射关系文件
在hibernate.cfg.xml中,声明账务账号和业务账号的映射关系文件,代码如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE hibernate-configuration PUBLIC
- "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
- <hibernate-configuration>
- <session-factory>
- <!-- 数据库连接信息,根据自己的数据库进行配置 -->
- <property name="connection.url">
- jdbc:oracle:thin:@localhost:1521:xe
- </property>
- <property name="connection.username">lhh</property>
- <property name="connection.password">123456</property>
- <property name="connection.driver_class">
- oracle.jdbc.OracleDriver
- </property>
-
- <!-- Hibernate配置信息 -->
- <!-- dialect方言,用于配置生成针对哪个数据库的SQL语句 -->
- <property name="dialect">
- <!-- 方言类,Hibernate提供的,用于封装某种特定数据库的方言 -->
- org.hibernate.dialect.OracleDialect
- </property>
- <!-- Hibernate生成的SQL是否输出到控制台 -->
- <property name="show_sql">true</property>
- <!-- 将SQL输出时是否格式化。为了方便截图,我将其设置为false -->
- <property name="format_sql">false</property>
-
- <!-- 声明映射关系文件 -->
- <mapping resource="com/tarena/entity/Emp.hbm.xml" />
- <mapping resource="com/tarena/entity/Account.hbm.xml" />
- <mapping resource="com/tarena/entity/Service.hbm.xml" />
- </session-factory>
- </hibernate-configuration>
步骤六:在账务账号实体类中追加集合属性
在账务账号实体类Account中,追加集合属性,用于封装它对应的所有业务账号,代码如下:
- package com.tarena.entity;
- import java.sql.Date;
- import java.util.Set;
- public class Account {
- private Integer id;
- private Integer recommenderId;
- private String loginName;
- private String loginPassword;
- private String status;
- private Date createDate;
- private Date pauseDate;
- private Date closeDate;
- private String realName;
- private String idcardNo;
- private Date birthdate;
- private String gender;
- private String occupation;
- private String telephone;
- private String email;
- private String mailaddress;
- private String zipcode;
- private String qq;
- private Date lastLoginTime;
- private String lastLoginIp;
- // 追加关联属性,用于存储相关的Service信息
- private Set<Service> services;
- public Set<Service> getServices() {
- return services;
- }
- public void setServices(Set<Service> services) {
- this.services = services;
- }
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public Integer getRecommenderId() {
- return recommenderId;
- }
- public void setRecommenderId(Integer recommenderId) {
- this.recommenderId = recommenderId;
- }
- public String getLoginName() {
- return loginName;
- }
- public void setLoginName(String loginName) {
- this.loginName = loginName;
- }
- public String getLoginPassword() {
- return loginPassword;
- }
- public void setLoginPassword(String loginPassword) {
- this.loginPassword = loginPassword;
- }
- public String getStatus() {
- return status;
- }
- public void setStatus(String status) {
- this.status = status;
- }
- public Date getCreateDate() {
- return createDate;
- }
- public void setCreateDate(Date createDate) {
- this.createDate = createDate;
- }
- public Date getPauseDate() {
- return pauseDate;
- }
- public void setPauseDate(Date pauseDate) {
- this.pauseDate = pauseDate;
- }
- public Date getCloseDate() {
- return closeDate;
- }
- public void setCloseDate(Date closeDate) {
- this.closeDate = closeDate;
- }
- public String getRealName() {
- return realName;
- }
- public void setRealName(String realName) {
- this.realName = realName;
- }
- public String getIdcardNo() {
- return idcardNo;
- }
- public void setIdcardNo(String idcardNo) {
- this.idcardNo = idcardNo;
- }
- public Date getBirthdate() {
- return birthdate;
- }
- public void setBirthdate(Date birthdate) {
- this.birthdate = birthdate;
- }
- public String getGender() {
- return gender;
- }
- public void setGender(String gender) {
- this.gender = gender;
- }
- public String getOccupation() {
- return occupation;
- }
- public void setOccupation(String occupation) {
- this.occupation = occupation;
- }
- public String getTelephone() {
- return telephone;
- }
- public void setTelephone(String telephone) {
- this.telephone = telephone;
- }
- public String getEmail() {
- return email;
- }
- public void setEmail(String email) {
- this.email = email;
- }
- public String getMailaddress() {
- return mailaddress;
- }
- public void setMailaddress(String mailaddress) {
- this.mailaddress = mailaddress;
- }
- public String getZipcode() {
- return zipcode;
- }
- public void setZipcode(String zipcode) {
- this.zipcode = zipcode;
- }
- public String getQq() {
- return qq;
- }
- public void setQq(String qq) {
- this.qq = qq;
- }
- public Date getLastLoginTime() {
- return lastLoginTime;
- }
- public void setLastLoginTime(Date lastLoginTime) {
- this.lastLoginTime = lastLoginTime;
- }
- public String getLastLoginIp() {
- return lastLoginIp;
- }
- public void setLastLoginIp(String lastLoginIp) {
- this.lastLoginIp = lastLoginIp;
- }
- }
步骤七:在账务账号映射关系文件中配置集合属性
在账务账号映射关系文件Account.hbm.xml中,配置追加的集合属性,代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="com.tarena.entity.Account" table="ACCOUNT">
- <id name="id" type="integer" column="id">
- <generator class="sequence">
- <param name="sequence">ACCOUNT_SEQ</param>
- </generator>
- </id>
- <property name="recommenderId"
- type="integer" column="RECOMMENDER_ID"/>
- <property name="loginName"
- type="string" column="LOGIN_NAME"/>
- <property name="loginPassword"
- type="string" column="LOGIN_PASSWD"/>
- <property name="status"
- type="string" column="STATUS"/>
- <property name="createDate"
- type="date" column="CREATE_DATE"/>
- <property name="pauseDate"
- type="date" column="PAUSE_DATE"/>
- <property name="closeDate"
- type="date" column="CLOSE_DATE"/>
- <property name="realName"
- type="string" column="REAL_NAME"/>
- <property name="idcardNo"
- type="string" column="IDCARD_NO"/>
- <property name="birthdate"
- type="date" column="BIRTHDATE"/>
- <property name="gender"
- type="string" column="GENDER"/>
- <property name="occupation"
- type="string" column="OCCUPATION"/>
- <property name="telephone"
- type="string" column="TELEPHONE"/>
- <property name="email"
- type="string" column="EMAIL"/>
- <property name="mailaddress"
- type="string" column="MAILADDRESS"/>
- <property name="zipcode"
- type="string" column="ZIPCODE"/>
- <property name="qq"
- type="string" column="QQ"/>
- <property name="lastLoginTime"
- type="date" column="LAST_LOGIN_TIME"/>
- <property name="lastLoginIp"
- type="string" column="LAST_LOGIN_IP"/>
-
- <!-- 配置services属性,采用一对多的关系 -->
- <set name="services">
- <!-- 用于指定关联条件,写关联条件的外键字段 -->
- <key column="ACCOUNT_ID"/>
- <!-- 用于指定采用哪种关系,加载哪方数据 -->
- <one-to-many class="com.tarena.entity.Service"/>
- </set>
- </class>
- </hibernate-mapping>
步骤八:创建测试类
在com.tarena.test包下创建一个测试类TestOneToMany,并在这个类中增加一个测试方法,查询出某条账务账号的数据,然后输出账务账号以及账务账号中追加的集合属性值,代码如下:
- package com.tarena.test;
- import java.util.Set;
- import org.hibernate.Session;
- import org.junit.Test;
- import com.tarena.entity.Account;
- import com.tarena.entity.Service;
- import com.tarena.util.HibernateUtil;
- public class TestOneToMany {
-
- @Test
- public void test1() {
- Session session = HibernateUtil.getSession();
- Account account =
- (Account) session.get(Account.class, 1011);
- System.out.println(
- account.getIdcardNo() + " , "
- + account.getRealName() + " , "
- + account.getBirthdate());
-
- System.out.println("------------------");
-
- Set<Service> services = account.getServices();
- System.out.println(services.getClass().getName());
- for (Service service : services) {
- System.out.println(service.getOsUserName());
- }
- session.close();
- }
-
- }
步骤九:测试
执行这个方法,控制台输出结果如下图,可以看出查询账务账号之后,Hibernate自动查询出了SERVICE数据并封装到了services属性中,并且对于SERVICE的查询是采用延迟加载机制的。
图-14
6.4 完整代码
以下为本案例的完整代码。
其中账务账号实体类Account完整代码如下:
账务账号映射关系文件Account.hbm.xml完整代码如下:
业务账号实体类Service完整代码如下:
业务账号实体类Service.hbm.xml完整代码如下:
主配置文件hibernate.cfg.xml完整代码如下:
测试类TestOneToMany完整代码如下: