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 ListUserDaoImpl.javalistUser(); public User listUserById(Long userId); public void deleteUser(Long userId); }
package 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 ListlistUser() { 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操控物件。
下面就是整個範例的操作畫面: