+2

Cách tìm giá trị tối đa trong Spring Data JPA

Khi làm việc với Spring Data JPA, việc truy xuất các giá trị cụ thể từ cơ sở dữ liệu là một yêu cầu phổ biến. Một trong những yêu cầu đó là tìm giá trị tối đa trong một cột cụ thể. Chúng ta hãy đi sâu tìm hiểu các cách tiếp cận khác nhau để tìm giá trị tối đa trong Spring Data JPA trong bài viết ngay sau đây nhé.

Giới thiệu

Spring Data JPA là một phần của tập hợp Spring Data, nhằm mục đích đơn giản hóa việc triển khai các lớp truy cập dữ liệu bằng cách giảm lượng mã boilerplate cần thiết. Nó cung cấp một cách tiếp cận nhất quán để truy cập dữ liệu trong khi hỗ trợ nhiều loại cơ sở dữ liệu quan hệ và không quan hệ. Spring Data JPA tập trung cụ thể vào JPA (Java Persistence API) như là tiêu chuẩn bền vững. Nó cung cấp các trừu tượng mạnh mẽ và hỗ trợ kho lưu trữ, cho phép các nhà phát triển dễ dàng tạo, đọc, cập nhật và xóa các bản ghi mà không cần viết các truy vấn SQL rõ ràng.

Thiết lập cơ sở dữ liệu trên Docker

Thông thường, thiết lập cơ sở dữ liệu là một bước tẻ nhạt nhưng với Docker, vì đó là một quá trình vô cùng đơn giản. Sau khi hoàn tất quá trình cài đặt Docker trên hệ điều hành Windows, hãy mở terminal và kích hoạt lệnh bên dưới để thiết lập và chạy postgresql.

-- Remember to change the password –
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD= --name postgres postgres
 
-- command to stop the Postgres docker container --
docker stop postgres
 
-- command to remove the Postgres docker container --
docker rm postgres

Hãy nhớ nhập mật khẩu bạn chọn. Nếu mọi thứ diễn ra tốt đẹp, máy chủ cơ sở dữ liệu postgresql sẽ hoạt động trên một số cổng – 5432 và bạn có thể kết nối với công cụ Dbeaver GUI để kết nối với máy chủ.

1. Thiết lập dữ liệu tiên quyết

Để tiếp tục, chúng ta sẽ thiết lập dữ liệu giả định cần thiết trong postgresql.

drop table product;
 
create table product (id serial primary key, name varchar(255) not null, price numeric(10, 2) not null);
 
select * from product;

Ví dụ về mã

1. Sự phụ thuộc

Thêm các phụ thuộc sau vào build.gradle trong tệp của bạn hoặc nếu bạn đã tạo một dự án Spring từ start.spring.io thì điều này không cần thiết vì tệp sẽ tự động được điền thông tin phụ thuộc.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.2'
    id 'io.spring.dependency-management' version '1.1.6'
}
 
group = 'jcg'
version = '0.0.1-SNAPSHOT'
 
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
 
repositories {
    mavenCentral()
}
 
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'org.postgresql:postgresql'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
 
tasks.named('test') {
    useJUnitPlatform()
}

2. Cấu hình thuộc tính ứng dụng và cơ sở dữ liệu

Thêm các thuộc tính sau vào tệp application.properties có trong resources thư mục.

spring.application.name=springjpafindmax
spring.main.banner-mode=off
 
# Database configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/some_database_name
spring.datasource.username=some_user
spring.datasource.password=some_password
spring.datasource.driver-class-name=org.postgresql.Driver
 
# JPA/Hibernate configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
 
# SQL logging
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

Cấu hình cơ sở dữ liệu:

  • spring.datasource.url: URL để kết nối với cơ sở dữ liệu PostgreSQL. Thay thế localhost:5432/mydatabasebằng URL cơ sở dữ liệu của bạn.
  • spring.datasource.username: Tên người dùng cho cơ sở dữ liệu PostgreSQL.
  • spring.datasource.password: Mật khẩu cho cơ sở dữ liệu PostgreSQL.
  • spring.datasource.driver-class-name: Lớp trình điều khiển JDBC cho PostgreSQL. Cấu hình JPA/Hibernate:
  • spring.jpa.hibernate.ddl-auto: Kiểm soát việc tạo lược đồ. updateđảm bảo rằng Hibernate sẽ chỉ cập nhật lược đồ mà không xóa nó. Các tùy chọn khác bao gồm create, create-drop, và validate.
  • spring.jpa.show-sql: Cho phép ghi nhật ký các câu lệnh SQL được Hibernate thực thi.
  • spring.jpa.properties.hibernate.dialect: Chỉ định phương ngữ SQL cho PostgreSQL. Ghi nhật ký SQL:
  • logging.level.org.hibernate.SQL: Ghi lại các câu lệnh SQL được Hibernate thực thi.
  • logging.level.org.hibernate.type.descriptor.sql.BasicBinder: Ghi lại các giá trị tham số SQL được liên kết với các câu lệnh SQL.

