Giới thiệu về Spring Data JDBC – Học Spring Boot

Spring Data JDBC là một thành viên mới được thêm vào trong Spring Data. Nó là sự kết hợp các thành phần của Spring Data JPA (Java Persistence API) và Spring JDBC. Bài viết này sẽ giới thiệu tới các bạn về khái niệm này và lý do tại sao chúng ta nên sử dụng nó trong dự án Spring Boot.

Giới thiệu

Spring Data JDBC là một thành viên mới trong gia đình Spring Data. Nó được tạo ra để khắc phục những điểm yếu của Spring JDBC và Spring Data JPA. Chúng ta thử cùng xem xét về Spring JDBC, khả năng làm việc của nó thì khá là thấp chủ yếu là được sử dụng để kết nối với cơ sở dữ liệu. Còn Spring Data JPA thì lại quá phức tạp vì nó cho chúng ta quá nhiều sự lựa chọn điều đó làm cho chúng ta rất khó để làm chủ được tất cả các lựa chọn này. Spring Data JDBC là một framework cung cấp cho chúng ta những chức năng giống như chúng ta đang sử dụng Spring Data JPA nhưng  lại dễ hiểu hơn rất nhiều bơi nó sử dụng nguyên tắc DDD. Nó còn giúp chúng ta được quyền kiểm soát nhiều hơn bằng cách làm việc ở cấp độ thấp hơn nó cho phép ta được quyết định khi các tương tác với cơ sở dữ liệu được thực hiện như Spring JDBC nhưng lại theo một cách dễ dàng hơn.

Spring Data JDBC vs Spring JDBC vs Spring Data JPA

Spring JDBC

Spring JDBC cung cấp cho chúng ta một framework để thực thi các câu lệnh SQL. Nó xử lý việc kết nối với cơ sở dữ liệu và giúp chúng ta thực thi các câu lệnh SQL bằng cách sử dụng JdbcTemplate. Nhờ vậy mà nó rất là linh hoạt vì chúng ta hoàn toàn có quyền được kiểm soát việc các câu lệnh SQL được thực thi.

Spring Data JPA

Spring Data JPA sử dụng các entity nên do đó cấu trúc của các class cần phải giống với cấu trúc của các bảng trong cơ sở dữ liệu. Ở dạng đơn giản nhất, mỗi bảng cơ sở dữ liệu sẽ đại diện cho một thực thể và có thể được ánh xạ gần như trực tiếp trên một entity class.

Sử dụng @Entity để đánh dấu một Entity class, sử dụng @OneToMany, @ManyToOne, @ManyToMany để tạo kết nối giữa các bảng.

Chúng ta cùng xem xét ví dụ sau để hiểu cách sử dụng @OneToMany và @ManyToOne trong các entity

@Entity

public

class

Rental

{ @Id @GeneratedValue(strategy = GenerationType.AUTO)

private

Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name =

"company_id"

)

private

RentalCompany company; } @Entity

public

class

RentalCompany

{ @Id @GeneratedValue(strategy = GenerationType.AUTO)

private

Long id; @OneToMany(fetch = FetchType.LAZY, mappedBy =

"company"

)

private

List

<Rental> rentals; }

Code language:

PHP

(

php

)

Spring Data JDBC

Khi mà chúng ta sử dụng đến Spring Data JDBC, ta sẽ cần tạo các entity class được kết nối với cơ sở dữ liệu. Nhưng điểm khác biệt lớn đó là nó sẽ có nhiều quy tắc hơn và chúng ta cần phải tuân theo khi tạo cấu trúc một class. Cấu trúc của class cần phải tuân theo luật lệ của mẫu thiết kể tổng hợp DDD. Spring Data thực thi điều này vì điều này sẽ dẫn đến việc tạo ra các dự án đơn giản và dễ hiểu hơn. Đây là một số luật mà chúng ta cần phải tuân theo:

  • Một entity có thể là một phẩn của aggregate
  • Tất các các mối quan hệ bên trong aggregate phải là một chiều
  • root aggregate cần được quản lý top relation

