W
e
l
c
o
m
e
: )

【SpringData&JPA从入门到精通】02-SpringData-@Query 与 @Modifying

笔记来源:尚硅谷SpringData教程(springdata经典,spring data快速上手)

@Query 与 @Modifying

1、@Query 注解

1.1、自定义查询

这种查询可以声明在 Repository 方法中,摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰,这是 Spring data 的特
有实现

@Query("SELECT C FROM Customer c WHERE c.customerId=?1")
Customer testGetByCustomerId2(Integer id);

测试方法

PersonRepository 类

@Query("select p from Person p where p.id=(select max(id) from Person p)")
Person getMaxIdPerson();

Test 类

@Test
public void testGetMaxIdPerson() {
    Person person = repository.getMaxIdPerson();
    System.out.println(person);
}

日志信息

Hibernate: 
    select
        person0_.id as id1_1_,
        person0_.add_id as add_id6_1_,
        person0_.address_id as address_2_1_,
        person0_.birth_day as birth_da3_1_,
        person0_.email as email4_1_,
        person0_.last_name as last_nam5_1_ 
    from
        jpa_persons person0_ 
    where
        person0_.id=(
            select
                max(person1_.id) 
            from
                jpa_persons person1_
        )
Person{id=27, lastName='XY', email='XY@qq.com', birthDay=2022-05-21 22:48:34.0}

结论:使用 @Query 注解可以自定义 JPQL 语句以实现更灵活的查询

1.2、索引参数与命名参数

索引参数

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

@Modifying
@Query("update User u set u.firstname=?1 where u.lastname=?2")
int setFixedFirstnameFor(String firstname,String lastname);

命名参数

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

@Query("select cirom customer u where c.firstname=:firstname or c.lastname=:lastname")
Customer findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname);

测试方法 1

PersonRepository 类

@Query("select p from Person p where p.lastName=?1 and p.email=?2")
Person getByLastNameAndEmail(String lastName, String email);

Test 类

@Test
public void testGetByLastNameAndEmail() {
    Person person = repository.getByLastNameAndEmail("AA", "AA@qq.com");
    System.out.println(person);
}

日志信息

Hibernate: 
    select
        person0_.id as id1_1_,
        person0_.add_id as add_id6_1_,
        person0_.address_id as address_2_1_,
        person0_.birth_day as birth_da3_1_,
        person0_.email as email4_1_,
        person0_.last_name as last_nam5_1_ 
    from
        jpa_persons person0_ 
    where
        person0_.last_name=? 
        and person0_.email=?
Person{id=1, lastName='AA', email='AA@qq.com', birthDay=2022-05-21 22:48:34.0}

测试方法 2

PersonRepository 类

@Query("select p from Person p where p.lastName=:lastName and p.email=:email")
Person getByEmailAndLastName(@Param("email") String email, @Param("lastName") String lastName);

Test 类

@Test
public void testGetByEmailAndLastName() {
    Person person = repository.getByEmailAndLastName("BB@qq.com", "BB");
    System.out.println(person);
}

日志信息

Hibernate: 
    select
        person0_.id as id1_1_,
        person0_.add_id as add_id6_1_,
        person0_.address_id as address_2_1_,
        person0_.birth_day as birth_da3_1_,
        person0_.email as email4_1_,
        person0_.last_name as last_nam5_1_ 
    from
        jpa_persons person0_ 
    where
        person0_.last_name=? 
        and person0_.email=?
Person{id=2, lastName='BB', email='BB@qq.com', birthDay=2022-05-21 22:48:34.0}

1.3、模糊查询

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

右模糊

@Query("select o from UserModel o where o.name like ?1%")
public List<UserModel> findByUuidOrAge(String name);

左模糊

@Query("select o from UserModel o where o.name like %?1")
public List<UserModel> findByUuidOrAge(String name);

全模糊

@Query("select o from UserModel o where o.name like %?1%")
public List<UserModel> findByUuidOrAge(String name);

测试方法 1

PersonRepository 类

@Query("select p from Person p where p.lastName like %?1% or p.email like %?2%")
List<Person> findLikeLastNameOrEmail(String lastName, String email);

Test 类

@Test
public void testFindLikeLastNameOrEmail() {
    List<Person> personList = repository.findLikeLastNameOrEmail("A", "BB");
    System.out.println(personList);
}

日志信息

Hibernate: 
    select
        person0_.id as id1_1_,
        person0_.add_id as add_id6_1_,
        person0_.address_id as address_2_1_,
        person0_.birth_day as birth_da3_1_,
        person0_.email as email4_1_,
        person0_.last_name as last_nam5_1_ 
    from
        jpa_persons person0_ 
    where
        person0_.last_name like ? 
        or person0_.email like ?
[Person{id=1, lastName='AA', email='AA@qq.com', birthDay=2022-05-21 22:48:34.0}, Person{id=2, lastName='BB', email='BB@qq.com', birthDay=2022-05-21 22:48:34.0}]

测试方法 2

PersonRepository 类

