Trong thế giới phát triển ứng dụng ngày nay, việc truy cập và quản lý dữ liệu hiệu quả là yếu tố then chốt cho mọi hệ thống. Spring Data JPA, một thành viên nổi bật trong gia đình Spring Data, ra đời để giải quyết những thách thức này.

Dòng Spring Data tổng thể giúp chúng ta xây dựng các ứng dụng dựa trên Spring một cách dễ dàng hơn, hỗ trợ đa dạng các phương thức truy cập dữ liệu mới mẻ, từ cơ sở dữ liệu phi quan hệ (NoSQL), các framework map-reduce, dịch vụ đám mây, cho đến việc tối ưu hóa đáng kể việc tương tác với cơ sở dữ liệu quan hệ truyền thống.
Bài viết này sẽ đi sâu vào Spring Data JPA, khám phá các tính năng cốt lõi và hướng dẫn bạn xây dựng một ứng dụng mẫu thực tế.
Spring Data JPA
Spring Data JPA thực sự là một lớp trừu tượng (abstraction layer) mạnh mẽ, giúp bạn giảm thiểu đáng kể lượng mã boilerplate (code lặp lại) khi làm việc với JPA (Java Persistence API).
Một số tính năng nổi bật mà Spring Data JPA cung cấp bao gồm:
- Tạo và hỗ trợ các repository được xây dựng với Spring và JPA.
- Hỗ trợ QueryDSL và các truy vấn JPA tùy chỉnh.
- Chức năng Audit (kiểm toán) cho các lớp domain, tự động ghi lại thông tin như thời gian tạo, người tạo, thời gian cập nhật, v.v.
- Hỗ trợ tải theo lô (batch loading), phân loại (sorting) và các truy vấn động (dynamical queries) một cách dễ dàng.
- Hỗ trợ ánh xạ XML cho các entity (mặc dù annotation là cách phổ biến hơn).
- Giảm thiểu kích thước mã nguồn cho các thao tác CRUD cơ bản bằng cách sử dụng
CrudRepositoryhoặcJpaRepository.
Khi nào nên sử dụng Spring Data JPA?
Tôi có thể nói rằng, nếu bạn cần nhanh chóng tạo một lớp repository dựa trên JPA mà chủ yếu phục vụ các thao tác CRUD (Create, Read, Update, Delete), và bạn không muốn tự tay tạo các abstract DAO (Data Access Object) hay triển khai các interface với quá nhiều mã lặp lại, thì Spring Data JPA là một lựa chọn tuyệt vời. Nó cho phép bạn tập trung vào logic nghiệp vụ thay vì sa lầy vào chi tiết triển khai tầng bền vững.
Ví dụ về Spring Data JPA
Với ví dụ về Spring Data JPA của chúng ta, chúng ta sẽ xây dựng một dịch vụ web RESTful đơn giản kết nối với cơ sở dữ liệu PostgreSQL. Chúng ta sẽ triển khai các thao tác CRUD cơ bản trên một tập dữ liệu mẫu.
Dữ liệu mẫu cho ví dụ Spring Data JPA
Sử dụng truy vấn SQL dưới đây để tạo bảng trong cơ sở dữ liệu PostgreSQL và thêm một số dữ liệu kiểm thử ban đầu:
create table people (
id serial not null primary key,
first_name varchar(20) not null,
last_name varchar(20) not null,
age integer not null
);
insert into people (id, first_name, last_name, age) values
(1, 'Vlad', 'Boyarskiy', 21),
(2,'Oksi', ' Bahatskaya', 30),
(3,'Vadim', ' Vadimich', 32);
Cấu trúc dự án Maven với Spring Data JPA
Cấu trúc dự án Maven điển hình sẽ bao gồm các thư mục src/main/java cho mã nguồn, src/main/resources cho các tệp cấu hình, và pom.xml cho các dependencies. Chúng ta sẽ xem xét chi tiết từng thành phần trong các phần sau.

