设计几个测试案例,以验证一级缓存的存在及特性。
实现此案例需要按照如下步骤进行。
步骤一:创建项目
复制项目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
下面是本案例的完整代码。
其中TestFirstCache完整代码如下:
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种方式:
设计出案例,来使用并验证一级缓存管理方法。
设计2个案例,使用同一个Session查询同一条数据2次,由于一级缓存的存在,第二次查询时将从一级缓存中取数,而不会查询数据库。
那么,如果在第二次查询之前将数据从缓存中移除,第二次查询时就会访问数据库。在这两个案例中,我们分别使用evict和clear方法将数据从缓存中移除。
实现此案例需要按照如下步骤进行。
步骤一:在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(); } #cold_bold /** #cold_bold * 验证缓存管理的方法evict #cold_bold */ #cold_bold @Test #cold_bold public void test3() { #cold_bold Session session = HibernateUtil.getSession(); #cold_bold Emp e1 = (Emp) session.get(Emp.class, 321); #cold_bold System.out.println(e1.getName()); #cold_bold session.evict(e1); #cold_bold Emp e2 = (Emp) session.get(Emp.class, 321); #cold_bold System.out.println(e2.getName()); #cold_bold session.close(); #cold_bold } #cold_bold #cold_bold /** #cold_bold * 验证缓存管理的方法clear #cold_bold */ #cold_bold @Test #cold_bold public void test4() { #cold_bold Session session = HibernateUtil.getSession(); #cold_bold Emp e1 = (Emp) session.get(Emp.class, 321); #cold_bold System.out.println(e1.getName()); #cold_bold session.clear(); #cold_bold Emp e2 = (Emp) session.get(Emp.class, 321); #cold_bold System.out.println(e2.getName()); #cold_bold session.close(); #cold_bold } }
步骤二:测试
分别执行这两个方法,并观察控制台,会发现都只输出了一次SQL,验证了这2个方法可以有效的管理一级缓存。
test3()执行后,控制台输出结果如下图,可以看出第二次查询访问了数据库,验证了evice管理一级缓存是有效的。
图-3
test4()执行后,控制台输出结果如下图,可以看出第二次查询访问了数据库,验证了clear管理一级缓存是有效的。
图-4
下面是本案例的完整代码。
其中TestFirstCache完整代码如下:
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(); } }
设计出案例,验证持久态对象的特性:
设计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
本案例的完整代码如下所示:
TestPersistent完整代码如下:
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(); } }
设计案例,验证session.load()方法和query.iterate()方法在查询时是采用延迟加载机制的。
实现此案例需要按照如下步骤进行。
步骤一:创建验证延迟加载的测试类
在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
本案例的完整代码如下所示:
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(); } }
请将NETCTOSS中资费模块DAO的实现,改用Hibernate中延迟加载的方法。
将CostDaoImpl中的findById方法的实现,改为session.load()。
为了避免session提前关闭导致延迟加载出现问题,需要在findById方法中不关闭session,而是自定义拦截器,在拦截器中调用完action之后关闭session。自然地,资费模块的action都应该引用这个拦截器。
实现此案例需要按照如下步骤进行。
步骤一:使用延迟加载方法
将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(); #cold_bold 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; #cold_bold /** #cold_bold * 使用ThreadLocal管理Session,可以保证一个线程中只有唯一的一个连接。 #cold_bold * 并且我们在获取连接时,它会自动的给我们返回当前线程对应的连接。 #cold_bold */ #cold_bold private static ThreadLocal<Session> tl = #cold_bold new ThreadLocal<Session>(); static { // 加载Hibernate主配置文件 Configuration conf = new Configuration(); conf.configure("/hibernate.cfg.xml"); sessionFactory = conf.buildSessionFactory(); } /** * 创建session */ public static Session getSession() { #cold_bold // ThreadLocal会以当前线程名为key获取连接 #cold_bold Session session = tl.get(); #cold_bold // 如果取到的当前线程的连接为空 #cold_bold if(session == null) { #cold_bold // 使用工厂创建连接 #cold_bold session = sessionFactory.openSession(); #cold_bold // ThreadLocal会以当前线程名为key保存session #cold_bold tl.set(session); #cold_bold } #cold_bold return session; } #cold_bold /** #cold_bold * 关闭session #cold_bold */ #cold_bold public static void close() { #cold_bold // ThreadLocal会以当前线程名为key获取连接 #cold_bold Session session = tl.get(); #cold_bold // 如果取到的当前线程的连接不为空 #cold_bold if(session != null) { #cold_bold // 关闭session #cold_bold session.close(); #cold_bold // 将当前线程对应的连接从ThreadLocal中移除 #cold_bold tl.remove(); #cold_bold } #cold_bold } 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"/> #cold_bold <!-- 保持session开启拦截器 --> #cold_bold <interceptor name="openSessionInterceptor" #cold_bold class="com.netctoss.interceptor.OpenSessionInViewInterceptor"/> <!-- 登录检查拦截器栈 --> <interceptor-stack name="loginStack"> <interceptor-ref name="loginInterceptor"/> #cold_bold <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(); #cold_bold// 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 { #cold_bold// 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); #cold_bold// session.close(); return cost; } }
步骤七:测试
重新部署项目,并启动tomcat,访问资费修改功能,效果如下图,可以看出使用了OpenSessionInViewInterceptor之后,的确解决了session提前关闭引发的延迟加载问题:
图-13
以下是本案例的完整代码。
其中CostDaoImpl完整代码如下:
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; } }
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(); } }
OpenSessionInViewInterceptor完整代码如下:
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> <!-- 生成验证码 --> <actionname="createImage" class="com.netctoss.action.CreateImageAction"> <!-- 使用stream类型的result --> <result name="success" type="stream"> <!-- 指定输出的内容 --> <param name="inputName">imageStream</param> </result> </action> </package> </struts>
使用一对多关联映射,在查询账务账号时,自动查询出它对应的全部业务账号。
一对多关联映射开发步骤:
实现此案例需要按照如下步骤进行。
步骤一:创建账务账号实体类
创建账务账号实体类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> #cold_bold <!-- 将SQL输出时是否格式化。为了方便截图,我将其设置为false --> #cold_bold <property name="format_sql">false</property> <!-- 声明映射关系文件 --> <mapping resource="com/tarena/entity/Emp.hbm.xml" /> #cold_bold <mapping resource="com/tarena/entity/Account.hbm.xml" /> #cold_bold <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; #cold_bold // 追加关联属性,用于存储相关的Service信息 #cold_bold private Set<Service> services; #cold_bold #cold_bold public Set<Service> getServices() { #cold_bold return services; #cold_bold } #cold_bold #cold_bold public void setServices(Set<Service> services) { #cold_bold this.services = services; #cold_bold } 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"/> #cold_bold <!-- 配置services属性,采用一对多的关系 --> #cold_bold <set name="services"> #cold_bold <!-- 用于指定关联条件,写关联条件的外键字段 --> #cold_bold <key column="ACCOUNT_ID"/> #cold_bold <!-- 用于指定采用哪种关系,加载哪方数据 --> #cold_bold <one-to-many class="com.tarena.entity.Service"/> #cold_bold </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
以下为本案例的完整代码。
其中账务账号实体类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>
业务账号实体类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>
测试类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(); } }