Spring Data JPA 查询方法那些事

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/J080624/article/details/82559318

Spring Data 提供了几个接口供继承使用,如 JpaRepository,另外还规定了方法查询中的关键字,即你命名的查询方法需要符合规范。

详情参考:SpringBoot整合Spring Data JPASpringDataJPA入门

本篇博文详细记录Spring Data JPA查询中的那些事。

【1】规范方法查询

① 只要符号命名规范的接口都可以被正常解析使用

  • 查询方法以find|read|get开头;
  • 涉及条件查询时,条件的属性用关键字连接;
  • 条件属性首字母大写;
  • 支持级联属性。若当前类有符合条件的属性时,优先使用而不使用级联属性。若想使用级联属性,则属性之间用 _ 进行连接。

如下所示:

//根据 lastName 来获取对应的 Person
Person getByLastName(String lastName);

//WHERE lastName LIKE ?% AND id < ?
List<Person> getByLastNameStartingWithAndIdLessThan(String lastName, Integer id);

//WHERE lastName LIKE %? AND id < ?
List<Person> getByLastNameEndingWithAndIdLessThan(String lastName, Integer id);

//WHERE email IN (?, ?, ?) OR birth < ?
List<Person> getByEmailInAndBirthLessThan(List<String> emails, Date birth);

② 支持级联查询

如User类中有属性为Address类。

User类如下:

@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tb_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
public class User implements Serializable{

    @Id //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
    private Integer id;

    @Column(name = "last_name",length = 50) //这是和数据表对应的一个列
    private String lastName;

    @Column //省略默认列名就是属性名
    private String email;

    @ManyToOne
    @JoinColumn(name = "address_id")
    private Address address;
    //...
}

UserRepository中添加方法如下:

List<User> getByAddressIdGreaterThan(Integer id);

进行测试,查看控制台打印SQL:

SELECT
    user0_.id AS id1_1_,
    user0_.address_id AS address_4_1_,
    user0_.email AS email2_1_,
    user0_.last_name AS last_nam3_1_
FROM
    tb_user user0_
LEFT OUTER JOIN tb_address address1_ ON user0_.address_id = address1_.id
WHERE
    address1_.id >?

其默认使用左外连接对tb_address表进行级联查询,根据tb_address表的id进行判断。


③ 如果User中有个自身属性为addressId,怎么处理?

User如下所示:

@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tb_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
public class User implements Serializable{

    @Id //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
    private Integer id;

    @Column(name = "last_name",length = 50) //这是和数据表对应的一个列
    private String lastName;

    @Column //省略默认列名就是属性名
    private String email;

    @ManyToOne
    @JoinColumn(name = "address_id")
    private Address address;

    @Column(name = "add_id")
    private int addressId;
    //...
}

此时再次测试接口方法:

 List<User> getByAddressIdGreaterThan(Integer id);

查看控制台打印SQL:

SELECT
    user0_.id AS id1_1_,
    user0_.address_id AS address_5_1_,
    user0_.add_id AS add_id2_1_,
    user0_.email AS email3_1_,
    user0_.last_name AS last_nam4_1_
FROM
    tb_user user0_
WHERE
    user0_.add_id >?

默认直接使用tb_user表的add_id(即User的私有addressId属性)进行查询!


那么此时还想根据Address.id进行查询怎么办?

若当前类有符合条件的属性时,优先使用当前类自身属性而不使用级联属性。若想使用级联属性,则属性之间用 _ 进行连接。

如下所示:

//WHERE a.id > ?
List<User> getByAddress_IdGreaterThan(Integer id);

查看控制台打印SQL:

SELECT
    user0_.id AS id1_1_,
    user0_.address_id AS address_5_1_,
    user0_.add_id AS add_id2_1_,
    user0_.email AS email3_1_,
    user0_.last_name AS last_nam4_1_
FROM
    tb_user user0_
LEFT OUTER JOIN tb_address address1_ ON user0_.address_id = address1_.id
WHERE
    address1_.id >?

使用左外连接对tb_address表进行级联查询,根据tb_address表的id进行判断。


【2】@Query注解

如果查询接口不符合命名规范呢,如果想使用自定义查询,比如子查询呢?

上面所讲述的方法将失效,此时就要用到@Query注解,注解里面使用JPQL语言或者普通SQL查询。

① 使用JPQL

关于JPQL参考博文:JPQL语言和Query接口JPQL查询实例

如下所示,查询id最大的用户:

@Query("select u from User u where u.id=(select max(u2.id) 
from User u2)")
User getMaxIdPerson(Integer id);

查看控制台打印SQL如下:

SELECT
    user0_.id AS id1_1_,
    user0_.address_id AS address_5_1_,
    user0_.add_id AS add_id2_1_,
    user0_.email AS email3_1_,
    user0_.last_name AS last_nam4_1_
FROM
    tb_user user0_
WHERE
    user0_.id = (
        SELECT
            max(user1_.id)
        FROM
            tb_user user1_
    )

② JPQL参数传递

怎么往@Query注解中的JPQL中传递参数呢?两种方式:索引参数和命名参数。

  • 索引参数

索引参数如下所示,索引值从1开始,查询中 ”?X”个数需要与方法定义的参数个数相一致,并且顺序也要一致。

实例如下:

@Query("select u from User u where u.lastName=?1 and u.email=?2")
User testQueryAnnotationParams1(String lastName,String email);

查看控制台打印SQL如下:

SELECT
    user0_.id AS id1_1_,
    user0_.address_id AS address_5_1_,
    user0_.add_id AS add_id2_1_,
    user0_.email AS email3_1_,
    user0_.last_name AS last_nam4_1_
FROM
    tb_user user0_
WHERE
    user0_.last_name =?
AND user0_.email =?

  • 命名参数

可以定义好参数名,赋值时采用@Param(“参数名”),而不用管顺序。推荐使用这种方式。

实例如下:

@Query("select u from User u where u.lastName=:lastName and u.email=:email")
User testQueryAnnotationParams2(@Param("lastName") String lastName, @Param("email") String email);

查看控制台打印SQL如下:

SELECT
    user0_.id AS id1_1_,
    user0_.address_id AS address_5_1_,
    user0_.add_id AS add_id2_1_,
    user0_.email AS email3_1_,
    user0_.last_name AS last_nam4_1_
FROM
    tb_user user0_
WHERE
    user0_.last_name =?
AND user0_.email =?

命名参数对比索引参数,其使用起来并没有什么大的差别。但是还是推荐在自定义使用JPQL查询时,使用命名参数,参数名一一对应,不容易混淆。

注意:如果使用命名参数,方法参数处必须使用@Param指定参数名!


③ Query中有like关键字

如果是 @Query 中有 LIKE 关键字,后面的参数需要前面或者后面加 %,这样在传递参数值的时候就可以不加 %:

//参数后面添加%
@Query("select u from User u where u.lastName like ?1%")
public List<User> findBylastName (String lastName );

//参数前面添加%
@Query("select u from User u where u.lastName like %?1")
public List<User> findBylastName (String lastName );

//参数前后添加%
@Query("select u from User u where u.lastName like %?1%")
public List<User> findBylastName (String lastName );

这里以参数后面添加%为例,查询控制台打印SQL如下所示:

SELECT
    user0_.id AS id1_1_,
    user0_.address_id AS address_5_1_,
    user0_.add_id AS add_id2_1_,
    user0_.email AS email3_1_,
    user0_.last_name AS last_nam4_1_
FROM
    tb_user user0_
WHERE
    user0_.last_name LIKE ?

其实也可以不在@Query中写%,而是传参过来。但是我想,你不会喜欢在参数中添加%的!


④ Native Query

就是想使用原生SQL查询怎么做?SpringData同样支持!

可以使用@Query来指定本地查询,只要设置nativeQuery为true。

示例如下:

@Query(nativeQuery = true,value = "select count(1) from tb_user")
long getTotalCount();

控制台打印SQL如下:

Hibernate: select count(1) from tb_user

【3】@Modifying 注解和事务

可以通过自定义的JPQL完成update和delete操作,JPQL不支持insert操作。

在@Query中编写JPQL语句进行update或者delete时,必须使用@Modifying注解,以通知SpringData这是一个update或者delete操作。

在update或者delete操作时,需要使用事务;此时需要在Service实现类的方法上声明事务@Transactional。

① @Query 与 @Modifying 执行更新操作

@Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用。

  • 不用@Modifying执行更新

接口方法如下:

@Query("update User  u set u.email = :email where u.id = :id")
int updateEmailById(@Param("id") Integer id,@Param("email") String email);

尝试进行操作抛异常:

org.hibernate.hql.internal.QueryExecutionRequestException:
Not supported for DML operations [update com.jane.model.User  u
set u.email = :email where u.id = :id]

意思是说不支持的数据库操作,关于DML科普如下:

DML(data manipulation language)数据操纵语言:就是我们最经常用到的 SELECT、UPDATE、INSERT、DELETE。 主要用来对数据库的数据进行一些操作
.
DDL(data definition language)数据库定义语言:其实就是我们在创建表的时候用到的一些sql,比如说:CREATE、ALTER、DROP等。DDL主要是用在定义或改变表的结构,数据类型,表之间的链接和约束等初始化工作上。
.
DCL(Data Control Language)数据库控制语言:是用来设置或更改数据库用户或角色权限的语句,包括(grant,deny,revoke等)语句。


  • 用@Modifying执行更新
@Modifying
@Query("update User  u set u.email = :email where u.id = :id")
int updateEmailById(@Param("id") Integer id,@Param("email") String email);

再次测试如下:

javax.persistence.TransactionRequiredException: 
Executing an update/delete query

意思是说执行update或者delete操作时,必须显示声明事务!


② 事务

Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。

对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明 。

进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。

Service实现类如下:

@Service
public class UserServiceImpl implements UserServcie{

    @Autowired
    UserRepository userRepository;

    @Transactional//这里声明事务
    public int updateEmailById(Integer id, String email) {
        System.out.println("进入Service方法。。。");
        int i = userRepository.updateEmailById(id, email);
        return i;
    }
}

查询控制台打印SQL如下:

Hibernate: update tb_user set email=? where id=?

在以前项目中 @Transactional一般是放在Service接口中的,并非实现类中。但是在这里放在接口中不行仍然抛出上面那个需要事务的异常。

这里写图片描述

猜你喜欢

转载自blog.csdn.net/J080624/article/details/82559318