Điều này có nghĩa là bằng cách đi theo các liên kết bắt đầu từ root aggregate, mọi thực thể bên trong tập hợp đều có thể được tìm thấy. Do đó, chúng ta không cần một kho lưu trữ cho mỗi thực thể như trong Spring Data JPA, mà chỉ cho các gốc tổng hợp. Để liên kết các lớp thực thể với nhau để tạo thành một tập hợp, ta cần sử dụng tham chiếu đối tượng. Các lớp thực thể bên trong một tập hợp chỉ có thể có mối quan hệ một-một và một-nhiều. Nếu ta có mối quan hệ một – một, thực thể của ta chỉ cần một tham chiếu đối tượng đến đối tượng kia. Khi có mối quan hệ một-nhiều, thực thể cần chứa một tập hợp các tham chiếu đối tượng. Để tạo quan hệ với các thực thể bên ngoài tập hợp, id cần được sử dụng để có được sự kết hợp thấp giữa các lớp này.

Một sự khác biệt lớn trong việc tạo các lớp được sử dụng bởi Spring Data JDBC so với Spring Data JPA là không cần sử dụng @Entity và không có annotation quan hệ như @OneToMany. Spring JDBC biết một lớp là một root aggregate khi nó chứa một kho lưu trữ cho lớp đó. Và do các quy tắc mà các thực thể tổng hợp được kết nối thông qua các tham chiếu đối tượng, Spring Data JDBC cũng biết các tập hợp là gì và có thể chuyển dữ liệu đến cơ sở dữ liệu dưới dạng các tập hợp.

Ví dụ:

public

class

RentalCompany

{ @Id

private

Long id;

private

String name;

private

Set<Rental> rentals; }

public

class

Rental

{ @Id

private

Long id;

private

String renter;

private

Long carId;

private

LocalDate startDate;

private

LocalDate endDate; }

public

class

Car

{ @Id

private

Long id;

private

String color;

private

String brand;

private

String model;

private

String licensePlate;

private

CarType type; }

Code language:

PHP

(

php

)

Chèn dữ liệu (Insert)

Spring JDBC

Với Spring JDBC chúng ta sẽ viết câu lệnh thêm bản ghi của chúng ta vào và thực thi chúng bằng cách sử dụng JDBC Template. Lợi ích của việc tự viết tất cả các câu Query đó là chúng ta có toàn quyền kiểm soát chúng.

SingleConnectionDataSource dataSource =

new

SingleConnectionDataSource(); dataSource.setDriverClassName(

"org.hsqldb.jdbcDriver"

); dataSource.setUrl(

"jdbc:hsqldb:data/jdbcexample"

); dataSource.setUsername(

"sa"

); dataSource.setPassword(

""

); JdbcTemplate template =

new

JdbcTemplate(ds); template.execute(

"create table car (id int, model varchar)"

); template.execute(

"insert into car (id, model) values (1, 'Volkswagen Beetle')"

); dataSource.destroy();

Code language:

JavaScript

(

javascript

)

Spring Data JPA

Còn với Spring Data JPA, chúng ta muốn thêm mới một bản ghi vào trong cơ sở dữ liệu thì chúng ta đơn giản chỉ cần kế thừa lại Interface có sẵn của Spring JPA và gọi phương thức save()

@Service

public

class

RentalService

{

private

RentalRepository rentalRepository;

public

RentalService(RentalRepository rentalRepository){ this.rentalRepository = rentalRepository; }

public

Rental create(Rental rental){

return

rentalRepository.save(rental); } }

Code language:

PHP

(

php

)

Spring Data JDBC

Spring Data JDBC sử dụng cú pháp có thể tương tự với Spring Data JPA. Sự khác biệt lớn nhất đó là việc quản lý persistence được xử lý bởi kho lưu trữ giống như trong Spring Data JPA, nhưng chỉ root aggregate mới có repository. Điều này có nghĩa là nếu ta muốn thêm hoặc cập nhật dữ liệu, toàn bộ tổng thể cần được lưu. Chúng ta sẽ cần gọi phương thức save của repository của root aggregate và điều này trước tiên sẽ save root aggregate và sau đó tất cả các thực thể được tham chiếu sẽ được lưu lại. Nếu bạn chỉ muốn chèn một phần của tổng hợp, chẳng hạn như chỉ tạo một Rental, thì toàn bộ aggregate sẽ được cập nhật và các thực thể được tham chiếu sẽ bị xóa và chèn lại.

@Service

public

class

RentalCompanyService

{

private

RentalCompanyRepository rentalCompanyRepository;

public

RentalCompanyService(RentalCompanyRepository rentalCompanyRepository){ this.rentalCompanyRepository = rentalCompanyRepository; }

public

RentalCompany addRental(Rental rental, Long rentalCompanyId){ RentalCompany rentalCompany = rentalRepository.findById(rentalCompanyId); rentalCompany.getRentals().add(rental);

return

rentalRepository.save(rentalCompany); } }

Code language:

PHP

(

php

)

Thực thi các câu lệnh query

Spring JDBC

Để thực thi các câu lệnh query chúng ta vẫn sử dụng đến JdbcTemplate. Nhược điểm của nó là chỉ cung cấp cho việc kết nối còn lại chúng ta sẽ phải tự làm tất cả mọi thứ. Nếu như cần tìm kiếm một đối tượng, ta sẽ cần ánh xạ kết quả tới các đối tượng Java bằng cách implement lại interface RowMapper.

Cùng xem xét ví dụ sau:

public

class

CarRowMapper

implements

RowMapper

<

Car

>

{ @Override

public

Car mapRow(ResultSet resultSet, int rowNumber) throws SQLException { Car car =

new

Car(); car.setId(resultSet.getInt(

"ID"

)); car.setColor(resultSet.getString(

"COLOR"

)); car.setBrand(resultSet.getString(

"BRAND"

)); car.setModel(resultSet.getString(

"MODEL"

));

return

car; } }

Code language:

PHP

(

php

)

Mapper này sẽ được chuyển đến cho JdbcTemplate và nó sẽ sử dụng để tạo các đối tượng Java.

List<Car> cars = jdbcTemplate.queryForObject(

"SELECT * FROM CAR WHERE ID = ?"

,

new

Object

[] {id},

new

CarRowMapper());

Code language:

JavaScript

(

javascript

)

Spring Data JPA

Nếu chúng ta sử dụng JPA thì nó đã cung cấp sẫn cho chúng ta các phương thức tìm kiếm như làm findById, hay là findByName, việc của chúng ta đó là chỉ cần gọi lại các phương thức đó, JPA sẽ tự thực thi các câu lệnh cho chúng ta.

@Service

public

class

RentalService

{

private

RentalRepository rentalRepository;

public

RentalService(RentalRepository rentalRepository){ this.rentalRepository = rentalRepository; }

public

List

<Rental> getRentalsByCarType(Long rentalCompanyId, CarType carType) {

return

rentalRepository.findByCompanyIdAndCarType(rentalCompanyId, carType); } }

Code language:

PHP

(

php

)

Repository của chúng ta sẽ khai báo một phương thức như sau

public

interface

RentalRepository

extends

PagingAndSortingRepository

<

Rental

,

Long

>

{

List

<Rental> findByCompanyIdAndCarType(Long rentalCompanyId, CarType carType); }

Code language:

PHP

(

php

)

Spring Data JDBC

Spring Data JDBC có ít trừu tượng hơn Spring Data JPA, nhưng sử dụng các khái niệm Spring Data để giúp dễ dàng thực hiện các câu lệnh CRUD hơn Spring JDBC.

Khi muốn thêm một phương thức vào repository của mình trong Spring Data JDBC, chúng ta sẽ cần thêm annotation @Query chứa truy vấn. Chúng ta sẽ phải sử dụng câu lệnh SQL thuần túy thay vì JPQL được sử dụng trong Spring Data JPA.

Ví dụ

@Service

public

class

RentalCompanyService

{

public

RentalCompanyRepository rentalCompanyRepository;

public

RentalCompanyService(RentalCompanyRepository rentalCompanyRepository){ this.rentalCompanyRepository = rentalCompanyRepository; }

public

List

<Rental> getRentalsByCarType(Long rentalCompanyId, CarType carType) {

return

rentalRepository.findByIdAndCarType(rentalCompanyId, carType); } }

Code language:

PHP

(

php

)

Điểm khác biệt với JPA đó là chúng ta sẽ sử dụng @Query như sau:

public

interface

RentalCompanyRepository

extends

CrudRepository

<

RentalCompany

,

Long

>

{ @Query(value =

"SELECT * "

+

"FROM Rental rental "

+

"JOIN Car car ON car.id = rental.car_id "

+

"WHERE rental.rental_company = :companyId "

+

"AND car.type = :carType"

)

List

<Rental> findRentalsByIdAndCarType(@Param(

"companyId"

) Long companyId, @Param(

"carType"

)String carType); }

Code language:

PHP

(

php

)

Lợi ích của việc sử dụng Spring Data JDBC

  • Một trong những lợi ích lớn nhất của Spring Data JDBC đó là nó sử dụng luật của DDD design như sử dụng các aggregate.
  • Nó rất dễ để có thể hiểu
  • Với Spring Data JDBC tất cả các câu query đều là eager vì vậy sẽ có ít câu query được gửi về database hơn nhờ đó tăng thêm năng suất cho hệ thống.