Các Dependencies Maven của Spring Data JPA
Chúng ta cần thêm các dependencies sau cho dự án ví dụ Spring Data JPA của chúng ta:
postgresql: Driver Java để kết nối với cơ sở dữ liệu PostgreSQL.spring-core,spring-context: Các dependencies cốt lõi của Spring Framework, cung cấp IoC Container và tính năng Bean Management.spring-webmvc,jackson-databind: Dành cho việc xây dựng ứng dụng Spring REST,jackson-databindgiúp chuyển đổi đối tượng Java sang JSON và ngược lại.spring-data-jpa,hibernate-entitymanager: Các thư viện chính để hỗ trợ Spring Data JPA và nhà cung cấp JPA (Hibernate) cho việc quản lý thực thể.
Dưới đây là nội dung của file pom.xml cuối cùng:
<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/maven-v4_0_0.xsd>">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev</groupId>
<artifactId>springData</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>Spring Data JPA Maven Webapp</name>
<url><https://maven.apache.org></url>
<properties>
<spring.framework>4.3.0.RELEASE</spring.framework>
<postgres.version>42.1.4</postgres.version>
<serializer.version>2.8.1</serializer.version>
<spring.data>1.3.4.RELEASE</spring.data>
<hibernate.manager>4.2.5.Final</hibernate.manager>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.framework}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgres.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.framework}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.framework}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring.data}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.manager}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${serializer.version}</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
</build>
</project>
Các lớp cấu hình Spring
Chúng ta sẽ tạo các lớp cấu hình Java để thiết lập ứng dụng Spring, thay vì sử dụng các tệp XML truyền thống.
package com.journaldev.spring.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.ejb.HibernatePersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.journaldev.spring.repository")
@PropertySource("classpath:database.properties")
public class DataConfig {
private final String PROPERTY_DRIVER = "driver";
private final String PROPERTY_URL = "url";
private final String PROPERTY_USERNAME = "user";
private final String PROPERTY_PASSWORD = "password";
private final String PROPERTY_SHOW_SQL = "hibernate.show_sql";
private final String PROPERTY_DIALECT = "hibernate.dialect";
@Autowired
Environment environment;
@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lfb = new LocalContainerEntityManagerFactoryBean();
lfb.setDataSource(dataSource());
lfb.setPersistenceProviderClass(HibernatePersistence.class);
lfb.setPackagesToScan("com.journaldev.spring.model");
lfb.setJpaProperties(hibernateProps());
return lfb;
}
@Bean
DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl(environment.getProperty(PROPERTY_URL));
ds.setUsername(environment.getProperty(PROPERTY_USERNAME));
ds.setPassword(environment.getProperty(PROPERTY_PASSWORD));
ds.setDriverClassName(environment.getProperty(PROPERTY_DRIVER));
return ds;
}
Properties hibernateProps() {
Properties properties = new Properties();
properties.setProperty(PROPERTY_DIALECT, environment.getProperty(PROPERTY_DIALECT));
properties.setProperty(PROPERTY_SHOW_SQL, environment.getProperty(PROPERTY_SHOW_SQL));
return properties;
}
@Bean
JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
Hãy cùng xem qua các annotation và cấu hình quan trọng trong lớp DataConfig:
@Configuration: Annotation này báo hiệu rằng đây là một lớp cấu hình của Spring, chứa các định nghĩa bean.@EnableTransactionManagement: Annotation này cho phép người dùng sử dụng tính năng quản lý giao dịch (transaction management) trong ứng dụng thông qua@Transactional.@EnableJpaRepositories("com.journaldev.spring.repository"): Chỉ định gói (package) nơi chứa các lớp repository mà Spring Data JPA sẽ tự động tạo các triển khai (implementation).@PropertySource("classpath:database.properties"): Cho biết rằng chúng ta có một file thuộc tính trong classpath. Các giá trị từ file này sẽ được tiêm (inject) vào biến môi trường (Environment), cho phép chúng ta truy cập các thông tin như thông tin kết nối cơ sở dữ liệu.
Nội dung của file database.properties như sau:
driver=org.postgresql.Driver
url=jdbc:postgresql://127.0.0.1:5432/postgres
user=postgres
password=postgres
hibernate.dialect=org.hibernate.dialect.PostgreSQL82Dialect
hibernate.show_sql=true
Để sử dụng Spring Data, trước tiên chúng ta phải cấu hình bean DataSource, chịu trách nhiệm cung cấp các kết nối đến cơ sở dữ liệu. Sau đó, chúng ta cần cấu hình bean LocalContainerEntityManagerFactoryBean. Chúng ta cần bean này để kiểm soát các entity và liên kết chúng với cơ sở dữ liệu. Trong bean này, bạn phải chỉ định persistence provider, tức là HibernatePersistence trong trường hợp của chúng ta, và gói chứa các lớp entity (com.journaldev.spring.model).
Bước tiếp theo là cấu hình bean cho quản lý giao dịch. Trong ví dụ của chúng ta, đó là JpaTransactionManager. Lưu ý rằng nếu không cấu hình transaction manager, chúng ta không thể sử dụng annotation @Transactional để quản lý giao dịch một cách tự động.
Các lớp AppInitializer và WebConfig (không được trình bày chi tiết ở đây, nhưng cần thiết trong một ứng dụng web thực tế) dùng để cấu hình ứng dụng của chúng ta như một ứng dụng web dựa trên Servlet 3.0+ mà không cần sử dụng file web.xml truyền thống.
Lớp Model
Lớp model (Person) đại diện cho bảng people trong cơ sở dữ liệu của chúng ta.
package com.journaldev.spring.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "people")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "age")
private Integer age;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
public Person() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
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;
}
@Override
public String toString() {
return "Person{" + "id=" + id + ", age=" + age + ", firstName='" + firstName + '\\\\'' + ", lastName='" + lastName
+ '\\\\'' + '}';
}
}
Tại đây, chúng ta có một vài annotation mới từ JPA. Hãy cùng tìm hiểu chi tiết hơn về chúng:
@Entity: Annotation này cho phép entity manager sử dụng lớp này và đưa nó vào ngữ cảnh (persistence context) để theo dõi và ánh xạ tới cơ sở dữ liệu.@Table(name = “people”): Liên kết lớpPersonvới bảng có tên làpeopletrong cơ sở dữ liệu.@Id: Chỉ định trườngidlà khóa chính (primary key) của bảng.@GeneratedValue(strategy = GenerationType.IDENTITY): Định nghĩa chiến lược tạo khóa chính.GenerationType.IDENTITYcó nghĩa là cơ sở dữ liệu sẽ tự động tạo giá trị cho cột khóa chính (ví dụ,SERIALtrong PostgreSQL).@Column(name = "age"): Chỉ định một cột trong cơ sở dữ liệu (age) mà trườngagesẽ được liên kết. Tương tự chofirst_namevàlast_name.
Spring Data JPA Repository
Bước tiếp theo là tạo JPA repository. Đây là nơi Spring Data JPA thực hiện phép màu của nó.
package com.journaldev.spring.repository;
import org.springframework.data.repository.CrudRepository;
import com.journaldev.spring.model.Person;
import java.util.List;
public interface PersonRepository<P> extends CrudRepository<Person, Long> {
List<Person> findByFirstName(String firstName);
}
Bằng cách kế thừa từ CrudRepository<T, ID>, nơi T là kiểu entity và ID là kiểu của khóa chính, chúng ta có thể gọi nhiều phương thức CRUD cơ bản mà không cần tự triển khai chúng. Spring Data JPA cung cấp sẵn các triển khai mặc định. Một số phương thức này bao gồm:
save(entity): Lưu hoặc cập nhật một entity.findOne(id): Tìm một entity theo ID.exists(id): Kiểm tra sự tồn tại của một entity theo ID.findAll(): Lấy tất cả các entity.count(): Đếm số lượng entity.delete(id): Xóa một entity theo ID.deleteAll(): Xóa tất cả các entity.
Chúng ta cũng có thể định nghĩa các phương thức truy vấn của riêng mình bằng cách tuân theo quy ước đặt tên của Spring Data JPA. Tên của các phương thức này nên sử dụng các từ khóa đặc biệt như “find”, “order”, “By” cùng với tên của các thuộc tính trong entity. Các nhà phát triển Spring Data JPA đã cố gắng tính toán đến đa số các tùy chọn có thể mà bạn cần, giúp bạn viết các truy vấn phức tạp chỉ bằng cách đặt tên phương thức một cách có ý nghĩa. Trong ví dụ của chúng ta, phương thức findByFirstName(String firstName) sẽ tự động tạo truy vấn SQL để trả về tất cả các mục từ bảng people nơi trường first_name bằng với giá trị firstName được truyền vào. Đây là một trong những tính năng quan trọng nhất của Spring Data JPA vì nó giảm đáng kể lượng mã boilerplate và tăng tốc độ phát triển. Ngoài ra, khả năng xảy ra lỗi cũng thấp hơn vì các phương thức này của Spring đã được kiểm thử kỹ lưỡng bởi nhiều dự án sử dụng chúng.
Lớp dịch vụ Spring
Bây giờ, khi mã Spring Data JPA của chúng ta đã sẵn sàng, bước tiếp theo là tạo lớp dịch vụ và định nghĩa các phương thức mà chúng ta sẽ làm việc với bảng cơ sở dữ liệu, thêm vào logic nghiệp vụ nếu cần.
package com.journaldev.spring.services;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.journaldev.spring.model.Person;
import com.journaldev.spring.repository.PersonRepository;
@Service
public class PersonService {
@Autowired
PersonRepository<Person> personRepository;
@Transactional
public List<Person> getAllPersons() {
return (List<Person>) personRepository.findAll();
}
@Transactional
public List<Person> findByName(String name) {
return personRepository.findByFirstName(name);
}
@Transactional
public Person getById(Long id) {
return personRepository.findOne(id);
}
@Transactional
public void deletePerson(Long personId) {
personRepository.delete(personId);
}
@Transactional
public boolean addPerson(Person person) {
return personRepository.save(person) != null;
}
@Transactional
public boolean updatePerson(Person person) {
return personRepository.save(person) != null;
}
}
Annotation @Service đánh dấu lớp này là một service component trong Spring, thường chứa logic nghiệp vụ. Annotation @Autowired tự động tiêm (inject) thể hiện của PersonRepository vào PersonService. Annotation @Transactional chỉ ra rằng phương thức sẽ được thực thi trong một giao dịch. Spring sẽ tự động xử lý việc quản lý giao dịch: mở giao dịch trước khi phương thức được gọi, commit nếu phương thức hoàn thành thành công, và rollback nếu có lỗi xảy ra.
Lớp Controller Spring
Bước cuối cùng là tạo lớp controller để phơi bày (expose) các API RESTful của chúng ta ra thế giới bên ngoài, cho phép các ứng dụng client tương tác với dữ liệu.
package com.journaldev.spring.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.journaldev.spring.model.Person;
import com.journaldev.spring.services.PersonService;
@RestController
public class PersonController {
@Autowired
PersonService personService;
@RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
public @ResponseBody Person getAllUsers(@PathVariable Long id) {
return personService.getById(id);
}
@RequestMapping(value = "/personByName/{name}", method = RequestMethod.GET)
public List<Person> getPersoneByName(@PathVariable String name) {
return personService.findByName(name);
}
@RequestMapping(value = "/person", method = RequestMethod.GET)
public List<Person> getAll() {
return personService.getAllPersons();
}
@RequestMapping(value = "/person/{id}", method = RequestMethod.DELETE)
public HttpStatus deletePersnone(@PathVariable Long id) {
personService.deletePerson(id);
return HttpStatus.NO_CONTENT;
}
@RequestMapping(value = "/person", method = RequestMethod.POST)
public HttpStatus insertPersone(@RequestBody Person person) {
return personService.addPerson(person) ? HttpStatus.CREATED : HttpStatus.BAD_REQUEST;
}
@RequestMapping(value = "/person", method = RequestMethod.PUT)
public HttpStatus updatePerson(@RequestBody Person person) {
return personService.updatePerson(person) ? HttpStatus.ACCEPTED : HttpStatus.BAD_REQUEST;
}
}
Annotation @RestController là một annotation tiện lợi kết hợp @Controller và @ResponseBody, báo hiệu rằng các phương thức trong lớp này sẽ trả về dữ liệu trực tiếp trong phần thân phản hồi HTTP (thường là JSON hoặc XML).
@RequestMapping: Ánh xạ các yêu cầu HTTP tới các phương thức xử lý cụ thể. Chúng ta có thể chỉ địnhvalue(URI) vàmethod(GET, POST, PUT, DELETE).@PathVariable: Trích xuất giá trị từ URI template (ví dụ/person/{id}sẽ trích xuấtid).@RequestBody: Ánh xạ phần thân yêu cầu HTTP vào một đối tượng Java (thường dùng cho POST/PUT khi gửi dữ liệu JSON).HttpStatus: Chúng ta trả về các mã trạng thái HTTP chuẩn để thông báo kết quả của thao tác cho client (ví dụ:CREATEDcho POST thành công,NO_CONTENTcho DELETE thành công,BAD_REQUESTcho lỗi).
Kiểm thử Spring Data JPA
Sau khi đã hoàn tất các bước trên, bạn chỉ cần xây dựng dự án Maven và triển khai tệp WAR vào máy chủ servlet yêu thích của bạn như Apache Tomcat hoặc Jetty. Bạn có thể sử dụng các công cụ như Postman, curl, hoặc thậm chí trình duyệt web để gửi các yêu cầu HTTP đến các endpoint API mà chúng ta đã định nghĩa.
Spring Data JPA Read All

Spring Data JPA Get By Name

Spring Data JPA Create

Spring Data JPA Update

Spring Data JPA Delete

Kết luận
Vậy là chúng ta đã cùng nhau đi qua các bước xây dựng một ứng dụng cơ bản sử dụng Spring Data JPA để tương tác với cơ sở dữ liệu. Từ cấu hình ban đầu đến việc triển khai các API RESTful, chúng ta đã thấy được sức mạnh và sự tiện lợi mà công nghệ này mang lại.
Với những kiến thức này, bạn hoàn toàn có thể tự tin xây dựng các ứng dụng phức tạp hơn, nơi việc tương tác dữ liệu trở nên dễ dàng và hiệu quả hơn rất nhiều.