1. 多表关联查询和排序
假设我们有两个实体类:Customer
和Order
,它们之间是一对多的关系,即一个客户可以有多个订单。我们想要查询某个客户的所有订单,并按订单金额进行降序排序。
java
@Entity
@Table(name = "customers")
public class Customer {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private List<Order> orders;
// getter和setter方法省略
}
@Entity
@Table(name = "orders")
public class Order {
@Id
private Long id;
private BigDecimal amount;
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
// getter和setter方法省略
}
通过使用@OneToMany
和@ManyToOne
注解,我们在Customer
实体类和Order
实体类之间建立了一对多的关系,并指定了关联的外键列。
现在,我们可以执行多表关联查询,并按订单金额进行降序排序。
java
@Repository
public class OrderRepository {
@PersistenceContext
private EntityManager entityManager;
public List<Order> getOrdersByCustomer(Long customerId) {
String queryString = "SELECT o FROM Order o WHERE o.customer.id = :customerId ORDER BY o.amount DESC";
TypedQuery<Order> query = entityManager.createQuery(queryString, Order.class);
query.setParameter("customerId", customerId);
return query.getResultList();
}
}
在上述代码中,OrderRepository
类的getOrdersByCustomer
方法执行了一个多表关联查询,并按订单金额进行降序排序。我们使用了JPQL语句来查询Order
实体类,同时通过条件表达式o.customer.id = :customerId
筛选出特定客户的订单。ORDER BY
子句用于指定排序的字段和排序方式。
最后,我们通过调用getResultList
方法执行查询,并返回满足条件的订单列表。
2. 复杂查询和关联操作
假设我们有三个实体类:Student
、Course
和Enrollment
,它们之间是多对多的关系,即一个学生可以选择多门课程,一门课程也可以有多个学生选择。我们想要查询选择了某门课程的所有学生,并且能够添加新的学生和课程。
java
@Entity
@Table(name = "students")
public class Student {
@Id
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "enrollments",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses;
// getter和setter方法省略
}
@Entity
@Table(name = "courses")
public class Course {
@Id
private Long id;
private String name;
@ManyToMany(mappedBy = "courses")
private List<Student> students;
// getter和setter方法省略
}
通过使用@ManyToMany
和@JoinTable
注解,我们在Student
实体类和Course
实体类之间建立了多对多的关系。JoinTable
注解用于指定关联表的名称和关联的外键列。
现在,我们可以执行复杂查询,获取选择了某门课程的所有学生,并且能够添加新的学生和课程。
java
@Repository
public class StudentRepository {
@PersistenceContext
private EntityManager entityManager;
public List<Student> getStudentsByCourse(Long courseId) {
String queryString = "SELECT s FROM Student s JOIN s.courses c WHERE c.id = :courseId";
TypedQuery<Student> query = entityManager.createQuery(queryString, Student.class);
query.setParameter("courseId", courseId);
return query.getResultList();
}
public void addStudentToCourse(Student student, Long courseId) {
Course course = entityManager.find(Course.class, courseId);
student.getCourses().add(course);
entityManager.persist(student);
}
}
在上述代码中,StudentRepository
类的getStudentsByCourse
方法执行了一个复杂查询,获取选择了某门课程的所有学生。我们使用了JPQL语句来查询Student
实体类,并通过JOIN
语句关联了Course
实体类。条件表达式c.id = :courseId
用于筛选出选择了特定课程的学生。
另外,StudentRepository
类的addStudentToCourse
方法用于向特定课程中添加新的学生。我们首先通过entityManager.find
方法获取到对应的课程实体对象,然后将学生添加到该课程的学生列表中,并通过entityManager.persist
方法将更改持久化到数据库中。
当涉及到JPA复杂查询和多表关系操作时,下面是更多案例,展示了不同的情况和用法:
1. 多对多关联查询和条件过滤
假设我们有两个实体类:Product
和Category
,它们之间是多对多的关系,即一个产品可以属于多个分类,一个分类也可以包含多个产品。我们想要查询属于某个特定分类且价格低于某个阈值的所有产品。
java
@Entity
@Table(name = "products")
public class Product {
@Id
private Long id;
private String name;
private BigDecimal price;
@ManyToMany
@JoinTable(
name = "product_category",
joinColumns = @JoinColumn(name = "product_id"),
inverseJoinColumns = @JoinColumn(name = "category_id")
)
private List<Category> categories;
// getter和setter方法省略
}
@Entity
@Table(name = "categories")
public class Category {
@Id
private Long id;
private String name;
@ManyToMany(mappedBy = "categories")
private List<Product> products;
// getter和setter方法省略
}
通过使用@ManyToMany
和@JoinTable
注解,我们在Product
实体类和Category
实体类之间建立了多对多的关系。JoinTable
注解用于指定关联表的名称和关联的外键列。
现在,我们可以执行多对多关联查询,并通过价格条件进行过滤。
java
@Repository
public class ProductRepository {
@PersistenceContext
private EntityManager entityManager;
public List<Product> getProductsByCategoryAndPrice(Long categoryId, BigDecimal maxPrice) {
String queryString = "SELECT p FROM Product p JOIN p.categories c WHERE c.id = :categoryId AND p.price < :maxPrice";
TypedQuery<Product> query = entityManager.createQuery(queryString, Product.class);
query.setParameter("categoryId", categoryId);
query.setParameter("maxPrice", maxPrice);
return query.getResultList();
}
}
在上述代码中,ProductRepository
类的getProductsByCategoryAndPrice
方法执行了一个多对多关联查询,并通过价格条件进行过滤。我们使用了JPQL语句来查询Product
实体类,并通过JOIN
语句关联了Category
实体类。条件表达式c.id = :categoryId
用于筛选出特定分类的产品,p.price < :maxPrice
用于筛选出价格低于指定阈值的产品。
最后,我们通过调用getResultList
方法执行查询,并返回满足条件的产品列表。
2. 自定义查询结果和投影
在某些情况下,我们可能只需要获取实体类的部分属性,而不是整个实体类的对象。这时可以使用投影(Projection)来自定义查询结果。
假设我们有一个Customer
实体类,包含id
、name
和email
等属性。我们想要查询所有客户的名称和邮箱信息。
java
@Entity
@Table(name = "customers")
public class Customer {
@Id
private Long id;
private String name;
private String email;
// getter和setter方法省略
}
现在,我们可以执行自定义查询,并只选择名称和邮箱两个属性。
java
@Repository
public class CustomerRepository {
@PersistenceContext
private EntityManager entityManager;
public List<Object[]> getCustomerNameAndEmail() {
String queryString = "SELECT c.name, c.email FROM Customer c";
TypedQuery<Object[]> query = entityManager.createQuery(queryString, Object[].class);
return query.getResultList();
}
}
在上述代码中,CustomerRepository
类的getCustomerNameAndEmail
方法执行了一个自定义查询,并只选择了客户的名称和邮箱属性。我们使用了JPQL语句来查询Customer
实体类,并在SELECT子句中指定了要选择的属性。由于返回的结果是一组对象数组,我们将查询结果的类型指定为Object[].class
。
最后,我们通过调用getResultList
方法执行查询,并返回包含名称和邮箱信息的对象数组列表。
注解写SQL
此外,JPA还提供了一些注解,如@Query
、@Param
和@Modifying
,可以更灵活地执行复杂查询操作。下面是几个示例,展示了如何使用这些注解进行复杂查询。
1. 使用@Query注解执行自定义查询
假设我们有一个名为User
的实体类,其中包含id
、name
和email
等属性。我们想要根据用户名进行模糊匹配查询符合条件的用户列表。
java
@Entity
@Table(name = "users")
public class User {
@Id
private Long id;
private String name;
private String email;
// getter和setter方法省略
}
我们可以在UserRepository
接口中使用@Query
注解来定义自定义查询。
java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword%")
List<User> findByKeyword(@Param("keyword") String keyword);
}
在上述代码中,我们在UserRepository
接口中定义了一个带有@Query
注解的方法findByKeyword
,该方法执行了一个自定义查询。我们使用JPQL语句在User
实体类中进行模糊匹配查询,条件是用户名(name
属性)包含给定关键字(keyword
参数)。
2. 使用@Query和@Modifying注解执行更新操作
除了查询操作,@Query
注解还可以用于执行更新操作,如更新、删除等。我们可以结合使用@Query
和@Modifying
注解来执行这样的操作。
假设我们要删除某个邮箱地址为给定值的用户。
java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Query("DELETE FROM User u WHERE u.email = :email")
void deleteByEmail(@Param("email") String email);
}
在上述代码中,我们在UserRepository
接口中定义了一个带有@Query
和@Modifying
注解的方法deleteByEmail
,该方法执行了一个自定义的删除操作。我们使用JPQL语句在User
实体类中删除邮箱地址为给定值(email
参数)的用户。
需要注意的是,在执行更新操作时,需要添加@Modifying
注解以通知JPA这是一个修改操作,并且不返回结果。
3. 使用命名参数和位置参数
在使用@Query
注解时,我们可以使用命名参数或位置参数来传递参数值。
java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name = :name AND u.email = ?1")
List<User> findByNameAndEmail(String name, String email);
}
在上述代码中,我们在@Query
注解中使用了命名参数:name
和位置参数?1
。命名参数使用冒号(:)后跟参数名称的方式,而位置参数使用问号(?)后跟参数的索引编号的方式。在方法的参数列表中,按照在查询语句中出现的顺序,依次传入参数值。
使用@Query
等注解进行多表数据的查询和操作时,你可以编写自定义的JPQL语句,来表达复杂的关联关系和条件。以下是几个示例,展示了如何使用@Query
等注解进行多表查询和操作。
1. 多表关联查询
假设我们有两个实体类:Order
和Customer
,它们之间是一对多的关系,即一个客户可以有多个订单。我们想要查询某个客户的所有订单。
java
@Entity
@Table(name = "orders")
public class Order {
@Id
private Long id;
private BigDecimal totalAmount;
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
// getter和setter方法省略
}
@Entity
@Table(name = "customers")
public class Customer {
@Id
private Long id;
private String name;
private String email;
// getter和setter方法省略
}
我们可以在OrderRepository
接口中使用@Query
注解来定义自定义查询。
java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o JOIN o.customer c WHERE c.id = :customerId")
List<Order> findByCustomerId(@Param("customerId") Long customerId);
}
在上述代码中,我们在OrderRepository
接口中定义了一个带有@Query
注解的方法findByCustomerId
,该方法执行了一个自定义查询。我们使用JPQL语句进行多表关联查询,通过JOIN
关键字将Order
实体类和Customer
实体类关联起来,并使用WHERE
子句筛选出特定客户的订单。
2. 多表关联更新操作
除了查询,@Query
注解还可以用于执行更新操作。假设我们要将某个客户的所有订单的金额增加10%。
java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Modifying
@Query("UPDATE Order o SET o.totalAmount = o.totalAmount * 1.1 WHERE o.customer.id = :customerId")
void increaseAmountByCustomer(@Param("customerId") Long customerId);
}
在上述代码中,我们在OrderRepository
接口中定义了一个带有@Query
和@Modifying
注解的方法increaseAmountByCustomer
,该方法执行了一个自定义的更新操作。我们使用JPQL语句将特定客户的订单金额增加10%。通过SET
子句更新totalAmount
属性,并通过WHERE
子句筛选出特定客户的订单。
需要注意的是,在执行更新操作时,需要添加@Modifying
注解以通知JPA这是一个修改操作,并且不返回结果。
对于上述的Order
实体类,如果想要执行CRUD操作(创建、读取、更新、删除),可以使用JPA提供的CrudRepository
接口或者JpaRepository
接口来定义相关方法。这些接口提供了一组常用的方法,例如save
、findById
、findAll
、delete
等,可以方便地对实体类进行操作。
下面展示了如何使用JpaRepository
对Order
实体类进行CRUD操作:
简单的CRUD操作
java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// 继承JpaRepository,无需额外定义方法
}
在上述代码中,OrderRepository
接口继承了JpaRepository<Order, Long>
,其中Order
是实体类的类型,Long
是实体类的主键类型。通过继承JpaRepository
,我们无需额外定义方法,即可直接使用继承的方法来执行CRUD操作。
使用示例:
java
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public Order createOrder(Order order) {
return orderRepository.save(order);
}
public Order getOrderById(Long orderId) {
return orderRepository.findById(orderId).orElse(null);
}
public List<Order> getAllOrders() {
return orderRepository.findAll();
}
public Order updateOrder(Order order) {
return orderRepository.save(order);
}
public void deleteOrder(Long orderId) {
orderRepository.deleteById(orderId);
}
}
在上述代码中,我们创建了一个OrderService
服务类,通过构造函数注入OrderRepository
对象。在OrderService
中,我们使用OrderRepository
的方法来执行CRUD操作。例如,createOrder
方法使用save
方法创建一个新的订单,getOrderById
方法使用findById
方法根据订单ID获取订单,getAllOrders
方法使用findAll
方法获取所有订单,updateOrder
方法使用save
方法更新订单,deleteOrder
方法使用deleteById
方法删除订单。
通过上述示例,你可以使用JpaRepository
提供的方法对Order
实体类进行CRUD操作。当然,你也可以根据具体需求自定义查询方法,使用@Query
注解等方式执行更复杂的查询和操作。
自定义类封装两个实体类的对象数据
如果我们需要查询两个表并且使用一个新的自定义类来接收查询结果,那么可以使用构造函数表达式或投影查询的方式来实现。
以下是两种方法的示例:
方法一:使用构造函数表达式
假设你有两个实体类 EntityA
和 EntityB
,并且你想要使用一个新的自定义类 CustomResult
来接收查询结果。
首先,创建一个包含所需属性的 CustomResult
类:
java
public class CustomResult {
private String propertyA;
private String propertyB;
public CustomResult(String propertyA, String propertyB) {
this.propertyA = propertyA;
this.propertyB = propertyB;
}
// getter 和 setter 方法...
}
然后,在你的 JPA 存储库(Repository)中使用构造函数表达式进行查询:
java
@Repository
public interface MyRepository extends JpaRepository<EntityA, Long> {
@Query("SELECT new com.example.CustomResult(a.propertyA, b.propertyB) FROM EntityA a LEFT JOIN a.entityB b")
List<CustomResult> findByCustomQuery();
}
在上面的示例中,我们使用构造函数表达式 new com.example.CustomResult(a.propertyA, b.propertyB)
来创建 CustomResult
对象,并将查询结果映射到自定义类中。我们使用了左连接 LEFT JOIN
将两个表进行连接。
当你调用 findByCustomQuery()
方法时,JPA 将执行查询并将结果封装到 CustomResult
对象中。
方法二:使用投影查询
另一种方法是使用投影查询,它允许你选择需要的属性,并将它们映射到一个接口或类的字段中。
首先,创建一个包含所需属性的接口 CustomResult
:
java
public interface CustomResult {
String getPropertyA();
String getPropertyB();
}
然后,在你的 JPA 存储库(Repository)中使用投影查询:
java
@Repository
public interface MyRepository extends JpaRepository<EntityA, Long> {
@Query("SELECT a.propertyA AS propertyA, b.propertyB AS propertyB FROM EntityA a LEFT JOIN a.entityB b")
List<CustomResult> findByCustomQuery();
}
在上面的示例中,我们使用投影查询将两个表的属性映射到接口 CustomResult
中的字段。我们使用了左连接 LEFT JOIN
将两个表进行连接。
当我们调用 findByCustomQuery()
方法时,JPA 将执行查询并将结果封装到 CustomResult
接口的实现类中。
这两种方法都可以用于查询两个表并使用一个新的自定义类来接收查询结果。可以根据实际需求选择其中一种方法来实现。
更接近底层的一种写法
在JPA中,你可以使用createNativeQuery()
方法执行原生SQL查询,并使用getResultList()
方法将查询结果以List<Object[]>
的形式返回。每个Object[]
表示一行记录,其中每个元素对应于一个查询结果列的值。
以下是一个示例,展示如何使用JPA执行原生SQL查询并使用Map
接收查询结果:
java
@Repository
public class MyRepository {
@PersistenceContext
private EntityManager entityManager;
public List<Object[]> executeNativeQuery(String sql) {
Query query = entityManager.createNativeQuery(sql);
List<Object[]> resultList = query.getResultList();
// List<Map<String, Object>> resultMapList = new ArrayList<>();
// for (Object[] result : resultList) {
// System.out.println(Arrays.toString(result));
// Map<String, Object> resultMap = new HashMap<>();
// for (int i = 0; i < result.length; i++) {
// String columnName = ""+i;
// resultMap.put(columnName, result[i]);
// }
// resultMapList.add(resultMap);
// }
//[1, 123456, aaaaaa]
//[2, 123456, kingdol] 拿到的结果是只有值,name要自己封装
return resultList;
}
public int executeNativeUpdate(String sql) {
Query query = entityManager.createNativeQuery(sql);
return query.executeUpdate();
}
}
在上面的示例中,我们使用EntityManager
的createNativeQuery()
方法创建一个原生SQL查询对象。然后,我们使用getResultList()
方法执行查询并将结果以List<Object[]>
的形式返回。
接下来,我们遍历结果列表,将每行记录转换为一个Map<String, Object>
对象。对于每行记录,我们遍历查询结果的每个元素,并使用Query
对象的getResultList()
方法获取对应的列名。然后,我们将列名作为键,查询结果值作为值,将它们存储到Map
中。最后,我们将每个Map
对象添加到一个List<Map<String, Object>>
中,并将其作为查询结果返回。