在上篇博客中:SpringBoot系列——Spring-Data-JPA:http://www.javashuo.com/article/p-eciktnjw-z.html,咱們實現了單表的基礎get、save(插入/更新)、list、page、delete接口,可是這樣每一個單表都要寫着一套代碼,重複而繁雜,那能不能寫成一套通用common代碼,每一個單表去繼承從而實現這套基礎接口呢?同時,咱們應該用Vo去接收、傳輸數據,實體負責與數據庫表映射。html
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;
}java
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;git
引入這兩個jargithub
<!-- 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方法spring
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更新apache
jpa實現局部更新數組
注意:jpa原生的save方法,更新的時候是全屬性進行updata,若是實體類的屬性沒有值它會幫你更新成null,若是你想更新部分字段請在通用CommonServiceImpl使用這個save方法,我這裏是在調用save以前先查詢數據庫獲取完整對象,將要更新的值複製到最終傳入save方法的對象中,從而實現局部更新
springboot
@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、碼雲: