Spring BootでWebAPI作成

Spring Tool Suite (STS) のダウンロード

https://spring.io/tools/sts

STSの日本語化

http://mergedoc.osdn.jp/
本体は不要なのでプラグインだけ落として readme を見て features と plugins を突っ込む。

テスト用DBの準備(MySQL)

DB、ユーザ作成

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

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

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

exit;

作成したユーザで接続し直してテーブル作成

mysql -h localhost -P 3306 -u example_user -p example_db

/* テーブル作成 */
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());

Spring Boot プロジェクトの作成

[新規] → [Spring スタータープロジェクト]

create_project.png

変えたい所がされば変更して「次へ」

create_project2.png

O/Rマッパー、テンプレートエンジンなどを選択

 ※build.gradle の dependencies に反映されるだけなので、後からでも変更可能。

create_project3.png

application.properties にDB接続情報を追加

src/main/resources/application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/example_db
spring.datasource.username=example_user
spring.datasource.password=example_pass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.tomcat.maxActive=30
spring.datasource.tomcat.maxIdle=20
spring.datasource.tomcat.minIdle=10
spring.datasource.tomcat.initialSize=5

※これらの値はOS環境変数などで上書き可能なので、ここではローカル用の設定をそのまま記述する。
※環境変数に定義する時の変数名は spring.datasource.url → SPRING_DATASOURCE_URL のように変換する。

テーブル定義の作成

しれっと lombok 使って setter 、getter を動的生成してるので、
lombok プラグインのインストールと、gradle.build の dependencies に compile('org.projectlombok:lombok:1.16.10') 等を追加する事。

src/main/java/com/example/demo/model/Book.java

package com.example.demo.model;

import java.io.Serializable;

import java.util.Date;

import lombok.Data;

@Data
public class Book implements Serializable {
	private static final long serialVersionUID = 1L;
	private int id;
	private String isbn;
	private String title;
	private int price;
	private int created_at;
	private Date createdAt;
	private Date updatedAt;
}

Daoの作成

src/main/java/com/example/demo/dao/BookDao.java

package com.example.demo.dao;

import java.util.List;

import com.example.demo.model.Book;

public interface BookDao {
    List<Book> selectAll();
    Book select(int id);
    void insert(Book book);
    int update(Book book);
    int delete(int id);
}

MyBatis用のDao定義を作成

src/main/resources/dao/BookDao.xml

<!--?xml version="1.0" encoding="UTF-8" ?-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.demo.dao.BookDao">
    <select id="selectAll" resultType="com.example.demo.model.Book">
        select * from books
    </select>
    <select id="select" parameterType="int" resultType="com.example.demo.model.Book">
        select * from books where id = #{id}
    </select>
    <insert id="insert" parameterType="com.example.demo.model.Book">
	    insert into books (isbn,title,price,created_at,updated_at)
	     values(#{isbn},#{title},#{price},now(),now())
	    <!-- MySQLの場合、以下で自動採番(AUTO_INCREMENT)されたIDをオブジェクトにセットできる -->
	    <selectKey resultType="int" keyProperty="id" order="AFTER">
			select @@IDENTITY   <!-- SELECT LAST_INSERT_ID() でも可 -->
	    </selectKey>
    </insert>
    <update id="update" parameterType="com.example.demo.model.Book">
        update books set isbn = #{isbn}, title = #{title}, price = #{price}, updated_at = now() where id = #{id}
    </update>
    <delete id="delete" parameterType="int">
        delete from books where id = #{id}
     </delete>
</mapper>

mybatis-config.xml を追加

参照: http://www.mybatis.org/mybatis-3/ja/configuration.html#settings

src/main/resources/mybatis-config.xml

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="true" />
        <setting name="useColumnLabel" value="true" />
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
</configuration>

MyBatis用のConfigクラスを追加

src/main/java/com/example/demo/config/SqlMappingConfig.java

package com.example.demo.config;
import java.io.IOException;

import javax.sql.DataSource;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;

@Configuration
@MapperScan("com.example.demo.dao")
public class SqlMappingConfig {
  /**
   * SqlSessionFactoryBean格納クラス。
   * @return SqlSessionFactoryBean。
   */
  @Bean
  public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    ResourcePatternResolver resolver =
        ResourcePatternUtils.getResourcePatternResolver(new DefaultResourceLoader());
    factory.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml"));
    factory.setMapperLocations(resolver.getResources("classpath:dao/**/*.xml"));
    return factory;
  }
}

コントローラの作成

src/main/java/com/example/demo/controller/BookController.java

package com.example.demo.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.dao.BookDao;
import com.example.demo.model.Book;

@RestController
@RequestMapping("/api")
public class BookController {

	@Autowired
	private BookDao bookMapper;

	@RequestMapping(value = "/books", method = RequestMethod.GET)
	public List<Book> selectAll() {
		List<Book> books = bookMapper.selectAll();
		for (Book book : books) {
			System.out.println(book.toString());
		}
		return books;
	}

	@RequestMapping(value = "/book/{id}", method = RequestMethod.GET)
	public Book select(@PathVariable("id") Integer id) {
		Book books = bookMapper.select(id);
		return books;
	}

	@RequestMapping(value = "/book", method = RequestMethod.POST)
	public Book create(@ModelAttribute Book book) {
		bookMapper.insert(book);
		return book;
	}

