10月30日

Spring Securityによるログアウトの方法

Spring Security でログアウトを実装するには、設定ファイル spring-security.xml に以下の設定を追加する。

	<sec:http auto-config="true">
		<intercept-url pattern="/**" access="ROLE_USER" />
		<sec:logout
			logout-url="/logout"
			logout-success-url="/"
			invalidate-session="true"
			delete-cookies="JSESSIONID" />
	</sec:http>

sec:logout タグ内の logout-url で指定した URL にアクセスすると、セッション情報が破棄される。

ログアウトを追加したい画面に対応する JSP に、以下のリンクを追加すればよい。
ログイン中のユーザーがここをクリックすると、セッション情報が破棄され、ログアウトする。

<a href="/logout">Logout</a>

トラブルシューティング

DBに登録でエラーになる

Q.新規ユーザーをDBに登録するところで例外が発生して登録できない。
A.コンソールの内容を確認すると、commit()で例外が発生している。メッセージを確認すると、「unique constraint or index violation」と表示。
プライマリキーを自動で割り当てるように、エンティティにアノテーションを追加すれば解決する。

10月3日

GITを使ってチーム内でソースコードを共有する

BacklogのプロジェクトでGitリポジトリを作成する

こちらを参考に、BacklogにGitリポジトリを作成する。

プロジェクトをローカルリポジトリにコミットする

プロジェクトをBacklogのリモートリポジトリにプッシュする

こちらを参考に、プロジェクトをリモートリポジトリにプッシュする。

リモートリポジトリをローカルにクローンする

代表者以外の人は、代表者がリモートリポジトリにプッシュしたプロジェクトを、ローカルにクローンする。

  1. Eclipseで[ファイル]-[インポート]を選択。
  2. ダイアログで[Git]-[Gitからプロジェクト]を選択して「次へ」をクリック。
  3. [Clone URI]を選択して「次へ」をクリック
  4. URI に、BacklogのプロジェクトのGitタブにあるURLを貼り付ける。
  5. backlog-1

  6. Backlogプロジェクトのユーザー名とパスワードを入力して「次へ」をクリック
  7. masterがチェックされた状態で「次へ」をクリック
  8. git-1

  9. ローカルの保管場所はそのままで「次へ」をクリック
  10. git-2

  11. プロジェクトのインポートもそのままの設定で「完了」をクリック
  12. git-4

  13. Eclipseにプロジェクトが追加された!
  14. git-5

ユーザー認証

ユーザー認証はSpring Securityを使用する。

こちらのサイトを参考に。

その他、「srping security hibernate ユーザー認証」などのキーワードで検索するとよい。

9月26日

Rooによる超高速開発

コントローラを作る

Roo Shell で以下のコマンドを実行。

> web mvc controller --class ~.HelloController --preferredMapping /helo

昨日の動作するプロジェクトのバックアップを以下の場所に置いたので、エラーが出る人がこちらからコピーしてみてください。

\\kgakusei1\share\澤田\SE3Java2014\20140925

FormDataクラスを作成する。

package jp.abc;

public class FormData {
	private String input;

	public String getInput() {
		return input;
	}

	public void setInput(String input) {
		this.input = input;
	}
}

index.jspxを編集する。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" 
     xmlns:spring="http://www.springframework.org/tags" 
     xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" 
     xmlns:form="http://www.springframework.org/tags/form"
     version="2.0">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <jsp:output omit-xml-declaration="yes"/>
  <spring:message code="label_helo_index" htmlEscape="false" var="title"/>
  <util:panel id="title" title="${title}">
    <spring:message code="application_name" htmlEscape="false" var="app_name"/>
    <h3>
      <spring:message arguments="${app_name}" code="welcome_titlepane"/>
    </h3>
    <p>${message}</p>
    <form:form modelAttribute="formData" action="form">
        <form:input path="input" />
        <input type="submit" />
    </form:form>
  </util:panel>
</div>

HelloController.java を編集する。

package jp.abc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@RequestMapping("/helo/**")
@Controller
public class HelloController {

