Top

JAVA Hibernate DAY03

  1. 使用多对一关联映射
  2. 关联查询的一些特性
  3. 级联添加/修改
  4. 级联删除
  5. HQL查询,按条件查询
  6. HQL查询,查询一部分字段
  7. HQL查询,分页查询
  8. HQL查询,多表联合查询
  9. Hibernate中的SQL查询
  10. 使用二级缓存
  11. 使用查询缓存

1 使用多对一关联映射

1.1 问题

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

1.2 方案

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

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

1.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:在业务账号实体类中追加属性

在业务账号实体类Service中,追加Account类型的属性,用于封装它对应的唯一账务账号。由于这个属性包含了账务账号的ID,因此accountId属性可以去掉了,实际上这个属性必须去掉,否则会报错。代码如下:

package com.tarena.entity;

import java.sql.Date;

public class Service {

	private Integer id;
#cold_bold//	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;
#cold_bold	// 追加属性,用于封装对应的Account记录
#cold_bold	private Account account;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

#cold_bold//	public Integer getAccountId() {
#cold_bold//		return accountId;
#cold_bold//	}
#cold_bold//
#cold_bold//	public void setAccountId(Integer accountId) {
#cold_bold//		this.accountId = accountId;
#cold_bold//	}
#cold_bold
#cold_bold	public Account getAccount() {
#cold_bold		return account;
#cold_bold	}
#cold_bold
#cold_bold	public void setAccount(Account account) {
#cold_bold		this.account = account;
#cold_bold	}
#cold_bold
	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>
		<!-- 
			由于account属性已经体现了业务账号与账务账号的关系,
			并且account属性可以包含账务账号ID,因此accountId可以去掉,
			实际上这里必须去掉这个属性的配置,否则会报错。
		-->
		<!-- <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"/>
			
		<!-- 配置account属性,采用多对一关系加载相关的account内容 -->
		<many-to-one name="account" column="ACCOUNT_ID"
			class="com.tarena.entity.Account"/>			
	</class>
</hibernate-mapping>

步骤三:创建测试类

在com.tarena.test包下,创建一个测试类TestManyToOne,并且增加一个测试方法。在方法中查询出一条业务账号数据,然后输出这个业务账号的一些属性,同时输出account属性的值。代码如下:

package com.tarena.test;

import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestManyToOne {
	
	@Test
	public void test1() {
		Session session = HibernateUtil.getSession();
		Service service = 
			(Service) session.get(Service.class, 2001);
		System.out.println(service.getOsUserName());
		System.out.println("------------------");
		System.out.println(
				service.getAccount().getId() + " " +
				service.getAccount().getIdcardNo());
	}
	
}

步骤四:测试

执行这个测试方法,控制台输出结果如下图,可以看出在查询SERVICE的同时,Hibernate自动查询出了它对应的ACCOUNT数据,并且这个关联查询是采用延迟加载机制实现的。

图-1

1.4 完整代码

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

其中业务账号实体类完整代码如下:

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;
	// 追加属性,用于封装关的Account记录
	private Account account;

	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 Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

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

}

业务账号映射关系文件代码如下:

<?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>
		<!-- 
			由于account属性已经体现了业务账号与账务账号的关系,
			并且account属性可以包含账务账号ID,因此accountId可以去掉,
			实际上这里必须去掉这个属性的配置,否则会报错。 
		-->
		<!-- <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"/>
			
		<!-- 配置account属性,采用多对一关系加载相关的account内容 -->
		<many-to-one name="account" column="ACCOUNT_ID"
			class="com.tarena.entity.Account"/>			
	</class>
</hibernate-mapping>

测试类代码如下:

package com.tarena.test;

import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestManyToOne {
	
	@Test
	public void test1() {
		Session session = HibernateUtil.getSession();
		Service service = 
			(Service) session.get(Service.class, 2001);
		System.out.println(service.getOsUserName());
		System.out.println("------------------");
		System.out.println(
				service.getAccount().getId() + " " +
				service.getAccount().getIdcardNo());
	}
	
}

2 关联查询的一些特性

2.1 问题

请按照如下的方式使用关联映射:

  1. 不采用延迟加载的方式查询关联属性。
  2. 采用关联查询一次性查出2张表的数据。

2.2 方案

可以按照如下的方式实现上述问题的要求:

  1. 通过lazy=”false”取消延迟加载。
  2. 通过fetch=”join”设置关联查询。

2.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:创建项目

复制项目HibernateDay02,粘贴并修改项目名为HibernateDay03。

步骤二:将一对多关联映射取消延迟加载

修改HibernateDay03项目中的Account.hbm.xml文件,将services属性的配置追加lazy=”false”,代码如下:

<?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属性,采用一对多的关系 -->
#cold_bold		<set name="services" lazy="false">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

执行TestOneToMany中的测试方法,控制台输出结果如下图,可以看出在查询账务账号之后Hibernate立刻查询了它对应的业务账号,已经取消了延迟加载。

图-2

步骤三:将多对一关联映射取消延迟加载

修改Service.hbm.xml,将account属性的配置追加lazy=”false”,代码如下:

<?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>
		<!-- 
			由于account属性已经体现了业务账号与账务账号的关系,
			并且account属性可以包含账务账号ID,因此accountId可以去掉,
			实际上这里必须去掉这个属性的配置,否则会报错。
		-->
		<!-- <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"/>
			
		<!-- 配置account属性,采用多对一关系加载相关的account内容 -->
#cold_bold		<many-to-one name="account" column="ACCOUNT_ID"
#cold_bold			class="com.tarena.entity.Account"
#cold_bold			lazy="false"/>			
	</class>
</hibernate-mapping>

执行TestManyToOne中的测试方法,控制台输出效果如下图,可以看出在查询业务账号之后Hibernate立刻查询了对应的账务账号,取消了延迟加载。

图-3

步骤四:将一对多关联映射设置为join方式抓取数据

修改Account.hbm.xml,将services属性的配置追加fetch=“join”,代码如下:

<?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属性,采用一对多的关系 -->
#cold_bold		<set name="services" lazy="false" fetch="join">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

执行TestOneToMany中的测试方法,控制台输出结果如下图,可以看出查询时采用了join方式查询。

图-4

步骤五:将多对一关联映射设置为join方式抓取数据

修改Service.hbm.xml,将account属性追加fetch=“join”,代码如下:

<?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>
		<!-- 
			由于account属性已经体现了业务账号与账务账号的关系,
			并且account属性可以包含账务账号ID,因此accountId可以去掉,
			实际上这里必须去掉这个属性的配置,否则会报错。
		-->
		<!-- <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"/>
			
		<!-- 配置account属性,采用多对一关系加载相关的account内容 -->