	@RequestMapping(value = "/book/{id}", method = RequestMethod.PUT)
	public Map<String,String>  update(@PathVariable("id") Integer id, @ModelAttribute Book book) {
		Map<String,String> results = new HashMap<String, String>();
		book.setId(id);
		int count = bookMapper.update(book);
		results.put("result", count == 1 ? "OK" : "NG");
		return results;
	}

	@RequestMapping(value = "/book/{id}", method = RequestMethod.DELETE)
	public Map<String,String> delete(@PathVariable("id") Integer id) {
		Map<String,String> results = new HashMap<String, String>();
		int count = bookMapper.delete(id);
		results.put("result", count == 1 ? "OK" : "NG");
		return results;
	}
}

ビルド

gradle_build.png

起動

gradle_boot_run.png

動作確認

# 一覧検索
curl -v http://localhost:8080/api/books

# 一意検索
curl -v http://localhost:8080/api/book/1

# 登録
curl -v -XPOST --data "title=TEST&isbn=XXXXX&price=1234" http://localhost:8080/api/book/

# 更新
curl -v -XPUT --data "title=UPDATE&isbn=YYYY&price=5678" http://localhost:8080/api/book/4

# 削除
curl -v -XDELETE http://localhost:8080/api/book/4

おまけ(動作確認用のページ追加)

src/main/java/com/example/demo/config/StaticResourceConfig.java

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class StaticResourceConfig extends WebMvcConfigurerAdapter {

	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }
}

src/main/resources/static/book.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
#booklist {
	border-collapse: collapse;
}
#booklist th, #booklist td {
	padding: 4px 10px;
	border: 1px solid #333;
}
#booklist th {
	background: #ccc;
}
#booklist td {
	cursor: pointer;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="/js/ajax.js"></script>
</head>
<body>
<form action="/api/books" method="get" >
    <input type="submit" value="一覧検索" data-method="get" data-callback="resultList" />
    <table id="booklist">
    <thead>
    <tr>
    		<th>id</th>
    		<th>isbn</th>
    		<th>title</th>
    		<th>price</th>
    </tr>
    </thead>
    <tbody>
    </tbody>
    </table>
</form>
<hr />
<form action="/api/book" method="get" >
    id : <input type="text" name=id value="1" /><br />
    <input type="submit" value="一意検索" data-method="get" />
</form>
<hr />
<form action="/api/book" method="post">
    isbn : <input type="text" name="isbn" value="123-4567890123" /><br />
    title : <input type="text" name="title" value="test1" /><br />
    price : <input type="number" name="price" value="1000" /><br />
    <input type="submit" value="登録" data-method="post" />
</form>
<hr />
<form action="/api/book">
    id : <input type="text" name="id" value="1" /><br />
    isbn : <input type="text" name="isbn" value="123-4567890123" /><br />
    title : <input type="text" name="title" value="test2" /><br />
    price : <input type="number" name="price" value="1000" /><br />
    <input type="submit" value="更新" data-method="put" />
</form>
<hr />
<form action="/api/book">
    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>

<script>
// 一覧検索結果の描画
window.resultList = function(result){
	console.log("callback!");
	$("#booklist tbody").empty();
	if (result && result.length > 0) {
		for (var i in result) {
			var rowHTml = "<tr>"
                        + "<td>"+result[i]["id"]+"</td>"
                        + "<td>"+result[i]["isbn"]+"</td>"
                        + "<td>"+result[i]["title"]+"</td>"
                        + "<td>"+result[i]["price"]+"</td>"
                        + "</tr>";
			$("#booklist tbody").append(rowHTml);
		}
	}
};
jQuery(function($){
	// 一覧の任意の行を選択時
	$(document).on("click", "#booklist tbody td", function(){
		var id    = $(this).parents("tr").find("td").eq(0).text();
		var isbn  = $(this).parents("tr").find("td").eq(1).text();
		var title = $(this).parents("tr").find("td").eq(2).text();
		var price = $(this).parents("tr").find("td").eq(3).text();
		$(document).find("[name=id]").val(id);
		$(document).find("[name=isbn]").val(isbn);
		$(document).find("[name=title]").val(title);
		$(document).find("[name=price]").val(price);
	});
});
</script>
</body>
</html>

src/main/resources/static/js/ajax.js

jQuery(function($){
    
    $("form").each(function(){
        $(this).find("input[type=submit]").on("click", function(e){
            $btn = $(this);
            $form = $(this).parents("form");
            var method = $btn.data("method").toLowerCase();
            var url = $form.attr("action");

            var callbackFunc = null;
            var callback = $btn.data("callback");
            if (callback && window[callback]) {
            		callbackFunc = window[callback];
            }

            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;
            });

            if ((method == "put" || method == "delete" || method == "get") && data["id"]) {
                url = url + "/" + data["id"];
                delete(data["id"]);
            }

            console.log("url   :" + url);
            console.log("method:" + method);
            console.log(data);

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

以上のファイルを作成すれば http://localhost:8080/static/book.html から CRUD の確認ができる。


添付ファイル: filegradle_boot_run.png 460件 [詳細] filegradle_build.png 505件 [詳細] filecreate_project3.png 503件 [詳細] filecreate_project2.png 459件 [詳細] filecreate_project.png 568件 [詳細]

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-10-22 (日) 23:42:42 (2369d)