1. 程式人生 > >WEB開發(7) Hibernate篇(上)

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&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;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>&nbsp;&nbsp;");
			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
EMAIL
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 @JoinColumnindicates 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型