#cold_bold		<many-to-one name="account" column="ACCOUNT_ID"
#cold_bold			class="com.tarena.entity.Account"
#cold_bold			lazy="false" fetch="join"/>			
	</class>
</hibernate-mapping>

执行TestManyToOne中的测试方法,控制台输出结果如下图,可以看出查询时采用了join方式查询。

图-5

2.4 完整代码

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

其中账务账号映射关系文件完整代码如下:

<?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" lazy="false" fetch="join">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

业务账号映射关系文件完整代码如下:

<?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>
		<!-- 
			由于account属性已经体现了业务账号与账务账号的关系,
			并且account属性可以包含账务账号ID,因此accountId可以去掉,
			实际上这里必须去掉这个属性的配置,否则会报错。 
		-->
		<!-- <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"/>
			
		<!-- 配置account属性,采用多对一关系加载相关的account内容 -->
		<many-to-one name="account" column="ACCOUNT_ID"
			class="com.tarena.entity.Account"
			lazy="false" fetch="join"/>			
	</class>
</hibernate-mapping>

3 级联添加/修改

3.1 问题

使用级联添加/修改,在添加/修改账务账号时,自动添加/修改其对应的业务账号。

3.2 方案

通过在映射关系文件中设置cascade=”save-update”,可以支持级联添加/修改。

3.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:在账务账号映射关系文件中设置级联添加/修改

在账务账号映射关系文件Account.hbm.xml中,通过cascade=“save-update”设置支持级联添加/修改,代码如下:

<?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属性,采用一对多的关系 -->
#cold_bold		<set name="services" 
#cold_bold			lazy="false" fetch="join" 
#cold_bold			cascade="save-update">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many 
				class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

步骤二:测试级联添加

在com.tarena.test包下,创建测试类TestCascade,并在类中增加测试级联添加的方法,代码如下:

package com.tarena.test;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.tarena.entity.Account;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestCascade {

	/**
	 * 级联添加
	 */
	@Test
	public void test1() {
		// 模拟要添加的账务账号
		Account a = new Account();
		a.setLoginName("gg");
		a.setLoginPassword("123");
		a.setRealName("gg");
		a.setIdcardNo("120392198410282549");
		a.setStatus("0");
		a.setTelephone("110");

		// 模拟要添加的业务账号
		Service s1 = new Service();
		s1.setAccount(a);
		s1.setOsUserName("gg1");
		s1.setLoginPassword("123");
		s1.setUnixHost("192.168.1.1");
		s1.setCostId(5);
		s1.setStatus("0");

		Service s2 = new Service();
		s2.setAccount(a);
		s2.setOsUserName("gg2");
		s2.setLoginPassword("123");
		s2.setUnixHost("192.168.1.2");
		s2.setCostId(5);
		s2.setStatus("0");

		// 将业务账号与账务账号关联
		a.setServices(new HashSet<Service>());
		a.getServices().add(s1);
		a.getServices().add(s2);

		Session session = HibernateUtil.getSession();
		Transaction ts = session.beginTransaction();
		try {
			session.save(a);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}

	}
	
}

执行test1()方法,控制台输出结果如下图,可以看出在新增账务账号之后,Hibernate自动新增了账务账号对应的业务账号数据,这就是级联添加所起到的作用。

图-6

此时,查询账务账号表,数据如下图,其中id=380的行就是刚刚添加的行。

图-7

再查询业务账号表,数据如下图,其中account_id=380的行就是级联添加的行。

图-8

步骤三:测试级联修改

在TestCascade测试类中,增加测试级联修改的方法,代码如下:

