VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
Spring Boot Web APIDữ liệu với JPA
Bài 13/19
6 phút

JPA và repository

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

Dữ liệu trong bộ nhớ mất mỗi lần tắt server. Cửa hàng thật cần lưu sản phẩm vào database, và bạn đã có sẵn Oracle từ khoá SQL. Spring Data JPA cho phép làm việc với Oracle bằng class và method quen thuộc, không phải tự viết SQL cho từng thao tác.

Khái niệm

🗄️ ORM (Object-Relational Mapper): thư viện chuyển qua lại giữa object Java và dòng trong bảng database, như Hibernate mà Spring Boot dùng qua chuẩn JPA.

🏷️ Entity: class gắn @Entity, mỗi object của nó ứng với một dòng trong bảng.

📚 Repository: interface kế thừa JpaRepository<T, ID>, được Spring Data tự viết phần cài đặt với sẵn các method như findAll, findById, save.

Java Oracle
class Product có @Table(name = "products") bảng PRODUCTS
field name cột NAME
một object Product một dòng trong bảng
field id có @Id khoá chính, tự tăng

Ví dụ

API dùng một user riêng tên shopboot, tách khỏi user shop của khoá SQL. Mở VS Code, kết nối Oracle bằng user system, mật khẩu oracle_pw, service name FREEPDB1, rồi chạy một lần:

CREATE USER shopboot IDENTIFIED BY shopboot_pw
  QUOTA UNLIMITED ON USERS;
GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE
  TO shopboot;

GRANT cấp cho user mới quyền đăng nhập và tạo bảng. User mới chưa có bảng nào, bài sau dùng migration để tạo bảng.

Thêm Spring Data JPA và driver Oracle vào <dependencies> của pom.xml. Không cần ghi <version>, Spring Boot chọn sẵn phiên bản khớp nhau.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>com.oracle.database.jdbc</groupId>
  <artifactId>ojdbc17</artifactId>
  <scope>runtime</scope>
</dependency>

Thêm thông tin kết nối vào application.properties. Mật khẩu shopboot_pw chỉ để thử trên máy.

spring.datasource.url=jdbc:oracle:thin:@localhost:1521/FREEPDB1
spring.datasource.username=shopboot
spring.datasource.password=shopboot_pw

Tạo entity, repository và controller:

// Product.java
package com.shop.api;

import jakarta.persistence.*;

@Entity
@Table(name = "products")
class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name = "";
    private long price;

    public Long getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) {
        this.name = name;
    }
    public long getPrice() { return price; }
    public void setPrice(long price) {
        this.price = price;
    }
}
// ProductRepository.java
package com.shop.api;

import org.springframework.data.jpa.repository.*;

interface ProductRepository
        extends JpaRepository<Product, Long> {
}
// ProductsController.java
package com.shop.api;

import java.util.List;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/products")
class ProductsController {
    private final ProductRepository products;

    public ProductsController(
            ProductRepository products) {
        this.products = products;
    }

    @GetMapping
    public List<Product> getAll() {
        return products.findAll();
    }
}
  • @Id đánh dấu khoá chính. GenerationType.IDENTITY để Oracle tự tăng giá trị, như cột GENERATED AS IDENTITY của khoá SQL. id có kiểu Long để nhận được null khi object chưa được lưu.
  • JpaRepository<Product, Long>: repository của entity Product, khoá chính kiểu Long. Interface không có thân, lúc khởi động Spring Data tạo object cài đặt nó và đăng ký vào container.
  • Để gọn, controller nhận thẳng ProductRepository. Bài Truy vấn sẽ đặt nó sau interface ProductStore như bài Dependency injection.
  • Hibernate đổi tên field sang chữ thường nối gạch dưới: unitPrice thành unit_price. Oracle lưu tên không có ngoặc kép thành chữ hoa, nên tên khớp với khoá SQL. Khoá chính tên id, nên cột là ID chứ không phải PRODUCT_ID.
  • findAll() đọc cả bảng, trả về List<Product>.

Thử ngay

Giữ các file cũ, thay Product.java, ProductsController.java và thêm ProductRepository.java theo phần Ví dụ, rồi chạy lại server và gọi:

curl -i http://localhost:5000/api/products

Đoán trước khi chạy: code compile được và server chạy bình thường. Lần gọi này trả về danh sách rỗng hay lỗi?

Xem kết quả
HTTP/1.1 500
{"timestamp":"…","status":500,
 "error":"Internal Server Error","path":"/api/products"}

Log của server:
ORA-00942: table or view "SHOPBOOT"."PRODUCTS" does not exist

Lỗi 500. Hibernate kết nối được Oracle, nhưng user shopboot chưa có bảng PRODUCTS, vì class Java không tự sinh ra bảng. Bài sau dùng migration để tạo bảng.

Lỗi hay gặp

Quên @Entity. Hibernate không coi Product là entity, Spring Data không tạo được repository, và server dừng ngay lúc khởi động với lỗi Not a managed type: class com.shop.api.Product.

// SAI — thiếu @Entity
// Product.java
package com.shop.api;

import jakarta.persistence.*;

@Table(name = "products")
class Product {
    @Id
    private Long id;
}
// ĐÚNG
// Product.java
package com.shop.api;

import jakarta.persistence.*;

@Entity
@Table(name = "products")
class Product {
    @Id
    private Long id;
}

Bỏ @Table(name = "products"). Hibernate lấy tên class làm tên bảng, thành PRODUCT số ít. Bảng của khoá SQL tên PRODUCTS, nên truy vấn báo ORA-00942: table or view "SHOPBOOT"."PRODUCT" does not exist.

Tóm tắt

  • JPA là chuẩn ORM của Java, Hibernate cài đặt nó: class thành bảng, object thành dòng.
  • Entity gắn @Entity, @Table, @Id. Repository kế thừa JpaRepository và được Spring Data tự cài đặt.
  • Kết nối Oracle bằng ba dòng spring.datasource trong application.properties.
  • API dùng user riêng. Chỉ có class thì chưa đủ, phải tạo bảng trước khi dùng.

Tự kiểm tra

0/3 câu
Câu 1

Class Customer có @Entity nhưng không có @Table. Hibernate truy vấn bảng nào trong Oracle?

Câu 2

ProductRepository chỉ là interface kế thừa JpaRepository, không có thân. Object cài đặt nó ở đâu ra?

Câu 3

Gọi API nhận 500, log báo ORA-00942: table or view "SHOPBOOT"."ORDERS" does not exist. Nguyên nhân?

Filter và interceptorMigration

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt