Spring BootでWebAPI作成 †Spring Tool Suite (STS) のダウンロード †STSの日本語化 †http://mergedoc.osdn.jp/ テスト用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; /* 索引作成 */ create index books_idx1 on books (isbn); /* データ作成 */ 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()) ,('978-4891004552', 'CODE COMPLETE 第2版 上 完全なプログラミングを目指して', 6588, NOW(), NOW()) ,('978-4891004569', 'CODE COMPLETE 第2版 下 完全なプログラミングを目指して', 6588, NOW(), NOW()) ,('978-4894712744', '達人プログラマー', 3456, NOW(), NOW()) ,('978-4822285241', 'ピープルウエア', 2200, NOW(), NOW()) ,('978-4621066089', '人月の神話', 3456, NOW(), NOW()); Spring Boot プロジェクトの作成 †[新規] → [Spring スタータープロジェクト] †変えたい所がされば変更して「次へ」 †O/Rマッパー、テンプレートエンジンなどを選択 †※build.gradle の dependencies に反映されるだけなので、後からでも変更可能。 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環境変数などで上書き可能なので、ここではローカル用の設定をそのまま記述する。 テーブル定義の作成 †しれっと lombok 使って setter 、getter を動的生成してるので、 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; } } ビルド †起動 †動作確認 †# 一覧検索 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 の確認ができる。 |