Hibernate的做用:java
一、 Hibernate解決ORM(對象關係映射)的問題,大大減小了持久層的代碼量python
二、 hql方言,解決了可移植性問題mysql
三、 效率問題,頻繁的鏈接和關閉,自動的封裝JDBC,自動使用鏈接池。Sessionweb
四、 具備緩存功能,節省查詢時間算法
五、 經過設定自動給數據加鎖,事務的隔離級別sql
Hibernate工做在持久層。數據庫
相似Hibernate的框架 apache的OJB,sun公司的JDO,Oracle的Toplink, apache 和 Google的Ibatis。apache
POJO:領域模型也叫實體類。包括表單bean和結果bean(不繼承任何類)windows
<struts1.0的Form 不屬於POJO,由於繼承了ActionForm類>數組
1、爲何要用Hibernate?
Java中的對象,數據庫中的關係。Hibernate主要是實現對象模型和關係模型直接的轉換。輕量級的封裝。面向對象的思想。
由於Hibernate有映射文件,因此才能對數據save,query等操做。
映射文件:***.hbm.xml文件,也叫源文件,把對象模型的繼承、關聯映射成數據庫中的主外鍵關係。
Hibernate自動的保持數據庫在的記錄和對象屬性的值同步(保證一致)。
Hibernate的session是持久化管理器,工做在持久層。
數據庫中的表的主鍵生成策略:
Native:Hibernate根據不一樣的數據庫生成不一樣的主鍵,自動增加
Uuid:UUID.randomUUID().toString(),32位字符串
Asigned:用戶本身生成的
Foreign:外鍵維護叫作參照完整性,維護關係。來自於對象裏關聯屬性的值
GUID:increment,線程不安全,建議不要使用
Identity:使用數據庫本身的自動增加策略,oracle數據庫不支持
對象與對象的關係:關聯(has a )和繼承(is a )
屬性映射:主鍵通常不從對象中拿,要麼從數據庫中生成,要麼hibernate給。外鍵從對象的關聯屬性上拿。(關聯屬性:屬性類型爲自定義類型必定是關聯屬性,若是屬性爲集合類型,該屬性有多是關聯屬性)
Hibernate3.2 的環境搭建:(測試環境—> 真實環境)
測試環境:(不佔內存的,導入的包不在項目裏面,只是作關聯)
一、 建立java Project
二、 建立公共的jar包, windowsàjavaàUser Libraryà包名
三、 在建立的包下導入jar包,(主jar包和lib下的全部jar包+mysql的jar包)
四、 導入項目庫,右鍵—>propertiesàjava buildPathà選擇本身建立的存放jar包的包名
五、 建立source folder (src文件夾)
六、 把ext文件夾下的hibernate.cfg.xml.,log4j.properties文件放在src下
七、 在hibernate.cfg.xml文件中配置持久化數據庫信息,配置方言(不一樣的數據庫有不一樣的方言)<property>
八、 導入映射文件<mapping resource=」com/hibernate/User.htm.xml」/>:將User對象和數據庫中的表進行ORM(對象關係映射)
真實環境:將jar包直接導入WEB-INF/lib下,配置信息與虛擬環境相同
Hibernate小案例
一、 建立實體類:POJO(表單bean、結果bean)User
二、 建立和實體類對應的元文件 User.hbm.xml(可能多個實體類對應一個元文件)
<hibernate-mapping>
<class name=」類的全路徑名 cn.bean.User」>//根據類名建立表 user
<id name=」id」>//id映射表示該屬性爲數據庫中的表的主鍵
<generator class=」uuid」>//generator 表示主鍵的生成策略。設置主鍵爲uuid,Hibernate自動生成
</id>
<property name=」name」/>//普通屬性用property映射,在表中的字段也叫name
</class>
</hibernate-mapping>
數據庫中主鍵映射:
若是在對象中爲String id,generator通常爲uuid,32位碼,Hibernate自動生成
若是在對象中爲int id,generator通常爲native,數據庫中自動生成,自增。
Hibernate會自動根據表單bean建立和表單bean類名相同的表。
會根據表單bean中的屬性自動建立表的字段,數據類型會自動解析。Int->int,Stringàvarchar,util Date à datetime 類型相對應。長度取默認值。
DDL:數據庫定義語言、DCL數據庫控制語言、DML:數據庫操縱語言
DDL(data definition language):
DDL比DML要多,主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定義或改變表(TABLE)的結構,數據類型,表之間的連接和約束等初始化工做上,他們大多在創建表時使用
DML(data manipulation language):
SELECT、UPDATE、INSERT、DELETE,這4條命令是用來對數據庫裏的數據進行操做的語言
DCL(Data Control Language):
是數據庫控制功能。是用來設置或更改數據庫用戶或角色權限的語句,包括(grant,deny,revoke等)語句。在默認狀態下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人員纔有權力執行DCL
Hbm2ddl工具類:
Configuration cfg=new Configuration().configure();//將src下的hibernate.hbm.xml文件讀到cfg中
Configuration cfg=new Configuration();//將src下的hibernate.properties文件讀到cfg中
SchemaExport export=new SchemaExport(cfg);
export.create(true,true);//按照User.hbm.xml文件中配置在數據庫中生成對應的表
Hiberante的配置信息:
<hibernate-configuration>
<session-factory>
<!—配置數據庫的鏈接信息-- >
<property name="hibernate.connection.url">jdbc:mysql://localhost/hibernate_fist</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">mysql</property>
<!—配置mysql數據的方言 -- >
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!—配置顯示sql語句,建表,查詢之類的,爲了方便跟蹤sql執行 -- >
<property name="hibernate.show_sql">true</property>
<!—配置表的自動生成策略,若是表不存在就建立,存在就再也不建立-- >
<property name="hibernate.hbm2ddl.auto">update</property>
<!—配置映射文件信息,用於數據庫中的表的生成-- >
<mapping resource="com/hibernate/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
JDK動態代理:對目標類實現同一個接口的代理(經過反射實現)
Cglib代理:代理類是目標類的子類
Hibernate使用的是鏈接池,dbcp鏈接池(內建鏈接池)
autocommit(),自動提交事務默認爲false,須要手動提交事務
測試類:把對象變成表中的記錄
一、 Configuration cfg=new Configuration().configure();//讀取配置文件,xml
二、 SessionFactory factory=cfg.buildSessionFactory();//建立工廠,session工廠是重量級的,線程安全,很是耗時,原則上只建立一次。最好是單例+同步。
SessionFactory的做用:生成Session對象,二級緩存,而且能夠配置緩存策略
三、 Session session=null;
session=factory.openSession();//經過工廠拿到Session對象,session是持久化管理器,session是線程不安全的。能夠拿到多個session。
四、 session.beginTransaction();//開啓事務(從數據庫的鏈接池中拿到鏈接),session開啓的事務是session級別的事務,本地事務,局部事務。(經過SessionFactory建立的事務是全局事務,能夠被多個SessionFactory對象使用)
什麼是事務?事務是對數據庫的操做(一致性,原子性,持久性,隔離性)
五、 建立User對象,給User對象賦值
當配置中的<id><generator class =」uuid」/></id>時,本身設定的主鍵是沒有用的。賦值後的user對象是瞬時態
六、 session.save(user);//session會爲user分配主標識,uuid,j將user對象歸入session的緩存,臨時集合insertron中。 保存數據
七、 session.getTransaction().commit();//session 拿到事務後提交事務。在這裏生成sql語句,將user對象中的數據存入數據庫中對應的字段裏。(操縱數據庫)
八、 session.getTransaction().rollback();// 發生異常就catch而且回滾
Hibernate的優勢:
代碼量少,提升生產力
更加面向對象,對象化
可移植性好(改方言,改配置就能夠啦)
支持透明持久化:輕量級,沒有侵入性
事務:
事務分爲總事務和子事務,子事務預提交,總事務發現其中一個子事務出錯就回滾。
公共請求事務對象代理:不是真正的操做只是代理對象。-- > 中間件(代理完成事務的提交)
事務的邊界:session.beginTransaction();
JDNI:java的名稱目錄接口,對鏈接池進行管理。
Hibernate能夠處理多種事務(本地事務,跨資源事務,局部事務),提供統一的事務邊界,(事務邊界由session獲取)
SessionFactory能夠生成全局事務,供多個session使用,而且具備二級緩存功能。
Configuration:封裝和管理hibernate的主配置文件。
一、 封裝和管理 :數據庫的底層信息<property>
二、 封裝和管理:映射文件元信息(元數據)<mapping-resources>
獲取方式:
一、 new Configuration();//拿到hibernate.properties文件
二、 new Configuration().configure();//拿到hibernate.hbm.xml的配置信息
SessionFactory:(session工廠也叫會話工廠)
SessionFactory經過Configuration建立,建立特色:cfg.buildSessionFactory();
SessionFactory功能:
一、 取得持久化對象session(factory.openSession())
二、 生成全局性事務(Tansaction) 事物共享控制安全性
三、 緩存(緩存元數據和不常修改的數據) 緩存後,通常不釋放。
緩存定義:第一次讀取後,第二次訪問時已經存入內存,直接讀取效率會高。
緩存目的:提升對數據庫的訪問效率
SessionFactory性能:
一、 SessionFactory屬於重量級,耗時,通常只在初始化的時候建立一次PlugIn
二、 原則上一個Hibernate能夠管理多個數據庫,每一個數據庫都配置同一個SessionFactory(可是這樣系統效率低,通常一個sessionFactory只管理一個數據庫)
三、 sessionFactory是線程安全的(支持多線程併發訪問操做)
Session:持久化管理器
做用:完成對數據庫的操做 ORM(對象關係映射)
Session的功能:
一、 Session一級緩存,具備緩存功能(緩存後才能對對象進行持久化操做)
二、 保證所管理的對象中的屬性和數據庫中的記錄同步(自動update)
三、 Session能夠取得事務(對session中緩存的數據完成實際的存取)
四、 Session做爲HQL查詢à生成的Query對象,執行hql語句(session.createQuery())
Query query = session.createQuery(「from User」); 其中User是實體類。該語句將把數據庫中t_user表中的所有查詢出來放到query對象中。
五、 Session能夠從鏈接池中拿到鏈接(關閉session,鏈接釋放)
六、 Session的主要做用是對數據庫進行操做
Session的性能:
一、 線程不安全(操做完數據庫就關閉session)
Lifecycle,Validatable:能夠對User請求作監聽,不使用的緣由是由於須要實現接口,太過於重量級了。
UserType(轉換器),Interceptor(攔截器):都屬於輕量級接口,對對象自動攔截,不多用,不須要實現Hibernate的任何接口和類
實際測試環境的搭建:
測試類和被測試類在同一個包下 src
測試類的路徑和被測試類相同
使用Junit測試原則:
一、 測試類繼承TestCase(Junit包下的一個類)
二、 測試類的類名 suffix=*Test(eg:UserTest)
三、 測試類的方法名:test開頭,後面是正常的方法名(eg:testLogin())
四、 測試類的方法不能有參數和任何返回值
調試時在debug—> Junit
Log4j.propeties:是爲了測試的時候打印日誌信息。
持久化對象的生命週期:
對象的三種狀態:瞬時、持久、離線
瞬時態(transient):new User(),剛new 出類的對象處於瞬時態,沒有歸入session的管理,數據庫中沒有對象的記錄,事務提交後這種對象沒法提交到數據庫(和數據庫無關聯)
持久態(presist):session. save(user),這時候user對象處於持久態,session管理,具備主標識,事務對持久態的對象作操做(和數據庫同步)
離線態(Detached):session關閉後對象狀態從持久態變爲離線態。不歸入session管理中,對象中的數據在數據庫中有對應的記錄,對象發生變化,數據庫中的值不會發生變化。(和數據庫不一樣步)
Junit測試和Debug調試
測試類的代碼和hibernate的代碼在同一個包下
Session緩存的特色:
Session session=factory.openSession();//經過session工廠拿到session對象
Variable à 查看actionQueue: session隊列中有三個臨時集合(數組)
一、insertions:elementData session.save();存放要插入的對象
二、updates :elementData session.update();存放要修改的對象
三、deletions:elementData,存放要刪除的對象
Session事務一提交,先到insertion數組中查看是否有對象,若是有對象,就發送有關插入數據的sql語句:insert into()
再從deleteions數組中查看是否有對象,若是有對象就發送關於刪除的sql語句:delete()
再從updates數組中查看是否有對象,若是有對象就發送關於更新的sql語句:update()
這三個集合都是臨時集合,發送完數據就會清空。
PersistanceContext:持久化容器
存放真正緩存中的數據,實體化類對象必定要和數據庫中記錄的狀態一致。
數據結構爲entityEntries 是一個Map
裏面有個Map集合,進入 entries 裏面有個table ,真正歸入session的中的數據存在table裏面。
局部事務:
一、 session.openTransaction();開啓事務,事務一提交,就去找session的actionQueue中的數組,發現哪一個數組中有對象就執行其對應的語句,
二、 User user=new User();建立user對象,user.setName(「zhangsan」);,這時候的user對象沒有id,(若是主鍵的生成策略是uuid,手動設定的id是無效的)這時候還的user對象處於瞬時狀態,沒有歸入session管理
三、 session.save(user);執行這條語句,通常不會發sql語句,只是將user對象歸入session管理,語句執行完後:session中的臨時數組:insertions數組中會有數據。User會分爲兩份存儲:
一份是instance :實例
一份是 state:狀態,session按照state狀態發送數據(執行insert語句)
Insert以後,若是發現實例和狀態的數據不同,會立刻執行update語句
四、 session.save(user)後,查看persistanceContext à entityEntries à map à table(table是HashMap的元素) value有值就繼續進去, 查看如下幾個狀態值:
existsInDatabase的狀態爲false,表示數據庫中沒有數據
這時候id已經被賦值(uuid)
loaderSate:存儲狀態,數據都存在這裏了(這裏的數據必須和數據庫中的數據同步)這就是session緩存的數據。
目前的user對象處於持久態
處於持久態對象的特色:必定有ID(不是全部持久態的數據會存放在臨時數組中,可是持久態的數據必定會在map集合中)
五、 user.setName(「tom」);歸入session管理以後,變爲持久態以後,修改持久態對象的值,
六、 session.commit();將修改後的數據提交,
actionQueue下的insertions數組中: instance實例中的值爲:tom;
state狀態的值爲:zhangsan
persisstanceContext底下的map中:數值仍是zhangsan
會按照insertions數組的state(狀態)的值發送insert into (zhagnsan)到數據庫中
insert語句執行完後,發現instance 和state 中的值不同時,就會再發一條update語句,保證一致。發完sql語句後,臨時集合中的數據會所有清空。
七、 session.commit()後,狀態:persistanceContext à entityEntries à map àtableà[0]àvalues
existsInDatabase的狀態爲true,表示數據庫中已經有了相應的記錄。這時候map中的name會改成tom(由於update了)
八、 session.close(),關閉session,把session中存放的實體對象所有清除,這時候user對象處於離線狀態,user對象還有主鍵,並且這個主鍵和數據庫中的主鍵同樣。離線態的對象在數據庫中有對應的記錄。
瞬時態:沒有歸入session的管理,數據庫中沒有對應的記錄
離線態:沒有歸入session的管理,數據庫中 有對應的記錄。(只要主標識和數據庫中的主鍵相同,主標識的class爲asigned,就爲離線態(new出來的對象也能夠是離線態)),離線態的對象不能直接save()進入持久態。
九、 將離線態變爲持久態:update(),saveOrUpdate(),lock(),從新開啓session,拿到事務,session.update(user); <若是new 出來的對象,本身設定的id和數據庫中主鍵相同,id標識爲assigned,那麼就只能用update方法,讓該對象變爲持久態>
十、 對象從離線態變爲持久態,臨時集合(insertions/updates/deletions)中 都 沒有數據。persistanceContext--àMap集合有數據,可是map中只有一個id記錄,數據庫中也會有相應的數據。
十一、 session.commit();就算沒有修改user對象的屬性值,hiberante也會發送update語句。Update以後,user也被歸入session管理
十二、 session.commit()後,再進入persistanceContext à entityEntries à map à table à[0]àvalues
loadedSate會有相應的user實體對象中的屬性的記錄了。
瞬時態—》持久態:插入(save)
離線態—》持久態:更新( update )
加載:(查詢)將數據庫中的記錄加載到對象中
一、 拿到session(經過靜態方法 自定義的getSession())
二、 User user=(User)Session.get(User.class,」這裏寫數據庫中的主鍵值」);//把關係模型轉換爲對象模型。經過User類中找到對應的表。(這裏是由於session經過sessionFactory建立,sessionFactory經過Configuration建立,Configuration能夠讀取整個hibernate.hbm.xml文件中的配置信息,因此session能夠找到對應的user.hbm.xml文件)找到User.hbm.xml文件。Session經過hbm.xml元數據找到對應的表。經過主鍵找到表中對應的那條記錄,自動生成對象,而且自動將數據庫中記錄的字段設定到user對象中對應的屬性中。
這裏發送了一條sql語句:select * from user where id=」上面的id參數值」;
這就叫作加載(load())。這時候的user對象處於持久態。(和數據庫同步)
三、 user對象已經進入持久態,歸入session管理。這時候緩存域(三個集合 insertions/deletions/updates)中是沒有數據的,persistanceContextà底下的map 的 value中會有數據。
四、 user.setName(「lisi」);//加載後,修改user對象中值。這時候session緩存的臨時集合中沒有數據,persistanceContext中的map中的數據也沒有變化,user對象中的數據被修改了。 Session發現user對象中的值和map中的值不同時,會自動發update語句,保證數據同步。
五、 session.commit();//修改後提交數據。Update以後,數據會變爲和對象中的值同樣。Session緩存中的數據也會變成和user對象中的值同樣。
持久態對象特色:一提交就發update.肯定保證對象和數據庫中數據同步。
處於持久態的對象在數據庫中不必定有對應的記錄。
session.commit()後:數據庫中記錄必定要和persistanaceContextàlocalState中的數據一致
使用get()方法加載數據的缺點:
一、若是從數據庫中找到了指定ID對應的記錄?
若是不須要使用User對象,會浪費內存
二、沒有從數據庫中找到ID對應的記錄?
Hiberante就不會建立對應的User對象,會返回null。
再調用該對象的方法時,會出現NullPointerException
根據以上問題,修改方案:使用Load();
User user=(User)Session.load(User.class,」這裏寫數據庫中的主鍵值」);//從這裏引入懶加載。執行load方法後,不會生成sql語句,意味着尚未訪問數據庫。可是會生成Cglib 代理的User對象,這個User對象是目標User對象的一個子類對象。因此這裏的User類不能定義爲final類型。
Cglib代理的原理:建立目標類的子類對象。
這時候代理User對象的target 值爲null;
user.getName();//調用代理對象的getName()方法,若是不爲null,代理對象會調用父類的getName()方法。而後à發送sql語句à建立User對象。
本週做業:
理解hiberante:juint,debug,生命週期,緩存,
理解物料管理項目
Get方法查詢數據:session.get(User.class,」對應的數據庫中的表的ID」);
一、經過類找表
二、經過主鍵找記錄
三、記錄映射
無論數據存不存在都會發送select語句,若是找到了就會生成user對象,將對應的記錄映射到對象的屬性中,若是沒有對應記錄就返回null。(不支持懶加載)
使用get查詢,會立刻發select語句,select * from user where id=」」;
若是後面再也不修改User對象的值,就不會再發update語句了
若是修改user對象的值就會發送update語句。
這裏主要比較user對象裏面的值和loadedState裏面的值,若是相同就不會發送update語句,若是不一樣就會發送update語句。
懶加載:(最大的問題就是會產生懶加載異常)
使用時再訪問數據庫,不使用數據的時候就不會訪問數據庫
懶加載有類上的懶加載,集合上的懶加載,單端上的懶加載
懶加載不支持多態查詢
悲觀鎖不支持懶加載
Load方法查詢數據:session.load(User.class,」對應的數據庫中的表的ID」);
不會立刻就發select語句,使用的時候纔會發送select語句,若是不使用就不發送
一、 建立目標對象的cglib代理對象(目標對象的子類對象)
目標類User必需要有明確的無參構造方法,(爲目標類建立代理對象,建立子類對象要先調用父類的無參構造方法),目標類不能是final類型
二、 對應的cglib代理對象下,有個target標識,標明目標對象是否生成,沒有生成目標對象的時候 target=null;
三、 執行代理對象的user.getName()時,;先看目標對象產生了沒有(判斷target的值),若是產生了就執行目標對象的getName()方法,若是沒有就從數據庫中查出主鍵所標識的記錄,根據記錄生成目標User對象,再調用該User對象的getName()方法。(生成目標對象後,target=user(目標對象的地址))
Load()方法在數據庫中找不到主鍵所標識的記錄時,會產生異常 :ObjectNotFoundException,而不是NullPointerException。這個異常在load()的時候產生這個異常,可是使用目標對象的時候就會拋出這個異常。
Get()方法在數據庫中找不到主鍵所標識的記錄時,會返回null,再在使用目標對象時,會拋出NullPointerException.
Get和load:
共同點:經過ID(主鍵)去數據庫中找相同的記錄,生成對應的User 對象
不一樣點:
一、 Get:不支持懶加載,立刻發送select語句
Load:支持懶加載,使用的時候才發送select語句
二、 若是使用get,發現主鍵在數據庫中找不到對應的記錄,不會生成user對象,也不會拋出異常,會返回null.(使用對象是拋出NullPointerException.)
若是使用load,發現主鍵在數據庫中找不到對應的記錄時, 使用時會拋出ObjectNotFoundException。
通常使用load,不多使用get.
刪除:(刪除的內容:數據庫中的數據)
先查 à 後刪
User user=(User)session.load(User.class,」ID」);//對象處於持久態
session.delete(user);//在session的緩存persistanceContext中的map集合中有該對象,existInDatabase=true,loadedState有該對象;
臨時集合中的deletions中有該對象,
User代理對象中也有數據。target=user;
session.getTransaction().commit();//這裏發送delete語句(進入瞬時態)
臨時集合deletions中數據被清空
Session裏面的persistanceContextàmap中沒有數據
數據庫中的數據也沒有
User對象依舊存在,target=user.
刪除了User對象對應的數據庫中的表的記錄,刪除後對象進入瞬時態。(不歸入session管理,數據庫中的記錄也沒有了)
(課堂做業:完成hiberante的簡單的增刪改查)
查看錶中的全部記錄:(Query)
Query query=session.createQuery(「from User」);//hql語句,from後面跟的是類名。經過session建立Query對象,實參爲hql語句。從User類所映射的user表中拿到每一條記錄,封裝到Query對象中。
Query對象的做用是:執行hql語句。
List list=query.list();//Query中的list方法,把Query封裝的每條記錄放到List集合中。
Hibernate的基本映射
若是主鍵爲String類型,通常配置爲uuid<generator class=」uuid」/>
關聯映射:
<hiberante-mapping package=」com.hibernate」>//多個實體類在同一個包下,這裏配置了包名,使用時直接寫具體類名就能夠了
<class name=」User」 table=」t_user」>//根據類名爲User的實體類生成表,table:指定數據庫中的表名爲t_user
<id name=」id」 column=」user_id」 length=」33」>//定義主鍵名在數據庫的表中的字段名爲 user_id,長度位33,(最好不要修改主鍵字段長度,使用默認長度255)
<generator class=」uuid」/>//標識主鍵生成策略爲uuid
</id>
/* Name:標識在User類的屬性
Unique:標識該字段在數據庫的表中必須惟一(不能重複)
Not-null:標識該字段在數據庫的表中不能爲null
爲空驗證:user.setName(「」);這裏不會產生異常,空串不爲空。
若是直接不給name賦值,String類型默認值爲null,就會產生NulPointerException
Length:指定該字段的最大長度
Column:指定該屬性映射在數據庫中的表中的字段名爲 t_name
*/
<property name=」name」 unique=」true」 not-null=」true」 length=」232」 column=」t_name」>
</class>
<hibernate-mapping>
主鍵生成策略:(做用/目的:一旦實體類歸入session的管理,session根據實體類的主鍵生成策略生成主標識,這個主標識就是數據庫中的記錄的主鍵)
主標識:
業務主鍵和業務標識:(有意義的惟一標識)--學號
邏輯主鍵和邏輯標識:(沒有意義的惟一標識),由數據庫或者是Hibernate生成uuid
根據實體類的ID類型肯定主鍵生成策略:
若是是字符串,hibernate生成 。uuid
若是是int/long,由數據庫生成。native
用<id>標籤來標識主鍵,後面必需要跟<generator class=」 主鍵生成策略
」>
1、由數據庫生成:
native:根據不一樣的數據庫自動使用不一樣的主鍵生成策略。(不是真正的生成策略,只是選擇對應的生成策略)
identity:(mysql,sqlserver)
sequence:(DB2,Oracle)
2、由程序生成:
increment(遞增):多臺應用服務器,可能會主鍵相沖,不多使用
uuid.hex:Hibernate採用這種方式生成主鍵,多態服務器同時生成主鍵,不會重複;
一、 IP地址
二、 JVM的啓動時間(1/4秒)
三、 系統時間
四、 隨機數(和指令計數器有關)
以上四種數據進行隨機組合,最後生成32 uuid 碼
3、用戶本身維護主鍵:
assigned:用戶本身輸入
4、外部引用
foreign:依賴於另外一個實體類的主鍵。
主鍵生成策略爲uuid: (主屬性類型爲String類型)
1三、 session.openTransaction();開啓事務,事務一提交,就去找session的actionQueue中的數組,發現哪一個數組中有對象就執行其對應的語句,
1四、 User user=new User();建立user對象,user.setName(「zhangsan」);,這時候的user對象沒有id,(主鍵的生成策略是uuid,手動設定的id是無效的)這時候還的user對象處於瞬時狀態,沒有歸入session管理
1五、 session.save(user);執行這條語句,通常不會發sql語句,只是將user對象歸入session管理,語句執行完後:session中的臨時數組:insertions數組中會有數據。User會分爲兩份存儲:
一份是instance :實例
一份是 state:狀態,session按照state狀態發送數據(執行insert語句)
Insert以後,若是發現實例和狀態的數據不同,會立刻執行update語句
1六、 session.save(user)後,查看persistanceContext à entityEntries à map à table(table是HashMap的元素) value有值就繼續進去, 查看如下幾個狀態值:
existsInDatabase的狀態爲false,表示數據庫中沒有數據
這時候id已經被賦值(uuid)
loadedSate:存儲狀態,數據都存在這裏了(這裏的數據必須和數據庫中的數據同步)這就是session緩存的數據。
目前的user對象處於持久態
處於持久態對象的特色:必定有ID(不是全部持久態的數據會存放在臨時數組中,可是持久態的數據必定會在map集合中)
1七、 user.setName(「tom」);歸入session管理以後,變爲持久態以後,修改持久態對象的值,
1八、 session.commit();將修改後的數據提交,
actionQueue下的insertions數組中: instance實例中的值爲:tom;
state狀態的值爲:zhangsan
persisstanceContext底下的map中:數值仍是zhangsan
會按照insertions數組的state(狀態)的值發送insert into (zhagnsan)到數據庫中
insert語句執行完後,發現instance 和state 中的值不同時,就會再發一條update語句,保證一致。發完sql語句後,臨時集合中的數據會所有清空。
session.commit()後,狀態:persistanceContext à entityEntries à map àtableà[0]àvalues
existsInDatabase的狀態爲true,表示數據庫中已經有了相應的記錄。這時候map中的name會改成tom(由於update了)
1九、 session.close(),關閉session,把session中存放的實體對象所有清除,這時候user對象處於離線狀態,user對象還有主鍵,並且這個主鍵和數據庫中的主鍵同樣。離線態的對象在數據庫中有對應的記錄。
瞬時態:沒有歸入session的管理,數據庫中沒有對應的記錄
離線態:沒有歸入session的管理,數據庫中 有對應的記錄。(只要主標識和數據庫中的主鍵相同,主標識的class爲asigned,就爲離線態(new出來的對象也能夠是離線態)),離線態的對象不能直接save()進入持久態。
主鍵生成策略爲native: (主屬性類型爲 int類型)
先看使用的數據庫,而後根據數據庫選擇主鍵生成方式,若是是mysql數據庫:identityà auto_increment
數據庫自動生成主鍵:首先是主屬性爲int類型,大部分都是從1開始(基本上沒有從0開始的數據)。
session.save(user);//就發送sql語句了,insert語句中不會插入ID,ID由數據庫自動生成,人爲設定的sql語句無效。發送sql語句:insert into(),目的是生成主鍵。
一旦發送sql語句了,數據庫中必定會有對應的記錄。
主鍵生成策略爲native,save後的特色:(進入持久態)
一、 session的臨時集合中沒有數據,(臨時集合是爲了發sql語句而存在的)
二、 session的緩存persistanceContext下的map下的existInDatabase=true;表示數據庫中有數據,可是是髒數據。(查不到)
髒數據:未經證實和檢測的數據
事務提交特色:先插入數據,再對數據進行驗證,若是正確就提交到數據庫中,若是有誤就回滾。沒有提交的數據叫作髒數據,mysql事務的傳播特性是可提交讀,看到的數據是提交後的數據,看不到髒數據,因此提交前在數據庫的表中查不到數據。
session.getTransaction().commit();//commit()後纔會檢查數據的正確性。(對數據庫中的髒數據進行確認,若是正確就將髒數據變爲可用數據《在數據庫中能夠查到》,若是不正確就將髒數據回滾。)
主鍵生成策略爲assigned(用戶提供)
主鍵標識 爲String類型
一、 不設置對象的ID值,直接save(),會從user對象中共取ID,發現沒有會拋出異常
二、 2.一、設置ID值
2.二、 session.save(user);從瞬時態進入持久態:不發送sql語句,user對象歸入session緩存,存放在session的臨時集合中的insertions中,session的緩存persistanceContext下的map的loadedState下也會有數據。數據庫中沒有數據,existInDatabase=false;
2.三、 session.getTransaction().commit();臨時集合中的數據清空,數據庫中有數據。existInDatabase=true;
經過配置讓數據庫自動生成表:
<property name=」 hiberante.hbm2ddl.auto」>update</property>
做業:分析三種主鍵生成策略在session緩存中的異同:
主鍵生成策略爲native和uuid,assigned的異同:
native:根據數據庫不一樣而自動選擇對應數據庫的主鍵生成策略。好比:mysql數據庫à主鍵生成策略爲:identity.主屬性類型爲int類型。(用戶手動設定的ID無效)
uuid:將IP地址,JVM的啓動時間,系統時間以及和指令相關的隨機數相結合隨機生成32位uuid碼。主屬性類型爲String類型。(用戶手動設定的ID無效)
assigned:用戶在建立對象的時候,手動設定主鍵(ID的值)。主屬性類型爲String類型,若是不設定ID 就會拋出異常。
session.getTransaction();//開啓事務
User user=new User();//建立User對象
user.setID(「123」);
user.setName(「zhangSan」);
這時候三種主鍵生成方式(native,uuid,assigned)都相同: user對象處於瞬時態,沒有歸入session管理(臨時集合和session緩存map中都沒有user對象的相關數據)
session.save(user);
相同點:將user對象歸入session管理,進入持久態。
native:(發送insert 語句)。臨時集合中沒有數據,session緩存的map下的table中有數據,loadedState中有user對象值,existInDatabase=true,user對象依舊存在,user對象的ID值變爲數據庫中自增的ID值。
uuid:臨時集合insertions中的instance和state中都有數據,ID爲hibernate自動生成的32位碼,session緩存的map下的table中有數據,loadedState中有user對象值,existInDatabase=false,user對象依舊存在(ID爲32位碼,其他屬性值不變)
assigned;臨時集合insertions中的instance和state中都有數據,ID爲用戶手動設定的值,session緩存的map下的table中有數據,loadedState中有user對象值,existInDatabase=false,user對象依舊存在(值和設定的同樣)。
user.setName(「Tom」);//修改進入持久態後的數據
相同點:user對象中的name屬性值變爲Tom,session 緩存的map下的table中的值不變。
native:臨時集合中依舊沒有數據。
uuid: 臨時集合insertions中的instance 指定的name屬性改變爲 Tom, state指定的name屬性依舊爲zhangSan,
assigned: 臨時集合insertions中的instance 指定的name屬性改變爲 Tom, state指定的name屬性依舊爲zhangSan。
session.getTransaction().commit();//提交事務
相同點:session緩存的map下的table中有數據,loadedState中user對象的name屬性值爲Tom,existInDatabase=true,
native:(發送update語句), user對象依然存在。
uuid: (發送兩條sql語句,先insert,後update)臨時集合insertions的數據清空, user對象依然存在。
assigned: (發送兩條sql語句,先insert,後update)臨時集合insertions的數據清空, user對象依然存在。
session.close();//關閉事務
相同點:session緩存map下的table中的數據被清空, user對象依然存在。user對象處於瞬時態。
native:臨時集合中一直沒有數據,
uuid:臨時集合中數據被清空,
assigned:臨時集合中數據被清空
關聯屬性:多對一:(Many-to-one)
關聯屬性的特色:
若是屬性的類型時自定義類型,那麼這個屬性就是關聯屬性
若是屬性的類型時集合或者數組,那麼這個屬性有多是關聯屬性
對象模型經過關聯屬性創建關聯關係
關係模型的關聯關係經過外鍵創建關係
誰往外鍵裏存放數據,誰就在維護關係。
核心技術:配置*.hbm.xml
關聯屬性的映射:(外鍵)
<many-to-one name=」group」 column=」groupid」 cascade=」」/>
column=」groupid」:這個值的設定參照group屬性對應的Group對象的id屬性。
這是怎麼知道引用的表是group表的主鍵?
由於User實體類中,有Group類型的group屬性。根據關聯屬性找到引用的外鍵表。
存儲數據:(主鍵生成策略都爲native)
Group group=new Group();//先建立Group對象,
group.setName(「zte」);// 給該對象的屬性賦值,(id自動分配)
session.save(group);//發送insert into 語句,取得id
將Group對象的地址賦值給User對象的group屬性中。
User user=new User();//再建立User對象,
user.setName(「zs」);// 給該對象的屬性賦值,(id自動分配)
user.setGroup(group);//經過user拿到group屬性所指的Group對象,取出該Group對象的id值,將取到的id設定到groupid字段中。(groupid是 hbm.xml文件中 many-to-one 標籤中的name指定的字段名)
session.save(user);//數據庫發送sql語句,insert into ,生成id。將user對象屬性的屬性值設定到數據庫中的user表中的對應的記錄的各個字段中。
cascade(級聯屬性): all,update-save,delete,none
加載:(將關係模型轉換爲對象模型)
User user=(User)session.load(User.class,1);
將從數據庫中查出來的記錄,將字段依次賦值給User對象的對應屬性。
Many-to-one:經過外鍵(groupid)找到對應的 Group對象對應的group表中的記錄,根據記錄再建立Group對象賦值,將Group對象設定到User對象的group屬性中。(經過外鍵找主鍵)
user.getName();//zs,這叫類上的懶加載,只發送一條sql語句,只查詢user表
user.getGroup().getName();//這就叫單端上的懶加載,用到兩張表的時候查詢兩張表
單端上的懶加載在外鍵設定的標籤上,<set><many-to-one>
外鍵能夠爲空,若是不爲空,必定是所關聯表的有效值(通常爲關聯表的主鍵)
TransientObjectException:持久態對象引用了瞬時態對象
若是不給Group對象的id屬性設值,那麼默認值爲0 ,
不save Group對象。Group對象爲瞬時態。id依舊爲默認值0;
建立User對象,給user對象的各個屬性賦值。
session.save(user);//發送sql語句,insert into
將Group對象的id屬性值(0)設置到User對象中的group屬性所指Group對象的id ,對應數據庫中的字段爲groupid上。
Mysql數據庫的主鍵不能爲0 ,因此這就違反了參照完整性。
session.getTransaction().commit();
提交,就檢查數據,發現錯誤就回滾。(commit之前的數據都是髒數據)
這裏的user對象處於持久態,user對象使用了瞬時態數據 group對象,瞬時態的對象的id是無效值。因此會出現TransientObjectExecption.
主要緣由:持久態對象引用了瞬時態對象,就會出現TransientObjectException
結論:持久態對象不能引用瞬時態對象。
解決辦法:
2、添加級聯屬性。
<many-to-one name=」group」 column=」groupid」 cascade=」all」>//持久態對象引用瞬時態對象,加了cascade時,會自動先把瞬時態對象save進入持久態。在執行session.save(user)這條語句時,會先執行 insert into group(),再執行insert into user();
cascade:(級聯屬性)
cascade的取值:all、none、save-update,delete
cascade的做用:存儲,而不是爲加載服務的。
級聯:兩個對象中的操做聯動性,對一個對象操做後,對其指定的級聯對象也須要執行相同的操做。(對象的連鎖操做)
all:全部狀況下執行級聯操做
none:任何狀況下都不執行級聯操做
delete:在刪除的時候執行級聯操做(容易出錯)
save-update:在保存和更新的時候執行級聯操做
Hql語句對全部的數據庫都同樣:
Query query=session.createQuery(「from User user where user.group.name=’zte’ 」);//從數據庫中找到User類對應的t_user表,從user表中找到user對象的group屬性所指定的Group對象對應的t_group表,找出t_group表中的name=‘zte’的那條記錄。
至關於sql語句;
select * from t_user ,t_group
where t_user.groupid=t_group.id
and t_group.name=’zte’;
一對一單向關聯:
對象模型:
Person: (類)
int id;
String name;
IdCard idCard;//關聯屬性
IdCard:(類)
int id;
String cardNo;
關係模型:
Person表和idCard表的主鍵相同,一 一對應
(兩個表的主鍵相同並不意味着兩個表有關係)
三大問題:(在*.hbm.xml文件中體現)
一、如何創建關係
二、如何存儲關係
三、如何加載關係
創建關係:(在數據庫中建表時)
t_person:(表)
具備關聯屬性的那個對象生成對應的表時,須要設定主鍵生成策略爲 foreign:t_person表中這樣設定,使得對象一 一對應。
<id name=」id」>
<generator class=」foreign」>
<param name=」property」>idCard</param>//引用的是idCard屬性的所指的IdCard對象的主標識 (id屬性值)
</generator>
</id>
一旦歸入session的管理,person對象的id須要和IdCard的id相同。(經過session保證一對一關係:執行save語句的時候將IdCard對象的id賦值給person對象的id)
必須加約束: <one-to-one name=」idCard」 constrained=」true」/> (建立t_person表的時候不會建立idCard字段,這裏表示引用的外鍵是當前t_person表的主鍵,外鍵參照idCard屬性對應的IdCard對象所映射的t_idCard表。)
一、 創建關係:表示當前person類所映射的表的主鍵就是外鍵,
二、 存儲關係:外鍵參照idCard屬性對應的類所映射的表生成
三、 加載關係:one-to –one ,經過主找主,爲當前映射屬性idCard賦值
若是不添加 constrained=true,在建立表的時候會缺乏 foreign key(id) references t_idCard (id)語句,就不會有外鍵。這樣t_person表和t_idCard表就沒有任何關係了。
t_idCard:(表)
和普通表同樣,沒有外鍵,主鍵爲id.<id name=id>
<generator class=」native」 /></id>
存儲關係:(在生成對象時創建存儲關係)
一、 先建立IdCard對象,
IdCard idCard=new IdCard();
idCard.setCardNo(「123」);
session.save(idCard);//發送sql語句,生成id主標識,歸入session管理
二、 建立Person對象
Person person =new Person();
person.setName(「tom」);
person.setIdCard(idCard);
session.save(person);//這裏不發送sql語句,(由於不須要數據庫爲之生成id)直接從Person對象的idCard屬性所指的IdCard對象中拿到主標識(IdCard對象中的id屬性值)設爲Person對象的主標識(id屬性)。從這裏創建了對象和對象之間的一對一關係。臨時集合中有數據(insertions),數據庫中沒有數據。
commit()就把Person的id屬性映射到t_person的id字段上。
主標識:爲對象中的id
主鍵:爲數據庫表中的id
三、 t_person表在數據庫中存儲Person對象的idCard(關聯)屬性的時候:把關聯屬性的值存在數據庫的表的外鍵上。 往對應的外鍵設值,經過映射發現,存儲idCard屬性的時候,外鍵就是主鍵 id。往主鍵id上設定值,t_person表的主鍵id值來自於idCard屬性所指的IdCard對象映射的t_idCard表的主鍵(id).
One-to-one:主鍵關聯,自動級聯。cascade=」all」
倘若在session.save(person);語句執行以前,不執行session.save(idCard)語句,
IdCard對象尚未歸入session管理,也沒有爲 IdCard對象分配主標識,IdCard對象處於瞬時態。
一、 執行session.save(person)時,發現IdCard沒有主標識,Person對象沒法拿到主標識。
二、 自動先發送IdCard的sql語句,insert into t_idCard (),先爲IdCard對象分配主標識(IdCard對象歸入session管理,進入持久態,IdCard在數據庫中有數據)
三、 將IdCard的主標識賦給Person對象的主標識,Person對象歸入session管理,進入持久態。Person在數據庫中沒有數據,由於person尚未發送sql語句,因此Person對象在臨時集合insertions中有數據。
加載關係: one-to-one(主找主)
Session.load(Person.class,1);
經過t_person表的主鍵找到所關聯的t_idCard表的主鍵對應的記錄,建立IdCard對象,並將該IdCard對象賦值給Person對象的idCard屬性。其他屬性直接賦值。
Session不容許同一個類的不一樣對象使用相同的ID。
若是兩個不一樣的對象引用同一個對象的id,會拋出異常。
例如:
IdCard idCard=new IdCard();//建立Idcard對象
idCard.setCardNo(「123」);
session.save(idCard);//發送sql語句,生成id主標識,歸入session管理
Person person =new Person();//建立第一個Person對象
person.setName(「tom」);
person.setIdCard(idCard);
session.save(person);//不會發送sql語句,將IdCard對象的主標識id給該Person對象
Person person1 =new Person();//建立第二個Person對象
person.setName(「lisa」);
person.setIdCard(idCard);//引用同一個IdCard對象
session.save(person1);// //不會發送sql語句,將IdCard對象的主標識id給該Person對象,因此在這裏會拋出異常,由於one-to-one不一樣對象不能引用同一個對象的id.
一對一主鍵雙向關聯: 兩張表中的數據能夠相互查詢
IdCard.hbm.xml中:主鍵生成策略爲native
這裏的person在實體類中爲Person類型。
<one-to-one name=」person」>:單獨使用不會在建立表的時候不會指定外鍵,也不能往外鍵中設值,只會加載關係,給Person屬性賦值。
person 不是關聯屬性,
Person.hbm.xml中:主鍵生成策略爲foreign
這裏的idCard在實體類中爲IdCard類型。
<one-to-one name=」 idCard」 constrained=」true」>:指定外鍵就是主鍵,外鍵的生成參照idCard屬性對應的IdCard類映射的idCard表。還能夠加載。
idCard爲關聯屬性,
創建關係:兩張表的關係依靠Person類中的idCard屬性創建關係。
維護關係:存儲Person的idCard屬性時,外鍵就是t_person的主鍵,維護關係。
加載關係:
session.load(IdCard.class,1);
IdCard類中的person屬性經過one-to-one(主找主),到t_person表中找到對應的記錄,建立Person對象給IdCard類的person 屬性賦值。
不是全部的類中的自定義類型都是關聯屬性,當前IdCard類中的person屬性就不是關聯屬性,
一對一惟一外鍵單向關聯:(外鍵 unique=」true」)
讓外鍵惟一,兩條記錄即是一 一對應。
Person的配置:使用 many-to-one.配置
<generator class=」native」/>
<many-to-one name=」idcard」 unique=」true」>//在映射關聯屬性建立關係的時候會在表中建立字段idCard,關係經過many-to-one :建立外鍵,外鍵參照idCard屬性的類所映射的t_idcard表。 設定unique=」true」,使得外鍵惟一。
<many-to-one name=」idcard」>:必定要先save所引用的對象,它不會自動級聯,在數據庫的表中會生成idcard字段。
<one-to-one name=」idcard」>指定主鍵就是外鍵,能夠自動級聯,在數據庫的表中不會生成idcard字段。
創建關係:
<one-to-one name=」idcard」>至關於:<many-to-one unique=」true」 name=」idcard」 cascade=」all」> 由many-to-one創建關係,指定idcard爲外鍵。
加載關係:
Many-to-one:找到idCard在t_idcard表中對應的記錄,生成IdCard對象,設定到Person對象中的idcard屬性上。
存儲關係:
Person存儲關係:存儲Person的idcard屬性值,往數據庫的idcard字段設值是存儲,由於idcard字段是外鍵。(若是往不一樣的對象的idcard屬性中存同一個的idcard對象,因爲設定了unique=」true」,就會拋出異常,外鍵重複。)
在這時候 持久態對象引用瞬時態對象,因爲瞬時態對象的默認id值爲0,不影響數據庫的存儲,在save()的時候不會產生異常,commit()的時候數據庫就會檢測0爲非法數據就會拋出異常。
一對一惟一外鍵的雙向關聯:
Person表的映射不變:
int id;
IdCard idcard;//關聯屬性,創建關係,維護關係,加載關係(外鍵)
<id name=」id」><generator class=」native」/><id>
<many-to-one name=」idcard」 unique=」true」>
IdCard表的映射:
int id;
String cardNo;
Person person;//不創建關係,不維護關係,只加載關係(非外鍵)
<id name=」id」><generator class=」native」/><id>
<property name=」cardNo」/>
<one-to-one name=」person」 property-ref=」idcard」/ >
property-ref=」idcard」:將位置指針挪到t_person表中的idcard屬性上。
property-ref:表示引用person屬性對應的Person對象所映射的t_person表中的idcard字段。
加載關係:
給IdCard的person屬性設值,one-to-one
主找主找不到對應的數據,property-ref=」idcard」,位置指針挪到idcard字段上,找到idcard字段對應的那條記錄,生成Person對象,將該person對象賦值到person屬性中,
MyEclipse快捷鍵:
Ctrl+shift+T:查找類
Ctrl+F:查找類中的具體方法
one-to-many 單向關聯:
一對多:由一的一方來創建關係,存儲關係,使用關係(加載)。
外鍵必定在多的一方。
一怎麼管理多:創建集合,管理多方(Set集合)
爲何不是List集合和map集合,由於Hibernate爲Set集合重寫了懶加載。
一個班級多個學生
Classes 類:(一方)外鍵關係建立方、維護方、使用方
int id;
String name;
Set students;
Student類:(多方)外鍵關係存放方
int id;
String name;
Set 集合能夠存聽任意類型的數據。(關聯屬性)
創建關係:
t_classes表在映射關聯屬性的時候,會建立外鍵,而且把外鍵設定在t_student表中,因此t_student表中有classesid字段。可是這個外鍵的關係由Classes維護。
Student.hbm.xml
<id name=」id」><generator class=」native」/></id>
<property name=」name」/>
Classes.hbm.xml
<id name=」id」><generator class=」native」/></id>
<property name=」name」/>
<set name=」students」>
<key column=」classesid」/>
<one-to-many class=」cn.hiberante.bean.Student」/>
</set>
一、 用set標籤映射關聯屬性,當前Classes對象中的students屬性
二、 建立Classes對象的時候在Set集合中存放 Student類型的數據
三、 建立外鍵字段 key column=」classesid」,加在<one-to-many>指定的class àStudent類所映射的t_student表中
四、 增長的字段classesid爲外鍵,經過 one-t-many:外鍵與當前表創建關聯,外鍵參照t_classes表
經過上面的映射:
t_student表中有三個字段, id,name,classesid;
t_classses表中有兩個字段:id,name
維護關係:(關係存儲,往外鍵裏面設值) native
1、建立多個學生對象(兩個或者兩個以上)
Student stu=new Student();
stu.setName(「tom」);
session.save(stu);//id 生成,name爲tom,外鍵classesid爲空,進入持久態,歸入session管理(外鍵能夠爲空) insert into t_student(name) values(?);
Student stu1=new Student();
stu.setName(「Jam」);
session.save(stu1);//id 生成,name爲Jam,外鍵classesid爲空,進入持久態,歸入session管理(外鍵能夠爲空)insert into t_student(name) values(?);
2、建立Set集合,將Student對象添加到集合中
Set students =new HashSet();
students.add(stu);
student.add(stu1);
3、建立Classes對象
Classes classes=new Classes();
classes.setName(「java1」);
classes.setStudents(students);//將建立的Set集合students設到Set集合類型屬性students中
session.save(classes);//發送sql語句,id自動生成,把Classes的name屬性存在t_classes表中的name字段上。insert into t_classes(name) values(?);
關聯屬性的存儲:(外鍵設值)
一、 從students這個Set集合中拿到第一個元素stu,取得stu對象,
二、 拿到stu對象主標識,(由於stu已經save過了,因此能取到該對象的主標識)
三、 經過stu對象的主標識找到數據庫中t_student表中對應的記錄
四、 存儲的時候拿到Classes對象的主標識id,(由於classes對象已經save)
五、 將Classes對象的主標識(id值)設定到stu對象對應的t_student表中的對應記錄的classesid字段上。
第二個元素stu1和第一個同樣;
session.beaginTransaction().commit();//提交後會發送update語句,有多少個學生對象會發送多少條update語句,update t_student set classesid=?where id=?;
加載關係:(將數據庫中查出來的記錄給對象賦值)
session.load(Classes.class,1);
classes.getName();//直接取出記錄中的name字段的值設置到name屬性中
classes.getStudents();
one-to-many,主找外:(一個對應多個)
一、 經過主鍵到對應的t_student表中找到外鍵,找到對應的第一條記錄
二、 建立Student對象,
三、 建立Set集合(有記錄時纔會建立)
四、 將Student對象add到Set集合中
五、 而後繼續經過主找外,依次找到全部的Student對象都分別add到Set集合中
六、 最後把Set集合設置到Classes對象的students屬性上。(students屬性爲Set類型)
One-to-many單向映射的缺點:
一、 若是t_student表中的classesid字段設置爲非空,就沒法保存數據
二、 由於不在Student這一端維護關係,全部Student不知道是哪一個班的
三、 須要發出多餘的update語句(數據庫負載大)
one-to-many 雙向關聯:
創建關係:
Student 類
int id;
String name;
Classes classes;
Student.hbm.xml文件:
<id class=」id」><generator class=」native」/></id>
<property name=」name」/>
<many-to-one name=」classes」 column=」classesid」>
讓外鍵重名,保證關係維護的一致性。
Classes 類:
int id;
String name;
Set students;
Classes.hbm.xml文件:
<id class=」id」><generator class=」native」/></id>
<property name=」name」/>
<set name=」students」 inverse=」true」 cascade=」all」>
<key column=」classesid」/>
<one-to-many class=」cn.hiberante.bean.Student」/>
</set>
Key指定的字段名必須和<many-to-one name=」classes」 column=」classesid」>指定的同樣
外鍵的關係由一端維護,兩頭使用關係。
雙向關聯時,由多的一方存儲關係,維護關係。
<set name=」students」 inverse=」true」 cascade=」all」>
<key column=」classesid」/>
<one-to-many class=」cn.hiberante.bean.Student」/>
</set>
讓多的一方(Student)維護關係。添加inverse=」true」
維護關係:(關係存儲,往外鍵裏面設值)
1、建立多個學生對象(兩個或者兩個以上)
Student stu=new Student();
stu.setName(「tom」);
session.save(stu);//id 生成,name爲tom,外鍵classesid爲空,進入持久態,歸入session管理(外鍵能夠爲空)Student能夠維護關係可是不維護關係。Classes不設值就爲null. insert into t_student(name,classesid) values(?,?);
Student stu1=new Student();
stu.setName(「Jam」);
session.save(stu1);//id 生成,name爲Jam,外鍵classesid爲空,進入持久態,歸入session管理(外鍵能夠爲空)Student能夠維護關係可是不維護關係。Classes不設值就爲null. insert into t_student(name,classesid) values(?,?);
2、建立Set集合,將Student對象添加到集合中
Set students =new HashSet();
students.add(stu);
student.add(stu1);
3、建立Classes對象
Classes classes=new Classes();
classes.setName(「java1」);
classes.setStudents(students);//將建立的Set集合students設到Set集合類型屬性students中
session.save(classes);//發送sql語句,id自動生成,把Classes的name屬性存在t_classes表中的name字段上。insert into t_classes(name) values(?);
這時候Classes想維護關係,可是看到inverse=」true」,就沒法維護關係,Student能夠維護關係可是不維護。建立的表 t_student 中的 classesid列爲null.
Inverse的做用:(值爲true/false)
一、 指定誰反轉另外一方來維護關係
二、 主要使用在set集合標籤上
三、 主要用於存儲關係
正確流程:
先建立classes對象,只給name屬性設值;(id,set集合都不設值)
再建立Student對象,每一個值都設值(id,name,classes)
再建立Set集合將Student對象add到set集合中
session.save(classes);//insert into t_classses(name) values(?);
inverse=true:
設定由另外一端往外鍵中設值。(對方來維護關係)
用在Set標籤上
用在存儲上(加載用不着inverse)
一般inverse後面會加 cascade 級聯屬性
cascade=all:
session.save(classes)當Classes對象準備維護關係的時候,inverse屬性爲true,讓Student屬性維護關係.因此就自動發送session.save(stu);就建立Student對象的 id主標識,往外鍵設值(Student對象的classid屬性)à拿到classes所指的Classes對象的主標識設到classesid上。
優勢:沒有發送多餘的sql語句(update)
Inverse和cascade:
一、 都是進行存儲的時候使用
二、 inverse à翻轉,指定由對方來存儲關係
三、 cascade在存儲數據之前,若是須要存儲另外一種數據,就會自動的發送save語句,先存儲另外一種數據再save當前對象。
四、 inverse:是關聯關係的控制方向,用在set標籤上
五、 cascade操做上的連鎖反應,用在 one-to-one,many-to-one,one-to-many標籤上
週末任務:
一、 整理出Hibernate案例,把筆記分析都添加到對應的案例上
二、 Many-to-one *
三、 One-to-one *
四、 One-to-many *
五、 Many-to-many
六、 單繼承映射關係
七、 看Oracle視頻,學會Oracle數據庫的基本操做 建表以及增刪改查
八、 複習一下struts1.0,儘可能完善本身的struts框架
九、 找到Hibernate源碼,看看源碼,加強本身對映射關係的理解
多對多---單向關聯:
示例:一我的能夠有多個角色,一個角色能夠由多我的擔當
從關係模型中體現多對多關係:建立中間表
中間表的特色:
至少有兩個外鍵,外鍵分別參照兩張表的主鍵
這兩個外鍵共同生成主鍵à聯合主鍵
兩個外鍵的值都由一個對象的關聯屬性設定,關聯屬性爲Set集合類型。
創建關係:
Role:(類)
int id;
String name;
Role.hbm.xml
<id name=」id」>
<generator class=」native」/></id>
<property name=」name」>
User (類)
int id;
String name;
Set roles
Roles屬性映射指定:
一、 Set裏面放什麼類型的數據
二、 建立第三張表 t_user_role
三、 往第三張表中添加兩個字段(兩個外鍵分別參照兩張表)
User.hbm.xml
<id name=」id」>
<generator class=」native」/></id>
<property name=」name」>
<set name=」roles」 table=」t_user_role」>
<key column=」userid」/>
<many-to-many class=」cn.hibernate.Role」 column=」roleid」>
</set>
一、 指定roles的Set集合中存放cn.hibernate.Role類型的數據
二、 Many-to-many, 須要建立中間表,映射時roles屬性時,建立t_user_role表
三、 <key column=」userid」/>指定外鍵字段userid,把這個字段添加到t_user_role表中
四、 userid這個外鍵字段 參照當前對象所對應的表t_user表
五、 在many-to-many 後面的column=」roleid」 指定外鍵字段 roleid, 把這個字段添加到t_user_role表中
六、 roleid這個外鍵字段參照class指定的Role所映射的t_role表
七、 userid和roleid爲 t_user_role表的聯合主鍵,分別參照t_user和t_role表
存儲關係:
先建立Role對象:
Role role1=new Role();
role1.setName(「zs」);
session.save(role1);//insert into t_tole(name) values (?);
Role role2=new Role();
role2.setName(「ls」);
session.save(role2); //insert into t_tole(name) values (?);
再建立User對象
User user=new User();
user.setName(「wife」);
建立Set集合,將Role對象添加到set集合中
Set set=new HashSet();
set.add(role1);
set.add(role2);
user.setRoles(set);//把存儲Role對象的Set集合設定到roles屬性中
session.save(user);//發送三條sql語句,在t_user_role表中建立兩條記錄
//insert into t_user (name) values (?);
//insert into t_user_role(userid,roleid) values (?,?);
//insert into t_user_role(userid,roleid) values (?,?);
在當前對象Set集合中有幾個元素,就會在t_user_role表中建立幾條記錄
id自動生成,name字段直接映射
存儲User對象的關聯屬性roles:
從roles的Set集合中拿出第一個元素role1,拿到role1對象的主標識,
將role1對象的(主標識)id,設到t_user_role的roleid字段上
拿到當前User對象的id, 設到t_user_role的userid字段上
從roles的Set集合中拿出第二個元素role2,拿到role2對象的主標識,
將role2對象的id主標識,設到t_user_role的roleid字段上
拿到當前User對象的id, 設到t_user_role的userid字段上
加載關係:
關聯屬性的加載,給關聯屬性roles賦值:
一、 many-to-many:分紅一對多(one-to-many),和多對一(many-to-one);
二、 one-to-many(主找外),經過t_user表中 id 找找t_user_role表中對應的userid
三、 經過userid自動找到 roleid字段
四、 many-to-one(外找主),經過roleid字段找到t_tole表中的對應的id指定的行生成記錄
五、 根據記錄生成Role對象,設到Set集合中
六、 繼續重複2-5(直到找完t_user表中指定id對應的t_user_role表中的全部記錄)
七、 將賦完值後的Set集合設定到roles屬性中
多對多---雙向關聯:
創建關係:
Role:(類)
int id;
String name;
Set users
Role.hbm.xml
<id name=」id」>
<generator class=」native」/></id>
<property name=」name」>
<set name=」users」 table=」t_user_role」>
<key column=」roleid」/>
<many-to-many class=」cn.hibernate.User」 column=」userid」>
</set>
User (類)
int id;
String name;
Set roles
Roles屬性映射指定:
四、 Set裏面放什麼類型的數據
五、 建立第三張表 t_user_role
六、 往第三張表中添加兩個字段(兩個外鍵分別參照兩張表)
User.hbm.xml
<id name=」id」>
<generator class=」native」/></id>
<property name=」name」>
<set name=」roles」 table=」t_user_role」>
<key column=」userid」/>
<many-to-many class=」cn.hibernate.Role」 column=」roleid」>
</set>
<set name=」」 table=」t_user_role」>//這裏table指定的表必須同樣
兩張表均可以維護關係,
User維護關係,set集合中存放Role類型的數據,
key column=」userid」,參照 t_user表
<many-to-many class=」cn.hibernate.Role」 column=」roleid」>
roleid參照 class指定的類Role對應的t_role表
Role維護關係,set集合中存放User類型的數據,
key column=」roleid」,參照 t_role表
<many-to-many class=」cn.hibernate.User」 column=」userid」>
user參照 class指定的類User對應的t_user表
建立關係能夠雙方建立,
維護關係只能一方維護
使用關係能夠雙方使用
存儲關係:
存儲的時候,只能由一方維護,只讓一方給關聯屬性設值,設值的一方就是在維護關係。
加載關係:
和單向關聯相似
關聯屬性的加載,給關聯屬性roles賦值:
一、 many-to-many:分紅一對多(one-to-many),和多對一(many-to-one);
二、 one-to-many(主找外),經過t_user表中 id 找找t_user_role表中對應的userid
三、 經過userid自動找到 roleid字段
四、 many-to-one(外找主),經過roleid字段找到t_tole表中的對應的id指定的行生成記錄
五、 根據記錄生成Role對象,設到Set集合中
六、 繼續重複2-5(直到找完t_user表中指定id對應的t_user_role表中的全部記錄)
七、 將賦值後的Set集合設定到roles屬性中
創建關係:
一個父類,兩個子類
一個hbm.xml文件
Hibernate默認 lazy=」true」
Extends.hbm.xml文件的配置:
<!— Animal爲父類,具備id,name,sex三個基本屬性-->
<class name=」Animal」 table=」t_animal」 lazy=」true」>
<id name=」id」><generator class=」native」/></id>
<!--注意元素的順序:discriminator要在property的上面-->
<!—discriminator :爲鑑別器,指定type爲鑑別器字段(not-null=true)。string是Hibernate定義的String類型。在t_animal中增長type字段。類型爲string,在數據庫中會轉換爲varchar類型。自動把 discriminator-value的值自動設定到該字段上。
-->
<discriminator column=」type」 type=」string」/>
<property name=」name」/>
<property name=」sex」/>
<!—Pig繼承了Animal,爲子類,Pig子類有新增的weight屬性
discriminator-value:指定鑑別值,設到鑑別器字段中
-->
<subclass name=」Pig」 discriminator-value=」P」>
<property name=」weight」/>
</subclass>
<!—Bird繼承了Animal,爲子類,Bird子類有新增的height屬性
discriminator-value:指定鑑別值,設到鑑別器字段中
-->
<subclass name=」Bird」 discriminator-value=」B」>
<property name=」height」/>
</subclass>
</class>
Discriminator(鑑別器)的做用:加載時,自動識別記錄生成對應的對象類型。
存儲關係:(識別子類類型,自動往鑑別器字段加鑑別值)
Pig pig=new Pig();
pig.setName(「xiaohua」);
pig.setSex(true);
pig.setWeight(100);
session.save(pig);//id自動生成
一、經過Pig這個類,找到映射文件中的子類subclass name=」Pig」
二、經過subclass子類找到父類class=」Animal」,
三、根據class=」Animal」找到對應的表 t_animal,
四、往Pig對象設定到t_animal表中id指定的記錄上的的各個字段。沒有height屬性的就設爲null,
五、type字段的設值:看到是subclass,就往type鑑別器字段自動設 discriminator-value指定的值。(這個值Hibernate根據配置信息自動設定)
加載關係:
加載子類對象:
session.load(Pig.class,1);// Sql:select * form t_animal where id=1 and type=’P’.
經過*.hbm.xml文件中找,經過Pig找到t_animal表
到表中找到id=1對應的記錄
在查找的時候會自動添加查找條件 type=’P’.
生成的記錄對應的對象爲Pig類型
加載父類對象:
Animal animal =(Animal)session.get(Animal.class,1);
if(animal instanceof Pig) ==true
到t_animal表中查出id=1的記錄,拿到全部字段,
發現type=」P」,知道應該建立Pig類型的對象。
load方法查詢不支持多態查詢,get方法查詢支持多態查詢。
Load支持懶加載,不支持多態查詢:由於load拿到的是cglib代理對象,代理類不是真正的實例 沒法用instenceof判斷類的歸屬
Query支持多態查詢,
session.createQuery(form Animal);
拿出全部的記錄而且生成對應子類對象。(指定類型)
多態查詢:單表查出數據,Hibernate能夠自動鑑別類的數據類型(指定生成對象類型)。
單表繼承惟一缺點是:多了冗餘字段。
若是關閉懶加載,load就支持多態查詢。(直接發送sql語句)lazy=false,默認狀況下lazy爲true。
繼承關係:每個具體的類映射成一張表
Animal爲父類,Pig爲子類。
創建關係:(創建三張表,t_animal,父類對應的表中有三個字段,t_pig,t_bird子類對應的表中只有兩個字段,主類的主鍵也爲外鍵,參照父類的主鍵。對應子類和父類主鍵保持一致)
映射文件:
<class name=」Animal」 table=」t-animal」>
<id name=」id」>
<generator class=」native」/>
</id>
<property name=」name」/>
<property name=」sex」/>
<joined-subclass name=」Pig」 table=」t_pig」>
<!—pid是當前表t_pig的主鍵,也是外鍵參照父類的t_animal表。-- >
<key column=」pid」/>
<property name=」weight」/>
</joined-subclass>
</class>
存儲關係:
Pig pig=new Pig();
pig.setName(「hua」);
pig.setSex(「male」);
pig.setWeight(100);
session.save(pig);
一、經過子類Pig找到父類Animal,再找到Animal對應的表t_animal
二、先往父類的表中存數據,t_animal(id,name,sex)
三、再往子類的表中存數據,將t_animal的id設到t_pig表中的pid字段。t_pig(weight,pid)
<從t_animal中拿到主鍵做爲當前t_pig表的主鍵(由於pid即便主鍵也是外鍵)>
加載關係:
Pig pig=(Pig)session.load(Pig.class,1);
一、先從t_pig中找對應記錄,拿到該記錄的主鍵(先找子表),找到weight字段,設到weight屬性中。
二、再經過外找主,找到t_animal中對應的記錄,將查到的記錄設到pig對象中。Sex,name.
優勢:結構清晰,一個類一張表
缺點:查找效率低,(須要多個表關聯查詢)
繼承映射:每一個子類一張表,父類沒有對應的表
缺點:主鍵生成策略不能爲native,只能爲assigned
Animal爲父類,Pig爲子類。
創建關係:
<!-- abstract=」true」:表示父類不生成表,映射的屬性字段被子類繼承,讓子類應用-- >
<class name=」Animal」 abstract=」true」>
<id name=」id」>
<!—這裏只能爲assigned-- >
<generator class=」assigned」/>
</id>
<property name=」name」/>
<property name=」sex」/>
<union-subclass name=」Pig」 table=」t_pig」>
<property name=」weight」/>
</union-subclass>
</class>
只有t_pig表,子類的映射的表不只有本身的屬性對應的字段,還會有父類的屬性對應的字段。(weight+name,sex)
存儲關係:
Pig pig=new Pig();
pig.setId(2);
pig.setName(「hua」);
pig.setSex(「male」);
pig.setWeight(100);
session.save(pig);
/*這裏不會發送sql語句,由於主鍵生成策略爲assigned,發sql語句是爲了拿到主鍵。這裏的主鍵已經本身手動指定,全部不須要發送sql語句。
Pig對象已經歸入session管理,id爲2.。*/
若是再建立一個Bird,
Bird bird=new Bird();
bird.setId(2);
session.save(bird);//這裏就會拋出異常,由於id已經在session中存在。
同一個類的不一樣對象,一旦歸入session管理,id必須不一樣。
同一個類的不一樣子類對象,id也必須不一樣。不然會出異常。
關於主鍵生成策略:
父類的屬性會被子類繼承,映射在子類的字段中,
父類的主鍵生成策略會被子類繼承。
若是爲native或者是uuid,有數據庫或者hibernate自動生成,只生成一次。
session.save()的時候,會爲父類對象生成主標識
同一個id會被父類的全部子類都繼承做爲子類的主標識id,就會形成多個子類對象引用相同的主標識,就會拋出異常。同一個類的不一樣子類對象,id必須不一樣。
單表繼承:
優勢:效率高
缺點是數據冗餘
一個類映射一張表:
優勢:結構清晰,一個類一張表
缺點:查找效率低,(須要多個表關聯查詢)
一個子類映射一張表:
優勢:結構清晰,效率高
缺點:主鍵不能自動生成,必須制定爲assigned(不符合hibernate)
建議使用第一種,單表繼承.
物料項目:
持久層:繼承映射和關聯映射同時使用
用戶需求: 主鍵生成策略爲assigned
鑑別器字段 discriminator column=」category」 type=」string」
存儲關係:(繼承關係的表中的數據的存儲)
經過子類對象找到子類所在的映射文件,拿到鑑別器值 discriminator-value=pig」
在映射文件中,經過子類找到父類,經過父類找到對應的表 t_data_dict
往父類的表(t_data_dict)中設值。
加載關係:(繼承關係的表中的數據的加載)
session.createQuery(from ItemCategory);
至關於sql語句:select *from t_data_dict where category=itemCategory;
由於查詢的是子類對象,多態查詢,會自動加上鑑別器的值category=itemCategory。
將查詢出來的記錄封裝到Query對象中,再經過.list()方法,將Query對象中的記錄存到list集合中。
關聯映射:(關於關聯屬性的映射- Item對象)
Item:
Private ItemCategory category;
Category爲關聯屬性,經過配置hbm.xml文件
<many-to-one name=」category」>
代表category爲外鍵,參照的是category屬性所屬的類ItemCategory所映射的表,可是因爲ItemCategory爲子類而且在單表繼承映射中,全部參照的是ItemCategory的父類所映射的表t_data_dict。
存儲關係:(關聯映射--存Item對象)
在Action中:
一、 從表單中拿到表單中的參數(普通屬性以及關聯屬性的值)
二、 建立Item對象,將普通屬性設到Item對象中(Item處於瞬時態)
三、 建立ItemCategory對象,ItemCategory category=new ItemCategory();(從數據庫中取出來的數據是離線態,在數據庫中有對應的記錄)
四、 category.setId (categoryId);// categoryId爲從表單中取出來的數據
五、 item.setItemCategory(category);//給關聯屬性賦值
六、 建立ItemUnit對象,ItemUnit unit=new ItemUnit();//離線態
七、 unit.setId(unitId);//unitId爲從表單中取出來的數據
八、 item.setUnitItem(unit);// //給關聯屬性設置
持久態對象能夠引入離線態對象,不會有異常,由於離線態對象在數據庫中有對應的id.
加載(關聯映射—給關聯屬性賦值):
session.load(Item.class,itemNo);
一、 找到hbm文件
二、 找到對應的表 t_items
三、 根據itemNo找到對應的記錄
四、 將對應的記錄設到Item對象中
五、 給關聯屬性ItemCategory賦值,many-to-one,外找主,找到t_data_dict中對應的記錄
六、 根據記錄生成對象,將對象賦值給Item對象中的category屬性
懶加載概念:只有真正使用該對象時,纔會建立對象;對於Hibernate而言,真正使用對象的時候纔會發送sql語句查詢數據庫。
懶加載--lazy主要用於加載數據。
lazy使用的標籤:
<class>:
<peroperty>:
<set><list>:
<one-to-one><one-to-many>:
懶加載的實現原理:產生cglib代理
一、 生成目標類的子類(cglib代理類)
二、 一旦須要使用對象的方法時,判斷target是否等於null(target爲代理對象中的一個狀態標識,默認爲null,對象建立後就會等於對象的類型 target=Group)
三、 若是target不等於null就直接使用
四、 若是target等於null 就發sql語句查詢數據庫根據記錄生成對象再使用。
懶加載的分類:
一、類上的懶加載<class>::true/false,默認爲true,開啓懶加載
二、集合上的懶加載<set>:: true/false/extra 默認爲true
三、單端關聯懶加載<one-to-one><one-to-many>::
:false/proxy/noproxy,默認爲proxy
類上的懶加載:(默認開啓lazy=」true」)
<class name=」Group」 table=」t_group」 lazy=」true」>
Group gourp=session.load(Group.class,1);//執行這條語句時不會立刻發送sql語句
建立的Group對象是cglib代理對象,target=null;
group.getId();//取得id的時候也不會發送sql語句,由於上面查詢條件中已經給出id的值。group依舊是cglib代理對象,target=null。
group.getName();//這時候group仍是cglib代理對象。而後發送sql語句。
cglib代理對象是目標對象的子類,會繼承目標對象的方法,會先判斷目標對象是否存在(target==null)。若是存在就會調用目標對象的方法;若是不存在就會發送sql語句,從數據庫中查詢記錄,根據記錄建立目標對象,執行目標對象的getName()方法。target=Group。
再執行一次 group.getName();這裏仍是代理對象,目標對象是由cglib代理對象調用。會判斷target==null,發現不等於null,就直接調用目標對象的getName()方法。
加載能夠不須要事務,事務只是用在存儲。
Hibernate支持三種事務:局部事務,全局事務,代理事務。
懶加載異常:
懶加載開啓,建立的是代理對象,先不使用代理對象(就不會發送sql語句建立目標對象),執行commit()方法後,執行session.close()方法把session關閉。而後再使用代理對象,須要發送sql語句建立目標對象時,發現鏈接已經關閉,就會拋出異常LazyInitializationException:懶加載初始化異常。
Session是線程不安全的,因此用完了要立刻關閉。
解決方案:
一、 關閉懶加載:可是效率會大大下降
二、 使用ThreadLocal
懶加載的實際應用:物料項目
一、load()方法在lazy=true的狀況下查詢出來的對象都是代理對象,在持久層是不會使用對象的
二、將代理對象下傳到業務層(struts的Action類中)
三、在Action類中經過request.setAttribute(「item」,item);傳到jsp頁面
四、在jsp頁面經過EL表達式輸出。經過item屬性名拿到對應的屬性值 item代理對象。
五、發現要使用的時候就會發送sql語句建立目標對象,可是session已經關閉因此會拋出異常。
正確處理方法:
在持久層不關閉session,使用Filter來管理session的建立和關閉
Public class HibernateFileter implements Filter{}
ThreadLocal hibernateHolder=new ThreadLocal();
一、加載類建立工廠類型的靜態變量(全局)
二、Tomcat一啓動就建立過濾器對象,一旦建立過濾器對象就會執行該對象的init()方法
三、在init()方法中,讀取Hibernate.hbm.xml文件,建立SessionFactory對象。
四、把工廠對象賦值給工廠類型的靜態全局變量
五、在HibernateFilter類中,建立getSession靜態方法,在此方法中獲得session
public static Session getSession(){
Session session=(Session)hibernateHolder.get();//取得當前線程綁定的session
if(session==null){
session=factory.openSession();
hibernateHolder.set(session);//把session和當前線程綁定
}
return session.
}
六、持久層經過HibernateFilte類中的的靜態方法 getSession()拿到session。
七、在實現Filter接口的類中,doFilter方法中:chain.doFilter()執行後對結果進行攔截,關閉session。先判斷當前線程是否已經綁定session,
Session session=(Session)hibernateHolder.get();//取得當前線程綁定的session
若是綁定了,取出的session不爲null,就執行判斷session.isOpen(),若是爲true就繼續執行session.close()方法關閉session,而後再將session從map集合中移除(解除當前線程與session的綁定關係)
try {
chain.doFilter(servletRequest, servletResponse);
} finally {
Session session = (Session)hibernateHolder.get();//從Map中取出session
if (session != null) {
if (session.isOpen()) {
session.close();//關閉session
}
hibernateHolder.remove();//移除session
}
}
ThreadLocal類的分析:
原理:在ThreadLocal類中有一個線程安全的Map,用於存儲每個線程的變量的副本。
public class ThreadLocal{
private Map values = Collections.synchronizedMap(new HashMap());;//線程安全
}
ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal<T>
Thread thraed=Thread.currentThread();//獲得當前線程對象
ThreadLocal:是線程作併發用的技術,主要方法get(),set(T),initialValue(),remove()
1、set(T):--設置到當前線程的局部變量
將當前線程和給session作綁定,線程做爲key,T(session)做爲value存入一個加鎖的線程安全的map集合中。values.put(thread,session);
public void set(Object newValue) {
values.put(Thread.currentThread(), newValue);
}
2、T get():返回當前線程的局部變量
從線程安全的map中集合中取出線程對應的session,若是有就直接用,若是沒有就建立session,再綁定到當前線程上 values.put(thread,session);
public Object get() {
Thread curThread = Thread.currentThread();
Object obj=values.get(currentThread);//先從map集合中取
if (obj == null && !values.containsKey(curThread)){//發現map集合中沒有當前線程做爲key對應的value,就建立再添加到map
obj = initialValue();
values.put(curThread, obj);
}
return obj;
}
3、T initialValue():---返回當前線程的局部變量的初始值
protected Object initialValue(){
return null;
}
4、remove():--移除當前線程的局部變量
map.remove(key);//根據map集合的移除元素的原則,根據key移除,
public void remove() {
values.remove(Thread.currentThread());
}
爲何必定要用線程安全的map?
多個線程併發的拿到session併發的訪問數據庫會形成數據混亂。線程安全的map,使得一次只能有一個線程訪問數據庫。並行變串行。
資源共享:
對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,而ThreadLocal爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。
Hibernate和struts的集成,使用攔截器(Filter)繼承。
OpenSessionInview模式:一直開啓session,直到數據輸出完再關閉。
集合上的懶加載:(extra,true,false)
在<set>標籤上使用,使用set標籤的主要有<one-to-many><many-to-many>
<class>標籤上的懶加載,不影響<set>上的懶加載。
普通屬性的加載是使用類上的懶加載
關聯屬性是使用集合上的懶加載
1、集合上的懶加載開啓lazy=true,類上的懶加載是開啓的 lazy=true:
session.load(Classes.class,1);//生成的是代理對象,target=null,不會發送sql語句
classes.getName();//只發一條sql語句,訪問t_classes,只給普通屬性設值
Set students=classes.getStudents();//不會發送sql語句訪問t_student表,說明當前students是PersistantSet(相似cglib)代理對象,不是真正存放Student對象的Set集合。
Iterator it=students.iterator();
While(it.hasNext()){
Student stu=it.next();
stu.getName();//真正使用到Set集合中的元素時纔會發送sql語句訪問t_student表
}
2、集合上的懶加載關閉lazy=false,類上的懶加載是開啓的 lazy=true:
加載Set集合類的students屬性和加載普通屬性同樣。
session.load(Classes.class,1);//生成的是代理對象,target=null,不會發送sql語句
classes.getName();//發送兩條sql語句,訪問t_classes,t_student表(後面都不發送sql語句了)
3、集合上的懶加載關閉lazy=false,類上的懶加載關閉lazy=false:
session.load(Classes.class,1);//生成的是目標對象,會發送兩條sql語句,分別訪問t_classes,t_student表
4、集合上的懶加載開啓lazy=true,類上的懶加載關閉lazy=false:
session.load(Classes.class,1);//生成的是目標對象,會發送一條sql語句,訪問t_classes表
Set students=classes.getStudents();//不會發送sql語句訪問t_student表,說明當前students是PersistantSet(相似cglib)代理對象,不是真正存放Student對象的Set集合。
Iterator it=students.iterator();
While(it.hasNext()){
Student stu=it.next();
stu.getName();//真正使用到Set集合中的元素時纔會發送sql語句訪問t_student表
}
5、集合上的懶加載lazy=extra,類上的懶加載關閉lazy=true:
session.load(Classes.class,1);//生成的是代理對象,不會發送sql語句,
classes.getName();//會發一條sql語句,訪問t_classes表
Set students=classes.getStudents();//不會發送sql語句訪問t_student表,說明當前students是PersistantSet(相似cglib)代理對象,不是真正存放Student對象的Set集合。
students.size();//這時候會使用函數從數據庫中查詢數據,select count(*)form t_student where classesid=1;,若是集合上的lazy爲true,就會先發送select *from student where classesid=1語句,查詢數據庫中的全部記錄,再計算出記錄的條數。
Iterator it=students.iterator();
While(it.hasNext()){
Student stu=it.next();
stu.getName();//真正使用到Set集合中的元素時纔會發送sql語句訪問t_student表
}
lazy=true和lazy=extra的功能差很少,可是extra更加智能,它會根據用戶的的條件發送比較智能的sql語句。
單端上的懶加載:(proxy,false,no-proxy)
一般用在<many-to-one><one-to-one>標籤上面
默認爲lazy=」proxy」,開啓狀態
<many-to-one name=」group」 lazy=」proxy」>
主要是在加載group(關聯)屬性的時候有用,
1、單端上的懶加載開啓lazy=proxy,類上的懶加載是開啓的 lazy=true:
session.load(User.class,1);//生成的是代理對象,target=null,不會發送sql語句
user.getName();//發送sql語句,訪問t_user表
Group group=user.getGroup();//拿到的而是Group的代理對象
Group.getName();發送sql語句訪問t_group.
2、單端上的懶加載關閉lazy=false,類上的懶加載關閉 lazy=false:
session.load(User.class,1);//發送兩條sql語句,訪問t_user和t_group
user.getName();
Group group=user.getGroup();
Group.getName();
3、單端上的懶加載開啓lazy=proxy,類上的懶加載關閉lazy=false:
session.load(User.class,1);//發送一條sql語句,只訪問t_user
user.getName();
Group group=user.getGroup();//不會發送sql語句,group爲代理對象
Group.getName();//發送sql語句訪問t_group
4、單端上的懶加載開啓lazy=no-proxy,類上的懶加載關閉lazy=false:
session.load(User.class,1);//發送一條sql語句,只訪問t_user
user.getName();
Group group=user.getGroup();//不會發送sql語句,group爲代理對象
Group.getName();//發送sql語句訪問t_group
Lazy的取值爲proxy和no-proxy功能和性能上都沒有很大的區別:
proxy:作懶加載是使用cglib代理,no-proxy使用的是加強的字節碼工具,在字節碼生成的時候增長了懶加載策略。
今天的任務:
將老師講的案例本身寫一遍(懶加載) *
晚上投簡歷 *
作>=1道算法題 *
查看hibernate源碼
Session_flush的做用:
一、 清理緩存(清理session臨時集合中的數據)
二、 生成sql語句(發送sql)
三、 將session的persistanceContext中的map->existsInDatabase的標記置爲true。
Flush:用在存儲上。
主鍵生成策略爲uuid(session.flush()的特色)
session.save(user)前—>> 對象處於瞬時態。
執行save(user)方法後,通常不會發送sql語句,由於主鍵是由hibernate自動生成。
session.save(user)後-->> 對象處於持久態。existsInDatabase=false
session.flush();//主要作了如下三件事:
一、 清理緩存(清理session臨時集合中的數據)
二、 生成sql語句(發送sql)
三、 將session的persistanceContext中的map->existsInDatabase的標記置爲true
數據庫中會有相應記錄,可是會看不到。(由於是 髒數據:沒有提交的數據)
session.commit();
一、 執行commit()方法的時候會執行flush()方法,因此能夠不顯示的寫出flush()方法。
二、 就算是顯示寫了flush()方法,commit()的時候仍是會再執行一次flush()
三、 執行flush()方法的步驟:
3.一、 先判斷臨時集合中有沒有數據
3.二、 若是沒有的話就直接commit().
3.三、 若是臨時集合中有數據的話就發送sql語句再清空臨時集合中的數據,而且將existsInDatabase標記置爲true,而後再執行commit()
主鍵生成策略爲native(session.flush()的特色)
session.save(user)前—>> 對象處於瞬時態。
執行save(user)方法後,通常會發送sql語句,由於主鍵是由數據庫自動生成。
session.save(user)後-->> 對象處於持久態。existsInDatabase=true
session.flush();//這時候flush()什麼都不作,由於臨時集合中沒有數據,sql語句已經發送,existsInDatabase=true。
session.commit();//會再次執行flush()方法,依舊什麼都不作。commit()方法對提交到數據庫的數據作驗證,成功就添加到數據庫,不成功就回滾。
主鍵生成策略爲uuid(session.evict()的特色)
session.save(user)前—>> 對象處於瞬時態。
執行save(user)方法後,一 般不會發送sql語句,由於主鍵是由hibernate自動生成。
session.save(user)後-->> 對象處於持久態臨時集合中有數據existsInDatabase=false
session.evict(user);//將user對象從session-- >> persistanceContext下的map中逐出。臨時集合中依舊有數據.
session.commit();//拋出異常,(persistanceContext下的map)EntityEntries屬性中的table中的數據不存在,找不到數據,沒法提交數據。(緩存使用不當)
產生異常的緣由:
主鍵生成策略爲uuid
session.save(user);//在臨時集合中有數據,session的map中有數據,existsInDatabase=true,loadedState中存放了user對象
session.evict(user);// 把user從map中逐出(session緩存),爲了管理緩存
而後 臨時集合中有數據,可是map中沒有數據
執行commit()方法前會先調用session.flush(),作如下三步操做:
解決辦法:(調用session.evict(user)之前先顯示調用session.flush())
session.save(user);
顯示的寫flush方法
先session.flush();//根據臨時集合中的數據發送sql語句,而後清空臨時集合,到session緩存中將existsInDatabase標記置爲true。
再session.evict(user);//將user對象從session緩存的map集合中移除
最後session.commit();//這時候依舊會執行session.flush()方法,可是不會拋出異常,由於flush()方法,是先查看臨時集合中有沒有數據,發現臨時集合是空的沒有數據就不會發送sql語句,也不用再次清空臨時集合,也不須要將existsInDatabase標記置爲true;直接執行commit()方法。
主鍵生成策略爲native(session.evict()方法的特色)
session.save(user)前—>> 對象處於瞬時態。
執行save(user)方法後,通常會發送sql語句,由於主鍵是由數據庫自動生成。
session.save(user)後-->> 對象處於持久態。臨時集合中沒有數據。existsInDatabase=true
session.evict(user);//自動把數據從session緩存的map中逐出
session.commit();//執行flush()方法,可是因爲主鍵生成策略爲native,臨時集合中已經沒有數據了,就不要用發送sql語句也不用將existsInDatabase標記置爲true了。直接執行commit()方法。 commit()方法對提交到數據庫的數據作驗證,成功就添加到數據庫,不成功就回滾。這裏不會拋出異常,由於這裏的flush()方法什麼都不用作。
主鍵生成策略爲assigned(session.evict()的特色)
session.save(user)前—>> 對象處於瞬時態。
執行save(user)方法後,通常不會發送sql語句,由於主鍵是由用戶手動生成。
session.save(user)後-->> 對象處於持久態。existsInDatabase=false,臨時集合中有數據。
session.evict(user);//自動把數據從緩存中逐出
session.commit();//無論assinged知道的主標識類型是int仍是String,執行session.evict(user)後執行session.commit()必定會拋出異常
解決辦法,在調用sessionn.evict(user)方法前顯示的調用session.flush()
Sql語句在數據庫中的執行順序:
發送sql語句的順序:
建立兩個對象 user,user1;
session.save(user);
user.setName(「ls」);
session.save(user1);
session.commit();
先發送insert into user () 存儲的對象是user
再發送 insert into user() 存儲的對象是user1
再發送 update user set name=ls 更新的對象是 user
可使用flush()方法調整語句的發送順序
建立兩個對象 user,user1;
session.save(user);
user.setName(「ls」);
session.flush();
session.save(user1);
session.commit();
先發送insert into user ()存儲的對象是 user
再發送 update user set name=ls user(由於flush()方法)
再發送 insert into user()存儲的對象是 user1
一、 爲何要加鎖?
在數據庫中不加鎖會丟失更新
緣由:用戶同時訪問數據庫,查看的數據相同,一方對數據作了修改可是另外一方不知道,因此就在原來的數據上修改。就數據不同。
不能併發的訪問數據庫中的數據,否則數據庫會崩潰的哦
保證數據庫的安全,並行變串行
悲觀鎖:(悲觀鎖不支持懶加載)
測試示例:
兩個方法串行執行:(load1(),load2())money=1000
一、load1();---- session.load(Test.class,1);
二、Debugà 查完數據後修改數據庫中的值 money-200可是 不執行commit()方法
三、執行另外一個查詢方法load2()-----ssession.load(Test.class,1);
四、修改數據 money-200而且一次執行完.
五、再接着執行第一個方法,commit()。更新後數據庫中的數據就是錯誤的。最後結果爲money=800。更新丟失
session.load(Test.class,1,LockMode.UPGRADE);//悲觀鎖,使用數據庫自身的鎖
LockMode.UPGRADE//利用數據庫的for update 子句加鎖—---記錄鎖(會出現幻讀)
鎖的分類:記錄鎖,表鎖,庫鎖
記錄鎖:只給當前查詢出來的記錄加鎖,其餘記錄依舊沒有變化
加悲觀鎖後的示例:(悲觀鎖中懶加載自動失效,load方法執行後就會發送sql語句)
兩個方法串行執行:(load1(),load2())money=1000
一、load1();---- session.load(Test.class,1,LockMode.UPGRADE);//立刻發sql語句查詢
二、使用debug調試à 查看記錄money=1000,而後修改數據庫中該記錄的的money字段, ,money=money-200可是 不執行commit()方法
三、執行另外一個查詢方法load2()-----ssession.load(Test.class,1,LockMode.UPGRADE); //發送sql語句查詢的時候發現查不出來數據。id=1的記錄已經被上了鎖。
四、修改數據 money值,money=money-200,執行commit()方法,會停留在記錄外面,查詢不出來記錄,由於前面已經上了鎖,要等前面那個鎖釋放才能夠訪問那條記錄。
五、再接着執行第一個方法,commit()。更新後數據庫中的數據money=800;
六、這時候load2()方法執行,查詢出來的數據money=800,以後才能對記錄作修改。
悲觀鎖中的記錄鎖的特色:避免了不可重複讀,可是存在幻讀。(效率低)
樂觀鎖:(樂觀鎖支持懶加載)
表中必須添加一個版本控制字段version,version默認值爲0,用戶讀到的記錄是同樣的 version和money。version在User類中定義爲 private int version;
version在映射文件中使用<version>標籤配置,version是版本控制字段,由Hibernate自動管理。
<version name=」version」/>:標識對樂觀鎖的控制
Load1()方法(debug調試,執行commit()方法之前的代碼)
User user=(User)session.load(User.class,1);//不會發送sql語句,生成代理對象
int money= user.getMoney();//使用到對象方法時發送sql語句(select * from user where id=1 and version=0),
user.setMoney(money-200);
user.setVersion(1); //查詢出數據後,修改表中記錄的值money = money-200;version=1。
session.update(user);//發送update語句,update user set money=money-200 and version=1 where id=1 and version=0;
暫不執行session.commit()這條語句。
Load2()方法(一次性執行完)
User user1=(User)session.load(User.class,1);//不會發送sql語句,生成代理對象
int money= user.getMoney();//使用到對象方法時發送sql語句(select * from user where id=1 and version=0),
user.setMoney(money-200);
user.setVersion(1); //查詢出數據後,修改表中記錄的值money = money-200;version=1。
session.update(user);//發送update語句,update user set money=money-200 and version=1 where id=1 and version=0;
執行session.commit()方法。修改數據庫中id=1對應的記錄
再繼續執行load1()方法中的session.commit()語句,發送sql語句update user set money=money-200 and version=1 where id=1 and version=0;
可是版本號version的值已經改爲1,因此commit()的時候就會拋出異常。
事物的隔離級別:
一、 未提交讀:沒有提交就能在數據庫中讀到數據
二、 可提交讀:提交後才能在數據庫中讀到數據,解決不可重複讀的方法是加悲觀鎖。(mysql和oracle的默認處理級別)
三、 可重複讀:加了悲觀鎖(記錄鎖)
四、 序列化讀:不會出現幻讀,加表鎖(不會出現幻讀,不會出現髒讀,也不會出現不可重複讀)
五、 髒讀:讀取未提交到數據庫中數據。
六、 不可重複讀:讀取數據時,若是有人修改了數據就發現不是原來的數據,會出錯(加悲觀鎖解決問題)
七、 幻讀:每次讀取數據的時候讀取到的記錄數一直在變化(解決方法應該加表鎖)
安全性強的使用使用悲觀鎖
併發性強的時候使用樂觀鎖
緩存的分類:
一級緩存:session級別,生存週期和session同樣
session.save(obj);將數據歸入session管理。load()和get(),iterator()方法都使用一級緩存。若是iterator()方法使用不當就會形成n+1問題。
Query 的list()方法不使用一級緩存,可是往一級緩存中放數據。(可能有n+1問題)
二級緩存:sessionFactory級別,工廠不會輕易銷燬,通常只建立一次。放到二級緩存中的數據特色:查詢次數頻繁,可是修改次數少
二級緩存:一級緩存能夠控制對二級緩存的使用。get(),save(),load() 既要往一級緩存中存數據,也要往二級緩存中存數據。
查詢緩存:爲了提升Query list的查找效率
一級緩存和二級緩存主要是緩存實體對象,不會緩存屬性
查詢緩存只緩存實體屬性
一級緩存:(load,get,list,iterator都支持一級緩存)
直接查詢load()或者get():
Session級別的緩存,通常不須要配置也不須要專門的管理,直接使用就能夠了。
Student stu=(Student)session.load(Student.class,1);
stu.getName();//先從一級緩存session中查看有沒有id=1的Student對象,有的話就直接使用,沒有的話就發sql語句訪問數據庫根據記錄生成對象再使用。<load支持一級緩存>
stu.getSex();//再次使用Student對象的時候,先從一級緩存session中查有沒有該對象,發現有就直接使用,再也不建立了。
Student stu1=(Student)session.get (Student.class,1);//這裏也不會發送sql語句,由於一級緩存中有id=1的Student對象,因此直接使用。<get()也支持一級緩存>
先save後查詢get()或者load(),查到的是save()過的數據
主鍵生成策略爲:native
Student stu=new Student();
stu.setName(「ls」);
stu.setSex(「male」);
Serializable id=session.save(stu);//save()方法的返回值是Integer類型的數據。獲得的id是對象的主標識。這裏save()後Student對象歸入session管理,進入緩存。
Serializable:序列化接口,String ,Integer都支持序列化協議。
Student stu1=(Student)session.get(Student.class,1);//這裏不會發送sql語句,由於先從緩存中查找數據,發現有id=1的Student對象,就直接使用。
缺點: 可能會讀到髒數據。
Query的iterate()方法
Student stu1=(Student)session.createQuery(「from Student s where s.id=1」).iterate().//執行到iterator()方法纔會發送sql語句
一、 經過id先從數據庫中查對象的id(select id from t_stu where id=1)
二、 根據id到session中查對象
三、 若是沒有就再發sql語句訪問數據庫 (select* from t_stu where id=1)
Student stu2=(Student)session.createQuery(「from Student s where s.id=1」).iterate().//再執行一次,仍是會發查詢id的sql語句(select id from t_stu where id=1)
而後發現session中有對象,就不發sql語句(select* from t_stu where id=1)了,直接從session中拿到對象使用。
N+1問題:(iterator()緩存使用不當)
session.createQuery(「from Student ).iterate();
一、 先發一條sql語句取出全部的id(select id from t_stu)
二、 查詢session中有沒有id對應的對象
三、 根據id依次發送sql語句訪問數據庫(select * from t_stu),數據庫中有多少條記錄就會發送多少條sql語句,若是數據庫中的記錄特別的多,這裏就會出現數據庫訪問擁塞,效率降低,形成n+1問題。
session.createQuery(「from Student ).iterate();//再查一次,這裏會先發sql語句查id,而後發現以及緩存session中有id對應的對象,就直接使用。不會再發送sql語句訪問數據庫了。
list()方法:只會往一級緩存中放數據,不會從一級緩存中取數據
List stus=session.createQuery(「from Student」).list();//直接發sql語句訪問數據庫,不會先查id,一次性直接把數據庫中的全部記錄放到一級緩存session中
Stus= session.createQuery(「from Student」).list();//list()只放不拿,仍是會再次發送sql語句訪問數據庫,查出全部記錄放到session中。
N=1問題的解決方法:
第一次使用list(),第二次使用iterator()方法
一、 list()先把從數據庫中查詢出全部數據放到一級緩存session中
二、 再使用iterator()方法使用一級緩存session中的數據
三、 iterator()會先發送sql語句查全部的id,而後去一級緩存session中查找id分別對應的對象
四、 發現對象已經存在一級緩存session中就直接使用。不會再發送多條sql語句訪問數據庫了。這樣就只發送了兩條sql語句,解決了N+1問題。
一級緩存的管理:(evict(),clear(),close())
使用flush()清除session中的緩存
session.flush();//清除臨時集合中的數據
session.clear();//清除persistanceContext下的map中的數據
必定要先flush(),而後再clear(),否則可能會產生異常
session.evict(user);//清除一條
session.clear();//清除多條數據
clear()和evict()的區別:clear()方法清除多條,evict()只能清除一條。
一級緩存只緩存對象,不緩存對象的屬性。
若是sql語句查詢的結果是記錄(一條或者多條),就會根據記錄生成對象(一個或多個)放到一級緩存session中。
若是sql語句查詢的結果是記錄的字段,就不會把字段映射成屬性放入一級緩存session中了。
一級緩存裏面的數據不能共享:session關閉後全部數據都銷燬。
Hibernate的缺點:Hibernate不會保證批量數據的同步,只能保證單條數據的同步。
二級緩存:(緩存的內容是對象,load,get,save都支持一級緩存)
二級緩存只有一個,生成周期很長,和SessionFactory的生命週期同樣。
緩存策略的做用是: 提供對緩存的方式
Hibernate通常使用Hcache這種緩存策略。
二級緩存的配置:
1、準備緩存策略的配置文件,在配置文件中寫緩存策略
在ehcache.xml中配置緩存策略:(配置在src下)
默認的緩存策略:
<defaultCache
maxElementsInMemeory=」10000」//緩存的最大空間
eternal=」false」//設置沒有任何對象常駐二級緩存中
timeToldleSeconds=」120」//若是120秒不使用就移除緩存
timeToLiveSeconds=」120」//最多能在二級緩存中呆120秒
overflowToDisk=」true」//若是緩衝存的數據超過10000,就將數據存到硬盤中
/>
配置指定的緩存策略:
<cache name=」cache1」
maxElementsInMemeory=」10000」//緩存的最大空間
eternal=」true」//設置全部對象都常駐二級緩存中
timeToldleSeconds=」 0」//無論多長時間沒有使用都不會移除緩存
timeToLiveSeconds=」 0」 //無論使用多長時間都不會移除緩存
overflowToDisk=」false」//溢出的時候不支持將數據存到硬盤上
/>
二級緩存經過緩存策略設定緩存中能存放多少個對象,存放多久,有多長的生存時間。
2、在Hibernate.cfg.xml文件中配置哪一個類的對象來管理二級緩存
<!-- 指定緩存產品提供類-->
<property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider</property>
org.hibernate.cache. EhCacheProvider:建立sessionFactory時,讀取Hibernate.cfg.xml文件,建立EhCacheProvider對象。經過插件執行對象的相應的方法--init(),讀取ehcache.xml配置文件。經過讀取ehcache.xml配置文件對緩存進行管理。
3、配置哪些對象使用二級緩存
<class-cache class=」cn.Student」 usage=」read-only」/>
4、指定使用二級緩存使用對象的使用方式
也能夠在實體類的hbm.xml文件中配置 <cache usage=」read-only」/>
緩存策略的集中方式:read-only(經常使用)read-write , nonstrict-read-write
五、 開啓二級緩存:
<!-- 開啓二級緩存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
二級緩存的使用:(只存放配置了的對象,get,load,save)
load()方法的二級緩存解析:
Student stu=(Student)session.load(Student.class,1);
Stu.getName();//發送sql語句從數據庫中拿到id=1的記錄生成對象,存入二級緩存也會存入一級緩存中。
Session.close();//關閉session,一級緩存中的數據被清空
Student stu=(Student)session.load(Student.class,1);
Stu.getName();再次訪問時,先從一級緩存session中查看有沒有對象,沒有再從二級緩存中查,發現有就直接使用。
get()方法的二級緩存解析:
Student stu=(Student)session.get(Student.class,1); //發送sql語句從數據庫中拿到id=1的記錄生成對象,存入二級緩存也會存入一級緩存中
stu.getName();
session.close();//關閉session,一級緩存中的數據被清空
Student stu=(Student)session.get(Student.class,1); 再次訪問時,先從一級緩存session中查看有沒有對象,沒有再從二級緩存中查,發現有就直接使用。
stu.getName();
結論:在二級緩存開啓的狀態下,load()和get()不只使用一級緩存,還會使用二級緩存。
二級緩存的管理:(沒有臨時集合,基本上都是map)
Student stu=(Student)session.get(Student.class,1); //發送sql語句從數據庫中拿到id=1的記錄生成對象,存入二級緩存也會存入一級緩存中
stu.getName()
session.close();//關閉session
factory =HibernateUtils.getSessionFactory();//拿到sessionFactory對象
factory.evict(Student.class);//清除二級緩存中的全部Student對象
factory.evict(Student.class,1);//只把二級緩存中id=1的Student對象
Student stu=(Student)session.get(Student.class,1); //再次發送sql語句從數據庫中拿到id=1的記錄生成對象,存入二級緩存也會存入一級緩存中(由於一級緩存和二級緩存中的數據都被清空了)
SessionFactory的做用:
一、 建立Session對象
二、 管理二級緩存
一級緩存和二級緩存的交互:
(一級緩存能夠控制如何使用二級緩存)
1、設定二級緩存只能讀不能寫
session.setCacheMode(CacheMode.GET);//設定僅從二級緩存中讀數據,而不向二級緩存中寫數據
Student stu=(Student)session.get(Student.class,1); //發送sql語句從數據庫中拿到id=1的記錄生成對象,只將數據存入一級緩存中。
stu.getName()
session.close();//關閉一級緩存,這時候一級緩存和二級緩存中都沒有數據,下一次使用對象時,會發送sql語句。
2、設定對二級緩存不作任何約束
Student stu=(Student)session.get(Student.class,1); //發送sql語句從數據庫中拿到id=1的記錄生成對象,將數據存入一級緩存和二級緩存中。
stu.getName()
session.close();//關閉一級緩存,這時候一級緩存沒有數據,二級緩存中有數據
3、設定二級緩存只能寫不能讀
session.setCacheMode(CacheMode.PUT); //設定只往二級緩存中寫數據,可是不能讀取二級緩存中的數據。
Student stu=(Student)session.get(Student.class,1); //雖然二級緩存中有數據,可是由於二級緩存中的數據不能讀取,因此要發送sql語句從數據庫中拿到id=1的記錄生成對象,將數據存入一級緩存和二級緩存中。
Stu.getName();
Session.close();/關閉session,一級緩存中的數據被清空。二級緩存中的數據還在。
4、設定對二級緩存不作任何約束
Student stu=(Student)session.get(Student.class,1); //不會發送sql語句,由於二級緩存中有數據,能夠讀取。
Stu.getName();
Session.close();/關閉session,一級緩存始終都沒有數據,二級緩存中的數據依舊在。
查詢緩存:(只能緩存屬性,不能緩存對象,只有list可使用查詢緩存)
list經過查詢緩存使用二級緩存
查詢緩存的配置:(不須要配置緩存策略)
一、 在Hibernate.cfg.xml中配置開啓查詢緩存:(二級和查詢緩存都開啓 true)
二、 在使用查詢緩存前要啓動查詢緩存
query.setCacheable(true);
一、 開啓查詢緩存(true),關閉二級緩存(false) 使用list()方法
Query query=session.createQuery(「select s.name form Student s);//查出全部的name屬性對應的字段
query.setCacheable(true);//啓動查詢緩存
List name=query.list();//發送sql語句,將查到的name屬性放到查詢緩存中
Query query=session.createQuery(「select s.name form Student s);//查出全部的name屬性對應的字段
query.setCacheable(true);//啓動查詢緩存,查詢緩存配置後也是默認開啓的
List name=query.list();//不會再發送sql語句,由於list()方法使用了查詢緩存,查詢緩存中已經有數據
結論:查詢緩存,值緩存屬性,不緩存對象;只有list()方法能使用查詢緩存
二、 查詢緩存不受一級緩存的影響
三、 開啓查詢緩存(true),關閉二級緩存(false) 使用iterate()方法
Query query=session.createQuery(「select s.name form Student s);//查出全部的name屬性對應的字段
query.setCacheable(true);//啓動查詢緩存
List name=query.iterate();//發送sql語句,由於是iterate()方法,不能使用查詢緩存,因此沒法將查到的name屬性放到查詢緩存中
Query query=session.createQuery(「select s.name form Student s);//查出全部的name屬性對應的字段
query.setCacheable(true);//啓動查詢緩存
List name=query.iterator();//仍是再次發送sql語句,由於iterator()方法不能使用查詢緩存,查詢緩存中沒有數據
結論:iterator()不使用查詢緩存。
四、 關閉查詢緩存(false),關閉二級緩存(false) 使用list()方法
Query query=session.createQuery(「select s form Student s);//查出全部的Student對象
查詢兩次,查詢完後關閉session,第二次查詢依舊會發送sql語句
五、 開啓查詢緩存(true),關閉二級緩存(false) 使用list()方法
Query query=session.createQuery(「select s form Student s);//查出全部的Student對象
query.setCacheable(true);//啓動查詢緩存
List stus=query.list();//查出的全部對象都存入一級緩存中,查出全部對象的id放入查詢緩存中。
Session.close();//關閉session,一級緩存中的數據被清空
Query query=session.createQuery(「select s form Student s);//查出全部的Student對象
query.setCacheable(true);//啓動查詢緩存
List stus=query.list();//先到查詢緩存中查看存放的全部對象的id,再到二級緩存中找id分別對應的對象,可是二級緩存已經關閉,全部查詢緩存會發送sql語句:slelect * from t_stu where id= id;有多個id就會發送多少條語句。這樣就會形成數據庫訪問擁塞,N+1問題。
6、 開啓查詢緩存(true),開啓二級緩存(true) 使用list()方法
就不會出現N+1問題,由於開啓二級緩存,二級緩存中就會有數據,直接使用就能夠了,不用再次發sql語句訪問數據庫拿到記錄生成對象。
N+1問題:
iterator()是對一級緩存使用不當,形成N+1問題
list()是對二級緩存的使用不當,形成N+1問題。
結論:Query的list方法不能使用一級緩存,可使用二級緩存,可是要開啓查詢緩存纔有用。
Iterate()也會使用二級緩存。
注意:
list()方法:直接取出全部對象
iterate()方法:先取出全部的記錄的id,再根據id到一級緩存中查看有沒有對應的對象,沒有的話就發送sql語句訪問數據庫拿到記錄生成對象。查詢出了對少個id就發送多少條sql語句。
今天的任務:
將緩存示例寫一遍(尤爲是二級緩存和查詢緩存)
寫>=1道算法題
看python文檔
複習一遍Hibernate
Lunix的指令
數據結構,算法
hibernate的查詢語言種類:
一、 標準化對象查詢(Criteria Query):徹底面向對象可是不夠成熟,不支持投影也不支持統計函數。
二、 原生的sql查詢語句(Native SQL Queries) :直接使用標準SQL語言和特定的數據庫相關聯的sql進行查詢。<和數據庫耦合性太強>
三、 Hibernate查詢語言(Hibernate Query Language HQL)
使用sql的語法,以面向對象的思想完成對數據庫的查詢,獨立於任何數據庫
以類和屬性來代替表和數據列
支持多態,支持各類各樣關聯
減小了SQL的冗餘, hql:不區分大小寫
Hql支持的數據庫操做:
鏈接(joins),笛卡爾積(cartesian products),投影(projection),聚合(max,avg),排序(ordering),子查詢(subquering),sql函數,分頁
HQL導航查詢示例:
Query query=session.createQuery(「from User user where user.group.name=’zte’ 」);
//查找User類對象的屬性group,group屬性所屬的類型Group對象的name
至關於sql語句:
select * from t_user ,t_group where t_group.name=’zte’ and t_group.id =t_user.groupid;
randomDate(「2017-1-1」,」2017-2-9」);//隨機生成一個「2017-1-1」,」2017-2-9」之間的日期
兩個或者兩個以上的普通屬性查詢
使用Object[]數組接收查詢出來的屬性
List students=session.createQuery(「select id ,name from Student」).list();
Iterator it=students.iterator();
While(it.hasNext()){
Object[] obj=(Object) it.next();
System.out.println(obj[0]+」---」+obj[1]);
}
先建立一個Object類型的數組對象Object[]
Object[]數組的長度和查詢的屬性的個數相同,一 一對應,obj[0]存放從數據庫中取出來的id值,obj[1]存放從數據庫中取出的name值。
Object[]數組元素的類型和Student實體類的屬性類型一 一對應。
List集合students中存放的元素是Object[]類型的數據,查詢出來多少條記錄就有多少個元素,有多少個Object[]數組。
使用Object[]數組接收查詢出來的屬性(添加別名,更加清晰)
List students=session.createQuery(「select s.id ,s.name from Student (as) s」).list(); //as能夠省略也能夠顯示的寫出來
Iterator it=students.iterator();
While(it.hasNext()){
Object[] obj=(Object) it.next();
System.out.println(obj[0]+」---」+obj[1]);
}
把從數據庫查到的記錄生成對象,再查出id和name屬性(不建議使用)
List students=session.createQuery(「select new Student( id ,name) from Student」).list();//自動調用Student類的有參的構造方法,建立Student對象
Iterator it=students.iterator();
While(it.hasNext()){
Student stu=(Student) it.next();
System.out.println(stu.getId()+」---」+stu.getName());
}
實體對象的查詢:(Select 查找對象 不能使用*)error: unexpected token *
List students=session.createQuery(「from Student」).list();//正確
List students1=session.createQuery(「from Student s」).list();//正確
List students2=session.createQuery(「select * from Student」).list();//這是錯誤的
List students3=session.createQuery(「select s from Student as s」).list();//這纔是正確的,as能夠省略
Iterator it=students.iterator();
While(it.hasNext()){
Student stu=(Student) it.next();
System.out.println(stu.getId()+」---」+stu.getName());
}
條件查詢:
一、寫定查詢條件
List students=session.createQuery(「select s.id,s.name from Student s where s.name like ‘%1%’ 」).list();//查詢出全部名字包含1的學生id和name
二、使用填充符,動態查詢條件
List students=session.createQuery(「select s from Student s where s.name like ?」).setParameter(0,」%1%」).list();
三、使用參數,把條件設到參數中
List students=session.createQuery(「select s from Student s where s.name like :myname 」).setParameter(「myname」,」%1%」).list();
四、使用多個參數,把多個條件分別設到參數中
List students=session.createQuery(「select s from Student s where s.name like :myname and s.id= : myid 」).setParameter(「myname」,」%1%」). setParameter(「myid」,1).list();
參數順序不同沒有關係。只和參數名有關。
五、使用一個參數,把多個條件設到一個參數中(setParameterList)
List students=session.createQuery(「select s from Student s where s.id in (:myids )」).setParameterList(「myids」,new Object[]{1,2,3}).list();
使用參數數組對象,查詢出id 爲1 ,2,或者3的學生對象
六、日期類型數據做爲條件的查詢(使用數據庫中的date_format()函數)
List students=session.createQuery(「select s from Student s where date_format(s.createTime,’%Y-%m’)=?」).setParameter (0,」2017-2」).list();
先把字符串轉換爲日期類型
再把日期和數據庫中日期作比較
六、日期類型數據做爲條件的查詢(使用自定義的日期轉換函數 sdf.parse())
SimpleDateFormat sdf=new SimpleDateFormat(「yyyy-MM-dd HH:mm:ss」);
List students=session.createQuery(「select s from Student s where sdf.parse (s.createTime,’%Y-%m’)=?」).setParameter (0,」2017-2-12 11:22:11」).list();
先把字符串轉換爲日期類型
再把日期和數據庫中日期作比較
若是非要使用select *語句 就要使用原生的sql語句
List students=session.createSQLQuery(「select * from Student).list();
分頁查詢:
List students=session.createQuery(「select s from Student s )
.setFirstResult(5)//查詢從第五條記錄開始的數據 ((pageNo-1)*pageSize)
.setMaxResult(2)//每頁最多顯示兩條(pageSize)
.list();
對象導航查詢:
List students=session.createQuery(「select s.name from Student s where s.classes.name like ‘%1%’ 「).list();
鏈接查詢:
內鏈接
List students=session.createQuery(「select s.name ,c.name from Student s join s.classes c」).list();//經過學生類的關聯屬性把學生類和班級類作內鏈接。(找出既有學生又有班級的記錄)
外鏈接:
左鏈接:
List students=session.createQuery(「select s.name ,c.name from Classes c left join c.student s」).list();拿到有學生的班以及沒有學生的班級(班級所有顯示,學生只顯示有班級的)
右鏈接:
List students=session.createQuery(「select s.name ,c.name from Classes c right join c.students s」).list();拿到有學生的班以及沒有班級的學生(學生所有顯示,班級只顯示有學生的)
分組查詢:
List students=session.createQuery(「select s.name ,count(*) from Classes c left join c.Student s group by s.name order by s.name」).list();
查詢過濾器:
使用場合:要求數據錄入人員只能看到本身錄入的數據時 ---使用查詢過濾器
使用條件:
在Student.hbm.xml映射文件中定義過濾器參數:
<filter-def name=」filtertest」>//查詢過濾器的名字
<filter-param name=」myid」 type=」integer」/>//參數名 參數類型爲integer
</filter-def>
使用步驟:(先在Student.hbm.xml文件中)
<filter name=」filtertest」 condition=」id < :myid」/>//condition配置條件,id< myid(參數)
在查詢方法中:session.enableFilter(「filtertest」) .setParameter(「myid」,10);//啓用查詢過濾器,而且設定參數。 表示查詢id<10的數據記錄。
外置命名查詢:
在Student.hbm.xml映射文件中採用<query>標籤來定義hql:
<query name=」searchStudents」>
<![CDATA[SELECT s FROM Student s where s.id<?]]>
</query>
List students=session.getNameQuery(「searchStudents」).setParameter(0,5).list();//安全性比較差,顯示寫出sql語句在配置文件中,容易被黑。
DML風格:(批量更新,批量操做)和緩存不一樣步
session.createQuery(「update Student s set s.name=? where s.id<?」)
.setParameter(0,」小仙女」)//更新name都爲小仙女
.setParameter(1,5)//id<5的記錄都被更新
.executeUpdate();
Hibernate的缺點:不支持批量更新,不是說不能批量更新,而是批量更新的操做不能保證緩存中的數據和數據庫中的數據同步。
Struts1和Hibernate的集成:
一、 建立web項目
二、 導入兩個框架的jar包以及jstl的jar包—--WEB-INF/lib
三、 在src下存放國際化資源文件
四、 在web.xml中配置struts1.0 <ActionServlet>,配置編碼過濾器
五、 配置struts-config.xml(一般用DispatchAction),放在WEB-INF下
六、 在src下配置hibernate.cfg.xml,log4j.properties
七、 在web.xml文件中配置HibernateFilter,一啓動就建立SessionFactory
八、 也能夠在struts-config.xml文件中使用插件配置Hibernate的啓動,可是建議在Filter中配置,由於filter還要處理openSessionInview問題。
九、 模型層:包括實體類和類對應的*hbm.xml文件.