Top

JAVA Hibernate DAY02

  1. 验证一级缓存
  2. 管理一级缓存
  3. 验证持久态对象的特性
  4. 验证延迟加载
  5. 在NETCTOSS中使用延迟加载
  6. 使用一对多关联映射

1 验证一级缓存

1.1 问题

设计几个测试案例,以验证一级缓存的存在及特性。

1.2 方案

  1. 用同一个Session查询同一条数据2次,如果只查询一次数据库,则验证了一级缓存的存在。
  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完整代码如下:

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.1 问题

掌握一级缓存管理的2种方式:

  1. 使用evict方法,从一级缓存中移除一个对象。
  2. 使用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();
	}
	
#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

2.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.1 问题

设计出案例,验证持久态对象的特性:

  1. 持久态对象存在于一级缓存中。
  2. 持久态对象可以自动更新至数据库。
  3. 持久态对象自动更新数据库的时机是session.flush()。

3.2 方案

设计3个案例,分别验证持久态对象的3个特性:

  1. 新增一条数据,在新增后该数据对象为持久态的,然后根据对象的ID再次查询数据。执行时如果控制台不重新输出SQL则验证了持久态对象存在于一级缓存。
  2. 新增一条数据,在新增后该对象为持久态的,然后修改这个对象的任意属性值,并提交事务。在执行时,如果发现数据库中的数据是修改后的内容,则验证了持久态对象可以自动更新至数据库。
  3. 查询一条数据,该数据对象为持久态的,然后修改对象的任意属性值,再调用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完整代码如下:

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();
	}
	
}

4 验证延迟加载

4.1 问题

设计案例,验证session.load()方法和query.iterate()方法在查询时是采用延迟加载机制的。

4.2 方案

  1. 验证session.load()的延迟加载,可以先查询某条EMP数据,然后输出一个分割线,在分割线的后面再使用这个对象,输出它的一些属性值。在运行时,如果查询EMP的SQL输出在分割线的后面,则验证了该方法是采用延迟加载机制的。
  2. 验证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 完整代码

本案例的完整代码如下所示:

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();
	}
	
}

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();
#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

5.4 完整代码

以下是本案例的完整代码。

其中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>

6 使用一对多关联映射

6.1 问题

使用一对多关联映射,在查询账务账号时,自动查询出它对应的全部业务账号。

6.2 方案

一对多关联映射开发步骤:

  1. 账务账号与业务账号具有一对多关系,他们的关系字段是service.account_id。
  2. 在账务账号中追加集合属性,用于封装它对应的一组业务账号。
  3. 在账务账号映射关系文件中配置此集合属性。

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>
#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

6.4 完整代码

以下为本案例的完整代码。

其中账务账号实体类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();
	}
	
}