    @RequestMapping(method = RequestMethod.POST, value = "{id}")
    public void post(@PathVariable Long id, ModelMap modelMap,
    		HttpServletRequest request,
    		HttpServletResponse response) {
    }

    @RequestMapping
    public String index(Model model) {
    	model.addAttribute("formData", new FormData());
        return "helo/index";
    }

    @RequestMapping(method = RequestMethod.POST, value = "/form")
    public String post(@ModelAttribute FormData form, Errors result, Model model) {
    	model.addAttribute("message", "You typed: " + form.getInput());
    	return "helo/index";
    }
}

Rooは問題が多いのであきらめましょう!

examプロジェクトに戻ります

JUnitを使ったテスト

以下のサイトを参考に。
http://d.hatena.ne.jp/nakaearth/20131204

src/test/java に新規で jp.abc パッケージを作成。
[新規]-[JUnitテストケース]でテストクラスを作成。

package jp.abc;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class HomeControllerTest {

	@Autowired
	private WebApplicationContext wac;

	private MockMvc mockMvc;

	@Before
	public void setUp() throws Exception {
		mockMvc = webAppContextSetup(wac).build();
	}

	@Test
	public void testHome() {
		fail("まだ実装されていません");
	}

	@Test
	public void testQuery() {
		fail("まだ実装されていません");
	}

}

9月19日

STSでRooプロジェクトを作る

Maven install を実行する

[実行]-[maven]-[install]でエラーが出る人は、サーバーに置いた pom.xml をコピーして試してみること。

MyRooApp-0

Maven install に成功したときは、コンソールに以下のように表示される。

MyRooApp-3

wev mbc setup を実行する

Roo Shell でコマンド「web mvc setup」を実行する。

TomcatにMyRooAppを追加する

「サーバー」タブでTomcatにMyRooAppを追加し、Tomcatを開始する。
http://localhost:8080/MyRooAppにアクセスすると、Rooの画面が表示される。

MyRooApp-4

データベースのセットアップ

Roo Shell で以下のコマンドを実行する。

> persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT 

MyRooApp-5

コマンドプロンプトでmavenを使えるようにする

maven をコピーする。
サーバー上のファイル apache-maven-3.2.3-bin.zip をローカルにコピーする。

mvn-1

se3 の下に java フォルダを作成し、そこにZIPファイルを解凍する。

スタートメニューのコンピュータを右クリックし、プロパティを選択する。
システムの詳細設定をクリックする。
環境変数をクリックする。

JAVA_HOMEとPATHの2つの環境変数を追加する。

mvn-2

スタートメニューからコマンドプロンプトを開き、cd コマンドでMyRooAppのフォルダに移動し、mvn install コマンドを実行する。

mvn-3

BUILD FAILED でエラーになった場合は、-Xオプションを指定してmavenを実行する。

> mvn -X install

エラーになった原因が表示されるので、その原因を解決する。

9月18日

Spring Rooを使ってみる

Spring Rooのインストール

以下のサイトを参考に、Spring Roo をインストールする。
Spring Roo チュートリアルをやってみた 1

Springダッシュボードの右下にある 「IDE EXTENSIONS」 をクリック。
「Spring Roo」をチェックしてインストールを実行。

Roo のインストールが終わったら Eclipse を再起動する。

プロジェクトを作成する

メニューから[ファイル]-[新規]-[プロジェクト]を選択する。
Spring内にある「Spring Roo Project」を選択し、「次へ」をクリック。
以下の内容を入力して「次へ」をクリック。
 プロジェクト名: MyRooApp
 Top level package name: jp.abc
「完了」をクリックしてプロジェクトを作成する。

「Roo shell」が表示されるまでしばらく待機する。

プロジェクトが生成さけれど、pom.xml で「プラグイン実行がライフサイクル構成でカバーされていません」のエラーが出ている。
これを解消するには、Eclipseマーケットプレイスで「m2e-apt」をインストールする。

それでもエラーが消えないので、pom.xml のエラー上にマウスカーソルを移動してクイックフィックスを表示し、「新規m2eコネクタの…」をクリックする。

