WEB開發(7) Hibernate篇(上)
Hibernate是資料持久化的一種解決方案。
介紹ORM
認識Hibernate
Hibernate影響了java的發展而推出了JPA規範
Hibernate原理
用註解來建實體類
package javaweb.bean; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @Entity // @Entity表示該類能被Hibernate持久化 @Table(name = "tb_cat") // 指定該Entity對應的資料表名 public class Cat { @Id // 指定該列為主鍵 @GeneratedValue(strategy = GenerationType.AUTO) // 主鍵型別,auto為自增長型別 private Integer id; @Column(name = "name") // 指定屬性對應資料庫表的列為name private String name; @Column(name = "description") private String description; @ManyToOne // 指定實體類之間的關係,本例為多個Cat對應一個mother的關係 @JoinColumn(name = "mother_id") private Cat mother; @Temporal(TemporalType.TIMESTAMP) // 日期型別 @Column(name = "createDate") private Date createDate; // 省略getter、setter... }
註解的型別和使用都在註釋裡了,注意到,可以用註釋來指定表的關係,從而實現一些高階的指向
修改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"> <hibernate-configuration> <session-factory> <!-- JDBC配置程式碼 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/zijun?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">stagiaire</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <!-- 是否在控制檯列印生成的SQL語句 --> <property name="show_sql">true</property> <property name="format_sql">true</property> <!-- 指定hibernate啟動的時候,自動建立(create)表,也可以為(update) --> <property name="hbm2ddl.auto">create </property> <!-- 對應的實體類 --> <!-- 1.用xml檔案進行配置 --> <mapping resource="user.hbm.xml" /> <!-- 2.用註解配置的,直接給出實體類就行了 --> <mapping class="javaweb.bean.Cat" /> </session-factory> </hibernate-configuration>
HibernateUtil的程式碼
package javaweb; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { private static final SessionFactory sessionFactory=buildSessionFactory(); private static SessionFactory buildSessionFactory() { try { // Create the SessionFactory from hibernate.cfg.xml return new Configuration().configure("hibernate.cfg.xml").buildSessionFactory(); } catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } public static void shutdown() { // Close caches and connection pools getSessionFactory().close(); } }
一個工具類,幫助我們去配置hibernate,主要工作就是返回一個Session。
新版本中原本註解配置需要使用的特定“AnnotationConfiguration”被“Configuration”合併了,配置的話只要用Configuration就都可以完成。
配置log4j
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
(Maven好用得讓我哭出來了)
因為配置中的hbm2ddl.auto屬性為create,所以每次執行這個程式碼都會把表drop掉再create的。如果只想更新資料,不把原來表給drop了,hbm2ddl.auto要用update。
此外,因為使用的方言是MySQL5Dialect,直接繼承於MySQLDialect,所以使用的引擎為MyISAM(不支援外來鍵)。如果想要使用InnoDB的引擎的話,應該採用MySQL55Dialect(而且它支援事務操作)
<!-- 本來再hibernate.cfg.xml裡的方言配置,報錯了說創不了表 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 改成了MySQL5Dialect就行了,能夠成功drop表、create表-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
DAO層
package javaweb.dao;
import java.io.Serializable;
import java.util.List;
import org.hibernate.Session;
import javaweb.HibernateUtil;
public class BaseDAO<T> {
public void create(T object) {
Session session = HibernateUtil.getSessionFactory().openSession();
try {
session.beginTransaction();
session.persist(object);
} catch (Exception e) {
session.getTransaction().rollback();
} finally {
session.close();
}
}
public void update(T object) {
Session session = HibernateUtil.getSessionFactory().openSession();
try {
session.beginTransaction();
session.update(object);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
} finally {
session.close();
}
}
public void delete(T object) {
Session session = HibernateUtil.getSessionFactory().openSession();
try {
session.beginTransaction();
session.delete(object);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
} finally {
session.close();
}
}
@SuppressWarnings("unchecked")
public T find(Class <? extends T> clazz, Serializable id) {
Session session = HibernateUtil.getSessionFactory().openSession();
try {
session.beginTransaction();
return (T) session.get(clazz, id); // 根據id查詢實體類物件Entity Bean
} finally {
session.getTransaction().commit();
session.close();
}
}
@SuppressWarnings("unchecked")
public List<T> list(String hql) {
Session session = HibernateUtil.getSessionFactory().openSession(); // 開啟一個Hibernate會話
try {
session.beginTransaction(); // 開啟事務
return session.createQuery(hql).list(); // 查詢hql結果,返回List物件
} finally {
session.getTransaction().commit(); // 提交事務
session.close(); // 關閉Hibernate會話
}
}
}
CatServlet
我們是通過直接訪問Servlet來實現頁面的訪問的,因為一般servlet顯示網頁很麻煩,所以這個servlet主要是先接受引數,然後再“添油加醋”(新增一些物件、變數)接著forward到對應的JSP檔案去顯示。這樣的話,這個servlet實際上扮演了中(pi)轉(tiao)站(ke)的身份,由於使用forward是在伺服器端的行為,所以在使用者看來url地址是沒有差別的(酷)。
package javaweb.servlet;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javaweb.bean.Cat;
import javaweb.dao.BaseDAO;
/**
* Servlet implementation class CatServlet
*/
public class CatServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
BaseDAO<Cat> baseDAO = new BaseDAO<Cat>();
/**
* @see HttpServlet#HttpServlet()
*/
public CatServlet() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action=request.getParameter("action");
// 根據url中的action引數實現分流
if ("initAdd".equals(action)) {
initAdd(request,response);
} else if ("add".equals(action)) {
add(request,response);
} else if ("edit".equals(action)) {
edit(request,response);
} else if ("save".equals(action)) {
save(request,response);
} else if ("view".equals(action)) {
view(request,response);
} else if ("list".equals(action)) {
list(request,response);
} else if ("delete".equals(action)) {
delete(request,response);
} else {
list(request,response);
}
}
private void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id=Integer.parseInt(request.getParameter("id"));
Cat cat = baseDAO.find(Cat.class, id); // 先找到要delete的貓
if (cat!=null) {
List<Cat> catList = baseDAO.list("select c from Cat c where c.mother.id="+id);
if (catList.size()>0) { // 有女兒的cat不能直接刪
request.setAttribute("msg", "'"+cat.getName()+"' can't not be deleted. "+" Please delete its child Cats.");
} else {
baseDAO.delete(cat);
request.setAttribute("msg", "delete '"+cat.getName()+"' succeeded.");
}
}
list(request, response);
}
private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("catList", baseDAO.list(" from Cat ")); // 索要屬性,放到reqest裡面
request.getRequestDispatcher("/listCat.jsp").forward(request, response); // forward到jsp顯示
}
private void view(HttpServletRequest request, HttpServletResponse response) {
// TODO Auto-generated method stub
}
private void save(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id=Integer.parseInt(request.getParameter("id"));
int motherId=Integer.parseInt(request.getParameter("motherId"));
String name=request.getParameter("name");
String description=request.getParameter("description");
Cat cat=baseDAO.find(Cat.class, id);
Cat mother=baseDAO.find(Cat.class, motherId);
cat.setName(name); // 暫時物件,一會拿去持久化
cat.setDescription(description);
cat.setMother(mother);
boolean hasLoop=false; // 迴圈檢測,我的媽媽不能同時又是我的女兒
Cat tempMother=mother;
while (tempMother!=null) { // greater-mother can't be the current cat
if (tempMother.getId().intValue()==cat.getId().intValue()) {
hasLoop=true;
break;
}
tempMother=tempMother.getMother();
}
if (!hasLoop) {
baseDAO.update(cat); // 更新,資料持久化
request.setAttribute("msg", "save '"+cat.getName()+"' succeeded."); // 給msg屬性設定,便於在listCat.jsp中顯示最新訊息
} else {
request.setAttribute("msg", "save failed. Loop founded.");
}
list(request, response); // 頁面跳轉為顯示頁
}
private void edit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id=Integer.parseInt(request.getParameter("id"));
Cat cat=baseDAO.find(Cat.class, id);
// in modify mode, the cat being used is assigned from listCat.jsp, so we have the cat.id and cat.name in the beginning
request.setAttribute("cat", cat);
request.setAttribute("catList", baseDAO.list(" from Cat "));
request.getRequestDispatcher("/addCat.jsp").forward(request, response); // addCat.jsp同時能夠實現初始化(增加)Cat和修改Cat的屬性,十分優雅的寫法
}
private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int motherId = Integer.parseInt(request.getParameter("motherId"));
String name=request.getParameter("name");
String description=request.getParameter("description");
Cat mother=baseDAO.find(Cat.class, motherId); // search for the Mother selected
Cat cat = new Cat();
cat.setMother(mother);
cat.setName(name);
cat.setDescription(description);
cat.setCreateDate(new Date());
baseDAO.create(cat); // save to Database
request.setAttribute("msg", "add '"+cat.getName()+"' succeeded.");
list(request, response);
}
private void initAdd(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Cat> catList = baseDAO.list(" select c from Cat c ");
request.setAttribute("catList", catList); // 得到所有的貓貓(便於在裡面選一個當媽媽),然後forward去新增頁面
request.getRequestDispatcher("/addCat.jsp").forward(request, response);
}
}
listCat.jsp
<%@ page import="java.util.List"%>
<%@ page import="javaweb.bean.Cat"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ page isELIgnored="false" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>${ msg }</title>
</head>
<body>
<h4>${ msg }</h4>
[<a href="CatServlet?action=initAdd">add cat</a>]
[<a href="CatServlet?action=list">list cats</a>]
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Mother</th>
<th>Operation</th>
</tr>
<%
@SuppressWarnings("unchecked")
List<Cat> catList = (List<Cat>) request.getAttribute("catList");
for(Cat cat:catList){
out.write("<tr>");
out.write("<td>"+cat.getId()+"</td>");
out.write("<td>"+cat.getName()+"</td>");
out.write("<td>"+cat.getDescription()+"</td>");
String motherString="";
Cat mother= cat.getMother();
while(mother!=null){
if(motherString.trim().length()==0){
motherString=mother.getName();
}else{
motherString=mother.getName()+" / "+motherString;
}
mother=mother.getMother();
}
out.write("<td>"+motherString+"</td>");
out.write("<td>"+"<a href=\"CatServlet?action=delete&id="+cat.getId()+"\" onclick=\"return confirm('sure to delete?');\">delete</a> ");
out.write("<a href=CatServlet?action=edit&id="+cat.getId()+">modify</a>");
out.write("</td>");
out.write("</tr>");
}
%>
</table>
</body>
</html>
要注意的是開頭那句,isELIgnored="false",一開始EL表示式原樣輸出(${ msg },就直接打印出來了而不是尋找attribute裡面一個叫msg的),加了這句話才能實現好了。還有,EL表示式必須是 dollar加大括號!( ${} )
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ page import="java.util.List" %>
<%@ page import="javaweb.bean.Cat" %>
<%@ page isELIgnored="false" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>${ param.action=='initAdd'?'add cat info':'modify cat info' }</title>
</head>
<body>
${ param.action=='initAdd'?'add cat info':'modify cat info' }
[<a href=CatServlet?action=initAdd>add cat</a>]
[<a href=CatServlet?action=list>cat list</a>]
<form action="CatServlet" method="post">
<!-- secretly change action to 'add' or 'save', and id to the current cat 'id'(used only in modifying) -->
<input type="hidden" name="action" value="${ param.action=='initAdd'?'add':'save' }">
<input type="hidden" name="id" value="${cat.id}">
<table>
<tr>
<td>Name:</td>
<td><input type="text" name="name" value="${cat.name}"/></td> <!-- place the name of cat, prepared only for 'modification' -->
</tr>
<tr>
<td>Mother:</td>
<td><select name="motherId">
<option value="0">------please select--------</option>
<%
@SuppressWarnings("unchecked")
List<Cat> catList=(List<Cat>)request.getAttribute("catList");
for(Cat cat : catList){
out.write("<option value="+cat.getId()+">");
String name=cat.getName();
Cat mother=cat.getMother();
while(mother!=null){
name=mother.getName()+"/"+name;
mother=mother.getMother();
}
out.write(""+name+"");
out.write("</option>");
}
%>
</select>
<!-- used only for modification, to change the 'option' in 'select' of the motherId to the current Cat -->
<!-- script (normal state, not 'defer' or 'async') runs in the middle of the parsing of html -->
<script type="text/javascript">document.getElementsByName('motherId')[0].value='${0+cat.mother.id}';</script>
</td>
</tr>
<tr>
<td>Description:</td>
<td><textarea name="description">${cat.description}</textarea></td> <!-- place the description of the current cat, shows only for 'modification' -->
</tr>
<tr>
<td></td>
<td><input type="submit" value="${param.action=='initAdd'?'submit':'save'}"/></td>
</tr>
</table>
</form>
</body>
</html>
非常優雅的一段程式碼,同時將兩個功能(initAdd和edit)整合到一起了,一開始寫增加貓(add)還覺得雲裡霧裡,等寫到edit的時候就驚了。
EL表示式裡面很多做了區分,add和edit該輸出的東西是不一樣的。非常棒。
新增Nixon貓
刪除有女兒Theo的Kitty貓
修改Theo為Mimmy的女兒
刪除Kitty
成功實現了!
非常棒,感覺這個東西激起了我滴興趣了。
需要注意的是,這裡是樹節點的結構,最終的媽媽貓只有Mary White,不能生造一個別的媽媽貓(當然,可以更改)。
概述小結
*********** 遇到一個JavaScript的知識點 ***********
在html中,script的執行順序一般來說分三種情況,如下圖:
‘normal’:正常地巢狀在html中,先編譯之前的html,然後下載、執行本script,接著編譯之後的script
‘async’:邊編譯html邊非同步地下載,然後執行script,接著編譯之後的script
’defer‘:邊編譯html邊非同步地下載,但是在html載入完了才執行script
後兩者要引用.js檔案的時候才能使用
Hibernate體系
Hibernate就相當於是橫亙在Java應用和JDBC之間的一個橋樑
一些概念
SessionFactory負責產生Session、Session負責管理一次使用者操作(並維護當前的事務和資料庫連線)、Transaction事務(操作失敗了能回滾)
可持久化物件的狀態
就三個狀態,僅在建立了java檔案建立的物件時Session開啟前(臨時Transient),在Session裡面的java物件並且該物件已寫進資料庫(持久化Persistent),Session關閉後的java檔案裡的物件,成為了孤兒(分離Detached)
hibernate配置
相當有彈性,能夠支援各種資料庫,一般來說預設的配置就能夠工作了。
配置檔案可為XML或者Properties檔案。
此外,也能夠將配置當作引數在執行時配置
獲取SessionFactory
獲取、斷開資料庫連線
Hibernate連線池引數
就是主要把握c3p0在配置檔案裡怎麼寫才能生效,以及JNDI配置的使用(能夠鬆耦合)。
可配置引數
以及
JDBC可選引數
Hibernate日誌
本章小結
ORM實體對映
JPA提供了註解對映模式,
註解配置
XML配置
DOCTYPE指明檔案位置,dtd驗證xml檔案格式
將配置好的實體類xml在hibernate.cfg.xml中宣告
主鍵對映
1)註解方式
配置主鍵用@Id,用@Column宣告列名;如果列名和屬性名相同時,@Column配置可省略;@GeneratedValue用於指明生成策略(隨後談到)
2)xml方式
主鍵生成策略(@GeneratedValue)
1)註解方式
2)xml方式
user.hbm.xml的配置
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="javaweb.bean.User" table="USER"> <!-- 實體類和資料庫的表之間的對應 -->
<id name="userId" type="int" column="USER_ID">
<generator class="assigned"/> <!-- 每次新增使用者的ID都是手動指定的 -->
</id>
<property name="username"> <!-- 實體類的屬性和資料庫的表中列名之間的對應 -->
<column name="USERNAME"/>
</property>
<property name="createdBy">
<column name="CREATED_BY"/>
</property>
<property name="createdDate" type="date">
<column name="CREATED_DATE"/>
</property>
</class>
</hibernate-mapping>
除了自增( @GeneratedValue(strategy = GenerationType.AUTO) 等價於 <generator class="native"/> )、Indentity(Identity根本上說是一個int,只佔4位元組)或指定assigned之外,還有很多其他奇怪的方法去生成ID(主鍵)的值,如GUID(Global unique identifier全域性唯一識別符號,它是由網絡卡上的標識數字(每個網絡卡都有唯一的標識號)以及 CPU 時鐘的唯一數字生成的的一個 16 位元組的二進位制值。)。
普通屬性配置
註解@屬性配置
除非有特殊需求去配置,不然預設也夠用了
XML屬性配置
區分沒有配置的時候,xml的配置傾向於認為沒有對應的資料庫列。@配置則會預設有這一列,列名和屬性名一樣。
日期屬性配置
@配置日期屬性
日期:java.sql.Date
時間:java.sql.Time
both:java.sql.TimeStamp
xml配置日期屬性
臨時屬性對映
用註解@的話,一些普通的方法有被強行加入資料庫的風險,需要手動加上@Transient臨時屬性。
版本屬性
悲觀鎖會把資料鎖死,只能一個執行緒用。
樂觀鎖會保留當前版本,再去儲存,如果儲存完版本變了,需要重新儲存過程。直到存進去之後版本號還是一樣的,說明儲存過程安全。
註解配置樂觀鎖
xml配置樂觀鎖
加了一行程式碼配置了樂觀鎖之後,別的照常操作就行。樂觀鎖的執行是hibernate的事。
實體對映小結
實體關係對映
單邊一對多的關係:電子郵件管理
UML圖表示
用表來表示
TB_PERSON |
---|
ID |
NAME |
TB_EMAIL |
---|
ID |
PERSON_ID |
根據UML圖,Person有emails屬性。Person中有多個Email關聯,而Email中只有一個Person關聯。所以Person是一方,Email是多方
package javaweb.bean;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name="table_person")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@OneToMany(fetch = FetchType.EAGER, targetEntity = Email.class, cascade = {
CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH
})
@JoinColumns(value= {@JoinColumn(name="person_id", referencedColumnName = "id")}) // config one to many, and column relationship
@OrderBy(value = "email desc") // config order
private List<Email> emails=new ArrayList<Email>();
public List<Email> getEmails() {
return emails;
}
public void setEmails(List<Email> emails) {
this.emails = emails;
}
}
仔細說說這個註解究竟做了什麼:
1)@OneToMany
其中:FetchType.EAGER
是即時載入實體關係,目標類是Email類,層級(資料表中實現的功能)包括儲存、刪除、合併、更新
@OneToMany(fetch = FetchType.EAGER, targetEntity = Email.class, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH})
- 在“一方”定義了一對多的關係,其中當前類為“一方”,另外一個類(Email類)為“多方”,定義多對一的關係。
- 在定義這種關係的時候,需要考慮的問題就是:究竟在哪個表裡面多加一列出來?
2)@JoinColumn
表明了當前實體類是該關係的所有者,即:該實體對應的表中擁有額外的一列,這列儲存指向參考表的外來鍵
The annotation
@JoinColumn
indicates that this entity is the owner of the relationship (that is: the corresponding table has a column with a foreign key to the referenced table)
相對應地,@mappedBy
是指出當前的實體類是該關係的另外一頭
mappedBy
indicates that the entity in this side is the inverse of the relationship, and the owner resides in the "other" entity
*** 假如沒有用註解@JoinColumn
的方式聯合的話,Hibernate就會預設通過一個專門的表/Table來連線兩個表的關係
3)單向關係,沒有mappedBy。(mappedBy屬性定義了此類為雙向關係的維護端,注意:mappedBy 屬性的值為此關係的另一端的屬性名。)
這個例子其實很不好。權當用來理解概念吧。
雙向的ManyToOne和OneToMany都沒有問題,但是Person和Email這種單向OneToMany是不推薦使用的,因為Hibernate會產生一些不可預料的表以及執行一些不必要的SQL語句。詳情看這裡
package javaweb.bean;
import javax.persistence.*;
@Entity
@Table(name = "tb_email")
public class Email {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String email;
}
<mapping class="javaweb.bean.Person"/>
<mapping class="javaweb.bean.Email"/>
接下來測試執行程式碼
Fetch就是控制延時載入與否的
FetchType.EAGER即時載入 FetchType.LAZY延時載入
如何區分各種關係
多對一(Many-To-One)、一對多(One-To-Many),單邊(Unidirectional)、雙邊(Bidirectional),諸如此類的關係,可以參考這裡
雙邊的多對一關係
配置Clazz類
@Entity // Entity配置
@Table(name="tb_class") // Table配置
public class Clazz {
@Id // id配置
@GeneratedValue(strategy = GenerationType.AUTO) //GeneratedValue配置
private Integer id;
private String name; // 班級名,使用預設配置(交給Hibernate,不用加註解)
@OneToMany(mappedBy = "clazz") // OneToMany,使用反向配置(關係存放在另外一個實體類中)
private List<Student> students = new ArrayList<Student>(); // 所有的學生
}
雙邊關係中,控制權通常交給多方,因此這裡OneToMany沒有配置資料庫的外來鍵列,而只配置了一個mappedBy屬性,值為clazz,告訴Hibernate,配置資訊要到Student類的clazz屬性中找。
配置Student類
@Entity
@Table(name="tb_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST})
@JoinColumn(name = "class_id") // 外來鍵為class_id
private Clazz clazz; // 另外一個類配置了OneToMany的mappedBy,就來到這裡找外來鍵的配置
private String name;
private String sex;
}
@JoinColumn配置了外來鍵列,這裡把控制權交給了多方,也可以交給一方。
當一方控制時,一方的配置和單邊的一對多配置完全相同。
測試類
public class TestClazzStudent {
public static void main(String[] args) {
Clazz clazz=new Clazz();
clazz.setName("3year2class");
Student student1=new Student();
student1.setName("ao ZHOU");
student1.setSex("M");
Student student2=new Student();
student2.setName("qiang LIU");
student2.setSex("F");
Session session=HibernateUtil.getSessionFactory().openSession(); //開啟會話
session.beginTransaction(); //開啟事務
session.persist(clazz); //持久化clazz
session.persist(student1); //持久化學生1
session.persist(student2);
//設定班級,由於控制權配置在多方,因此要通過在多方student來儲存實體間的關聯
student1.setClazz(clazz); //儲存學生1和班級的關係
student2.setClazz(clazz);
//儲存依賴關係
session.save(student1);
session.save(student2);
session.getTransaction().commit(); //提交事務
session.beginTransaction(); //開闢一個新事務
// 查詢名為三年二班的班級
Clazz c = (Clazz) session.createQuery("select c from Clazz c where c.name=:name").setParameter("name", "3year2class").uniqueResult();
session.refresh(c); // 重新從資料庫獲取資料,相當於'clean...',防止快取
System.out.println("all students from 3year2class:");
for (Student s : c.getStudents()) {
System.out.println("\tname: "+s.getName()+", gender: "+s.getSex());
} //輸出學生
List<Student> students=session.createQuery("select s from Student s where s.clazz.name=:name ").setParameter("name", "3year2class").list();
System.out.println("all students from 3year2class:");
for (Student s : students) {
System.out.println("\tname: "+s.getName()+", gender: "+s.getSex());
}
session.getTransaction().commit(); //提交事務
session.close(); // 關閉會話
}
}
雙邊關係方便,但也有潛在帶來巨大開銷的風險
單邊多對多關係
UML圖
通常論壇文章都是多個帖子Post對應多個標籤Tag,反之亦然(多個Tag對應多個Post),因此我們可以用以下關係表達。注意,在多對多關係中需要增加一個表Table來表示關係。
左邊是標籤和帖子兩個實體類之間的關係,右邊是對應資料庫表中的關係
多對多屬性並須用@JoinTable屬性配置中間關係。
joinColumns配置當前的表與中間表的對應關係
inverseJoinColumns配置對面那個表(非當前表)與中間表的對應關係
1)註解@配置
Post.java
@Entity
@Table(name = "tb_post")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST})
@JoinTable(
name = "tb_tag_post",
joinColumns = @JoinColumn(name = "post_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")
)
private Set<Tag> tags = new HashSet<Tag>();
private String title;
@Column(columnDefinition = ("text"))
private String content;
}
Tag.java
@Entity
@Table(name = "tb_tag")
public class Tag {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
}
所謂單邊的關係,即只需要在兩個java類中的其中一個配置ManyToMany關係就可以了。雙邊就是相對地另外一個類也需要配置ManyToMany。
*** 單邊變雙邊的多對多,只需要在Tag中加多一個mappedBy指定Post裡與之對應的屬性即可:
@ManyToMany(mappedBy = "tags") private Set<Post> posts = new HashSet<Post>();
2)XML配置
預設開頭都是一樣的,主要是dtd語法的匹配mapping,如果沒有這個開頭不能正常運作。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
Tag.hbm.xml
<hibernate-mapping>
<class name="javaweb.bean.Tag" table="tb_tag">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name">
</property>
</class>
</hibernate-mapping>
Post.hbm.xml
<hibernate-mapping>
<class name="javaweb.bean.Post" table="tb_post">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="title">
</property>
<property name="content" type="text">
</property>
<set name="tags" table="tb_tag_post" cascade="persist" lazy="false">
<key column="post_id"></key>
<many-to-many column="tag_id" class="javaweb.bean.Tag" where=" name != '' " not-found="exception"></many-to-many>
</set>
</class>
</hibernate-mapping>
註解配置中注意set中的post_id和tag_id是如何分配的。這裡是以post為配置方,tag為被配置方。
*** 在雙方多對多配置中,只需在<hibernate-mapping>內追加以下程式碼表明Tag到Post的對映關係即可
<set name="posts" table="tb_tag_post" inverse="true"> <key column="tag_id"></key> <many-to-many column="post_id" class="javaweb.bean.Post"></many-to-many> </set>
單邊一對一關係
船員和船的關係,
以上分別是艦船與船員的實體間、資料庫間的關係
這裡既體現了一對多(一船多船員)也體現了一對一(一船一船長)的關係。
Ship.java
@Entity
@Table(name = "tb_ship")
public class Ship {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH})
@JoinColumn(name = "captain_id", unique = true)
private Sailor Captain;
@OneToMany(mappedBy = "ship", cascade = CascadeType.PERSIST)
private Set<Sailor> sailors = new HashSet<Sailor>();
}
Sailor.java
@Entity
@Table(name = "tb_sailor")
public class Sailor {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "ship")
private Ship ship;
}
測試程式碼
public static void main(String[] args) {
Ship ship = new Ship();
ship.setName("Tatanic");
Sailor captain=new Sailor();
captain.setName("Smith");
captain.setShip(ship);
Sailor sailor = new Sailor();
sailor.setName("Jack");
sailor.setShip(ship);
ship.setCaptain(captain);
ship.getSailors().add(sailor);
ship.getSailors().add(captain);
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.persist(ship);
List<Sailor> list = session.createQuery("select s from Sailor s where s.ship.name = :name").setParameter("name", "Tatanic").list();
for (Sailor s : list) {
System.out.println("Sailor: "+s.getName());
System.out.println("Ship: "+s.getShip().getName());
System.out.println("Cap: "+s.getShip().getCaptain().getName());
System.out.println();
}
session.getTransaction().commit();
session.close();
}
主鍵相同的一對一關係
一個客戶和他自己的住址是一對一的關係,因此可以用同一個主鍵進行儲存。
實體、資料表關係圖
注意:兩個表之間沒有關聯關係,Hibernate是根據逐漸判斷對應關係的。
@Entity
@Table(name = "tb_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@OneToOne
@PrimaryKeyJoinColumn
private Address address;
}
@Entity
@Table(name = "tb_address")
public class Address {
@Id
// @GeneratedValue(strategy = GenerationType.AUTO) // should not be assigned automatically
private Integer id;
@OneToOne
@PrimaryKeyJoinColumn
private Customer customer;
private String address;
private String zip;
private String telephone;
}
因為採用主鍵匹配一對一,所以Address中不能使用任何的主鍵生成策略,應由@PrimaryKeyJoinColumn自動配對兩個實體類的id一對一關係。
測試類
public static void main(String[] args) {
Customer customer = new Customer();
customer.setName("Helloween");
Address address = new Address();
address.setAddress("Guangzhou Xianggang Lu");
address.setTelephone("020-78783999");
address.setZip("510000");
// address.setCustomer(customer); // DONT do it manually!
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.persist(customer);
address.setId(customer.getId()); // BUT do need to set the id manually
session.persist(address);
session.flush();
List<Customer>list = session.createQuery("select c from Customer c where c.name=:name ").setParameter("name", "Helloween").list();
for (Customer c : list) {
session.refresh(c); // avoid cache, refresh from the DB
System.out.println("Customer Name: "+c.getName());
System.out.println("\t tele: "+c.getAddress().getTelephone());
System.out.println("\t zip: "+c.getAddress().getZip());
System.out.println("\t addr: "+c.getAddress().getAddress());
}
session.getTransaction().commit();
session.close();
}
XML配置Customer
僅僅多了one-to-one的一對一屬性,其他基本操作
XML配置Address
Map對映
@MapKey配置Map
@MapKey中小括號的意思是,用學生實體類中的name來作為Map<String, Student>中的String的值,以學生的名字作為學生物件的索引
XML配置Map
inverse等價於mappedBy,是用來指定是否反向屬性
當一對一、多對一時,一方表現實體型別屬性;
當一對多、多對多時,多方表現為List、Set等集合屬性,也可以配置Map型