Web Service แบบ RESTful protocol ได้รับความนิยมเป็นอย่างมากในปัจจุบัน หลายภาษามี web framework ของตัวอย่าง Java Spring Framework ก็เช่นกัน
SpringBoot เป็น Framework ที่ได้รับความนิยมมากกับ Java เพราะด้วยความง่ายที่เป็นสิ่งที่ถูกพัฒนาเพื่อแก้ปัญหาในการ Setup Project ที่ค่อนข้างยุ่งยากและซับซ้อนในการ Build RESTful API ขึ้นมาใช้งาน Springboot จึงได้รับความนิยมและได้เปรียบเรื่องความรวดเร็วในการ Setup Project
สิ่งที่จะได้รับเมื่ออ่านบทความบทนี้จบ
- Create Springboot Project
- CRUD Data with H2 Database
- Use GET,POST,PUT,DELETE Methods
- Content-Type : “application/json”
-
MockMvc
- CrudRepository Interface crud
เริ่มกันเลย
- init project https://start.spring.io/
- เพิ่ม h2database ในไฟล์ pom.xml
-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.poolsawat</groupId> <artifactId>MediumTestRestful</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>MediumTestRestful</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> </dependencies> <profiles> <profile> <id>DEV</id> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </profile> <profile> <id>TEST</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.15</version><!--$NO-MVN-MAN-VER$ --> </plugin> </plugins> </build> </profile> </profiles> </project>
-
- สร้าง Project Structure ตามนี้
- ไฟล์ CrudController.java
-
package com.poolsawat.medium.testrestful.controller; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.poolsawat.medium.testrestful.entity.Blog; import com.poolsawat.medium.testrestful.repository.BlogRepository; @Controller public class CrudController { @Autowired private BlogRepository blogRepository; @RequestMapping("/") public @ResponseBody String greeting() { return "Hello World"; } @GetMapping("/get") public @ResponseBody Iterable<Blog> getBlogs(){ return this.blogRepository.findAll(); } @GetMapping("/get/id/{id}") public @ResponseBody Optional<Blog> getBlog(@PathVariable(name="id") Long id){ return this.blogRepository.findById(id); } @PostMapping("/save") public @ResponseBody Blog saveBlog(@RequestBody Blog blog){ return this.blogRepository.save(blog); } @PutMapping("/update") public @ResponseBody Blog updateBlog(@RequestBody Blog blog){ return this.blogRepository.save(blog); } @DeleteMapping("/id/{id}") public @ResponseBody Long deleteBlog(@PathVariable(name="id") Long id) { this.blogRepository.deleteById(id); return id; } }
-
- ไฟล์ Blog.java
-
package com.poolsawat.medium.testrestful.entity; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Blog implements Serializable{ /** * */ private static final long serialVersionUID = 6833355522200232153L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; private String content; private String author; public Blog() { super(); // TODO Auto-generated constructor stub } public Blog(Long id, String title, String content, String author) { super(); this.id = id; this.title = title; this.content = content; this.author = author; } 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 getContent() { return content; } public void setContent(String content) { this.content = content; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { return "Blog [id=" + id + ", title=" + title + ", content=" + content + ", author=" + author + "]"; } }
-
- ไฟล์ BlogRepository.java
-
package com.poolsawat.medium.testrestful.repository; import java.util.List; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; import com.poolsawat.medium.testrestful.entity.Blog; @Repository public interface BlogRepository extends CrudRepository<Blog, Long> { List<Blog> findByTitle(String title) throws Exception; List<Blog> findByAuthor(String author) throws Exception; }
-
- ไฟล์ Application.java
-
package com.poolsawat.medium.testrestful; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import com.poolsawat.medium.testrestful.entity.Blog; import com.poolsawat.medium.testrestful.repository.BlogRepository; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public CommandLineRunner demo(BlogRepository blogRepository) { return (args) -> { blogRepository.save(new Blog(Long.valueOf("1"), "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.2) “Nuxt Directory Structure”", "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.2) “Nuxt Directory Structure”","poolsawat")); blogRepository.save(new Blog(Long.valueOf("2"), "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.1) “Setup Nuxt Project”", "SPA สร้าง Web Site Universal ด้วย Nuxt.js (EP.1) “Setup Nuxt Project”","poolsawat")); blogRepository.save(new Blog(Long.valueOf("3"), "สร้าง Project JSF Primeface ด้วย Maven พร้อมกับสอนทำระบบ Template Layout", "สร้าง Project JSF Primeface ด้วย Maven พร้อมกับสอนทำระบบ Template Layout","poolsawat")); }; } }
-
- ไฟล์ CrudControllerTest.java
-
package com.poolsawat.medium.testrestful; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.nullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class CrudControllerTest { @Autowired private MockMvc mockMvc; @Test public void testShouldSaveBlog() throws Exception { String content = "{\"id\" : 4,\"title\" : \"TestRestful\"}"; this.mockMvc.perform( post("/save") .content(content) .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.title", equalTo("TestRestful"))) .andExpect(jsonPath("$.content", nullValue())) .andExpect(jsonPath("$.author", nullValue())); } @Test public void testShouldGetReturnBlogs() throws Exception { this.mockMvc.perform(get("/get")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(2))); } @Test public void testShouldGetReturnBlogById() throws Exception { Integer id = 3; this.mockMvc.perform(get("/get/id/"+id)) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", equalTo(id))) .andExpect(jsonPath("$.title", equalTo("สร้าง Project JSF Primeface ด้วย Maven พร้อมกับสอนทำระบบ Template Layout"))); } @Test public void testShouldDeleteBlogById() throws Exception { Integer id = 1; this.mockMvc.perform(delete("/id/"+id)) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$", equalTo(id))); } @Test public void testShouldUpdateBlog() throws Exception { String content = "{\"id\" : 2,\"title\" : \"ShouldUpdateBlog\",\"author\" : \"poolsawat\"}"; this.mockMvc.perform( put("/update") .content(content) .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", equalTo(2))) .andExpect(jsonPath("$.title", equalTo("ShouldUpdateBlog"))) .andExpect(jsonPath("$.content", nullValue())) .andExpect(jsonPath("$.author", equalTo("poolsawat"))); } }
-
- สั่ง mvn spring-boot:run
-
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ MediumTestRestful --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] <<< spring-boot-maven-plugin:2.0.5.RELEASE:run (default-cli) < test-compile @ MediumTestRestful <<< [INFO] [INFO] [INFO] --- spring-boot-maven-plugin:2.0.5.RELEASE:run (default-cli) @ MediumTestRestful --- . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.5.RELEASE) ... ... 2018-12-05 18:51:43.509 INFO 3096 --- [ main] c.p.medium.testrestful.Application : Started Application in 10.0 65 seconds (JVM running for 16.349)
-
- สั่ง mvn test (ไม่ต้อง spring-boot:run แล้ว) เพื่อ run unittest ทดสอบ api CRUD
-
... [INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.749 s - in com.poolsawat.medium.testrestful.CrudContr ollerTest 2018-12-05 19:02:23.789 INFO 5576 --- [ Thread-3] o.s.w.c.s.GenericWebApplicationContext : Closing org.springframework .web.context.support.GenericWebApplicationContext@23e84203: startup date [Wed Dec 05 19:02:12 ICT 2018]; root of context hierar chy 2018-12-05 19:02:23.798 INFO 5576 --- [ Thread-3] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFa ctory for persistence unit 'default' 2018-12-05 19:02:23.798 INFO 5576 --- [ Thread-3] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed drop of schema as part of SessionFactory shut-down' 2018-12-05 19:02:23.805 INFO 5576 --- [ Thread-3] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown ini tiated... 2018-12-05 19:02:23.811 INFO 5576 --- [ Thread-3] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown com pleted. [INFO] [INFO] Results: [INFO] [INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 17.707 s [INFO] Finished at: 2018-12-05T19:02:24+07:00 [INFO] ------------------------------------------------------------------------
-
- github source