Tạo Rest API với spring boot + mysql + jpa repository

Giới thiệu

Spring Boot là một dự án khá nổi bật trong hệ sinh thái Spring Framework. Nếu như trước đây, công đoạn khởi tạo một dự án Spring khá vất vả từ việc khai báo các dependency trong file pom.xml cho đến cấu hình bằng XML hoặc annotation phức tạp, tạo server cấu hình spring mvc, thì giờ đây với Spring Boot, chúng ta có thể tạo dự án Spring một cách nhanh chóng và cấu hình cũng đơn giản hơn. Dưới đây là một số tính năng nổi bật của Spring Boot:

  • Tạo các ứng dụng Spring độc lập
  • Nhúng trực tiếp Tomcat, Jetty hoặc Undertow (không cần phải deploy ra file WAR)
  • Các starter dependency giúp việc cấu hình Maven đơn giản hơn
  • Tự động cấu hình Spring khi cần thiết
  • Không sinh code cấu hình và không yêu cầu phải cấu hình bằng XML ... Sau đây mình sẽ hướng dẫn tạo 1 project Rest CRUD đơn giản với eclipse

Tạo dự án

New -> Project và điền thông tin cấu hình sau, thay đổi theo ý bạn

Service URL http://start.spring.io
Group : com.example
Artifact : easy-notes
Name : demo
Description : Rest API Demo
Package Name : com.example.demo
Packaging : jar ( default value)
Java Version : 1.8 (Default)
Dependencies : Web, JPA, MySQL,

ta sẽ tạo cấu trúc dự án như thế này

src/main/java
----com.example.demo
--------DemoApplication.java
----com.example.demo.api
--------RestApiController.java
----com.example.demo.model
--------Contact.java
----com.example.demo.service
--------ContactService.java

đây là nội dùng file pom.xml

<?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.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.M6</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

	<pluginRepositories>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>


</project>

ta có thể thêm thư viện vào thông qua các dependency, spring boot sẽ tự động down và cấu hình cần thiết cho các dependency

DemoApplication

đây là điểm chính của ứng dụng spring boot

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages={"com.example.demo"})
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

Annotation @SpringBootApplication là sự kết hợp giữa 3 annotation khác của Spring bao gồm: @SpringBootConfiguration, @ComponentScan, @EnableAutoConfiguration trong đó annotation @EnableAutoConfiguration là quan trọng nhất. Với @EnableAutoConfiguration annotation, Spring Boot sẽ tự động cấu hình cho ứng dụng của chúng ta dựa vào classpath, các annotations và các thông tin cấu hình mà chúng ta định nghĩa. Tất cả các annotation này sẽ giúp cho Spring Boot có thể tự động cấu hình cho ứng dụng của chúng ta một cách phù hợp và chúng ta không cần phải quan tâm nhiều đến việc cấu hình.

Tạo database và cấu hình kết nối mysql

