[置顶] spring boot项目实战:JPA
2017-10-03 21:42
531 查看
公司的项目中很大一部分属于内部平台,所以对性能的要求没有那么高,开发速度反而更重要,因此在搭建基础框架时选择使用JPA,没有使用mybitis,当然其中也有一部分原因是之前一直使用hibernate,对mybitis不太熟悉^_^。
2、 添加数据库配置
3、dao继承上层类
为了便于使用,提取了一个BaseDao,需要注意的是要在上层类上添加@ NoRepositoryBean注解。BaseDao继承JpaRepository和JpaSpecificationExecutor,JpaRepository提供了基本的crud等查询方法,JpaSpecificationExecutor提供了对复杂查询的支持。
完成了以上三步,已经可以在service内注入dao,通过dao进行数据库curd等操作。
简单查询可以通过以上方式方便的实现,简化了很多dao层的代码,使用着还是很爽的,具体规则比较简单,基本上就是findBy开始,后续跟上实体属性,中间配以And、Or、In、like等组成方法名,也就是用方法名来描述查询规
4000
则。如果是嵌套对象,可以通过“_”来区分子对象的属性,比如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 类型,也可以是数组或者不定长参数;
在@Query内直接书写HQL语句即可,参数可通过”?1,?2”这样的方式设置,下标从1开始。
原生sql查询
使用也比较简单,将@Query内的nativeQuery设置为true即可。写SQL语句时,可以现在本地mysql客户端上测试号SQL语句的正确性,当需要索引时,创建合适的索引。在此基础上看下SQL的性能,如不理想,需调整SQL,可使用explain对SQL语句进行分析,查询执行逻辑,针对性优化。
新增、修改
直接调用dao的save方法,支持单个保存和批量保存。
删除
直接调用dao的delete方法,支持根据id、对象、对象集合等删除方式,使用时查看下提示方法就可以了。
使用PageRequest构建分页请求对象,页码下标从0开始
多条件复杂分页查询
带条件分页查询有两种方式:
1. 使用原生SQL进行分页查询,但是前提是多个查询条件必须同时存在,不能有不生效的条件,比如用户列表,用户姓名可以不作为过滤条件,这种情况原生SQL就不适用了,需要使用下面第二种方式
2. 使用Specification进行复杂查询,示例代码如下:
根据以上示例,基本满足了常用的查询需求,更多情况可根据规则尝试一下即可,也可百度搜索下JPA Specification,有很多教程。
简化多条件分页查询
使用Specification需要每次都写一大段模板代码,使用起来还是比较繁琐,使用入门也有些难度,基于此,在service层的公共代码出对查询进行了部分封装,简化常见多条件分页查询。
提前公共list方法,查询条件在map内设置,查询条件在key内设置,这样大部分的查询请求就可以不再关注Specification的语法,不用写那一大段的复杂代码了
JPA提供了简便的根据方法名称进行查询的方式,使用难度很低
JPA通过@Query注解,支持类HQL语句查询;也可以使用原生SQL查询,只需要将nativeQuery属性设置为true即可
无条件分页查询可通过自带的findAll方法即可
多条件页查询,有两种实现方式,当每个条件都是必选时,可使用@query带分页条件来实现;当有可选条件时,需要使用Specification来实现
为了简化常见的多个可选条件分页查询的代码,在service层提供了一个上层方法,以map的方式设置查询条件,大部分情况下不需要程序员再关注Specification的语法,降低使用难度
本人搭建好的spring boot web后端开发框架已上传至GitHub,欢迎吐槽!
https://github.com/q7322068/rest-base,已用于多个正式项目,当前可能因为版本问题不是很完善,后续持续优化,希望你能有所收获!
一、配置JPA
1、添加maven依赖<!-- jpa --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
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, Integer>{ User findByUsernameAndDel(String username, int del); } @NoRepositoryBean public interface BaseDao<T,ID extends Serializable> extends JpaSpecificationExecutor<T>,JpaRepository<T, ID>{ }
为了便于使用,提取了一个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<Role> findByIdIn(Set<Integer> roleIds); //根据用户id,查询用户角色关系记录 List<UserRole> findByUserId(int uid);
简单查询可以通过以上方式方便的实现,简化了很多dao层的代码,使用着还是很爽的,具体规则比较简单,基本上就是findBy开始,后续跟上实体属性,中间配以And、Or、In、like等组成方法名,也就是用方法名来描述查询规
4000
则。如果是嵌套对象,可以通过“_”来区分子对象的属性,比如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<Permission> listByRoleIds(Set<Integer> 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<User> page = userDao.findAll(pageable);
使用PageRequest构建分页请求对象,页码下标从0开始
多条件复杂分页查询
带条件分页查询有两种方式:
1. 使用原生SQL进行分页查询,但是前提是多个查询条件必须同时存在,不能有不生效的条件,比如用户列表,用户姓名可以不作为过滤条件,这种情况原生SQL就不适用了,需要使用下面第二种方式
2. 使用Specification进行复杂查询,示例代码如下:
public Page<User> listByPage(final Map<String, String> params,Pageable pageable){ Specification<User> spec = new Specification<User>() { @Override public Predicate toPredicate(Root<User> root,CriteriaQuery<?> query,CriteriaBuilder cb) { List<Predicate> list = new ArrayList<Predicate>(); 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<Integer> ids = Lists.newArrayList(); ids.add(1); ids.add(2); In<Integer> in = cb.in(root.get("id").as(Integer.class)); in.value(1); in.value(2); return cb.or(in);*/ } }; Page<User> 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<T> list(Map<String, Object> params,Pageable pageable); @Override public Page<T> list(final Map<String, Object> params,Pageable pageable){ Specification<T> spec = new Specification<T>() { @Override public Predicate toPredicate(Root<T> root,CriteriaQuery<?> query,CriteriaBuilder cb) { List<Predicate> list = new ArrayList<Predicate>(); for(Entry<String, Object> 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<T> page = getDAO().findAll(spec, pageable); return page; } private Predicate getPredicate(String[] arr, Object value, Root<T> 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<T> 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<T> 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<T> 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<T> 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<String, Object> params = Maps.newHashMap(); params.put("type", type); params.put("status", status); params.put("username:like", username); params.put("name:like", name); Page<User> rs = this.userService.list(params, new PageRequest(page, size, new Sort(Direction.DESC, "updateTime")));
提前公共list方法,查询条件在map内设置,查询条件在key内设置,这样大部分的查询请求就可以不再关注Specification的语法,不用写那一大段的复杂代码了
小结
配置JPA很简单,添加maven依赖,配置数据库连接信息,dao继承上层类即可在service内注入dao,进行crud等操作JPA提供了简便的根据方法名称进行查询的方式,使用难度很低
JPA通过@Query注解,支持类HQL语句查询;也可以使用原生SQL查询,只需要将nativeQuery属性设置为true即可
无条件分页查询可通过自带的findAll方法即可
多条件页查询,有两种实现方式,当每个条件都是必选时,可使用@query带分页条件来实现;当有可选条件时,需要使用Specification来实现
为了简化常见的多个可选条件分页查询的代码,在service层提供了一个上层方法,以map的方式设置查询条件,大部分情况下不需要程序员再关注Specification的语法,降低使用难度
本人搭建好的spring boot web后端开发框架已上传至GitHub,欢迎吐槽!
https://github.com/q7322068/rest-base,已用于多个正式项目,当前可能因为版本问题不是很完善,后续持续优化,希望你能有所收获!
相关文章推荐
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:swagger2在线文档
- [置顶] spring boot项目实战:redis
- [置顶] spring boot项目实战-集合操作