AspectJ のコネクタが表示されるので、それをインストールする。

Eclipseを再起動し、ビルドが実行されると、エラーが消える。

再起動後にビルドが終わらない等が発生した場合は、Eclipseを再起動してみる。

エラーが消えてビルドが完了していれば、MyRooAppプロジェクトを右クリックし、[実行]-[Maven install]を選択する。
コンソールに「BUILD SUCCESS」が表示されることを確認する。

Roo Shell で「web mvc setup」コマンドを入力しENTERで実行する。

9月12日

エンティティの連携

SpringMVCでは関連付けされたエンティティの永続化も簡単にできる。

Profileクラスの追加

Resultは試験番号と得点だけを保持するので、受験者の名前を保持する Profile クラスを追加して関連付け、受験者の名前も永続化できるようにする。

Profile.java

package jp.abc;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.NotEmpty;

@Entity
@Table(name = "profile")
public class Profile {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column
	@NotNull
	private long id;

	@Column
	@NotEmpty
	private String name;

	@OneToOne
	private Result result;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Result getResult() {
		return result;
	}

	public void setResult(Result result) {
		this.result = result;
	}
}

ResultクラスにもProfileへの参照を追加する。

Result.java

package jp.abc;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;

@Entity
@Table(name = "result")
public class Result {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column
	private long id;

	@Length(min=3, max=10)
	@Column(nullable = false, unique = true)
	private String number;

	@Range(min=0, max=100)
	@Column(nullable = false)
	private int score;

	@OneToOne
	private Profile profile;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getNumber() {
		return number;
	}

	public void setNumber(String number) {
		this.number = number;
	}

	public int getScore() {
		return score;
	}

	public void setScore(int score) {
		this.score = score;
	}

	public Profile getProfile() {
		return profile;
	}

	public void setProfile(Profile profile) {
		this.profile = profile;
	}
}

インタフェースProfileDaoを作成する。

ProfileDao.java

package jp.abc;

import java.io.Serializable;
import java.util.List;

public interface ProfileDao<T> extends Serializable {
	public List<T> getAll();
	public T findById(long id);
	public void add(T data);
	public void update(T data);
	public void delete(T data);
	public void delete(long id);
}

ProfileDaoの実装クラスProfileDaoImpleを作成する。

ProfileDaoImple.java

package jp.abc;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class ProfileDaoImpl implements ProfileDao<Profile> {
	private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit");

	public List<Profile> getAll() {
		EntityManager manager = factory.createEntityManager();
		List<Profile> list = null;
		Query query = manager.createQuery("from Profile");
		list = query.getResultList();
		manager.close();
		return list;
	}

	public Profile findById(long id) {
		EntityManager manager = factory.createEntityManager();
		return (Profile)manager.createQuery("from Profile where id = " + id).getSingleResult();
	}

	public void add(Profile data) {
		EntityManager manager = factory.createEntityManager();
		EntityTransaction tx = manager.getTransaction();
		tx.begin();
		manager.persist(data);
		tx.commit();
		manager.close();
	}

	public void update(Profile data) {
		EntityManager manager = factory.createEntityManager();
		EntityTransaction tx = manager.getTransaction();
		tx.begin();
		manager.merge(data);
		tx.commit();
		manager.close();
	}

	public void delete(Profile data) {
		EntityManager manager = factory.createEntityManager();
		EntityTransaction tx = manager.getTransaction();
		tx.begin();
		Profile p = manager.merge(data);
		manager.remove(p);
		tx.commit();
		manager.close();
	}

	public void delete(long id) {
		delete(findById(id));
	}

}

ビューテンプレートを作成する。

profile.jsp

<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${title}</title>
</head>
<body>

<h1>${title}</h1>
<p>${message}</p>

