7月3日

試験範囲について

  • Eclipseを使った開発
  • ストリームを利用したファイル入出力とURLからのデータ取得
  • FizzBuzz問題を例題としてTDDによる開発手法の学習
  • TDDBC大阪の例題の自動販売機を実装する
  • JavaによるWebアプリケーションの基礎(Servlet/JSP/JSTL)
  • JSPによる入力フォームとServletでのデータの受け取り

サービスとリポジトリは便利ですが、使わなくてもWebアプリケーションを作れるので、今回はパスします。
興味がある人は自分でやってみてください。

エンティティの連携

MsgDataエンティティを作成する。

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.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.NotEmpty;

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

	@Column
	private String title;

	@Column(nullable = false)
	@NotEmpty
	private String message;

	@ManyToOne
	private MyData mydata;

	public MsgData() {
		super();
		mydata = new MyData();
	}

	public long getId() {
		return id;
	}

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

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public MyData getMydata() {
		return mydata;
	}

	public void setMydata(MyData mydata) {
		this.mydata = mydata;
	}
}

MyDataを修正する
MyDataに、MsgDataへの参照を追加する。
インスタンス変数を追加したら、Eclipseの[ソース]-[getterおよびsetterの生成]でアクセサメソッドを自動生成する。

package jp.abc;

import java.util.List;

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

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

	@Column(length = 50, nullable = false)
	private String name;

	@Column(length = 200, nullable = true)
	private String mail;

	@Column(nullable = true)
	private Integer age;

	@Column(nullable = true)
	private String memo;

	@OneToMany(cascade = CascadeType.ALL)
	@Column(nullable = true)
	private List<MsgData> msgdatas;

	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 String getMail() {
		return mail;
	}

	public void setMail(String mail) {
		this.mail = mail;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getMemo() {
		return memo;
	}

	public void setMemo(String memo) {
		this.memo = memo;
	}

	public List<MsgData> getMsgdatas() {
		return msgdatas;
	}

	public void setMsgdatas(List<MsgData> msgdatas) {
		this.msgdatas = msgdatas;
	}

}

MsgDataDaoの用意

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

package jp.abc;

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

public interface MsgDataDao<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);
}

MsgDataDaoImplクラスを作成する。
テキストの通りに各メソッドに @Override アノテーションをつけるとコンパイルエラーになる。
これは、プロジェクトの設定で Javaコンパイラのバージョンが1.5になっているため。
1.6に変更すれば @Override アノテーションのコンパイルエラーは消える。
同時に、プロジェクト・ファセットの設定でバージョンを合わせる必要がある。

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 MsgDataDaoImpl implements MsgDataDao<MsgData> {
	private static EntityManagerFactory factory =
			Persistence.createEntityManagerFactory("persistenceUnit");

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

	@Override
	public MsgData findById(long id) {
		EntityManager manager = factory.createEntityManager();
		Query q = manager.createQuery("from MsgData where id = " + id);
		return (MsgData)q.getSingleResult();
	}

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

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

	@Override
	public void delete(MsgData data) {
		EntityManager manager = factory.createEntityManager();
		EntityTransaction tx = manager.getTransaction();
		tx.begin();
		MsgData entity = manager.merge(data);
		manager.remove(entity);
		tx.commit();
		manager.close();
	}

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

}

ビューテンプレートの用意

[新規]-[JSPファイル]でJSPファイルを作成する。
ファイル名はテキストの通り「showMsgData.jsp」とする。

