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 提供的数据访问简化层,主要优势包括:

  1. 简化的 API:减少样板代码,提供直观的查询方法
  2. Active Record 模式:实体类直接包含数据访问方法
  3. Repository 模式:分离数据访问逻辑
  4. 类型安全:编译时检查,减少运行时错误
  5. 性能优化:针对 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 中的数据持久化技术,主要包括:

  1. Hibernate ORM 集成:了解了 Quarkus 如何集成 Hibernate ORM,提供强大的对象关系映射能力

  2. Panache 简化层:学习了 Panache 如何简化数据访问代码,提供 Active Record 和 Repository 两种模式

  3. 实体映射:掌握了实体类的定义、关联关系映射、验证注解的使用

  4. 查询技术:学习了多种查询方式,包括 Panache 查询、JPQL、Criteria API 和原生 SQL

  5. 事务管理:了解了声明式和编程式事务管理,以及事务传播机制

4.8.2 技术要点总结

  • 实体设计:合理设计实体关系,使用适当的获取策略和级联操作
  • 查询优化:选择合适的查询方式,避免 N+1 查询问题
  • 事务边界:正确定义事务边界,确保数据一致性
  • 性能考虑:使用分页查询、延迟加载等技术优化性能
  • 数据验证:在实体层和服务层进行数据验证

4.8.3 最佳实践

  1. 实体设计原则

    • 使用有意义的实体名称和属性名
    • 合理设计表结构和索引
    • 使用适当的数据类型和约束
  2. 查询优化策略

    • 使用分页避免大结果集
    • 选择合适的获取策略
    • 使用查询缓存提高性能
  3. 事务管理策略

    • 保持事务简短
    • 避免长时间持有连接
    • 正确处理异常和回滚

4.8.4 下一章预告

下一章我们将学习微服务通信与集成,包括: - REST 客户端开发 - 消息队列集成 - 服务发现与负载均衡 - 分布式追踪 - 断路器模式

通过本章的学习,你已经掌握了 Quarkus 数据持久化的核心技术,为构建企业级应用奠定了坚实的基础。