Spring Data JPA之JPQL的应用
JPQL全称Java Persistence Query Language。
是一种Java持久化查询语言,一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起·使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。
其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性。
SQL是面向对象关系数据库,他操作的是数据表和数据列。
这就是两者的区别之处。
JPQL所提供的查询语法主要分为三类:
1.查询用的 SELECT 语法
2.更新用的 UPDATE 语法
3.删除用的 DELETE 语法
学习过Hibernate的同鞋应该知道,在Hibernate中,有一个HQL,其实JPQL和HQL是极其相似的。
JPQL——查询数据
现在就来演示JPQL的应用。
从官网上可以看到应用的示例,使用的是占位符,在占位符后面加上的是参数的位置,第1个参数位置就是1,通过这种方式,来绑定请求方法中的参数:
@Query(" from User as u where u.username = ?1")
List<User> findByUsername(String username);
先在UserDao中,添加一个以用户名和密码查询的接口方法,使用@Query注解来进行JPQL的应用:
在上面的示例中,可以看到@Query注解的value值有两种写法,一个是有select,一个是简写,使用哪一种都是可以的。
需要注意的就是,在JPQL中有两个占位符,分别指定应用了方法上的两个参数,这里是故意把参数的顺序放反的,目的是让大家进一步认识到,JPQL中占位符的取值是如何处理的,所以,这里一定要注意了,这是JQPL的应用规则。(在SpringBoot1.5X版本中,占位符可以不加参数位置索引的,但必须与占位符对应,从2.x开始去除这种应用方式。)
Dao编写好了,现在就来完成请求的映射。
定义一个测试类,在类中新建一个测试方法,代码如下:
@SpringBootTest
class SpringbootDataJpaApplicationTests {
@Autowired
private UserDao userDao;
@Test
@Test
void queryUsers(){
List<User> users = userDao.findByUsername("张三");
for(User user1 :users){
System.out.println(user1.getUsername());
}
}
}
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.birthday as birthday3_0_, user0_.password as password4_0_, user0_.sex as sex5_0_, user0_.username as username6_0_ from user user0_ where user0_.username=?
张三
要注意了,查询所有属性的数据时,select是可以省略不写的
JPQL——模糊排序查询
在UserDao中,添加方法与JPQL,要求是:根据用户名进行模糊查询,以主键进行降序排序。
//添加方法与JPQL,要求是:根据用户名进行模糊查询,以主键进行降序排序
@Query("select u from User as u where u.username like %?1% order by u.id desc ")
List<User> findUserLikeByUsernameSortUid(String username);
@Query("select u from User as u where u.username like %:name% order by u.id desc ")
List<User> findUserLikeByUsernameSortUid2(@Param("name") String username);
@Query("select u from User as u where u.username like %:#{#username}% order by u.id desc ")
List<User> findUserLikeByUsernameSortUid3(@Param("user") User user);
在实际的开发中,有的时候会接收很多的参数,如果都分别在方法上逐一定义出来,那么方法体看上去会非常的臃肿,这个时候就需要把参数进行封装了,也就是说,参数是一个实体类,那么从实体类中获取指定的属性数据,就需要注意了,在JPQL中,要获取对象的属性值,就要使用这样的方式==> :#{#对象名.属性名}
在进行操作之前,还需要给参数使用@Param来对参数进行指定别名,这样才可以使用参数读取的方式来实现数据的获取,参数读取的方式【:参数别名】
这里一定要注意了,语法规则是什么样的。
同时,在模糊查询中,匹配符可以直接给出,使用的是百分号,当然,也可以在JPQL不使用,但在参数中就必须要给了,否则模糊查询有可能得不到数据。
需要注意的是,在官网中,有这样的一个应用表达式:#{#entityName}
这个表达式的功能是引用@Entity标注的实体类,比如我们的User类。这是动态获取类的方式,也可以理解为一个变量。在使用的过程中有一个注意事项,那么就是#{#entityName}和:#{#对象名.属性名}不能同时出现,要么使用#{#entityName},配合?占位符,要么直接给出类名,使用:#{#对象名.属性名}。
也就是说,使用#{#entityName}的时候,如果执行的方法中参数是对象的,无法获取对象中的属性值,两者一起使用会报错,因为都是变量,在不确定的情况下,编译无法通过。
接下来在测试类中,添加查询的方法:
@Test
void queryUsersLike(){
// List<User> users = userDao.findUserLikeByUsernameSortUid("三");
List<User> users = userDao.findUserLikeByUsernameSortUid2("张");
for(User user1 :users){
System.out.println(user1.getUsername());
}
}
JPQL——聚合函数应用
在查询数据库时,还需要进行统计之类的算术操作,比如求和,求平均,统计数量等等。在JPQL中,也是可以完成这些操作的。
先来在User类中添加一个age属性,使用这个属性来进行测试:
@JsonIgnoreProperties(value = "hibernateLazyInitializer")//忽略属性
@Entity //标注这是一个数据库映射的实体类
public class User {
@Id //标注这是一个主键属性
//主键生成策略
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 16) //普通字段,长度为varchar(16),此时列名就是属性名
private String username;
private String password;
@Column(length = 2) //指定字段名
private String sex;
private Integer age;
private Date birthday;
//省略get/set方法
}
这个属性是新增的,而我们在配置数据表生成策略的时候使用的是update,所以,当服务器启动的时候,就会帮助我们新增一个age字段到数据表中。
现在来定义UserDao方法:
@Query("select avg(u.age) as 平均分,sum(u.age) as 总年龄,count(u.id) as 数据量 from User as u")
Map<String,Object> getMathData();
在使用算术运算时,可以同时查询多个数据,这也是投影查询中的一种,默认的情况下,查询返回的是一个List集合,在集合中,是一个Object[]数组,我们可以使用Map来进行接收,这样就不会这么麻烦。
定义一个测试方法:
@Test
void queryMath(){
Map<String,Object> data = userDao.getMathData();
data.forEach((k,v)-> System.out.println(k + "==" + v));
}
测试结果:
Hibernate: select avg(user0_.age) as col_0_0_, sum(user0_.age) as col_1_0_, count(user0_.id) as col_2_0_ from user user0_
平均分==44.3333
总年龄==133
数据量==4
JPQL——属性投影查询
好了,有了聚合查询的示例,再来使用属性投影查询就方便的多了,现在来查询所有数据的username和age、sex三个数据,其他的咱不要。
定义出相应在的方法:
@Query("select u.username as username,u.password as password,u.sex as sex from User as u")
List<Map<String,Object>> findUsers();
@Query("select new User(username,password,sex) from User")
List<User> findUsers2();
投影查询,可以使用两种形式,一个是使用map封装,一个是使用实体类封装。要使用类封装,就必须要有相应的构造方法。
public User(String username,String password,String sex) {
this.username = username;
this.password = password;
this.sex = sex;
}
通过构造方法,传递的数据不需要as取别名,只需要把查询到的数据进行传递就OK了,3种写法都是可以实现数据的传递。
如果是map接收,那么就必须使用as取别名,否则,key都是null。
Dao接口方法和请求方法都定义完成,就可以进行测试了,定义测试方法
@Test
void queryUsers2() {
List<Map<String, Object>> users = userDao.findUsers();
users.forEach(map -> {
map.forEach((k,v)-> System.out.println(k + "==" + v));
});
}
@Test
void queryUsers3() {
List<User> users = userDao.findUsers2();
users.forEach(System.out::println);
}
JSON的序列化问题
如果把数据进行JSON化,那么就需要注意了。
第1种方法,就是使用jackson工具提供的注解@JsonInclude,将它标记放在属性上,如果该属性为NULL则不参与序列化,如果放在类上边,那对这个类的全部属性起作用。
它的值有:
1.Include.Include.ALWAYS 默认
2.Include.NON_DEFAULT 属性为默认值不序列化
3.Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化
4.Include.NON_NULL 属性为NULL 不序列化
我们希望User类中所有的属性,只要是null值就不参与序列化:
这种方法是指定jackson工具JSON序列化的方式,在启动类中,注入一个jackson工具中序列化对象ObjectMapper对象:
@Bean
public ObjectMapper objectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object paramT, JsonGenerator paramJsonGenerator,
SerializerProvider paramSerializerProvider) throws IOException {
//设置返回null转为 空字符串""
paramJsonGenerator.writeString("");
}
});
return objectMapper;
}
可以看到,除了使用注解来指定外,还可以使用方法来指定,就是通过ObjectMapper对象来完成这个功能的。
这样是全局的应用方式,现在把User上使用的@JsonInclude注解去掉,重启服务器再测试:
搞定
JPQL——修改、删除数据
@Modifying //使用JPQL来进行测试或操作
@Transactional //事务管理
@Query("update User set password = ?1 where id = ?2")
int updateUserByUid(String password,Integer uid);
@Modifying //使用JPQL来进行删或改操作
@Transactional //事务管理
@Query("delete from User where id = ?1")
int deleteUserByUid(Integer uid);
在UserDao中定义的接口方法上,使用了@Modifying注解,这是因为JPQL默认的情况下,是用来做查询操作的,如果要使用到修改和删除的操作上,就必须要添加上@Modifying注解,告诉程序,这是一个修改或者删除的操作,否则就会出现如下所示的异常:
org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML
operations [UPDATE com.edu118.entity.User SET username = :__$synthetic$__1,password =
:__$synthetic$__2 WHERE uid = :__$synthetic$__3]
而要实现数据的修改或者删除操作,就要使用到事务,默认是没有事务的,所以,要使用上@Transactional注解,意思就是当前的方法添加事务的应用,如果没有添加,就会出现如下所示的异常:
javax.persistence.TransactionRequiredException: Executing an update/delete query
因此,出现这两个异常的情况,就应该知道哪里出现了问题,非常容易解决。
方法定义完成之后,就可以进行测试了,定义测试方法:
@Test
void deleteData(){
userDao.deleteUserByUid(2);
}
@Test
void updateData() {
userDao.updateUserByUid("abaaaa", 3);
}
测试的结果省略!
方法名映射查询
在Spring Data JPA的应用中,还提供了一种非常方便的操作方式,那就是通过方法名来确定查询的功能。
官网参考地址:
https://docs.spring.io/spring-data/jpa/docs/2.3.0.RELEASE/reference/html/#jpa.query-methods
那么,什么是以方法名来确定查询功能呢???
比如现在UserDao中有一个接口方法:
//@Query("select u from User as u where u.username like %?1% order by u.uid desc")
List<User> findUsersByUsernameIsLikeOrderByUidDesc(String username);
//@Query("select u from User as u where u.username = ?1 and u.password = ?2")
List<User> findUsersByUsernameAndPassword(String username,String password);
现在,在JPQLController中,添加一个请求方法:
@Test
void queryUsers4() {
//List<User> users = userDao.findUsersByUsernameAndPassword("信盈达科技", "123456");
List<User> users = userDao.findUsersByUsernameIsLikeOrderByUidDesc("%达%");
users.forEach(System.out::println);
}
测试结果省略。这就是方法名映射查询操作功能。
在做一些简单的常规的操作时,就可能使用这种方式,通过方法名来指定查询的条件,这是Spring Data JPA提供的一个快捷应用方式,这种应用方式,也是有应用规则的,那就是方法名的定义,可以在官网上得到参考:
https://docs.spring.io/spring-data/jpa/docs/2.3.0.RELEASE/reference/html/#jpa.query-methods.query-creation
现在就来看看,方法定义的规则:
方法名必须是以findBy或者是find + 类名 + By开头。
比如示例中使用的是findByUsernameAndPassword(),也可以使用findUserByUsernameAndPassword()。
完整的方法名规则:
findBy | find类名By + 属性名(首字母大写) + 查询方式 + 条件连接符 + 属性名 + 查询方式…
每一个属性就是一个查询的条件,在实体类中,属性名首字母是小写的,在这里要变成大写,属性与属性之间,使用条件连接符拼接,这些连接符也是关键字。
条件连接符有AND和OR,还有比较的,还有分组、模糊查询等等查询方式,根据你的需求来使用就可以了。
方法名的定义方式有了,还需要注意另外一点,那就是参数的定义,使用方法名映射的方式,参数的定义一定要与方法名中的条件完全一致,不参是参数的数据类型,还是参数的顺序,都必须要一样,否则有可能查询数据失败。
这种方式,只做查询。
サクラチル | AO FUJIMORI #Pixiv