* JAX-RSによる簡単なWebサービス作成 [#ve822faf]
#setlinebreak(on)

&color(red){書きかけの記事};

JAX-RS を使用してDBと連携する簡単なWebサービスを作成する手順を記載する。
既存環境をできるだけ流用する想定とし、手順は eclipse+Tomcat プラグイン の開発環境での手順とする。

※環境構築は [[JAX-RSの環境構築]] も参照
※値の受取、返却についての詳細は [[JAX-RSでの入力値の受取と返却]] を参照
&br;

#contents
-- 関連
--- [[JAX-RSの環境構築]]
--- [[JAX-RSでの入力値の受取と返却]]


** 環境構築 [#uf3b86c6]
#html(<div style="padding-left:20px;">)

*** データベースの作成 [#j36ceb92]
#html(<div style="padding-left:20px;">)
以下、MySQLターミナルから行う
#myterm(){{

/* DB作成 */
CREATE DATABASE example_db DEFAULT CHARACTER SET utf8;

/* ユーザ作成 */
CREATE USER example_user@localhost IDENTIFIED BY 'example_pass';

/* 権限付与 */
GRANT ALL ON example_db.* TO example_user@localhost;

/* テーブル作成 */
CREATE TABLE `books` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `isbn` varchar(255) DEFAULT NULL,
  `title` varchar(255) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/* データ作成 */
insert into books(isbn,title,price,created_at,updated_at)
 values('978-4822280536', 'デッドライン', 2376, NOW(), NOW())
          ,('978-4873114798', 'プログラマが知るべき97のこと', 2052, NOW(), NOW())
          ,('978-4873115658', 'リーダブルコード', 2592, NOW(), NOW());
}}
#html(</div>)
*** Java、Tomcat のインストール [#l03384cf]
#html(<div style="padding-left:20px;">)
※7以降をインストール
#html(</div>)

*** eclipse、Tomcatプラグインのインストール [#na08e513]
#html(<div style="padding-left:20px;">)
※eclipse4.2以降だとmavenプラグイン等が最初から入っているので楽。(上記の jackson 等もMavenで取得できるので)
#html(</div>)

*** 関連 jar を取得する [#j2b22f7b]
#html(<div style="padding-left:20px;">)
Gradle で取得する
#mycode(){{
dependencies {
    compile "org.glassfish.jersey.containers:jersey-container-servlet:latest.release"
    compile "org.glassfish.jersey.media:jersey-media-json-jackson:latest.release"
    compile "mysql:mysql-connector-java:latest.release"
} 
}}
※ 詳細は [[Gradleでjarの取得だけを行う]] を参照

手動で取得する場合は以下を参照。(ただし、バージョンの不整合等で動かないかもしれない)
|ライブラリ名|ダウンロード元|補足|h
|Jersey|https://jersey.java.net/download.html||
|jackson|http://wiki.fasterxml.com/JacksonDownload|Annotations、Streaming API、Databind の3つをダウンロード|
|MySQLコネクタ|https://www.mysql.com/products/connector/|JDBC Driver for MySQL (Connector/J) をダウンロード|

#html(</div>)


#html(</div>)


** プロジェクトの作成 [#saf8d91e]
#html(<div style="padding-left:20px;">)

*** 動的Webプロジェクトの作成 [#w5c7e3d1]
プロジェクト名、コンテキストルート とも  "MyService" として作成

*** jar のコピー [#cb8e7bd1]
- ダウンロードした jersey の api、ext、lib の 各 jarファイルを WEB-INF/lib 配下にコピー
- ダウンロードした Jackson、MySQLコネクタの jar を WEB-INF/lib 配下にコピー

*** server.xml の編集 [#h28fd97c]
Context 配下にResource設定を追加
#myhtmlcode(){{
&lt;Context docBase="MyService"
	&lt;Resource name="jdbc/datasource"
			               auth="Container"
			               type="javax.sql.DataSource"
			               driverClassName="com.mysql.jdbc.Driver"
			               url="jdbc:mysql://localhost:3306/example_db"
			               username="example_user"
			               password="example_pass" 
			                /&gt;
&lt;/Context&gt;
}}
※ http://dev.mysql.com/doc/connector-j/en/connector-j-usagenotes-tomcat.html

*** web.xml の編集 [#ocdaa091]
#myhtmlcode(){{
 ・
 ・
  <!-- /ws で始まるURLをWebサービスとする -->
  &lt;servlet&gt;
    &lt;servlet-name>javax.ws.rs.core.Application&lt;/servlet-name&gt;
  &lt;/servlet&gt;
  &lt;servlet-mapping&gt;
    &lt;servlet-name&gt;javax.ws.rs.core.Application&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/ws/*&lt;/url-pattern&gt;
  &lt;/servlet-mapping&gt;

  &lt;!-- server.xmlで定義したDB接続への参照を定義 --&gt;
  &lt;resource-ref&gt;
    &lt;res-ref-name&gt;jdbc/datasource&lt;/res-ref-name&gt;
    &lt;res-type&gt;javax.sql.DataSource&lt;/res-type&gt;
    &lt;res-auth&gt;Container&lt;/res-auth&gt;
  &lt;/resource-ref&gt;
&lt;/web-app&gt;
}}
※name、type、auth は server.xml で定義したものと合わせる。

*** サービスの親クラスの作成 [#n3c8faca]
とりあえず動作確認に必要な実装だけ
#mycode(){{
package example;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class ServiceBase {

	/**
	 * DB接続の取得
	 * @return DB接続
	 */
	public Connection getConnection(){

		Connection con = null;
		try {
			// Webアプリ実行用
			Context context = new InitialContext();
			DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/MySQLDB");
			con = dataSource.getConnection();
		} catch (Exception e) {
			// POJOでの実行用
			try {
				Class.forName("com.mysql.jdbc.Driver");
				con = DriverManager.getConnection("jdbc:mysql://localhost:3306/example_db","example_user","example_pass");
				con.setAutoCommit(false);
			} catch (Exception e1) {
				e1.printStackTrace();
			}
		}
		
		return con;
	}

	/**
	 * SQL実行(検索)
	 * @param con DB接続
	 * @param sql SQL文
	 * @return 結果
	 * @throws SQLException
	 */
	public List<Map<String,Object>> selectQuery(Connection con, String sql, Object[] params) throws SQLException{
		
		List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();

		PreparedStatement statement = null;
		try {
			statement = con.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					Object param = params[i];
					if (param instanceof Integer) {
						statement.setInt(i+1, (Integer)param);
					}
					if (param instanceof String) {
						statement.setString(i+1, (String)param);
					}
				}
			}

			ResultSet result = statement.executeQuery();
			java.sql.ResultSetMetaData metaData = result.getMetaData();
			int colCnt = metaData.getColumnCount();

			while(result.next()){
				Map<String,Object> rec = new HashMap<String, Object>();
				for (int i = 1; i <= colCnt; i++) {
					String colName = metaData.getColumnName(i);
					int colType = metaData.getColumnType(i);
					if (colType == java.sql.Types.VARCHAR){
						rec.put(colName,   result.getString(colName));
					}
					if (colType == java.sql.Types.INTEGER){
						rec.put(colName,   result.getInt(colName));
					}
					if (colType == java.sql.Types.TIMESTAMP){
						Timestamp ts = result.getTimestamp(colName);
						if (ts != null) {
							rec.put(colName,   ts.toString());
						}
					}
				}
				list.add(rec);
			}
			
		} finally {
			try {
				if (statement != null) {
					statement.close();
				}
			} catch (Exception e) {
			}
		}

		
		return list;
	}

	public List<Map<String,Object>> selectQuery(Connection con, String sql) throws SQLException{
		return selectQuery(con, sql, null);
	} 
	
} 
}}