package com.tarena.test;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.tarena.entity.Account;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestCascade {

	/**
	 * 级联添加
	 */
	@Test
	public void test1() {
		// 模拟要添加的账务账号
		Account a = new Account();
		a.setLoginName("gg");
		a.setLoginPassword("123");
		a.setRealName("gg");
		a.setIdcardNo("120392198410282549");
		a.setStatus("0");
		a.setTelephone("110");

		// 模拟要添加的业务账号
		Service s1 = new Service();
		s1.setAccount(a);
		s1.setOsUserName("gg1");
		s1.setLoginPassword("123");
		s1.setUnixHost("192.168.1.1");
		s1.setCostId(5);
		s1.setStatus("0");

		Service s2 = new Service();
		s2.setAccount(a);
		s2.setOsUserName("gg2");
		s2.setLoginPassword("123");
		s2.setUnixHost("192.168.1.2");
		s2.setCostId(5);
		s2.setStatus("0");

		// 将业务账号与账务账号关联
		a.setServices(new HashSet<Service>());
		a.getServices().add(s1);
		a.getServices().add(s2);

		Session session = HibernateUtil.getSession();
		Transaction ts = session.beginTransaction();
		try {
			session.save(a);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}

	}

#cold_bold	/**
#cold_bold	 * 级联修改
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test2() {
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		// 查询出要修改的账务账号
#cold_bold		Account account = 
#cold_bold			(Account) session.get(Account.class, 380);
#cold_bold		// 模拟对账务账号的修改
#cold_bold		account.setLoginName("pp");
#cold_bold		Set<Service> services = account.getServices();
#cold_bold		for (Service service : services) {
#cold_bold			// 模拟对业务账号的修改
#cold_bold			service.setLoginPassword("pp");
#cold_bold		}
#cold_bold		Transaction ts = session.beginTransaction();
#cold_bold		try {
#cold_bold			session.update(account);
#cold_bold			ts.commit();
#cold_bold		} catch (HibernateException e) {
#cold_bold			e.printStackTrace();
#cold_bold			ts.rollback();
#cold_bold		} finally {
#cold_bold			session.close();
#cold_bold		}
#cold_bold	}
	
}

执行test2()方法后,控制台输出的结果如下图,可以看出在修改完账务账号之后,Hibernate自动修改了它对应的业务账号数据。

图-9

此时,查询账务账号表,id=380的行已经发生了改变。

图-10

再查询业务账号表,account_id=380的行也发生了改变。

图-11

3.4 完整代码

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

其中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" 
			lazy="false" fetch="join" 
			cascade="save-update">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many 
				class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

TestCascade完整代码如下:

package com.tarena.test;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.tarena.entity.Account;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestCascade {

	/**
	 * 级联添加
	 */
	@Test
	public void test1() {
		// 模拟要添加的账务账号
		Account a = new Account();
		a.setLoginName("gg");
		a.setLoginPassword("123");
		a.setRealName("gg");
		a.setIdcardNo("120392198410282549");
		a.setStatus("0");
		a.setTelephone("110");

		// 模拟要添加的业务账号
		Service s1 = new Service();
		s1.setAccount(a);
		s1.setOsUserName("gg1");
		s1.setLoginPassword("123");
		s1.setUnixHost("192.168.1.1");
		s1.setCostId(5);
		s1.setStatus("0");

		Service s2 = new Service();
		s2.setAccount(a);
		s2.setOsUserName("gg2");
		s2.setLoginPassword("123");
		s2.setUnixHost("192.168.1.2");
		s2.setCostId(5);
		s2.setStatus("0");

		// 将业务账号与账务账号关联
		a.setServices(new HashSet<Service>());
		a.getServices().add(s1);
		a.getServices().add(s2);

		Session session = HibernateUtil.getSession();
		Transaction ts = session.beginTransaction();
		try {
			session.save(a);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}

	}

	/**
	 * 级联修改
	 */
	@Test
	public void test2() {
		Session session = HibernateUtil.getSession();
		// 查询出要修改的账务账号
		Account account = 
			(Account) session.get(Account.class, 380);
		// 模拟对账务账号的修改
		account.setLoginName("pp");
		Set<Service> services = account.getServices();
		for (Service service : services) {
			// 模拟对业务账号的修改
			service.setLoginPassword("pp");
		}
		Transaction ts = session.beginTransaction();
		try {
			session.update(account);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}
	}
	
}

4 级联删除

4.1 问题

使用级联删除,在删除账务账号时,自动删除其对应的业务账号。

4.2 方案

通过在映射关系文件中设置cascade=“delete”,可以支持级联删除,如果想全面支持级联添加、修改和删除,可以设置cascade=“all”。

4.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:在账务账号映射关系文件中设置级联删除

在账务账号映射关系文件Account.hbm.xml中,通过cascade=“all”设置支持级联删除,代码如下:

<?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属性,采用一对多的关系 -->
#cold_bold		<set name="services" 
#cold_bold			lazy="false" fetch="join" 
#cold_bold			cascade="all">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many 
				class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

步骤二:测试级联删除

在TestCascade测试类中,增加测试级联删除的方法,代码如下:

package com.tarena.test;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.tarena.entity.Account;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestCascade {

	/**
	 * 级联添加
	 */
	@Test
	public void test1() {
		// 模拟要添加的账务账号
		Account a = new Account();
		a.setLoginName("gg");
		a.setLoginPassword("123");
		a.setRealName("gg");
		a.setIdcardNo("120392198410282549");
		a.setStatus("0");
		a.setTelephone("110");

		// 模拟要添加的业务账号
		Service s1 = new Service();
		s1.setAccount(a);
		s1.setOsUserName("gg1");
		s1.setLoginPassword("123");
		s1.setUnixHost("192.168.1.1");
		s1.setCostId(5);
		s1.setStatus("0");

		Service s2 = new Service();
		s2.setAccount(a);
		s2.setOsUserName("gg2");
		s2.setLoginPassword("123");
		s2.setUnixHost("192.168.1.2");
		s2.setCostId(5);
		s2.setStatus("0");

		// 将业务账号与账务账号关联
		a.setServices(new HashSet<Service>());
		a.getServices().add(s1);
		a.getServices().add(s2);

		Session session = HibernateUtil.getSession();
		Transaction ts = session.beginTransaction();
		try {
			session.save(a);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}

	}

	/**
	 * 级联修改
	 */
	@Test
	public void test2() {
		Session session = HibernateUtil.getSession();
		// 查询出要修改的账务账号
		Account account = 
			(Account) session.get(Account.class, 380);
		// 模拟对账务账号的修改
		account.setLoginName("pp");
		Set<Service> services = account.getServices();
		for (Service service : services) {
			// 模拟对业务账号的修改
			service.setLoginPassword("pp");
		}
		Transaction ts = session.beginTransaction();
		try {
			session.update(account);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}
	}

#cold_bold	/**
#cold_bold	 * 级联删除
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test3() {
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		Account account = (Account) session.get(Account.class, 380);
#cold_bold		Transaction ts = session.beginTransaction();
#cold_bold		try {
#cold_bold			session.delete(account);
#cold_bold			ts.commit();
#cold_bold		} catch (HibernateException e) {
#cold_bold			e.printStackTrace();
#cold_bold			ts.rollback();
#cold_bold		} finally {
#cold_bold			session.close();
#cold_bold		}
#cold_bold	}
	
}

步骤三:测试

执行test3()方法,控制台输出结果如下图,可以看出程序在运行时报错了:

图-12

步骤四:排错

上述错误是执行第二个SQL时产生的,这个SQL的目的是维护关系字段,将其置为null,而这个外键字段存在非空约束,因此报错。类似的事情在级联添加时也看到过,参考图-6,不同的是级联添加时要将关联字段设置为新生成的账务账号ID。然而,在级联新增或删除时业务账号时,业务账号本身已经维护好了关联字段,因此这个额外的操作是多余的,可以去掉,我们可以在关联属性上通过inverse=“true“去掉这个行为,代码如下:

<?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属性,采用一对多的关系 -->
#cold_bold		<set name="services" 
#cold_bold			lazy="false" fetch="join" 
#cold_bold			cascade="all" inverse="true">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many 
				class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

步骤五:测试

再次执行test3()方法,控制台输出结果如下图,可以看出本次删除成功了,不但删除了账务账号数据,在此之前还删除了它所对应的所有业务账号。

图-13

4.4 完整代码

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

其中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" 
			lazy="false" fetch="join" 
			cascade="all" inverse="true">
			<!-- 用于指定关联条件,写关联条件的外键字段 -->
			<key column="ACCOUNT_ID"/>
			<!-- 用于指定采用哪种关系,加载哪方数据 -->
			<one-to-many 
				class="com.tarena.entity.Service"/>
		</set>
	</class>
</hibernate-mapping>

TestCascade完整代码如下:

package com.tarena.test;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.tarena.entity.Account;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestCascade {

	/**
	 * 级联添加
	 */
	@Test
	public void test1() {
		// 模拟要添加的账务账号
		Account a = new Account();
		a.setLoginName("gg");
		a.setLoginPassword("123");
		a.setRealName("gg");
		a.setIdcardNo("120392198410282549");
		a.setStatus("0");
		a.setTelephone("110");

		// 模拟要添加的业务账号
		Service s1 = new Service();
		s1.setAccount(a);
		s1.setOsUserName("gg1");
		s1.setLoginPassword("123");
		s1.setUnixHost("192.168.1.1");
		s1.setCostId(5);
		s1.setStatus("0");

		Service s2 = new Service();
		s2.setAccount(a);
		s2.setOsUserName("gg2");
		s2.setLoginPassword("123");
		s2.setUnixHost("192.168.1.2");
		s2.setCostId(5);
		s2.setStatus("0");

		// 将业务账号与账务账号关联
		a.setServices(new HashSet<Service>());
		a.getServices().add(s1);
		a.getServices().add(s2);

		Session session = HibernateUtil.getSession();
		Transaction ts = session.beginTransaction();
		try {
			session.save(a);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}

	}

	/**
	 * 级联修改
	 */
	@Test
	public void test2() {
		Session session = HibernateUtil.getSession();
		// 查询出要修改的账务账号
		Account account = 
			(Account) session.get(Account.class, 380);
		// 模拟对账务账号的修改
		account.setLoginName("pp");
		Set<Service> services = account.getServices();
		for (Service service : services) {
			// 模拟对业务账号的修改
			service.setLoginPassword("pp");
		}
		Transaction ts = session.beginTransaction();
		try {
			session.update(account);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}
	}

	/**
	 * 级联删除
	 */
	@Test
	public void test3() {
		Session session = HibernateUtil.getSession();
		Account account = (Account) session.get(Account.class, 380);
		Transaction ts = session.beginTransaction();
		try {
			session.delete(account);
			ts.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			ts.rollback();
		} finally {
			session.close();
		}
	}
	
}

5 HQL查询,按条件查询

5.1 问题

使用带条件的HQL 查询业务账号数据。

5.2 方案

在HQL中拼入条件,如name=?,然后在查询之前使用query给参数赋值。

5.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:编写按条件查询方法

在com.tarena.test包下创建测试类TestHQL,并在类中增加按条件查询的测试方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	/**
	 * 按条件查询
	 */
	@Test
	public void test1() {
		String hql = "from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Service> services = query.list();
		for(Service service : services){
			System.out.println(service.getId()
					+ " " + service.getUnixHost()
					+ " " + service.getOsUserName());
		}
		session.close();
	}
	
}

步骤二:测试

执行test1()方法,控制台输出效果如下图,可以看到按条件查询出的结果:

图-14

5.4 完整代码

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

其中TestHQL完整代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	/**
	 * 按条件查询
	 */
	@Test
	public void test1() {
		String hql = "from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Service> services = query.list();
		for(Service service : services){
			System.out.println(service.getId()
					+ " " + service.getUnixHost()
					+ " " + service.getOsUserName());
		}
		session.close();
	}
	
}

