- 追加された行はこの色です。
- 削除された行はこの色です。
* 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(){{
<Context docBase="MyService"
<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"
/>
</Context>
}}
※ http://dev.mysql.com/doc/connector-j/en/connector-j-usagenotes-tomcat.html
*** web.xml の編集 [#ocdaa091]
#myhtmlcode(){{
・
・
<!-- /ws で始まるURLをWebサービスとする -->
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<!-- server.xmlで定義したDB接続への参照を定義 -->
<resource-ref>
<res-ref-name>jdbc/datasource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
}}
※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>)