Spring Data 提供了几个接口供继承使用,如 JpaRepository,另外还规定了方法查询中的关键字,即你命名的查询方法需要符合规范。
详情参考:SpringBoot整合Spring Data JPA、SpringDataJPA入门。
本篇博文详细记录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接口中的,并非实现类中。但是在这里放在接口中不行仍然抛出上面那个需要事务的异常。