Java Spring Framework 筆記 - 初探Spring Web 學習筆記(2)

這篇將延續前一篇文章,建立起DAO層與Service層程式,並將資料呈現到畫面上。

建立 Person Model

在前一篇在資料庫建立了 Person的Table, Person Model是為了可以對應到 Person Table的各個欄位,等於是 DB與 Java 物件的對應: Person.java:

package org.joseph.example.model;

public class Person {

	private String uuid;
	private String firstName;
	private String lastName;
	private String gender;
	private String email;

	public String getUuid() {
		return uuid;
	}

	public void setUuid(String uuid) {
		this.uuid = uuid;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

}

建立 Person DAO

首先先新增一個 PersonDAO.java:

package org.joseph.example.dao;

@Component("personDAO")
public class PersonDAO {
	
	private NamedParameterJdbcTemplate jdbc;
	
    @Autowired
	public void setDataSource(DataSource jdbc) {
		this.jdbc = new NamedParameterJdbcTemplate(jdbc);
	}
	
	public List<Person> listPersons(){
		return this.jdbc.query("SELECT * FROM PERSON", new RowMapper<Person>() {
			public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
				// TODO Auto-generated method stub
				Person person = new Person();
				person.setUuid(rs.getString("uuid"));
				person.setFirstName(rs.getString("lastName"));
				person.setFirstName(rs.getString("firstName"));
				person.setGender(rs.getString("gender"));
				person.setEmail(rs.getString("email"));
				return person;
			}
		});
	}
}

在這邊於類別名稱上加上 Component,目的是為了讓Spring 可以自動掃描到這個檔案,並允許作為注入來源。同時,加上了一個 listPersons的方法,用來將資料表內所有資料都列出來,透過NamedParameterJdbcTemplate 提供的對資料庫存取方式來取得資料表內資料,並透過RowMapper將取出來的原始資料轉為 Person java物件

建立好 PersonDAO後,接著要建立對應的 bean configuration file:

dao-context.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"
	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-4.3.xsd">


	<context:component-scan base-package="org.joseph.example.dao"></context:component-scan>
</beans>

建立一個基本的 bean configuration file,並啟用 context namespace,同時加上 component-scan的 element,指定basic-package為DAO的 package:org.joseph.example.dao

同時為了要可以透過Autowired方式注入JNDI的 DataSource,須要額外啟用 JEE namespace:

啟用jee

接著在切換到JEE的頁簽,加上jee:jndi-lookup

jee設定

jee設定

jndi-name設定可以參考web.xml中對應到 Tomcat context.xml JNDI的設定,目的是讓Spring可以將JNDI 資源作為可注入的來源。

接著要在專案中加入ContextLoaderListener,目的在讓Spring可以讀取自己定義的Container, 預設會讀取application-context.xml,而這邊要讀取的是dao-context.xml,因此要將這個設定加入到ContextLoaderListener的設定中,須要透過加上contextConfigLocation設定,告訴Spring要讀取的檔案路徑,讓Spring在啟動後讀取這個Container設定。

在spring-web.jar中可以找到org.springframework.web.context.ContextLoaderListener.class,並且加上contextConfigLocation設定。

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>spring-website</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:org/joseph/example/config/dao-context.xml</param-value>
	</context-param>

	<servlet>
		<description></description>
		<display-name>persons</display-name>
		<servlet-name>persons</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>persons</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	<resource-ref>
		<description>DB Connection</description>
		<res-ref-name>jdbc/mydb</res-ref-name>
		<res-type>javax.sql.DataSource</res-type>
		<res-auth>Container</res-auth>
	</resource-ref>

</web-app>

如果要測試是否有在啟動時讀取 dao-context.xml設定,這邊可以在PersonDAO上加入一個constructor來測試:

personDAO:

	PersonDAO(){
		System.out.println("PersonDAO loaded.");
	}

啟動設定

建立 Person Service

定義一個Service讓 Controller呼叫,呼叫的流程為 Controller -> Service -> Dao -> DataSource。

建立Service的步驟與建立DAO的部分相當類似,首先先建立一個PersonService.java:

@Service("personService")
public class PersonService {

	@Autowired
	private PersonDAO personDao;
	
	public List<Person> getPersonsList(){
		return this.personDao.listPersons();
	}
}

因為PersonService須要使用 PersonDAO,所以可以透過Autowired的方式注入。

並定義一個getPersonsList方法,透過這個方法調用personDao,並回傳資料

接著建立service-context.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"
	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-4.3.xsd">


	<context:component-scan base-package="org.joseph.example.service"></context:component-scan>
</beans>

同時指定component-scan目錄為service之目錄。

建立好component-scan.xml後,同樣要在web.xml內的 contextConfigLocation設定內加上路徑,如果contextConfigLocation內有多個路徑,以逗號最為區隔:

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
		classpath:org/joseph/example/config/dao-context.xml,
		classpath:org/joseph/example/config/service-context.xml</param-value>
	</context-param>

將資料呈現至網頁

這邊調整原本的PersonController,將原本測試的程式移除,改為呼叫PersonService來取得資料,而 PersonSerice透過 Autowired方式注入:

PersonController:

package org.joseph.example.controller;

import java.util.List;

import org.joseph.example.model.Person;
import org.joseph.example.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class PersonController {

	@Autowired
	private PersonService personService;

	@RequestMapping("/")
	public String showIndex(Model model) {
		List<Person> personsList = this.personService.getPersonsList();
		model.addAttribute("persons", personsList);
		return "index";
	}
}

這邊將透過Service取出之資料方入 Model的 Map中,接著會回到 index.jsp,讓資料呈現在畫面上。

index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:forEach var="row" items="${personsList}">
    FirstName: ${row.firstName}<br/>
    LastName: ${row.lastName}<br/>
    Gender: ${row.gender}<br/>
    Email: ${row.email}<br/>
</c:forEach>
</body>
</html>

執行結果:

執行結果

到這邊已經基本串起整個資料流程了,由請求進入personController後,接著透過personService呼叫personDao將資料取出來,最後在personController內將所取出之資料寫入Model的Map中,在JSP由SPEL將資料取出並呈現在畫面上。

在下一篇將會完成整個資料的新增、刪除與修改功能。