Trang chủHướng dẫnSpring Data JPA là gì? Cách sử dụng và ví dụ CRUD với PostgreSQL
Java

Spring Data JPA là gì? Cách sử dụng và ví dụ CRUD với PostgreSQL

CyStack blog 10 phút để đọc
CyStack blog03/10/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 10 minutes

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.

Spring Data JPA là gì?

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 CrudRepository hoặc JpaRepository.

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.

Spring Data JPA là gì?

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-databind giú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 AppInitializerWebConfig (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ớp Person với bảng có tên là people trong cơ sở dữ liệu.
  • @Id: Chỉ định trường id là 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.IDENTITY có nghĩa là cơ sở dữ liệu sẽ tự động tạo giá trị cho cột khóa chính (ví dụ, SERIAL trong PostgreSQL).
  • @Column(name = "age"): Chỉ định một cột trong cơ sở dữ liệu (age) mà trường age sẽ được liên kết. Tương tự cho first_namelast_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@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ỉ định value (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ất id).
  • @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ụ: CREATED cho POST thành công, NO_CONTENT cho DELETE thành công, BAD_REQUEST cho 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 Example Read All

Spring Data JPA Get By Name

Spring JPA Read

Spring Data JPA Create

Spring Data JPA Create

Spring Data JPA Update

Spring Data JPA Update

Spring Data JPA Delete

Spring Data JPA Example 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.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất