[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 นี้ลงที่ฐานข้อมูลครับ

<!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 sent to your email.