6 HQL查询,查询一部分字段

6.1 问题

使用HQL查询,要求只查询一部分字段。

6.2 方案

可以通过select子句明确指定要返回的字段,注意HQL中select子句中写的是属性,而不是表中的字段。

6.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:编写查询一部分字段方法

在TestHQL中增加查询一部分字段的方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
// 其他查询方法略
#cold_bold	/**
#cold_bold	 * 查询一部分字段
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test2() {
#cold_bold		String hql = "select id,unixHost,osUserName " +
#cold_bold				"from Service where unixHost=?";
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		Query query = session.createQuery(hql);
#cold_bold		query.setString(0, "192.168.0.20");
#cold_bold		List<Object[]> services = query.list();
#cold_bold		for(Object[] service : services) {
#cold_bold			System.out.println(service[0]
#cold_bold					+ " " + service[1]
#cold_bold					+ " " + service[2]);
#cold_bold		}
#cold_bold		session.close();
#cold_bold	}
	
}

步骤二:测试

执行test2(),控制台输出结果如下图,可以看到查询出来的这些字段的值。

图-15

6.4 完整代码

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

其中TestHQL完整代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	/**
	 * 按条件查询
	 */
	@Test
	public void test1() {
		String hql = "from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Service> services = query.list();
		for(Service service : services){
			System.out.println(service.getId()
					+ " " + service.getUnixHost()
					+ " " + service.getOsUserName());
		}
		session.close();
	}
	
	/**
	 * 查询一部分字段
	 */
	@Test
	public void test2() {
		String hql = "select id,unixHost,osUserName " +
				"from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Object[]> services = query.list();
		for(Object[] service : services) {
			System.out.println(service[0]
					+ " " + service[1]
					+ " " + service[2]);
		}
		session.close();
	}
	
}

7 HQL查询,分页查询

7.1 问题

按照每页显示3条数据,查询第1页的条件,查询出业务账号表中满足条件的记录,并且查询出总页数。

7.2 方案

  1. 分页查询时,在执行查询之前可以通过query对象设置分页参数值,query.setFirstResult(起点),以及query.setMaxResults(页容量)。
  2. 查询总页数,只需要通过hql查询出总行数,再根据总行数计算总页数即可,而查询总行数的hql格式为select count(*) from 对象。

7.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:编写分页查询的方法

在TestHQL中,增加分页查询的方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	// 其他查询方法略
	
#cold_bold	/**
#cold_bold	 * 分页查询
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test3() {
#cold_bold		int page = 1;
#cold_bold		int pageSize = 3;
#cold_bold
#cold_bold		String hql = "from Service order by id";
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		Query query = session.createQuery(hql);
#cold_bold		// 追加分页参数设置
#cold_bold		int from = (page - 1) * pageSize;
#cold_bold		query.setFirstResult(from);// 设置起点,从0开始
#cold_bold		query.setMaxResults(pageSize);// 设置页容量
#cold_bold		List<Service> services = query.list();
#cold_bold		for (Service service : services) {
#cold_bold			System.out.println(
#cold_bold				service.getId() + " " 
#cold_bold				+ service.getUnixHost() + " " 
#cold_bold				+ service.getOsUserName());
#cold_bold		}
#cold_bold		session.close();
#cold_bold	}
	
}

步骤二:测试

执行test3(),控制台输出结果如下图,输出了第1页的3条数据。

图-16

步骤三:编写查询总页数的方法

在TestHQL中,增加查询总页数的方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
// 其他查询方法略
#cold_bold	/**
#cold_bold	 * 查询总页数
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test4() {
#cold_bold		int pageSize=3;
#cold_bold		String hql = "select count(*) from Service";
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		Query query = session.createQuery(hql);
#cold_bold		int rows = Integer.parseInt(query.uniqueResult().toString());
#cold_bold		int totalPages = 0;
#cold_bold		if(rows%pageSize == 0) {
#cold_bold			totalPages = rows/pageSize;
#cold_bold		} else {
#cold_bold			totalPages = rows/pageSize+1;
#cold_bold		}
#cold_bold		System.out.println(totalPages);
#cold_bold		session.close();
#cold_bold	}
	
}

