Sharing is caring!

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



เริ่มกันเลย

  1. init project https://start.spring.io/
    1. 1
  2. เพิ่ม h2database ในไฟล์ pom.xml
    1. <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>
      
  3. สร้าง Project Structure ตามนี้
    1. ไฟล์ CrudController.java
      1. 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;
          }
          
        }
        



    2. ไฟล์ Blog.java
      1. 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 + "]";
          }
          
          
        }
        
    3. ไฟล์ BlogRepository.java
      1. 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;
        }
        
    4. ไฟล์ Application.java
      1. 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"));
            };
          }
        }
    5. ไฟล์ CrudControllerTest.java
      1. 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")));
          }
        }
        
  4. สั่ง mvn spring-boot:run
    1. [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)
  5. สั่ง mvn test (ไม่ต้อง spring-boot:run แล้ว) เพื่อ run unittest ทดสอบ api CRUD
    1. ...
      [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
      [email protected]: 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] ------------------------------------------------------------------------

  6. github source