SpringBoot系列——Spring-Data-JPA(升级版)
前言
在上篇博客中:SpringBoot系列——Spring-Data-JPA:https://www.cnblogs.com/huanzi-qch/p/9970545.html,我们实现了单表的基础get、save(插入/更新)、list、page、delete接口,但是这样每个单表都要写着一套代码,重复而繁杂,那能不能写成一套通用common代码,每个单表去继承从而实现这套基础接口呢?同时,我们应该用Vo去接收、传输数据,实体负责与数据库表映射。
common代码
Vo与实体转换
/**
* 实体转换工具
*/
public class FastCopy { /**
* 类型转换:实体Vo <->实体 例如:UserVo <-> User
* 支持一级复杂对象复制
*/
public static <T> T copy(Object src, Class<T> targetType) {
T target = null;
try {
target = targetType.newInstance();
BeanWrapper targetBean = new BeanWrapperImpl(target);
BeanMap srcBean = new BeanMap(src);
for (Object key : srcBean.keySet()) {
try {
String srcPropertyName = key + "";
Object srcPropertyVal = srcBean.get(key);
//&& StringUtils.isEmpty(targetBean.getPropertyValue(srcPropertyName))
if (!StringUtils.isEmpty(srcPropertyVal) && !"class".equals(srcPropertyName)) {
Class srcPropertyType = srcBean.getType(srcPropertyName);
Class targetPropertyType = targetBean.getPropertyType(srcPropertyName);
if (targetPropertyType != null) {
if (srcPropertyType == targetPropertyType) {
targetBean.setPropertyValue(srcPropertyName, srcPropertyVal);
} else {
if(srcPropertyVal == null){
continue;
}
Object targetPropertyVal = targetPropertyType.newInstance();
BeanUtils.copyProperties(srcPropertyVal, targetPropertyVal);
targetBean.setPropertyValue(srcPropertyName, targetPropertyVal); BeanWrapper targetBean2 = new BeanWrapperImpl(targetPropertyVal);
BeanMap srcBean2 = new BeanMap(srcPropertyVal);
srcBean2.keySet().forEach((srcPropertyName2) -> {
Class srcPropertyType2 = srcBean2.getType((String) srcPropertyName2);
Class targetPropertyType2 = targetBean2.getPropertyType((String) srcPropertyName2);
if (targetPropertyType2 != null && srcPropertyType2 != targetPropertyType2
&& srcBean2.get(srcPropertyName2) != null && !"class".equals(srcPropertyName2)) {
Object targetPropertyVal2 = null;
try {
targetPropertyVal2 = targetPropertyType2.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
BeanUtils.copyProperties(srcBean2.get(srcPropertyName2), targetPropertyVal2);
targetBean2.setPropertyValue((String) srcPropertyName2, targetPropertyVal2);
}
}); }
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return target;
} /**
* 类型转换:实体Vo <->实体 例如:List<UserVo> <-> List<User>
*/
public static <T> List<T> copyList(List srcList, Class<T> targetType) {
List<T> newList = new ArrayList<>();
for (Object entity : srcList) {
newList.add(copy(entity, targetType));
}
return newList;
} /**
* 获取/过滤对象的空属性
*/
public static String[] getNullProperties(Object src) {
BeanWrapper srcBean = new BeanWrapperImpl(src); //1.获取Bean
Set<String> properties = new HashSet<>(); //3.获取Bean的空属性
for (PropertyDescriptor p : srcBean.getPropertyDescriptors()) {
String propertyName = p.getName();
Object srcValue = srcBean.getPropertyValue(propertyName);
if (StringUtils.isEmpty(srcValue)) {
srcBean.setPropertyValue(propertyName, null);
properties.add(propertyName);
}
}
String[] result = new String[properties.size()];
return properties.toArray(result);
} /**
* 获取对象的非空属性
*/
public static Map<String, Object> getNotNullProperties(Object src) {
BeanWrapper srcBean = new BeanWrapperImpl(src); //1.获取Bean
PropertyDescriptor[] pds = srcBean.getPropertyDescriptors(); //2.获取Bean的属性描述
Map<String, Object> properties = new LinkedHashMap<>(); //3.获取Bean的非空属性
for (PropertyDescriptor p : pds) {
String key = p.getName();
Object value = srcBean.getPropertyValue(key);
if (!StringUtils.isEmpty(value) && !"class".equals(key)) {
properties.put(key, value);
}
} return properties;
} /**
* 将Object数组转为实体类VO
*/
public static <V> List<V> getEntityVo(List<Object[]> propertyArrayList, Class<V> voClass) {
List<V> list = new ArrayList<>();
try {
if (propertyArrayList != null) {
for (Object[] propertyArray : propertyArrayList) {
V vo = voClass.newInstance();
Field[] fields = vo.getClass().getDeclaredFields();
for (int i = 0; i < propertyArray.length; i++) {
Field voField = fields[i];
Object queryVal = propertyArray[i];
if (voField.getType() == String.class && queryVal instanceof BigDecimal) {
queryVal = String.valueOf(queryVal);
}
voField.setAccessible(true);//获取授权
voField.set(vo, queryVal);
}
list.add(vo);
} }
} catch (Exception e) {
throw new RuntimeException(e);
}
return list;
} }
注:BeanMap类引入的是:org.apache.commons.beanutils.BeanMap;
引入这两个jar
<!-- Vo与实体的转换工具类需要用到 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
PS:2019-06-13补充,后续发现这个工具类有点复杂,而且阅读起来很吃力,因此特意重写了一下,逻辑更清晰,注释健全,类名不重要(因为后面我们进行了改名,改成了CopyUtil),重点关注copy方法
package cn.huanzi.qch.springbootjpa.util; import org.apache.commons.beanutils.BeanMap;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl; import java.util.ArrayList;
import java.util.List; /**
* 实体转换工具
*/
public class CopyUtil { /**
* 类型转换:实体Vo <->实体 例如:UserVo <-> User
* 支持一级复杂对象复制
*/
public static <T> T copy(Object src, Class<T> targetType) {
T target = null;
try {
//创建一个空目标对象,并获取一个BeanWrapper代理器,用于属性填充,BeanWrapperImpl在内部使用Spring的BeanUtils工具类对Bean进行反射操作,设置属性。
target = targetType.newInstance();
BeanWrapper targetBean = new BeanWrapperImpl(target); //获取源对象的BeanMap,属性和属性值直接转换为Map的key-value 形式
BeanMap srcBean = new BeanMap(src);
for (Object key : srcBean.keySet()) {
//源对象属性名称
String srcPropertyName = key + "";
//源对象属性值
Object srcPropertyVal = srcBean.get(key);
//源对象属性类型
Class srcPropertyType = srcBean.getType(srcPropertyName);
//目标对象属性类型
Class targetPropertyType = targetBean.getPropertyType(srcPropertyName); //源对象属性值非空判断、目标对象属性类型非空判断,如果为空跳出,继续操作下一个属性
if ("class".equals(srcPropertyName) || targetPropertyType == null) {
continue;
} //类型相等,可直接设置值,比如:String与String 或者 User与User
if (srcPropertyType == targetPropertyType) {
targetBean.setPropertyValue(srcPropertyName, srcPropertyVal);
}
//类型不相等,比如:User与UserVo
else {
/* 下面的步骤与上面的步骤基本一致 */ //如果源复杂对象为null,直接跳过,不需要复制
if(srcPropertyVal == null){
continue;
} Object targetPropertyVal = targetPropertyType.newInstance();
BeanWrapper targetPropertyBean = new BeanWrapperImpl(targetPropertyVal); BeanMap srcPropertyBean = new BeanMap(srcPropertyVal);
for (Object srcPropertyBeanKey : srcPropertyBean.keySet()) {
String srcPropertyBeanPropertyName = srcPropertyBeanKey + "";
Object srcPropertyBeanPropertyVal = srcPropertyBean.get(srcPropertyBeanKey);
Class srcPropertyBeanPropertyType = srcPropertyBean.getType(srcPropertyBeanPropertyName);
Class targetPropertyBeanPropertyType = targetPropertyBean.getPropertyType(srcPropertyBeanPropertyName); if ("class".equals(srcPropertyBeanPropertyName) || targetPropertyBeanPropertyType == null) {
continue;
} if (srcPropertyBeanPropertyType == targetPropertyBeanPropertyType) {
targetPropertyBean.setPropertyValue(srcPropertyBeanPropertyName, srcPropertyBeanPropertyVal);
} else {
//复杂对象里面的复杂对象不再进行处理
}
}
//设置目标对象属性值
targetBean.setPropertyValue(srcPropertyName, targetPropertyBean.getWrappedInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return target;
} /**
* 类型转换:实体Vo <->实体 例如:List<UserVo> <-> List<User>
*/ public static <T> List<T> copyList(List srcList, Class<T> targetType) {
List<T> newList = new ArrayList<>();
for (Object entity : srcList) {
newList.add(copy(entity, targetType));
}
return newList;
} }
通用service、repository
/**
* 通用Service
*
* @param <V> 实体类Vo
* @param <E> 实体类
* @param <T> id主键类型
*/
public interface CommonService<V, E,T> { Result<PageInfo<V>> page(V entityVo); Result<List<V>> list(V entityVo); Result<V> get(T id); Result<V> save(V entityVo); Result<T> delete(T id);
}
/**
* 通用Service实现类
*
* @param <V> 实体类Vo
* @param <E> 实体类
* @param <T> id主键类型
*/
public class CommonServiceImpl<V, E, T> implements CommonService<V, E, T> { private Class<V> entityVoClass;//实体类Vo private Class<E> entityClass;//实体类 @Autowired
private CommonRepository<E, T> commonRepository;//注入实体类仓库 public CommonServiceImpl() {
Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
this.entityVoClass = (Class<V>) types[0];
this.entityClass = (Class<E>) types[1];
} @Override
public Result<PageInfo<V>> page(V entityVo) {
//实体类缺失分页信息
if (!(entityVo instanceof PageCondition)) {
throw new RuntimeException("实体类" + entityVoClass.getName() + "未继承PageCondition。");
}
PageCondition pageCondition = (PageCondition) entityVo;
Page<E> page = commonRepository.findAll(Example.of(FastCopy.copy(entityVo, entityClass)), pageCondition.getPageable());
return Result.of(PageInfo.of(page, entityVoClass));
} @Override
public Result<List<V>> list(V entityVo) {
List<E> entityList = commonRepository.findAll(Example.of(FastCopy.copy(entityVo, entityClass)));
List<V> entityModelList = FastCopy.copyList(entityList, entityVoClass);
return Result.of(entityModelList);
} @Override
public Result<V> get(T id) {
Optional<E> optionalE = commonRepository.findById(id);
if (!optionalE.isPresent()) {
throw new RuntimeException("ID不存在!");
}
return Result.of(FastCopy.copy(optionalE.get(), entityVoClass));
} @Override
public Result<V> save(V entityVo) {
E e = commonRepository.save(FastCopy.copy(entityVo, entityClass));
return Result.of(FastCopy.copy(e, entityVoClass));
} @Override
public Result<T> delete(T id) {
commonRepository.deleteById(id);
return Result.of(id);
}
}
/**
* 通用Repository
*
* @param <E> 实体类
* @param <T> id主键类型
*/
@NoRepositoryBean
public interface CommonRepository<E,T> extends JpaRepository<E,T>, JpaSpecificationExecutor<E> {
}
2019-05-13更新
jpa实现局部更新
注意:jpa原生的save方法,更新的时候是全属性进行updata,如果实体类的属性没有值它会帮你更新成null,如果你想更新部分字段请在通用CommonServiceImpl使用这个save方法,我这里是在调用save之前先查询数据库获取完整对象,将要更新的值复制到最终传入save方法的对象中,从而实现局部更新
@Override
public Result<V> save(V entityVo) {
//传进来的对象(属性可能残缺)
E entity = CopyUtil.copy(entityVo, entityClass); //最终要保存的对象
E entityFull = entity; //为空的属性值,忽略属性,BeanUtils复制的时候用到
List<String> ignoreProperties = new ArrayList<String>(); //获取最新数据,解决部分更新时jpa其他字段设置null问题
try {
//反射获取Class的属性(Field表示类中的成员变量)
for (Field field : entity.getClass().getDeclaredFields()) {
//获取授权
field.setAccessible(true);
//属性名称
String fieldName = field.getName();
//属性的值
Object fieldValue = field.get(entity); //找出Id主键
if (field.isAnnotationPresent(Id.class) && !StringUtils.isEmpty(fieldValue)) {
Optional<E> one = commonRepository.findById((T) fieldValue);
if (one.isPresent()) {
entityFull = one.get();
}
} //找出值为空的属性,值为空则为忽略属性,或者被NotFound标注,我们复制的时候不进行赋值
if(null == fieldValue || field.isAnnotationPresent(NotFound.class)){
ignoreProperties.add(fieldName);
}
}
/*
org.springframework.beans BeanUtils.copyProperties(A,B); 是A中的值付给B
org.apache.commons.beanutils; BeanUtils.copyProperties(A,B);是B中的值付给A
把entity的值赋给entityFull,第三个参数是忽略属性,表示不进行赋值
*/
BeanUtils.copyProperties(entity, entityFull, ignoreProperties.toArray(new String[0]));
} catch (IllegalAccessException e) {
e.printStackTrace();
} E e = commonRepository.save(entityFull);
return Result.of(CopyUtil.copy(e, entityVoClass));
}
2019-09-18补充:上面的save方法实现了局部更新,也就是每次调用save之前先用id去查库,然后替换传进来的值;本次实现的是,如果是新增,自动添加UUID为主键,同时自动判断createTime,updateTime,也就是说如果前端不传这两个值,后台来维护创建时间、更新时间,当然,这种便利是有前提的,要求实体类的Id属性排在第一位
@Override
public Result<V> save(V entityVo) {
//传进来的对象(属性可能残缺)
E entity = CopyUtil.copy(entityVo, entityClass); //最终要保存的对象
E entityFull = entity; //为空的属性值,忽略属性,BeanUtils复制的时候用到
List<String> ignoreProperties = new ArrayList<String>(); //获取最新数据,解决部分更新时jpa其他字段设置null问题
try {
//新增 true,更新 false,要求实体类的Id属性排在第一位,因为for循环读取是按照顺序的
boolean isInsert = false; //反射获取Class的属性(Field表示类中的成员变量)
for (Field field : entity.getClass().getDeclaredFields()) {
//获取授权
field.setAccessible(true);
//属性名称
String fieldName = field.getName();
//属性的值
Object fieldValue = field.get(entity); //找出Id主键
if (field.isAnnotationPresent(Id.class)) {
if(!StringUtils.isEmpty(fieldValue)){
//如果Id主键不为空,则为更新
Optional<E> one = commonRepository.findById((T) fieldValue);
if (one.isPresent()) {
entityFull = one.get();
}
}else{
//如果Id主键为空,则为新增
fieldValue = UUIDUtil.getUUID();
//set方法,第一个参数是对象
field.set(entity, fieldValue);
isInsert = true;
}
}
//如果前端不传这两个值,后台来维护创建时间、更新时间
if(isInsert && "createTime".equals(fieldName) && StringUtils.isEmpty(fieldValue)){
//set方法,第一个参数是对象
field.set(entity, new Date());
}
if("updateTime".equals(fieldName) && StringUtils.isEmpty(fieldValue)){
//set方法,第一个参数是对象
field.set(entity, new Date());
} //找出值为空的属性,值为空则为忽略属性,或者被NotFound标注,我们复制的时候不进行赋值
if(null == fieldValue || field.isAnnotationPresent(NotFound.class)){
ignoreProperties.add(fieldName);
}
}
/*
org.springframework.beans BeanUtils.copyProperties(A,B); 是A中的值付给B
org.apache.commons.beanutils; BeanUtils.copyProperties(A,B);是B中的值付给A
把entity的值赋给entityFull,第三个参数是忽略属性,表示不进行赋值
*/
BeanUtils.copyProperties(entity, entityFull, ignoreProperties.toArray(new String[0]));
} catch (IllegalAccessException e) {
e.printStackTrace();
} E e = commonRepository.save(entityFull);
return Result.of(CopyUtil.copy(e, entityVoClass));
}
需要用到UUID工具类
import java.util.UUID; /**
* UUID工具类
*/
public class UUIDUtil { /**
* 生成32位UUID编码
*/
public static String getUUID(){
return UUID.randomUUID().toString().trim().replaceAll("-", "");
}
}
单表使用
单表继承通用代码,实现get、save(插入/更新)、list、page、delete接口
Vo
/**
* 用户类Vo
*/
@Data
public class UserVo extends PageCondition implements Serializable { private Integer id; private String username; private String password; private Date created; private String descriptionId; //机架类型信息
private DescriptionVo description;
}
/**
* 用户描述类Vo
*/
@Data
public class DescriptionVo implements Serializable {
private Integer id; private String userId; private String description;
}
controller、service、repository
@RestController
@RequestMapping("/user")
public class UserController { @Autowired
private UserService userService; @RequestMapping("/getAllUser")
public ModelAndView getAllUser(){
Result result=userService.getAllUser();
ModelAndView mv=new ModelAndView();
mv.addObject("userList",result.getData());
mv.setViewName("index.html");
return mv;
} /*
CRUD、分页、排序
*/ @RequestMapping("page")
public Result<PageInfo<UserVo>> page(UserVo entityVo) {
return userService.page(entityVo);
} @RequestMapping("list")
public Result<List<UserVo>> list(UserVo entityVo) {
return userService.list(entityVo);
} @RequestMapping("get/{id}")
public Result<UserVo> get(@PathVariable("id") Integer id) {
return userService.get(id);
} @RequestMapping("save")
public Result<UserVo> save(UserVo entityVo) {
return userService.save(entityVo);
} @RequestMapping("delete/{id}")
public Result<Integer> delete(@PathVariable("id") Integer id) {
return userService.delete(id);
}
}
public interface UserService extends CommonService<UserVo, User,Integer>{ Result getAllUser();
}
@Service
@Transactional
public class UserServiceImpl extends CommonServiceImpl<UserVo, User,Integer> implements UserService { @Autowired
private UserRepository userRepository; @Override
public Result getAllUser() {
List<User> userList = userRepository.getAllUser();
if(userList != null && userList.size()>0){
ArrayList<UserVo> userVos = new ArrayList<>();
for(User user : userList){
userVos.add(FastCopy.copy(user, UserVo.class));
}
return Result.of(userVos);
}else {
return Result.of(userList,false,"获取失败!");
}
}
}
@Repository
public interface UserRepository extends CommonRepository<User, Integer> { @Query(value = "from User") //HQL
// @Query(value = "select * from tb_user",nativeQuery = true)//原生SQL
List<User> getAllUser(); }
经测试,所有的接口都可以使用,数据传输正常,因为传输的Vo,分页信息跟杂七杂八的字段、数据都在Vo,所有看起来会比较杂。更新接口依旧跟上一篇的一样,接收到的是什么就保存什么。
后记
单表的增删改查接口,直接继承这一套通用代码即可实现,无需再重复编写,大大提升开发效率。
代码开源
代码已经开源、托管到我的GitHub、码云:
GitHub:https://github.com/huanzi-qch/springBoot
码云:https://gitee.com/huanzi-qch/springBoot
SpringBoot系列——Spring-Data-JPA(升级版)的更多相关文章
- spring-boot (三) spring data jpa
学习文章来自:http://www.ityouknow.com/spring-boot.html spring data jpa介绍 首先了解JPA是什么? JPA(Java Persistence ...
- springboot整合spring Data JPA
今天敲代码,一连串的错误,我也是服气~果然,我们不是在出bug,就是在找bug的路上…… 今天完成的是springboot整合spring data JPA ,出了一连串的错,真是头大 java.sq ...
- springboot整合spring data jpa 动态查询
Spring Data JPA虽然大大的简化了持久层的开发,但是在实际开发中,很多地方都需要高级动态查询,在实现动态查询时我们需要用到Criteria API,主要是以下三个: 1.Criteria ...
- springboot集成Spring Data JPA数据查询
1.JPA介绍 JPA(Java Persistence API)是Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.它的出现主要是为 ...
- spring boot学习(4) SpringBoot 之Spring Data Jpa 支持(1)
第一节:Spring Data Jpa 简介 Spring-Data-Jpa JPA(Java Persistence API)定义了一系列对象持久化的标准,目前实现这一规范的产品有Hibernate ...
- IntelliJ IDEA 2017版 spring-boot使用Spring Data JPA使用Repository<T, T>编程
1.环境搭建pom.xml搭建 <?xml version="1.0" encoding="UTF-8"?> <project xmlns=& ...
- spring boot学习(5) SpringBoot 之Spring Data Jpa 支持(2)
第三节:自定义查询@Query 有时候复杂sql使用hql方式无法查询,这时候使用本地查询,使用原生sql的方式: 第四节:动态查询Specification 使用 什么时候用呢?比如搜索有很多条 ...
- IntelliJ IDEA 2017版 spring-boot使用Spring Data JPA搭建基础版的三层架构
1.配置环境pom <?xml version="1.0" encoding="UTF-8"?> <project xmlns="h ...
- PostgreSQL 、springboot 、spring data jpa 集成
项目地址:https://gitee.com/zhxs_code/PostgreSQL_springboot_jpa_demo.git 增删查改都已经实现. 重点部分: 1.定义自己的方言. pack ...
- SpringBoot系列之Spring Data Jpa集成教程
SpringBoot系列之Spring Data Jpa集成教程 Spring Data Jpa是属于Spring Data的一个子项目,Spring data项目是一款集成了很多数据操作的项目,其下 ...
随机推荐
- Vue(二十九)页面加载过慢问题
1.使用按需加载 2.路由懒加载
- koa 写简单服务
这两天用koa写了点服务,这里面和express还是有部分区别的 1.静态服务: koa 中,是有中间件, koa-static, const static_f = require('koa-sta ...
- ArcGIS Runtime For Android 100.3天地图不加载问题
ArcGIS Runtime 100.3 不加载天地图问题 参考这篇帖子:https://community.esri.com/thread/220496-1003-webtiledlayer-can ...
- Redmine 安装、搭建
参考两个博客: 1.https://blog.csdn.net/g19881118/article/details/59476045 2.https://www.cnblogs.com/chendal ...
- C语言复习2_运算符
今天复习一下C语言的运算符 1.赋值运算符 单等号 = 顺序是:从右往左 2.复合运算符 #include <stdio.h> #include <stdlib.h> int ...
- ubuntu系统下手动安装autoconf安装包
首先简单介绍一下autoconf.Autoconf是一个可以适应多种unix类系统的shell脚本的工具. 我在往虚拟机中安装应用时,需要用到该工具,于是想下载一个.但是由于系统内核版本低,已不能用a ...
- freekan5.9电影网站安装及源码分享
Freekan是一套目前非常火的电影网站系统,全自动采集,支持对接公众号 服务器环境:centos7,宝塔面板,php7.1(重要),nignx,mysql 1.首先上传压缩包到网站目录,然后解压 2 ...
- [SQL]LeetCode601. 体育馆的人流量 | Human Traffic of Stadium
SQL架构 Create table If Not Exists stadium (id int, visit_date DATE NULL, people int) Truncate table s ...
- springboot 实战之一站式开发体验
都说springboot是新形势的主流框架工具,然而我的工作中并没有真正用到springboot: 都说springboot里面并没有什么新技术,不过是组合了现有的组件而已,但是自己却说不出来: 都说 ...
- java8新特征
一:Lambda 表达式 为什么使用 :使用 Lambda 表达式可以使代码变的更加简洁紧凑. 表达了什么?: 匿名内部类的新写法: 语法 :(parameters) -> express ...