一、Hibernate中的關聯關係 1.1、單向一對多關聯關係 按照以下步驟配置hibernate中持久化類的一對多對象關聯: (1).持久化類添加關聯類的相關屬性及getter/setter方法。 (2).映射文件中建立該屬性和資料庫表欄位的映射信息。 比如班級對學生是一對多的關係,班級類Grad ...
一、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); } }