3. Tạo lớp mô hình

Tạo lớp thực thể người dùng để tương tác với JpaRepository giao diện và thực hiện các hoạt động SQL.

@Entity
public class Product {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Double price;
 
    // Getters and Setters
}

4. Tạo lớp tương tác dữ liệu – Triển khai JPA

Tạo giao diện kho lưu trữ để tương tác với Product thực thể nhằm tương tác với bảng SQL thông qua JpaRepository giao diện và thực hiện các hoạt động SQL.

  • Phương pháp findTopByOrderByPriceDesc là phương pháp truy vấn phái sinh, lấy sản phẩm có giá cao nhất. Wrapper Optional được sử dụng để xử lý trường hợp không tìm thấy sản phẩm nào. Trong Spring Data JPA, các phương pháp truy vấn phái sinh được tạo bằng cách định nghĩa tên phương pháp trong giao diện kho lưu trữ theo quy ước đặt tên cụ thể. Các tên phương pháp này sau đó được Spring Data JPA phân tích cú pháp để tự động tạo truy vấn phù hợp.
  • Phương pháp findMaxPriceProduct chọn sản phẩm có giá cao nhất. Truy vấn phụ (SELECT MAX(p2.price) FROM Product p2) tìm giá cao nhất và truy vấn chính chọn sản phẩm có giá đó.
  • Phương pháp findMaxPriceProductNative sử dụng truy vấn SQL gốc để tìm sản phẩm có giá cao nhất. Thuộc tính nativeQuery = true cho biết đây là truy vấn SQL gốc. Vì tôi không sử dụng chú thích @Table trong lớp Entity, mà tôi đã tạo truy vấn dựa trên tên thực thể thay vì tên bảng. Xin lưu ý rằng giao diện này không yêu cầu lớp triển khai vì Spring sẽ tự động xử lý.
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    //Using derived query
    Optional<Product> findTopByOrderByPriceDesc();
 
    //Using JPQL
    @Query("SELECT p FROM Product p WHERE p.price = (SELECT MAX(p2.price) FROM Product p2)")
    Product findMaxPriceProduct();
 
    //Using native query
    @Query(value = "SELECT * FROM Product p WHERE p.price = (SELECT MAX(p2.price) FROM Product p2)", nativeQuery = true)
    Product findMaxPriceProductNative();
}

5. Tạo lớp tương tác dữ liệu tùy chỉnh – Entity Manager & Criteria API

Tôi sẽ định nghĩa một giao diện kho lưu trữ tùy chỉnh và cung cấp triển khai của nó. Lớp triển khai sẽ bao gồm các phương thức sau:

  • Phương pháp tùy chỉnh sử dụng EntityManager để thực hiện truy vấn JPQL nhằm tìm sản phẩm có giá cao nhất.
  • Criteria API cho phép chúng ta xây dựng các truy vấn theo chương trình. Chúng ta tạo một truy vấn phụ để tìm giá tối đa và sau đó sử dụng nó trong truy vấn chính để tìm sản phẩm có giá đó. Xin lưu ý rằng giao diện được lược bỏ vì lý do ngắn gọn, vì nó chỉ bao gồm các khai báo phương thức.
@Repository
public class ProductRepositoryCustomImpl implements ProductRepositoryCustom {
    @PersistenceContext
    private EntityManager entityManager;
 
    @Override
    public Product findMaxPriceProduct() {
        String query = "SELECT p FROM Product p WHERE p.price = (SELECT MAX(p2.price) FROM Product p2)";
        return entityManager.createQuery(query, Product.class).getSingleResult();
    }
 
    @Override
    public Product findMaxPriceProductWithCriteriaAPI() {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Product> cq = cb.createQuery(Product.class);
        Root<Product> product = cq.from(Product.class);
 
        Subquery<Double> subquery = cq.subquery(Double.class);
        Root<Product> subProduct = subquery.from(Product.class);
        subquery.select(cb.max(subProduct.get("price")));
 
        cq.where(cb.equal(product.get("price"), subquery));
 
        return entityManager.createQuery(cq).getSingleResult();
    }
}