<table>
  <form:form modelAttribute="profile">
    <tr>
      <td></td>
      <td><form:errors path="*" /></td>
    </tr>
    <tr>
      <td><form:label path="name">名前</form:label></td>
      <td><form:input path="name" size="20" /></td>
    </tr>
    <tr>
      <td><form:label path="result">受験番号</form:label></td>
      <td><form:input path="result" size="20" /></td>
    </tr>
    <tr>
      <td></td>
      <td><input type="submit"></td>
    </tr>
  </form:form>
</table>

<hr>
<c:if test="${list != null}">
<table>
  <tr><th>ID</th><th>受験番号</th><th>名前</th></tr>
  <c:forEach var="obj" items="${list}">
    <tr>
      <td>${obj.id}</td>
      <td>${obj.result.number}</td>
      <td>${obj.name}</td>
    </tr>
  </c:forEach>
</table>
</c:if>

</body>
</html>

コントローラを作成する。

ProfileController.java

package jp.abc;

import java.util.List;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ProfileController {

	@RequestMapping(value = "/profile", method = RequestMethod.GET)
	public String profile(Model model) {
		model.addAttribute("title", "Sample");
		model.addAttribute("message", "Profileのサンプルです");
		Profile p = new Profile();
		model.addAttribute("profile", p);
		ProfileDao<Profile> dao = new ProfileDaoImpl();
		List<Profile> list = dao.getAll();
		model.addAttribute("list", list);
		return "profile";
	}

	@RequestMapping(value = "/profile", method = RequestMethod.POST)
	public String profileForm(@Valid @ModelAttribute Profile p, Errors result, Model model) {
		if (result.hasErrors()) {
			model.addAttribute("title", "Sample ERROR");
			model.addAttribute("message", "値をチェックしてください");
		} else {
			model.addAttribute("title", "Sample");
			model.addAttribute("message", "Profileのサンプルです");
			ProfileDaoImpl dao = new ProfileDaoImpl();
			dao.add(p);
			List<Profile> list = dao.getAll();
			model.addAttribute("list", list);
		}
		return "/profile";
	}
}

プロパティエディタを作成する。

ResultPropertyEditor.java

package jp.abc;

import java.beans.PropertyEditorSupport;

public class ResultPropertyEditor extends PropertyEditorSupport {
	public String getAsText() {
		Result r = (Result)getValue();
		if (r == null) {
			return "";
		} else {
			return r.getNumber();
		}
	}

	public void setAsText(String value) {
		ResultDao dao = new ResultDaoImpl();
		Result r = dao.find(value);
		setValue(r);
	}
}

コントローラにInitBinderを追加する。

ProfileController.java

	@InitBinder
	protected void initBinder(HttpServletRequest request,
			ServletRequestDataBinder binder) throws Exception {
		binder.registerCustomEditor(Result.class, new ResultPropertyEditor());
	}

exam-6

受験番号と名前に続いて得点も表示するようにビューテンプレートを変更する。

profile.jsp

<table>
  <tr><th>ID</th><th>受験番号</th><th>名前</th><th>得点</th></tr>
  <c:forEach var="obj" items="${list}">
    <tr>
      <td>${obj.id}</td>
      <td>${obj.result.number}</td>
      <td>${obj.name}</td>
      <td>${obj.result.score}</td>
    </tr>
  </c:forEach>
</table>

exam-7

9月11日

サービスとリポジトリ

DIでサービスを使えるようにするとさらに簡単になる。

application-config.xml

	<bean
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
		id="entityManagerFactory">
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="showSql" value="true" />
			</bean>
		</property>
		<property name="persistenceUnitName" value="persistenceUnit" />
		<property name="dataSource" ref="dataSource" />
		<property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml" />
	</bean>
	<bean
		class="org.springframework.orm.jpa.JpaTransactionManager"
		id="transactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<tx:annotation-driven/>
	<bean
		class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
	<jpa:repositories base-package="jp.abc.exam.repositories" />

Serviceを作る

テキストではMyDataServiceになっているが、これはMyDataを扱っているアプリだから。
成績検索アプリではResultを扱うのでResultServiceを作成する。

ResultService.java

