2010年8月17日 星期二

[Struts2]-Hibernate Integration

在談論Hibernate前我們要先了解JPA(Java Persistence API)

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 {

  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的method內是用一個userDao類別來封裝物件與db之間的transaction。
同時在這個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.java
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 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操控物件。

下面就是整個範例的操作畫面:

沒有留言:

張貼留言