用习惯了mybatis及mybatisplus,刚开始拿起spring-data-jpa,其无法局部更新、原生sql查询返回非JavaBean的List<Map<String, Object>>
、分页无法返回自定义对象等等的问题着实差点没让我拔出我的30米大砍刀。不过咱吐槽归吐槽,遇到问题,想办法一一去解决问题即可。
一、让JPA支持局部更新
jpa中的新增与更新都是调用的save()方法,以是否携带id做区分,无奈的是它对局部变量更新并未做何处理,依旧需要我们自己动手。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Override @Transactional public Long update(TestCommand command) { Optional<TestEntity> optional = testRepository.findById(command.getId()); if (!optional.isPresent()) { return 0L; } Appointment target = optional.get(); BeanUtils.copyProperties(command, target, BeanUtil.getNullProperty(command)); target.setOther(command.getOther()); TestEntity result = testRepository.save(target); if (result == null) { return 0L; } return result.getId(); }
|
代码中的getNullProperty作为通用工具类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public static String[] getNullProperty(Object source) { final BeanWrapper src = new BeanWrapperImpl(source); PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>(); for (PropertyDescriptor pd : pds) { Object srcValue = src.getPropertyValue(pd.getName()); if (srcValue == null) { emptyNames.add(pd.getName()); } } String[] result = new String[emptyNames.size()]; return emptyNames.toArray(result); }
|
二、让JPA原生SQL查询返回ListBean
jpa使用@Query
注解来做原生sql查询,一般是这样的画风:
1 2
| @Query(value = "SELECT id, name FROM xxx WHERE type = :type", nativeQuery = true) List<Map<String, Object>> findTest(@Param("type") String type);
|
其返回值List<Map<String, Object>>
怎么看怎么不舒服,那么如何将之转换为我们想要的List<JavaBean>
呢?先直接看看经封装转换后的返回效果:
1 2 3 4
| List<Map<String, Object>> mapList = testRepository.findTest("testType");
List<TestResult> beanList = BeanUtil.mapToBean(mapList, TestResult.class);
|
工具类代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
public static <T> List<T> mapToBean(List<Map<String, Object>> datas, Class<T> beanClass) { List<T> list = null; String fieldName = ""; String methodname = ""; Object methodsetvalue = ""; try { list = new ArrayList<T>(); Field fields[] = beanClass.getDeclaredFields(); for (Map<String, Object> mapdata : datas) { T t = beanClass.newInstance(); for (Field field : fields) { fieldName = field.getName(); if ("serialVersionUID".equals(fieldName)) { continue; } methodname = "set" + StringUtils.capitalize(fieldName); methodsetvalue = mapdata.get(fieldName); Method m = beanClass.getDeclaredMethod(methodname, field.getType()); if (field.getType() == Integer.class) { if (methodsetvalue == null) { } else { m.invoke(t, Integer.parseInt(methodsetvalue.toString())); } } else if (field.getType() == Long.class) { if (methodsetvalue == null) { } else { m.invoke(t, Long.parseLong(methodsetvalue.toString())); } } else if (field.getType() == Boolean.class) { if (methodsetvalue == null) { m.invoke(t, Boolean.FALSE); } else { m.invoke(t, Boolean.parseBoolean(methodsetvalue.toString())); } } else if (field.getType() == Byte.class) { if (methodsetvalue == null) { m.invoke(t, null); } else { m.invoke(t, Byte.parseByte(methodsetvalue.toString())); } } else if (field.getType() == Short.class) { if (methodsetvalue == null) { m.invoke(t, null); } else { m.invoke(t, Short.parseShort(methodsetvalue.toString())); } } else if (field.getType() == Float.class) { if (methodsetvalue == null) { m.invoke(t, null); } else { m.invoke(t, Float.parseFloat(methodsetvalue.toString())); } } else if (field.getType() == Double.class) { if (methodsetvalue == null) { m.invoke(t, null); } else { m.invoke(t, Double.parseDouble(methodsetvalue.toString())); } } else { m.invoke(t, methodsetvalue); }
} list.add(t); } } catch (InstantiationException e) { try { throw new Exception("创建beanClass实例异常", e); } catch (Exception ex) { ex.printStackTrace(); } } catch (IllegalAccessException e) { try { throw new Exception("创建beanClass实例异常", e); } catch (Exception ex) { ex.printStackTrace(); } } catch (SecurityException e) { try { throw new Exception("获取[" + fieldName + "] getter setter 方法异常", e); } catch (Exception ex) { ex.printStackTrace(); } } catch (NoSuchMethodException e) { try { throw new Exception("获取[" + fieldName + "] getter setter 方法异常", e); } catch (Exception ex) { ex.printStackTrace(); } } catch (IllegalArgumentException e) { try { throw new Exception("[" + methodname + "] 方法赋值异常", e); } catch (Exception ex) { ex.printStackTrace(); } } catch (InvocationTargetException e) { try { throw new Exception("[" + methodname + "] 方法赋值异常", e); } catch (Exception ex) { ex.printStackTrace(); } } return list; }
|
三、让JPA分页查询返回自定义对象
JPA分页查询的方式有多种,以最常见的Page<TestEntity> page = testRepository.findAll(specification, pageable);
为例,其返回值的泛型即为实体。如果想返回自定对象怎么办?不好意思,在不写SQL的情况下JPA目前并没有相应的支持,只能自己去动手丰衣足食。以Jave8的语法为例,我们可以这样处理:
首先我们需要自己再封装一个Page对象,用来取代原生返回的Page对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| package com.xfktech.nurse.appointment.nursing.common.po;
import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl;
import java.util.List;
@Getter @Setter @NoArgsConstructor public class PageResponse<T> {
private int pageNum;
private int pageSize;
private int totalPage;
private long totalSize;
private boolean last;
private List<T> content;
public static <T> PageResponse<T> of(Page<T> page) { return new PageResponse<>((PageImpl<T>) page); }
private PageResponse(PageImpl<T> page) { this.setPageNum(page.getPageable().getPageNumber() + 1); this.setPageSize(page.getPageable().getPageSize()); this.setTotalPage(page.getTotalPages()); this.setTotalSize(page.getTotalElements()); this.setLast(page.isLast()); this.setContent(page.getContent()); } }
|
为接口返回值新建vo转换类,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TestGenerator { public static TestResult test(TestEntity entity) {
return TestResult.builder() .id(entity.getId()) .name(entity.getCreateTime()) .build(); } }
|
最后,仅需要在如常调用原生分页Page<TestEntity> page = testRepository.findAll(specification, pageable);
之后,将page做以下转换即可:
1
| PageResponse<TestResult> result = PageResponse.of(page.map(TestGenerator::test));
|
如需往test()方法额外传参,那么也可以选择这样写:
1
| PageResponse<TestResult> result = PageResponse.of(page.map(obj -> AlarmGenerator.common(test, "xxx")));
|
完整分页示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Override @Transactional(readOnly = true) public PageResponse<TestResult> list(TestQuery query) { Pageable pageable = PageRequest.of(query.getPageNum(), query.getPageSize()); Specification<TestEntity> specification = (Specification<TestEntity>) (root, criteriaQuery, criteriaBuilder) -> { List<Predicate> list = new ArrayList<>(); list.add(criteriaBuilder.equal(root.get("isDelete"), false)); criteriaQuery.orderBy(criteriaBuilder.desc(root.get("createTime"))); return criteriaBuilder.and(list.toArray(new Predicate[list.size()])); }; Page<TestEntity> page = testRepository.findAll(specification, pageable); PageResponse<TestResult> result = PageResponse.of(page.map(TestGenerator :: test)); return result; }
|
The end.
参考资料: