您的位置:

JPA联表查询详解

Java Persistence API (JPA)是Java EE技术之一,提供了一种数据持久化的解决方案。在很多实际应用中,需要从多个表中获取数据并进行联表查询,因此JPA联表查询是非常重要的。本文将从多个方面进行详细讲解。

一、基本的JPA联表查询

在JPA中,实体类对应数据库中的表,而查询的结果封装为实体对象。当需要进行多表查询时,可以利用JPA的关联(@ManyToOne,@OneToOne,@OneToMany和@ManyToMany)来定义关联关系,从而方便进行联表查询。

以一个订单(Order)和顾客(Customer)的关系为例,Order作为主表,Customer作为从表,它们之间是一对多的关系,因此Order需要使用@ManyToOne注解来定义与Customer的关联:

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "order_no")
    private String orderNo;

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;

    // getter and setter
}

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "customer")
    private List<Order> orders;

    // getter and setter
}

在上面的实体类中,Order使用了@ManyToOne注解,定义了与Customer的关联,同时使用@JoinColumn注解指定了外键字段名称。而Customer使用了@OneToMany注解,定义了与Order的关联关系,同时指定了mappedBy参数,表示由Order维护关系。

在进行联表查询时,只需要使用EntityManager的createQuery方法来创建查询语句,然后使用select方法指定需要查询的字段,使用from方法指定要查询的主表,使用join方法来指定要关联的从表,最后使用where方法来添加过滤条件即可:

TypedQuery<Order> query = em.createQuery(
    "select o from Order o join o.customer c where c.name = :customerName", Order.class);
query.setParameter("customerName", "Alice");
List<Order> orders = query.getResultList();

上面的查询语句使用了JOIN语句将Order和Customer表关联起来,并添加了过滤条件c.name = :customerName,指定只查询顾客名称为Alice的订单信息。

二、JPA的左外联表查询

在实际开发中,需要查询某张主表的所有记录以及对应的从表信息,但是从表中可能没有与主表对应的记录。此时就需要用到左外联表查询。

以一个商品(Item)和库存(Inventory)的关系为例,Inventory中的商品信息不一定全部与Item对应,有可能某些商品还未入库,因此需要使用左外联表查询来获取所有 Item 对象以及对应的库存信息:

@Entity
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "price")
    private BigDecimal price;

    // getter and setter
}

@Entity
public class Inventory {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "quantity")
    private Integer quantity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    // getter and setter
}

在上面的实体类中,Inventory使用了@ManyToOne注解来定义与Item的关联关系。由于默认情况下JPA对于@ManyToOne和@OneToMany关系都使用延迟加载,因此需要在查询时使用JOIN FETCH语句来强制加载:

TypedQuery<Item> query = em.createQuery(
    "select i from Item i left join fetch i.inventories", Item.class);
List<Item> items = query.getResultList();
for (Item item : items) {
    System.out.println(item.getName() + ": " + item.getInventories().size());
}

上面的查询语句使用了LEFT JOIN FETCH语句将Item和Inventory表进行左外联表查询,并使用了item.getInventories()方法来获取所有库存信息。由于使用了JOIN FETCH语句,因此会立即查询出所有记录,而不会像默认情况下那样使用延迟加载的方式。

三、JPA的多表联合查询

在某些情况下,需要从多个表中获取数据并合并成一个结果集。此时就需要使用到多表联合查询。

以一个订单(Order)、顾客(Customer)和订单项(OrderItem)之间的关系为例,需查询出某位顾客购买的所有商品的订单信息:

@Entity
public class OrderItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "quantity")
    private Integer quantity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    // getter and setter
}

TypedQuery<OrderItem> query = em.createQuery(
    "select i from OrderItem i where i.order.customer.name = :customerName", OrderItem.class);
query.setParameter("customerName", "Alice");
List<OrderItem> items = query.getResultList();
for (OrderItem item : items) {
    System.out.println(item.getOrder().getOrderNo() + ": " + item.getQuantity());
}

上面的查询语句使用了 i.order.customer.name = :customerName 来限定只查询顾客名称为Alice的订单项。注意到OrderItem中同时有与Order和Item的关系,因此可以使用 i.order 和 i.item 进行多表联合查询。

四、JPA的子查询

在某些场景下,需要在主查询内进行子查询以获取更精细的数据。此时就需要使用到JPA的子查询功能。

以一个订单(Order)和订单项(OrderItem)的关系为例,需查询所有订单中购买Toy商品的信息:

TypedQuery<Order> query = em.createQuery(
    "select o from Order o where o.id in (select i.order.id from OrderItem i " +
    "where i.item.name = :itemName)", Order.class);
query.setParameter("itemName", "Toy");
List<Order> orders = query.getResultList();
for (Order order : orders) {
    System.out.println(order.getOrderNo() + ": " + order.getCustomer().getName());
}

上面的查询语句使用了"select i.order.id from OrderItem i where i.item.name = :itemName"来获取所有购买Toy商品的订单项ID,然后使用"select o from Order o where o.id in (...) "来查询所有对应的订单信息。

五、JPA的动态条件查询

在实际开发中,需要根据用户的查询条件来动态构建查询语句,此时就需要使用到JPA的动态条件查询。

以一个订单(Order)的几个属性为例,假设用户输入了订单编号、下单时间的开始和结束时间,需要根据用户输入来动态构建查询语句:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> root = query.from(Order.class);

List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(orderNo)) {
    predicates.add(cb.equal(root.get("orderNo"), orderNo));
}
if (startTime != null) {
    Path<Date> createDate = root.get("createdAt");
    predicates.add(cb.greaterThanOrEqualTo(createDate, startTime));
}
if (endTime != null) {
    Path<Date> createDate = root.get("createdAt");
    predicates.add(cb.lessThanOrEqualTo(createDate, endTime));
}

query.select(root).where(predicates.toArray(new Predicate[]{}));
List<Order> orders = em.createQuery(query).getResultList();

以上代码中,首先使用EntityManager的getCriteriaBuilder方法创建CriteriaBuilder对象,然后使用CriteriaQuery来构建查询语句。在构建过程中,需要根据用户的输入来动态添加过滤条件,即利用Predicate来实现。最后利用EntityManager的createQuery方法执行查询操作。

六、总结

JPA联表查询是Java EE技术中非常重要的一部分,本文从基本的JPA联表查询、左外联表查询、多表联合查询、子查询和动态条件查询五个方面进行了详细的讲解,希望能够帮助读者更好地掌握JPA的联表查询功能。