Bài viết này sẽ hướng dẫn các bạn cách kết nối ứng dụng Spring với các cơ sở dữ liệu SQL ví dụ như MySQL.

Giới thiệu

Spring Data chính là middle ware Hibernate, nếu như ngày trước khi sử dụng Spring MVC với Hibernate chúng ta sẽ phải lần lượt thêm các gói jar thư viện của riêng Hibernate vào project Spring và tiếp theo sau đó chúng ta sẽ phải cấu hinh các config của Hibernate với file persistence.xml hoặc với các bean của Spring MVC. Giờ đây công việc đó đã được rút gọn rất nhiều bằng việc chỉ cần khai báo thêm duy nhất 1 dependency spring-data và các property cấu hình vào file application.properties. Và ứng dụng Spring chúng ta đã được kết nối với Database.

Khâu Chuẩn bị

Setup môi trường

Trước khi bắt đầu vào bài hướng dẫn, các bạn cần chuẩn bị các công cụ - phần mềm sau:

Các thư viện quan trọng:

  • Spring Boot 2.0.5
  • Spring Data 2.0.5. Thư viện quan trọng để kết nối ứng dụng Spring với các Cơ Sở Dữ Liệu.
  • MySQL JDBC Connector 8.0.x

Chúng ta bắt đầu vào bài hướng dẫn.

Tạo Database bằng script 

Chúng ta cần phải tạo sẵn 1 database trong MySQL để ứng dụng Spring có thể kết nối vào. Cần lưu ý rằng thư viện Spring Data có thể hỗ trợ chúng ta tạo ra các table trong Database nhưng tạo ra Database thì không do đó chúng ta cần chuẩn bị trước 1 Database rỗng và các table sẽ được tạo khi chạy ứng dụng Spring. 

Để đơn giản, bài viết không đi sâu vào chi tiết setup MySQL trên PC mà sẽ sử dụng được tích hợp sẵn trên phần mềm XAMPP.  

Username và password mặc định là root -''

Sau khi start MySQL trên XAMPP, trên PC  service MySQL được khởi tạo và chạy trên port 3306 (mặc định). 

Tiếp theo, chúng ta dùng MySQL Workbench để kết nối vào service MySQL với Username và Password mặc định như trên.

create database testspringdata character set UTF8mb4 collate utf8mb4_bin

Trong ví dụ này database chúng ta có tên là "testspringdata". Các bạn lưu ý chúng ta nên có option define character set UTF8mb4 collate utf8mb4_bin để database của chúng ta có thể lưu được các ký tự UTF-8 mà cụ  thể ở đây là các ký tự Tiếng Việt. Tới đây chúng ta đã hoàn tất các bước chuẩn bị cho môi trường bên ngoài, và có thể bắt đầu tập trung vào ứng dụng Spring. 

Cấu trúc thư mục project

Khai báo thư viện Maven.

Như các bài hướng dẫn trước, tôi vẫn sử dụng Maven để build project. Các bước khởi tạo project Spring với Maven các bạn có thể tham khảo tại đây Spring Boot Hello World

Cấu trúc hoàn chỉnh của project:

<project xmlns="https://maven.apache.org/POM/4.0.0"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>thecoderoot</groupId>
	<artifactId>springboot_springdata</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<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>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.13</version>
		</dependency>

		<!--spring data -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!--test module -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.16.8</version>
			<scope>provided</scope>
		</dependency>

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

úng ta như sau:

 


Khai báo cấu hình kết nối CSDL

Để kết nối với một cơ sở dữ liệu bất kỳ chúng ta đều biết phải cần có Username và Password để kết nối. Chúng ta sẽ khai báo các thông tin này ở file application.properties như sau:

#url & username, password to connect to databae
spring.datasource.url = jdbc:mysql://localhost:3306/thecoderoot?useUnicode=yes&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=

#hibernate jpa
spring.jpa.hibernate.ddl-auto=update

Về bản chất Spring Data (hoặc Hibernate) cũng sử dụng JDBC để kết nối ứng dụng Spring (là ứng dụng Java) để kết nối tới các loại cơ sở dữ liệu SQL, nên ở đây chúng ta vẫn có thể thấy được sự hiện diện của đường link kết nối có format jdbc:xxx://<host>:<port>/<database-name>. Spring Boot hỗ trợ chúng ta một interface nhanh gọn để cấu hình các thông số kết nối database qua các propterty của spring.datasource.xxx

Ngoài ra, chúng ta còn có thể cấu hình cho riêng các option Hibernate.

spring.jpa.hibernate.ddl-auto=update

Option này có nghĩa là chúng ta sẽ "update" các table: Nếu table chưa tồn tại thì Spring Data sẽ tự động tạo chúng ở database, và nếu chúng đã tồn tại ở database và có sự thay đổi về cấu trúc (tên, cột, kiểu dữ liệu, ...) thì chúng sẽ được update lại. Tuy nhiên các bạn cần cẩn thận khi sử dụng  option này:  các cột đã được tạo ra sẽ không được xoá đi nếu chúng ta chỉnh lại tên cột ở class Entity và data đã tồn tại cũng sẽ không được migrate qua cột mới, mà chúng ta phải thực hiện migrate tay cho trường hợp này.

 

Entity Book

package thecoderoot.springboot.springdata.model;

import lombok.Getter;

