Hibernate
一、hibernate是什么
- 一種框架,一種orm框架
(objext relation mapping)對象關系映射框架。 - hibernate處於項目的持久層位置,又叫持久化框架。
- hibernate實際上是對jdbc進行輕量級的封裝。
二、需求
- 切換數據庫需要重寫編寫sql。
- 使用jdbc操作數據庫語句編寫比較麻煩。
- 讓程序員只關注業務邏輯,不再關心數據庫。
三、快速入門案例
使用手動配置hibernate方式開發一個hibernate項目,完成相關操作。
開發流程
- 創建一個項目
- 畫出簡單的項目框架圖
- 引入Hibernate包
- 開發Hibernate三種方式
- 由Domain object --> mapping --> db。 (官方推薦)
- 由DB開始,用工具生成Mapping的Domain object。(使用較多)
- 由映射文件開始。
我們使用第二種方式
首先在hibernate數據庫下創建student表
create table student(
id int primary key,
name varchar(20) not null,
age varchar(20) not null,
);
5.開發domain對象 和對象關系映射
對象關系映射文件,用於指定domain對象和表的映射關系,該文件的取名有規范,domain對象hbm.xml,一般和domain對象同一包下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.sun.hibernate.model">
<class name="Student">
<id name="id"></id>
<property name="name"></property>
<property name="age"></property>
</class>
</hibernate-mapping>
6.手動配置hibernate.cfg.xml文件,該文件用於配置連接的數據庫類型,diver,用戶名,密碼....同時管理對象關系映射文件和該文件的名稱,這個文件一般放在src目錄下。
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/hibernate</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<!-- <property name="connection.pool_size">1</property> -->
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Enable Hibernate's automatic session context management -->
<!--<property name="current_session_context_class">thread</property>-->
<!-- Drop and re-create the database schema on startup -->
<!-- <property name="hbm2ddl.auto">create</property> -->
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<mapping resource="com/sun/hibernate/model/Student.hbm.xml"/>
</session-factory>
</hibernate-configuration>
7. 創建對應的student模型
package com.sun.hibernate.model;
public class Student {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
8.測試方法(向數據庫中添加一條數據)
package com.sun.hibernate.model;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;
public class StudentTest {
public static void main(String[] args){
Student s = new Student();
s.setId(1);
s.setName("s1");
s.setAge(2);
//1.創建Configuration ,該對象用於讀取hibernate.cfg.xml,並完成初始化
Configuration cfg = new Configuration();
//2.創建SessionFactory<這是一個會話工廠,是一個重量級對象>
SessionFactory sf = cfg.configure().buildSessionFactory();
//3.創建一個session,相當於jdbc Connection<不是jsp中的那個session>
Session session = sf.openSession();
//4.對hibernate而言,要求程序員在進行增加,刪除,修改時必須使用事物提交
session.beginTransaction();
session.save(s); //insert into ... <sql語句被hibernate封裝了>
session.getTransaction().commit();
session.close();
sf.close();
}
}
直接運行StudentTest就可以將數據插入到數據庫中了。
四、 hibernate緩存原理
1.session緩存(一級緩存)
Session內置不能被卸載,Session的緩存是事務范圍的緩存(Session對象的生命周期通常對應一個數據庫事務或者一個應用事務)。
一級緩存中,持久化類的每個實例都具有唯一的OID。
2.二級緩存
第二級緩存是可選的,是一個可配置的插件,默認下SessionFactory不會啟用這個插件。
Hibernate提供了org.hibernate.cache.CacheProvider接口,它充當緩存插件與Hibernate之間的適配器。
什么樣的數據適合存放到第二級緩存中?
- 很少被修改的數據
- 不是很重要的數據,允許出現偶爾並發的數據
- 不會被並發訪問的數據
- 常量數據
不適合存放到第二級緩存的數據? - 經常被修改的數據
- 絕對不允許出現並發訪問的數據,如財務數據,絕對不允許出現並發
- 與其他應用共享的數據。
Hibernate查找對象如何應用緩存?
當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查;
查不到,如果配置了二級緩存,那么從二級緩存中查;
如果都查不到,再查詢數據庫,把結果按照ID放入到緩存刪除、更新、增加數據的時候,同時更新緩存。
五、 Query接口
通過Query接口我們可以完成更加復雜的查詢任務
快速入門
/*獲取query應用【這里student不是表,而是domain】
where后面的條件可以是一個類的屬性名,也可以是表的字段,按照hibernate規定,我們還是應該使用類的屬性名*/
Query query = session.createQuery("from Teacher where id = 2");
//通過list方法獲取結果,這個list會自動將封裝成對應的dommain
//所以我們jdbc進行二次封裝的工作沒有
List<Teacher> list = query.list();
for(Teacher tea: list){
System.out.println(tea.getName() + " " + tea.getAge());
}
六、 criteria接口簡單使用
快速入門
Criteria cri = session.createCriteria(Teacher.class).setMaxResults(4);
List<Teacher> list = cri.list();
for(Teacher tea:list){
System.out.println(tea.getId() + " " + tea.getName());
}
七、HQL
(hibernate query language)
1.需求
在現有的只是基礎上,對對象的批量刪除,修改,查詢還不能很方便的實現。
模擬創建表
2.list
查詢所有對象
List<Student> list = session.createQuery("from Student").list();
3.uniqueResult
如果檢索一個對象,明確知道最多只有一個,則建議使用該方法。
具體用法:
Student stu = (Student) session.createQuery(" from Student where sid = 20040001").uniqueResult();
4.HQL常見用法
distinct的用法:(用於過濾重復記錄)
List<Object[]> list = (List<Object[]>)session.createQuery(" select distinct ssex,sage from Student").list();
between...and的用法
List<Object[]> list = (List<Object[]>)session.createQuery(" select distinct sname,ssex,sage from Student where sage between 22 and 24").list();
in 和 not in的用法
List<Student> list = session.createQuery("from Student where sdept in ('數學系','計算機系')").list();
group 和 having 和 order by
group用法:(查詢各個系的學生的平均年齡)
List<Object[]> list = session.createQuery("select avg(sage),sdept from Student group by sdept").list();
having的使用(顯示人數大於等於2的系)
List<Object[]> list = session.createQuery("select count(*),sdept from Student group by sdept having count(*) >= 2").list();
(查詢各個系女生的人數)
List<Object[]> list = session.createQuery("select count(*),sdept from Student where ssex='M' group by sdept").list();
order by用法(查詢所有學生的成績拼按照成績高低排序)
List<Object[]> list = session.createQuery("select student.sname,course.cname,grade from Studcourse order by grade DESC").list();
查詢計算機系共有多少學生
如果返回一列數據,取出數據是必須用Object,而不是Object[]
List<Object[]> list = session.createQuery("select count(*) from Student where sdept='計算機系'").list();
查詢總成績是多少
List<Object[]> list = session.createQuery("select sum(grade) from Studcourse").list();
查詢課程號為1001的課程名稱,最高分和最低分
List<Object[]> list = session.createQuery("select course.cname,min(grade),max(grade) from Studcourse where course.cid=1011").list();
查詢各科大於80分的學生的名字,科目,分數
List<Object[]> list =session.createQuery("select student.sname,course.cname,grade from Studcourse where grade > 80").list();
計算各科大於80分的的學生數量
List<Object[]> list = session.createQuery("select course.cname,count(*) from Studcourse where grade>80 group by course.cid").list();
5.HQL分頁技術
查詢所有學生成績進行高低排序並只分頁顯示。
//獲取頁數
int pageCount = Integer.parseInt(session.createQuery("select count(*) from Studcourse").uniqueResult().toString()) /3 +1;
System.out.println("一共有"+pageCount+"頁");
//開始查詢
for(int i = 0; i < pageCount; i++){
List<Object[]> list = session.createQuery("select student.sname,course.cname,grade from Studcourse order by grade DESC").
setFirstResult(i*3).setMaxResults(3).list();
for(int j = 0; j < list.size(); j ++) {
Object[] obj = list.get(j);
System.out.println(obj[0].toString() + " " + obj[1].toString() + " " + obj[2].toString());
}
System.out.println("*******第"+(i+1)+"頁**********");
}
6.參數綁定
好處:
1.可讀性高
2.效果好
3.防止sql注入漏洞
一般寫法:
List<Student> list = session.createQuery("from Student where sage = 22 and sname = '張三'").list();
參數綁定寫法:
如果我們的參數是冒號形式給出的,則可以這樣寫:
List<Student> list = session.createQuery("from Student where sage=:sage andsname=:sname").setString("sage", "22").setString("sname", "張三").list();
如果我們的參數是問號形式給出的,則可以這樣寫:
List<Student> list = session.createQuery("from Student where sage=? and sname=?").setString(0, "22").setString(1, "張三").list();
將綁定分開寫:
Query query = session.createQuery("from Student where sage=? and sname=?");
query.setInteger(0, 22);
query.setString(1, "張三");
List<Student> list = query.list();
映射文件中得到hql語句
hibernate提供了一種更加靈活的查詢方法。
把hql語句配置到對象關系映射文件
<query name="myquerytest">
from Student
</query>
八、hibernate對象的三種關系映射
- one-to-one: 身份證<---->人 丈夫<--->妻子
- one-to-many: 部門<--->員工
- many-to-one:員工<--->部門
- many-to-many:學生<--->老師(盡量避免)
(在實際開發過程中,如果出現了多對多的關系,我們應該盡量裝換為兩個一對多或者多對一的關系,這樣程序好控制,同時不會有冗余)
1.one-to-one
一對一兩種方式:
(1)基於主鍵的一對一
(人<--->身份證 one<--->one)
//Person.java,略去了相關的get/set方法
private Integer id;
private String name;
private IdCard idCard;
//Person.hbm.xml
<hibernate-mapping package="com.sun.hibernate.model">
<class name="Person">
<id name="id" type="java.lang.Integer">
<!-- 我們手動分配id -->
<generator class="assigned" />
</id>
<property name="name" type="java.lang.String">
<column name="name" length="128" />
</property>
<!-- 這里配置person和idcard屬性是一對一關系 -->
<one-to-one name="idCard"></one-to-one>
</class>
</hibernate-mapping>
//IdCard.java,略去了相關的get/set方法
private Integer id;
private Date validata;
private Person person;
//IdCard.hbm.xml
<hibernate-mapping package="com.sun.hibernate.model">
<class name="IdCard">
<id name="id" type="java.lang.Integer">
<!-- 因為我們這里是基於主鍵的one-to-one, 所以我們使用外鍵策略 -->
<generator class="foreign">
<!-- 這里值,是指跟那個屬性ont-to-one -->
<param name="property">person</param>
</generator>
</id>
<property name= "validata" type="java.util.Date">
<column name="validata" />
</property>
<!-- constrained設置為true才會使得在數據提交的時候同時提交相關的關聯關系,在此例中如果沒有IdCard表將不會有外鍵-->
<one-to-one name="person" constrained="true" />
</class>
</hibernate-mapping>
(2)基於外鍵的一對一
(人<--->身份證 one<--->one)
//Person.java,略去了相關的get/set方法(和基於主鍵的Person.java一致)
private Integer id;
private String name;
private IdCard idCard;
//Person.hbm.xml(和基於主鍵的Person.hbm.xml一致)
<hibernate-mapping package="com.model.one2one">
<class name="Person">
<id name="id" type="java.lang.Integer">
<!-- 我們手動分配id -->
<generator class="assigned" />
</id>
<property name="name" type="java.lang.String">
<column name="name" length="128" />
</property>
<!-- 這里配置person和idcard屬性是一對一關系 -->
<one-to-one name="idCard"></one-to-one>
</class>
</hibernate-mapping>
//IdCard.java,略去了相關的get/set方法(和基於主鍵的IdCard.java一致)
private Integer id;
private Date validate;
private Person person;
//IdCard.hbm.xml(注意與基於主鍵的IdCard.hbm.xml的區別)
<hibernate-mapping package="com.model.one2one">
<class name="IdCard">
<!-- 基於外鍵的one-one -->
<id name="id" type="java.lang.Integer">
<generator class="assigned" />
</id>
<property name="validate" type="java.util.Date">
<column name="validate" />
</property>
<many-to-one name="person" unique="true" />
</class>
</hibernate-mapping>
//測試函數
//添加一組Person/idcard
Session session = null;
Transaction ts = null;
try {
session = MySessionFactory.getSessionFactory().openSession();
ts = session.beginTransaction();
Person p1 = new Person();
p1.setId(100);
p1.setName("小明");
IdCard idCard = new IdCard();
idCard.setId(2015001);//如果是基於主鍵的one-to-one關系則不用設置
idCard.setValidate(new Date());
idCard.setPerson(p1);
session.save(p1);
session.save(idCard);
ts.commit();
} catch (Exception e) {
e.printStackTrace();
}finally{
if(session != null && session.isOpen()){
session.close();
}
}
2.one-to-many
配置文件示例
(學生<----->選課表 one<--->many)
//Student.hbm.xml配置文件
//Student.java定義Studcourse代碼:private Set<Studcourse> studcourses = new HashSet<Studcourse>(0);
<set name="studcourses" inverse="true">
<key>
<column name="sid" />
</key>
<one-to-many class="com.domain.Studcourse" />
</set>
//Studcourse.hbm.xml配置文件
//Studcourse定義Student代碼:private Student student;
<many-to-one name="student" class="com.domain.Student" fetch="select">
<column name="sid" />
</many-to-one>
3.many-to-many
學生<--->課程
many-to-many在實際開發過程中,如果出現這種多對多的關系,我們應該盡量轉換為兩個一對多或者多對一的關系,這樣程序好控制,同時不會有冗余。
所以學生<--->課程我們轉換為學生<--->選課記錄表(many<--->one) 選課記錄表<--->課程(one<--->many)
學生表(student)
sid | sname | sdept |
---|---|---|
2001 | 李華 | 計科 |
選課表(studcourse)
idstudcourse | sid | cid | grade |
---|---|---|---|
1 | 2001 | A101 | 87 |
課程表(course)
cid | cname | ccredit |
---|---|---|
A101 | java編程課 | 3 |
代碼及配置
//Student.java
private Integer sid;
private String sname;
private String sdept;
private Set<Studcourse> studcourses = new HashSet<Studcourse>(0);
//Student.hbm.xml
<hibernate-mapping>
<class name="com.domain.Student" table="student" catalog="course">
<id name="sid" type="java.lang.Integer">
<column name="sid" />
<generator class="identity" />
</id>
<property name="sname" type="string">
<column name="sname" length="20" not-null="true" />
</property>
<property name="sdept" type="string">
<column name="sdept" length="10" not-null="true" />
</property>
<set name="studcourses" inverse="true">
<key>
<column name="sid" />
</key>
<one-to-many class="com.domain.Studcourse" />
</set>
</class>
</hibernate-mapping>
//Studcourse.java
private Integer idstudCourse;
private Course course;
private Student student;
private Integer grade;
<hibernate-mapping>
<class name="com.domain.Studcourse" table="studcourse" catalog="course">
<id name="idstudCourse" type="java.lang.Integer">
<column name="idstudCourse" />
<generator class="identity" />
</id>
<many-to-one name="course" class="com.domain.Course" fetch="select">
<column name="cid" />
</many-to-one>
<many-to-one name="student" class="com.domain.Student" fetch="select">
<column name="sid" />
</many-to-one>
<property name="grade" type="java.lang.Integer">
<column name="grade" />
</property>
</class>
</hibernate-mapping>
Course.java
private Integer cid;
private String cname;
private Integer ccredit;
private Set<Studcourse> studcourses = new HashSet<Studcourse>(0);
//Course.hbm.xml
<hibernate-mapping>
<class name="com.domain.Course" table="course" catalog="course">
<id name="cid" type="java.lang.Integer">
<column name="cid" />
<generator class="identity" />
</id>
<property name="cname" type="string">
<column name="cname" length="40" not-null="true" />
</property>
<property name="ccredit" type="java.lang.Integer">
<column name="ccredit" />
</property>
<set name="studcourses" inverse="true">
<key>
<column name="cid" />
</key>
<one-to-many class="com.domain.Studcourse" />
</set>
</class>
</hibernate-mapping>
//Test.java
Student stu = new Student();
stu.setSname("王寶強");
stu.setSsex("M");
stu.setSdept("表演系");
stu.setSage(32);
stu.setSaddress("河南");
Course course = new Course();
course.setCid(1016);
course.setCname("asp.net");
course.setCcredit(4);
Studcourse stucourse = new Studcourse();
stucourse.setIdstudCourse(20009);
stucourse.setStudent(stu);
stucourse.setCourse(course);
stucourse.setGrade(79);
Session session = null;
Transaction ts = null;
try {
session = HibernateUtil.openSession();
ts = session.beginTransaction();
session.save(course);
session.save(stu);
session.save(stucourse);
ts.commit();
} catch (Exception e) {
if(ts != null){
ts.rollback();
}
throw new RuntimeException(e.getMessage());
}finally{
//關閉session
if(session != null && session.isOpen()){
session.close();
}
}
九、 對象的三種狀態
1. 瞬時狀態(transient)
數據庫中沒有數據與之對應,超過作用域會被JVM垃圾回收器回收,一般是new出來且且與session沒有關聯的對象。
2.持久狀態(persitent)
數據庫中有數據與之對應,與當前session有關聯,並且相關聯的session沒有關閉,事物沒有提交;持久對象狀態發生改變,在事務提交時會影響數據庫(hibernate能檢測得到)
3.托管狀態/游離狀態(datached)
數據庫中有數據與之對應,但當前沒有session與之關聯;脫管狀態發生改變時,hibernate不能檢測到。
Student stu = new Student(); //stu就是瞬時狀態
stu.setSname("王寶強");
stu.setSsex("M");
stu.setSdept("表演系");
stu.setSage(32);
stu.setSaddress("河南");
Session session = null;
Transaction ts = null;
try {
session = HibernateUtil.openSession();
ts = session.beginTransaction();
session.save(stu);
//stu既處於session管理下,
//同時stu被保存到數據庫中,因此stu此時是持久態
stu.setSname("唐國強");//hibernate能檢測到,並且會提交到數據庫中去
ts.commit();
session.close();
//這是stu被保存到數據庫中,沒有處於session的管理之下
//此時stu就是脫管狀態(游離態)
} catch (Exception e) {
if(ts != null){
ts.rollback();
}
throw new RuntimeException(e.getMessage());
}finally{
}
十、級聯操作
所謂級聯操作就是說,當你進行某個操作(添加、修改、刪除),就有hibernate自動給你完成。
比如刪除一個學生那么這個學生相應的選課表也要被刪除。
配置(配置cascade對應的屬性就可以實現相應的級聯操作)cascade:create,merge,save-update,delete,lock,refresh,evict,replicate;默認值為none,也可以設置為all
級聯操作一般在one-to-one和one-to-many中比較有用,在many-to-one和many-to-many中沒有什么意義。
<set name="studcourses" inverse="true" cascade="save-update">
十一、 其他
1. 什么是pojo類,他有什么要求?
- pojo類和一張表對應
- 一般放在com.XXX.domain下
- pojo需要一個主鍵屬性(用於標示一個pojo對象)
- 除了主鍵屬性,它還應當有其他屬性,屬性的訪問權限為private
- 提供get/set方法
- 它應當有一個無參構造方法(hibernate反射)
- pojo類其實就是一個javaBean(有時也叫Data對象)
2. hibernate核心類和接口
1. configuration類
2. 讀取配置文件
3. 管理關系映射文件
4. 加載hibernate的驅動,url,用戶
5. 管理hibernate配置信息
3. hibernate.cfg.xml
4. 對象關系映射文件
5. SessionFactory接口(會話工廠)
1. 可以緩存sql語句和數據(稱為session級緩存)
2. 是一個重量級的類,因此需要保證一個數據庫有一個SessionFactory
6. 通過SessionFactory獲取Session的兩個方法
1. openSession()是獲取一個新的Session
2. getCurrentSession()獲取和當前綁定的session,換言之,在同一個線程中,我們獲取的session是同一個session。(如果希望使用getCurrentSession需要配置hibernate.cfg.xml)
<property name="current_session_context_class">thread</property>
3.如何選擇
如果在同一個線程中,保證使用同一個session,則使用getCurrentSession(),如果在同一個線程中需要使用不同的Session,則使用opentSession()
4. openSession() 和 getCurrentSession()的區別
* 通過getCurrentSession獲取的session在事務提交以后會自動關閉,通過openSession獲取的session則必須手動關閉,但是我們建議不管什么形式獲取的session都進行判斷后手動關閉。
* 如果是通過getCurrentSession()獲取session進行查詢時,也要進行事務提交。
7. 本地事務:針對一個數據庫的事物
全局事務:跨數據庫的事物(jta)
8. session接口:
主要功能和作用
1. session一個實例代表與數據庫的一次操作。(當然一次操作可以使crud組合)
2. session實例是通過SessionFactory獲取,用完需要關閉。
3. session實例是線程不同步的(不安全),因此要保證在同一線程中使用,可以用getCurrentSession()
4.session可以看做是持久化管理器,它與持久化操作相關的接口。
get() 和load()區別
1. get()方法直接返回實體類,如果找不到數據則返回null。load()會返回一個實體代理對象(當前這個對象可以自動轉化為實體對象)。但當代理對象被調用時,如果數據不存在,就會拋出org.hibernate.ObjectNotFoundException異常。
2. load先到緩存(session緩存/二級緩存)中去查,如果沒有則返回一個代理對象(不馬上到DB中去找),等后面使用這個代理對象操作的時候,才到DB中去查詢,這就是我們常說的load在默認情況下支持延遲加載(lazy--->我們可以在配置文件中關閉懶加載功能).
懶加載
簡述:當我們查詢一個對象的時候,在默認情況下返回的只是該對象普通屬性,當用戶使用哪個對象屬性是才會向數據庫中發出一次查詢,這種現象我們成為懶加載現象
禁用懶加載功能
方法一:(配置文件中進行禁用)
<class name="Student" lazy="false">
方法二:(顯示初始化,Hibernate.initizlize(代理對象))
Hibernate.initlalize(stu.getDept());
方法三:(通過過濾器openSessionview解決)
3. get先到緩存(session緩存/二級緩存)中去查,如果沒有就到DB中去查(即馬上發出sql)。總之,如果你確定DB中有這個對象就用load(),不確定就用get()(這樣效率高)