6. Tạo lớp chính

Lớp SpringjpafindmaxApplication là điểm vào chính của ứng dụng Spring Boot, cho thấy cách tìm giá trị lớn nhất trong một cột cụ thể bằng các phương pháp khác nhau với Spring Data JPA. Lớp được chú thích bằng @SpringBootApplication, biểu thị đây là ứng dụng Spring Boot. Lớp này triển khai CommandLineRunner, cho phép thực thi mã bổ sung sau khi ứng dụng Spring Boot khởi động.

Các giao diện ProductRepository và ProductRepositoryCustom được tự động kết nối vào lớp thông qua hàm khởi tạo, thúc đẩy tính bất biến và dễ kiểm tra hơn.

Phương pháp này main khởi chạy ứng dụng Spring Boot bằng cách gọi SpringApplication.run với lớp ứng dụng và đối số dòng lệnh. Trong run, một danh sách Product các đối tượng được tạo và điền dữ liệu mẫu. Mỗi đối tượng Product đều có tên và giá, và các sản phẩm này được lưu vào cơ sở dữ liệu bằng phương pháp productRepository.saveAll này.

Sau đó, giá tối đa của sản phẩm được tính bằng nhiều phương pháp khác nhau:

  • Truy vấn phái sinh: Phương pháp findTopByOrderByPriceDesc được sử dụng để tìm sản phẩm có giá cao nhất. Nếu tìm thấy sản phẩm, giá của sản phẩm đó sẽ được in ra.
  • JPQL: findMaxPriceProduct Phương pháp này sử dụng Ngôn ngữ truy vấn liên tục Java (JPQL) để tìm sản phẩm có giá cao nhất và in giá của sản phẩm đó ra.
  • Truy vấn gốc: findMaxPriceProductNative Phương pháp này sử dụng truy vấn SQL gốc để tìm sản phẩm có giá cao nhất và in giá của sản phẩm đó ra.
  • Phương pháp kho lưu trữ tùy chỉnh: findMaxPriceProduct Phương pháp từ ProductRepositoryCustom được sử dụng để tìm sản phẩm có giá cao nhất bằng cách sử dụng triển khai tùy chỉnh và giá của sản phẩm đó sẽ được in ra.
  • API tiêu chí: findMaxPriceProductWithCriteriaAPI Phương pháp này ProductRepositoryCustom sử dụng API tiêu chí để tìm sản phẩm có giá cao nhất và in giá của sản phẩm đó ra.
@SpringBootApplication
public class SpringjpafindmaxApplication implements CommandLineRunner {
 
    private final ProductRepository productRepository;
    private final ProductRepositoryCustom productRepositoryCustom;
 
    //Doing constructor injection.
    @Autowired
    public SpringjpafindmaxApplication(ProductRepository pr, ProductRepositoryCustom prc) {
        this.productRepository = pr;
        this.productRepositoryCustom = prc;
    }
 
    public static void main(String[] args) {
        SpringApplication.run(SpringjpafindmaxApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        //Create some products
        List<Product> products = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Product product = new Product();
            product.setName("Product " + i);
            product.setPrice((double) (i * 100));
            products.add(product);
        }
        productRepository.saveAll(products);
 
        //Using derived query
        productRepository.findTopByOrderByPriceDesc()
                .ifPresent(p -> System.out.println("Derived Query Max Price: " + p.getPrice()));
 
        //Using jpql
        Product maxPriceProductJPQL = productRepository.findMaxPriceProduct();
        System.out.println("JPQL Max Price: " + maxPriceProductJPQL.getPrice());
 
        //Using Native Query
        Product maxPriceProductNative = productRepository.findMaxPriceProductNative();
        System.out.println("Native Query Max Price: " + maxPriceProductNative.getPrice());
 
        //Using Default Repository Method
        Product maxPriceProductCustom = productRepositoryCustom.findMaxPriceProduct();
        System.out.println("Custom Repository Max Price: " + maxPriceProductCustom.getPrice());
 
        // Using Criteria API
        Product maxPriceProductCriteria = productRepositoryCustom.findMaxPriceProductWithCriteriaAPI();
        System.out.println("Criteria API Max Price: " + maxPriceProductCriteria.getPrice());
    }
}

7. Chạy ứng dụng

Chạy ứng dụng Spring Boot của bạn và ứng dụng sẽ được khởi động trên số cổng được chỉ định trong tệp thuộc tính ứng dụng. Ngay khi ứng dụng được khởi động, các nhật ký sau đây hiển thị đầu ra của nhiều phương pháp DAO khác nhau sẽ được hiển thị trên bảng điều khiển IDE.

2024-08-05T10:15:44.838+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : alter table if exists product alter column id set data type bigint
Hibernate: alter table if exists product alter column id set data type bigint
2024-08-05T10:15:44.861+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : alter table if exists product alter column price set data type float(53)
Hibernate: alter table if exists product alter column price set data type float(53)
 
...
 
2024-08-05T10:15:47.146+05:30  INFO 23068 --- [springjpafindmax] [  restartedMain] j.s.SpringjpafindmaxApplication          : Started SpringjpafindmaxApplication in 10.091 seconds (process running for 12.377)
2024-08-05T10:15:47.247+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : insert into product (name,price) values (?,?) returning id
Hibernate: insert into product (name,price) values (?,?) returning id
2024-08-05T10:15:47.334+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : insert into product (name,price) values (?,?) returning id
Hibernate: insert into product (name,price) values (?,?) returning id
2024-08-05T10:15:47.337+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : insert into product (name,price) values (?,?) returning id
Hibernate: insert into product (name,price) values (?,?) returning id
2024-08-05T10:15:47.339+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : insert into product (name,price) values (?,?) returning id
Hibernate: insert into product (name,price) values (?,?) returning id
2024-08-05T10:15:47.341+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : insert into product (name,price) values (?,?) returning id
Hibernate: insert into product (name,price) values (?,?) returning id
 
...
 
2024-08-05T10:15:47.604+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : select p1_0.id,p1_0.name,p1_0.price from product p1_0 order by p1_0.price desc fetch first ? rows only
Hibernate: select p1_0.id,p1_0.name,p1_0.price from product p1_0 order by p1_0.price desc fetch first ? rows only
Derived Query Max Price: 400.0
 
2024-08-05T10:15:47.642+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : select p1_0.id,p1_0.name,p1_0.price from product p1_0 where p1_0.price=(select max(p2_0.price) from product p2_0)
Hibernate: select p1_0.id,p1_0.name,p1_0.price from product p1_0 where p1_0.price=(select max(p2_0.price) from product p2_0)
JPQL Max Price: 400.0
 
2024-08-05T10:15:47.765+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : SELECT * FROM Product p WHERE p.price = (SELECT MAX(p2.price) FROM Product p2)
Hibernate: SELECT * FROM Product p WHERE p.price = (SELECT MAX(p2.price) FROM Product p2)
Native Query Max Price: 400.0
 
2024-08-05T10:15:47.789+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : select p1_0.id,p1_0.name,p1_0.price from product p1_0 where p1_0.price=(select max(p2_0.price) from product p2_0)
Hibernate: select p1_0.id,p1_0.name,p1_0.price from product p1_0 where p1_0.price=(select max(p2_0.price) from product p2_0)
Custom Repository Max Price: 400.0
 
2024-08-05T10:15:47.796+05:30 DEBUG 23068 --- [springjpafindmax] [  restartedMain] org.hibernate.SQL                        : select p1_0.id,p1_0.name,p1_0.price from product p1_0 where p1_0.price=(select max(p2_0.price) from product p2_0)
Hibernate: select p1_0.id,p1_0.name,p1_0.price from product p1_0 where p1_0.price=(select max(p2_0.price) from product p2_0)
Criteria API Max Price: 400.0

Kết luận

Tôi đã khám phá nhiều cách tiếp cận khác nhau để tìm giá trị tối đa trong Spring JPA. Mỗi phương pháp có trường hợp sử dụng riêng:

  • Truy vấn phái sinh: Đơn giản và ngắn gọn cho các truy vấn cơ bản.
  • JPQL: Linh hoạt và hỗ trợ các truy vấn phức tạp.
  • Truy vấn gốc: SQL trực tiếp cho các hoạt động quan trọng về hiệu suất.
  • Phương pháp lưu trữ tùy chỉnh: Logic tùy chỉnh và xử lý truy vấn phức tạp.
  • Tiêu chí API: Xây dựng truy vấn động và an toàn về kiểu.

Việc lựa chọn phương pháp phù hợp phụ thuộc vào các yêu cầu cụ thể và độ phức tạp của truy vấn bạn cần thực hiện. Cảm ơn các bạn đã theo dõi bài viết này.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí