Appearance
JPQL
除了方法命名查询,Spring Data Jpa还提供了一种类似于SQL语句面向对象的查询语句JPQL
相对于命名查询,JPQL涵盖更多的数据库操作,它可以做到命名查询无法做到的复杂的SQL操作,比如连接查询、批量更新和删除、分组聚合等
- JPQL语法的基本概念是语句中出现的不再是表名、列名,而是java实体类名和属性名,并且区分大小写。JPQL的关键字和SQL的一样,不区分大小写。当查询全属性时,不能使用"select *"、而要通过"select 别名"的形式查询
对比:
// SQL,查询所有
select * from employee e
// JPLQ语句,查询全属性
select e from Employee e
// JPQL语句,查询全属性的一种简写形式
from Employee e
// SQL语句,查询name列
select e.name from employee e
// JPQL语句,查询name列
select e.name from Employee e
通过@Query注解在持久层的方法上书写JPQL语句,这样当调用该方法时就会通过JPQL与定义的逻辑操作数据库
语法
运算符
- jpql提供算术运算符,其算术运算符和标准的sql一致
sql
select emp from Employee emp where (emp.age - 5) = 20
- jpql提供的关系运算,其关系运算符与标准的sql一致
sql
select emp from Employee whhere emp.age > 20
- JPQL的逻辑运算符符合java面向对象的特性,有between and、like、in、is null、is empty、not、and、or,其中is null用来判断普通属性值是否为null,is empty用来判断属性值是否为null
sql
select emp from Employee emp where emp emp.age between 20 and 30
函数
字符串有concat、substring、trim、upper、lower、locate等,其中concat用来拼接字符串,trom用来去掉字符串的前后空格,length用来得到字符串的长度,locate是定位函数
时间函数有CURRENT_DATE(日期)、CURRENT_TIME(时间)和CURRENT_TIMESTAMP(日期时间)
算数函数有ABS(绝对值)、SQRT(平方根)、MOD(取模)、SIZE(集合数量)
分组查询
和sql一样,jpql也使用group by ...having
聚合函数有avg(distint)、sum(distint)、count(distint)、min(distint)、max(distint)
连接查询
- jPQL的连接查询和SQL的查询显著不同,根本原因是其类与类的关系是包含关系,连接时可以通过自身属性直接关联
sql
select emp.name, emp.userNumber, dept.deptCode from Employee left join emp.dept dept
- 使用fetch可以及时查询相关联的数据,但该数据只包含在使用对象中,不会出现在结果集中
sql
select emp from Employee left join fetch emp.dept
参数查询
- 如果语句需要获取动态的参数,则可以使用将" :参数名 "作为占位符的形式指定动态参数,参数名只需要和方法的形式参数名保持一致即可
java
@Query(value = "select emp from Employee emp where emp.name = :name")
List<Employee> findByName(String name);
- 如果使用实体类型的参数,则需要使用" #{} "的形式,并且实体参数前也要加" # "号。
- 注意:必须增加@Param("employee"),否则传入的实体类对象属性无法注入,会报gender为null
java
@Query(value = "select emp from Employee emp where emp.gender like concat('%', :#{#employee.gender}, '%') ")
List<Employee> findByGender(@Param("employee") Employee employee);
java
@Test
public void test1() {
Employee emp = new Employee();
emp.setGender("男");
List<Employee> byGender = employeeRepository.findByGender(emp);
for (Employee employee : byGender) {
logger.info(employee.getName());
}
}
本地SQL
- Spring data Jpa处理提供JPQL查询,也给开发者提供了使用原生sql的权利,即使用@Query注解的权利。当value属性中添加sql语句时,可以使用nativeQuery=true来标记这是一条原生sql
- 但一般不推荐使用这种方法,因为使用它相当于使用mybatis框架,将sql与java分离,并将结果集整理成集合返回,这不溯河spring data jpa的ORM思想
java
@Query(value = "select emp.name from employee emp where age between :startAge and :endAge ;", nativeQuery = true)
List<String> findByAgeBetween(@Param("startAge") Integer startAge, @Param("endAge") Integer endAge);
java
@Test
public void test2() {
List<String> list = employeeRepository.findByAgeBetween(9, 11);
for (String name : list) {
System.out.println(name);
}
}
- 注意返回值类型必须是:
List<String> 不可以是 List<Employee>
否则会报nested exception is org.hibernate.exception.SQLGrammarException: could not execute query
异常。需要根据查询数据,匹配返回值类型,只能使用Map<String, Object>进行接收。 如下示例:
@Query(value = "select emp.name, emp.user_number from employee emp where age between :startAge and :endAge ;", nativeQuery = true)
List<Map<String, Object>> findByAgeBetween(@Param("startAge") Integer startAge, @Param("endAge") Integer endAge);
java
@Test
public void test2() {
List<Map<String, Object>> list = employeeRepository.findByAgeBetween(9, 11);
for (Map employee : list) {
logger.info((String) employee.get("name"));
}
}
- 也不能自定义DTO类进行接收,JPA 原生 sql 的返回不能直接转换成对象,原生 sql 的返回形式是 Map 、
List<Map>
或 Object,因此可以使用List<Map<String,Object>>
接收
增删改
- jpql没有提供inset方法,这是因为insert操作不涉及条件筛选,通过JpaRepository提供的save方法即可
- 如果update操作通过save方法进行修改,则只能通过id的方式筛选对象。如果想通过其他属性找到对象并进行修改,则save方法无法满足需求,同理delete操作也有通过非id属性筛选的情况。即如果想要通过id属性进行修改和删除操作,则save方法和deleteById方法就可以满足需求,但是如果西昂要通过其他属性进行修改和删除,则需要通过JPQL方式实现
- 如果要进行修改和删除的JQPL操作,则要在该方法活该类上添加@Tansactional事务注解,另外还需要在方法上加@Modifying注解,该注解标记此方法是一个修改和删除的方法,其返回类型是void或者Integer。整数结果的值是数据库受到影响的行数
java
@Transactional
@Modifying
@Query(value = "update Employee emp set emp.name = :name where emp.userNumber = :userNumber")
Integer updateNameByUserNumber(@Param("name") String name, @Param("userNumber") Integer userNumber);