java

SpringBoot2 สร้าง CRUD RESTful API พร้อม UnitTest แบบรวดเร็ว

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

DynamicReport Engine สร้างครั้งเดียวออก Report ได้ทุกแบบ (Hybrid Report)

โปรเจคเกือบจะทุกระบบที่พัฒนาขึ้นมาจำเป็นต้องมองหาระบบออกรายงาน เพื่อดูรูปแบบมิติของข้อมูลในมุมมองต่าง ๆ และรูปแบบชนิดของไฟล์หลากหลายชนิด เช่น PDF , XLS ,HTML , DOCX ,PPTX , CSV , TEXT , XML เป็นต้น ถ้าความต้องการของลูกค้าอยากจะได้ทุกชนิดละคงต้องประเมินเวลาไปอย่างน้อย 1 เดือนสำหรับการที่ทำ Report ให้ออกมาตามประเภทไฟล์ข้างต้นนี้ทั้งหมด แต่ลูกค้าที่น่ารักใจร้อนอยากจะได้เร็ว ๆ ให้เวลาช้าที่สุด 1 สัปดาห์ได้ไหม บทความนี้ช่วยคุณแก้ปัญหานี้ได้แน่นอน

สำหรับบทความนี้ก็เหมาะสำหรับนักพัฒนาที่มีพื้นฐาน Java สักหน่อย เริ่มกันเลย

    1. สร้าง Maven Project ขึ้นมา (จะเป็น Java Web หรือ Java Application ก็แล้วแต่ลักษณะงานของคุณ)
    2. เพิ่ม .pom ตามนี้
      <?xml version="1.0" encoding="UTF-8"?>
      <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>MyDynamicReport</artifactId>
          <version>1.0-SNAPSHOT</version>
          <packaging>jar</packaging>
          <properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <maven.compiler.source>1.7</maven.compiler.source>
              <maven.compiler.target>1.7</maven.compiler.target>
              
              <slf4j.version>1.7.7</slf4j.version>
              
              <jasperreports.version>6.3.1</jasperreports.version>
              <dynamicreport.version>5.0.0</dynamicreport.version>
              <groovy.version>1.8.6</groovy.version>
              <poi.version>3.10.1</poi.version>
              
          </properties>
          <dependencies>        
      
              <dependency>
                  <groupId>org.codehaus.groovy</groupId>
                  <artifactId>groovy-all</artifactId>
                  <version>${groovy.version}</version>
              </dependency>        
              <dependency>
                  <groupId>net.sourceforge.dynamicreports</groupId>
                  <artifactId>dynamicreports-core</artifactId>
                  <version>${dynamicreport.version}</version>
              </dependency>        
              <dependency>
                  <groupId>net.sf.jasperreports</groupId>
                  <artifactId>jasperreports</artifactId>
                  <version>${jasperreports.version}</version>
                  <exclusions>
                      <exclusion>
                          <groupId>commons-logging</groupId>
                          <artifactId>commons-logging</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              
              <!--Poi-->
              <dependency>
                  <groupId>org.apache.poi</groupId>
                  <artifactId>poi</artifactId>
                  <version>${poi.version}</version>
              </dependency>
              
              
              <!-- Logging -->
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-api</artifactId>
                  <version>${slf4j.version}</version>
                  <scope>compile</scope>
              </dependency>
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-log4j12</artifactId>
                  <version>${slf4j.version}</version>
              </dependency>
              <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
              <dependency>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
                  <version>1.2</version>
              </dependency>
      
          </dependencies>
      </project>




  1. สร้าง GenarateReport.java
    package com.poolsawat.dynamic;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.util.Date;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import static net.sf.dynamicreports.report.builder.DynamicReports.*;
    import net.sf.dynamicreports.report.builder.style.StyleBuilder;
    import net.sf.dynamicreports.report.constant.HorizontalTextAlignment;
    import net.sf.dynamicreports.report.datasource.DRDataSource;
    import net.sf.dynamicreports.report.exception.DRException;
    import net.sf.jasperreports.engine.JRDataSource;
    
    /**
     *
     * @author poola410
     */
    public class GenarateReport {
    
        public GenarateReport() {
            build();
        }
    
        private void build() {
            try {
                report()//create new report design
                        //.setDefaultFont(stl.font().setFontName("TH SarabunPSK").setFontSize(16))                    
                        .columns(
                                col.column("PostId", "id", type.integerType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()),
                                col.column("PostTitle", "title", type.stringType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()),
                                col.column("PostAuthor", "author", type.stringType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()),
                                col.column("PostPublicDate", "public_date", type.dateType())
                                        .setStyle(getTableStyle())
                                        .setTitleStyle(getTableStyle()))
                        .title(cmp.text("PoolsawatBlogs"))//shows report title
                        .pageFooter(cmp.pageXofY())//shows number of page at page footer
                        .setDataSource(createDataSource())//set datasource
                        //.show();//create and show report
                        .toPdf(new FileOutputStream(new File("./src/main/resources/output/poolsawat.pdf")))
                        .toXls(new FileOutputStream(new File("./src/main/resources/output/poolsawat.xls")))
                        .toCsv(new FileOutputStream(new File("./src/main/resources/output/poolsawat.csv")))
                        .toXlsx(new FileOutputStream(new File("./src/main/resources/output/poolsawat.xlsx")))
                        .toHtml(new FileOutputStream(new File("./src/main/resources/output/poolsawat.html")))
                        .toPptx(new FileOutputStream(new File("./src/main/resources/output/poolsawat.pptx")))
                        .toText(new FileOutputStream(new File("./src/main/resources/output/poolsawat.text")))
                        .toXml(new FileOutputStream(new File("./src/main/resources/output/poolsawat.xml")));
                        
            } catch (DRException e) {
                e.printStackTrace();
            } catch (FileNotFoundException ex) {
                Logger.getLogger(GenarateReport.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        private StyleBuilder getTableStyle(){
            return stl.style().setBorder(stl.border(stl.pen1Point()))
                    .setHorizontalTextAlignment(HorizontalTextAlignment.LEFT);
        }
    
        private JRDataSource createDataSource() {
            DRDataSource dataSource = new DRDataSource("id", "title","author","public_date");
            dataSource.add(1215,"Quartz Scheduler มันคืออะไร การใช้งานเบื้องต้น", "admin", new Date());
            dataSource.add(1150,"Gson Open Source Library สำหรับจัดการ JSON Formatter", "admin", new Date());
            dataSource.add(1132,"สร้าง Spring MVC 4 ร่วมกับ Apache Tiles 3", "admin", new Date());
            dataSource.add(1099,"[ES6] Promise คืออะไร", "admin", new Date());
            dataSource.add(1072,"ประสบการณ์ทำเว็บไซต์ให้ปลอดภัยด้วย HTTPS ไม่ยากอย่างที่คิด", "admin", new Date());
            dataSource.add(1025,"เล่าประสบการณ์แข่งขัน Hackathon ครั้งแรกให้ชีวิต", "admin", new Date());
            dataSource.add(1002,"สรุปสิ่งที่ได้รับจากงาน Cloud Functions for Firebase and Next Generation of Web", "admin", new Date());
            return dataSource;
        }
    
        public static void main(String[] args) {
            new GenarateReport();
        }
    }
    
  2. สั่ง RUN File GenarateReport.java
  3. ดู output ตาม Path

 DynamicReport สามารถทำ Report ที่แสดงในรูปแบบของกราฟได้ด้วย ศึกษาเพิ่มเติมจากลิ้งนี้

Github source ZIP , Github Source Repository

Quartz Scheduler มันคืออะไร การใช้งานเบื้องต้น

Quartz Scheduler เป็น Open Source Project นิยมนำมาใช้งานกับโปรเจคระดับองค์กร ทำหน้าที่เป็นเครื่องมือจัดการเรื่อง Job Scheduler คล้ายกับ Cron ในระบบปฏิบัติการ linux ช่วยจัดการเรื่อง Job ที่มีการทำงานตั้งแต่ 1 Process ขึ้นไปใช้กับงาน Process ที่ต้องใช้เวลาในการ Processing ยาวนานกินเวลาหลายชั่วโมง ภายใน quartz-scheduler จะมี Class CronTrigger ซึ่งช่วยทำหน้าที่จัดการเวลาการทำงาน เช่น “ตอน 8.00 น.ทุกวันจันทร์ถึงวันศุกร์” หรือ “เวลา 1.30 น. ทุกวันศุกร์สุดท้ายของเดือน” ช่วยเพิ่มประสิทธิภาพการจัด scheduler ได้ออย่างดี

เริ่มต้นการใช้งาน

เพิ่ม dependency ในไฟล์ pom.xml

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>1.8.6</version>
</dependency>

สร้าง Class Job Execute

public class DumbJob implements Job{

  public void execute(JobExecutionContext arg0) throws JobExecutionException {
    System.out.println("Poolsawat.com Run Scheduler Job");		
  }

}

ตัวอย่าง ตั้งเวลาการทำงานทุกนาที โดยเริ่มทำงานตอนทำงานในนาทีถัดไปหลังจากสั่งเริ่มการทำงาน

try {	
  SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
  
  Scheduler sched = schedFact.getScheduler();

  sched.start();

  JobDetail jobDetail = new JobDetail("myJob", null, DumbJob.class);

  Trigger trigger = TriggerUtils.makeMinutelyTrigger();
  trigger.setStartTime(TriggerUtils.getEvenMinuteDate(new Date()));
  trigger.setName("myTrigger");

  sched.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
  e.printStackTrace();
}

ตัวอย่าง ตั้งเวลาการทำงานช่วงเวลา 00:00 น. โดยเริ่มทำงานทันที่

try {
  SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

  Scheduler sched = schedFact.getScheduler();

  JobDetail jobDetail = new JobDetail("myJob",
      Scheduler.DEFAULT_GROUP, 
      DumbJob.class);
  
  Trigger trigger = TriggerUtils.makeDailyTrigger(0, 0);
  trigger.setStartTime(new Date());
  trigger.setName("myTrigger");

  sched.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
  e.printStackTrace();
}

สร้าง Class Job ที่รับ Parameter

public class DataMapJob implements Job{
  public void execute(JobExecutionContext context) throws JobExecutionException {
    JobDetail detail = context.getJobDetail();
    JobDataMap mapData = detail.getJobDataMap();		
    System.out.println("param1 ::=="+mapData.getString("param1"));
    System.out.println("param2 ::=="+mapData.getString("param2"));
  }
}

ตัวอย่าง การส่งค่า Parameter เข้า Process ภายใน Class Job

try {
  SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

  Scheduler sched = schedFact.getScheduler();

  sched.start();

  JobDetail jobDetail = new JobDetail("myJob", null, DataMapJob.class);
  
  
  JobDataMap dataMap = new JobDataMap();
  dataMap.put("param1", "poolsawat.com");
  dataMap.put("param2", "quartz");
  jobDetail.setJobDataMap(dataMap);
  
  Trigger trigger = TriggerUtils.makeMinutelyTrigger();
  trigger.setStartTime(TriggerUtils.getEvenMinuteDate(new Date()));
  trigger.setName("myTrigger");

  sched.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
  e.printStackTrace();
}

ตัวอย่าง ตั้งค่าใน Job ทำงานแค่ครั้งเดียว

try {
  SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

  Scheduler sched = schedFact.getScheduler();

  sched.start();

  JobDetail jobDetail = new JobDetail("myJob", null, DataMapJob.class);
  
  SimpleTrigger trigger = new SimpleTrigger("myTrigger",
      null,
      new Date(),
      null,
      0,
      0L);
  
  sched.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
  e.printStackTrace();
}

เพิ่มเติม…

Gson Open Source Library สำหรับจัดการ JSON Formatter

“JSON” ไม่ใช่เรื่องใหม่อะไร เพราะปัจจุบันมีการใช้งานกันอย่างแพร่หลาย สำหรับเอาไว้ใช้จัดการข้อมูลรับส่งระหว่าง frontend และ backend เพราะด้วยความง่ายและดูเป็นมาตฐาน มีรูปแบบที่ตายตัว ที่ใคร ๆ ก็สามารถเข้าใจได้เพราะเป็นการเก็บข้อมูลแบบ Key และ value

Java Library ที่ใช้สำหรับจัดการ JSON Data ก็มีมากมายให้ได้เลือกใช้งาน เช่น Google-Gson Library ,Flexjson ,Json-io ,Genson ,JSONiJ ,Jackson ,JSON-lib เป็นต้น ผมเองเป็นคนนึงที่นิยมใช้งาน Library ที่เป็น Open Source และต้องมีกลุ่ม Community ที่ใหญ่ และ Gson เองก็ได้รับความนิยมมาก เพราะด้วยความง่ายในการใช้งาน

การเริ่มใช้งาน Gson

เพิ่ม dependency

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.6.2</version>
</dependency>




การใช้งานเบื้องต้นของ Gson จะมีด้วยกัน 2 รูปแบบ ด้วยกัน คือ Serialization ,Deserialization

Serialization การแปลงข้อมูลที่อยู่ในรูปแบบ Object ไปเป็น Json String Format

Gson gson = new Gson();
gson.toJson(1);            // ==> 1
gson.toJson("abcd");       // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values);       // ==> [1]

Deserialization  การแปลงข้อมูลในรูปแบบ Json String Format ไปเป็น Object

int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);

ตัวอย่างการใช้งานง่าย ๆ

สร้าง POJO Class

public class EasyObject {
    private String valueString;
    private int valueInteger;
    private Date valueDate;
    private boolean valueBoolean;
    // getter , setter

    @[email protected] public String toString() { return "EasyObject [valueString=" + valueString + ", valueInteger=" + valueInteger + ", valueDate=" + valueDate + ", valueBoolean=" + valueBoolean + "]"; }
}

ตัวอย่าง Method Serialization

private void exampleSerialization(){
  EasyObject easy = new EasyObject();
  easy.setValueBoolean(true);
  easy.setValueDate(new Date());
  easy.setValueInteger(999);
  easy.setValueString("poolsawat.com");
  //Serialization 
  System.out.println("serialization :: "+new Gson().toJson(easy));  
}
/*
serialization :: {"valueString":"poolsawat.com","valueInteger":999,"valueDate":"Oct 4, 2017 9:35:34 PM","valueBoolean":true} 
*/

ตัวอย่าง Method Deserialization

private void exampleDeserialization(){
  String json = "{\"valueString\":\"poolsawat.com\",\"valueInteger\":999,\"valueDate\":\"Oct 4, 2017 9:30:08 PM\",\"valueBoolean\":true}";
  EasyObject easy = new Gson().fromJson(json, EasyObject.class);
  System.out.println("deserialization :: "+easy.toString());  
}
/* 
deserialization :: EasyObject [valueString=poolsawat.com, valueInteger=999, valueDate=Wed Oct 04 21:30:08 ICT 2017, valueBoolean=true] 
*/

เมื่อใช้งาน Gson ไปสักระยะก็จะพบปัญหา หรือ สิ่งที่อยากปรับแต่งกับข้อมูลผลลัพธ์ให้เป็นไปตามความต้องการ Gson เองก็มี  GsonBuilder Class สำหรับให้เอาไว้ Custom Serialization and Deserialization

ตัวอย่างการแสดงผลลัพธ์ทาง Console ให้อยู่ในรูปแบบที่สวยงานโดยการ กำหนด PrettyPrinting

private void examplePrettyPrinting(){
  EasyObject easy = new EasyObject();
  easy.setValueBoolean(true);
  easy.setValueDate(new Date());
  easy.setValueInteger(999);
  easy.setValueString("poolsawat.com");
  //Serialization 
  Gson gson = new GsonBuilder().setPrettyPrinting().create();		
  System.out.println("serialization :: "+gson.toJson(easy));
}

/*
serialization :: {
  "valueString": "poolsawat.com",
  "valueInteger": 999,
  "valueDate": "Oct 4, 2017 9:54:58 PM",
  "valueBoolean": true
}*/

ตัวอย่างต้องการ custom field ที่มี value เป็น null อยากที่จะให้แสดงผลลัพธ์ field ที่เป็น null นั้นออกมาด้วยจำเป็นต้องกำหนด ให้  .serializeNulls()

private void exampleSerializeNulls(){
  EasyObject easy = new EasyObject();
  easy.setValueBoolean(true);
  //easy.setValueDate(new Date());
  easy.setValueInteger(999);
  easy.setValueString("poolsawat.com");
  
  //Serialization exclude null
  Gson gson = new GsonBuilder().setPrettyPrinting()				
      .create();		
  System.out.println("serialization exlude null:: "+gson.toJson(easy));
  
  //Serialization exclude null
  gson = new GsonBuilder().setPrettyPrinting()				
      .serializeNulls()
      .create();		
  System.out.println("serialization want null :: "+gson.toJson(easy));
}
/*
serialization exlude null:: {
  "valueString": "poolsawat.com",
  "valueInteger": 999,
  "valueBoolean": true
}
serialization want null :: {
  "valueString": "poolsawat.com",
  "valueInteger": 999,
  "valueDate": null,
  "valueBoolean": true
}*/




หากต้องการที่จะกำหนดชื่อใหม่ให้กับ key name ก็สามารถทำได้ โดยไม่ด้วยกัน 2 วิธี คือการกำหนดผ่าน annotation @SerializedName และ anonymous inner type การ setFieldNamingStrategy

@SerializedName("rename_valueBoolean") // เพิ่มเข้า
private boolean valueBoolean;

private void exampleRenameFieldKeyWithAnnotation(){
  EasyObject easy = new EasyObject();
  easy.setValueBoolean(true);
  //easy.setValueDate(new Date());
  easy.setValueInteger(999);
  easy.setValueString("poolsawat.com");
  
  Gson gson = new GsonBuilder().setPrettyPrinting()					
      .create();		
  System.out.println("serialization :: "+gson.toJson(easy));
}
/*
serialization :: {
  "valueString": "poolsawat.com",
  "valueInteger": 999,
  "rename_valueBoolean": true
}
*/

private boolean valueBoolean; // ไม่ต้องมี annotation แล้ว

private void exampleRenameFieldKeyWithAnonymous(){
  EasyObject easy = new EasyObject();
  easy.setValueBoolean(true);
  //easy.setValueDate(new Date());
  easy.setValueInteger(999);
  easy.setValueString("poolsawat.com");
  
  Gson gson = new GsonBuilder().setPrettyPrinting()		
      .setFieldNamingStrategy(new FieldNamingStrategy() {
        
        public String translateName(Field f) {
          if (f.getName().equals("valueBoolean"))
            return "rename_valueBoolean";
          else
            return f.getName();
        }
      })
      .create();		
  System.out.println("serialization :: "+gson.toJson(easy));
}

/*
serialization :: {
  "valueString": "poolsawat.com",
  "valueInteger": 999,
  "rename_valueBoolean": true
}
*/

สำหรับการกำหนดรุปแบบ format ให้ field ที่เป็น type date ก็สามารถกำหนดให้ได้ด้วยการกำหนด setDateFormat ตาม format ที่เรากำหนดเองได้เลย

private void exampleDateFormat(){
  EasyObject easy = new EasyObject();
  easy.setValueBoolean(true);
  easy.setValueDate(new Date());
  easy.setValueInteger(999);
  easy.setValueString("poolsawat.com");
  
  Gson gson = new GsonBuilder().setPrettyPrinting()		
      .setDateFormat("dd/MM/yyyy HH:mm:ss")
      .create();
  System.out.println("serialization dd/MM/yyyy HH:mm:ss :: "+gson.toJson(easy));		
  
  gson = new GsonBuilder().setPrettyPrinting()		
      .setDateFormat("yyyy/MM/dd")
      .create();
  System.out.println("serialization yyyy/MM/dd :: "+gson.toJson(easy));		
}
/*
serialization dd/MM/yyyy HH:mm:ss :: {
  "valueString": "poolsawat.com",
  "valueInteger": 999,
  "valueDate": "04/10/2017 22:29:53",
  "rename_valueBoolean": true
}
serialization yyyy/MM/dd :: {
  "valueString": "poolsawat.com",
  "valueInteger": 999,
  "valueDate": "2017/10/04",
  "rename_valueBoolean": true
}
*/

ในบางครั้งเราก็ไม่ต้องการสร้าง POJO สำหรับการเก็บ Data ชุดใหม่ Gson ก็มี Class สำหรับสร้าง Json Object เองได้สำหรับคนที่อยากกำหนด key ที่หลากหลาย

private void exampleJsonObject(){
  JsonObject object = new JsonObject();
  object.addProperty("blog_title", "Gson Open Source Library สำหรับจัดการ JSON Formatter");
  object.addProperty("blog_content", "“JSON” ไม่ใช่เรื่องใหม่อะไร เพราะปัจจุบันมีการ...");
  object.addProperty("blog_date", "04/10/2017");
  object.addProperty("blog_author", "poolsawat apin");
  Gson gson = new GsonBuilder().setPrettyPrinting().create();		
  System.out.println("serialization JsonObject :: "+gson.toJson(object));		
}
/*
serialization JsonObject :: {
  "blog_title": "Gson Open Source Library สำหรับจัดการ JSON Formatter",
  "blog_content": "“JSON” ไม่ใช่เรื่องใหม่อะไร เพราะปัจจุบันมีการ...",
  "blog_date": "04/10/2017",
  "blog_author": "poolsawat apin"
}
*/

อยากสร้างในรูปแบบของ Json Array ก็สามารถทำได้

private void exampleJsonArray(){
  JsonArray array = new JsonArray();
  
  JsonObject object1 = new JsonObject();
  object1.addProperty("blog_title", "Gson Open Source Library สำหรับจัดการ JSON Formatter");
  object1.addProperty("blog_content", "“JSON” ไม่ใช่เรื่องใหม่อะไร เพราะปัจจุบันมีการ...");
  object1.addProperty("blog_date", "04/10/2017");
  object1.addProperty("blog_author", "poolsawat apin");
  array.add(object1);
  
  JsonObject object2 = new JsonObject();
  object2.addProperty("blog_title", "สร้าง Spring MVC 4 ร่วมกับ Apache Tiles 3");
  object2.addProperty("blog_content", "สำหรับงานพัฒนาเว็บไซต์ด้วยภาษา Java มี Frameworks ให้เลือกอย่างมากมาย ...");
  object2.addProperty("blog_date", "15/09/2017");
  object2.addProperty("blog_author", "poolsawat apin");
  array.add(object2);
  
  Gson gson = new GsonBuilder().setPrettyPrinting().create();		
  System.out.println("serialization JsonArray :: "+gson.toJson(array));					
}
/*
serialization JsonArray :: [
  {
    "blog_title": "Gson Open Source Library สำหรับจัดการ JSON Formatter",
    "blog_content": "“JSON” ไม่ใช่เรื่องใหม่อะไร เพราะปัจจุบันมีการ...",
    "blog_date": "04/10/2017",
    "blog_author": "poolsawat apin"
  },
  {
    "blog_title": "สร้าง Spring MVC 4 ร่วมกับ Apache Tiles 3",
    "blog_content": "สำหรับงานพัฒนาเว็บไซต์ด้วยภาษา Java มี Frameworks ให้เลือกอย่างมากมาย ...",
    "blog_date": "15/09/2017",
    "blog_author": "poolsawat apin"
  }
]
*/

เหล่านี้เป็นตัวอย่างการใช้งานเบื้องต้นส่วนหนึ่งเท่านั้น มี UserGuide ให้ได้เข้าไปดูวิธีการใช้งานเพิ่มเติมได้

Interceptors คืออะไร ใช้งานอย่างไรใน Spring MVC

สำหรับนักพัฒนาเว็บไซต์ สาย Java น่าจะคุ้นเคยกับคำว่า Spring Framwork มันก็คือเครื่องมือพัฒนาโปรแกรมที่ต้องทำตามกรอบการทำงาที่กำหนดให้ โดยตัว Spring Framework นี้ก็มีแบ่งแยกออกเป็นหลายรูปแบบ 1 ใน นั้นก็คือ Spring MVC ที่เป็นเครื่องมือการสร้างเว็บ ที่รองรับแนวคิดแบบ MVC (Model , View ,Controller)

ถ้ามาพูดถึงเรื่องการพัฒนาเว็บที่มีการติดต่อระหว่าง Client กับ Server จึงต้องติดต่อผ่านการ Http Request คือสิ่งที่ Client เรียกร้องขอไปที่ฝั่ง Server และเมื่อฝั่ง Server ได้รับแล้วจะส่ง Http Response ตอบกลับมายังฝั่ง Client

ถ้าสมมติสร้างเว็บไซต์มา 1 เว็บด้วย Spring MVC และอยากจะตรวจสอบ Authentication การเข้าใช้งานแต่ละหน้าเว็บเราจะให้ไปใส่ Code การตรวจสอบสิทธิทุก ๆ หน้าเว็บมันก็เพิ่มงานของนักพัฒนาไปอีก และถ้ามันจะมีสิ่งที่จะสามารถเอา Code การตรวจสอบสิทธินี้มาไว้ที่เดียวได้มันก็จะดีไม่ใช่น้อย

Interceptors คืออะไร

Interceptor คือ Feature นึงใน Spring MVC ทำหน้าที่ดัก HTTP Request  ที่เข้ามาก่อนที่  Controller ทำงานและหลัง Controller ทำงานได้

ตัวอย่างขั้นตอนการใช้งาน

สร้าง Class ExampleIntercepter เพื่อ implements AsyncHandlerInterceptor ขึ้นมา 1 Class

package xxx.xxxx.xxxxx;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class ExampleIntercepter implements AsyncHandlerInterceptor {

	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception {
		// TODO Auto-generated method stub

	}

	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception {
		// TODO Auto-generated method stub

	}

	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
		// TODO Auto-generated method stub
		// ******** Insert Code Check Before Load Web Page ***********
		return false;
	}

}

interceptors

  • preHandle จะถูกเรียกก่อนที่ Controller ทำงาน
  • postHandle จะถูกเรียกหลังจาก Controller ทำงาน
  • afterCompletion  จะถูกเรียกหลังจาก Render View เรียบร้อยแล้ว

จากนั้นเพิ่มโค๊ด เข้าไปในไฟล์ dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-4.0.xsd
 http://www.springframework.org/schema/mvc
 http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

	<context:component-scan base-package="xxx.xxxx.xxxxx" />

	<mvc:resources mapping="/asset/**" location="/asset/"
		cache-period="10000" />
	<mvc:default-servlet-handler />

	<tx:annotation-driven />

	<bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix">
			<value>/jsp/</value>
		</property>
		<property name="suffix">
			<value>.jsp</value>
		</property>
	</bean>
        
        <!-- Code interceptors config-->
	<mvc:interceptors>
		<mvc:interceptor>                           
			<mvc:mapping path="/**" />
			<bean class="xxx.xxxx.xxxxx.ExampleIntercepter" />
		</mvc:interceptor>
	</mvc:interceptors>
</beans>

options เพิ่มเติม กรณีไม่ต้องการให้ Request บางตัวไม่ต้องผ่าน intercepter นี้ก็สามารถระบุได้

<mvc:interceptors>
		<mvc:interceptor>                           
			<mvc:mapping path="/**" />
			<mvc:exclude-mapping path="/login**" />
			<mvc:exclude-mapping path="/checkLogin**" />
			<bean class="xxx.xxxx.xxxxx.ExampleIntercepter" />
		</mvc:interceptor>
</mvc:interceptors>

จากโค๊ดนี้ต้องการให้ url ที่เรียกเข้า controller ที่มีชื่อ ด้วยคำว่า login,checkLogin สามารถผ่านไปได้ ไม่ต้องเข้า Intercepter




Maven คืออะไร และการติดตั้งเพื่อใช้งานเบื้องต้น

maven logo

 

Maven คืออะไร
Maven เป็นเครื่องมือการบริหารจัดการโครงการซอฟแวร์ เอาง่ายๆก็เอาไว้จัดการ library ที่ใช้กับโปรเจคอันใหญ่โตมโหฬารของเรา ซึ่งเจ้า maven เนื่ยจะเข้ามาช่วยจดการควบคุม Library เช่นการจะเพิ่ม Library ลบ หรือแม้แต่จะแก้ไข (การจะเพิ่มเวอร์ชั่น หรือ ลดเวอร์ชั่น ของ Library) ก็สามารถทำได้ง่ายมาก โดยไม่ต้องไปตามหาโหลดไฟล์ Library จากเว็บ ต้องนำไฟล์นั้นมา Extract File ย้ายไฟล์เข้ามาในโปรเจค แล้วสุดท้ายยังต้องมากำหนด path file ให้โปรเจคของเรารู้จักว่าไฟล์อยู่ที่ไหน ซึ่งนั้นมันยุ่งยากซึ่งกว่าจะใช้งานได้ก็ใช้เวลาไปหลายนาที แต่ทำไมเรายังต้องใช้วิธีการแบบนั้นอยู่ละ ลองมาดูกันว่า maven มาช่วยเราได้อย่างไร

ขั้นตอนการติดตั้งเบื่องต้น

1. ขั้นแรกเราก็เข้าไปเว็บ Maven กันก่อน เปิดเว็บ Maven ซึ่ง Maven เป็นการพัฒนาของค่าย Apache ทีมพัฒนา Open Source อยู่แล้ว

1.1 กด เมนู Download แล้วจะเจอหน้านี้

maven_1

1.2 โหลดมาแล้ว Extract File ซะ

maven_3

2.  ไปจัดการ Environment เพื่อให้เครื่องเรารู้จักกับ Maven

2.1 คลิกขวาที่ My computer

2.2 เลือก Properties

2.3 เลือก Advance System Setting

maven_4

 

2.4 เลือก Environment Variable…

maven_5

 

2.5 กด New ใส่ Valiable name : M2_HOME ,Variable value : ..path..\apache-maven-3.3.3

maven_6

 

2.6 set path โดยเพิ่ม

%M2_HOME\bin%

เข้าไป

maven_7

 

2.6.1 กดปุ่ม OK

3.  ตรวจสอบการตั้งค่า เปิด command line ขึ้นมา พิมพ์

mvn --version

 

maven_8

 

3.1  ใครตรวจสอบแล้วขึ้นข้อมูลมาแสดงว่าติดตั้งและตั้งค่าสำเร็จ

 

ตอนต่อไปเราจะมาเริ่มการใช้งาน Maven กับ Tools Eclipse กันและติดตามกันนะครับ