การสร้างแอปพลิเคชัน MVC ด้วย Spring Framework: บทช่วยสอนสำหรับผู้เริ่มต้น

เผยแพร่แล้ว: 2022-03-11

มักกล่าวกันว่า Java ซับซ้อนเกินไป และใช้เวลานานเกินไปในการสร้างแอปพลิเคชันอย่างง่าย อย่างไรก็ตาม Java นำเสนอแพลตฟอร์มที่เสถียรพร้อมระบบนิเวศที่สมบูรณ์รอบด้าน ซึ่งทำให้เป็นตัวเลือกที่ยอดเยี่ยมสำหรับการพัฒนาซอฟต์แวร์ที่มีประสิทธิภาพ

Spring Framework ซึ่งเป็นหนึ่งในเฟรมเวิร์กที่ทรงพลังในระบบนิเวศ Java มาพร้อมกับคอลเลกชันของโมเดลการเขียนโปรแกรมและการกำหนดค่าโดยมีเป้าหมายเพื่อลดความซับซ้อนในการพัฒนาแอพพลิเคชั่นที่มีประสิทธิภาพและทดสอบได้ใน Java

Spring Framework

ในบทช่วยสอนนี้ เราจะใช้ความท้าทายในการสร้างแอปพลิเคชันอย่างง่ายที่จะทำหน้าที่เป็นฐานข้อมูลของนักพัฒนาซอฟต์แวร์โดยใช้ Spring Framework และ Java Persistence API (JPA)

แอปพลิเคชันเป็นไปตามสถาปัตยกรรม MVC มาตรฐาน โดยจะมีคอนโทรลเลอร์ (คลาส ContractsController) มุมมอง (ตามเทมเพลต Thymeleaf) และโมเดล (อ็อบเจ็กต์แผนที่ Java) เพื่อความง่าย เราจะใช้ฐานข้อมูลในหน่วยความจำที่อยู่เบื้องหลัง JPA เพื่อยืนยันข้อมูลในขณะที่แอปพลิเคชันกำลังทำงาน

เริ่มต้นใช้งาน Spring Framework Tutorial

ในการสร้างแอปพลิเคชันที่ใช้ Spring เราจำเป็นต้องใช้หนึ่งในเครื่องมือสร้างต่อไปนี้:

  • Maven
  • Gradle

ในบทช่วยสอนนี้ เราจะใช้ Maven หากคุณไม่คุ้นเคยกับเครื่องมือเหล่านี้ วิธีง่ายๆ ในการเริ่มต้นคือดาวน์โหลด Spring Tool Suite ชุดนี้มีไว้สำหรับ Spring Framework และมาพร้อมกับ IDE ที่ใช้ Eclipse ของตัวเอง

ใน Spring Tool Suite เราสร้างโปรเจ็กต์ใหม่โดยเลือก “Spring Starter Project” จากใต้เมนู “ไฟล์ > ใหม่”

กวดวิชา Spring Framework

เมื่อสร้างโปรเจ็กต์ใหม่แล้ว เราจะต้องแก้ไขไฟล์คอนฟิกูเรชัน Maven, “ pom.xml ” และเพิ่มการพึ่งพาต่อไปนี้:

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-commons</artifactId> </dependency>

การขึ้นต่อกันที่อยู่ในรายการเหล่านี้จะโหลด Spring Boot Web, Thymeleaf, JPA และ H2 (ซึ่งจะทำหน้าที่เป็นฐานข้อมูลในหน่วยความจำของเรา) ไลบรารีที่จำเป็นทั้งหมดจะถูกดึงโดยอัตโนมัติ

คลาสเอนทิตี

เพื่อให้สามารถเก็บข้อมูลเกี่ยวกับนักพัฒนาและทักษะของพวกเขาได้ เราจะต้องกำหนดเอนทิตีสองคลาส: “ Developer ” และ “ Skill

