[SPRING] สร้าง Form Upload ไฟล์ และบันทึกข้อมูลครับ

หลังจากโดนมรสุมงานหนักมาตั้งแต่ต้นปีครับ กว่าจะได้กลับบ้านที่ก็ปาไป 3 ทุ่มกว่าๆ ถึง 4 ทุ่มและ เริ่มกลับมาทำ Thesis สักทีครับ สำหรับโจทย์ที่ผมเอามาเขียน Blog ครั้งนี้เป็นการ Upload File จากนั้น Save ที่ไฟล์ที่ Server ครับ พร้อมกับบันทึกข้อมูลที่ป้อนเข้ามาจาก User ครับ

เตรียมตัว

  • Tools พร้อม Code พร้อมครับ สำหรับผมใช้ Code ที่ตั้งโครงจาก Blog ก่อนๆมาพัฒนาต่อครับ

Just Coding ครับ

  • กำหนด Dependency ที่เกี่ยวข้องกันก่อนในไฟล์ pom.xml ครับ
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
       <groupId>nz.net.ultraq.thymeleaf</groupId>
       <artifactId>thymeleaf-layout-dialect</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <scope>runtime</scope>
    </dependency>
    
  • มากำหนด application.properties ครับ
    #เปิด Option การใช้ Multipart
    spring.http.multipart.enabled=true 
    #กำหนด Part ที่ Upload File
    bpmn.upload.path = D:\\BPMN\\99Web\\WuMuBPMN\\src\\main\\resources\\static\\BPMNUpload
    #กำหนดรูปแบบไฟล์ที่สามารถ Upload ได้
    bpmn.upload.extensions=bpmn
  • กำหนด Entity ครับ
    @Entity
    public class testItem{
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private int testItemId;
        //บอก Path ที่เก็บไฟล์ BPMN
        private String testItemPath;
        private String testItemName;
        @Transient
        private MultipartFile BPMNFile;
        
        //พวก getter และ setter ผมขอละไว้นะครับ
    }
    • สังเกตุดีๆ ผมมี Property ที่เกี่ยวกับ Multipart ชื่อ BPMNFile ครับ โดย Property นี้ ผมกำหนดเป็น @Transient  ครับ ซึ่งเป็นตัวที่บอก Hibernate ว่าไม่ต้องสร้าง Column นี้ลงที่ฐานข้อมูลครับ
  • สร้าง View ที่เกี่ยวข้องครับ - อันนี้ผมใช้ Thymeleaf Layout ครับ (สามารถศึกษาเพิ่มเติมได้จาก Blog ผมครับ อิอิ) แต่ผมใน Blog นี้ผมใส่เฉพาะ View ครับ
    <!DOCTYPE html>
    <html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" 
          layout:decorator="layouts/mainLayout">
    <head>
        <title>Layout</title>
    </head>
    <body>
        <div layout:fragment="content">
            <h2>Test Item Detail</h2>
            <div>
                <form class="form-horizontal" th:object="${testitem}" th:action="@{/testitem}" method="post" enctype="multipart/form-data">
                    <!-- <input type="hidden" th:field="*{testitemid}"/> -->
                    <div class="form-group">
                        <label class="col-sm-2 control-label">test item name:</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" th:field="*{testItemName}"/>
                            <label th:if="${#fields.hasErrors('testItemName')}" th:class="'error'">test Item Name Error</label>
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="col-sm-2 control-label">test item file:</label>
                        <div class="col-sm-10">
                            <input type="file" class="form-control"  th:field="*{BPMNFile}"/>
                            <label th:if="${#fields.hasErrors('testItemPath')}" th:class="'error'">test Item Path Error</label>
                        </div>
                        <label class="col-sm-2 control-label">test item path:</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" th:field="*{testItemPath}" readonly = true/>
                            <label th:if="${#fields.hasErrors('testItemPath')}" th:class="'error'">test Item Path Error</label>
                        </div>
                    </div>
                    <div class="form-group">
                        <button type="submit" class="btn btn-default">Submit</button>
                    </div>
                </form>
            </div>
        </div>
    </body>
    </html>
    • สังเกตุในส่วนของ <input type="file" class="form-control" th:field="*{BPMNFile}"/>  ครับ ในส่วนของ th:field="*{BPMNFile}"  ครับ ตรง BPMNFile จะสัมพันธ์กับในส่วนของ Entity ครับ ^__^
  • สร้าง Controller กันครับ มีครบครันสำหรับ CRUD Operation ไปดู Code ได้เลยครับ
    package com.cu.thesis.WuMuBPMN.controllers.manageTest;
    
    //ส่วนขอการ Import เชื่อใจใน Editor ครับ
    
    @Controller
    public class testItemController
    {
        //ดึงค่ามาจาก application.properties
        @Value("${bpmn.upload.path}")     
        private String uploadPath;
        @Value("${bpmn.upload.extensions}")  
        private String bpmnExtension;
    
        @Autowired
        private testItemService testItemServiceI;
    
       
        /*
         * ดึงข้อมูล TEst Case ทั้งหมด
         */
        @RequestMapping(value = "testitems", method = RequestMethod.GET)
        public String listTestItem(Model model)
        {
            model.addAttribute("testitems", testItemServiceI.listAll());
            System.out.println("Returning testitems:");
            return "manageTest/testItemList";
        }
    
        /**
         * ดูข้อมูล Test Item
         */
        @RequestMapping(value = "testitem/{id}")
        public String viewTestItem(@PathVariable Integer id, Model model)
        {
            model.addAttribute("testitem", testItemServiceI.getById(id));
            return "manageTest/viewTestItem";
        }
    
        /**
         * Edit Entry สำหรับ Edit
         */
        @RequestMapping(value = "testitem/edit/{id}")
        public String editPerson(@PathVariable Integer id, Model model) {
            model.addAttribute("testitem", testItemServiceI.getById(id));
            return "manageTest/editTestItem.html";
        }
    
        /**
         * new Entity สำหรับ New
         */
        @RequestMapping(value = "testitem/new")
        public String newTestItem(Model model)
        {
            model.addAttribute("testitem", new testItem());
            return "manageTest/editTestItem.html";
        }
    
        /**
         * บันทึกข้อมูลลง DB
         */
        @RequestMapping(value ="testitem", method = RequestMethod.POST)
        public String saveTestItem(@Valid testItem testItemEntry
                                 , BindingResult result)
        {
            //จัดการ File
            String FilePath;
            try {
                if(result.hasErrors()) {
                    return "manageTest/editTestItem.html";
                }
                FilePath = SaveBPMNFile(testItemEntry.getBPMNFile(), uploadPath);
                testItemEntry.setTestItemPath(FilePath);
                testItemServiceI.save(testItemEntry);
            } 
            catch (IOException e) {
                e.printStackTrace();
            } 
            catch (FileUploadException e)
            {
                e.printStackTrace();
            }
            return "redirect:/testitems";
        }
    
        protected String SaveBPMNFile(MultipartFile file, String uploadDirectory) throws IOException, FileUploadException
        {
          String fileName = file.getOriginalFilename();
          CheckValidFileExtension(file);
          Path path = Paths.get(uploadDirectory, fileName);
          Files.copy(file.getInputStream(), path);
    
          return uploadDirectory +"/"+ fileName;
        }
    
        protected void CheckValidFileExtension(MultipartFile file) throws FileUploadException
        {
            String extension = FilenameUtils.getExtension(file.getOriginalFilename());
            if (!extension.equals(bpmnExtension))
            {
                throw new FileUploadException("Not valid BPMN Extension");
            }
        }
    
        @RequestMapping(value ="testitem/delete/{id}")
        public String deleteTestItem(@PathVariable Integer id) throws IOException
        {
            //ลบแล้ว ต้องเอาออกให้เกลี้ยงนะ
            testItem testItemEntry = testItemServiceI.getById(id);
            Files.deleteIfExists(Paths.get(testItemEntry.getTestItemPath()));
            testItemServiceI.delete(id);
            //กลับไปที่หน้าจอ List
            return "redirect:/testitems";
        }
    }
  • ส่วน Service / DAO ใช้จาก CRUDRepository ตามปกติครับ ผมเลยไม่ใส่ Code นะครับ
  • หน้าตาของระบบ (ผมไม่มีหัวการออกแบบเลยครับ 555)
    • หน้า List
    • ตอน Edit ครับ

ปัญหาที่พบหละ ?

  • ปัญหา Exception: MultipartException: Current request is not a multipart request
    แก้ไขโดย - ตรวจสอบที่ View ในส่วนของ Form ว่าลืมใส่ enctype="multipart/form-data" หรือป่าว
  • ปัญหา Required request part 'file' is not present
    แก้ไขโดย - ตรวจสอบที่ View ว่ามีการผูก Mulitipart Element อย่าง เช่น File Upload หรือ <input type="file" class="form-control" th:field="*{BPMNFile}"/>   ครับ ยังไม่ได้ผูกกับ Model (Entity) แต่ Method ใน Controller ครับ มัน Require ตัว Multipart ครับ ถ้าใน Thymeleaf เป็นส่วน th:field="*{BPMNFile}"  ครับ

 


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts to your email.