Google Code Prettify

2016年4月30日 星期六

spring MVC - getting started

spring 2.5.x 版以前的 MVC 架構不是很有彈性,大部份的 web 程式會整合 struts,到了 spring 3.x 版後改進了不少,現在刻意整合 struts 似乎不是那麼有意義了,底下的筆記整理出如何開始一個 spring MVC 程式。
(source code: Bitbucket)

這個程式只會有三個網頁:
  1. index.jsp: 首頁,使用者輸入帳號、密碼後按【登入】。
  2. success.jsp: 帳號、密碼正確導到此頁。
  3. fail.jsp: 帳號、密碼錯誤導到此頁。
  • 加入 jar 檔: 如果你的專案形態是 maven 專案,請確定在 pom.xml 中有加入如下片斷:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>
如果不是 maven 專案,請到 http://mvnrepository.com/artifact/org.springframework/spring-webmvc 下載相關 jar 檔。
  • web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
    <display-name>MVC</display-name>
  
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:beans-config.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:mvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
  
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
        </servlet-mapping>
  
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
  1. 綠色部份,設定在 web server 啟動時載入 classpath 中的 mvc-config.xml。
  2. 黃色部份,當 url 中有 *.do 由 spring 處理。
  3. 橘色部份,雖然這個程式並沒有要 spring 載入其它 bean,但是這部份的設定不能省略,beans-config.xml 可以如下給個空檔。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


</beans>
  • mvc 設定: 在 Source Folder 裡新增一個 spring 設定檔,即上面 web.xml 中指定的 MVC 設定檔 - mvc-config.xml,加入如下內容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
>

    <context:component-scan base-package="idv.steven.mvc.controller" />
    <mvc:annotation-driven enable-matrix-variables="true"/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:prefix="/jsp/"
        p:suffix=".jsp"
    />
</beans>
  1. 綠色部份,base-package 是 controller class 所在 package,如果設定後 spring 會在 web server 啟動時,將 controller class 載入,如果這個 package 下還有下層 package,spring 也會一併搜尋。
  2. 黃色部份,由 controller 導向 jsp 時,會自動在名稱前加 /jsp/,名稱後加 .jsp,所以如果傳回 success,即是導向 /jsp/success.jsp 網頁,不這麼設定的話,controller 就要寫清楚要導向那個 jsp 網頁. 
  • 網頁: 這裡只說明 index.jsp,另外 success.jsp 及 fail.jsp 隨意的顯示一段訊息以確定程式正確導到正確網頁即可,index.jsp 如下: 注意網址,有特別加上 .do,以讓 spring 來處理,submit 後傳給 controller 帳號、密碼。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>MVC 首頁</title>
</head>
<body>

    <form method="post" action=<c:url value="/index.do" />>
        帳號: <input type="text" name="userName"/>
        密碼: <input type="password" name="password"/>
        <input type="submit" value="登入"/>&nbsp;
    </form>    
    
</body>
</html>
  • form class: 為了讓上面網頁傳入的兩個參數有個承載的容器,定義一個 POJO 如下,裡面的變數對應著上面網頁的變數,注意大小寫要相同。
public class IndexForm {
    private String userName;
    private String password;
    
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
  • Controller: 
@Controller
public class IndexController {
    
    @RequestMapping("/index")
    public String login(IndexForm form) {
        if ("steven".equalsIgnoreCase(form.getUserName()) && "123456".equals(form.getPassword())) {
            
            return "success";
        }
        
        return "fail";
    }
}
  1. 綠色部份,Controller 類別前要加上 @Controller 以供 spring 識別。
  2. 橘色部份,@RequestMapping 內為網址,當 index.jsp submit 後,spring 會在 controller 中找出網址相同的,執行該 method,有關 @RequestMapping 的詳細用法,會另篇說明。
  3. 黃色部份,這裡傳入 form class,但其實並非只能這麼使用,參數可以由 0 到 無限多個,詳細用法也會另篇說明,這裡可以看的出來 index.jsp 傳過來的變數剛好會被 form class 所承接。
  4. 紫色部份,controller 執行完要導向那個 jsp 呢? 這裡傳回 sucess 及 fail,分別代表要導向 /jsp/success.jsp 及 /jsp/fail.jsp。
  • see also
        JPA + Spring: 資料庫連線設定 (JNDI@Tomcat)



2016年4月19日 星期二

JPA: 新增含 SEQUENCE 欄位的資料

接繼上一篇 (Hibernate + Spring + JPA (configure)),之前只說明到怎麼查詢,這裡將說明新增資料的方法。假設有如下的 table:

最後一個欄位 ID 是序號,由 1 開始每次會自動加 1,因為我的資料庫是使用 Oracle,需要在 Oracle 新增一個 SEQUENCE,方法如下:
CREATE SEQUENCE  "STEVEN"."SQ_LOGINLOG"  MINVALUE 1 MAXVALUE 9999999999 INCREMENT BY 1 START WITH 1 NOCACHE ORDER NOCYCLE;
Java 的 Entity 如下宣告,注意兩件事:
  1. Oracle 自動產生序號時,GenerationType 要用 SEQUENCE (綠色)。
  2. sequenceName (紅色) 和資料庫中的 SEQUENCE 名稱要一樣,表示要使用剛剛建立的那一個 SEQUENCE。
@Entity
public class LoginLog implements Serializable {
    private String emNo;
    private Date loginTime;
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sq_loginlog")
    @SequenceGenerator(name="sq_loginlog", sequenceName="SQ_LOGINLOG", allocationSize=1)
    private Long id;
    
    public String getEmNo() {
        return emNo;
    }
    public void setEmNo(String emNo) {
        this.emNo = emNo;
    }
    public Date getLoginTime() {
        return loginTime;
    }
    public void setLoginTime(Date loginTime) {
        this.loginTime = loginTime;
    }
    
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
}
DAO 的寫法如下,注意以下幾點:
  1. EntityManager 在查詢時可以直接使用 spring 設定檔中的 bean,在 insert、update、delete 時不可以! 必須每次重新由 EntityManagerFactory 中建立一個新的。
  2. 建立 EntityManagerFactory 時,persistence 的名稱要用 persistence.xml 中的 persistence-unit 的名稱 (參看上一篇)。 
@Repository
public class LoginLogDaoImpl implements LoginLogDao {
    @Autowired
    private LoginLog dto;
    
    @Override
    public int save(String emNo, Date loginTime) {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit");
        EntityManager entityManager = factory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        transaction.begin();
        dto.setEmNo(emNo);
        dto.setLoginTime(loginTime);
        
        entityManager.persist(dto);
        transaction.commit();
        
        entityManager.close();
        
        return 1;
    }
}