package jp.abc;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ResultService {
	@PersistenceContext
	private EntityManager manager;

	@Transactional
	public List<Result> getAll() {
		return (List<Result>)manager.createQuery("from Result").getResultList();
	}
}

コントローラでサービスを使う。
テキストでは既存のコントローラを書き換えているが、ここでは新しいコントローラを作成する。

ListController.java

package jp.abc;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;

@Controller
public class ListController {
	@Autowired
	private ResultService service;
	
	@RequestMapping(value = "/list", method = RequestMethod.GET)
	public String list(Model model) {
		model.addAttribute("title", "Sample");
		model.addAttribute("message", "Selectのサンプルです");
		List<Result> list = service.getAll();
		model.addAttribute("list", list);
		return "list";
	}
}

JSPを用意する。

list.jsp

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${title}</title>
</head>
<body>

<h1>${title}</h1>
<p>${message}</p>

<table>
  <tr><th>ID</th><th>受験番号</th><th>得点</th></tr>
  <c:forEach var="r" items="${list}">
    <tr>
      <td>${r.id}</td>
      <td>${r.number}</td>
      <td>${r.score}</td>
    </tr>
  </c:forEach>
</table>

</body>
</html>

9月5日

成績検索アプリを完成させる

  1. SpringMVCプロジェクトを作成
  2. 成績を検索する画面を作成
  3. HomeControllerを作成
  4. 成績の検索結果を表示する画面を作成
  5. HomeControllerに検索を実行するメソッドを追加
  6. 成績を登録する画面を作成
  7. EditControllerを作成
  8. 成績を登録するリクエストを受け取れるようにする
  9. Resultエンティティを作成
  10. Resultを使うようにedit.jspを修正
  11. DAOを使用してデータベースに保存する
  12. 重複した受験番号を登録できないようにする

検索でエラーを表示する

JSTL の c:if タグを使用して、error != null のときはエラーメッセージを表示し、そうでないときは検索結果を表示する。

result.jsp

<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${title}</title>
</head>
<body>

<h1>${title}</h1>

<c:if test="${error != null}">
<p style="color:#ff0000;"><b>${error}</b></p>
</c:if>
<c:if test="${error == null}">
<p>
受験番号 ${number} の得点は ${score} です。
</p>
</c:if>

<a href="/exam/home">戻る</a>
</body>
</html>

コントローラでは、結果が見つからないときはエラーメッセージを設定する。

HomeController.java

	@RequestMapping(value = "/home", method = RequestMethod.POST)
	public String query(@RequestParam("number") String number, Model model) {
		model.addAttribute("title", "成績の検索結果");
		model.addAttribute("number", number);
		ResultDao dao = new ResultDaoImpl();
		Result r = dao.find(number);
		if (r == null) {
			model.addAttribute("error", "存在しない受験番号です");
			model.addAttribute("score", "");
		} else {
			model.addAttribute("score", r.getScore());
		}
		return "result";
	}

重複した受験番号のチェック

Resultエンティティの number の @Column アノテーションで unique = true を指定すると、一意性を保証することになっているが、バリデーションではチェックされないのであまり意味がない。

重複チェックをするには、保存する前に同一の値が存在するかどうかを確認するのがよい。

参考: http://kenro.biz/blog/?p=172

EditController.java

	@RequestMapping(value = "/edit", method = RequestMethod.POST)
	public String add(@Valid @ModelAttribute Result result,
			BindingResult errors, Model model) {
		ResultDao dao = new ResultDaoImpl();
		if (errors.hasErrors()) {
			model.addAttribute("title", "成績の入力");
		} else {
			model.addAttribute("title", "成績の入力");
			model.addAttribute("result", result);
			try {
				dao.add(result);
			} catch (PersistenceException e) {
				model.addAttribute("message", "受験番号が重複しています");
			}
		}
		List<Result> list = dao.getAll();
		model.addAttribute("list", list);
		return "edit";
	}