步骤四:测试

执行test4(),控制台输出结果如下图,输出了总页数。

图-17

7.4 完整代码

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

其中TestHQL完整代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	/**
	 * 按条件查询
	 */
	@Test
	public void test1() {
		String hql = "from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Service> services = query.list();
		for(Service service : services){
			System.out.println(service.getId()
					+ " " + service.getUnixHost()
					+ " " + service.getOsUserName());
		}
		session.close();
	}
	
	/**
	 * 查询一部分字段
	 */
	@Test
	public void test2() {
		String hql = "select id,unixHost,osUserName " +
				"from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Object[]> services = query.list();
		for(Object[] service : services) {
			System.out.println(service[0]
					+ " " + service[1]
					+ " " + service[2]);
		}
		session.close();
	}
	
	/**
	 * 分页查询
	 */
	@Test
	public void test3() {
		int page = 1;
		int pageSize = 3;

		String hql = "from Service order by id";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		// 追加分页参数设置
		int from = (page - 1) * pageSize;
		query.setFirstResult(from);// 设置起点,从0开始
		query.setMaxResults(pageSize);// 设置页容量
		List<Service> services = query.list();
		for (Service service : services) {
			System.out.println(
				service.getId() + " " 
				+ service.getUnixHost() + " " 
				+ service.getOsUserName());
		}
		session.close();
	}

	/**
	 * 查询总页数
	 */
	@Test
	public void test4() {
		int pageSize=3;
		String hql = "select count(*) from Service";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		int rows = Integer.parseInt(query.uniqueResult().toString());
		int totalPages = 0;
		if(rows%pageSize == 0) {
			totalPages = rows/pageSize;
		} else {
			totalPages = rows/pageSize+1;
		}
		System.out.println(totalPages);
		session.close();
	}
	
}

8 HQL查询,多表联合查询

8.1 问题

使用HQL,联合查询出业务账号与账务账号的数据。

8.2 方案

HQL中多表联合查询的方式有3种,分别是

  1. 对象方式关联
  2. join方式关联
  3. select子句关联

使用这三种方式来做业务账号与账务账号的联合查询。

8.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:编写对象方式关联的方法

在TestHQL中,增加对象方式关联的查询方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	// 其他查询方法略
	
#cold_bold	/**
#cold_bold	 * 多表联合查询-对象方式关联
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test5() {
#cold_bold		String hql = "select " +
#cold_bold				"s.id," +
#cold_bold				"s.osUserName," +
#cold_bold				"s.unixHost, " +
#cold_bold				"a.id,a.realName," +
#cold_bold				"a.idcardNo " +
#cold_bold				"from Service s,Account a " +
#cold_bold				"where s.account.id=a.id ";
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		Query query = session.createQuery(hql);
#cold_bold		List<Object[]> list = query.list();
#cold_bold		for (Object[] objs : list) {
#cold_bold			System.out.println(
#cold_bold					objs[0] + " " +
#cold_bold					objs[1] + " " +
#cold_bold					objs[2] + " " +
#cold_bold					objs[3] + " " +
#cold_bold					objs[4] + " " +
#cold_bold					objs[5]);
#cold_bold		}
#cold_bold		session.close();
#cold_bold	}
	
}

步骤二:测试

执行test5(),控制台输出结果如下图,可以查询出关联的数据。

图-18

步骤三:编写join方式关联的方法

在TestHQL中,增加join方式关联的查询方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	// 其他查询方法略
	
#cold_bold	/**
#cold_bold	 * 多表联合查询-join方式关联
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test6() {
#cold_bold		String hql = "select " +
#cold_bold				"s.id,s.osUserName," +
#cold_bold				"s.unixHost, " +
#cold_bold				"a.id," +
#cold_bold				"a.realName," +
#cold_bold				"a.idcardNo " +
#cold_bold				"from Service s join s.account a ";
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		Query query = session.createQuery(hql);
#cold_bold		List<Object[]> list = query.list();
#cold_bold		for (Object[] objs : list) {
#cold_bold			System.out.println(
#cold_bold					objs[0] + " " +
#cold_bold					objs[1] + " " +
#cold_bold					objs[2] + " " +
#cold_bold					objs[3] + " " +
#cold_bold					objs[4] + " " +
#cold_bold					objs[5]);
#cold_bold		}
#cold_bold		session.close();
#cold_bold	}
	
}

步骤四:测试

执行test6(),控制台输出结果如下图,可以查询出关联的数据。

图-19

步骤五:编写select子句关联的方法

在TestHQL中,增加select子句关联的查询方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
// 其他查询方法略	
#cold_bold	/**
#cold_bold	 * 多表联合查询-select子句关联
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test7() {
#cold_bold		String hql = "select " +
#cold_bold				"id," +
#cold_bold				"osUserName," +
#cold_bold				"unixHost, " +
#cold_bold				"account.id," +
#cold_bold				"account.realName," +
#cold_bold				"account.idcardNo " +
#cold_bold				"from Service ";
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		Query query = session.createQuery(hql);
#cold_bold		List<Object[]> list = query.list();
#cold_bold		for (Object[] objs : list) {
#cold_bold			System.out.println(
#cold_bold					objs[0] + " " +
#cold_bold					objs[1] + " " +
#cold_bold					objs[2] + " " +
#cold_bold					objs[3] + " " +
#cold_bold					objs[4] + " " +
#cold_bold					objs[5]);
#cold_bold		}
#cold_bold		session.close();
#cold_bold	}
	
}

步骤六:测试

执行test7(),控制台输出结果如下,可以查询出关联的数据。

图-20

8.4 完整代码

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

