Hibernate框架
1.Hibernate的概述
1.1 Hibernate框架的作用
Hibernate框架是一個數據訪問框架(也叫持久層框架,可將實體物件變成持久物件,詳見第5章)。通過Hibernate框架可以對資料庫進行增刪改查操作,為業務層構建一個持久層。可以使用它替代以前的JDBC訪問資料。
1.2 Hibernate訪問資料庫的優點
1)簡單,可以簡化資料庫操作程式碼。
2)Hibernate可以自動生成SQL,可以將ResultSet中的記錄和實體類自動的對映(轉化)。
3)Hibernate不和資料庫關聯,是一種通用的資料庫框架(支援30多種資料庫),可以方便資料庫移植。任何資料庫都可以執行它的API
1.3 JDBC訪問資料庫的缺點
1)需要編寫大量的複雜的SQL語句、表字段多時SQL也繁瑣、設定各個問號值。
2)需要編寫實體物件和記錄之間的程式碼,較為繁瑣。
3)資料庫移植時需要修改大量的SQL語句。
1.4 Hibernate的設計思想
Hibernate是基於ORM(Object Relation Mapping)思想設計的,稱為物件關係對映。負責Java物件和資料庫表資料之間的對映。
Hibernate是一款主流的ORM工具,還有其他很多ORM工具,如:MyBatis
ORM工具在完成Java物件和資料庫之間的對映後:HQL
1)在查詢時,直接利用工具取出“物件”(不論是查詢一條記錄還是多條記錄,取出的都是一個個物件,我們不用再去轉化實體了)。
2)在增刪改操作時,直接利用工具將“物件”更新到資料庫表中(我們不用再去把物件轉成資料了)。
3)中間的SQL+JDBC細節,都被封裝在了工具底層,不需要程式設計師參與。
u注意事項:
vJava程式想訪問資料庫,只能通過JDBC的方式,而
vHibernate是以“物件”為單位進行資料庫的操作。
2.Hibernate的基本使用
2.1 Hibernate的主要結構
1)hibernate.cfg.xml(僅1個):Hibernate的主配置檔案,主要定義資料連線引數和框架設定引數。
u注意事項:就是個xml檔案,只是名字比較奇葩!
2)Entity實體類(n個,一個表一個):主要用於封裝資料庫資料。
3)hbm.xml對映檔案(n個):主要描述實體類和資料表之間的對映資訊。描述表與類,欄位與屬性的對應關係。
u注意事項:hbm.xml是個字尾,如:命名可寫Cost.hbm.xml。
2.2 Hibernate主要的API
1)Configuration:用於載入hibernate.cfg.xml配置資訊。用於建立SessionFactory。
2)SessionFactory:儲存了hbm.xml中描述的資訊,內建了一些預編譯的SQL,可以建立Session物件。
3)Session:負責對資料表執行增刪改查操作。表示Java程式與資料庫的一次連線會話,是對以前的Connection物件的封裝。和JSP中的session不是一回事,就是名字一樣而已。
4)Query:負責對資料表執行特殊查詢操作+增刪改。
5)Transaction:負責Hibernate操作的事務管理。預設情況下Hibernate事務關閉了自動提交功能,需要顯式的追加事務管理(如呼叫Transaction物件中的commit();提交事務)!
u注意事項:
v這些API都是在Hibernate包下的,導包別導錯!
v第一次訪問資料庫比較慢,比較耗資源,因為載入的資訊多。
2.3 Hibernate使用步驟
step1:建立資料庫表。
step2:建立Java工程(Web工程也可),引入Hibernate開發包和資料庫驅動包。必須引入的包:hibernate3.jar、cglib.jar、dom4j.jar、commons-collections.jar、commons-logging.jar…等,下載地址:http://hibernate.org/orm/downloads/
step3:新增hibernate.cfg.xml配置檔案,檔案內容如下:
<?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"> <!-- Generated by MyEclipse Hibernate Tools. --> <hibernate-configuration> <session-factory> <!-- 指定方言,決定Hibernate生成哪種SQL --> <property name="dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="connection.url"> jdbc:mysql://localhost:3306/hibdata </property> <property name="connection.username">root</property> <property name="connection.password">root</property> <property name="connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="myeclipse.connection.profile">mysql</property> <!-- 框架引數,將hibernate底層執行的SQL語句從控制檯顯示 --> <property name="show_sql">true</property> <!-- 格式化顯示的SQL --> <property name="format_sql">true</property> <!-- 指定對映描述檔案 --> <mapping resource="com/hxh/pojo/News.hbm.xml" /> </session-factory> </hibernate-configuration>
注意事項:應該放在原始檔的src目錄下,預設為hibernate.cfg.xml。檔案內容是Hibernate工作時必須用到的基礎資訊。
step4:編寫Entity實體類(也叫POJO類),例如:新聞實體類News
private Integer nid; private String title; private String content; private String photo; private Integer ntid;……getter/setter方法
注意事項:POJO類表示普通類(Plain Ordinary Old Object),沒有格式的類,只有屬性和對應的getter/setter方法,而沒有任何業務邏輯方法的類。這種類最多再加入equals()、hashCode()、toString()等重寫父類Object的方法。不承擔任何實現業務邏輯的責任。
step5:編寫hbm.xml對映(檔案)描述資訊:對映檔案用於指明POJO類和表之間的對映關係(xx屬性對應xx欄位),一個類對應一個對映檔案。例如:News.hbm.xml內容如下:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- 定義COST_CHANG表和Cost型別之間的對映資訊 --> <hibernate-mapping><!-- <hibernate-mapping package="包名寫這也行"> --> <!-- name:包名.類名,指定是哪個類;table:資料庫中哪個表;catalog:對Oracle而言為哪個資料庫,對MySQl而言為某個使用者(MySQl是在使用者下建表,Oracle是在庫中建表), 不寫也行(若用工具則會自動生成)。例如,select * from news_chang則會在hibernate.cfg配置檔案中定義的庫(或使用者)下去找表。若寫了則為select * from system.news_chang --> <?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"> <!-- 此處表示的是進行POJO類與資料表之間的對映 --> <hibernate-mapping> <!-- 配置的是類的相關定義,name表示類的名字,同時設定table對應的資料表,catalog表示的是資料庫的名字 --> <class name="com.offcn.pojo.News" table="news" catalog="hibdata"> <!-- 配置主鍵,name表示的是類之中的屬性名稱,type表示的是News類的nid型別 --> <id name="nid" type="java.lang.Integer"> <!-- News類中的nid屬性與資料表之中對應的列,列名稱是nid --> <column name="nid" /> <!-- 主鍵的生成方式,本次使用的是資料表資料的自增長 --> <generator class="native"></generator> </id> <!-- 配置類之中的其它普通屬性資訊,name表示的是News類中的屬性名稱 --> <property name="title" type="java.lang.String"> <!-- 定義類中的屬性與表之中的對映欄位 --> <column name="title" length="50" not-null="true" /> </property> <property name="content" type="java.lang.String"> <column name="content" length="65535" /> </property> <property name="photo" type="java.lang.String"> <column name="photo" length="200" /> </property> <property name="ntid" type="java.lang.Integer"> <column name="ntid" /> </property> </class> </hibernate-mapping>
注意事項:
對映檔案預設與POJO類放在一起;命名規則為:類名.hbm.xml。
hbm.xml中已寫出的屬性與欄位的對映要一一對應,若表中沒有某個欄位,卻寫了對映關係,則報錯:找不到實體類。
step6:利用Hibernate API實現DAO
1)新建HibernateUtil類,用於封裝建立Session的方法。如下:每個使用者會對應一個Session,但是SessionFactory是共享的。
public class HibernateUtil {
private static SessionFactory sf;
static{//不用每次都載入配置資訊,所以放static塊中,否則每次都載入會耗費資源
Configuration conf=new Configuration();//載入主配置hibernate.cfg.xml
conf.configure("/hibernate.cfg.xml");
sf=conf.buildSessionFactory();//獲取SessionFactory }
public static Session getSession(){//獲取Session
Session session =sf.openSession(); return session; } }
2)新建NewsDao介面
package com.hxh.dao; import java.util.List; import com.hxh.pojo.News; public interface NewsDao { public News findById(int id); public void save(News news); public void delete(int id); public void update(News news); public List<News> findAll(); }
3)新建NewsDaoImpl類,用於實現NewsDao介面
package com.hxh.dao; import java.util.List; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; import com.hxh.pojo.News; import com.hxh.utils.HibernateUtil; public class NewsDaoImpl implements NewsDao { private Session session; public NewsDaoImpl(){ session=HibernateUtil.getSession(); } /* * @see com.offcn.dao.NewsDao#findById(int) * /** get方法執行查詢,按主鍵當條件查詢,如何判斷是主鍵,是根據寫的描述檔案來定, * get方法就是findById,就是按主鍵去查,需指定:操作哪個類和id(主鍵)條件值即可,其他條件查詢做不了 */ @Override public News findById(int id) { // TODO Auto-generated method stub News news=(News) session.get(News.class, id); session.close(); return news; } /** save方法執行增加操作,注意1:獲取事務並開啟,增刪改要注意,查詢可以不管事務,因為沒對資料庫進行修改;注意2:主鍵值根據hbm.xml中的<generator>定義生成,執行後,會先獲取序列值,再去做insert操作。 即先:select COST_SEQ_CHANG.nextval from dual; 然後:insert into …… */ @Override public void save(News news) { // TODO Auto-generated method stub News vo = new News() ; vo.setTitle("今天是週五啦,高興不?"); vo.setPhoto("nophoto.jpg"); vo.setContent("據說今天有中到大雨,小心被淹死哦,哈哈哈,可以免費游泳!"); // 6、開啟事務 Transaction tran = session.beginTransaction() ; // 7、執行更新 session.save(vo); // 8、提交事務 tran.commit(); // 9、關閉連線 session.close() ; } /** delete方法執行刪除操作,由於Hibernate以“物件”為單位進行資料庫操作, * 所以這裡要傳進去一個物件,雖然是個物件,但還是按主鍵做條件刪除,只要把主鍵值設定上就行,其他非主鍵值不用管。也可先通過id查再刪 */ @Override public void delete(int id) { // TODO Auto-generated method stub Transaction tx=session.beginTransaction(); News news=new News(); news.setContent("ijikij即可立即"); session.delete(news); tx.commit(); session.close(); } /* * @see com.offcn.dao.NewsDao#update(com.offcn.pojo.News) */ @Override public void update(News news) { // TODO Auto-generated method stub session=HibernateUtil.getSession(); Transaction tx=session.beginTransaction(); session.update(news);//將cost物件更新到資料庫 tx.commit(); session.close(); } /* * ** 特殊查詢,SQL語句:String sql="select * from COST_CHANG"; HQL語句:String hql="from Cost"; (Hibernate Query Language)是面向物件的查詢語句。 from後寫對映的類名,它是Hibernate中特有的查詢語句,根據對映的類去查詢。 */ @Override public List<News> findAll() { session=HibernateUtil.getSession(); String hql="from News";//HQL語句 Query query=session.createQuery(hql); List<News> list=query.list();//執行查詢,返回List集合 session.close(); return list; } }
4)新建TestCostDAO類,使用junit測試
package hxh.offcn.test; import java.sql.Date; import java.util.List; import org.junit.Test; import com.hxh.dao.NewsDao; import com.hxh.dao.NewsDaoImpl; import com.hxh.pojo.News; public class TestDao { @Test public void testFindById(){//當get方法沒有記錄時,返回null NewsDao NewsDao = new NewsDaoImpl(); News News = NewsDao.findById(1); System.out.println(News); } @Test public void testSave(){//id主鍵列由Hibernate管理,這裡不用設定 News vo = new News() ; vo.setTitle("w今天是週五啦,高興不?"); vo.setPhoto("nophoto.jpg"); vo.setContent("據說今天有中到大雨,小心被淹死哦,哈哈哈,可以免費游泳!"); NewsDao NewsDao = new NewsDaoImpl(); NewsDao.save(vo); } @Test public void testUpdate(){ NewsDao NewsDAO=new NewsDaoImpl(); /** 注意事項:更新部分欄位,不能和實現類中的刪除那樣,做一個物件出來!否則沒設定的欄位將被改為空! 即不能:News News=new News(); News.setId(90); News.setStatus("1"); News.setStartTime(new Date(System.currentTimeMillis())); */ News news=NewsDAO.findById(1);//只能先通過id找到帶有所有值的物件 news.setContent("我跟發新股夠了"); NewsDAO.update(news); } @Test public void testDelete(){ NewsDao NewsDAO=new NewsDaoImpl(); NewsDAO.delete(1); } @Test public void testFindAll(){ NewsDao NewsDAO=new NewsDaoImpl(); List<News> list=NewsDAO.findAll(); for(News c:list){ System.out.println(c); } } }
3.關聯對映
3.1一對多關係one-to-many
step1:新建專案,匯入Hibernate開發包,借用學生班級實體:Student和Grade,配置hibernate.cfg.xml和兩個實體的hbm.xml對映檔案。
step2:一個年級對應多個學生,所以為一對多關係。因此為One方Grade實體類新增Set集合屬性,以及對應的get/set方法。
//追加屬性,用於儲存相關聯的學生資訊 private Set<Student> students = new HashSet<Student>();
step3:在One方Grade.hbm.xml對映檔案中,加入Set節點的對映
簡單說明:
<set name="屬性名"> <!-- 關聯條件,column寫外來鍵欄位,會預設的與其表的主鍵相關聯 --> <key column="指定關聯條件的外來鍵欄位"></key> <!-- 指定採用一對多關係,class指定關聯的型別 --> <one-to-many class="要關聯的另一方(N方)"/> </set>
具體實現:
<!-- 描述services屬性,採用一對多關係載入service記錄 --> <!-- 是list集合用<list name=""></list> set集合用<set name=""></set> --> <set name="students" cascade="all" lazy="false"> <key column="gid"/> <one-to-many class="com.hxh.pojo.Student"/> </set>
step4:借用5.17節4)中的step1中的HibernateUtil類
step5:新建TestOneToMany.java類用於測試
/** * 功能1:查詢一個班級的所有學生資訊 */ @Test public void findById(){ Session session = HibernateUtil.getSessionFactory().openSession(); Grade g = (Grade)session.get(Grade.class, 1); System.out.println(g.getGid()+" "+ g.getGname()+" "+ g.getGdesc()); Set <Student>students =g.getStudents(); for(Student stu:students){ System.out.println(stu.getSid()+" "+stu.getSname()+" "+stu.getSex()); } session.close(); } /** * 功能2:一名新生加入到一個現有的班中 */ @Test public void update() { Student stu = new Student(); stu.setSid(3); stu.setSname("李婷3"); stu.setSex("女"); Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tr = session.beginTransaction(); Grade g = (Grade)session.get(Grade.class, 1); /*Set studentset=g.getStudents(); System.out.println(studentset);*/ g.getStudents().add(stu); session.save(g); session.save(stu); tr.commit(); session.close(); } /** * 功能3:刪除現有班級中一名學生 */ @Test public void deleteStudent(){ Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tr = session.beginTransaction(); Grade g = (Grade)session.get(Grade.class, 1); Student stu = (Student)session.get(Student.class, 1); g.getStudents().remove(stu); session.update(g); session.update(stu); tr.commit(); session.close(); } /** * 功能4:查詢一個學生的班級資訊 */ @Test public void findById2(){ Session session = HibernateUtil.getSessionFactory().openSession(); Student stu = (Student)session.load(Student.class, 1); System.out.println(stu.getSid() + " " + stu.getSname()); Grade grade = stu.getGrade(); System.out.println(grade.getGname()); System.out.println(grade.getGdesc()); session.close(); } /** * 功能5:一名現有學生轉入到另一個現有班級中 */ @Test public void update2() { Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tr = session.beginTransaction(); Grade g = (Grade)session.get(Grade.class, 2); Student stu = (Student)session.get(Student.class, 3); g.getStudents().add(stu); session.update(g); //session.update(stu); tr.commit(); session.close(); } }
6.2一對一關係one-to-one
step1:新建專案,匯入Hibernate開發包,借用學生和考卷兩個實體:Student和Paper,配置hibernate.cfg.xml和兩個實體的hbm.xml對映檔案。
step2:可一個學生對應一份考卷,所以為一對一關係。因此為Student實體類新增Paper屬性,以及對應的get/set方法,同時為Paper實體類新增Student屬性,以及對應的get/set方法。
//追加屬性,用於儲存關聯的Paper資訊
private Paper paper;//
Paper實體類
package com.hxh.pojo; public class Paper { private int pid; private String pdesc; private Student student; public int getPid() { return pid; } public void setPid(int pid) { this.pid = pid; } public String getPdesc() { return pdesc; } public void setPdesc(String pdesc) { this.pdesc = pdesc; } public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } }
step3:配置Student.hbm.xml對映檔案和Paper.hbm.xml
簡單說明:
<one-to-one name="屬性名" class="要關聯的另一方型別Account" column="關聯條件的外來鍵欄位"/> <!-- 指明外來鍵欄位,不寫主鍵 -->
注意事項:此時沒有<set name="屬性名"></set>標籤。
具體實現:
<one-to-one name="paper" class="com.hxh.pojo.Paper" lazy="false" cascade="all"/>
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.hxh.pojo.Paper" table="PAPER" > <id name="pid" type="java.lang.Integer"> <column name="PID" /> <generator class="assigned" /> </id> <property name="pdesc" type="java.lang.String"> <column name="PDESC" length="50" not-null="true" /> </property> <many-to-one name="student" class="com.hxh.pojo.Student" unique="true" > <column name="sid"/> </many-to-one> </class> </hibernate-mapping>
step4:借用5.17節4)中的step1中的HibernateUtil類
step5:新建TestOne2one .java類用於測試
package com.hxh.test; import org.hibernate.Session; import org.hibernate.Transaction; import com.hxh.pojo.Paper; import com.hxh.pojo.Student; import com.hxh.utils.HibernateUtil; public class TestOne2one { public static void main(String[] args) { //save(); findById2(); } /** * 功能1:新增一個學生及其學生證資訊 */ public static void save() { Student stu = new Student(); stu.setSid(11070130); stu.setSname("趙飛"); stu.setSex("男"); Paper paper = new Paper(); paper.setPid(123456); paper.setPdesc("北京大學學生證"); paper.setStudent(stu); stu.setPaper(paper); Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tr = session.beginTransaction(); session.save(stu); tr.commit(); session.close(); } /** * 功能2:根據學生證查詢相應學生資訊 */ public static void findById2() { Session session = HibernateUtil.getSessionFactory().openSession(); Paper paper= (Paper)session.get(Paper.class, 123456); System.out.println(paper.getPdesc()); System.out.println(paper.getStudent().getSname()); session.close(); } }