公司的项目中很大一部分属于内部平台,所以对性能的要求没有那么高,开发速度反而更重要,因此在搭建基础框架时选择使用JPA,没有使用mybitis,当然其中也有一部分原因是之前一直使用hibernate,对mybitis不太熟悉_。
一、配置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等组成方法名,也就是用方法名来描述查询规则。如果是嵌套对象,可以通过“_"来区分子对象的属性,比如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<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
- NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> 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开始
多条件复杂分页查询
带条件分页查询有两种方式:
- 使用原生SQL进行分页查询,但是前提是多个查询条件必须同时存在,不能有不生效的条件,比如用户列表,用户姓名可以不作为过滤条件,这种情况原生SQL就不适用了,需要使用下面第二种方式
- 使用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,已用于多个正式项目,当前可能因为版本问题不是很完善,后续持续优化,希望你能有所收获!