其中TestHQL完整代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestHQL {
	
	/**
	 * 按条件查询
	 */
	@Test
	public void test1() {
		String hql = "from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Service> services = query.list();
		for(Service service : services){
			System.out.println(service.getId()
					+ " " + service.getUnixHost()
					+ " " + service.getOsUserName());
		}
		session.close();
	}
	
	/**
	 * 查询一部分字段
	 */
	@Test
	public void test2() {
		String hql = "select id,unixHost,osUserName " +
				"from Service where unixHost=?";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		query.setString(0, "192.168.0.20");
		List<Object[]> services = query.list();
		for(Object[] service : services) {
			System.out.println(service[0]
					+ " " + service[1]
					+ " " + service[2]);
		}
		session.close();
	}
	
	/**
	 * 分页查询
	 */
	@Test
	public void test3() {
		int page = 1;
		int pageSize = 3;

		String hql = "from Service order by id";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		// 追加分页参数设置
		int from = (page - 1) * pageSize;
		query.setFirstResult(from);// 设置起点,从0开始
		query.setMaxResults(pageSize);// 设置页容量
		List<Service> services = query.list();
		for (Service service : services) {
			System.out.println(
				service.getId() + " " 
				+ service.getUnixHost() + " " 
				+ service.getOsUserName());
		}
		session.close();
	}

	/**
	 * 查询总页数
	 */
	@Test
	public void test4() {
		int pageSize=3;
		String hql = "select count(*) from Service";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		int rows = Integer.parseInt(query.uniqueResult().toString());
		int totalPages = 0;
		if(rows%pageSize == 0) {
			totalPages = rows/pageSize;
		} else {
			totalPages = rows/pageSize+1;
		}
		System.out.println(totalPages);
		session.close();
	}
	
	/**
	 * 多表联合查询-对象方式关联
	 */
	@Test
	public void test5() {
		String hql = "select " +
				"s.id," +
				"s.osUserName," +
				"s.unixHost, " +
				"a.id,a.realName," +
				"a.idcardNo " +
				"from Service s,Account a " +
				"where s.account.id=a.id ";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		List<Object[]> list = query.list();
		for (Object[] objs : list) {
			System.out.println(
					objs[0] + " " +
					objs[1] + " " +
					objs[2] + " " +
					objs[3] + " " +
					objs[4] + " " +
					objs[5]);
		}
		session.close();
	}
	
	/**
	 * 多表联合查询-join方式关联
	 */
	@Test
	public void test6() {
		String hql = "select " +
				"s.id,s.osUserName," +
				"s.unixHost, " +
				"a.id," +
				"a.realName," +
				"a.idcardNo " +
				"from Service s join s.account a ";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		List<Object[]> list = query.list();
		for (Object[] objs : list) {
			System.out.println(
					objs[0] + " " +
					objs[1] + " " +
					objs[2] + " " +
					objs[3] + " " +
					objs[4] + " " +
					objs[5]);
		}
		session.close();
	}
	
	/**
	 * 多表联合查询-select子句关联
	 */
	@Test
	public void test7() {
		String hql = "select " +
				"id," +
				"osUserName," +
				"unixHost, " +
				"account.id," +
				"account.realName," +
				"account.idcardNo " +
				"from Service ";
		Session session = HibernateUtil.getSession();
		Query query = session.createQuery(hql);
		List<Object[]> list = query.list();
		for (Object[] objs : list) {
			System.out.println(
					objs[0] + " " +
					objs[1] + " " +
					objs[2] + " " +
					objs[3] + " " +
					objs[4] + " " +
					objs[5]);
		}
		session.close();
	}
	
}

9 Hibernate中的SQL查询

9.1 问题

在Hibernate中直接使用SQL查询业务账号数据。

9.2 方案

Hibernate中可以通过SQLQuery对象来执行原始的SQL。

9.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:编写使用SQL查询的方法

在com.tarena.test包下,创建测试类TestOtherQuery,并在类中增加使用SQL查询的方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestOtherQuery {
	
	/**
	 * 使用SQL查询
	 */
	@Test
	public void test1() {
		String sql = "select * " +
				"from SERVICE where unix_host=?";
		Session session = HibernateUtil.getSession();
		SQLQuery query = session.createSQLQuery(sql);
		query.setString(0, "192.168.0.20");
		query.addEntity(Service.class);
		//采用Service类型封装一条数据
		List<Service> list = query.list();
		for(Service service : list){
			System.out.println(
				service.getId() + " " +
				service.getOsUserName() + " " +
				service.getUnixHost() + " " 
			);
		}
		session.close();
	}
	
}

步骤二:测试

执行test1(),控制台输出结果如下图,是直接执行SQL所查询到的内容:

图-21

9.4 完整代码

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

其中TestOtherQuery完整代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Service;
import com.tarena.util.HibernateUtil;

public class TestOtherQuery {
	
	/**
	 * 使用SQL查询
	 */
	@Test
	public void test1() {
		String sql = "select * " +
				"from SERVICE where unix_host=?";
		Session session = HibernateUtil.getSession();
		SQLQuery query = session.createSQLQuery(sql);
		query.setString(0, "192.168.0.20");
		query.addEntity(Service.class);
		//采用Service类型封装一条数据
		List<Service> list = query.list();
		for(Service service : list){
			System.out.println(
				service.getId() + " " +
				service.getOsUserName() + " " +
				service.getUnixHost() + " " 
			);
		}
		session.close();
	}
	
}

10 使用二级缓存

10.1 问题

在查询员工时,使用二级缓存,使得不同的session可以共享这些员工数据。

10.2 方案

使用ehcache缓存组件来支持二级缓存,二级缓存的使用步骤为

  1. 导入二级缓存驱动包
  2. 引入二级缓存配置文件
  3. 在hibernate.cfg.xml中开启二级缓存并指定二级缓存驱动类
  4. 在映射关系文件中设置缓存策略
  5. 执行查询

10.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:导入二级缓存驱动包

导入二级缓存驱动包ehcache-1.2.3.jar,导入后项目中包结构如下图:

图-22

步骤二:引入二级缓存配置文件

引入二级缓存配置文件ehcache.xml,并配置缓存参数,代码如下:

<ehcache>

<!-- 
	缓存到硬盘时的缓存路径,java.io.tmpdir表示
	系统默认缓存路径。
     -->
<diskStore path="java.io.tmpdir"/>

<!-- 
	默认缓存配置。
	maxElementsInMemory:
		二级缓存可容纳最大对象数。
	eternal:
		是否保持二级缓存中对象不变。
	timeToIdleSeconds:
		允许对象空闲的时间,即对象最后一次访问起,超过该时间即失效。
	timeToLiveSeconds:
		允许对象存活的时间,即对象创建起,超过该时间即失效。
	overflowToDisk:
		内存不足时,是否允许使用硬盘缓存,写入路径参见diskStore。
     -->
<defaultCache
        maxElementsInMemory="300"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="300"
        overflowToDisk="true"
        />

	<!-- 自定义缓存配置 -->
<cache name="myCache"
        maxElementsInMemory="2000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

</ehcache>

步骤三:开启二级缓存、指定二级缓存驱动类

在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>
		
#cold_bold		<!-- 开启二级缓存 -->
#cold_bold		<property name="hibernate.cache.use_second_level_cache">
#cold_bold			true
#cold_bold		</property>
#cold_bold		<!-- 指定所采用的二级缓存驱动类 -->
#cold_bold		<property name="hibernate.cache.provider_class">
#cold_bold			org.hibernate.cache.EhCacheProvider
#cold_bold		</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>

步骤四:设置缓存策略