import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "book")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "book_name")
    private String bookName;

    @Column(name = "author_name")
    private String authorName;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_ts", updatable = false)
    @Getter
    private Date createTs;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "update_ts")
    @Getter
    private Date updateTs;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getAuthorName() {
        return authorName;
    }

    public void setAuthorName(String authorName) {
        this.authorName = authorName;
    }

    public void setInfo(Book info){
        this.bookName = info.getBookName();
        this.authorName = info.getAuthorName();
    }
}
  • @Entity: Khai báo với Spring class này sẽ được kết nối với Database dưới dạng 1 table nào đó.
  • @Table(name=<table_name>): Annotation này giúp chúng ta define được chính xác tên table ở database. Nếu không define annotation này, tên table sẽ được sinh tự động theo rule của Hibernate. VD: từ class MyBook, tên table sẽ là my_book.
  • @Id: Định danh Primary Key cho table
  • @GeneratedValue(strategy = GenerationType.IDENTITY): Giá trị số tự tăng dần incremental.
  • @Column: Tương tự như @Table, annotation này giúp chúng ta define được các option cho column.

 

Repository

package thecoderoot.springboot.springdata.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import thecoderoot.springboot.springdata.model.Book;

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {

    public Book findByBookName(String name);
}

Sử dụng JpaRepository giúp chúng ta có thể define được những hàm tìm đối tương theo 1 thuộc tính nào đó. Ở đây chúng ta cần tìm đối tượng Book theo trường BookName, chúng ta chỉ cần khai báo 1 hàm như trên.  

 

Controller

package thecoderoot.springboot.springdata.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import thecoderoot.springboot.springdata.model.Book;
import thecoderoot.springboot.springdata.repository.BookRepository;

import java.util.List;

@RestController
@RequestMapping(value = "/api/book/v1")
public class RestBookController {

    @Autowired
    private BookRepository bookRepository;

    @GetMapping(value = "/")
    public List<Book> findAll(){
        return bookRepository.findAll();
    }

    @GetMapping(value = "/{id}")
    public Book findById(@PathVariable(value = "id") Long id){
        return bookRepository.getOne(id);
    }

    @PostMapping(value = "/add")
    public Book add(@RequestBody Book book){
        return bookRepository.save(book);
    }

    @PutMapping(value = "/edit")
    public Book edit(@RequestBody Book newInfoBook){
        //find book by id in database
        Book original = bookRepository.getOne(newInfoBook.getId());

        if (original != null){
            //set new info for Book and save to database
            original.setInfo(newInfoBook);
            return bookRepository.save(original);
        }

        //cannot find book by id
        return null;
    }
}

 

Main class

package thecoderoot.springboot.springdata;

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

@SpringBootApplication
public class Main {

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

Sau cùng chúng ta cần khai báo cho class Main để chạy ứng dụng Spring Boot. Và chạy ứng dụng Spring Boot.

2019-05-04 22:23:53.119  INFO 5467 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2019-05-04 22:23:53.120  INFO 5467 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'dataSource' has been autodetected for JMX exposure
2019-05-04 22:23:53.125  INFO 5467 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2019-05-04 22:23:53.164  INFO 5467 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8899 (http) with context path ''
2019-05-04 22:23:53.168  INFO 5467 --- [  restartedMain] thecoderoot.springboot.springdata.Main   : Started Main in 4.337 seconds (JVM running for 5.254)

Và table book sẽ được tạo tự động trong database với tên table cùng các cột như trong class Entity chúng ta đã define.

 

Testing

Để đảm bảo cho chương trình ở trên của chúng ta trước tiên là có thể kết nối được với Database và sau là các code logic từ lúc nhận request ở tầng Controller cho đến khi lưu dữ liệu xuống Database hoạt động đúng như theo chúng ta mong đợi thì không có cách nào hơn ngoài việc testing. Chúng ta có thể dùng nhiều phương pháp khác nhau, dùng trình duyệt, Postman, ... Ở đây trong bài ví dụ này tôi sẽ chọn phương pháp viết các đoạn test Integration cho các api tức là cho tầng Controller, làm như vậy ngoài việc test được chức năng như yêu cầu tôi còn có thể dễ dàng bảo trì các đoạn test code này như là 1 phần source code của chính chương trình. Các bạn có thể tìm lại cách setup integration test trong Spring Boot tại Spring Boot Integration Test

package thecoderoot.springboot.springdata.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
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.context.annotation.ComponentScan;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import thecoderoot.springboot.springdata.model.Book;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ComponentScan(basePackages = {"thecoderoot.springboot.springdata"})
public class RestBookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    private static final String API_URL = "/api/book/v1";

    @Test
    public void testInsertBook() throws Exception {
        Book book = new Book();
        book.setBookName("Harry Potter");
        book.setAuthorName("J. K. Rowling");
        String jsonEntity = new ObjectMapper().writeValueAsString(book);

        mockMvc.perform(MockMvcRequestBuilders.post(API_URL + "/add")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(jsonEntity))
                .andExpect(MockMvcResultMatchers.status().isOk());

    }
}

Và chúng ta kiểm tra database:

Đến đây chúng ta đã có thể kết luận được rằng Ứng dụng Spring đã kết nối thành công với Cơ Sở Dữ Liệu MySQL.

Đối với các api khác, các bạn thực hiện tương tự.

Các bạn download source code  tại đây

Chúc các bạn thành công.

AutoCode.VN

minhnhatict@gmail.com