JAVA Hibernate DAY03
- 使用多对一关联映射
- 关联查询的一些特性
- 级联添加/修改
- 级联删除
- HQL查询,按条件查询
- HQL查询,查询一部分字段
- HQL查询,分页查询
- HQL查询,多表联合查询
- Hibernate中的SQL查询
- 使用二级缓存
- 使用查询缓存
1 使用多对一关联映射
1.1 问题
使用多对一关联映射,在查询业务账号时,自动查询出它对应的账务账号。
1.2 方案
多对一关联映射开发步骤:
- 业务账号与账务账号具有多对一关系,他们的关系字段是service.account_id。
- 在业务账号中追加Account类型的属性,用于封装它对应的唯一账务账号。
- 在业务账号映射关系文件中配置此属性。
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:在业务账号实体类中追加属性
在业务账号实体类Service中,追加Account类型的属性,用于封装它对应的唯一账务账号。由于这个属性包含了账务账号的ID,因此accountId属性可以去掉了,实际上这个属性必须去掉,否则会报错。代码如下:
- 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;
- }
- }
步骤二:在业务账号映射关系文件中配置这个属性
在业务账号映射关系文件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 完整代码
以下为本案例的完整代码。
其中业务账号实体类完整代码如下:
业务账号映射关系文件代码如下:
测试类代码如下:
2 关联查询的一些特性
2.1 问题
请按照如下的方式使用关联映射:
- 不采用延迟加载的方式查询关联属性。
- 采用关联查询一次性查出2张表的数据。
2.2 方案
可以按照如下的方式实现上述问题的要求:
- 通过lazy=”false”取消延迟加载。
- 通过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属性,采用一对多的关系 -->
- <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内容 -->
- <many-to-one name="account" column="ACCOUNT_ID"
- class="com.tarena.entity.Account"
- 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属性,采用一对多的关系 -->
- <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内容 -->
- <many-to-one name="account" column="ACCOUNT_ID"
- class="com.tarena.entity.Account"
- lazy="false" fetch="join"/>
- </class>
- </hibernate-mapping>
执行TestManyToOne中的测试方法,控制台输出结果如下图,可以看出查询时采用了join方式查询。
图-5
2.4 完整代码
以下为本案例的完整代码。
其中账务账号映射关系文件完整代码如下:
业务账号映射关系文件完整代码如下:
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属性,采用一对多的关系 -->
- <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>
步骤二:测试级联添加
在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();
- }
- }
- /**
- * 级联修改
- */
- @Test
- public void test2() {
- Session session = HibernateUtil.getSession();
- // 查询出要修改的账务账号
- Account account =
- #cold_bold (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();
- }
- }
-
- }
执行test2()方法后,控制台输出的结果如下图,可以看出在修改完账务账号之后,Hibernate自动修改了它对应的业务账号数据。
图-9
此时,查询账务账号表,id=380的行已经发生了改变。
图-10
再查询业务账号表,account_id=380的行也发生了改变。
图-11
3.4 完整代码
以下为本案例的完整代码。
其中Account.hbm.xml完整代码如下:
TestCascade完整代码如下:
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属性,采用一对多的关系 -->
- <set name="services"
- lazy="false" fetch="join"
- 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();
- }
- }
- /**
- * 级联删除
- */
- @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();
- }
- }
-
- }
步骤三:测试
执行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属性,采用一对多的关系 -->
- <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>
步骤五:测试
再次执行test3()方法,控制台输出结果如下图,可以看出本次删除成功了,不但删除了账务账号数据,在此之前还删除了它所对应的所有业务账号。
图-13
4.4 完整代码
以下为本案例的完整代码。
其中Account.hbm.xml完整代码如下:
TestCascade完整代码如下:
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完整代码如下:
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 {
-
- // 其他查询方法略
- /**
- * 查询一部分字段
- */
- @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();
- }
-
- }
步骤二:测试
执行test2(),控制台输出结果如下图,可以看到查询出来的这些字段的值。
图-15
6.4 完整代码
以下为本案例的完整代码。
其中TestHQL完整代码如下:
7 HQL查询,分页查询
7.1 问题
按照每页显示3条数据,查询第1页的条件,查询出业务账号表中满足条件的记录,并且查询出总页数。
7.2 方案
- 分页查询时,在执行查询之前可以通过query对象设置分页参数值,query.setFirstResult(起点),以及query.setMaxResults(页容量)。
- 查询总页数,只需要通过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 {
-
- // 其他查询方法略
-
- /**
- * 分页查询
- */
- @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();
- }
-
- }
步骤二:测试
执行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 {
-
- // 其他查询方法略
- /**
- * 查询总页数
- */
- @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();
- }
-
- }
步骤四:测试
执行test4(),控制台输出结果如下图,输出了总页数。
图-17
7.4 完整代码
以下为本案例的完整代码。
其中TestHQL完整代码如下:
8 HQL查询,多表联合查询
8.1 问题
使用HQL,联合查询出业务账号与账务账号的数据。
8.2 方案
HQL中多表联合查询的方式有3种,分别是
- 对象方式关联
- join方式关联
- 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 {
-
- // 其他查询方法略
-
- /**
- * 多表联合查询-对象方式关联
- */
- @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();
- }
-
- }
步骤二:测试
执行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 {
-
- // 其他查询方法略
-
- /**
- * 多表联合查询-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();
- }
-
- }
步骤四:测试
执行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 {
-
- // 其他查询方法略
- /**
- * 多表联合查询-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();
- }
-
- }
步骤六:测试
执行test7(),控制台输出结果如下,可以查询出关联的数据。
图-20
8.4 完整代码
以下为本案例的完整代码。
其中TestHQL完整代码如下:
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完整代码如下:
10 使用二级缓存
10.1 问题
在查询员工时,使用二级缓存,使得不同的session可以共享这些员工数据。
10.2 方案
使用ehcache缓存组件来支持二级缓存,二级缓存的使用步骤为
- 导入二级缓存驱动包
- 引入二级缓存配置文件
- 在hibernate.cfg.xml中开启二级缓存并指定二级缓存驱动类
- 在映射关系文件中设置缓存策略
- 执行查询
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>
-
- <!-- 开启二级缓存 -->
- <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>
步骤五:编写查询方法,测试二级缓存
在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();
- }
-
- /**
- * 返回SessionFactory
- */
- public static SessionFactory getSessionFactory() {
- return sessionFactory;
- }
-
- 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("-----------------");
- 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完整代码如下:
hibernate.cfg.xml完整代码如下:
emp.hbm.xml完整代码如下:
TestSecondCache完整代码如下:
11 使用查询缓存
11.1 问题
在查询多条员工数据时,使用查询缓存,缓存查询的HQL,使得再次使用同样HQL查询时不必重新访问数据库。
11.2 方案
查询缓存的使用步骤是
- 开启二级缓存
- 在hibernate.cfg.xml中开启查询缓存
- 在查询之前,开启查询缓存
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>
- <!-- 开启查询缓存 -->
- <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中,增加测试查询缓存的方法,使用相同的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();
- }
-
- /**
- * 查询缓存
- */
- @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("---------------");
-
- 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(),控制台输出结果如下图,可见在使用相同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("---------------");
- 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完整代码如下:
TestSecondCache完整代码如下:
TopJAVA Hibernate DAY03
- 使用多对一关联映射
- 关联查询的一些特性
- 级联添加/修改
- 级联删除
- HQL查询,按条件查询
- HQL查询,查询一部分字段
- HQL查询,分页查询
- HQL查询,多表联合查询
- Hibernate中的SQL查询
- 使用二级缓存
- 使用查询缓存
1 使用多对一关联映射
1.1 问题
使用多对一关联映射,在查询业务账号时,自动查询出它对应的账务账号。
1.2 方案
多对一关联映射开发步骤:
- 业务账号与账务账号具有多对一关系,他们的关系字段是service.account_id。
- 在业务账号中追加Account类型的属性,用于封装它对应的唯一账务账号。
- 在业务账号映射关系文件中配置此属性。
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:在业务账号实体类中追加属性
在业务账号实体类Service中,追加Account类型的属性,用于封装它对应的唯一账务账号。由于这个属性包含了账务账号的ID,因此accountId属性可以去掉了,实际上这个属性必须去掉,否则会报错。代码如下:
- 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;
- }
- }
步骤二:在业务账号映射关系文件中配置这个属性
在业务账号映射关系文件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 完整代码
以下为本案例的完整代码。
其中业务账号实体类完整代码如下:
业务账号映射关系文件代码如下:
测试类代码如下:
2 关联查询的一些特性
2.1 问题
请按照如下的方式使用关联映射:
- 不采用延迟加载的方式查询关联属性。
- 采用关联查询一次性查出2张表的数据。
2.2 方案
可以按照如下的方式实现上述问题的要求:
- 通过lazy=”false”取消延迟加载。
- 通过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属性,采用一对多的关系 -->
- <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内容 -->
- <many-to-one name="account" column="ACCOUNT_ID"
- class="com.tarena.entity.Account"
- 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属性,采用一对多的关系 -->
- <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内容 -->
- <many-to-one name="account" column="ACCOUNT_ID"
- class="com.tarena.entity.Account"
- lazy="false" fetch="join"/>
- </class>
- </hibernate-mapping>
执行TestManyToOne中的测试方法,控制台输出结果如下图,可以看出查询时采用了join方式查询。
图-5
2.4 完整代码
以下为本案例的完整代码。
其中账务账号映射关系文件完整代码如下:
业务账号映射关系文件完整代码如下:
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属性,采用一对多的关系 -->
- <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>
步骤二:测试级联添加
在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();
- }
- }
- /**
- * 级联修改
- */
- @Test
- public void test2() {
- Session session = HibernateUtil.getSession();
- // 查询出要修改的账务账号
- Account account =
- #cold_bold (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();
- }
- }
-
- }
执行test2()方法后,控制台输出的结果如下图,可以看出在修改完账务账号之后,Hibernate自动修改了它对应的业务账号数据。
图-9
此时,查询账务账号表,id=380的行已经发生了改变。
图-10
再查询业务账号表,account_id=380的行也发生了改变。
图-11
3.4 完整代码
以下为本案例的完整代码。
其中Account.hbm.xml完整代码如下:
TestCascade完整代码如下:
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属性,采用一对多的关系 -->
- <set name="services"
- lazy="false" fetch="join"
- 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();
- }
- }
- /**
- * 级联删除
- */
- @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();
- }
- }
-
- }
步骤三:测试
执行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属性,采用一对多的关系 -->
- <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>
步骤五:测试
再次执行test3()方法,控制台输出结果如下图,可以看出本次删除成功了,不但删除了账务账号数据,在此之前还删除了它所对应的所有业务账号。
图-13
4.4 完整代码
以下为本案例的完整代码。
其中Account.hbm.xml完整代码如下:
TestCascade完整代码如下:
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完整代码如下:
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 {
-
- // 其他查询方法略
- /**
- * 查询一部分字段
- */
- @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();
- }
-
- }
步骤二:测试
执行test2(),控制台输出结果如下图,可以看到查询出来的这些字段的值。
图-15
6.4 完整代码
以下为本案例的完整代码。
其中TestHQL完整代码如下:
7 HQL查询,分页查询
7.1 问题
按照每页显示3条数据,查询第1页的条件,查询出业务账号表中满足条件的记录,并且查询出总页数。
7.2 方案
- 分页查询时,在执行查询之前可以通过query对象设置分页参数值,query.setFirstResult(起点),以及query.setMaxResults(页容量)。
- 查询总页数,只需要通过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 {
-
- // 其他查询方法略
-
- /**
- * 分页查询
- */
- @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();
- }
-
- }
步骤二:测试
执行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 {
-
- // 其他查询方法略
- /**
- * 查询总页数
- */
- @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();
- }
-
- }
步骤四:测试
执行test4(),控制台输出结果如下图,输出了总页数。
图-17
7.4 完整代码
以下为本案例的完整代码。
其中TestHQL完整代码如下:
8 HQL查询,多表联合查询
8.1 问题
使用HQL,联合查询出业务账号与账务账号的数据。
8.2 方案
HQL中多表联合查询的方式有3种,分别是
- 对象方式关联
- join方式关联
- 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 {
-
- // 其他查询方法略
-
- /**
- * 多表联合查询-对象方式关联
- */
- @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();
- }
-
- }
步骤二:测试
执行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 {
-
- // 其他查询方法略
-
- /**
- * 多表联合查询-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();
- }
-
- }
步骤四:测试
执行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 {
-
- // 其他查询方法略
- /**
- * 多表联合查询-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();
- }
-
- }
步骤六:测试
执行test7(),控制台输出结果如下,可以查询出关联的数据。
图-20
8.4 完整代码
以下为本案例的完整代码。
其中TestHQL完整代码如下:
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完整代码如下:
10 使用二级缓存
10.1 问题
在查询员工时,使用二级缓存,使得不同的session可以共享这些员工数据。
10.2 方案
使用ehcache缓存组件来支持二级缓存,二级缓存的使用步骤为
- 导入二级缓存驱动包
- 引入二级缓存配置文件
- 在hibernate.cfg.xml中开启二级缓存并指定二级缓存驱动类
- 在映射关系文件中设置缓存策略
- 执行查询
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>
-
- <!-- 开启二级缓存 -->
- <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>
步骤五:编写查询方法,测试二级缓存
在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();
- }
-
- /**
- * 返回SessionFactory
- */
- public static SessionFactory getSessionFactory() {
- return sessionFactory;
- }
-
- 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("-----------------");
- 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完整代码如下:
hibernate.cfg.xml完整代码如下:
emp.hbm.xml完整代码如下:
TestSecondCache完整代码如下:
11 使用查询缓存
11.1 问题
在查询多条员工数据时,使用查询缓存,缓存查询的HQL,使得再次使用同样HQL查询时不必重新访问数据库。
11.2 方案
查询缓存的使用步骤是
- 开启二级缓存
- 在hibernate.cfg.xml中开启查询缓存
- 在查询之前,开启查询缓存
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>
- <!-- 开启查询缓存 -->
- <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中,增加测试查询缓存的方法,使用相同的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();
- }
-
- /**
- * 查询缓存
- */
- @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("---------------");
-
- 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(),控制台输出结果如下图,可见在使用相同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("---------------");
- 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完整代码如下:
TestSecondCache完整代码如下: