JPA và repository
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 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_pwTạ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ộtGENERATED AS IDENTITYcủa khoá SQL.idcó kiểuLongđể nhận đượcnullkhi object chưa được lưu.JpaRepository<Product, Long>: repository của entityProduct, khoá chính kiểuLong. 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 interfaceProductStorenhư bài Dependency injection. - Hibernate đổi tên field sang chữ thường nối gạch dưới:
unitPricethànhunit_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ênid, nên cột làIDchứ không phảiPRODUCT_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 existLỗ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ừaJpaRepositoryvà được Spring Data tự cài đặt. - Kết nối Oracle bằng ba dòng
spring.datasourcetrongapplication.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âuClass Customer có @Entity nhưng không có @Table. Hibernate truy vấn bảng nào trong Oracle?
ProductRepository chỉ là interface kế thừa JpaRepository, không có thân. Object cài đặt nó ở đâu ra?
Gọi API nhận 500, log báo ORA-00942: table or view "SHOPBOOT"."ORDERS" does not exist. Nguyên nhân?