@Query("select p from Person p where p.lastName like %:lastName% or p.email like %:email%")
List<Person> findLikeEmailOrLastName(@Param("email") String email, @Param("lastName") String lastName);

Test 类

@Test
public void testFindLikeEmailOrLastName() {
    List<Person> personList = repository.findLikeEmailOrLastName("DD", "C");
    System.out.println(personList);
}

日志信息

Hibernate: 
    select
        person0_.id as id1_1_,
        person0_.add_id as add_id6_1_,
        person0_.address_id as address_2_1_,
        person0_.birth_day as birth_da3_1_,
        person0_.email as email4_1_,
        person0_.last_name as last_nam5_1_ 
    from
        jpa_persons person0_ 
    where
        person0_.last_name like ? 
        or person0_.email like ?
[Person{id=3, lastName='CC', email='CC@qq.com', birthDay=2022-05-21 22:48:34.0}, Person{id=4, lastName='DD', email='DD@qq.com', birthDay=2022-05-21 22:48:34.0}]

1.4、本地查询

还可以使用 @Query 来指定本地查询,只要设置 nativeQuerytrue,比如:

@Query(value="select * from tbl_user where name like %?1", nativeQuery=true)
public List<UserModel> findByUuidOrAge(String name);

测试方法

PersonRepository 类

@Query(value = "select count(1) FROM jpa_persons", nativeQuery = true)
int getTotalCount();

Test 类

@Test
public void testGetTotalCount() {
    int totalCount = repository.getTotalCount();
    System.out.println(totalCount);
}

日志信息

Hibernate: 
    select
        count(1) 
    FROM
        jpa_persons
27

2、@Modifying 注解

2.1、执行更新操作

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

@Modifying
@Query("UPDATE Customerg SET c.customerName=?1")
int updateCustomerName(String cn);

注意:

  • 方法的返回值应该是 int,表示更新语句所影响的行数
  • 在调用的地方必须加事务,没有事务不能正常执行

测试方法 1

不加 @Modifying 注解会怎么样?

PersonRepository 类

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

Test 类

@Test
public void testUpdateEmailById() {
    int updateCount = repository.updateEmailById("aaaa@qq.com", 1);
    System.out.println(updateCount);
}

日志信息

org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [update com.vectorx.springdata.entities.Person p set p.email=:email where p.id=:id]; nested exception is java.lang.IllegalStateException: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [update com.vectorx.springdata.entities.Person p set p.email=:email where p.id=:id]
	//...
Caused by: java.lang.IllegalStateException: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [update com.vectorx.springdata.entities.Person p set p.email=:email where p.id=:id]
	//...
Caused by: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [update com.vectorx.springdata.entities.Person p set p.email=:email where p.id=:id]

报错抛出了 QueryExecutionRequestException 异常,提示 Query 不支持 DML 更新操作

测试方法 2

仅仅加 @Modifying 注解会怎么样?

PersonRepository 类:唯一区别就是加上 @Modifying 注解

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

日志信息

org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
	//...
Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query
	//...

报错抛出了 TransactionRequiredException 异常,表明更新或删除操作必须要有一个事务

2.2、事务

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

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

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

测试方法 3

@Modifying@Transactional 注解都加上

PersonService 类

@Service
public class PersonService
{
    @Autowired
    private PersonRepository personRepository;

    @Transactional
    public int updateEmailById(String email, Integer id) {
        return personRepository.updateEmailById(email, id);
    }
}

Test 类

public class SpringDataTest
{
    private ApplicationContext context;
    private PersonRepository repository;
    private PersonService service;
    {
        context = new ClassPathXmlApplicationContext("applicationContext.xml");
        repository = context.getBean(PersonRepository.class);
        service = context.getBean(PersonService.class);
    }

    // ...
    
    @Test
    public void testUpdateEmailById() {
        int updateCount = service.updateEmailById("aaaa@qq.com", 1);
        System.out.println(updateCount);
    }
}

日志信息

Hibernate: 
    update
        jpa_persons 
    set
        email=? 
    where
        id=?
1

数据表 JPA_PERSONS 数据

image-20220525214542067

结论

1、可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作。注意:JPQL 不支持使用 INSERT

2、在 @Query 注解中编写 JPQL 语句,但必须使用 @Modifying 进行修饰。以通知 SpringData 这是一个 UPDATE 或 DELETE 操作

3、UPDATE 或 DELETE 操作需要使用事务,此时需要定义 Service 层。在 Service 层的方法上添加事务操作

4、默认情况下,SpringData 的每个方法上有事务,但都是一个只读事务。他们不能完成修改操作!

总结

本节主要掌握 @Query@Modifying 两个注解:

  • @Query 注解支持索引参数、命名参数、模糊查询和本地查询
  • @Modifying 注解支持更新操作,如 update 和 delete,但不支持 insert(准确来说,JPQL 不支持 insert);需要结合 @Query@Transactional 注解使用

附上导图,仅供参考

@Query 与 @Modifying

posted @ 2022-05-25 22:06  VectorX  阅读(130)  评论(0编辑  收藏  举报