Hibernate关联映射及高级查询
一、Hibernate中的关联关系
1.1、单向一对多关联关系
按照以下步骤配置hibernate中持久化类的一对多对象关联:
(1).持久化类添加关联类的相关属性及getter/setter方法。
(2).映射文件中建立该属性和数据库表字段的映射信息。
比如班级对学生是一对多的关系,班级类Grade类和Grade.hbm.xml文件如下:
package com.pb.hibernate.po; import java.util.HashSet; import java.util.Set; public class Grade { private int gid; private String gname; private String gdesc; private Set students=new HashSet(); public Set getStudents() { return students; } public void setStudents(Set students) { this.students = students; } public int getGid() { return gid; } public void setGid(int gid) { this.gid = gid; } public String getGname() { return gname; } public void setGname(String gname) { this.gname = gname; } public String getGdesc() { return gdesc; } public void setGdesc(String gdesc) { this.gdesc = gdesc; } public Grade() { super(); } public Grade(int gid, String gname, String gdesc, Set students) { super(); this.gid = gid; this.gname = gname; this.gdesc = gdesc; this.students = students; } }
Grade.hbm.xml文件:
<?xml version="1.0"?> <!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.pb.hibernate.po.Grade" table="GRADE" lazy="false" schema="scott"> <id name="gid" type="java.lang.Integer"> <column name="GID"></column> <generator class="assigned"></generator> </id> <property name="gname" type="java.lang.String"> <column name="GNAME" length="20" not-null="true"></column> </property> <property name="gdesc" type="java.lang.String"> <column name="GDESC" length="50"></column> </property> <set name="students" cascade="save-update" inverse="true"> <key column="GID"></key> <!--学生表的外键 --> <one-to-many class="com.pb.hibernate.po.Student"/> <!-- 一对多关联关系one-to-many --> </set> </class> </hibernate-mapping>
使用<set>元素和<one-to-many>元素配置一对多关系,<set>元素常用属性如下:
1.name:关联类属性的名称,是必须填写,没有默认值。
2.table:关联类的目标数据库表,可以不关联数据表,没有默认值。
3.lazy:指定关联对象延迟加载策略,默认为true.
4.fetch:设置抓取数据的策略,默认为select.
5.inverse:描述对象之间关联关系的维护方式,默认为false.
1.2、单向多对一关联关系
多对一关联关系和配置一对多关联关系步骤一样,不同的是在配置文件中使用<many-to-one>元素配置多对一关联。
package com.pb.hibernate.po; import java.util.HashSet; import java.util.Set; public class Student { private int sid; private String sname; private String sex; private Grade grade;// 定义班级属性 private Paper paper; private Set courses=new HashSet(); public Set getCourses() { return courses; } public void setCourses(Set courses) { this.courses = courses; } public int getSid() { return sid; } public void setSid(int sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Grade getGrade() { return grade; } public void setGrade(Grade grade) { this.grade = grade; } public Paper getPaper() { return paper; } public void setPaper(Paper paper) { this.paper = paper; } public Student() { super(); } }
在Student.hbm.xml文件中配置实体与表的关联关系:
<?xml version="1.0"?>
<!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.pb.hibernate.po.Student" table="STUDENT" schema="scott">
<id name="sid" column="SID" type="java.lang.Integer">
<generator class="assigned"/>
</id>
<property name="sname" type="java.lang.String">
<column name="SNAME" length="20" not-null="true"></column>
</property>
<property name="sex" type="java.lang.String">
<column name="SEX" length="20"></column>
</property>
<!-- 配置学生与班级关联关系 -->
<many-to-one name="grade" class="com.pb.hibernate.po.Grade">
<column name="GID"></column>
</many-to-one>
<one-to-one name="paper" class="com.pb.hibernate.po.Paper" cascade="all" property-ref="student"></one-to-one>
<set name="courses" table="SC_HIB" cascade="save-update">
<key column="sid"></key>
<many-to-many class="com.pb.hibernate.po.Course" column="cid"></many-to-many>
</set>
</class>
</hibernate-mapping>
1.3、双向一对多关联关系:
单向一对多和单向多对一可以分别配置使用,如果同时配置了两者,就成了双向一对多关联关系,其实在上面就完成了双向一对多关联关系。
1.4、一对一关联关系
数据库表的一对一关联关系可以通过主键关联以及外键关联实现,常使用外键进行关联。
使用学生表Student和学生证表paper是一对一关联,xml配置文件如下:
Student表的hibernate.hbm.xml文件在上面已经描述不再赘述,下面是学生证表对应的xml配置文件:
<?xml version="1.0"?>
<!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.pb.hibernate.po.Paper" table="PAPER" schema="scott">
<id name="pid" column="PID" type="int">
<generator class="assigned"/>
</id>
<property name="pdesc" type="string">
<column name="PDESC" length="50" not-null="true"></column>
</property>
<many-to-one name="student" class="com.pb.hibernate.po.Student" unique="true">
<column name="SID"></column>
</many-to-one>
</class>
</hibernate-mapping>
需要注意的是在Student.hbm.xml中,<one-to-one>并不需要指定属性所对应的数据表的列,而是通过paperty-ref指向paper类中的关联对象属性student,在paper.hbm.xml中添加了属性upique="true"的<many-to-one>作用等同与<one-to-one>。
1.5、多对多关联关系
Hibernate中多对多对象关联的实现方式有一下两种:
1.不创建中间表的持久化类,只创建两端数据库表的持久化类,在映射文件中使用<many-to-many>标签设置映射。
2.创建中间表,两端数据表的持久化类,针对中间表的持久化类分别和两端的数据库表的持久化类创建一对多关联。
数据库中多对多关联关系是通过中间表实现的,通过中间表,将两个表之间的多对多关联关系分别转换为它们和中间表之间的一对多关联关系。
使用学生Student对课程Course之间的多对多关联关系为例设置多对多关联关系,其中Student类的xml文件的配置已经在上面描述,下面是Course类的xml文件配置信息。
<?xml version="1.0"?> <!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.pb.hibernate.po.Course" table="COURSE_HIB" schema="scott"> <id name="cid" column="CID" type="int"> <generator class="assigned"/> </id> <property name="cname" type="string"> <column name="CNAME" length="20" not-null="true"></column> </property> <property name="cdesc" type="string"> <column name="CDESC" length="50"></column> </property> <set name="students" table="SC_HIB" cascade="save-update" inverse="true"> <key column="cid"></key> <many-to-many class="com.pb.hibernate.po.Student" column="sid"></many-to-many> </set> </class> </hibernate-mapping>
在配置Student对象和Course对象时候我们注意到有两个属性:cascade(级联)和inverse(反转)
1.cascade属性:级联操作是指当主控方执行某项操作时,是否要对被关联放也执行相同的操作,常用的有<many-to-one/>,<one-to-one/>,<set/>,使用cascade属性的常用值如下:
(1).all:对所有操作进行级联操作。
(2).save-update:执行保存和更新操作时进行级联操作。
(3).delete:执行删除操作时进行级联操作。
(4).none:对所有操作不进行级联操作。
2.inverse属性:是对象之间关联关系的维护方式,它可以将维护关联关系的任务反转,由对方完成,inverse只存在与集合标记的元素中,inverse为true时,数量为一的一方反转关联关系维护给多的一方,inverse为false时为主动方,有主动方负责维护关联关系。
二、Hibernate检索方式
Hibernate中提供了一下几种在数据库中检索对象的方式:
(1).导航对象图检索方式:根据已经加载的对象,导航到其他对象,如关联对象的查询。
(2).OID检索方式:按照对象的OID来检索对象。
(3).HQL检索方式:使用专门的HQL查询接口和面向对象的HQL查询语言。
(4).QBC(Query By Criteria)检索方式:QBC提供的API来检索对象,这种API封装了基于字符串形式的查询语言,提供了更加面向对象的查询接口。
(5).本地SQL检索方式:这也是官方推荐的标准查询方式。
2.1、HQL查询
HQL(Hibernate Query Language)是Hibernate提供的一种面向对象的查询语言,HQL提供了了更加丰富灵活并且强大的功能。
>使用HQL可以避免使用JDBC查询的一些弊端,不需要再编写复杂的sql,将针对实体类及属性进行查询。
>查询结果直接放在List中的对象中,不需要,再次封装。
>独立于数据库,对不同的数据库根据Hibernate dialect属性自动生成不同的sql语句。
Query接口是HQL查询接口,提供了各种查询功能,它相当于JDBC的Statement和PreparedStatement,通过Session的createQuery创建其对象。理解其list()与iterate()方法的查询机制,将有助于查询性能的优化。
>list()方法返回List对象,iterate()方法直接返回Iterator对象。
>list()方法将不会在缓存中读取数据,它总是一次性地从数据库中直接查询所有符合条件的数据,同时将获取的数据写入缓存。
>iterate()方法是获取符合条件的数据的id后,需要时根据id在缓存中寻找符合条件的数据,若缓存中没有符合条件的数据,再到数据库中查询。
HibernateUtil工具类:
package com.pb.hibernate.util; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { /** * 初始化一个ThreadLocal对象,ThreadLocal对象有get(),set()方法; */ private static final ThreadLocal<Session> sessionTL=new ThreadLocal<Session>(); private static Configuration conf; private static SessionFactory sf; //静态代码块,只执行一次 static{ try { //解析配置文件 conf=new Configuration().configure(); //创建sesssion工厂 sf=conf.buildSessionFactory(); } catch (Exception e) { e.printStackTrace(); } } /** * 得到session对象,同时设置session对象到ThreadLocal对象 * 确保一个线程用一个session对象,而不是多个线程共享一个session对象 * @return 从ThradLocal对象中得到的session对象 */ public static Session getCurrentSession(){ //多线程不公用session Session session=sessionTL.get(); if (session==null) { //得到session对象 session=sf.openSession(); //将session对象保存到threadLocal对象中 sessionTL.set(session); } return session; } /** * 关闭session ,同时从ThreadLocal对象中清除缓存 */ public static void closeSession(){ Session session =sessionTL.get(); sessionTL.set(null);//先清空threadLocal session.close(); } }
DAO层使用hql查询:
/** * 通过hql语句的query.list()方法得到所有的像的集合 * @return */ public List<Dept> getAll(){ //1.得到session Session session=HibernateUtil.getCurrentSession(); //2.hql语句 String hql="from Dept"; //得到query对象 Query query=session.createQuery(hql); List<Dept> list=query.list(); return list; }
2.2、属性查询
属性查询只查找持久化类的部分属性而不是全部属性,通过属性查询有两种方式:
(1).通过直接指定属性进行属性查询,例如:
/** * @param deptName * @param location * @return */ public List<Dept> getDeptByNameAndLoc(String deptName,String location){ Session session=HibernateUtil.getCurrentSession(); String hql="select deptName,location from Dept"; Query query=session.createQuery(hql); List<Object[]> list2=query.list(); for (Object[] objects : list2) { System.out.println(objects[0]+""+objects[1]); } return null; }
(2).通过构造方法进行属性查询,使用这种方法需要持久化类中添加相应的构造方法。
/** * @param deptName * @param location * @return */ public List<Dept> getDeptByNameAndLoc(String deptName,String location){ Session session=HibernateUtil.getCurrentSession(); String hql="select new Dept( deptName,location) from Dept"; Query query=session.createQuery(hql); List<Object[]> list2=query.list(); for (Object[] objects : list2) { System.out.println(objects[0]+""+objects[1]); } return null; }
2.3、参数绑定
(1).使用"?"占位符。
public List<Dept> getDeptByDeptName(String deptName){ Session session=HibernateUtil.getCurrentSession(); String hql="from Dept where deptName like ? "; Query query=session.createQuery(hql); //setXxx ;Xxx:数据类型 query.setString(0,"%"+deptName+"%"); List<Dept> list=query.list(); return list; }
(2).使用命名参数。
//参数名称绑定参数 public List<Dept> get(){ Session session=HibernateUtil.getCurrentSession(); //命名参数 String hql="from Dept where deptName=:deptName"; Query query=session.createQuery(hql); /** * 实体类以及对应的映射文件,自动生成 * 方向工程 */ query.setString("deptName", "deptName");
// query.setParameter("deptName","deptName"); List<Dept> list=query.list(); for (Dept dept : list) { System.out.println(dept.getDeptName()); } return null; }
2.4、Hibernate分页
/** * 通过hql语句的query.list()方法得到所有的像的集合 * 分页查询 * @return */ public List<Dept> getAll(int pageIndex){ //1.得到session Session session=HibernateUtil.getCurrentSession(); //2.hql语句 String hql="from Dept"; //得到query对象 Query query=session.createQuery(hql); /** * 每页显示2条数据 * 显示第五页 */ query.setMaxResults(3);//pageSize每页显示多少条数据 query.setFirstResult((pageIndex-1)*3);//设置第一个,不包括第一个数据(pageIndex-1)*pageSize List<Dept> list=query.list(); return list; } /** * 得到总页数 * @param deptName * @return */ public int getTotalCount(String deptName){ Session session=HibernateUtil.getCurrentSession(); StringBuffer hql=new StringBuffer("select count(*) from Dept where 1=1"); List params=new ArrayList(); if (deptName!=null&&!"".equals(deptName)) { hql.append(" and deptName like ?"); params.add("%"+deptName+"%"); } Query query=session.createQuery(hql.toString()); for (int i = 0; i < params.size(); i++) { query.setParameter(i, params.get(i));//第二个参数放入的是Object类型 } long count =(Long)query.uniqueResult(); int totalCount=(int)count; if (totalCount%3==0) { return totalCount/3; }else { return totalCount/3+1; } }
2.5、Criteria查询概述
Criteria查询(Query By Criteria, QBC)是与HQL完全不同的一种查询机制。Criteria查询又称对象查询,它采用对象的方式,封装查询条件,并提供了Restriction等类型做辅助,可以使编写查询代码更加方便。
使用Criteria的示例:
/** * 使用Criteria查询 */ public Dept getDeptByCriteria(){ try { Session session=HibernateUtil.getCurrentSession(); Criteria criteria = session.createCriteria(Dept.class); Dept dept=(Dept) criteria.uniqueResult(); return dept; } catch (HibernateException e) { e.printStackTrace(); } return null; } public Dept getDeptByCriteriaUseRestrictions(){ try { Session session=HibernateUtil.getCurrentSession(); Criteria criteria = session.createCriteria(Dept.class); criteria.add(Restrictions.eq("deptNo", "101"));// 添加限制条件 //criteria.addOrder(Order.desc("deptNo"));排序 Dept dept=(Dept) criteria.uniqueResult(); return dept; } catch (HibernateException e) { e.printStackTrace(); } return null; }
在Criteria查询中使用Example示例查询:
在使用Criteria查询时,设定查询条件并非一定使用Restrictions,如果属性条件更多,使用Restrictions也不方便,Criteria允许先创建一个对象模板,以这样一个对象模板作为查询依据,查询出来属性与类似的对象。也就是依照已有的对象,查询与其属性相同或者相似的其他对象,这种查询也称示例查询(Query By Example,QBE)。
public List<Dept> getDeptByExample(){ List<Dept> result = null; try { Session session=HibernateUtil.getCurrentSession(); Dept dept = new Dept(); dept.setLocation("上海"); Criteria criteria = session.createCriteria(Dept.class); /** * Hibernate在自动生成SQL语句时,将自动过滤对象的空属性, * 根据有非空属性生成查询条件,如果要想更加精准可以设置更多属性值 */ criteria.add(Example.create(dept)); result = criteria.list(); return result; } catch (HibernateException e) { e.printStackTrace(); } return null; }
使用Criteria实现统计、分组、分页:
/** * 使用Criteria查询 */ public List<Dept> getDeptByCriteria(){ List<Dept> result = null; try { Session session=HibernateUtil.getCurrentSession(); Criteria criteria = session.createCriteria(Dept.class); criteria.setProjection(Projections.projectionList() .add(Projections.groupProperty("deptName")) // 按部门名称分组 .add(Projections.rowCount()) // 统计所有记录数 .add(Projections.avg("deptName"))// 统计平均数 .add(Projections.max("deptNo")));// 求最大 result= criteria.list(); return result; } catch (HibernateException e) { e.printStackTrace(); } return null; } //分页 public List<Dept> getDeptByCriteriaPage(){ List<Dept> result = null; try { Session session=HibernateUtil.getCurrentSession(); Criteria criteria = session.createCriteria(Dept.class); criteria.setFirstResult(1); criteria.setMaxResults(3); result= criteria.list(); return result; } catch (HibernateException e) { e.printStackTrace(); } return null; }
2.6、命名HQL查询
在Dept.hbm.xml中配置:
<?xml version="1.0"?> <!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.jbit.hwd.entity.Dept" table="DEPT"> <id name="deptNo"> <column name="DEPTNO"></column> <generator class="sequence"> <param name="sequence">seq_dept</param> </generator> </id> <!-- 略--> </class> <query name="dept"> <![CDATA[ from Dept d where d.deptNo=:deptNo ]]> </query> </hibernate-mapping>
查询:
public List<Dept> getDeptByCriteria(){ List<Dept> result = null; try { Session session=HibernateUtil.getCurrentSession(); Query query = session.getNamedQuery("dept"); Dept dept = new Dept(); dept.setDeptNo(101); query.setProperties(dept); result= query.list(); return result; } catch (HibernateException e) { e.printStackTrace(); } return null; }
2.7、DetachedCriteria查询
public List<StudentExam> getAllExamByExamId(Integer examId, String[] classIds) { DetachedCriteria detachedCriteria = DetachedCriteria .forClass(getEntityClass()); detachedCriteria.add(Restrictions.eq("exam.id", examId)); detachedCriteria.add(Restrictions.eq("isDelete", MySchoolConstant.IS_DELETED_NO)); boolean error = false; Integer[] intClassIds = new Integer[classIds.length]; for (int i = 0; i < classIds.length; i++) { if (StringUtils.isEmpty(classIds[i])) { error = true; break; } intClassIds[i] = Integer.valueOf(classIds[i]); } if (!error && intClassIds.length > 0) { detachedCriteria.add(Restrictions.in("classId", intClassIds)); } return findList(detachedCriteria); }
2.8、本地sql查询
HQL查询并不能涵盖所有的查询特性,一些复杂的查询还必须借助sql达到期望的目标,也就是本地sql,使用query.createSQLQuery(String sql)方法,同时使用addEntity()方法将别名与实体类关联起来。
public ZAnswer findStudentZAnswerListByParams(Integer recruitId,Integer examId,Integer questionId,Integer userId) { StringBuffer sql = new StringBuffer(); sql.append(" SELECT * FROM Z_ANSWER t "); sql.append(" WHERE t.`IS_DELETED` = 0 "); sql.append(" AND t.`RECRUIT_ID` = :recruitId "); sql.append(" AND t.`EXAM_ID` = :examId "); sql.append(" AND t.`TEST_QUESTION_ID` = :questionId "); sql.append(" AND t.`ANSWER_USER_ID` = :userId "); sql.append(" ORDER BY t.`IS_CURRENT` DESC,t.`CREATE_TIME` DESC "); Query query = this.getSession().createSQLQuery(sql.toString()).addEntity(ZAnswer.class); query.setParameter("recruitId", recruitId); query.setParameter("examId", examId); query.setParameter("questionId", questionId); query.setParameter("userId", userId); @SuppressWarnings("unchecked") List<ZAnswer> result = query.list(); //取得第一条记录 ZAnswer answer = null; //排除重复 if(result.size() > 0){ answer = (ZAnswer) result.get(0); for (ZAnswer zAnswer : result) { if(zAnswer.getId() != answer.getId()){ zAnswer.setIsDelete(1); this.update(zAnswer); } } } return answer; }
2.9、Hibernate调用存储过程
创建两个存储过程,在存储过程中in表示输入,out表示输出。
1.根据id查找某条数据:
CREATE PROCEDURE `findEmpById`(IN id INTEGER(11)) begin select * from emp where empId=id; end;
2.根据id查找某个字段并返回
CREATE PROCEDURE `getNameById`(in id integer(11),out eName varchar(50)) begin select empName into eName from emp where empId=id; end;
调用第一个存储过程:
package com.test; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class 调用存储过程 { /** * @param args * @throws SQLException */ public static void main(String[] args) throws SQLException { Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session = factory.openSession(); Connection con = session.connection(); String sql = "{call findEmpById(?)}"; CallableStatement cs = con.prepareCall(sql); cs.setObject(1, 2); ResultSet rs = cs.executeQuery(); while(rs.next()){ int id = rs.getInt("empId"); String name = rs.getString("empName"); System.out.println(id+"\t"+name); } } }
调用第二个存储过程:
package com.test; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.SQLException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class 调用存储过程1 { public static void main(String[] args) throws SQLException { Configuration config = new Configuration().configure(); SessionFactory sessionFactory = config.buildSessionFactory(); Session session = sessionFactory.openSession(); Connection conn = session.connection(); String sql = "{call getNameById(?,?)}"; CallableStatement cs = conn.prepareCall(sql); cs.setObject(1, 3); //设置输出参数 cs.registerOutParameter(2, java.sql.Types.VARCHAR); //设置第二个参数为输出参数 cs.execute(); //调用存储过程 String name = cs.getString(2);//获取输出参数 System.out.println(name); } }