<!DOCTYPE html>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ 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>
<style>
h1 { font-size: 16pt; background-color: #ccccff; padding: 3px;}
p { color: #000066; }
</style>
</head>
<body>
  <h1>${title}</h1>
  <p>${message}</p>
  <table>
  <form:form modelAttribute="msgdata">
    <tr>
      <td></td>
      <td><form:errors path="*" /></td>
    </tr>
    <tr>
      <td><form:label path="title">タイトル</form:label></td>
      <td><form:input path="title" size="20" /></td>
    </tr>
    <tr>
      <td><form:label path="message">メッセージ</form:label></td>
      <td><form:textarea path="message" cols="20" rows="5" /></td>
    </tr>
    <tr>
      <td><form:label path="mydata">MYDATA_ID</form:label></td>
      <td><form:input path="mydata" size="20" /></td>
    </tr>
    <tr>
      <td></td>
      <td><input type="submit" /></td>
    </tr>
  </form:form>
  </table>
  <hr />
  <c:if test="${datalist != null}">
  <table>
    <tr>
      <th>ID</th><th>投稿者</th><th>タイトル</th><th>メッセージ</th>
    </tr>
    <c:forEach var="obj" items="${datalist}" varStatus="status">
      <tr>
        <td><c:out value="${obj.id}" /></td>
        <td><c:out value="${obj.mydata.name}" /></td>
        <td><c:out value="${obj.title}" /></td>
        <td><c:out value="${obj.message}" /></td>
      </tr>
    </c:forEach>
  </table>
  </c:if>
</body>
</html>

コントローラに新しいURL用のメソッドを追加する

	@RequestMapping(value = "/msg", method = RequestMethod.GET)
	public String msg(Model model) {
		model.addAttribute("title", "Sample");
		model.addAttribute("message", "MsgDataのサンプルです。");
		MsgData msgdata = new MsgData();
		model.addAttribute("msgdata", msgdata);
		MsgDataDao<MsgData> dao = new MsgDataDaoImpl();
		List<MsgData> list = dao.getAll();
		model.addAttribute("datalist", list);
		return "showMsgData";
	}

	@RequestMapping(value = "/msg", method = RequestMethod.POST)
	public String msgform(@Valid @ModelAttribute MsgData msgdata,
			Errors result, Model model) {
		System.out.println("msgform: " + msgdata.getMydata());
		if (result.hasErrors()) {
			model.addAttribute("title", "Sample [ERROR]");
			model.addAttribute("message", "値を再チェックしてください。");
			return "showMsgData";
		}
		MsgDataDao<MsgData> dao = new MsgDataDaoImpl();
		dao.add(msgdata);
		return "redirect:/msg";
	}

POSTするとエラーが発生する。
フォームではMYDATA_IDは単なる文字としての値なので、これをMyDataのインスタンスに変換するための処理が必要になる。
そのために、PropertyEditor を継承するクラス MyDataPropertyEditor を用意する。

package jp.abc;

import java.beans.PropertyEditorSupport;

public class MyDataPropertyEditor extends PropertyEditorSupport {
	public String getAsText() {
		MyData value = (MyData)getValue();
		System.out.println("getAsText: " + value);
		if (value == null) {
			return "";
		} else {
			return "" + value.getId();
		}
	}

	public void setAsText(String value) {
		MyDataDao<MyData> dao = new MyDataDaoImpl();
		MyData mydata = (MyData)dao.findById(Long.parseLong(value));
		System.out.println("setAsText: " + mydata);
		setValue(mydata);
	}
}

作成した MyDataPropertyEditor を使用するためのコードをコントローラに追加する。

@Controller
public class MyDataConroller {

	@InitBinder
	protected void initBinder(HttpServletRequest req,
			ServletRequestDataBinder binder) throws Exception {
		binder.registerCustomEditor(MyData.class, new MyDataPropertyEditor());
	}

5月1日

ファイルを読み書きする
Eclipseで開発する場合、プロジェクトの直下が作業フォルダになる。
テキストでは絶対パスを記述しているが、ここでは相対パスでファイルを指定する。

package c14;

import java.io.FileReader;

public class Main16_1 {

	public static void main(String[] args) throws Exception {
		String filename = "test.txt";
		FileReader fr = new FileReader(filename);
		char c1 = (char)fr.read();
		char c2 = (char)fr.read();
		fr.close();
		System.out.println("c1=" + c1 + ", c2=" + c2);
	}

}

例外の処理をきちんとコードとして記述すると以下のようになる。

package c16;

import java.io.FileReader;
import java.io.IOException;

public class Main16_1 {

	public static void main(String[] args) {
		String filename = "src\\c16\\Account.java";
		FileReader fr = null;
		try {
			fr = new FileReader(filename);
			char c1 = (char)fr.read();
			char c2 = (char)fr.read();
			System.out.println("c1=" + c1 + ", c2=" + c2);
		} catch (Exception e) {
			e.printStackTrace();
			try {
				if (fr != null) fr.close();
			} catch (IOException e1) {}
		}
	}

}

BufferedReader を使って1行ずつ読み込み、コンソールに出力するように書き換える。

package c16;

import java.io.BufferedReader;
import java.io.FileReader;

public class Main16_1 {

	public static void main(String[] args) {
		String filename = "src\\c16\\Account.java";
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(filename));
			while (br.ready()) {
				String s = br.readLine();
				System.out.println(s);
			}
		} catch (Exception e) {
			e.printStackTrace();
			try {
				if (br != null) br.close();
			} catch (Exception e1) {}
		}
	}

}

ファイルに文字を書き込んでみる。
テキストp.604のリスト16-2を、try-with-resources文で書き換えたものが以下のコード。

package c16;

import java.io.FileWriter;

public class Main16_2 {

	public static void main(String[] args) {
		String filename = "test.txt";
		try (FileWriter fw = new FileWriter(filename)) {
			fw.write('そ');
			fw.write('れ');
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

実行すると、c16プロジェクトの直下に test.txt が作られる。
表示されていない場合は、c16プロジェクトを右クリックして、「リフレッシュ」を選択すると、パッケージエクスプローラの表示が更新される。

ファイルを読み込んで別のファイルにコピーするプログラムを作成する。

package c16;

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Copy {

	public static void main(String[] args) {
		String src = "src\\c16\\Account.java";
		String dst = "out.txt";
		try (FileInputStream in = new FileInputStream(src);
			 FileOutputStream out = new FileOutputStream(dst)) {
			while (true) {
				int c = in.read();
				if (c < 0) break;
				out.write(c);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

プログラムを実行すると、c16プロジェクトの直下に out.txt ファイルが生成される。
ファイルの内容は Account.java がコピーされている。

Webページを取得する
テキストp.605のリスト16-3を作ってみる。
アクセス先のURLは自由に指定してよい。

package c16;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

public class Main16_3 {

	public static void main(String[] args) {
		try {
			URL u = new URL("http://www.yahoo.co.jp");
			try (InputStream is = u.openStream()) {
				while (true) {
					int i = is.read();
					if (i < 0) break;
					char c  = (char)i;
					System.out.print(c);
				}
				System.out.println();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

}

実行すると、URLで指定したWebサイトのページの内容がコンソールに出力される。

FizzBuzz問題
FizzBuzz問題をTDDで実装する。

まず最初にFizzBuzzクラスを作成する。
指定した数値に対して、答えを返す say() メソッドを定義する。
とりあえず、nullを返しておく。

package tdd;

public class FizzBuzz {
	public String say(int i) {
		return null;
	}
}

[新規]-[その他]で「JUnitテスト・ケース」を選択して、テストクラスを生成する。

テスト対象メソッドは say() を選択して完了するとテストクラスが生成される。

package tdd;

import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {

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

テストを実装する。
1のときは文字列”1″が返されることを確認するテスト。

package tdd;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {

	@Test
	public void testSay1() {
		FizzBuzz fz = new FizzBuzz();
		String s = fz.say(1);
		assertThat(s, is("1"));
	}
}

テストにパスするようにFizzBuzzクラスを修正する。

package tdd;

public class FizzBuzz {
	public String say(int i) {
		return "1";
	}
}

2のテストを追加する。

package tdd;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {

	@Test
	public void testSay1() {
		FizzBuzz fz = new FizzBuzz();
		String s = fz.say(1);
		assertThat(s, is("1"));
	}

	@Test
	public void testSay2() {
		FizzBuzz fz = new FizzBuzz();
		String s = fz.say(2);
		assertThat(s, is("2"));
	}
}

テストにパスするようにFizzBuzzを修正する。

package tdd;

public class FizzBuzz {
	public String say(int i) {
		return String.valueOf(i);
	}
}

3のテストを追加する。

package tdd;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {

	@Test
	public void testSay1() {
		FizzBuzz fz = new FizzBuzz();
		String s = fz.say(1);
		assertThat(s, is("1"));
	}

	@Test
	public void testSay2() {
		FizzBuzz fz = new FizzBuzz();
		String s = fz.say(2);
		assertThat(s, is("2"));
	}

	@Test
	public void testSay3() {
		FizzBuzz fz = new FizzBuzz();
		String s = fz.say(3);
		assertThat(s, is("Fizz"));
	}
}

4月28日

Eclipseを使ったJavaアプリケーションの開発に慣れよう!

Calendar クラスなどは、Ctrl+SPACEで補完する。
自動的にimport文まで生成してくれるので便利!

テキストp.537のリスト14-3に、SimpleDateFormatを追加した例。

package c14;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Main14_3 {

	public static void main(String[] args) {
		Date now = new Date();
		Calendar c = Calendar.getInstance();
		c.setTime(now);
		int y = c.get(Calendar.YEAR);
		System.out.println("今年は" + y + "年です");
		c.set(2010, 8, 22, 1, 23, 45);
		SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println(f.format(c.getTime()));
	}

}

テキストp.558の練習問題をやってみよう。

練習問題14-1

package c14;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Main14_1 {

	public static void main(String[] args) {
		Date now = new Date();
		Calendar c = Calendar.getInstance();
		c.setTime(now);
		int day = c.get(Calendar.DAY_OF_MONTH);
		c.set(Calendar.DAY_OF_MONTH, day + 100);
		Date d = c.getTime();
		SimpleDateFormat f = new SimpleDateFormat("yyyy年MM月dd日");
		System.out.println(f.format(d));
	}

}

練習問題14-2用のテストプログラムを作成する。

package c14;

public class AccountTest {

	public static void main(String[] args) {
		Account a = new Account();
		a.setAccountNumber("4649");
		a.setBalance(1592);
		System.out.println(a);

		Account a1 = new Account();
		a1.setAccountNumber("4649");
		Account a2 = new Account();
		a2.setAccountNumber(" 4649");
		System.out.println(a1.equals(a2));
	}

}