一、基本介绍
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就更不用说了.
<!--JPA start-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.10.4.RELEASE</version>
</dependency>
<!--JPA end-->
<!--hibernate start-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!--hibernate end-->
<!--mysql start-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mysql end-->
2、整合Spring
整合Spring主要有以下几点要注意:
- 数据源配置
- JPA提供者,JPA属性配置
- 事务配置
- jpa:repositories 配置
具体如下代码:
<!-- 加载数据库配置文件 -->
<context:property-placeholder location="classpath:config.properties"/>
<!--配置数据库连接池Druid-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 数据库基本信息配置 -->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="filters" value="${filters}" />
<!-- 最大并发连接数 -->
<property name="maxActive" value="${maxActive}" />
<!-- 初始化连接数量 -->
<property name="initialSize" value="${initialSize}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${maxWait}" />
<!-- 最小空闲连接数 -->
<property name="minIdle" value="${minIdle}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="SELECT 1" />
<property name="testWhileIdle" value="${testWhileIdle}" />
<property name="testOnBorrow" value="${testOnBorrow}" />
<property name="testOnReturn" value="${testOnReturn}" />
<property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="${removeAbandoned}" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="${logAbandoned}" />
</bean>
<!--第二步-->
<!--定义实体的工厂bean-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--实体类位置-->
<property name="packagesToScan" value="cn.mrdear.entity"/>
<!--持久化单元名-->
<property name="persistenceUnitName" value="TestJPA" />
<!--JPA提供者-->
<property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
<!--JPA属性-->
<property name="jpaProperties">
<props>
<!--配置方言-->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<!--激活查询日志功能-->
<prop key="hibernate.show_sql">false</prop>
<!--优雅地输出Sql-->
<prop key="hibernate.format_sql">false</prop>
<!--添加一条解释型标注-->
<prop key="hibernate.use_sql_comments">false</prop>
<!--配置如何根据java模型生成数据库表结构,常用update,validate-->
<prop key="hibernate.hbm2ddl.auto">none</prop>
</props>
</property>
</bean>
<!--第三步-->
<!--定义事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="dataSource" ref="dataSource"/>
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--第四步-->
<!--定义repository接口的存放目录-->
<!--定义接口实现的后缀,通常用Impl-->
<!--定义实体工厂的引用-->
<!--定义事务管理器的引用-->
<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<User> 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<User> 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<User> 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<User> 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<User> 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<User> findByName(String name, Pageable pageable);
除此之外也可以使用原生sql,只需要@Query(nativeQuery=true)标识即可.
创建查询顺序:
Spring Data JPA 在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?为此, 提供了 query-lookup-strategy 属性,用以指定查找的顺序。它有如下三个取值:
|
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<User> cq = cb.createQuery(User.class);
Root<User> 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> {
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> 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<T> implements Specification<T> {
/** 属性分隔符 */
private static final String PROPERTY_SEPARATOR = ".";
/**
* and条件
*/
private List<Filter> andFilters = new ArrayList<>();
/**
* or条件
*/
private List<Filter> orFilters = new ArrayList<>();
/**
* 排序属性
*/
private List<Order> orders = new ArrayList<>();
/**
* 获取Path
*
* @param path
* Path
* @param propertyPath
* 属性路径
* @return Path
*/
@SuppressWarnings("unchecked")
private <X> Path<X> getPath(Path<?> path, String propertyPath) {
if (path == null || StringUtils.isEmpty(propertyPath)) {
return (Path<X>) 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<T> 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<String>) 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<String>) 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<Number>) path, (Number) value));
}
break;
case lt:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) value));
}
break;
case ge:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) value));
}
break;
case le:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.le((Path<Number>) 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<String>) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path<String>) 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<T> 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<String>) 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<String>) 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<Number>) path, (Number) value));
}
break;
case lt:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) value));
}
break;
case ge:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) value));
}
break;
case le:
if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.le((Path<Number>) 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<String>) path), ((String) value).toLowerCase()));
} else {
restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like((Path<String>) 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<T> 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<T> 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<PcardOrder> {
}
编写测试代码,这个使用的是CriteriaBuilder构建查询的,所以查询字段都是JPQL字段,并不是原生sql:
QueryParams<PcardOrder> 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<PcardOrder> 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<Object> analysisQueryParams(StringBuilder sql, QueryParams<?> params){
List<String> strList = new ArrayList<>();
List<Object> 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<Map>集合
这样的返回值是一个List类型,取出的时候直接根据键值取即可。
四、项目实战
1、基本配置
参考《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<User> getUserlist() {
List<User> 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);
}
}