*** DTOの作成 [#b57e6f87]
#mycode(){{
package example.dto;

import java.io.Serializable;

import javax.ws.rs.FormParam;
import javax.xml.bind.annotation.XmlRootElement;

// ↓ このアノテーションは XMLで値を返却する場合のみ必要
@XmlRootElement
public class BookDto implements Serializable {

	private static final long serialVersionUID = 1L;

	private int id;

	// ↓ 入力データをDtoで受け取る場合はこのアノテーションが必要
	@FormParam("isbn")
	private String isbn;

	@FormParam("title")
	private String title;

	@FormParam("price")
	private int price;

	private java.util.Date createdAt;

	private java.util.Date updatedAt;

	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getIsbn() {
		return isbn;
	}
	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	public java.util.Date getCreatedAt() {
		return createdAt;
	}
	public void setCreatedAt(java.util.Date createdAt) {
		this.createdAt = createdAt;
	}
	public java.util.Date getUpdatedAt() {
		return updatedAt;
	}
	public void setUpdatedAt(java.util.Date updatedAt) {
		this.updatedAt = updatedAt;
	} 
} 
}}

*** サービスの作成 [#n3c8faca]
値の受取、返却についての詳細は [[JAX-RSでの入力値の受取と返却]] を参照
#mycode(){{
package example.service;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

import example.dto.BookDto;

@Path("books")
public class BooksResource extends ServiceBase {

	/**
	 * 一覧取得<br />
	 * @return 結果(JSON文字列)
	 */
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Map<String,Object> list() {

		List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
		Connection con = null;
		try {
			con = getConnection();
			list = selectQuery(con, "select * from books");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				con.close();
			} catch (Exception e) {
			}
		}

		// Mapを返却する場合
		Map<String,Object> result = new HashMap<String,Object>();
		result.put("list", list);

		// DTOを返却する事も可能
		/*
		Map<String,Object> result = new HashMap<String,Object>();
		List<BookDto> listDto = new ArrayList<BookDto>();
		for (int i = 0; i < 10; i++) {
			BookDto book = new BookDto();
			book.setid(i + 1);
			book.setIsbn("TEST" + i + 1);
			book.setTitle("タイトル" + i + 1);
			book.setPrice(1000 + i + 1);
			listDto.add(book);
		}
		result.put("list", listDto);
		*/

		// JSON文字列を組み立てて返却する場合(メソッドの戻り値を String に変更する事)
		//String jsonList = new ObjectMapper().writeValueAsString(list);
		//String result = "{ \"list\" : " + jsonList + " }";

		return result;
	}

	/**
	 * id指定検索
	 * @param id 対象データのid
	 * @return 結果(JSON文字列)
	 */
	@GET
	@Path("{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public BookDto show(@PathParam("id") Integer id) {

		// DTOを返却する場合(自動的にJSONに変換される)
		BookDto book = new BookDto();
		book.setId(id);
		book.setIsbn("ISBN" + id); 
		book.setTitle("タイトル" + id);
		book.setPrice(1000 + id);
		return book;
	}

	/**
	 * 登録処理.<br />
	 * @param isbn   ISBN
	 * @param title  タイトル
	 * @param price  料金
	 * @return JSON
	 */
	@POST
	@Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON , MediaType.APPLICATION_XML })
	@Produces(MediaType.APPLICATION_JSON)
	public String create(@BeanParam BookDto book) {
	//public String create(@FormParam("isbn") String isbn, @FormParam("title") String title, @FormParam("price") String price) {

		String isbn = book.getIsbn();
		String title = book.getTitle();
		int    price = book.getPrice();

		System.out.println("isbn  : " + isbn);
		System.out.println("title : " + title);
		System.out.println("price : " + price);

		// .
		// ここで登録処理を行う
		// .

		// JSON文字列を返却する場合
		return "{ \"method\" : \"create\", \"result\" : \"success\" }";
	}

	/**
	 * 更新処理.<br />
	 * @param params 入力データ
	 * @return 結果
	 */
	@PUT
	@Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON , MediaType.APPLICATION_XML })
	@Produces(MediaType.APPLICATION_JSON)
	public Map<String,Object> update(@BeanParam BookDto book) {
	//public Map<String,Object> update(MultivaluedMap<String, String> params) {

		System.out.println("isbn  : " + book.getIsbn());
		System.out.println("title : " + book.getTitle());
		System.out.println("price : " + book.getPrice());

		// 入力データを表示
		/*
		Iterator<Map.Entry<String,List<String>>> it = params.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String,List<String>> entry = it.next();
			String key = entry.getKey();
			List<String> vals = entry.getValue();
			System.out.println(key + "=" + vals.get(0)); // ここでは値リストの1件目だけを表示(MultivaluedMap はキーに対して複数の値を持つ為)
		}
		 */

		// JSON文字列を組み立てて返却する事も可能(メソッドの戻り値を String にする事)
		//String result = "";
		//result = "{ \"method\" : \"update\", \"result\" : \"success\" }";

		// DTOを返却する事も可能(メソッドの戻り値を DTOのクラス名(Book)にする事)
		//BookDto result = new BookDto();
		//result.setIsbn("test1");
		//result.setTitle("test2");

		// Mapを返却する事も可能(メソッドの戻り値を Map にする事)
		Map<String,Object> result = new HashMap<String,Object>();
		result.put("method", "update");
		result.put("result", "success");
		result.put("var1"  , "111");
		result.put("var2"  , "222");

		return result;
	}

	/**
	 * 削除処理.<br />
	 * @param id id
	 * @return 結果 ※@Producesで指定した形式で返却される
	 */
	@DELETE
	@Path("{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public String delete(@PathParam("id") int id) {
		System.out.println("delete! id=" + id);
	    return "{ \"method\" : \"delete\", \"result\" : \"success\" }";
	}

	/**
	 * 動作確認用.<br />
	 * @param args
	 */
	public static void main(String[] args) {

		// 一覧表示
		System.out.println(new BooksResource().list());

		// id指定検索
		System.out.println(new BooksResource().show(1));
	} 
} 
}}

*** サービスの公開とサーバの開始 [#ef3d9893]
サーバを作成し、公開および開始する


#html(</div>)


** 動作確認 [#ce2d650d]
#html(<div style="padding-left:20px;">)

*** クライアント処理の作成 [#w0d71969]

index.html
#myhtmlcode(){{
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="test.js"></script>
</head>
<body>

<form action="ws/books" method="get" >
	<input type="submit" value="一覧検索" data-method="get" />
</form>

<hr />

<form action="ws/books/1" method="get" >
	<input type="submit" value="一意検索" data-method="get" />
</form>

<hr />

<form action="ws/books" method="post">
	id : <input type="text" name="id" value="1" /><br />
	title : <input type="text" name="title" value="test1" /><br />
	<input type="submit" value="登録" data-method="post" />
</form>

<hr />

<form action="ws/books">
	id : <input type="text" name="id" value="1" /><br />
	title : <input type="text" name="title" value="test2" /><br />
	<input type="submit" value="更新" data-method="put" />
</form>

<hr />

<form action="ws/books">
	id : <input type="text" name="id" value="1" /><br />
	<input type="submit" value="削除" data-method="delete" />
</form>

<hr />
結果
<div id="result" style="border:1px solid #000000;padding:10px;"></div>

</body>
</html>
}}

test.js
#mycode(){{
$(document).ready(function(){
	
	$("form").each(function(){
		$(this).find("input[type=submit]").on("click", function(e){

			$btn = $(this);
			$form = $(this).parents("form");
			var method = $btn.data("method");
			var url = $form.attr("action");
			if (method == "delete") {
				url = url + "/" + $form.find("[name=id]").val();
			}
			
			console.log("url   :" + url);
			console.log("method:" + method);
			
			var data = {};
			$form.find("[name]").each(function(){
				var name = $(this).attr("name");
				var val = $(this).val();
				if ($(this).attr("type") == "checkbox") {
					val = ($(this).prop("checked") ? $(this).val() : data[name]) || ""; 
				} else if ($(this).attr("type") == "radio"){
					val = ($(this).prop("checked") ? $(this).val() : data[name]) || ""; 
				}
				data[name] = val;
			});
			console.log(data);

			$.ajax({
				 "url"  : url
				,"data" : data
				,"type" : method
				,"dataType" : "json"
				,"success" : function(a1){
					console.log("success!");
					console.log(a1);
					var resultText = "url : " + url + "<br />"
					               + "method : " + method + "<br />"
					               + "result : " + JSON.stringify(a1);
					$("#result").html(resultText);
				}
				,"error" : function(a1){
					console.log("error!");
					console.log(a1);
				}
			});
			return false;
		});
	});

});
}}

http://localhost:8080/MyService/ にアクセスし、動作を確認する。

#html(</div>)


** 他、細々とした制御(validation等)を行いたい場合 [#k689a377]
#html(<div style="padding-left:20px;">)

ResourceConfig などを継承した 独自クラスを作成し、web.xml に定義する。
※ResourceConfig は javax.ws.rs.core.Application を継承しているクラス。

#html(</div>)

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS