4.1 Hibernate ORM 与 Panache 简介
4.1.1 Hibernate ORM 概述
Hibernate ORM 是 Java 生态系统中最流行的对象关系映射(ORM)框架。Quarkus 集成了 Hibernate ORM,并提供了 Panache 扩展,简化了数据访问层的开发。
graph TB
A[Quarkus 数据持久化架构] --> B[Hibernate ORM]
A --> C[Panache]
A --> D[数据源配置]
A --> E[事务管理]
B --> B1[实体映射]
B --> B2[查询语言 HQL/JPQL]
B --> B3[缓存机制]
B --> B4[延迟加载]
C --> C1[Active Record 模式]
C --> C2[Repository 模式]
C --> C3[简化查询 API]
C --> C4[分页支持]
D --> D1[数据库连接池]
D --> D2[多数据源]
D --> D3[连接配置]
E --> E1[声明式事务]
E --> E2[编程式事务]
E --> E3[事务传播]
E --> E4[回滚策略]
4.1.2 Panache 优势
Panache 是 Quarkus 提供的数据访问简化层,主要优势包括:
- 简化的 API:减少样板代码,提供直观的查询方法
- Active Record 模式:实体类直接包含数据访问方法
- Repository 模式:分离数据访问逻辑
- 类型安全:编译时检查,减少运行时错误
- 性能优化:针对 Quarkus 优化的查询执行
4.2 环境配置与依赖
4.2.1 添加依赖
在 pom.xml
中添加必要的依赖:
<dependencies>
<!-- Hibernate ORM with Panache -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<!-- JDBC 驱动 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<!-- 或者使用 H2 进行开发测试 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<!-- 数据库迁移 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
</dependency>
<!-- JSON 支持 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<!-- 验证支持 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
</dependencies>
4.2.2 数据库配置
在 application.properties
中配置数据库连接:
# 数据源配置
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_user
quarkus.datasource.password=quarkus_pass
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_db
# 连接池配置
quarkus.datasource.jdbc.min-size=5
quarkus.datasource.jdbc.max-size=20
quarkus.datasource.jdbc.acquisition-timeout=PT30S
# Hibernate 配置
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.log.bind-parameters=true
quarkus.hibernate-orm.show-sql=true
quarkus.hibernate-orm.format-sql=true
# 开发环境配置
%dev.quarkus.datasource.db-kind=h2
%dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
%dev.quarkus.hibernate-orm.sql-load-script=import.sql
# 测试环境配置
%test.quarkus.datasource.db-kind=h2
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
%test.quarkus.hibernate-orm.database.generation=drop-and-create
# Flyway 配置
quarkus.flyway.migrate-at-start=true
quarkus.flyway.locations=classpath:db/migration
4.3 实体映射
4.3.1 基本实体定义
package com.example.entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "books")
public class Book extends PanacheEntity {
@NotBlank(message = "Title is required")
@Size(max = 255, message = "Title must not exceed 255 characters")
@Column(nullable = false)
public String title;
@NotBlank(message = "Author is required")
@Size(max = 100, message = "Author must not exceed 100 characters")
@Column(nullable = false)
public String author;
@Pattern(regexp = "^(978|979)[0-9]{10}$", message = "Invalid ISBN format")
@Column(unique = true)
public String isbn;
@Size(max = 1000, message = "Description must not exceed 1000 characters")
@Column(columnDefinition = "TEXT")
public String description;
@DecimalMin(value = "0.0", inclusive = false, message = "Price must be positive")
@Digits(integer = 8, fraction = 2, message = "Invalid price format")
@Column(precision = 10, scale = 2)
public BigDecimal price;
@Min(value = 0, message = "Stock cannot be negative")
@Column(name = "stock_quantity")
public Integer stockQuantity = 0;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
public BookStatus status = BookStatus.AVAILABLE;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
public Category category;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "publisher_id")
public Publisher publisher;
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
public List<Review> reviews;
@ManyToMany(mappedBy = "books")
@JsonIgnore
public List<Author> authors;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
public LocalDateTime updatedAt;
// 自定义查询方法
public static List<Book> findByTitle(String title) {
return find("title", title).list();
}
public static List<Book> findByAuthor(String author) {
return find("author", author).list();
}
public static List<Book> findByCategory(Category category) {
return find("category", category).list();
}
public static List<Book> findByStatus(BookStatus status) {
return find("status", status).list();
}
public static List<Book> findAvailableBooks() {
return find("status = ?1 and stockQuantity > 0", BookStatus.AVAILABLE).list();
}
public static List<Book> findByPriceRange(BigDecimal minPrice, BigDecimal maxPrice) {
return find("price >= ?1 and price <= ?2", minPrice, maxPrice).list();
}
public static List<Book> searchByKeyword(String keyword) {
return find("lower(title) like ?1 or lower(author) like ?1 or lower(description) like ?1",
"%" + keyword.toLowerCase() + "%").list();
}
// 业务方法
public boolean isAvailable() {
return status == BookStatus.AVAILABLE && stockQuantity > 0;
}
public void decreaseStock(int quantity) {
if (stockQuantity < quantity) {
throw new IllegalStateException("Insufficient stock");
}
stockQuantity -= quantity;
if (stockQuantity == 0) {
status = BookStatus.OUT_OF_STOCK;
}
}
public void increaseStock(int quantity) {
stockQuantity += quantity;
if (status == BookStatus.OUT_OF_STOCK && stockQuantity > 0) {
status = BookStatus.AVAILABLE;
}
}
}
// 书籍状态枚举
public enum BookStatus {
AVAILABLE,
OUT_OF_STOCK,
DISCONTINUED,
COMING_SOON
}
4.3.2 关联实体定义
// 分类实体
@Entity
@Table(name = "categories")
public class Category extends PanacheEntity {
@NotBlank(message = "Category name is required")
@Size(max = 100, message = "Category name must not exceed 100 characters")
@Column(nullable = false, unique = true)
public String name;
@Size(max = 500, message = "Description must not exceed 500 characters")
public String description;
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
@JsonIgnore
public List<Book> books;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
public LocalDateTime updatedAt;
// 自定义查询方法
public static Category findByName(String name) {
return find("name", name).firstResult();
}
public static List<Category> findByNameContaining(String keyword) {
return find("lower(name) like ?1", "%" + keyword.toLowerCase() + "%").list();
}
public long getBookCount() {
return Book.count("category", this);
}
}
// 出版社实体
@Entity
@Table(name = "publishers")
public class Publisher extends PanacheEntity {
@NotBlank(message = "Publisher name is required")
@Size(max = 200, message = "Publisher name must not exceed 200 characters")
@Column(nullable = false)
public String name;
@Email(message = "Invalid email format")
public String email;
@Size(max = 20, message = "Phone must not exceed 20 characters")
public String phone;
@Size(max = 500, message = "Address must not exceed 500 characters")
public String address;
@Size(max = 200, message = "Website must not exceed 200 characters")
public String website;
@OneToMany(mappedBy = "publisher", cascade = CascadeType.ALL)
@JsonIgnore
public List<Book> books;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
public LocalDateTime updatedAt;
public static Publisher findByName(String name) {
return find("name", name).firstResult();
}
public static List<Publisher> findByNameContaining(String keyword) {
return find("lower(name) like ?1", "%" + keyword.toLowerCase() + "%").list();
}
}
// 作者实体
@Entity
@Table(name = "authors")
public class Author extends PanacheEntity {
@NotBlank(message = "Author name is required")
@Size(max = 100, message = "Author name must not exceed 100 characters")
@Column(nullable = false)
public String name;
@Size(max = 1000, message = "Biography must not exceed 1000 characters")
@Column(columnDefinition = "TEXT")
public String biography;
@Past(message = "Birth date must be in the past")
@Column(name = "birth_date")
public LocalDate birthDate;
@Size(max = 100, message = "Nationality must not exceed 100 characters")
public String nationality;
@ManyToMany
@JoinTable(
name = "book_authors",
joinColumns = @JoinColumn(name = "author_id"),
inverseJoinColumns = @JoinColumn(name = "book_id")
)
@JsonIgnore
public List<Book> books;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
public LocalDateTime updatedAt;
public static Author findByName(String name) {
return find("name", name).firstResult();
}
public static List<Author> findByNationality(String nationality) {
return find("nationality", nationality).list();
}
}
// 评论实体
@Entity
@Table(name = "reviews")
public class Review extends PanacheEntity {
@NotBlank(message = "Reviewer name is required")
@Size(max = 100, message = "Reviewer name must not exceed 100 characters")
@Column(name = "reviewer_name", nullable = false)
public String reviewerName;
@Min(value = 1, message = "Rating must be at least 1")
@Max(value = 5, message = "Rating must not exceed 5")
@Column(nullable = false)
public Integer rating;
@Size(max = 2000, message = "Comment must not exceed 2000 characters")
@Column(columnDefinition = "TEXT")
public String comment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", nullable = false)
public Book book;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
public LocalDateTime updatedAt;
public static List<Review> findByBook(Book book) {
return find("book", book).list();
}
public static List<Review> findByRating(Integer rating) {
return find("rating", rating).list();
}
public static Double getAverageRatingForBook(Book book) {
return find("select avg(r.rating) from Review r where r.book = ?1", book)
.project(Double.class)
.firstResult();
}
}
4.4 Repository 模式
4.4.1 Repository 接口定义
package com.example.repository;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import com.example.entity.Book;
import com.example.entity.BookStatus;
import com.example.entity.Category;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class BookRepository implements PanacheRepository<Book> {
// 基本查询方法
public Optional<Book> findByIsbn(String isbn) {
return find("isbn", isbn).firstResultOptional();
}
public List<Book> findByTitle(String title) {
return find("title", title).list();
}
public List<Book> findByAuthor(String author) {
return find("author", author).list();
}
public List<Book> findByCategory(Category category) {
return find("category", category).list();
}
public List<Book> findByStatus(BookStatus status) {
return find("status", status).list();
}
// 复杂查询方法
public List<Book> findAvailableBooks() {
return find("status = ?1 and stockQuantity > 0", BookStatus.AVAILABLE).list();
}
public List<Book> findByPriceRange(BigDecimal minPrice, BigDecimal maxPrice) {
return find("price >= ?1 and price <= ?2", minPrice, maxPrice).list();
}
public List<Book> searchByKeyword(String keyword) {
String query = "lower(title) like ?1 or lower(author) like ?1 or lower(description) like ?1";
String searchTerm = "%" + keyword.toLowerCase() + "%";
return find(query, searchTerm).list();
}
// 分页查询
public List<Book> findAllPaged(int page, int size) {
return findAll().page(Page.of(page, size)).list();
}
public List<Book> findByCategoryPaged(Category category, int page, int size) {
return find("category", category).page(Page.of(page, size)).list();
}
// 排序查询
public List<Book> findAllSorted(Sort sort) {
return findAll(sort).list();
}
public List<Book> findByCategorySorted(Category category, Sort sort) {
return find("category", category, sort).list();
}
// 分页和排序组合
public List<Book> findAllPagedAndSorted(int page, int size, Sort sort) {
return findAll(sort).page(Page.of(page, size)).list();
}
// 统计方法
public long countByCategory(Category category) {
return count("category", category);
}
public long countByStatus(BookStatus status) {
return count("status", status);
}
public long countAvailableBooks() {
return count("status = ?1 and stockQuantity > 0", BookStatus.AVAILABLE);
}
// 存在性检查
public boolean existsByIsbn(String isbn) {
return count("isbn", isbn) > 0;
}
public boolean existsByTitle(String title) {
return count("title", title) > 0;
}
// 批量操作
public int updateStockByCategory(Category category, int stockChange) {
return update("stockQuantity = stockQuantity + ?1 where category = ?2",
stockChange, category);
}
public int updateStatusByCategory(Category category, BookStatus newStatus) {
return update("status = ?1 where category = ?2", newStatus, category);
}
public long deleteByStatus(BookStatus status) {
return delete("status", status);
}
// 自定义 JPQL 查询
public List<Book> findTopRatedBooks(int limit) {
return find("""
select b from Book b
left join b.reviews r
group by b
order by avg(r.rating) desc
""").page(Page.ofSize(limit)).list();
}
public List<Book> findBooksWithReviewCount(int minReviews) {
return find("""
select b from Book b
where (select count(r) from Review r where r.book = b) >= ?1
""", minReviews).list();
}
public List<Object[]> getBookStatsByCategory() {
return getEntityManager()
.createQuery("""
select c.name, count(b), avg(b.price), sum(b.stockQuantity)
from Book b
join b.category c
group by c.name
order by count(b) desc
""", Object[].class)
.getResultList();
}
}
4.4.2 服务层实现
package com.example.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import com.example.entity.Book;
import com.example.entity.BookStatus;
import com.example.entity.Category;
import com.example.repository.BookRepository;
import com.example.dto.BookSearchCriteria;
import com.example.dto.PageResult;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class BookService {
@Inject
BookRepository bookRepository;
@Inject
CategoryService categoryService;
// 基本 CRUD 操作
public List<Book> findAll() {
return bookRepository.listAll();
}
public Optional<Book> findById(Long id) {
return bookRepository.findByIdOptional(id);
}
public Optional<Book> findByIsbn(String isbn) {
return bookRepository.findByIsbn(isbn);
}
@Transactional
public Book create(@Valid Book book) {
// 验证 ISBN 唯一性
if (book.isbn != null && bookRepository.existsByIsbn(book.isbn)) {
throw new IllegalArgumentException("Book with ISBN " + book.isbn + " already exists");
}
// 设置默认值
if (book.status == null) {
book.status = BookStatus.AVAILABLE;
}
if (book.stockQuantity == null) {
book.stockQuantity = 0;
}
bookRepository.persist(book);
return book;
}
@Transactional
public Book update(Long id, @Valid Book updatedBook) {
Book existingBook = bookRepository.findById(id);
if (existingBook == null) {
throw new IllegalArgumentException("Book not found with id: " + id);
}
// 检查 ISBN 唯一性(排除当前书籍)
if (updatedBook.isbn != null && !updatedBook.isbn.equals(existingBook.isbn)) {
if (bookRepository.existsByIsbn(updatedBook.isbn)) {
throw new IllegalArgumentException("Book with ISBN " + updatedBook.isbn + " already exists");
}
}
// 更新字段
existingBook.title = updatedBook.title;
existingBook.author = updatedBook.author;
existingBook.isbn = updatedBook.isbn;
existingBook.description = updatedBook.description;
existingBook.price = updatedBook.price;
existingBook.stockQuantity = updatedBook.stockQuantity;
existingBook.status = updatedBook.status;
existingBook.category = updatedBook.category;
existingBook.publisher = updatedBook.publisher;
return existingBook;
}
@Transactional
public boolean deleteById(Long id) {
return bookRepository.deleteById(id);
}
// 查询方法
public List<Book> findByCategory(Long categoryId) {
Category category = categoryService.findById(categoryId)
.orElseThrow(() -> new IllegalArgumentException("Category not found"));
return bookRepository.findByCategory(category);
}
public List<Book> findByAuthor(String author) {
return bookRepository.findByAuthor(author);
}
public List<Book> findByStatus(BookStatus status) {
return bookRepository.findByStatus(status);
}
public List<Book> findAvailableBooks() {
return bookRepository.findAvailableBooks();
}
public List<Book> findByPriceRange(BigDecimal minPrice, BigDecimal maxPrice) {
return bookRepository.findByPriceRange(minPrice, maxPrice);
}
public List<Book> searchByKeyword(String keyword) {
return bookRepository.searchByKeyword(keyword);
}
// 分页查询
public PageResult<Book> findAllPaged(int page, int size) {
List<Book> books = bookRepository.findAllPaged(page, size);
long totalCount = bookRepository.count();
return new PageResult<>(books, page, size, totalCount);
}
public PageResult<Book> searchBooks(BookSearchCriteria criteria) {
// 构建查询条件
StringBuilder queryBuilder = new StringBuilder("1=1");
List<Object> parameters = new ArrayList<>();
if (criteria.getKeyword() != null && !criteria.getKeyword().trim().isEmpty()) {
queryBuilder.append(" and (lower(title) like ?" + (parameters.size() + 1) +
" or lower(author) like ?" + (parameters.size() + 1) +
" or lower(description) like ?" + (parameters.size() + 1) + ")");
String searchTerm = "%" + criteria.getKeyword().toLowerCase() + "%";
parameters.add(searchTerm);
}
if (criteria.getCategoryId() != null) {
queryBuilder.append(" and category.id = ?" + (parameters.size() + 1));
parameters.add(criteria.getCategoryId());
}
if (criteria.getStatus() != null) {
queryBuilder.append(" and status = ?" + (parameters.size() + 1));
parameters.add(criteria.getStatus());
}
if (criteria.getMinPrice() != null) {
queryBuilder.append(" and price >= ?" + (parameters.size() + 1));
parameters.add(criteria.getMinPrice());
}
if (criteria.getMaxPrice() != null) {
queryBuilder.append(" and price <= ?" + (parameters.size() + 1));
parameters.add(criteria.getMaxPrice());
}
// 构建排序
Sort sort = Sort.by(criteria.getSortBy(),
"desc".equalsIgnoreCase(criteria.getSortOrder()) ?
Sort.Direction.Descending : Sort.Direction.Ascending);
// 执行查询
List<Book> books = bookRepository.find(queryBuilder.toString(), sort, parameters.toArray())
.page(Page.of(criteria.getPage(), criteria.getSize()))
.list();
long totalCount = bookRepository.count(queryBuilder.toString(), parameters.toArray());
return new PageResult<>(books, criteria.getPage(), criteria.getSize(), totalCount);
}
// 库存管理
@Transactional
public void updateStock(Long bookId, int quantity) {
Book book = findById(bookId)
.orElseThrow(() -> new IllegalArgumentException("Book not found"));
if (quantity > 0) {
book.increaseStock(quantity);
} else if (quantity < 0) {
book.decreaseStock(-quantity);
}
}
@Transactional
public void reserveStock(Long bookId, int quantity) {
Book book = findById(bookId)
.orElseThrow(() -> new IllegalArgumentException("Book not found"));
if (!book.isAvailable()) {
throw new IllegalStateException("Book is not available");
}
book.decreaseStock(quantity);
}
// 统计方法
public long getTotalCount() {
return bookRepository.count();
}
public long getCountByCategory(Long categoryId) {
Category category = categoryService.findById(categoryId)
.orElseThrow(() -> new IllegalArgumentException("Category not found"));
return bookRepository.countByCategory(category);
}
public long getAvailableCount() {
return bookRepository.countAvailableBooks();
}
public List<Object[]> getStatsByCategory() {
return bookRepository.getBookStatsByCategory();
}
}
4.5 高级查询技术
4.5.1 JPQL 查询
package com.example.repository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.inject.Inject;
import com.example.entity.Book;
import com.example.dto.BookStatistics;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@ApplicationScoped
public class BookQueryRepository {
@Inject
EntityManager entityManager;
// 复杂 JPQL 查询
public List<Book> findBooksWithHighRating(double minRating) {
String jpql = """
select b from Book b
where b.id in (
select r.book.id from Review r
group by r.book.id
having avg(r.rating) >= :minRating
)
order by b.title
""";
return entityManager.createQuery(jpql, Book.class)
.setParameter("minRating", minRating)
.getResultList();
}
public List<Book> findBooksPublishedInPeriod(LocalDateTime startDate, LocalDateTime endDate) {
String jpql = """
select b from Book b
where b.createdAt between :startDate and :endDate
order by b.createdAt desc
""";
return entityManager.createQuery(jpql, Book.class)
.setParameter("startDate", startDate)
.setParameter("endDate", endDate)
.getResultList();
}
public List<Book> findBooksWithoutReviews() {
String jpql = """
select b from Book b
where not exists (
select 1 from Review r where r.book = b
)
order by b.createdAt desc
""";
return entityManager.createQuery(jpql, Book.class)
.getResultList();
}
// 聚合查询
public BookStatistics getBookStatistics() {
String jpql = """
select new com.example.dto.BookStatistics(
count(b),
avg(b.price),
min(b.price),
max(b.price),
sum(b.stockQuantity)
)
from Book b
""";
return entityManager.createQuery(jpql, BookStatistics.class)
.getSingleResult();
}
public List<Object[]> getCategoryStatistics() {
String jpql = """
select
c.name,
count(b),
avg(b.price),
sum(b.stockQuantity),
avg(coalesce(r.rating, 0))
from Category c
left join c.books b
left join b.reviews r
group by c.id, c.name
order by count(b) desc
""";
return entityManager.createQuery(jpql, Object[].class)
.getResultList();
}
// 动态查询
public List<Book> findBooksWithDynamicCriteria(String title, String author,
BigDecimal minPrice, BigDecimal maxPrice,
Long categoryId) {
StringBuilder jpql = new StringBuilder("select b from Book b where 1=1");
if (title != null && !title.trim().isEmpty()) {
jpql.append(" and lower(b.title) like lower(:title)");
}
if (author != null && !author.trim().isEmpty()) {
jpql.append(" and lower(b.author) like lower(:author)");
}
if (minPrice != null) {
jpql.append(" and b.price >= :minPrice");
}
if (maxPrice != null) {
jpql.append(" and b.price <= :maxPrice");
}
if (categoryId != null) {
jpql.append(" and b.category.id = :categoryId");
}
jpql.append(" order by b.title");
TypedQuery<Book> query = entityManager.createQuery(jpql.toString(), Book.class);
if (title != null && !title.trim().isEmpty()) {
query.setParameter("title", "%" + title + "%");
}
if (author != null && !author.trim().isEmpty()) {
query.setParameter("author", "%" + author + "%");
}
if (minPrice != null) {
query.setParameter("minPrice", minPrice);
}
if (maxPrice != null) {
query.setParameter("maxPrice", maxPrice);
}
if (categoryId != null) {
query.setParameter("categoryId", categoryId);
}
return query.getResultList();
}
// 原生 SQL 查询
public List<Object[]> getMonthlyBookStats() {
String sql = """
select
extract(year from created_at) as year,
extract(month from created_at) as month,
count(*) as book_count,
avg(price) as avg_price
from books
where created_at >= current_date - interval '12 months'
group by extract(year from created_at), extract(month from created_at)
order by year desc, month desc
""";
return entityManager.createNativeQuery(sql)
.getResultList();
}
public List<Book> findTopSellingBooks(int limit) {
String sql = """
select b.* from books b
inner join (
select book_id, count(*) as order_count
from order_items oi
inner join orders o on oi.order_id = o.id
where o.status = 'COMPLETED'
group by book_id
order by order_count desc
limit ?
) top_books on b.id = top_books.book_id
""";
return entityManager.createNativeQuery(sql, Book.class)
.setParameter(1, limit)
.getResultList();
}
}
4.5.2 Criteria API
package com.example.repository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import jakarta.inject.Inject;
import com.example.entity.Book;
import com.example.entity.Category;
import com.example.entity.Review;
import com.example.dto.BookSearchCriteria;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@ApplicationScoped
public class BookCriteriaRepository {
@Inject
EntityManager entityManager;
public List<Book> findBooksWithCriteria(BookSearchCriteria criteria) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Book> query = cb.createQuery(Book.class);
Root<Book> book = query.from(Book.class);
List<Predicate> predicates = new ArrayList<>();
// 关键词搜索
if (criteria.getKeyword() != null && !criteria.getKeyword().trim().isEmpty()) {
String keyword = "%" + criteria.getKeyword().toLowerCase() + "%";
Predicate titlePredicate = cb.like(cb.lower(book.get("title")), keyword);
Predicate authorPredicate = cb.like(cb.lower(book.get("author")), keyword);
Predicate descriptionPredicate = cb.like(cb.lower(book.get("description")), keyword);
predicates.add(cb.or(titlePredicate, authorPredicate, descriptionPredicate));
}
// 分类过滤
if (criteria.getCategoryId() != null) {
Join<Book, Category> categoryJoin = book.join("category");
predicates.add(cb.equal(categoryJoin.get("id"), criteria.getCategoryId()));
}
// 价格范围
if (criteria.getMinPrice() != null) {
predicates.add(cb.greaterThanOrEqualTo(book.get("price"), criteria.getMinPrice()));
}
if (criteria.getMaxPrice() != null) {
predicates.add(cb.lessThanOrEqualTo(book.get("price"), criteria.getMaxPrice()));
}
// 状态过滤
if (criteria.getStatus() != null) {
predicates.add(cb.equal(book.get("status"), criteria.getStatus()));
}
// 库存过滤
if (criteria.getInStock() != null && criteria.getInStock()) {
predicates.add(cb.greaterThan(book.get("stockQuantity"), 0));
}
// 应用所有条件
if (!predicates.isEmpty()) {
query.where(cb.and(predicates.toArray(new Predicate[0])));
}
// 排序
if (criteria.getSortBy() != null) {
Order order;
if ("desc".equalsIgnoreCase(criteria.getSortOrder())) {
order = cb.desc(book.get(criteria.getSortBy()));
} else {
order = cb.asc(book.get(criteria.getSortBy()));
}
query.orderBy(order);
}
TypedQuery<Book> typedQuery = entityManager.createQuery(query);
// 分页
if (criteria.getPage() >= 0 && criteria.getSize() > 0) {
typedQuery.setFirstResult(criteria.getPage() * criteria.getSize());
typedQuery.setMaxResults(criteria.getSize());
}
return typedQuery.getResultList();
}
public List<Book> findBooksWithHighRating(double minRating) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Book> query = cb.createQuery(Book.class);
Root<Book> book = query.from(Book.class);
// 子查询:计算平均评分
Subquery<Double> subquery = query.subquery(Double.class);
Root<Review> review = subquery.from(Review.class);
subquery.select(cb.avg(review.get("rating")))
.where(cb.equal(review.get("book"), book));
query.select(book)
.where(cb.greaterThanOrEqualTo(subquery, minRating))
.orderBy(cb.asc(book.get("title")));
return entityManager.createQuery(query).getResultList();
}
public Long countBooksWithCriteria(BookSearchCriteria criteria) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<Book> book = query.from(Book.class);
List<Predicate> predicates = new ArrayList<>();
// 应用相同的过滤条件(省略具体实现,与上面类似)
// ...
query.select(cb.count(book));
if (!predicates.isEmpty()) {
query.where(cb.and(predicates.toArray(new Predicate[0])));
}
return entityManager.createQuery(query).getSingleResult();
}
}
4.6 事务管理
4.6.1 声明式事务
package com.example.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import com.example.entity.Book;
import com.example.entity.Order;
import com.example.entity.OrderItem;
import com.example.repository.BookRepository;
import com.example.repository.OrderRepository;
import java.util.List;
@ApplicationScoped
public class OrderService {
@Inject
BookRepository bookRepository;
@Inject
OrderRepository orderRepository;
@Inject
InventoryService inventoryService;
@Inject
PaymentService paymentService;
@Inject
NotificationService notificationService;
// 默认事务(REQUIRED)
@Transactional
public Order createOrder(Order order) {
// 验证订单
validateOrder(order);
// 检查库存并预留
for (OrderItem item : order.getItems()) {
Book book = bookRepository.findById(item.getBook().id);
if (book == null) {
throw new IllegalArgumentException("Book not found: " + item.getBook().id);
}
if (!book.isAvailable() || book.stockQuantity < item.getQuantity()) {
throw new IllegalStateException("Insufficient stock for book: " + book.title);
}
// 预留库存
book.decreaseStock(item.getQuantity());
item.setUnitPrice(book.price);
}
// 计算总价
order.calculateTotal();
// 保存订单
orderRepository.persist(order);
return order;
}
// 新事务(REQUIRES_NEW)
@Transactional(TxType.REQUIRES_NEW)
public void processPayment(Long orderId) {
Order order = orderRepository.findById(orderId);
if (order == null) {
throw new IllegalArgumentException("Order not found");
}
try {
// 处理支付
paymentService.processPayment(order);
// 更新订单状态
order.setStatus(OrderStatus.PAID);
} catch (PaymentException e) {
// 支付失败,恢复库存
restoreInventory(order);
order.setStatus(OrderStatus.PAYMENT_FAILED);
throw e;
}
}
// 不使用事务(NOT_SUPPORTED)
@Transactional(TxType.NOT_SUPPORTED)
public void sendOrderConfirmation(Long orderId) {
Order order = orderRepository.findById(orderId);
if (order != null) {
notificationService.sendOrderConfirmation(order);
}
}
// 强制事务(MANDATORY)
@Transactional(TxType.MANDATORY)
public void updateOrderStatus(Long orderId, OrderStatus status) {
Order order = orderRepository.findById(orderId);
if (order == null) {
throw new IllegalArgumentException("Order not found");
}
order.setStatus(status);
}
// 支持事务(SUPPORTS)
@Transactional(TxType.SUPPORTS)
public Order findOrderById(Long orderId) {
return orderRepository.findById(orderId);
}
// 从不使用事务(NEVER)
@Transactional(TxType.NEVER)
public List<Order> generateReport() {
// 生成报告,不需要事务
return orderRepository.findAll().list();
}
// 回滚配置
@Transactional(rollbackOn = {Exception.class})
public void completeOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
if (order == null) {
throw new IllegalArgumentException("Order not found");
}
// 处理支付
processPayment(orderId);
// 更新库存
inventoryService.updateInventory(order);
// 发送通知
notificationService.sendOrderConfirmation(order);
// 更新状态
order.setStatus(OrderStatus.COMPLETED);
}
// 不回滚特定异常
@Transactional(dontRollbackOn = {ValidationException.class})
public void processOrderWithValidation(Order order) {
try {
validateOrder(order);
createOrder(order);
} catch (ValidationException e) {
// 验证异常不回滚事务,记录日志
logger.warn("Order validation failed: {}", e.getMessage());
throw e;
}
}
private void validateOrder(Order order) {
if (order.getItems() == null || order.getItems().isEmpty()) {
throw new ValidationException("Order must have at least one item");
}
for (OrderItem item : order.getItems()) {
if (item.getQuantity() <= 0) {
throw new ValidationException("Item quantity must be positive");
}
}
}
private void restoreInventory(Order order) {
for (OrderItem item : order.getItems()) {
Book book = bookRepository.findById(item.getBook().id);
if (book != null) {
book.increaseStock(item.getQuantity());
}
}
}
}
4.6.2 编程式事务
package com.example.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.UserTransaction;
import jakarta.transaction.Status;
import com.example.entity.Book;
import com.example.entity.Order;
import com.example.repository.BookRepository;
import com.example.repository.OrderRepository;
@ApplicationScoped
public class ProgrammaticTransactionService {
@Inject
UserTransaction userTransaction;
@Inject
BookRepository bookRepository;
@Inject
OrderRepository orderRepository;
public void processComplexOrder(Order order) {
try {
// 开始事务
userTransaction.begin();
// 第一阶段:验证和预留库存
for (OrderItem item : order.getItems()) {
Book book = bookRepository.findById(item.getBook().id);
if (book == null || !book.isAvailable()) {
throw new IllegalStateException("Book not available");
}
if (book.stockQuantity < item.getQuantity()) {
// 部分库存可用,创建部分订单
if (book.stockQuantity > 0) {
item.setQuantity(book.stockQuantity);
book.decreaseStock(book.stockQuantity);
} else {
// 移除不可用的商品
order.getItems().remove(item);
}
} else {
book.decreaseStock(item.getQuantity());
}
}
// 检查是否还有有效商品
if (order.getItems().isEmpty()) {
userTransaction.rollback();
throw new IllegalStateException("No items available for order");
}
// 第二阶段:保存订单
order.calculateTotal();
orderRepository.persist(order);
// 第三阶段:处理支付(在新事务中)
userTransaction.commit();
// 在新事务中处理支付
processPaymentInNewTransaction(order.id);
} catch (Exception e) {
try {
if (userTransaction.getStatus() == Status.STATUS_ACTIVE) {
userTransaction.rollback();
}
} catch (Exception rollbackException) {
logger.error("Failed to rollback transaction", rollbackException);
}
throw new RuntimeException("Failed to process order", e);
}
}
private void processPaymentInNewTransaction(Long orderId) {
try {
userTransaction.begin();
Order order = orderRepository.findById(orderId);
if (order == null) {
throw new IllegalArgumentException("Order not found");
}
// 模拟支付处理
boolean paymentSuccess = processPayment(order);
if (paymentSuccess) {
order.setStatus(OrderStatus.PAID);
userTransaction.commit();
} else {
userTransaction.rollback();
// 在另一个事务中恢复库存
restoreInventoryInNewTransaction(orderId);
}
} catch (Exception e) {
try {
if (userTransaction.getStatus() == Status.STATUS_ACTIVE) {
userTransaction.rollback();
}
} catch (Exception rollbackException) {
logger.error("Failed to rollback payment transaction", rollbackException);
}
throw new RuntimeException("Payment processing failed", e);
}
}
private void restoreInventoryInNewTransaction(Long orderId) {
try {
userTransaction.begin();
Order order = orderRepository.findById(orderId);
if (order != null) {
for (OrderItem item : order.getItems()) {
Book book = bookRepository.findById(item.getBook().id);
if (book != null) {
book.increaseStock(item.getQuantity());
}
}
order.setStatus(OrderStatus.CANCELLED);
}
userTransaction.commit();
} catch (Exception e) {
try {
if (userTransaction.getStatus() == Status.STATUS_ACTIVE) {
userTransaction.rollback();
}
} catch (Exception rollbackException) {
logger.error("Failed to rollback inventory restoration", rollbackException);
}
logger.error("Failed to restore inventory", e);
}
}
private boolean processPayment(Order order) {
// 模拟支付处理逻辑
return Math.random() > 0.1; // 90% 成功率
}
}
4.7 实践练习
4.7.1 练习1:构建完整的图书管理系统
创建一个完整的图书管理系统,包括实体、仓库、服务和 REST API:
// 订单实体
@Entity
@Table(name = "orders")
public class Order extends PanacheEntity {
@NotBlank(message = "Customer name is required")
@Size(max = 100, message = "Customer name must not exceed 100 characters")
@Column(name = "customer_name", nullable = false)
public String customerName;
@Email(message = "Invalid email format")
@Column(name = "customer_email")
public String customerEmail;
@Size(max = 20, message = "Phone must not exceed 20 characters")
@Column(name = "customer_phone")
public String customerPhone;
@Size(max = 500, message = "Address must not exceed 500 characters")
@Column(name = "shipping_address")
public String shippingAddress;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
public OrderStatus status = OrderStatus.PENDING;
@DecimalMin(value = "0.0", inclusive = false, message = "Total amount must be positive")
@Digits(integer = 10, fraction = 2, message = "Invalid total amount format")
@Column(name = "total_amount", precision = 12, scale = 2)
public BigDecimal totalAmount = BigDecimal.ZERO;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
public List<OrderItem> items = new ArrayList<>();
@CreationTimestamp
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
public LocalDateTime updatedAt;
// 业务方法
public void addItem(Book book, int quantity, BigDecimal unitPrice) {
OrderItem item = new OrderItem();
item.order = this;
item.book = book;
item.quantity = quantity;
item.unitPrice = unitPrice;
item.totalPrice = unitPrice.multiply(BigDecimal.valueOf(quantity));
items.add(item);
calculateTotal();
}
public void removeItem(OrderItem item) {
items.remove(item);
calculateTotal();
}
public void calculateTotal() {
totalAmount = items.stream()
.map(item -> item.totalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
public static List<Order> findByCustomerEmail(String email) {
return find("customerEmail", email).list();
}
public static List<Order> findByStatus(OrderStatus status) {
return find("status", status).list();
}
}
// 订单状态枚举
public enum OrderStatus {
PENDING,
CONFIRMED,
PAID,
SHIPPED,
DELIVERED,
CANCELLED,
PAYMENT_FAILED
}
// 订单项实体
@Entity
@Table(name = "order_items")
public class OrderItem extends PanacheEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
public Order order;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", nullable = false)
public Book book;
@Min(value = 1, message = "Quantity must be at least 1")
@Column(nullable = false)
public Integer quantity;
@DecimalMin(value = "0.0", inclusive = false, message = "Unit price must be positive")
@Digits(integer = 8, fraction = 2, message = "Invalid unit price format")
@Column(name = "unit_price", precision = 10, scale = 2, nullable = false)
public BigDecimal unitPrice;
@DecimalMin(value = "0.0", inclusive = false, message = "Total price must be positive")
@Digits(integer = 10, fraction = 2, message = "Invalid total price format")
@Column(name = "total_price", precision = 12, scale = 2, nullable = false)
public BigDecimal totalPrice;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
public LocalDateTime updatedAt;
public static List<OrderItem> findByOrder(Order order) {
return find("order", order).list();
}
public static List<OrderItem> findByBook(Book book) {
return find("book", book).list();
}
}
4.7.2 练习2:REST API 控制器
package com.example.resource;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import com.example.entity.Book;
import com.example.entity.BookStatus;
import com.example.service.BookService;
import com.example.dto.BookSearchCriteria;
import com.example.dto.PageResult;
import io.quarkus.panache.common.Sort;
import java.math.BigDecimal;
import java.util.List;
@Path("/api/books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {
@Inject
BookService bookService;
@GET
public Response getAllBooks(@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size,
@QueryParam("sort") @DefaultValue("title") String sortBy,
@QueryParam("order") @DefaultValue("asc") String sortOrder) {
try {
PageResult<Book> result = bookService.findAllPaged(page, size);
return Response.ok(result).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error retrieving books: " + e.getMessage())
.build();
}
}
@GET
@Path("/{id}")
public Response getBookById(@PathParam("id") Long id) {
return bookService.findById(id)
.map(book -> Response.ok(book).build())
.orElse(Response.status(Response.Status.NOT_FOUND)
.entity("Book not found")
.build());
}
@GET
@Path("/isbn/{isbn}")
public Response getBookByIsbn(@PathParam("isbn") String isbn) {
return bookService.findByIsbn(isbn)
.map(book -> Response.ok(book).build())
.orElse(Response.status(Response.Status.NOT_FOUND)
.entity("Book not found")
.build());
}
@POST
public Response createBook(@Valid Book book) {
try {
Book createdBook = bookService.create(book);
return Response.status(Response.Status.CREATED)
.entity(createdBook)
.build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(e.getMessage())
.build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error creating book: " + e.getMessage())
.build();
}
}
@PUT
@Path("/{id}")
public Response updateBook(@PathParam("id") Long id, @Valid Book book) {
try {
Book updatedBook = bookService.update(id, book);
return Response.ok(updatedBook).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(e.getMessage())
.build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error updating book: " + e.getMessage())
.build();
}
}
@DELETE
@Path("/{id}")
public Response deleteBook(@PathParam("id") Long id) {
try {
boolean deleted = bookService.deleteById(id);
if (deleted) {
return Response.noContent().build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.entity("Book not found")
.build();
}
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error deleting book: " + e.getMessage())
.build();
}
}
@GET
@Path("/search")
public Response searchBooks(@QueryParam("keyword") String keyword,
@QueryParam("categoryId") Long categoryId,
@QueryParam("status") BookStatus status,
@QueryParam("minPrice") BigDecimal minPrice,
@QueryParam("maxPrice") BigDecimal maxPrice,
@QueryParam("inStock") Boolean inStock,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size,
@QueryParam("sort") @DefaultValue("title") String sortBy,
@QueryParam("order") @DefaultValue("asc") String sortOrder) {
try {
BookSearchCriteria criteria = new BookSearchCriteria();
criteria.setKeyword(keyword);
criteria.setCategoryId(categoryId);
criteria.setStatus(status);
criteria.setMinPrice(minPrice);
criteria.setMaxPrice(maxPrice);
criteria.setInStock(inStock);
criteria.setPage(page);
criteria.setSize(size);
criteria.setSortBy(sortBy);
criteria.setSortOrder(sortOrder);
PageResult<Book> result = bookService.searchBooks(criteria);
return Response.ok(result).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error searching books: " + e.getMessage())
.build();
}
}
@PUT
@Path("/{id}/stock")
public Response updateStock(@PathParam("id") Long id,
@QueryParam("quantity") int quantity) {
try {
bookService.updateStock(id, quantity);
return Response.ok().entity("Stock updated successfully").build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(e.getMessage())
.build();
} catch (IllegalStateException e) {
return Response.status(Response.Status.CONFLICT)
.entity(e.getMessage())
.build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error updating stock: " + e.getMessage())
.build();
}
}
@GET
@Path("/statistics")
public Response getStatistics() {
try {
List<Object[]> stats = bookService.getStatsByCategory();
return Response.ok(stats).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error retrieving statistics: " + e.getMessage())
.build();
}
}
}
4.7.3 练习3:数据传输对象(DTO)
package com.example.dto;
import com.example.entity.BookStatus;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
public class BookSearchCriteria {
private String keyword;
private Long categoryId;
private BookStatus status;
private BigDecimal minPrice;
private BigDecimal maxPrice;
private Boolean inStock;
@Min(value = 0, message = "Page must be non-negative")
private int page = 0;
@Min(value = 1, message = "Size must be positive")
@Max(value = 100, message = "Size must not exceed 100")
private int size = 20;
@Pattern(regexp = "^(title|author|price|createdAt|stockQuantity)$",
message = "Invalid sort field")
private String sortBy = "title";
@Pattern(regexp = "^(asc|desc)$", message = "Sort order must be 'asc' or 'desc'")
private String sortOrder = "asc";
// Getters and Setters
public String getKeyword() { return keyword; }
public void setKeyword(String keyword) { this.keyword = keyword; }
public Long getCategoryId() { return categoryId; }
public void setCategoryId(Long categoryId) { this.categoryId = categoryId; }
public BookStatus getStatus() { return status; }
public void setStatus(BookStatus status) { this.status = status; }
public BigDecimal getMinPrice() { return minPrice; }
public void setMinPrice(BigDecimal minPrice) { this.minPrice = minPrice; }
public BigDecimal getMaxPrice() { return maxPrice; }
public void setMaxPrice(BigDecimal maxPrice) { this.maxPrice = maxPrice; }
public Boolean getInStock() { return inStock; }
public void setInStock(Boolean inStock) { this.inStock = inStock; }
public int getPage() { return page; }
public void setPage(int page) { this.page = page; }
public int getSize() { return size; }
public void setSize(int size) { this.size = size; }
public String getSortBy() { return sortBy; }
public void setSortBy(String sortBy) { this.sortBy = sortBy; }
public String getSortOrder() { return sortOrder; }
public void setSortOrder(String sortOrder) { this.sortOrder = sortOrder; }
}
// 分页结果 DTO
public class PageResult<T> {
private List<T> content;
private int page;
private int size;
private long totalElements;
private int totalPages;
private boolean first;
private boolean last;
public PageResult(List<T> content, int page, int size, long totalElements) {
this.content = content;
this.page = page;
this.size = size;
this.totalElements = totalElements;
this.totalPages = (int) Math.ceil((double) totalElements / size);
this.first = page == 0;
this.last = page >= totalPages - 1;
}
// Getters and Setters
public List<T> getContent() { return content; }
public void setContent(List<T> content) { this.content = content; }
public int getPage() { return page; }
public void setPage(int page) { this.page = page; }
public int getSize() { return size; }
public void setSize(int size) { this.size = size; }
public long getTotalElements() { return totalElements; }
public void setTotalElements(long totalElements) { this.totalElements = totalElements; }
public int getTotalPages() { return totalPages; }
public void setTotalPages(int totalPages) { this.totalPages = totalPages; }
public boolean isFirst() { return first; }
public void setFirst(boolean first) { this.first = first; }
public boolean isLast() { return last; }
public void setLast(boolean last) { this.last = last; }
}
// 书籍统计 DTO
public class BookStatistics {
private long totalBooks;
private BigDecimal averagePrice;
private BigDecimal minPrice;
private BigDecimal maxPrice;
private long totalStock;
public BookStatistics(long totalBooks, BigDecimal averagePrice,
BigDecimal minPrice, BigDecimal maxPrice, long totalStock) {
this.totalBooks = totalBooks;
this.averagePrice = averagePrice;
this.minPrice = minPrice;
this.maxPrice = maxPrice;
this.totalStock = totalStock;
}
// Getters and Setters
public long getTotalBooks() { return totalBooks; }
public void setTotalBooks(long totalBooks) { this.totalBooks = totalBooks; }
public BigDecimal getAveragePrice() { return averagePrice; }
public void setAveragePrice(BigDecimal averagePrice) { this.averagePrice = averagePrice; }
public BigDecimal getMinPrice() { return minPrice; }
public void setMinPrice(BigDecimal minPrice) { this.minPrice = minPrice; }
public BigDecimal getMaxPrice() { return maxPrice; }
public void setMaxPrice(BigDecimal maxPrice) { this.maxPrice = maxPrice; }
public long getTotalStock() { return totalStock; }
public void setTotalStock(long totalStock) { this.totalStock = totalStock; }
}
4.8 本章小结
4.8.1 核心概念回顾
本章深入探讨了 Quarkus 中的数据持久化技术,主要包括:
Hibernate ORM 集成:了解了 Quarkus 如何集成 Hibernate ORM,提供强大的对象关系映射能力
Panache 简化层:学习了 Panache 如何简化数据访问代码,提供 Active Record 和 Repository 两种模式
实体映射:掌握了实体类的定义、关联关系映射、验证注解的使用
查询技术:学习了多种查询方式,包括 Panache 查询、JPQL、Criteria API 和原生 SQL
事务管理:了解了声明式和编程式事务管理,以及事务传播机制
4.8.2 技术要点总结
- 实体设计:合理设计实体关系,使用适当的获取策略和级联操作
- 查询优化:选择合适的查询方式,避免 N+1 查询问题
- 事务边界:正确定义事务边界,确保数据一致性
- 性能考虑:使用分页查询、延迟加载等技术优化性能
- 数据验证:在实体层和服务层进行数据验证
4.8.3 最佳实践
实体设计原则:
- 使用有意义的实体名称和属性名
- 合理设计表结构和索引
- 使用适当的数据类型和约束
查询优化策略:
- 使用分页避免大结果集
- 选择合适的获取策略
- 使用查询缓存提高性能
事务管理策略:
- 保持事务简短
- 避免长时间持有连接
- 正确处理异常和回滚
4.8.4 下一章预告
下一章我们将学习微服务通信与集成,包括: - REST 客户端开发 - 消息队列集成 - 服务发现与负载均衡 - 分布式追踪 - 断路器模式
通过本章的学习,你已经掌握了 Quarkus 数据持久化的核心技术,为构建企业级应用奠定了坚实的基础。