一、基本介绍
JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,百度百科说是JDK为了实现ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准,事实上也是,JPA几乎都是接口,实现都是Hibernate在做,宏观上面看,在JPA的统一之下Hibernate很良好的运行。
我们都知道,在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通过这个对象来操作数据库。我们一般按照三层结构来看的话,Service层做业务逻辑处理,Dao层和数据库打交道,在Dao中,就存在着上面的对象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD,所有的基础CRUD框架都提供,我们使用起来感觉很方便,很给力,业务逻辑层面的处理ORM是没有提供的,如果使用原生的框架,业务逻辑代码我们一般会自定义,会自己去写SQL语句,然后执行。在这个时候,Spring-data-jpa的威力就体现出来了,ORM提供的能力他都提供,ORM框架没有提供的业务逻辑功能Spring-data-jpa也提供,全方位的解决用户的需求。使用Spring-data-jpa进行开发的过程中,常用的功能,我们几乎不需要写一条sql语句。
二、环境配置
1、maven配置
首先需要spring相关架包,其实spring-data-jpa里面已经依赖了,如果你想用自己的版本则需要额外引入spring相关包.JPA实现还都是hibernate去实现的,所以还需要hibernate相关包.mysql就更不用说了.
org.springframework.data
spring-data-jpa
1.10.4.RELEASE
org.hibernate
hibernate-core
h
i
b
e
r
n
a
t
e
.
v
e
r
s
i
o
n
<
/
v
e
r
s
i
o
n
>
<
/
d
e
p
e
n
d
e
n
c
y
>
<
d
e
p
e
n
d
e
n
c
y
>
<
g
r
o
u
p
I
d
>
o
r
g
.
h
i
b
e
r
n
a
t
e
<
/
g
r
o
u
p
I
d
>
<
a
r
t
i
f
a
c
t
I
d
>
h
i
b
e
r
n
a
t
e
−
e
n
t
i
t
y
m
a
n
a
g
e
r
<
/
a
r
t
i
f
a
c
t
I
d
>
<
v
e
r
s
i
o
n
>
{hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>
hibernate.version</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate−entitymanager</artifactId> <version>{hibernate.version}
mysql
mysql-connector-java
${mysql.version}
2、整合Spring
整合Spring主要有以下几点要注意:
数据源配置
JPA提供者,JPA属性配置
事务配置
jpa:repositories 配置
具体如下代码:
<context:property-placeholder location=“classpath:config.properties”/>
org.hibernate.dialect.MySQL5Dialect
false
false
false
none
<jpa:repositories base-package=“cn.mrdear.repository”
repository-impl-postfix=“Impl”
entity-manager-factory-ref=“entityManagerFactory”
transaction-manager-ref=“transactionManager”/>
<tx:annotation-driven transaction-manager=“transactionManager”/>
3、创建实体类
实体类中常用注解:
@Entity :声明这个类是一个实体类
@Table:指定映射到数据库的表格
@Id :映射到数据库表的主键属性,一个实体只能有一个属性被映射为主键
@GeneratedValue:主键的生成策略
@Column配置单列属性
@Entity//标识该为一个实体
@Table(name = “user”)//关联数据库中的user表
public class User {
@Id//标识该属性为主键
private Integer id;
private String name;
private String address;
private String phone;
//省略get和set
}
4、Repository接口
Repository: 最顶层的接口,是一个空接口,目的是为了统一所有的Repository的类型,且能让组件扫描时自动识别
CrudRepository: Repository的子接口,提供CRUD 的功能。
PagingAndSortingRepository:CrudRepository的子接口, 添加分页排序。
JpaRepository: PagingAndSortingRepository的子接口,增加批量操作等。
JpaSpecificationExecutor: 用来做复杂查询的接口。
由图来看,一般使用JpaRepository这个接口做查询即可.这个接口拥有如下方法:
delete删除或批量删除
findOne查找单个
findAll查找所有
save保存单个或批量保存
saveAndFlush保存并刷新到数据库
知道这些我们就可以创建repository 了:
//User表示该Repository与实体User关联,主键类型为Integer
public interface UserRepository extends JpaRepository<User,Integer> {
}
这样就完成了一个基本Repository的创建,可以直接使用其中的方法,而不需要去写实现类。
5、测试
关于测试这里,我把测试案例写到test文件夹的话,总是报实体类未被JPA管理,所以改写到java文件夹,具体原因未知.
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“spring/applicationContext.xml”);
UserRepository userRepository = (UserRepository) applicationContext.getBean(“userRepository”);
System.out.println(userRepository.findAll());
System.out.println(userRepository.findOne(1));
System.out.println(userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,“id”))));
}
三、CRUD操作
1、增加
增加可以使用JpaRepository接口里面的save方法.查看源码可以发现实际上是使用了em.persist(entity)来使对象进入持久化状态,最后提交事务的时候再一起更新到数据库:
User user = new User();
user.setId(99);
user.setAddress(“上海”);
user.setName(“张三”);
user.setPhone(“110”);
//保存单个
userRepository.save(user);
//保存或更新
userRepository.saveAndFlush(user);
List users = new ArrayList<>();
users.add(user);
//保存多个
userRepository.save(users);
这里还可以批量插入,对于mysql支持INSERT user VALUES (20,‘王二’,‘111’,‘111’),(21,‘王二’,‘111’,‘111’);类似这样的sql语句,具体实现就需要自己去写实现了,这样可以一次插入多条记录,效率很高.至于一次插入多少条就可以根据你的业务量来自己制定。
2、删除
删除都是根据主键来删除的,区别就是多条sql和单条sql :
User user = new User();
user.setId(21);
user.setName(“王二”);
/**
- 删除都是根据主键删除
*/
//删除单条,根据主键值
userRepository.delete(20);
//删除全部,先findALL查找出来,再一条一条删除,最后提交事务
userRepository.deleteAll();
//删除全部,一条sql
userRepository.deleteAllInBatch();
List users = new ArrayList<>();
users.add(user);
//删除集合,一条一条删除
userRepository.delete(users);
//删除集合,一条sql,拼接or语句 如 id=1 or id=2
userRepository.deleteInBatch(users);
3、修改
修改也是根据主键来更新的 :
User user = new User();
user.setId(1);
user.setName(“张三”);
/**
- 更新也是根据主键来更新 update XX xx where id=1
*/
userRepository.saveAndFlush(user);
批量更新的话,就调用entityManager的merge函数来更新.
//首先在service层获取持久化管理器:
@PersistenceContext
private EntityManager em;
//批量更新方法,同理插入,删除也都可以如此做.
@Transactional
public void batchUpateCustom(List users) {
// TODO Auto-generated method stub
for(int i = 0; i < users.size(); i++) {
em.merge(users.get(i));
if(i % 30== 0) {
em.flush();
em.clear();
}
}
}
4、简单查询
单表查询,大部分都可以使用下面的方法解决,多表联合查询的话,下面方法就不是很实用,下一节分析多表查询。
1.使用JpaRepository方法
//查找全部
userRepository.findAll();
//分页查询全部,返回封装了分页信息
Page pageInfo = userRepository.findAll(new PageRequest(1, 3, Sort.Direction.ASC,“id”));
//查找全部,并排序
userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,“id”)));
User user = new User();
user.setName(“小红”);
//条件查询,可以联合分页,排序
userRepository.findAll(Example.of(user));
//查询单个
userRepository.findOne(1);
2.解析方法名创建查询
规则:find+全局修饰+By+实体的属性名称+限定词+连接词+ …(其它实体属性)+OrderBy+排序属性+排序方向。例如:
//分页查询出符合姓名的记录,同理Sort也可以直接加上
public List findByName(String name, Pageable pageable);
全局修饰: Distinct, Top, First
关键词: IsNull, IsNotNull, Like, NotLike, Containing, In, NotIn,
IgnoreCase, Between, Equals, LessThan, GreaterThan, After, Before…
排序方向: Asc, Desc
连接词: And, Or
And — 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
Or — 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
Between — 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
LessThan — 等价于 SQL 中的 “<”,比如 findBySalaryLessThan(int max);
GreaterThan — 等价于 SQL 中的”>”,比如 findBySalaryGreaterThan(int min);
IsNull — 等价于 SQL 中的 “is null”,比如 findByUsernameIsNull();
IsNotNull — 等价于 SQL 中的 “is not null”,比如 findByUsernameIsNotNull();
NotNull — 与 IsNotNull 等价;
Like — 等价于 SQL 中的 “like”,比如 findByUsernameLike(String user);
NotLike — 等价于 SQL 中的 “not like”,比如 findByUsernameNotLike(String user);
OrderBy — 等价于 SQL 中的 “order by”,比如 findByUsernameOrderBySalaryAsc(String user);
Not — 等价于 SQL 中的 “! =”,比如 findByUsernameNot(String user);
In — 等价于 SQL 中的 “in”,比如 findByUsernameIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
NotIn — 等价于 SQL 中的 “not in”,比如 findByUsernameNotIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
嵌套实体:
主实体中子实体的名称+ _ +子实体的属性名称
List findByAddress_ZipCode(ZipCode zipCode)
表示查询所有 Address(地址)的zipCode(邮编)为指定值的所有Person(人员)
3.JPQL查询
一个类似HQL的语法,在接口上使用@Query标识:
@Query(“select a from user a where a.id = ?1”)
public User findById(Long id);
使用@Modifying标识修改
@Modifying
@Query(“update User a set a.name = ?1 where a.id < ?2”)
public int updateName(String name, Long id);
携带分页信息:
@Query(“select u from User u where u.name=?1”)
public List findByName(String name, Pageable pageable);
除此之外也可以使用原生sql,只需要@Query(nativeQuery=true)标识即可.
创建查询顺序:
Spring Data JPA 在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?为此, 提供了 query-lookup-strategy 属性,用以指定查找的顺序。它有如下三个取值:
create — 通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过 @Query 指定的查询语句,都将会被忽略。
create-if-not-found — 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方法名字来创建查询。这是 query-lookup-strategy 属性的默认值。
use-declared-query — 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常。
4.计数
计数就直接使用JpaRepository的count方法:
//查找总数量
userRepository.count();
User user = new User();
user.setName(“小红”);
//条件计数
userRepository.count(Example.of(user));
5.判断是否存在
计数就直接使用JpaRepository的exists方法:
//根据主键判断是否存在
userRepository.exists(1);
User user = new User();
user.setName(“小红”);
//根据条件判断是否存在
userRepository.exists(Example.of(user));
6.自定义查询
首先自定义一个接口,用于定义自定义方法,如UserRepositoryCustom,然后让UserRepository实现该接口,这样的话就可以使用其中的方法。然后写UserRepositoryImpl实现UserRepositoryCustom接口。
最后设置jpa:repositories的repository-impl-postfix=“Impl”,这样的话JPA会查找自定义实现类命名规则,这样的话JPA在相应UserRepository包下面查找实现类,找到则会使用其中的实现方法,而不去自己实现。具体可以看项目demo,或者下一节的复杂查询。
5、复杂查询
1.使用CriteriaBuilder构建JPQL
在UserRepositoryImpl中使用CriteriaBuilder实现根据id查询,下面是代码:
public void findById(Integer id){
//select u from User u where u.id = 1
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(User.class);
Root root = cq.from(User.class); //from User
cq.select(root); //select * from User
javax.persistence.criteria.Predicate pre = cb.equal(root.get(“id”).as(Integer.class),id);//id=1
cq.where(pre);//where id=1
Query query = entityManager.createQuery(cq);//select u from User u where u.id = 1
System.out.println(query.getResultList());
}
缺点:
代码量多
不易维护
条件复杂的话,则会显得很混乱.
2.使用JpaSpecificationExecutor查询
该接口有如下方法,里面传入条件都是Specification,该接口会返回一个Predicate条件集合,因此就可以在这里封装:
public interface JpaSpecificationExecutor {
T findOne(Specification spec);
List findAll(Specification spec);
Page findAll(Specification spec, Pageable pageable);
List findAll(Specification spec, Sort sort);
long count(Specification spec);
}
1.构造过滤条件集合
Operator枚举类里面的operator属性为了构建原生sql使用:
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.io.Serializable;
/**
- 筛选
/
public class Filter implements Serializable {
private static final long serialVersionUID = -8712382358441065075L;
/*
* 运算符
/
public enum Operator {
/* 等于 /
eq(" = "),
/* 不等于 /
ne(" != "),
/* 大于 /
gt(" > "),
/* 小于 /
lt(" < "),
/* 大于等于 /
ge(" >= "),
/* 小于等于 /
le(" <= "),
/* 类似 /
like(" like "),
/* 包含 /
in(" in "),
/* 为Null /
isNull(" is NULL "),
/* 不为Null /
isNotNull(" is not NULL ");
Operator(String operator) {
this.operator = operator;
}
private String operator;
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
}
/* 默认是否忽略大小写 /
private static final boolean DEFAULT_IGNORE_CASE = false;
/* 属性 /
private String property;
/* 运算符 /
private Filter.Operator operator;
/* 值 /
private Object value;
/* 是否忽略大小写 /
private Boolean ignoreCase = DEFAULT_IGNORE_CASE;
/*
* 构造方法
/
public Filter() {
}
/*
* 构造方法
*
* @param property
* 属性
* @param operator
* 运算符
* @param value
* 值
/
public Filter(String property, Filter.Operator operator, Object value) {
this.property = property;
this.operator = operator;
this.value = value;
}
/*
* 构造方法
*
* @param property
* 属性
* @param operator
* 运算符
* @param value
* 值
* @param ignoreCase
* 忽略大小写
/
public Filter(String property, Filter.Operator operator, Object value, boolean ignoreCase) {
this.property = property;
this.operator = operator;
this.value = value;
this.ignoreCase = ignoreCase;
}
/*
* 返回等于筛选
*
* @param property
* 属性
* @param value
* 值
* @return 等于筛选
/
public static Filter eq(String property, Object value) {
return new Filter(property, Filter.Operator.eq, value);
}
/*
* 返回等于筛选
*
* @param property
* 属性
* @param value
* 值
* @param ignoreCase
* 忽略大小写
* @return 等于筛选
/
public static Filter eq(String property, Object value, boolean ignoreCase) {
return new Filter(property, Filter.Operator.eq, value, ignoreCase);
}
/*
* 返回不等于筛选
*
* @param property
* 属性
* @param value
* 值
* @return 不等于筛选
/
public static Filter ne(String property, Object value) {
return new Filter(property, Filter.Operator.ne, value);
}
/*
* 返回不等于筛选
*
* @param property
* 属性
* @param value
* 值
* @param ignoreCase
* 忽略大小写
* @return 不等于筛选
/
public static Filter ne(String property, Object value, boolean ignoreCase) {
return new Filter(property, Filter.Operator.ne, value, ignoreCase);
}
/*
* 返回大于筛选
*
* @param property
* 属性
* @param value
* 值
* @return 大于筛选
/
public static Filter gt(String property, Object value) {
return new Filter(property, Filter.Operator.gt, value);
}
/*
* 返回小于筛选
*
* @param property
* 属性
* @param value
* 值
* @return 小于筛选
/
public static Filter lt(String property, Object value) {
return new Filter(property, Filter.Operator.lt, value);
}
/*
* 返回大于等于筛选
*
* @param property
* 属性
* @param value
* 值
* @return 大于等于筛选
/
public static Filter ge(String property, Object value) {
return new Filter(property, Filter.Operator.ge, value);
}
/*
* 返回小于等于筛选
*
* @param property
* 属性
* @param value
* 值
* @return 小于等于筛选
/
public static Filter le(String property, Object value) {
return new Filter(property, Filter.Operator.le, value);
}
/*
* 返回相似筛选
*
* @param property
* 属性
* @param value
* 值
* @return 相似筛选
/
public static Filter like(String property, Object value) {
return new Filter(property, Filter.Operator.like, value);
}
/*
* 返回包含筛选
*
* @param property
* 属性
* @param value
* 值
* @return 包含筛选
/
public static Filter in(String property, Object value) {
return new Filter(property, Filter.Operator.in, value);
}
/*
* 返回为Null筛选
*
* @param property
* 属性
* @return 为Null筛选
/
public static Filter isNull(String property) {
return new Filter(property, Filter.Operator.isNull, null);
}
/*
* 返回不为Null筛选
*
* @param property
* 属性
* @return 不为Null筛选
/
public static Filter isNotNull(String property) {
return new Filter(property, Filter.Operator.isNotNull, null);
}
/*
* 返回忽略大小写筛选
*
* @return 忽略大小写筛选
/
public Filter ignoreCase() {
this.ignoreCase = true;
return this;
}
/*
* 获取属性
*
* @return 属性
/
public String getProperty() {
return property;
}
/*
* 设置属性
*
* @param property
* 属性
/
public void setProperty(String property) {
this.property = property;
}
/*
* 获取运算符
*
* @return 运算符
/
public Filter.Operator getOperator() {
return operator;
}
/*
* 设置运算符
*
* @param operator
* 运算符
/
public void setOperator(Filter.Operator operator) {
this.operator = operator;
}
/*
* 获取值
*
* @return 值
/
public Object getValue() {
return value;
}
/*
* 设置值
*
* @param value
* 值
/
public void setValue(Object value) {
this.value = value;
}
/*
* 获取是否忽略大小写
*
* @return 是否忽略大小写
/
public Boolean getIgnoreCase() {
return ignoreCase;
}
/*
* 设置是否忽略大小写
*
* @param ignoreCase
* 是否忽略大小写
/
public void setIgnoreCase(Boolean ignoreCase) {
this.ignoreCase = ignoreCase;
}
/*
* 重写equals方法
*
* @param obj
* 对象
* @return 是否相等
/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
if (this == obj) {
return true;
}
Filter other = (Filter) obj;
return new EqualsBuilder().append(getProperty(), other.getProperty()).append(getOperator(), other.getOperator()).append(getValue(), other.getValue()).isEquals();
}
/*
* 重写hashCode方法
*
* @return HashCode
*/
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(getProperty()).append(getOperator()).append(getValue()).toHashCode();
}
}
2.构造排序字段
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.io.Serializable;
/**
- 排序
- @author copy from shopxx
/
public class Order implements Serializable {
private static final long serialVersionUID = -3078342809727773232L;
/*
* 方向
/
public enum Direction {
/* 递增 /
asc,
/* 递减 /
desc
}
/* 默认方向 /
private static final Order.Direction DEFAULT_DIRECTION = Order.Direction.desc;
/* 属性 /
private String property;
/* 方向 /
private Order.Direction direction = DEFAULT_DIRECTION;
@Override
public String toString() {
return property+" " + direction.name();
}
/*
* 构造方法
/
public Order() {
}
/*
* 构造方法
*
* @param property
* 属性
* @param direction
* 方向
/
public Order(String property, Order.Direction direction) {
this.property = property;
this.direction = direction;
}
/*
* 返回递增排序
*
* @param property
* 属性
* @return 递增排序
/
public static Order asc(String property) {
return new Order(property, Order.Direction.asc);
}
/*
* 返回递减排序
*
* @param property
* 属性
* @return 递减排序
/
public static Order desc(String property) {
return new Order(property, Order.Direction.desc);
}
/*
* 获取属性
*
* @return 属性
/
public String getProperty() {
return property;
}
/*
* 设置属性
*
* @param property
* 属性
/
public void setProperty(String property) {
this.property = property;
}
/*
* 获取方向
*
* @return 方向
/
public Order.Direction getDirection() {
return direction;
}
/*
* 设置方向
*
* @param direction
* 方向
/
public void setDirection(Order.Direction direction) {
this.direction = direction;
}
/*
* 重写equals方法
*
* @param obj
* 对象
* @return 是否相等
/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
if (this == obj) {
return true;
}
Order other = (Order) obj;
return new EqualsBuilder().append(getProperty(), other.getProperty()).append(getDirection(), other.getDirection()).isEquals();
}
/*
* 重写hashCode方法
*
* @return HashCode
*/
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(getProperty()).append(getDirection()).toHashCode();
}
}
3.查询语句生成
1.基本框架
/**
- 封装查询条件的实体
/
public class QueryParams implements Specification {
/* 属性分隔符 /
private static final String PROPERTY_SEPARATOR = “.”;
/*
* and条件
/
private List andFilters = new ArrayList<>();
/*
* or条件
/
private List orFilters = new ArrayList<>();
/*
* 排序属性
/
private List orders = new ArrayList<>();
/*
* 获取Path
*
* @param path
* Path
* @param propertyPath
* 属性路径
* @return Path
*/
@SuppressWarnings(“unchecked”)
private Path getPath(Path<?> path, String propertyPath) {
if (path == null || StringUtils.isEmpty(propertyPath)) {
return (Path) path;
}
String property = StringUtils.substringBefore(propertyPath, PROPERTY_SEPARATOR);
return getPath(path.get(property), StringUtils.substringAfter(propertyPath, PROPERTY_SEPARATOR));
}
}
2.分析and条件
/**
- 转换为Predicate
*/
@SuppressWarnings(“unchecked”)
private Predicate toAndPredicate(Root root,CriteriaBuilder criteriaBuilder) {
Predicate restrictions = criteriaBuilder.conjunction();
if (root == null || CollectionUtils.isEmpty(andFilters)) {
return restrictions;
}
for (Filter filter : andFilters) {
if (filter == null) {
continue;
}
String property = filter.getProperty();
Filter.Operator operator = filter.getOperator();
Object value = filter.getValue();
Boolean ignoreCase = filter.getIgnoreCase();
Path<?> path = getPath(root, property);
if (path == null) {
continue;
}
//根据运算符生成相应条件
switch (operator) {
case eq:
if (value != null) {
if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(path, value));
}
} else {
restrictions = criteriaBuilder.and(restrictions, path.isNull());
}
break;
case ne:
if (value != null) {
if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(path, value));
}
} else {
restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
}
break;
case gt:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.gt((Path) path, (Number) value));
}
break;
case lt:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt((Path) path, (Number) value));
}
break;
case ge:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge((Path) path, (Number) value));
}
break;
case le:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.le((Path) path, (Number) value));
}
break;
case like:
if (String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
if (BooleanUtils.isTrue(ignoreCase)) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path) path, (String) value));
}
}
break;
case in:
restrictions = criteriaBuilder.and(restrictions, path.in(value));
break;
case isNull:
restrictions = criteriaBuilder.and(restrictions, path.isNull());
break;
case isNotNull:
restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
break;
}
}
return restrictions;
}
3.分析or条件
把and中的and改为or即可:
/**
- 转换为Predicate
*/
@SuppressWarnings(“unchecked”)
private Predicate toOrPredicate(Root root,CriteriaBuilder criteriaBuilder) {
Predicate restrictions = criteriaBuilder.disjunction();
if (root == null || CollectionUtils.isEmpty(andFilters)) {
return restrictions;
}
for (Filter filter : orFilters) {
if (filter == null) {
continue;
}
String property = filter.getProperty();
Filter.Operator operator = filter.getOperator();
Object value = filter.getValue();
Boolean ignoreCase = filter.getIgnoreCase();
Path<?> path = getPath(root, property);
if (path == null) {
continue;
}
switch (operator) {
case eq:
if (value != null) {
if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(path, value));
}
} else {
restrictions = criteriaBuilder.or(restrictions, path.isNull());
}
break;
case ne:
if (value != null) {
if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(path, value));
}
} else {
restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
}
break;
case gt:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.gt((Path) path, (Number) value));
}
break;
case lt:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lt((Path) path, (Number) value));
}
break;
case ge:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.ge((Path) path, (Number) value));
}
break;
case le:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.le((Path) path, (Number) value));
}
break;
case like:
if (String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
if (BooleanUtils.isTrue(ignoreCase)) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like((Path) path, (String) value));
}
}
break;
case in:
restrictions = criteriaBuilder.or(restrictions, path.in(value));
break;
case isNull:
restrictions = criteriaBuilder.or(restrictions, path.isNull());
break;
case isNotNull:
restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
break;
}
}
return restrictions;
}
4.分析排序条件
/**
- 转换为Order
*/
private List<javax.persistence.criteria.Order> toOrders(Root root,CriteriaBuilder criteriaBuilder) {
List<javax.persistence.criteria.Order> orderList = new ArrayList<javax.persistence.criteria.Order>();
if (root == null || CollectionUtils.isEmpty(orders)) {
return orderList;
}
for (Order order : orders) {
if (order == null) {
continue;
}
String property = order.getProperty();
Order.Direction direction = order.getDirection();
Path<?> path = getPath(root, property);
if (path == null || direction == null) {
continue;
}
switch (direction) {
case asc:
orderList.add(criteriaBuilder.asc(path));
break;
case desc:
orderList.add(criteriaBuilder.desc(path));
break;
}
}
return orderList;
}
最后在toPredicate方法中构造最终条件:
/**
- 生成条件的
- @param root 该对象的封装
- @param query 查询构建器
- @param cb 构建器
- @return 条件集合
*/
@Override
public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate restrictions = cb.and(toAndPredicate(root,cb));
restrictions = cb.and(restrictions,toOrPredicate(root,cb));
query.orderBy(toOrders(root,cb));
return restrictions;
}
加上方便的链式调用方法:
/**
- 添加一个and条件
- @param filter 该条件
- @return 链式调用
/
public QueryParams and(Filter filter){
this.andFilters.add(filter);
return this;
}
/* - 添加多个and条件
- @param filter 该条件
- @return 链式调用
/
public QueryParams and(Filter …filter){
this.andFilters.addAll(Arrays.asList(filter));
return this;
}
/* - 添加一个or条件
- @param filter 该条件
- @return 链式调用
/
public QueryParams or(Filter filter){
this.orFilters.add(filter);
return this;
}
/* - 添加多个or条件
- @param filter 该条件
- @return 链式调用
/
public QueryParams or(Filter …filter){
this.orFilters.addAll(Arrays.asList(filter));
return this;
}
/* - 升序字段
- @param property 该字段对应变量名
- @return 链式调用
/
public QueryParams orderASC(String property){
this.orders.add(Order.asc(property));
return this;
}
/* - 降序字段
- @param property 该字段对应变量名
- @return 链式调用
/
public QueryParams orderDESC(String property){
this.orders.add(Order.desc(property));
return this;
}
/* - 清除所有条件
- @return 该实例
/
public QueryParams clearAll(){
if (!this.andFilters.isEmpty()) this.andFilters.clear();
if (!this.orFilters.isEmpty()) this.orFilters.clear();
if (!this.orders.isEmpty()) this.orders.clear();
return this;
}
/* - 清除and条件
- @return 该实例
/
public QueryParams clearAnd(){
if (!this.andFilters.isEmpty()) this.andFilters.clear();
return this;
}
/* - 清除or条件
- @return 该实例
/
public QueryParams clearOr(){
if (!this.orFilters.isEmpty()) this.andFilters.clear();
return this;
}
/* - 清除order条件
- @return 该实例
*/
public QueryParams clearOrder(){
if (!this.orders.isEmpty()) this.orders.clear();
return this;
}
//省略get和set方法
4.测试
首先让PcardOrderRepository接口继承加上JpaSpecificationExecutor:
public interface PcardOrderRepository extends JpaRepository<PcardOrder,String>,PcardOrderRepositoryCustom,JpaSpecificationExecutor {
}
编写测试代码,这个使用的是CriteriaBuilder构建查询的,所以查询字段都是JPQL字段,并不是原生sql:
QueryParams queryParams = new QueryParams<>();
//使用Specification条件查询,使用JPQL字段查询
queryParams
.and(Filter.eq(“acctId”,“0014779934917371041”),Filter.ne(“orderAmt”,0L),
Filter.eq(“orderRespCd”,“00”))
.or(Filter.eq(“orderTypeId”,“A003”),Filter.eq(“orderTypeId”,“A007”),
Filter.eq(“orderTypeId”,“A021”),Filter.eq(“orderTypeId”,“A018”))
.orderDESC(“createTime”);
Page JPQLlist = pcardOrderRepository.findAll(queryParams,new PageRequest(0,2));
//构造出来的条件
where
1=1
and pcardorder0_.acct_id=?
and pcardorder0_.order_amt<>0
and pcardorder0_.order_resp_cd=?
and (
0=1
or pcardorder0_.order_type_id=?
or pcardorder0_.order_type_id=?
or pcardorder0_.order_type_id=?
or pcardorder0_.order_type_id=?
)
order by
pcardorder0_.create_time desc limit ?
3.原生sql查询
还是利用上面的Filter,具体还是遍历+拼接,示例中我卸载了公共方法中,需要使用的Impl直接extends即可。
1.解析条件
解析条件实际上就是拼接sql,代码很简单:
/**
- 公共方法的repository
/
@NoRepositoryBean
public class BaseRepository {
private static Logger logger = LoggerFactory.getLogger(BaseRepository.class);
/*
* 分析查询参数,并且合并到sql语句中
* @param sql JPQL查询语句
* @param params 查询参数
* @return 参数对应的value
*/
@SuppressWarnings(“Unchecked”)
protected List analysisQueryParams(StringBuilder sql, QueryParams<?> params){
List strList = new ArrayList<>();
List valueList = new ArrayList<>();
int i = 1;
//分析or条件
for (Filter filter : params.getOrFilters()) {
if (filter.getValue() != null){
strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ?" + (i++));
valueList.add(filter.getValue());
}else {
strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" “);
}
}
if (!strList.isEmpty()){
sql.append(” and “).append(”( “).append(StringUtils.join(strList,” or “)).append(” )");
}
strList.clear();
//分析and条件
for (Filter filter : params.getAndFilters()) {
if (filter.getValue() != null){
strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ?" + (i++));
valueList.add(filter.getValue());
}else {
strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" “);
}
}
sql.append(” and “).append(StringUtils.join(strList,” and “));
//分析排序字段
if (!params.getOrders().isEmpty()){
sql.append(” order by “);
sql.append(StringUtils.join(params.getOrders(),”,"));
}
logger.debug(“解析后的sql:”+sql.toString());
logger.debug(“对应的值为:”+valueList);
return valueList;
}
}
2.测试
在自定义接口中加入方法:
public interface PcardOrderRepositoryCustom {
List findByQueryParam(QueryParams queryParams, Pageable pageable);
}
然后实现类中需要写部分sql
@NoRepositoryBean
public class PcardOrderRepositoryImpl extends BaseRepository implements PcardOrderRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public List findByQueryParam(QueryParams queryParams, Pageable pageable) {
StringBuilder sql = new StringBuilder("select * from tbl_pcard_order where 1=1 ");
List values = analysisQueryParams(sql,queryParams);
Query query = entityManager.createNativeQuery(sql.toString());
for (int i = 0; i < values.size(); i++) {
query.setParameter(i+1,values.get(i));
}
query.setFirstResult(pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
return query.getResultList();
}
}
测试代码:
//使用原生sql查询,注意这里使用原生sql字段,并非JPQL字段,
//本质是根据BaseRepository.analysisQueryParams 来拼接条件,可以根据自己的需求更改
queryParams.clearAll()
.and(Filter.eq(“acct_id”,“0014779934917371041”),Filter.ne(“order_amt”,0L),
Filter.eq(“order_resp_cd”,“00”))
.or(Filter.eq(“order_type_id”,“A003”),Filter.eq(“order_type_id”,“A007”),
Filter.eq(“order_type_id”,“A021”),Filter.eq(“order_type_id”,“A018”))
.orderDESC(“create_time”);
List nativeSqlList = pcardOrderRepository.findByQueryParam(queryParams,new PageRequest(0,2));
//构造出来的sql
where
1=1
and (
order_type_id = ?
or order_type_id = ?
or order_type_id = ?
or order_type_id = ?
)
and acct_id = ?
and order_amt != ?
and order_resp_cd = ?
order by
create_time desc limit ?
4.使用原生sql查询出Map集合
使用原生sql进行表关联查询,返回值一般都用List:
public List<Object[]> findById(int id) {
String sql = “select u.,c. from user u,commont c where u.id = c.id and id=?1”;
Query query = entityManager.createNativeQuery(sql);
query.setParameter(1,id);
return query.getResultList();
}
那么就要改进,使其返回Map:
public List findById (int id) {
String sql = “select u.,c. from user u,commont c where u.id = c.id and id=?1”;
Query query = entityManager.createNativeQuery(sql);
query.setParameter(1,id);
//转换为Map集合
query.unwrap(org.hibernate.SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
return query.getResultList();
}
//实际上返回的是一个List
四、项目实战
1、基本配置
参考《 Spring Boot整理——Thymeleaf模板》的项目,首先修改build.gradle的配置如下:
// buildscript 代码块中脚本优先执行
buildscript {
// ext 用于定义动态属性
ext {
springBootVersion = ‘1.5.2.RELEASE’
}
// 自定义 Thymeleaf 和 Thymeleaf Layout Dialect 的版本
ext['thymeleaf.version'] = '3.0.3.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.0'
// 自定义 Hibernate 的版本
ext['hibernate.version'] = '5.2.8.Final'
// 使用了 Maven 的中央仓库(你也可以指定其他仓库)
repositories {
//mavenCentral()
maven {
url 'http://maven.aliyun.com/nexus/content/groups/public/'
}
}
// 依赖关系
dependencies {
// classpath 声明说明了在执行其余的脚本时,ClassLoader 可以使用这些依赖项
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
// 使用插件
apply plugin: ‘java’
apply plugin: ‘eclipse’
apply plugin: ‘org.springframework.boot’
// 打包的类型为 jar,并指定了生成的打包的文件名称和版本
jar {
baseName = ‘jpa-in-action’
version = ‘1.0.0’
}
// 指定编译 .java 文件的 JDK 版本
sourceCompatibility = 1.8
// 默认使用了 Maven 的中央仓库。这里改用自定义的镜像库
repositories {
//mavenCentral()
maven {
url ‘http://maven.aliyun.com/nexus/content/groups/public/’
}
}
// 依赖关系
dependencies {
// 该依赖对于编译发行是必须的
compile(‘org.springframework.boot:spring-boot-starter-web’)
// 添加 Thymeleaf 的依赖
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
// 添加 Spring Data JPA 的依赖
compile(‘org.springframework.boot:spring-boot-starter-data-jpa’)
// 添加 MySQL连接驱动 的依赖
compile('mysql:mysql-connector-java:6.0.5')
// 该依赖对于编译测试是必须的,默认包含编译产品依赖和编译时依
testCompile(‘org.springframework.boot:spring-boot-starter-test’)
//添加H2的依赖
runtime('com.h2database:h2:1.4.193')
}
然后application.properties为:
THYMELEAF
spring.thymeleaf.encoding=UTF-8
热部署静态文件
spring.thymeleaf.cache=false
使用HTML5标准
spring.thymeleaf.mode=HTML5
#使用H2 控制台 访问页面http://localhost:8080/h2-console/,
spring.h2.console.enabled=true
DataSource 测试时可以先不用mysql数据库
#spring.datasource.url=jdbc:mysql://localhost/blog?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
#spring.datasource.username=root
#spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
JPA
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=create-drop
注意:在测试阶段可以使用H2数据库,其管理页面如下(http://localhost:8080/h2-console/ ),默认的数据库为jdbc:h2:mem:testdb,如果启用mysql,把mysql注释放开即可。
2、实体改造
@Entity // 实体
public class User implements Serializable{
private static final long serialVersionUID = 1L;
@Id // 主键
@GeneratedValue(strategy=GenerationType.IDENTITY) // 自增长策略
private Long id; // 用户的唯一标识
@Column(nullable = false) // 映射为字段,值不能为空
private String name;
@Column(nullable = false)
private Integer age;
@Override
public String toString() {
return String.format(
“User[id=%d, name=’%s’, age=’%d’]”,
id, name, age);
}
}
3、持久化层改造
接口改成如下内容,实现类删除,用spring 提供的
/**
-
用户仓库.
*/
public interface UserRepository extends CrudRepository<User, Long>{
}
4、控制层改造
@RestController
@RequestMapping("/users")
public class UserController {@Autowired
private UserRepository userRepository;
/**- 从 用户存储库 获取用户列表
- @return
/
private List getUserlist() {
List users = new ArrayList<>();
for (User user : userRepository.findAll()) {
users.add(user);
}
return users;
}
/* - 查询所用用户
- @return
*/
@GetMapping
public ModelAndView list(Model model) {
model.addAttribute(“userList”, getUserlist());
model.addAttribute(“title”, “用户管理”);
return new ModelAndView(“users/list”, “userModel”, model);
}
/**
- 根据id查询用户
- @param message
- @return
/
@GetMapping("{id}")
public ModelAndView view(@PathVariable(“id”) Long id, Model model) {
User user = userRepository.findOne(id);
model.addAttribute(“user”, user);
model.addAttribute(“title”, “查看用户”);
return new ModelAndView(“users/view”, “userModel”, model);
}
/* - 获取 form 表单页面
- @param user
- @return
/
@GetMapping("/form")
public ModelAndView createForm(Model model) {
model.addAttribute(“user”, new User(null, null));
model.addAttribute(“title”, “创建用户”);
return new ModelAndView(“users/form”, “userModel”, model);
}
/* - 新建用户
- @param user
- @param result
- @param redirect
- @return
/
@PostMapping
public ModelAndView create(User user) {
userRepository.save(user);
return new ModelAndView(“redirect:/users”);
}
/* - 删除用户
- @param id
- @return
/
@GetMapping(value = “delete/{id}”)
public ModelAndView delete(@PathVariable(“id”) Long id, Model model) {
userRepository.delete(id);
model.addAttribute(“userList”, getUserlist());
model.addAttribute(“title”, “删除用户”);
return new ModelAndView(“users/list”, “userModel”, model);
}
/* - 修改用户
- @param user
- @return
*/
@GetMapping(value = “modify/{id}”)
public ModelAndView modifyForm(@PathVariable(“id”) Long id, Model model) {
User user = userRepository.findOne(id);
model.addAttribute(“user”, user);
model.addAttribute(“title”, “修改用户”);
return new ModelAndView(“users/form”, “userModel”, model);
}
}
作者:局外人F
来源:CSDN
原文:https://blog.csdn.net/qq_22172133/article/details/81192040
版权声明:本文为博主原创文章,转载请附上博文链接!