ResultDaoImpl.java

    public void add(Result result) {
    	EntityManager manager = factory.createEntityManager();
    	EntityTransaction transaction = manager.getTransaction();
    	transaction.begin();
    	Result r = find(result.getNumber());
    	if (r == null) {
    		try {
    			manager.persist(result);
    			transaction.commit();
    		} catch (PersistenceException e) {
    			transaction.rollback();
    		}
        	manager.close();
    	} else {
    		throw new PersistenceException();
    	}
    }

9月4日

試験成績検索アプリを作成する

前期で学習した、SpringMVCを使ってCRUD機能を実装するWebアプリの復習のため、簡単なWebアプリを作成する。

先週の内容の復習

  1. SpringMVCプロジェクトを作成
  2. 成績を検索する画面を作成
  3. HomeControllerを作成
  4. 成績の検索結果を表示する画面を作成
  5. HomeControllerに検索を実行するメソッドを追加
  6. 成績を登録する画面を作成
  7. EditControllerを作成
  8. 成績を登録するリクエストを受け取れるようにする
  9. Resultエンティティを作成
  10. Resultを使うようにedit.jspを修正
  11. DAOを使用してデータベースに保存する

HSQLDBと接続する

Eclipseでプラグインの設定を変更する。
データベース名を変更してHSQLDBサーバーを再起動する。

exam-5

検索の実装

受験番号を元に成績を検索する実装を追加する。

DAOに検索のためのメソッドを追加する。

ResultDao.java

package jp.abc;

import java.io.Serializable;
import java.util.List;

public interface ResultDao extends Serializable {
    public List<Result> getAll();
    public void add(Result result);
    public Result find(String number);
}

ResultDaoImplに検索の実装を追加する。

ResultDaoImpl.java

	public Result find(String number) {
        EntityManager manager = factory.createEntityManager();
		CriteriaBuilder builder = manager.getCriteriaBuilder();
		CriteriaQuery<Result> query = builder.createQuery(Result.class);
		Root<Result> root = query.from(Result.class);
		query.select(root).where(builder.equal(root.get("number"), number));
        return manager.createQuery(query).getSingleResult();
	}

HomeController でDAOを利用する。

HomeController.java

	@RequestMapping(value = "/home", method = RequestMethod.POST)
	public String query(@RequestParam("number") String number, Model model) {
		model.addAttribute("title", "成績の検索結果");
		model.addAttribute("number", number);
		ResultDao dao = new ResultDaoImpl();
		Result r = dao.find(number);
		model.addAttribute("score", r.getScore());
		return "result";
	}

存在しない受験番号だったらエラー表示する。

DAOは、クエリで見つからないときはnullを返すようにする。

ResultDaoImple.java

	public Result find(String number) {
        EntityManager manager = factory.createEntityManager();
		CriteriaBuilder builder = manager.getCriteriaBuilder();
		CriteriaQuery<Result> query = builder.createQuery(Result.class);
		Root<Result> root = query.from(Result.class);
		query.select(root).where(builder.equal(root.get("number"), number));
		Result r = null;
		try {
			r = manager.createQuery(query).getSingleResult();
		} catch (NoResultException e) {
			//
		}
		return r;
	}

HomeControllerは結果がnullならメッセージを送信する。

HomeController.java

	@RequestMapping(value = "/home", method = RequestMethod.POST)
	public String query(@RequestParam("number") String number, Model model) {
		model.addAttribute("title", "成績の検索結果");
		model.addAttribute("number", number);
		ResultDao dao = new ResultDaoImpl();
		Result r = dao.find(number);
		if (r == null) {
			model.addAttribute("message", "存在しない受験番号です");
			model.addAttribute("score", "");
		} else {
			model.addAttribute("score", r.getScore());
		}
		return "result";
	}

8月29日

エンティティを作成する

ひとりひとりの得点に対応するエンティティを作成する。試験成績なので Result とする。
プロジェクトエクスプローラでJavaリソース以下の src/main/java にある、jp.abc パッケージを右クリック。[新規]-[クラス]を選択。
名前に Result を入力して完了。

Result.java

package jp.abc;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "result")
public class Result {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column
	private long id;

	@Column(nullable = false)
	private String number;