CREATE TABLE `contact` (
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `age` int(3) NOT NULL,
  `dob` date NOT NULL,
  `email` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `contact`
  ADD PRIMARY KEY (`id`);
ALTER TABLE `contact`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;COMMIT;

INSERT INTO `contact` (`name`, `age`, `dob`, `email`, `id`) VALUES
('Nguyễn văn B', 22, '2017-11-15', '[email protected]', 2),
('Nguyễn A', 22, '2017-11-01', '[email protected]', 3),
('Nguyễn C', 23, '1997-11-01', '[email protected]', 4),
('Test', 25, '1990-11-01', '[email protected]', 5);

config

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=

Chúng ta cấu hình kết nối với database trong file application.properties nhớ thay đổi username, password, database cho phù hợp

Tạo Model

package com.example.demo.model;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Contact implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String name;
	private Date dob;
	private int age;
	private String email;
	@Id
    @GeneratedValue(strategy=GenerationType.AUTO)
	private long id;
	public Contact(){
		id=0;
	}
	public Contact(int id,String name, int age, Date dob, String email) {
		this.id =id;
		this.name =name;
		this.age =age;
		this.dob =dob;
		this.email =email;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Date getDob() {
		return dob;
	}
	public void setDob(Date dob) {
		this.dob = dob;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	} 
}

các model sẽ phải dùng annotation @Entity Ta có thể dùng các annnotation khác thiết lập: @Table(name = "nametable") trên model để thay đổi tên table muốn mapping @ID : xác định khóa chính @GeneratedValue : tự động tăng @NotBlank :không được null hoặc rỗng @Column(nullable = false, updatable = false) cấu hình column như khai báo @Temporal(TemporalType.TIMESTAMP) chuyển đổi giá trị data từ java sang database cho hợp lí @JsonIgnoreProperties đây là 1 annotaion của jackson . Spring Boot sử dụng Jackson for Serializing and Deserializing đối tượng java từ JSON. ... để trên các thuộc tính của model ,để cấu hình tương ứng cho từng annotation

Tạo service sử dụng JpaRepository để truy cập dữ liệu từ database

package com.example.demo.service;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.model.Contact;

@Repository
public interface ContactService extends JpaRepository<Contact, Long>{
	//todo
}

Spring Data JPA đã làm mọi việc cho chúng ta. JpaRepository interface đã định nghĩa các method cho các hoạt đông CRUD trên entity. Việc của chúng ta là extend JpaRepository , không cần phải implement lại các methods Vạ chúng ta có thể sử dụng các function như save(), getOne(), findAll(), count(), delete() ...

Tạo controller

package com.example.demo.api;

import java.util.List;

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.model.Contact;
import com.example.demo.service.ContactService;

@RestController
@RequestMapping("/api")
public class RestApiController {
	public static Logger logger = LoggerFactory.getLogger(RestApiController.class);
	
	@Autowired
	ContactService contactService;
	
	@RequestMapping(value = "/contact/", method = RequestMethod.GET)
	public ResponseEntity<List<Contact>> listAllContact(){
		List<Contact> listContact= contactService.findAll();
		if(listContact.isEmpty()) {
			return new ResponseEntity(HttpStatus.NO_CONTENT);
		}
		return new ResponseEntity<List<Contact>>(listContact, HttpStatus.OK);
	}
	
	@RequestMapping(value = "/contact/{id}", method = RequestMethod.GET)
	public Contact findContact(@PathVariable("id") long id) {
		Contact contact= contactService.getOne(id);
		if(contact == null) {
			ResponseEntity.notFound().build();
		}
		return contact;
	}
	
	@RequestMapping(value = "/contact/", method = RequestMethod.POST)
	public Contact saveContact(@Valid @RequestBody Contact contact) {
		return contactService.save(contact);
	}
	
	@RequestMapping(value = "/contact/", method = RequestMethod.PUT)
	public ResponseEntity<Contact> updateContact(@PathVariable(value = "id") Long contactId, 
	                                       @Valid @RequestBody Contact contactForm) {
		Contact contact = contactService.getOne(contactId);
	    if(contact == null) {
	        return ResponseEntity.notFound().build();
	    }
	    contact.setName(contactForm.getName());
	    contact.setAge(contactForm.getAge());

	    Contact updatedContact = contactService.save(contact);
	    return ResponseEntity.ok(updatedContact);
	}
	
	@RequestMapping(value = "/contact/{id}", method = RequestMethod.DELETE)
	public ResponseEntity<Contact> deleteContact(@PathVariable(value = "id") Long id) {
		Contact contact = contactService.getOne(id);
	    if(contact == null) {
	        return ResponseEntity.notFound().build();
	    }

	    contactService.delete(contact);
	    return ResponseEntity.ok().build();
	}
}

@RestController annotation là sự kết hợp giữa @Controller@ResponseBody annotations. @RequestMapping("/api") để khai báo các url của các api trong controller này sẽ bắt đầu với '/api'

lấy tất cả liên hệ

@RequestMapping(value = "/contact/", method = RequestMethod.GET)
	public ResponseEntity<List<Contact>> listAllContact(){
		List<Contact> listContact= contactService.findAll();
		if(listContact.isEmpty()) {
			return new ResponseEntity(HttpStatus.NO_CONTENT);
		}
		//return ResponseEntity<List<Contact>>(listContact, HttpStatus.OK);
		return new ResponseEntity<List<Contact>>(listContact, HttpStatus.OK);
	}

ở đây nó sẽ gọi method findAll() của JpaRepository trả về toàn bộ danh sách liên hệ có trong database method: khai báo phương thức khi call url, GET, PUT, PATCH, POST,....

tạo mới liên hệ

@RequestMapping(value = "/contact/", method = RequestMethod.POST)
	public Contact saveContact(@Valid @RequestBody Contact contact) {
		return contactService.save(contact);
	}

@RequestBody tương ứng với body của request @Valid để đảm bảo request body là hợp lệ nếu ta dùng các annotation validation như @NotBlank...

lấy một liên hệ

@RequestMapping(value = "/contact/{id}", method = RequestMethod.GET)
	public Contact findContact(@PathVariable("id") long id) {
		Contact contact= contactService.getOne(id);
		if(contact == null) {
			ResponseEntity.notFound().build();
		}
		return contact;
	}

@PathVariable mapping biến ở đường dẫn với biến tham số gọi method getOne() để lấy dữ liệu thông qua đối số

update liên hệ

@RequestMapping(value = "/contact/", method = RequestMethod.PUT)
	public ResponseEntity<Contact> updateContact(@PathVariable(value = "id") Long contactId, 
	                                       @Valid @RequestBody Contact contactForm) {
		Contact contact = contactService.getOne(contactId);
	    if(contact == null) {
	        return ResponseEntity.notFound().build();
	    }
	    contact.setName(contactForm.getName());
	    contact.setAge(contactForm.getAge());

	    Contact updatedContact = contactService.save(contact);
	    return ResponseEntity.ok(updatedContact);
	}

Xóa liên hệ

@RequestMapping(value = "/contact/{id}", method = RequestMethod.DELETE)
	public ResponseEntity<Contact> deleteContact(@PathVariable(value = "id") Long id) {
		Contact contact = contactService.getOne(id);
	    if(contact == null) {
	        return ResponseEntity.notFound().build();
	    }

	    contactService.delete(contact);
	    return ResponseEntity.ok().build();
	}

Chạy ứng dụng

có thể chạy ứng dụng bằng 2 cách

Click chuột phải vào Project -> Run as -> Spring Boot App

or vào thư mục gốc projcet chạy bằng command line

mvnw spring-boot:run

Test

Ta dùng Postman để test lấy 1 liên hệ http://localhost:8080/api/contact/{id} method GET lấy tất cả liên hệ http://localhost:8080/api/contact/ method GET tạo mới 1 liên hệ http://localhost:8080/api/contact/ method POST update 1 liên hệ http://localhost:8080/api/contact/{id} method PUT