ทั้งสองสิ่งนี้ถูกกำหนดให้เป็นคลาส Java ธรรมดาพร้อมคำอธิบายประกอบบางส่วน การเพิ่ม “@Entity” ก่อนคลาส ทำให้อินสแตนซ์พร้อมใช้งานใน JPA ซึ่งจะทำให้ง่ายต่อการจัดเก็บและเรียกข้อมูลอินสแตนซ์จากที่เก็บข้อมูลถาวรเมื่อจำเป็น นอกจากนี้ คำอธิบายประกอบ “@Id” และ “@GeneratedValue” ยังช่วยให้เราสามารถระบุฟิลด์ ID เฉพาะสำหรับเอนทิตีและมีค่าสร้างโดยอัตโนมัติเมื่อจัดเก็บไว้ในฐานข้อมูล

เนื่องจากนักพัฒนาสามารถมีทักษะมากมาย เราสามารถกำหนดความสัมพันธ์แบบกลุ่มต่อกลุ่มอย่างง่ายโดยใช้คำอธิบายประกอบ “@ManyToMany”

นักพัฒนา

 @Entity public class Developer { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String firstName; private String lastName; private String email; @ManyToMany private List<Skill> skills; public Developer() { super(); } public Developer(String firstName, String lastName, String email, List<Skill> skills) { super(); this.firstName = firstName; this.lastName = lastName; this.email = email; this.skills = skills; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public List<Skill> getSkills() { return skills; } public void setSkills(List<Skill> skills) { this.skills = skills; } public boolean hasSkill(Skill skill) { for (Skill containedSkill: getSkills()) { if (containedSkill.getId() == skill.getId()) { return true; } } return false; } }

ทักษะ

 @Entity public class Skill { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String label; private String description; public Skill() { super(); } public Skill(String label, String description) { super(); this.label = label; this.description = description; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }

ที่เก็บ

ด้วย JPA เราสามารถกำหนดอินเทอร์เฟซ DeveloperRepository ที่มีประโยชน์มากและอินเทอร์เฟซ SkillRepository ซึ่งช่วยให้ดำเนินการ CRUD ได้ง่าย อินเทอร์เฟซเหล่านี้จะช่วยให้เราเข้าถึงนักพัฒนาและทักษะที่เก็บไว้ผ่านการเรียกใช้เมธอดง่ายๆ เช่น:

  • “respository.findAll()”: ส่งคืนนักพัฒนาทั้งหมด
  • “repository.findOne(id)”: ส่งคืนผู้พัฒนาพร้อม ID ที่กำหนด

ในการสร้างอินเทอร์เฟซเหล่านี้ สิ่งที่เราต้องทำคือขยายอินเทอร์เฟซ CrudRepository

ที่เก็บนักพัฒนา

 public interface DeveloperRepository extends CrudRepository<Developer, Long> { }

คลังเก็บทักษะ

 public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }

ฟังก์ชันการทำงานสำหรับวิธีการเพิ่มเติม " findByLabel " ที่ประกาศไว้ที่นี่จะถูกจัดเตรียมโดยอัตโนมัติโดย JPA

คอนโทรลเลอร์

ต่อไป เราสามารถทำงานกับคอนโทรลเลอร์สำหรับแอปพลิเคชันนี้ได้ ผู้ควบคุมจะจับคู่คำขอ URI เพื่อดูเทมเพลตและดำเนินการประมวลผลที่จำเป็นทั้งหมดในระหว่างนั้น

 @Controller public class DevelopersController { @Autowired DeveloperRepository repository; @Autowired SkillRepository skillRepository; @RequestMapping("/developer/{id}") public String developer(@PathVariable Long id, Model model) { model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "developer"; } @RequestMapping(value="/developers",method=RequestMethod.GET) public String developersList(Model model) { model.addAttribute("developers", repository.findAll()); return "developers"; } @RequestMapping(value="/developers",method=RequestMethod.POST) public String developersAdd(@RequestParam String email, @RequestParam String firstName, @RequestParam String lastName, Model model) { Developer newDeveloper = new Developer(); newDeveloper.setEmail(email); newDeveloper.setFirstName(firstName); newDeveloper.setLastName(lastName); repository.save(newDeveloper); model.addAttribute("developer", newDeveloper); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + newDeveloper.getId(); } @RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST) public String developersAddSkill(@PathVariable Long id, @RequestParam Long skillId, Model model) { Skill skill = skillRepository.findOne(skillId); Developer developer = repository.findOne(id); if (developer != null) { if (!developer.hasSkill(skill)) { developer.getSkills().add(skill); } repository.save(developer); model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + developer.getId(); } model.addAttribute("developers", repository.findAll()); return "redirect:/developers"; } }