在Emp.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.Emp" table="emp">
#cold_bold		<!-- 
#cold_bold			开启二级缓存,指定二级缓存策略。
#cold_bold			可以用region属性指定自定义的缓存设置。
#cold_bold		-->
#cold_bold		<cache usage="read-only" />
		
		<!-- 配置主键属性和字段的关系 -->
		<id name="id" type="integer" column="id">
			<!-- 用来指明主键的生成方式 -->
			<generator class="sequence">
				<!-- 指定用于生成主键的sequence -->
				<param name="sequence">emp_seq</param>
			</generator>
		</id>
		
		<!-- 配置实体类中属性与表中字段的关系 -->
		<property name="name" 
			type="string" column="name"/>
		<property name="age" 
			type="integer" column="age"/>
		<property name="salary" 
			type="double" column="salary"/>
		<property name="birthday" 
			type="date" column="birthday"/>
		<property name="lastLoginTime" 
			type="timestamp" column="last_login_time"/>
		<property name="marry"
			type="yes_no" column="marry"/>
	</class>
</hibernate-mapping>

步骤五:编写查询方法,测试二级缓存

在com.tarena.test包下,创建一个测试类TestSecondCache,增加一个测试二级缓存的方法,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Emp;
import com.tarena.util.HibernateUtil;

public class TestSecondCache {

	/**
	 * 二级缓存
	 */
	@Test
	public void test1() {
		Session session1 = HibernateUtil.getSession();
		Emp e1 = (Emp) session1.get(Emp.class, 321);
		System.out.println(e1.getName());
		
		System.out.println("-----------------");
		
		Session session2 = HibernateUtil.getSession();
		Emp e2 = (Emp) session2.get(Emp.class, 321);
		System.out.println(e2.getName());
		
		session1.close();
		session2.close();
	}
	
}

步骤六:测试

执行test1(),控制台输出结果如下图,可见第二次查询没有访问数据库,是二级缓存存在的缘故:

图-23

步骤七:管理二级缓存

二级缓存是SessionFactory级缓存,由它负责管理,因此需要获取到SessionFactory才能管理二级缓存,我们先在HibernateUtil中增加获取SessionFactory的方法,代码如下:

package com.tarena.util;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

	private static SessionFactory sessionFactory;
	
	static {
		// 加载Hibernate主配置文件
		Configuration conf = new Configuration();
		conf.configure("/hibernate.cfg.xml");
		sessionFactory = conf.buildSessionFactory();
	}

	/**
	 * 创建session
	 */
	public static Session getSession() {
		return sessionFactory.openSession();
	}
	
#cold_bold	/**
#cold_bold	 * 返回SessionFactory
#cold_bold	 */
#cold_bold	public static SessionFactory getSessionFactory() {
#cold_bold		return sessionFactory;
#cold_bold	}
	
	public static void main(String[] args) {
		System.out.println(getSession());
	}

}

修改test1()方法,在第二次查询前清理二级缓存,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Emp;
import com.tarena.util.HibernateUtil;

public class TestSecondCache {

	/**
	 * 二级缓存
	 */
	@Test
	public void test1() {
		Session session1 = HibernateUtil.getSession();
		Emp e1 = (Emp) session1.get(Emp.class, 321);
		System.out.println(e1.getName());
		
		System.out.println("-----------------");
#cold_bold		HibernateUtil.getSessionFactory().evict(Emp.class);
		
		Session session2 = HibernateUtil.getSession();
		Emp e2 = (Emp) session2.get(Emp.class, 321);
		System.out.println(e2.getName());
		
		session1.close();
		session2.close();
	}
	
}

再次执行test1(),控制台输出结果如下图,由于清理了二级缓存,因此第二次查询时访问了数据库。

图-24

10.4 完整代码

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

其中缓存配置文件ehcache.xml完整代码如下:

<ehcache>

<!-- 
	缓存到硬盘时的缓存路径,java.io.tmpdir表示
	系统默认缓存路径。
     -->
<diskStore path="java.io.tmpdir"/>

<!-- 
	默认缓存配置。
	maxElementsInMemory:
		二级缓存可容纳最大对象数。
	eternal:
		是否保持二级缓存中对象不变。
	timeToIdleSeconds:
		允许对象空闲的时间,即对象最后一次访问起,超过该时间即失效。
	timeToLiveSeconds:
		允许对象存活的时间,即对象创建起,超过该时间即失效。
	overflowToDisk:
		内存不足时,是否允许使用硬盘缓存,写入路径参见diskStore。
     -->
<defaultCache
        maxElementsInMemory="300"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="300"
        overflowToDisk="true"
        />

	<!-- 自定义缓存配置 -->
<cache name="myCache"
        maxElementsInMemory="2000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

</ehcache>

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>
		
		<!-- 开启二级缓存 -->
		<property name="hibernate.cache.use_second_level_cache">
			true
		</property>
		<!-- 指定所采用的二级缓存驱动类 -->
		<property name="hibernate.cache.provider_class">
			org.hibernate.cache.EhCacheProvider
		</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>

emp.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.Emp" table="emp">
		<!-- 
			开启二级缓存,指定二级缓存策略。
			可以用region属性指定自定义的缓存设置。
		-->
		<cache usage="read-only" />
		
		<!-- 配置主键属性和字段的关系 -->
		<id name="id" type="integer" column="id">
			<!-- 用来指明主键的生成方式 -->
			<generator class="sequence">
				<!-- 指定用于生成主键的sequence -->
				<param name="sequence">emp_seq</param>
			</generator>
		</id>
		
		<!-- 配置实体类中属性与表中字段的关系 -->
		<property name="name" 
			type="string" column="name"/>
		<property name="age" 
			type="integer" column="age"/>
		<property name="salary" 
			type="double" column="salary"/>
		<property name="birthday" 
			type="date" column="birthday"/>
		<property name="lastLoginTime" 
			type="timestamp" column="last_login_time"/>
		<property name="marry"
			type="yes_no" column="marry"/>
	</class>
</hibernate-mapping>

TestSecondCache完整代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Emp;
import com.tarena.util.HibernateUtil;

public class TestSecondCache {

	/**
	 * 二级缓存
	 */
	@Test
	public void test1() {
		Session session1 = HibernateUtil.getSession();
		Emp e1 = (Emp) session1.get(Emp.class, 321);
		System.out.println(e1.getName());
		
		System.out.println("-----------------");
		HibernateUtil.getSessionFactory().evict(Emp.class);
		
		Session session2 = HibernateUtil.getSession();
		Emp e2 = (Emp) session2.get(Emp.class, 321);
		System.out.println(e2.getName());
		
		session1.close();
		session2.close();
	}
	
}

