Java的物件導向模型與關聯資料庫模型之間有相當程度的不匹配,而一些物件與資料庫資料同步、更新,也就是所謂的Java永續儲存(Persistence)問題,在過去,Object/Relational Mapping(ORM)有 JBoss Hibernate、Oracle TopLink等解決方案,而JPA為吸收這些方案的經驗,所製訂出的Java永續儲存標準。
使用JPA,底層可以使用不同廠商的ORM實作,而介面則是JPA的標準,若您使用NetBeans+Glassfish,則預設的底層實作為TopLink,JBoss的工具其底層實作則為Hibernate。
要在Struts2使用Hibernate Plugin,必須有下列libs:
antlr-2.7.6.jar commons-collections-3.1.jar commons-fileupload-1.2.1.jar commons-io-1.3.2.jar commons-lang-2.3.jar commons-logging-1.1.jar dom4j-1.6.1.jar ejb3-persistence.jar freemarker-2.3.13.jar hibernate3.jar hibernate-annotations.jar hibernate-commons-annotations.jar hibernate-validator.jar hsqldb.jar javassist-3.9.0.GA.jar jta-1.1.jar junit-3.8.1.jar log4j-1.2.15.jar ognl-2.6.11.jar slf4j-api-1.5.8.jar slf4j-log4j12-1.5.8.jar struts2-convention-plugin-2.1.6.jar struts2-core-2.1.6.jar struts2-fullhibernatecore-plugin-1.4-GA.jar xwork-2.1.2.jar另外在struts2.xml的package中,要繼承hibernate-default如下:
<struts> <package name="user" extends="hibernate-default"> <action name="saveOrUpdateUser" method="saveOrUpdate" class="web.UserAction"> <result name="success" type="redirect">listUser</result> </action> <action name="listUser" method="list" class="web.UserAction"> <result name="success">/register.jsp</result> </action> <action name="editUser" method="edit" class="web.UserAction"> <result name="success">/register.jsp</result> </action> <action name="deleteUser" method="delete" class="web.UserAction"> <result name="success" type="redirect">listUser</result> </action> </package> </struts>上面的struts.xml也透露出了struts2的另一個應用,DispatchAction functionality,
action可以指定到class內的method,而不一定是預設的execute method。
接下來我們設定hibernate.cfg.xml來做O/R Mapping,
hibernate.cfg.xml主要是設定資料庫連線資訊,如url、driver、帳密等,
並註明這個連線資訊mapping到那些object。
<hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property> <property name="hibernate.connection.username">root</property> <property name="connection.password">syscom</property> <property name="connection.pool_size">1</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">true</property> <property name="hbm2ddl.auto">create</property> <mapping class="domain.User"/> </session-factory> </hibernate-configuration>這裡的mapping是直接mapping到一個User類別。
直接mapping到物件的話要使用annotation。使用Hibernate Annotations,
不需要使用HBM映射檔案,而是直接在POJO上使用Annotation設定對映關係,
如下的User.java:
package domain;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity //說明這是一個映射物件
@Table(name = "USER") //mapping到那一個table
public class User implements Serializable {
private Long id;
private String name;
private String gender;
private String country;
private String aboutYou;
private Boolean mailingList;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "USER_ID") //非必要,在欄位名稱與屬性名稱不同時使用
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "USER_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Column(name = "USER_GENDER")
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Column(name = "USER_COUNTRY")
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Column(name = "USER_ABOUT_YOU")
public String getAboutYou() {
return aboutYou;
}
public void setAboutYou(String aboutYou) {
this.aboutYou = aboutYou;
}
@Column(name = "USER_MAILING_LIST")
public Boolean getMailingList() {
return mailingList;
}
public void setMailingList(Boolean mailingList) {
this.mailingList = mailingList;
}
}
所有的Annotation都是在javax.persistence這個package之下,這是標準的 JPA Annotations,Hibernate實作了這些Annotations,其中id是個特殊的屬性,Hibernate會使用它來作為主鍵識別,你可以定義主鍵產生的方式,這邊設定為自動產生主鍵,依賴於資料庫自動增生主鍵的機制,也就是相當於在XML中設定native。
使用Hibernate另一個酷炫的好處是我們不用事先建立資料表,只要hibernate.cfg.xml設定檔
內的hbm2ddl.autocreate參數值設為create,基於JPA的概念,
Hibernate底層會幫我們完成這一切。自動建立的資料表如下:
這裡要特別注意的是,如果hbm2ddl.autocreate參數值設為create,則每次系統重啟時資料表
會被清空,如果要保留原本資料表內的資料,參數值則必須設為update。
接著我們回頭來看前面的struts.xml,
可以看出這是一個新增、刪除、修改使用者資料的範例,
接下來我們將這個範例檔補齊。
首先編寫前端的render頁面register.jsp:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Registration Page</title>
<s:head />
<style type="text/css">
@import url(style.css);
</style>
</head>
<body>
<s:form action="saveOrUpdateUser">
<s:push value="user">
<s:hidden name="id" />
<s:textfield name="name" label="User Name" />
<s:radio name="gender" label="Gender" list="{'Male','Female'}" />
<s:select name="country" list="{'India','USA','UK'}" headerKey=""
headerValue="Select" label="Select a country" />
<s:textarea name="aboutYou" label="About You" />
<s:checkbox name="mailingList"
label="Would you like to join our mailing list?" />
<s:submit />
</s:push>
</s:form>
<s:if test="userList.size() > 0">
<div class="content">
<table class="userTable" cellpadding="5px">
<tr class="even">
<th>Name</th>
<th>Gender</th>
<th>Country</th>
<th>About You</th>
<th>Mailing List</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<s:iterator value="userList" status="userStatus">
<tr
class="<s:if test="#userStatus.odd == true ">odd</s:if><s:else>even</s:else>">
<td><s:property value="name" /></td>
<td><s:property value="gender" /></td>
<td><s:property value="country" /></td>
<td><s:property value="aboutYou" /></td>
<td><s:property value="mailingList" /></td>
<td><s:url id="editURL" action="editUser">
<s:param name="id" value="%{id}"></s:param>
</s:url> <s:a href="%{editURL}">Edit</s:a></td>
<td><s:url id="deleteURL" action="deleteUser">
<s:param name="id" value="%{id}"></s:param>
</s:url> <s:a href="%{deleteURL}">Delete</s:a></td>
</tr>
</s:iterator>
</table>
</div>
</s:if>
</body>
</html>
接下來是後端的action物件,UserAction.java:package web; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; import dao.UserDAO; import dao.UserDAOImpl; import domain.User; public class UserAction extends ActionSupport implements ModelDriven在action的method內是用一個userDao類別來封裝物件與db之間的transaction。{ private User user = new User(); private List userList = new ArrayList (); private UserDAO userDAO = new UserDAOImpl(); @Override public User getModel() { return user; } public String saveOrUpdate() { userDAO.saveOrUpdateUser(user); return SUCCESS; } public String list() { userList = userDAO.listUser(); return SUCCESS; } public String delete() { HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST); userDAO.deleteUser(Long.parseLong(request.getParameter("id"))); return SUCCESS; } public String edit() { HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST); user = userDAO.listUserById(Long.parseLong(request.getParameter("id"))); return SUCCESS; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public List getUserList() { return userList; } public void setUserList(List userList) { this.userList = userList; } }
同時在這個action我們可以看到struts2的另一個特殊用法,ModelDriven Action,
繼承ModelDriven interface後,action invoke過程就會涉入modelDriven interceptor,
此時framework就可以自動傳送前端表單資料到所覆寫的getModel() method所回傳的物件。
UserDao.java
package dao;
import java.util.List;
import domain.User;
public interface UserDAO {
public void saveOrUpdateUser(User user);
public List listUser();
public User listUserById(Long userId);
public void deleteUser(Long userId);
}
UserDaoImpl.javapackage dao;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.googlecode.s2hibernate.struts2.plugin.annotations.SessionTarget;
import com.googlecode.s2hibernate.struts2.plugin.annotations.TransactionTarget;
import domain.User;
public class UserDAOImpl implements UserDAO {
@SessionTarget
Session session;
@TransactionTarget
Transaction transaction;
@Override
public void saveOrUpdateUser(User user) {
try {
session.saveOrUpdate(user);
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
}
}
@Override
public void deleteUser(Long userId) {
try {
User user = (User) session.get(User.class, userId);
session.delete(user);
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
@Override
public List listUser() {
List courses = null;
try {
courses = session.createQuery("from User").list();
} catch (Exception e) {
e.printStackTrace();
}
return courses;
}
@Override
public User listUserById(Long userId) {
User user = null;
try {
user = (User) session.get(User.class, userId);
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
}
UserDAOImpl是本篇另一個重點,它說明如何利用annotation來取得物件與db之間的
操控權session、transaction。
利用@SessionTarget、@TransactionTarget這兩個annotation可以從hibernate.cfg.xml設定檔中,取得相對應的session、transaction控制物件。
除了利用annotation來取得session外,我們也能用傳統的方式,編寫一個session factory,
這裡要注意的是,POJO類別(User.java)內如果是使用annotation來設定對映關係的話,
就必須要利用AnnotationConfiguration類別來取得SessionFactory。
下面我們編寫一個HibernateUtil class來專門取得SessionFactory。
package dao;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.SessionFactory;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
接著我們就能從SessionFactory內取得session及transaction操控物件。下面就是整個範例的操作畫面:







