Query相关操作
常用场景及写法说明
对比维度 | 原生 SQL | JPQL |
---|---|---|
引用对象 | 直接引用数据库中的表和列,大小写敏感性取决于数据库配置。 示例:在 MySQL 中查询 users 表的 name 列sql<br>SELECT name FROM users WHERE id = 1;<br>
|
引用实体类和其属性,遵循 Java 命名规范且区分大小写。 示例:查询 User 实体类的 name 属性java<br>@Query("SELECT u.name FROM User u WHERE u.id = :id")<br>List<String> findUserNamesById(Long id);<br>
|
函数使用 | 可使用数据库特定函数,不同数据库函数及用法有差异。 示例:MySQL 中格式化日期 sql<br>SELECT DATE_FORMAT(create_time, '%Y-%m-%d') FROM orders;<br>
|
提供标准函数,不支持数据库特定函数。 示例:使用标准日期格式化函数 java<br>@Query("SELECT FUNCTION('DATE_FORMAT', o.createTime, '%Y-%m-%d') FROM Order o")<br>List<String> findFormattedOrderDates();<br>
|
表连接语法 | 支持多种连接语法,不同数据库语法细节有别。 示例:MySQL 的内连接 sql<br>SELECT * FROM orders<br>INNER JOIN users ON orders.user_id = users.id;<br>
|
通过实体类关联关系进行连接,语法偏面向对象。 示例:连接 Order 和 User 实体类java<br>@Query("SELECT o FROM Order o JOIN o.user u WHERE u.name = :userName")<br>List<Order> findOrdersByUserName(String userName);<br>
|
返回类型 | 查询结果为数据库原始数据,常需手动映射到 Java 对象。若查询字段与实体属性不匹配,处理复杂。 示例:返回 Object[] 数组java<br>@Query(value = "SELECT id, name FROM users", nativeQuery = true)<br>List<Object[]> findUserBasicInfo();<br>
|
查询结果可直接映射到实体类对象或属性集合,JPA 自动完成映射。 示例:返回 User 实体列表java<br>@Query("SELECT u FROM User u")<br>List<User> findAllUsers();<br>
|
多表查询结果 | 多表查询返回多个表的字段组合,以 Object[] 数组形式返回,需按索引取值,代码可读性和可维护性差。示例: java<br>@Query(value = "SELECT users.name, orders.order_no FROM users JOIN orders ON users.id = orders.user_id", nativeQuery = true)<br>List<Object[]> findUserOrderInfo();<br>
|
通过实体类关联关系,将结果映射到包含关联对象的实体类中,符合面向对象设计思想,易理解和维护。 示例: java<br>@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :userId")<br>List<User> findUserWithOrders(Long userId);<br>
|
性能 | 直接操作数据库表,能充分利用数据库特性和优化机制,在复杂查询或高性能场景下可能性能更好。例如使用数据库索引优化查询。 | JPA 对查询进行解析和转换,生成 SQL 语句,此过程有一定性能开销,但 JPA 也会进行缓存等优化。 |
可移植性 | 不同数据库 SQL 语法和特性差异大,切换数据库时需大量修改语句,可移植性差。 | 与数据库无关,只要 JPA 实现支持,同一查询语句可在不同数据库运行,可移植性好。 |
1. 自定义查询
原生 SQL 查询(针对表名)
原生 SQL 查询使用的是特定数据库支持的 SQL 语法,直接操作数据库中的表和列。在 JPA 中使用原生 SQL 查询时,需要将 @Query
注解的 nativeQuery
属性设置为 true
。
JPQL 查询(针对实体类)
JPQL 是一种面向对象的查询语言,它与具体的数据库无关,使用实体类和属性名进行查询。在 JPA 中使用 JPQL 查询时,不需要设置 nativeQuery
属性。
JPQL 查询示例
// 仓库接口
public interface UserS05Repository extends JpaRepository<UserS05Entity, Long> {
// 使用 JPQL 查询
@Query("SELECT user FROM UserS05Entity user WHERE user.name LIKE %:keyword%")
List<UserS05Entity> findUsersByNameContaining(String keyword);
}
-
解释:此方法使用 JPQL 查询
UserS05Entity
实体中name
字段包含指定关键字的所有记录。%:keyword%
是模糊查询的写法,:
用于绑定方法参数keyword
。
原生 SQL 查询示例
public interface UserS05Repository extends JpaRepository<UserS05Entity, Long> {
// 使用原生 SQL 查询
@Query(value = "SELECT * FROM user_s05 WHERE email LIKE %?1%", nativeQuery = true)
List<UserS05Entity> findUsersByEmailContaining(String keyword);
}
-
解释:
nativeQuery = true
表示使用原生 SQL 查询。?1
是参数占位符,对应方法的第一个参数keyword
。
2. 更新操作
如示例所示,@Query
注解也可用于执行更新操作,通常需要结合 @Modifying
注解来标记这是一个修改操作。
JpaRepository<UserS05Entity, Long> {
// 更新操作
@Modifying
@Transactional
@Query("UPDATE UserS05Entity AS user SET user.name = :newName WHERE user.id = :id")
int updateUserName(Long id, String newName);
}
-
解释:
@Modifying
注解标记这是一个修改操作,@Transactional
注解确保操作在事务中执行。方法返回值表示受影响的行数。
3. 删除操作
同样,@Query
注解可用于执行删除操作,也需要结合 @Modifying
和 @Transactional
注解。
public interface UserS05Repository extends JpaRepository<UserS05Entity, Long> {
// 删除操作
@Modifying
@Transactional
@Query("DELETE FROM UserS05Entity user WHERE user.id = :id")
int deleteUserById(Long id);
}
-
解释:此方法使用 JPQL 删除
UserS05Entity
实体中id
等于指定参数的记录,返回值表示受影响的行数。
4. 聚合查询
可以使用 @Query
进行聚合查询,如统计记录数量、求和等。
public interface UserS05Repository extends JpaRepository<UserS05Entity, Long> {
// 聚合查询:统计记录数量
@Query("SELECT COUNT(user) FROM UserS05Entity user")
long countAllUsers();
}
-
解释:此方法使用 JPQL 统计
UserS05Entity
实体的记录数量,返回值为统计结果。
是的,原生 SQL 查询针对的是数据库中的表名,而 JPQL(Java Persistence Query Language)查询针对的是实体类(Entity)。下面为你详细解释二者的区别以及具体示例。
在 JPA 中,@Query
注解同样可以用于分页查询、插入和批量插入以及连表查询,下面为你详细介绍这些场景的实现方式。
1. 分页查询
分页查询允许你从数据库中检索特定范围的数据,在处理大量数据时非常有用。可以结合 Spring Data JPA 的 Pageable
接口来实现分页功能。
示例代码
// 仓库接口
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword%")
Page<User> findUsersByNameContaining(String keyword, Pageable pageable);
}
代码解释
-
Pageable
接口用于封装分页信息,如页码、每页记录数、排序规则等。 -
Page<User>
是 Spring Data JPA 提供的分页结果对象,包含了当前页的数据列表、总记录数、总页数等信息。 - 在使用时,可以通过以下方式调用该方法:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Page<User> getUsersByKeyword(String keyword, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("id").ascending());
return userRepository.findUsersByNameContaining(keyword, pageable);
}
}
2. 插入操作
虽然 JPA 通常使用 save
方法来插入数据,但在某些复杂场景下,也可以使用 @Query
注解执行原生 SQL 插入语句。
示例代码
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Transactional
@Query(value = "INSERT INTO user (name) VALUES (:name)", nativeQuery = true)
int insertUser(String name);
}
代码解释
-
@Modifying
注解用于标记这是一个修改操作(插入、更新、删除)。 -
@Transactional
注解确保操作在事务中执行。 -
nativeQuery = true
表示使用原生 SQL 语句。 - 方法返回值表示受影响的行数。
3. 批量插入
批量插入可以提高插入大量数据的性能,同样可以使用 @Query
注解结合原生 SQL 实现。
示例代码
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Transactional
@Query(value = "INSERT INTO user (name) VALUES :names", nativeQuery = true)
int batchInsertUsers(List<String> names);
}
代码解释
- 此示例假设数据库支持使用
VALUES
后跟多个值列表的方式进行批量插入。不同数据库的批量插入语法可能有所不同,需要根据实际情况调整。 -
:names
是参数占位符,需要将List<String>
转换为合适的 SQL 语法格式。
4. 连表查询
连表查询用于从多个相关表中检索数据,可以使用 JPQL 或原生 SQL 实现。
JPQL 连表查询示例
// 订单仓库接口
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o JOIN o.user u WHERE u.name = :userName")
List<Order> findOrdersByUserName(String userName);
}
代码解释
-
JOIN o.user u
表示将Order
实体和关联的User
实体进行连接。 - 通过
WHERE u.name = :userName
筛选出用户名为指定值的订单。
原生 SQL 连表查询示例
{
@Query(value = "SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.name = ?1", nativeQuery = true)
List<Order> findOrdersByUserNameNative(String userName);
}
代码解释
-
JOIN users u ON o.user_id = u.id
是原生 SQL 的连接语句,用于连接orders
表和users
表。 -
?1
是参数占位符,对应方法的第一个参数userName
。
通过以上示例,你可以看到 @Query
注解在不同查询场景下的灵活应用。根据实际需求选择合适的查询方式和语法,以实现高效的数据检索和操作。
动态查询
JpaSpecificationExecutor 允许我们通过 Specification 构建动态查询,适用于查询条件不固定的场景,尤其是多个可选条件的动态查询。
@Query 查询则适用于查询条件已知的情况,能够通过 JPQL 或原生 SQL 直接写查询语句,简单直观,性能较好。
场景说明:如前端可以根据组合字段进行搜索,也可以搜索字段为空
实例
1、创建 Specification 来构建动态查询条件
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Predicate;
public class UserSpecification {
// 根据用户名构建查询条件
public static Specification<UserEntity> hasUsername(String username) {
return (root, query, builder) -> {
if (username == null || username.isEmpty()) {
return builder.conjunction(); // 如果没有提供用户名,则返回一个永远为真的条件
}
return builder.equal(root.get("username"), username);
};
}
// 根据邮箱构建查询条件
public static Specification<UserEntity> hasEmail(String email) {
return (root, query, builder) -> {
if (email == null || email.isEmpty()) {
return builder.conjunction(); // 如果没有提供邮箱,则返回一个永远为真的条件
}
return builder.like(root.get("email"), "%" + email + "%");
};
}
// 根据年龄构建查询条件
public static Specification<UserEntity> hasAgeGreaterThan(Integer age) {
return (root, query, builder) -> {
if (age == null) {
return builder.conjunction(); // 如果没有提供年龄,则返回一个永远为真的条件
}
return builder.greaterThan(root.get("age"), age);
};
}
}
- 创建 Repository 接口
在 Repository 接口中继承 JpaSpecificationExecutor,这样就可以通过 Specification 动态生成查询。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface UserRepository extends JpaRepository<UserEntity, Long>, JpaSpecificationExecutor<UserEntity> {
}
4、controller层调用
@GetMapping("/search")
public List<UserEntity> searchUsers(
@RequestParam(required = false) String username,
@RequestParam(required = false) String email,
@RequestParam(required = false) Integer age) {
return userService.findUsers(username, email, age); // 调用动态查询
}
事件发布监听
涉及注解:
@DomainEvents:用于标记领域事件的发布方法,返回所有要发布的事件。
@TransactionalEventListener:监听特定事件,执行业务逻辑。
该方法可以执行一些业务逻辑,如发送通知、记录日志、更新其他数据源等
@AfterDomainEventPublication:该方法在所有领域事件被成功发布后调用,通常用于执行一些后续操作,如日志记录、清理资源等
1、入库一个信息
public UserEntity create() {
UserEntity domain = UserEntity.builder().username(RandomStringUtils.randomAlphabetic(6)).build();
return repository.save(domain);
}
2、UserEntity 实体类中添加一个事件发布
@DomainEvents
public Collection<Object> events() {
return Collections.singletonList(new UserCreateEvent(this.id, this.username));
}
3、新增一个监听方法处理该事件
@TransactionalEventListener(UserCreateEvent.class)
public void userEvent(UserCreateEvent event) {
log.info("{}", event);
}
4、UserEntity 实体类中添加一个事件被成功发布后调用的方法
@AfterDomainEventPublication
public void afterDomainEventPublication() {
log.info("afterDomainEventPublication");
}
数据调用流程:调用create()方法—> 触发领域事件@DomainEvents—> 发布事件—> 处理事件@TransactionalEventListener —> 执行后续操作afterDomainEventPublication
投影返回需要的数据
@ProjectedPayload
public interface IWebDataBindingProjection {
@JsonPath("$.firstname")
String getFirstname();
@JsonPath("$.lastname")
String getLastname();
}
json存储
@Type(JsonType.class)
@Column(columnDefinition = "JSON")
或者
@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "json")