11 使用查询缓存

11.1 问题

在查询多条员工数据时,使用查询缓存,缓存查询的HQL,使得再次使用同样HQL查询时不必重新访问数据库。

11.2 方案

查询缓存的使用步骤是

  1. 开启二级缓存
  2. 在hibernate.cfg.xml中开启查询缓存
  3. 在查询之前,开启查询缓存

11.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:开启二级缓存

开启二级缓存,由于上个案例已经完成,这一步就可以省略。但是要注意,查询缓存是基于二级缓存的,使用查询缓存的前提是开启二级缓存。

步骤二:开启查询缓存

在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>
		
		<!-- 开启二级缓存 -->
		<property name="hibernate.cache.use_second_level_cache">
			true
		</property>
		<!-- 指定所采用的二级缓存驱动 -->
		<property name="hibernate.cache.provider_class">
			org.hibernate.cache.EhCacheProvider
		</property>		
#cold_bold		<!-- 开启查询缓存 -->
#cold_bold		<property name="hibernate.cache.use_query_cache">
#cold_bold			true
#cold_bold		</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>

步骤三:查询前开启查询缓存

在TestSecondCache中,增加测试查询缓存的方法,使用相同的HQL执行2次查询,每次查询前都设置开启查询缓存,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Emp;
import com.tarena.util.HibernateUtil;

public class TestSecondCache {

	/**
	 * 二级缓存
	 */
	@Test
	public void test1() {
		Session session1 = HibernateUtil.getSession();
		Emp e1 = (Emp) session1.get(Emp.class, 321);
		System.out.println(e1.getName());
		
		System.out.println("-----------------");
		HibernateUtil.getSessionFactory().evict(Emp.class);
		
		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	 * 查询缓存
#cold_bold	 */
#cold_bold	@Test
#cold_bold	public void test2() {
#cold_bold		Session session = HibernateUtil.getSession();
#cold_bold		
#cold_bold		String hql = "from Emp";
#cold_bold		Query query = session.createQuery(hql);
#cold_bold		// 开启查询缓存
#cold_bold		query.setCacheable(true);
#cold_bold		List<Emp> emps = query.list();
#cold_bold		for(Emp e : emps) {
#cold_bold			System.out.println(e.getId() + " " + e.getName());
#cold_bold		}
#cold_bold		
#cold_bold		System.out.println("---------------");
#cold_bold		
#cold_bold		hql = "from Emp";
#cold_bold		query = session.createQuery(hql);
#cold_bold		// 开启查询缓存
#cold_bold		query.setCacheable(true);
#cold_bold		emps = query.list();
#cold_bold		for(Emp e : emps) {
#cold_bold			System.out.println(e.getId() + " " + e.getName());
#cold_bold		}
#cold_bold		
#cold_bold		session.close();
#cold_bold	}
	
}

步骤四:测试

执行test2(),控制台输出结果如下图,可见在使用相同HQL查询时,第二次查询不必再次访问数据库。

图-25

步骤五:管理查询缓存

修改test2(),在第二次查询之前清理查询缓存,代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Emp;
import com.tarena.util.HibernateUtil;

public class TestSecondCache {

	/**
	 * 二级缓存
	 */
	@Test
	public void test1() {
		Session session1 = HibernateUtil.getSession();
		Emp e1 = (Emp) session1.get(Emp.class, 321);
		System.out.println(e1.getName());
		
		System.out.println("-----------------");
		HibernateUtil.getSessionFactory().evict(Emp.class);
		
		Session session2 = HibernateUtil.getSession();
		Emp e2 = (Emp) session2.get(Emp.class, 321);
		System.out.println(e2.getName());
		
		session1.close();
		session2.close();
	}
	
	/**
	 * 查询缓存
	 */
	@Test
	public void test2() {
		Session session = HibernateUtil.getSession();
		
		String hql = "from Emp";
		Query query = session.createQuery(hql);
		// 开启查询缓存
		query.setCacheable(true);
		List<Emp> emps = query.list();
		for(Emp e : emps) {
			System.out.println(e.getId() + " " + e.getName());
		}
		
		System.out.println("---------------");
#cold_bold		HibernateUtil.getSessionFactory().evictQueries();
		
		hql = "from Emp";
		query = session.createQuery(hql);
		// 开启查询缓存
		query.setCacheable(true);
		emps = query.list();
		for(Emp e : emps) {
			System.out.println(e.getId() + " " + e.getName());
		}
		
		session.close();
	}
	
}

再次执行test2(),控制台输出结果如下图,可见第二次查询也访问了数据库,主要是清理了查询缓存中数据的缘故。

图-26

11.4 完整代码

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

其中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>
		
		<!-- 开启二级缓存 -->
		<property name="hibernate.cache.use_second_level_cache">
			true
		</property>
		<!-- 指定所采用的二级缓存驱动 -->
		<property name="hibernate.cache.provider_class">
			org.hibernate.cache.EhCacheProvider
		</property>		
		<!-- 开启查询缓存 -->
		<property name="hibernate.cache.use_query_cache">
			true
		</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>

TestSecondCache完整代码如下:

package com.tarena.test;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.junit.Test;
import com.tarena.entity.Emp;
import com.tarena.util.HibernateUtil;

public class TestSecondCache {

	/**
	 * 二级缓存
	 */
	@Test
	public void test1() {
		Session session1 = HibernateUtil.getSession();
		Emp e1 = (Emp) session1.get(Emp.class, 321);
		System.out.println(e1.getName());
		
		System.out.println("-----------------");
		HibernateUtil.getSessionFactory().evict(Emp.class);
		
		Session session2 = HibernateUtil.getSession();
		Emp e2 = (Emp) session2.get(Emp.class, 321);
		System.out.println(e2.getName());
		
		session1.close();
		session2.close();
	}
	
	/**
	 * 查询缓存
	 */
	@Test
	public void test2() {
		Session session = HibernateUtil.getSession();
		
		String hql = "from Emp";
		Query query = session.createQuery(hql);
		// 开启查询缓存
		query.setCacheable(true);
		List<Emp> emps = query.list();
		for(Emp e : emps) {
			System.out.println(e.getId() + " " + e.getName());
		}
		
		System.out.println("---------------");
		HibernateUtil.getSessionFactory().evictQueries();
		
		hql = "from Emp";
		query = session.createQuery(hql);
		// 开启查询缓存
		query.setCacheable(true);
		emps = query.list();
		for(Emp e : emps) {
			System.out.println(e.getId() + " " + e.getName());
		}
		
		session.close();
	}
	
}