	@Column(nullable = false)
	private int score;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getNumber() {
		return number;
	}

	public void setNumber(String number) {
		this.number = number;
	}

	public int getScore() {
		return score;
	}

	public void setScore(int score) {
		this.score = score;
	}
}

edit.jspの変更

エンティティを作ったので、edit.jsp もエンティティ経由でデータをやりとりするように変更する。

edit.jsp

<!DOCTYPE html>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${title}</title>
</head>
<body>

<h1>${title}</h1>

<p>受験番号と得点を入力してください</p>
<form:form modelAttribute="result">
  <form:label path="number">受験番号</form:label>
  <form:input path="number" />
  <form:label path="score">得点</form:label>
  <form:input path="score" />
  <br/>
  <input type="submit" value="登録" />
</form:form>

</body>
</html>

コントローラからJSPにデータを渡す

edit.jspでエンティティを参照するようにしたので、JSPにエンティティを渡すように修正する。

EditController.java

package jp.abc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class EditController {

	@RequestMapping(value = "/edit", method = RequestMethod.GET)
	public String home(Model model) {
		model.addAttribute("title", "成績の入力");
		Result r = new Result();
		model.addAttribute("result", r);
		return "edit";
	}

}

動作確認

エンティティを使うように変更したので、動作するか確認する。

入力チェックを追加する

まずは登録ボタンを押したときにエラーにならないようにする。
POSTメソッドのリクエストを受け取るようにする。

EditController.java

package jp.abc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class EditController {

	@RequestMapping(value = "/edit", method = RequestMethod.GET)
	public String home(Model model) {
		model.addAttribute("title", "成績の入力");
		Result r = new Result();
		model.addAttribute("result", r);
		return "edit";
	}

	@RequestMapping(value = "/edit", method = RequestMethod.POST)
	public String add(Model model) {
		model.addAttribute("title", "成績の入力");
		Result r = new Result();
		model.addAttribute("result", r);
		return "edit";
	}
}

入力チェックの追加

入力チェックは、テキストp.230~240を参考にする。

バリデーション用ライブラリの追加

pom.xmlに以下のコードを追加する。

pom.xml

		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.1.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>5.0.1.Final</version>
		</dependency>

エンティティにバリデーションのアノテーションを追加する

Result.java

	@NotEmpty
	@Length(min=3, max=10)
	@Column(nullable = false)
	private String number;

	@Range(min=0, max=100)
	@Column(nullable = false)
	private int score;

コントローラにバリデーションのコードを追加する

	@RequestMapping(value = "/edit", method = RequestMethod.POST)
	public String add(@Valid @ModelAttribute Result result,
			BindingResult errors, Model model) {
		model.addAttribute("title", "成績の入力");
		Result r = new Result();
		model.addAttribute("result", r);
		return "edit";
	}

JSPにエラー表示用のコードを追加する

エラー表示用のタグ <form:errors /> を追加する。

edit.jsp

<p>受験番号と得点を入力してください</p>
<form:form modelAttribute="result">
  <form:errors path="*" element="div"/><br/>
  <form:label path="number">受験番号</form:label>

HSQLDBの用意

exam プロジェクトにデータベースアクセスのためのライブラリを追加する。

pom.xml の最後の </dependencies>の直前に以下の内容を追加する。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.1</version>
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.2.9</version>
</dependency>
<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>jta</artifactId>
    <version>1.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>3.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.0.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>3.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring-framework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.3.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.7.4</version>
</dependency>
 
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
        <exclusion>
            <groupId>xml-apis</groupId>
            <artifactId>xml-apis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

database.propertiesの作成

以下の spring フォルダを右クリックし、[新規]-[ファイル]を選択
exam
+ src
  + main
  + resources
   + spring

ファイル名に database.properties を入力して完了。
テキストp.270 のリスト5-4 の内容を入力する。

database.driverClassName=org.hsqldb.jdbc.JDBCDriver
database.url=jdbc:hsqldb:hsql://localhost/exam
database.username=sa
database.password=