การแมป URI กับเมธอดทำได้โดยใช้คำอธิบายประกอบ “@RequestMapping” อย่างง่าย ในกรณีนี้ ทุกวิธีของคอนโทรลเลอร์จะจับคู่กับ URI

พารามิเตอร์โมเดลของเมธอดเหล่านี้อนุญาตให้ส่งข้อมูลไปยังมุมมองได้ โดยพื้นฐานแล้ว สิ่งเหล่านี้คือแผนที่ง่ายๆ ของกุญแจสู่ค่า

แต่ละเมธอดของตัวควบคุมจะส่งกลับชื่อของเทมเพลต Thymeleaf เพื่อใช้เป็นมุมมอง หรือ URL ในรูปแบบเฉพาะ (“เปลี่ยนเส้นทาง: ”) เพื่อเปลี่ยนเส้นทางไปที่ ตัวอย่างเช่น เมธอด “developer” และ “_developersList_” จะคืนค่าชื่อของเทมเพลต ในขณะที่ “developersAdd” และ “developersAddSkill” ส่งคืน URL ที่จะเปลี่ยนเส้นทางไป

ภายในคอนโทรลเลอร์ คำอธิบายประกอบ “@Autowired” จะกำหนดอินสแตนซ์ที่ถูกต้องของที่เก็บที่กำหนดไว้โดยอัตโนมัติในฟิลด์ที่เกี่ยวข้อง ซึ่งช่วยให้สามารถเข้าถึงข้อมูลที่เกี่ยวข้องจากภายในคอนโทรลเลอร์โดยไม่ต้องจัดการกับโค้ดสำเร็จรูปจำนวนมาก

มุมมอง

สุดท้าย เราต้องกำหนดเทมเพลตสำหรับสร้างมุมมอง สำหรับสิ่งนี้ เราใช้ Thymeleaf ซึ่งเป็นเอ็นจิ้นการสร้างเทมเพลตอย่างง่าย โมเดลที่เราใช้ในวิธีการควบคุมมีให้โดยตรงในเทมเพลต เช่น เมื่อเราป้อนสัญญาลงในคีย์ " สัญญา " ในโมเดล เราจะสามารถเข้าถึงฟิลด์ชื่อเป็น "contract.name" จากภายในเทมเพลตได้

Thymeleaf มีองค์ประกอบและคุณลักษณะพิเศษบางอย่างที่ควบคุมการสร้าง HTML พวกมันใช้งานง่ายและตรงไปตรงมามาก ตัวอย่างเช่น ในการเติมเนื้อหาขององค์ประกอบ span ด้วยชื่อของทักษะ สิ่งที่คุณต้องทำคือกำหนดแอตทริบิวต์ต่อไปนี้ (สมมติว่าคีย์ " ทักษะ " ถูกกำหนดไว้ในโมเดล):

 <span th:text="${skill.label}"></span>

ในทำนองเดียวกัน ในการตั้งค่าแอตทริบิวต์ “ href ” ขององค์ประกอบสมอ สามารถใช้แอตทริบิวต์พิเศษ “ th:href ” ได้

ในใบสมัครของเรา เราจำเป็นต้องมีเทมเพลตอย่างง่ายสองแบบ เพื่อความชัดเจน เราจะข้ามแอตทริบิวต์สไตล์และคลาสทั้งหมด (เช่น Bootstrap) ที่นี่ในโค้ดเทมเพลตที่ฝังไว้

รายชื่อนักพัฒนา

 <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developers database</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developers</h1> <table> <tr> <th>Name</th> <th>Skills</th> <th></th> </tr> <tr th:each="developer : ${developers}"> <td th:text="${developer.firstName + ' ' + developer.lastName}"></td> <td> <span th:each="skill,iterStat : ${developer.skills}"> <span th:text="${skill.label}"/><th:block th:if="${!iterStat.last}">,</th:block> </span> </td> <td> <a th:href="@{/developer/{id}(id=${developer.id})}">view</a> </td> </tr> </table> <hr/> <form th:action="@{/developers}" method="post" enctype="multipart/form-data"> <div> First name: <input name="firstName" /> </div> <div> Last name: <input name="lastName" /> </div> <div> Email: <input name="email" /> </div> <div> <input type="submit" value="Create developer" name="button"/> </div> </form> </body> </html>

รายละเอียดผู้พัฒนา

 <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developer</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developer</h1> Name: <b th:text="${developer.firstName}" /> <b th:text="${developer.lastName}" /><br/> Email: <span th:text="${developer.email}" /><br/> Skills: <span th:each="skill : ${developer.skills}"> <br/>&nbsp;&nbsp;<span th:text="${skill.label}" /> - <span th:text="${skill.description}" /> </span> <form th:action="@{/developer/{id}/skills(id=${developer.id})}" method="post" enctype="multipart/form-data" > <select name="skillId"> <option th:each="skill : ${skills}" th:value="${skill.id}" th:text="${skill.description}">Skill</option> </select> <input type="submit" value="Add skill"/> </form> </body> </html>

เรียกใช้เซิร์ฟเวอร์

สปริงมีโมดูลการบูต ซึ่งช่วยให้เราสามารถเริ่มต้นเซิร์ฟเวอร์ได้อย่างง่ายดายจากบรรทัดคำสั่งเป็นแอปพลิเคชัน Java ของบรรทัดคำสั่ง:

 @SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

เนื่องจากเราใช้ฐานข้อมูลในหน่วยความจำ จึงควรบูตสแตรปฐานข้อมูลด้วยข้อมูลที่กำหนดไว้ล่วงหน้าบางส่วนเมื่อเปิดใช้งาน ด้วยวิธีนี้เราจะมีข้อมูลอย่างน้อยบางส่วนในฐานข้อมูลเมื่อเซิร์ฟเวอร์เริ่มทำงาน

 @Override public void run(String... args) throws Exception { Skill javascript = new Skill("javascript", "Javascript language skill"); Skill ruby = new Skill("ruby", "Ruby language skill"); Skill emberjs = new Skill("emberjs", "Emberjs framework"); Skill angularjs = new Skill("angularjs", "Angularjs framework"); skillRepository.save(javascript); skillRepository.save(ruby); skillRepository.save(emberjs); skillRepository.save(angularjs); List<Developer> developers = new LinkedList<Developer>(); developers.add(new Developer("John", "Smith", "[email protected]", Arrays.asList(new Skill[] { javascript, ruby }))); developers.add(new Developer("Mark", "Johnson", "[email protected]", Arrays.asList(new Skill[] { emberjs, ruby }))); developers.add(new Developer("Michael", "Williams", "[email protected]", Arrays.asList(new Skill[] { angularjs, ruby }))); developers.add(new Developer("Fred", "Miller", "[email protected]", Arrays.asList(new Skill[] { emberjs, angularjs, javascript }))); developers.add(new Developer("Bob", "Brown", "[email protected]", Arrays.asList(new Skill[] { emberjs }))); developerRepository.save(developers); }

บทสรุป

Spring เป็นเฟรมเวิร์กเอนกประสงค์ที่อนุญาตให้สร้างแอปพลิเคชัน MVC การสร้างแอปพลิเคชันอย่างง่ายด้วย Spring นั้นรวดเร็วและโปร่งใส แอปพลิเคชันยังสามารถรวมเข้ากับฐานข้อมูลได้อย่างง่ายดายโดยใช้ JPA

ซอร์สโค้ดของโครงการทั้งหมดนี้มีอยู่ใน GitHub