简单及记录一下相关的使用,后续会对这部分内容进行补充。
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,底层使用了 Hibernate 的 JPA 技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。
1、基本使用
所需依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
数据库连接信息的配置:
spring.datasource.url=jdbc:mysql://localhost:3306/learn?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#######下面的配置是为解决项目启动时遇到的异常
#异常内容:org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set错误
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
######Hibernate中常用的配置
spring.jpa.properties.hibernate.hbm2ddl.auto=update
# 配置指定对数据库表结构的处理方式,值有:create、create-drop、update、validate
#
# create:每次加载hibernate的时候,都会重新根据模型生成表。如果表已存在,会先删除该表再生成。
# create-drop:启动项目加载hibernate的时候,会生成表。停止项目时,会把生成的表删除掉。
# update:常用属性。每次加载hibernate的时候,会生成表。如果表存在,会根据模型的属性变化来更新表结构,这个过程不会做删表处理。
# validate:每次加载hibernate的时候,会检查表结构,但不会生成表。
spring.jpa.show-sql=true # 打印sql
创建实体类:
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
@Entity
@Data
@Table(name = "user")
public class User {
@Id
private String id;
private String name;
private String password;
}
注意:@Entity和@Id注解一定要有。
创建UserRepository继承自JpaRepository<T,TD>:
public interface UserRepository extends JpaRepository<User, String> {
User findByName(String username);
}
JpaRepository<T,TD>中T表示数据表模型对应的类,TD表示主键id的类型。
注意:
- 使用自定义的字段来查询时,可以参考文档 jpa查询文档,同时
需要注意字段要与数据库中的保持一致原则
;例如:数据库中字段为course_id,实体类中为courseId,则方法名应为findByCourseId;这样就可以匹配(方法名的命名规则是由hibernate的解析方式所决定的,如:解析时只会将courseId与数据库中的course_id字段进行匹配,如果实体中定义为courseid,方法名为findByCourseid,则一样会查询失败)。
其余层Service层、Controller层的代码这里就不写了,与常用的写法一致。
2、 @Query注解的使用
使用 @Query注解,当不确定数据库中的字段时使用原生的SQL来进行数据库的查询操作,接下来将上述的查询过程来使用 @Query注解来进行一个改造,具体的改造主要出现在UserRepository中,修改内容如下:
// 将原有的方法名,更改为findA,A为User中不存在的属性
public interface UserRepository extends JpaRepository<User,Long> {
User findA(String username);
}
这时启动程序会报如下的错误:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property findA found for type User!
接下来将使用 @Query注解:
public interface UserRepository extends JpaRepository<User,Long> {
@Query(value="select * from USER where name=?1")
User findA(String username);
}
启动程序,会报如下的错误:
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: * near line 1, column 8 [select * from USER where name=?1]
报这个错的原因是由于,Spring Data JPA是基于hibernate的,所以在进行查询的时候使用的是HQL而非SQL,所以会出现问题,解决办法:
(1)将SQL语句更改为HQL语句,例如上例改为: from USER where name=?1
(2)加上 nativeQuery=true ,意思即为使用原生的SQL语句继续查询操作。
如:@Query(value=“select * from USER where name=?1”,nativeQuery=true)
2.1、在@query中添加条件判断
这里以postgreSQ
L为例来介绍,日常开发中常会遇到这样的问题,需要进行多条件的判断,这就难免有些条件为空的情况,这时可以在@Query中使用COALESCE
函数(相当于mysql中的ISNULL
函数)和CASE WHEN condition THEN result [WHEN ...] [ELSE result] END
(相当于mysql中的if
函数),具体代码如下:
@Query(value = "SELECT * FROM trn_pub_train " + "WHERE "//
+ "CASE WHEN COALESCE(?1,'')='' THEN 1=1 ELSE name=?1 END and "//
+ "CASE WHEN COALESCE(?2,'')='' THEN 1=1 ELSE category_id=?2 END and "//
+ "CASE WHEN COALESCE(?3,'')='' THEN train_status=?3 ELSE train_status=?3 END ", nativeQuery = true)
Page<Train> findTrains(String name, String catId, String status, Pageable pageable);
注意:
COALESCE(val1,val2...)
返回参数中第一个不为空的参数值,如:COALESCE(null,’’,‘233’),返回‘’
但是在实际的开发中发现如果方法中的参数值为null
时,会报org.postgresql.util.PSQLException: 错误: 操作符不存在: character varying = bytea
的错误(在sql服务上不会报错),因此可以在代码中对参数进行转化,如下:if (StringUtils.isEmpty(name)) {name="";}
在mysql中的写法如下:
// if(?1 !='',x1=?1,1=1) 这里用来判断第一个参数不为'',也可以使用ISNULL()函数来判断null值
@Query(value = "select * from xxx where if(?1 !='',x1=?1,1=1) and if(?2 !='',x2=?2,1=1)" +
"and if(?3 !='',x3=?3,1=1) ",nativeQuery = true)
List<XXX> find(String X1,String X2,String X3);
2.1.1、在@query中使用in查询
in查询可以将所有的参数保存在集合中,如下所示:
@Query(value = "SELECT * FROM trn_pub_train " + "WHERE "//
+ "CASE WHEN COALESCE(?1,'')='' THEN 1=1 ELSE name=?1 END and "//
+ "CASE WHEN COALESCE(?2,'')='' THEN 1=1 ELSE category_id=?2 END and "//
+ "CASE WHEN COALESCE(?3,'')='' THEN train_status=?3 ELSE train_status=?3 END and "
+ "group_id in (?4)", nativeQuery = true)
Page<Train> findTrains(String name, String catId, String status,List<String> ids, Pageable pageable);
2.2、使用@query注解实现update/insert/delete
使用@query注解实现update/insert/delete
时需要用到另外一个注解@Modifying
,如果执行的为update/delete
的操作,还需要加上@Transactional
注解,如下所示:
@Transactional
@Modifying(clearAutomatically = true)
@Query("UPDATE user SET state='0' WHERE id=?1",nativeQuery = true)
void updateState(String id);
clearAutomatically = true
意味着会自动清除实体中保存的数据,为可选参数
。
insert/delete
的操作与上述类似,这里就不写了。
3、使用Example快速实现动态查询
通过一个例子来说明下Example的使用,这里使用JPA中的默认方法findAll
;
- 先来看下
org.springframework.data.domain.Example<T>
一个泛型接口,可以使用Example.of(T probe, ExampleMatcher matcher)
方法来创建该接口的实例;probe
为实体对象,比如我们要查询User表,那User对象就是可以作为Probe;ExampleMatcher
用于设置一些匹配的规则 ;
- 再来看下
findAll
方法findAll(Example<S> example, Pageable pageable);
,该方法借助于Example接口的实例来实现查询。
接下来介绍下使用过程,代码如下:
// 先来创建Example的实例
Train train=new Train(); // 要查询的数据表对应的实体类
trian.setName("张三");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("createTime")// 忽略createTime字段,这里要写实体类中的属性名而不能是数据库中的字段名“create_time”
.withMatcher("status", GenericPropertyMatchers.contains())// 模糊查询:status like '%' + ?0 + '%'
.withIgnoreNullValues();// 忽略值为null的字段
Exampl<Train> example=Example.of(train,matcher);
Rageable pageable=PageRequest.of(pageNum, pageSize, Sort.by(Direction.DESC, "createTime"));
//Sort.by(Direction.DESC, "createTime") 这里要写实体类中的属性名而不能是数据库中的字段名“create_time”
// 执行查询
List<Train> list=trainRepository.findAll(example,pageable);
上述代码中两处需要使用实体类中的属性名的地方,如果使用数据库中的字段名,会提示
No property created found for type
。
关于Example的更多使用可以参考官网相关内容:Query by Example 这种方式由于本身的局限性:
- 不支持过滤条件分组。即不支持过滤条件用 or(或) 来连接,所有的过滤查件,都是简单一层的用 and(并且) 连接。
- 仅支持
字符串的开始/包含/结束/正则表达式匹配
和 其他属性类型的精确匹配
。查询时,对一个要进行匹配的属性(如:姓名 name),只能传入一个过滤条件值,如以Customer为例,要查询姓“刘”的客户,“刘”这个条件值就存储在表示条件对象的Customer对象的name属性中,针对于“姓名”的过滤也只有这么一个存储过滤值的位置,没办法同时传入两个过滤值。正是由于这个限制,有些查询是没办法支持的,例如要查询某个时间段内添加的客户,对应的属性是 addTime,需要传入“开始时间”和“结束时间”两个条件值,而这种查询方式没有存两个值的位置,所以就没办法完成这样的查询。推荐博文:
springdata jpa使用Example快速实现动态查询
SpringBoot2(五):Spring Data JPA 使用详解
QueryByExampleExecutor接口的查询