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与实体转换,逻辑更清晰,注释健全,类名不重要(因为后面我们进行了改名,之前叫FastCopy,现在改成了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) { //输出到日志文件中 log.error(ErrorUtil.errorInfoToString(e)); } 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; } }
注: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>
2020-10-16更新
优化了copy方法,支持多层复杂对象复制,同时新增 Object[]转Vo方法
package cn.huanzi.qch.baseadmin.util; import lombok.extern.slf4j.Slf4j; import org.apache.commons.beanutils.BeanMap; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import java.lang.reflect.Field; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * 实体转换工具 */ @Slf4j public class CopyUtil { /** * 类型转换:实体Vo <->实体 例如:UserVo <-> User * 默认支持1层复杂对象复制 */ public static <T> T copy(Object src, Class<T> targetType) { return CopyUtil.copy(src,targetType,1); } /** * 同上,支持count多层复杂对象复制 */ public static <T> T copy(Object src, Class<T> targetType,Integer count) { //执行一层,自减1 count--; 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 { //满足条件,跳出递归 if(count <= -1){ return target; } //如果源复杂对象为null,直接跳过,不需要复制 if(srcPropertyVal == null){ continue; } //设置目标对象属性值 targetBean.setPropertyValue(srcPropertyName, CopyUtil.copy(srcPropertyVal, targetPropertyType, count)); } } } catch (Exception e) { //输出到日志文件中 log.error(ErrorUtil.errorInfoToString(e)); } 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 src : srcList) { newList.add(CopyUtil.copy(src, targetType)); } return newList; } /** * 类型转换:Object[]转Vo * 当使用自定义SQL查询,查询字段跟实体对应不上时,可以使用Object[]接值 * em.createNativeQuery(sql.toString()),第二个参数不传时,默认就是用Object[]来接值 * 因为是Object[]转Vo,是按顺序来取值、设置,所有要求两边的字段、属性顺序要一一对应 */ public static <T> T copyByObject(Object[] src, Class<T> targetType){ T targetVo = null; try { //遍历Object[]转换为Field[] targetVo = targetType.newInstance(); Field[] fields = targetType.getDeclaredFields(); int length = src.length < fields.length ? src.length : fields.length; for (int i = 0; i < length; i++) { Field field = fields[i]; Object fieldVal = src[i]; if (fieldVal instanceof Character || fieldVal instanceof BigDecimal) { fieldVal = String.valueOf(fieldVal); } field.setAccessible(true);//获取授权 field.set(targetVo, fieldVal);//赋值 } } catch (InstantiationException | IllegalAccessException e) { ErrorUtil.errorInfoToString(e); } return targetVo; } /** * 类型转换:List<Object[]>转List<Vo> */ public static <T> List<T> copyListByObject(List<Object[]> srcList, Class<T> targetType) { List<T> newList = new ArrayList<>(); if (srcList != null) { for (Object[] src : srcList) { newList.add(CopyUtil.copyByObject(src,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方法的对象中,从而实现局部更新
另外,直接调用EntityManager的merge,也是传什么就保存什么
@PersistenceContext private EntityManager em; //注意:直接调用EntityManager的merge,传进去的实体字段是什么就保存什么 E e = em.merge(entity); em.flush();
@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)){ //先赋值给fieldValue,以免后续进行copy对象判断属性是否为忽略属性是出错 fieldValue = new Date(); //set方法,第一个参数是对象 field.set(entity, fieldValue); } if("updateTime".equals(fieldName) && StringUtils.isEmpty(fieldValue)){ //先赋值给fieldValue,以免后续进行copy对象判断属性是否为忽略属性是出错 fieldValue = new Date(); //set方法,第一个参数是对象 field.set(entity, fieldValue); } //找出值为空的属性,值为空则为忽略属性,或者被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、码云:
版权声明
捐献、打赏
支付宝
微信