persistence.xmlの作成

以下の resources フォルダを右クリックし、[新規]-[フォルダー]を選択
exam
+ src
  + main
  + resources

フォルダ名に META-INF を入力して完了。
作成した META-INF フォルダを右クリックし、[新規]-[ファイル]を選択
ファイル名に persistence.xml を入力して完了。
作成した persistence.xml に以下の内容を記述する。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  version="2.0"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
      <property name="hibernate.hbm2ddl.auto" value="update" />
      <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbc.JDBCDriver" />
      <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:hsql://localhost/exam" />
    </properties>
  </persistence-unit>
</persistence>

application-config.xmlの変更

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
		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/jee
		http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
		http://www.springframework.org/schema/data/jpa
		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

	<context:property-placeholder location="classpath:spring/database.properties"/>

	<bean class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close" id="dataSource">
		<property name="driverClassName" value="$database.driverClassName}" />
		<property name="url" value="${database.url}" />
		<property name="username" value="${database.username}" />
		<property name="password" value="${database.password}" />
		<property name="testOnBorrow" value="true" />
		<property name="testOnReturn" value="true" />
		<property name="testWhileIdle" value="true" />
		<property name="timeBetweenEvictionRunsMillis" value="1800000" />
		<property name="numTestsPerEvictionRun" value="3" />
		<property name="minEvictableIdleTimeMillis" value="1800000" />
	</bean>
</beans>

DAOのインタフェースと実装

DAOインタフェースの用意

プロジェクトエクスプローラでJavaリソース以下の src/main/java にある、jp.abc パッケージを右クリック。[新規]-[インタフェース]を選択。
名前に ResultDao を入力して完了。

ResultDao.java

package jp.abc;
 
import java.io.Serializable;
import java.util.List;
 
public interface ResultDao extends Serializable {
    public List<Result> getAll();
    public void add(Result result);
}

DAOの実装を追加する

DAOの実装を追加する。

ResultDaoImpl.java

package jp.abc;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
 
public class ResultDaoImpl implements ResultDao {
    private static EntityManagerFactory factory =
        Persistence.createEntityManagerFactory("persistenceUnit");
 
    public List<Result> getAll() {
        EntityManager manager = factory.createEntityManager();
        Query query = manager.createQuery("from Result");
        List<Result> list = query.getResultList();
        manager.close();
        return list;
    }
 
    public void add(Result result) {
        EntityManager manager = factory.createEntityManager();
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        manager.persist(result);
        transaction.commit();
        manager.close();
    }
}

コントローラからJSPに登録済みのリストをわたす

EditController.java

	@RequestMapping(value = "/edit", method = RequestMethod.POST)
	public String add(@Valid @ModelAttribute Result result,
			BindingResult errors, Model model) {
		ResultDao dao = new ResultDaoImpl();
		if (errors.hasErrors()) {
			model.addAttribute("title", "成績の入力");
		} else {
			model.addAttribute("title", "成績の入力");
			model.addAttribute("result", result);
			dao.add(result);
		}
		List<Result> list = dao.getAll();
		model.addAttribute("list", list);
		return "edit";
	}

ControllerとJSP

データ登録画面に登録済みのリストを表示する

edit.jsp

<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${title}</title>
</head>
<body>

<h1>${title}</h1>

<p>受験番号と得点を入力してください</p>
<form:form modelAttribute="result">
  <form:errors path="*" element="div" cssStyle="color:red" /><br/>
  <form:label path="number">受験番号</form:label>
  <form:input path="number" />
  <form:label path="score">得点</form:label>
  <form:input path="score" />
  <br/>
  <input type="submit" value="登録" />
</form:form>
<hr/>
<c:if test="${list != null}">
  <table>
  <tr><th>ID</th><th>受験番号</th><th>得点</th></tr>
  <c:forEach var="r" items="${list}">
    <tr>
      <td>${r.id}</td>
      <td>${r.number}</td>
      <td>${r.score}</td>
    </tr>
  </c:forEach>
  </table>
</c:if>

</body>
</html>