spring boot实战之JPA

公司的项目中很大一部分属于内部平台,所以对性能的要求没有那么高,开发速度反而更重要,因此在搭建基础框架时选择使用JPA,没有使用mybitis,当然其中也有一部分原因是之前一直使用hibernate,对mybitis不太熟悉_

一、配置JPA

1、添加maven依赖



    mysql
    mysql-connector-java



    org.springframework.boot
    spring-boot-starter-data-jpa

2、 添加数据库配置

spring.jpa.database = MYSQL
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
# Show or not log for each sql query
spring.jpa.show-sql = true
spring.datasource.url=jdbc:mysql://localhost:3306/base?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.max-active=3
spring.datasource.max-idle=1
spring.datasource.min-idle=1
spring.datasource.initial-size=1

3、dao继承上层类

public interface UserDao  extends BaseDao{
    User findByUsernameAndDel(String username, int del);
}

@NoRepositoryBean
public interface BaseDao extends JpaSpecificationExecutor,JpaRepository{

}

为了便于使用,提取了一个BaseDao,需要注意的是要在上层类上添加@ NoRepositoryBean注解。BaseDao继承JpaRepository和JpaSpecificationExecutor,JpaRepository提供了基本的crud等查询方法,JpaSpecificationExecutor提供了对复杂查询的支持。

完成了以上三步,已经可以在service内注入dao,通过dao进行数据库curd等操作。

二、JPA查询

1、 根据方法名实现查询

//根据用户名和标记删除字段查询对应的用户信息
User findByUsernameAndDel(String username, int del);

//根据code查询对应的角色
Role findByCode(String code);

//根据id集合查询对应的角色集合
Set findByIdIn(Set roleIds);

//根据用户id,查询用户角色关系记录
List findByUserId(int uid);

简单查询可以通过以上方式方便的实现,简化了很多dao层的代码,使用着还是很爽的,具体规则比较简单,基本上就是findBy开始,后续跟上实体属性,中间配以And、Or、In、like等组成方法名,也就是用方法名来描述查询规则。如果是嵌套对象,可以通过“_"来区分子对象的属性,比如findByCompany_name(String name)就是以子对象company内的name属性为查询条件。常用查询关键字如下:

  • 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 类型,也可以是数组或者不定长参数;

2、使用@Query查询

类HQL语句

@Query("select u from User u where u.username = ?1") 
public AccountInfo findByAccountId(String username); 

在@Query内直接书写HQL语句即可,参数可通过"?1,?2"这样的方式设置,下标从1开始。

原生sql查询

@Query(value="select perm.* from role_permission rp left join permission perm on rp.permission_id=perm.id where rp.role_id in(?1);",nativeQuery=true)
List listByRoleIds(Set roles);

//根据userId删除用户角色关系
@Query(value = "delete from user_role where user_id=?1 ", nativeQuery = true)  
@Modifying
void deleleByUserId(int uid);

使用也比较简单,将@Query内的nativeQuery设置为true即可。写SQL语句时,可以现在本地mysql客户端上测试号SQL语句的正确性,当需要索引时,创建合适的索引。在此基础上看下SQL的性能,如不理想,需调整SQL,可使用explain对SQL语句进行分析,查询执行逻辑,针对性优化。

新增、修改
直接调用dao的save方法,支持单个保存和批量保存。

删除
直接调用dao的delete方法,支持根据id、对象、对象集合等删除方式,使用时查看下提示方法就可以了。

3、分页查询

不带条件分页查询

Pageable pageable = new PageRequest(0, 10, new Sort(Direction.DESC, "updateTime"));
Page page = userDao.findAll(pageable);
  • 使用PageRequest构建分页请求对象,页码下标从0开始

多条件复杂分页查询
带条件分页查询有两种方式:

  1. 使用原生SQL进行分页查询,但是前提是多个查询条件必须同时存在,不能有不生效的条件,比如用户列表,用户姓名可以不作为过滤条件,这种情况原生SQL就不适用了,需要使用下面第二种方式
  2. 使用Specification进行复杂查询,示例代码如下:
public Page listByPage(final Map params,Pageable pageable){
    Specification spec = new Specification() {  
        @Override
        public Predicate toPredicate(Root root,CriteriaQuery query,CriteriaBuilder cb) {
            List list = new ArrayList();  
            String type = params.get("type");
            String status= params.get("status");
            String username = params.get("username");
            String name = params.get("name");
            
            if(StringUtils.isNotBlank(type)){  
                list.add(cb.equal(root.get("type").as(Integer.class), NumberUtils.toInt(type)));  
            }  
            if(StringUtils.isNotBlank(status)){  
                list.add(cb.equal(root.get("status").as(Integer.class), NumberUtils.toInt(status)));  
            }
            if(StringUtils.isNotBlank(username)){  
                list.add(cb.like(root.get("username").as(String.class), String.format("%%%s%%", username)));  
            }  
            if(StringUtils.isNotBlank(name)){  
                list.add(cb.like(root.get("name").as(String.class), String.format("%%%s%%", name)));   
            }
            list.add(cb.equal(root.get("del"), Constants.DEL_NO));
            Predicate[] p = new Predicate[list.size()];  
            return cb.and(list.toArray(p));  
            
            //in条件查询
            /*List ids = Lists.newArrayList();
            ids.add(1);
            ids.add(2);
            In in = cb.in(root.get("id").as(Integer.class));
            in.value(1);
            in.value(2);
            return cb.or(in);*/
        }  
    };  
    Page page = userDao.findAll(spec, pageable);

根据以上示例,基本满足了常用的查询需求,更多情况可根据规则尝试一下即可,也可百度搜索下JPA Specification,有很多教程。

简化多条件分页查询
使用Specification需要每次都写一大段模板代码,使用起来还是比较繁琐,使用入门也有些难度,基于此,在service层的公共代码出对查询进行了部分封装,简化常见多条件分页查询。

/**
 * 分页多条件查询
 * 注:多个条件间是and关系 & 参数是属性对应的类型
 * @author yangwk
 * @time 2017年8月1日 下午3:50:46
 * @param params {"username:like":"test"} 键的格式为字段名:过滤方式,过滤方式见{@code QueryTypeEnum}
 * @param pageable 分页信息 new PageRequest(page, size,new Sort(Direction.DESC, "updateTime"))
 * @return
 */
Page list(Map params,Pageable pageable);

@Override
public Page list(final Map params,Pageable pageable){
    Specification spec = new Specification() {  
        @Override
        public Predicate toPredicate(Root root,CriteriaQuery query,CriteriaBuilder cb) {
            List list = new ArrayList();
            for(Entry entry : params.entrySet()){
                Object value = entry.getValue();
                if(value == null || StringUtils.isBlank(value.toString())){
                    continue;
                }
                String key = entry.getKey();
                String[] arr = key.split(":");
                Predicate predicate = getPredicate(arr,value,root,cb);
                list.add(predicate);
            }
            Predicate[] p = new Predicate[list.size()];  
            return cb.and(list.toArray(p));  
        }
    };  
    Page page = getDAO().findAll(spec, pageable);
    return page;
}


private Predicate getPredicate(String[] arr, Object value,
        Root root, CriteriaBuilder cb) {
    if(arr.length == 1){
        return cb.equal(root.get(arr[0]).as(value.getClass()), value);  
    }
    if(QueryTypeEnum.like.name().equals(arr[1])){
        return cb.like(root.get(arr[0]).as(String.class), String.format("%%%s%%", value));
    }
    if(QueryTypeEnum.ne.name().equals(arr[1])){
        return cb.notEqual(root.get(arr[0]).as(value.getClass()), value);
    }
    if(QueryTypeEnum.lt.name().equals(arr[1])){
        return getLessThanPredicate(arr,value,root,cb);
    }
    if(QueryTypeEnum.lte.name().equals(arr[1])){
        return getLessThanOrEqualToPredicate(arr,value,root,cb);
    }
    if(QueryTypeEnum.gt.name().equals(arr[1])){
        return getGreaterThanPredicate(arr,value,root,cb);
    }
    if(QueryTypeEnum.gte.name().equals(arr[1])){
        return getGreaterThanOrEqualToPredicate(arr,value,root,cb);
    }
    throw new UnsupportedOperationException(String.format("不支持的查询类型[%s]",arr[1]));
}

private Predicate getLessThanPredicate(String[] arr, Object value,
        Root root, CriteriaBuilder cb) {
    if(Integer.class == value.getClass()){
        return cb.lessThan(root.get(arr[0]).as(Integer.class), (int)value);
    }
    if(Long.class == value.getClass()){
        return cb.lessThan(root.get(arr[0]).as(Long.class), (long)value);
    }
    if(Double.class == value.getClass()){
        return cb.lessThan(root.get(arr[0]).as(Double.class), (double)value);
    }
    if(Float.class == value.getClass()){
        return cb.lessThan(root.get(arr[0]).as(Float.class), (float)value);
    }
    if(Timestamp.class == value.getClass()){
        return cb.lessThan(root.get(arr[0]).as(Timestamp.class), (Timestamp)value);
    }
    if(Date.class == value.getClass()){
        return cb.lessThan(root.get(arr[0]).as(Date.class), (Date)value);
    }
    return cb.lessThan(root.get(arr[0]).as(String.class), String.valueOf(value));
}

private Predicate getLessThanOrEqualToPredicate(String[] arr,
        Object value, Root root, CriteriaBuilder cb) {
    if(Integer.class == value.getClass()){
        return cb.lessThanOrEqualTo(root.get(arr[0]).as(Integer.class), (int)value);
    }
    if(Long.class == value.getClass()){
        return cb.lessThanOrEqualTo(root.get(arr[0]).as(Long.class), (long)value);
    }
    if(Double.class == value.getClass()){
        return cb.lessThanOrEqualTo(root.get(arr[0]).as(Double.class), (double)value);
    }
    if(Float.class == value.getClass()){
        return cb.lessThanOrEqualTo(root.get(arr[0]).as(Float.class), (float)value);
    }
    if(Timestamp.class == value.getClass()){
        return cb.lessThanOrEqualTo(root.get(arr[0]).as(Timestamp.class), (Timestamp)value);
    }
    if(Date.class == value.getClass()){
        return cb.lessThanOrEqualTo(root.get(arr[0]).as(Date.class), (Date)value);
    }
    return cb.lessThanOrEqualTo(root.get(arr[0]).as(String.class), String.valueOf(value));
}

private Predicate getGreaterThanPredicate(String[] arr,
        Object value, Root root, CriteriaBuilder cb) {
    if(Integer.class == value.getClass()){
        return cb.greaterThan(root.get(arr[0]).as(Integer.class), (int)value);
    }
    if(Long.class == value.getClass()){
        return cb.greaterThan(root.get(arr[0]).as(Long.class), (long)value);
    }
    if(Double.class == value.getClass()){
        return cb.greaterThan(root.get(arr[0]).as(Double.class), (double)value);
    }
    if(Float.class == value.getClass()){
        return cb.greaterThan(root.get(arr[0]).as(Float.class), (float)value);
    }
    if(Timestamp.class == value.getClass()){
        return cb.greaterThan(root.get(arr[0]).as(Timestamp.class), (Timestamp)value);
    }
    if(Date.class == value.getClass()){
        return cb.greaterThan(root.get(arr[0]).as(Date.class), (Date)value);
    }
    return cb.greaterThan(root.get(arr[0]).as(String.class), String.valueOf(value));
}

private Predicate getGreaterThanOrEqualToPredicate(String[] arr,Object value,
        Root root, CriteriaBuilder cb) {
    if(Integer.class == value.getClass()){
        return cb.greaterThanOrEqualTo(root.get(arr[0]).as(Integer.class), (int)value);
    }
    if(Long.class == value.getClass()){
        return cb.greaterThanOrEqualTo(root.get(arr[0]).as(Long.class), (long)value);
    }
    if(Double.class == value.getClass()){
        return cb.greaterThanOrEqualTo(root.get(arr[0]).as(Double.class), (double)value);
    }
    if(Float.class == value.getClass()){
        return cb.greaterThanOrEqualTo(root.get(arr[0]).as(Float.class), (float)value);
    }
    if(Timestamp.class == value.getClass()){
        return cb.greaterThanOrEqualTo(root.get(arr[0]).as(Timestamp.class), (Timestamp)value);
    }
    if(Date.class == value.getClass()){
        return cb.greaterThanOrEqualTo(root.get(arr[0]).as(Date.class), (Date)value);
    }
    return cb.lessThanOrEqualTo(root.get(arr[0]).as(String.class), String.valueOf(value));
}  

@ApiModel(value="查询条件支持的过滤方式")
public enum QueryTypeEnum {
    like,
    equal,
    ne,
    lt,
    lte,
    gt,
    gte
}

//使用示例
Map params = Maps.newHashMap();
params.put("type", type);
params.put("status", status);
params.put("username:like", username);
params.put("name:like", name);
Page rs = this.userService.list(params, new PageRequest(page, size, new Sort(Direction.DESC, "updateTime")));

提前公共list方法,查询条件在map内设置,查询条件在key内设置,这样大部分的查询请求就可以不再关注Specification的语法,不用写那一大段的复杂代码了

小结

  1. 配置JPA很简单,添加maven依赖,配置数据库连接信息,dao继承上层类即可在service内注入dao,进行crud等操作
  2. JPA提供了简便的根据方法名称进行查询的方式,使用难度很低
  3. JPA通过@Query注解,支持类HQL语句查询;也可以使用原生SQL查询,只需要将nativeQuery属性设置为true即可
  4. 无条件分页查询可通过自带的findAll方法即可
  5. 多条件分页查询,有两种实现方式,当每个条件都是必选时,可使用@query带分页条件来实现;当有可选条件时,需要使用Specification来实现
  6. 为了简化常见的多个可选条件分页查询的代码,在service层提供了一个上层方法,以map的方式设置查询条件,大部分情况下不需要程序员再关注Specification的语法,降低使用难度

本人搭建好的spring boot web后端开发框架已上传至GitHub,欢迎吐槽!
https://github.com/q7322068/rest-base,已用于多个正式项目,当前可能因为版本问题不是很完善,后续持续优化,希望你能有所收获!

你可能感兴趣的:(spring boot实战之JPA)