分享公司Entity与DTO之间数据拷贝的方法
主题
最早以前自学java web的时候,数据库查询出来一个Entity对象(CMP对象).就直接传给前台展示了.并没有用到DTO对象,开始并没有觉得有什么不好...后来发现还是需要一些DTO对象来专门用来传值与前台展示用的.因为直接使用Entity对象有几个地方会比较麻烦:
1.Entity对象的成员域和数据库字段是对应的(比如使用hibernate).所以不能额外向里面增加字段.但是有时候前台就是需要这个字段.反之,前台要向后台传一些值,entity也没办法接受,前台Form表单不可能和数据库公用同一套对象的.
2.有时候完成1个业务需要N个entity.一个一个传给前台很麻烦.这个不是最关键的,最关键的是如果你的service第一次需求定下来的时候只需要1个entity对象做为参数,后来需求变更了,需要用到另外一个entity,这个时候如果修改service的方法签名,增加一个入参,那所有用到这个service的地方都需要修改.而且其他地方不一定有那个业务相关的entity.而方法参数为单独一个DTO的时候就没有这个问题,需要什么其他属性,直接在DTO增加成员域即可.
3.entity可能是由框架控制生命周期的,比如hibernate,如果他的某个属性是懒加载的,那万一不在session中调用的话会抛出异常,而DTO对象很干净,并不会有什么问题.你可以自己控制DTO,喜欢就存在缓存里,不喜欢也没关系.同时entity对象如果属性变化的话会有是否需要同步更新数据库的问题,而DTO与数据库并没有什么卵关系.
对象拷贝
所以DTO还是需要的.这样的话就需要entity与dto之间进行数据库的拷贝. 最简单的方法当然是自己调用setget方法..但是显然这样比较麻烦.....因为成员域多的话一个一个set起来很累....
有不少框架都支持对象间属性的拷贝.公司的方法是采用CGLIB的BeanCopier去copy对象,并且在外面稍微包装了一层.
1 @SuppressWarnings("rawtypes") 2 public final class ConverterUtil { 3 4 /** 5 * The Constant LOGGER. 6 */ 7 private static final Logger LOGGER = LoggerFactory.getLogger(ConverterUtil.class); 8 9 /** 10 * The Constant CACHED_COPIER_MAP. 11 */ 12 private static final Map<String, BeanCopier> CACHED_COPIER_MAP = new ConcurrentHashMap<String, BeanCopier>(); 13 14 /** 15 * The Constant CACHED_CUSTOM_CONVERTER_MAP. 16 */ 17 private static final Map<String, ObjectConverter> CACHED_CUSTOM_CONVERTER_MAP = new ConcurrentHashMap<String, ObjectConverter>(); 18 19 /** 20 * Instantiates a new converter util. 21 */ 22 private ConverterUtil() { 23 } 24 25 /** 26 * Convert list. 27 * 28 * @param <T> 29 * the generic type 30 * @param <F> 31 * the generic type 32 * @param source 33 * the source 34 * @param target 35 * the target 36 * @param sourceList 37 * the source list 38 * @return the list 39 */ 40 public static <T, F> List<F> convertList(Class<T> source, Class<F> target, List<T> sourceList) { 41 return convertList(source, target, sourceList, null, null); 42 } 43 44 /** 45 * Convert list. 46 * 47 * @param <T> 48 * the generic type 49 * @param <F> 50 * the generic type 51 * @param source 52 * the source 53 * @param target 54 * the target 55 * @param sourceList 56 * the source list 57 * @param converter 58 * the converter 59 * @param customConverterClass 60 * the custom converter class 61 * @return the list 62 */ 63 public static <T, F> List<F> convertList(Class<T> source, Class<F> target, List<T> sourceList, Converter converter,// NOSONAR 64 Class<? extends ObjectConverter> customConverterClass) { 65 if (CollectionUtils.isNotEmpty(sourceList)) { 66 @SuppressWarnings("unchecked") 67 List<F> targetList = new ArrayList(); 68 for (T t : sourceList) { 69 try { 70 F f = target.newInstance(); 71 targetList.add(convert(t, f, converter, customConverterClass)); 72 } catch (Exception e) { 73 LOGGER.error("When copy instance" + t, e); 74 } 75 } 76 return targetList; 77 } else { 78 return null;// NOSONAR 79 } 80 81 } 82 83 /** 84 * Convert. 85 * 86 * @param <T> 87 * the generic type 88 * @param <F> 89 * the generic type 90 * @param source 91 * the source 92 * @param target 93 * the target 94 * @return the f 95 */ 96 public static <T, F> F convert(T source, F target) { 97 return convert(source, target, null, null); 98 } 99 100 /** 101 * Convert. 102 * 103 * @param <T> 104 * the generic type 105 * @param <F> 106 * the generic type 107 * @param source 108 * the source 109 * @param target 110 * the target 111 * @param converter 112 * the converter 113 * @param customConverterClass 114 * the custom converter class 115 * @return the f 116 */ 117 public static <T, F> F convert(T source, F target, Converter converter, 118 Class<? extends ObjectConverter> customConverterClass) { 119 if (source == null || target == null) { 120 return null; // NOSONAR 121 } 122 copy(source, target, converter, customConverterClass); 123 return target; 124 } 125 126 /** 127 * Private methods. 128 * 129 * @param <T> 130 * the generic type 131 * @param <F> 132 * the generic type 133 * @param source 134 * the source 135 * @param target 136 * the target 137 * @param converter 138 * the converter 139 * @param customConverterClass 140 * the custom converter class 141 */ 142 143 @SuppressWarnings("unchecked") 144 private static <T, F> void copy(T source, F target, Converter converter, 145 Class<? extends ObjectConverter> customConverterClass) { 146 BeanCopier beanCopier = getBeanCopierInstance(source, target.getClass(), converter); 147 beanCopier.copy(source, target, converter); 148 ObjectConverter customConverter = getCustomConverterInstance(customConverterClass); 149 if (customConverter != null) { 150 if (target.getClass().getName().endsWith("CMP")) { 151 customConverter.convertFromDto(source, target); 152 } else if (target.getClass().getName().endsWith("DTO")) { 153 customConverter.convertToDto(source, target); 154 } 155 } 156 } 157 158 /** 159 * Gets the bean copier instance. 160 * 161 * @param <T> 162 * the generic type 163 * @param <F> 164 * the generic type 165 * @param source 166 * the source 167 * @param targetClass 168 * the target class 169 * @param converter 170 * the converter 171 * @return the bean copier instance 172 */ 173 private static <T, F> BeanCopier getBeanCopierInstance(T source, Class<F> targetClass, Converter converter) { 174 String key = source.getClass().getName() + "#" + targetClass.getName(); 175 BeanCopier beanCopier = CACHED_COPIER_MAP.get(key); 176 if (beanCopier == null) { 177 synchronized (CACHED_COPIER_MAP) { 178 beanCopier = CACHED_COPIER_MAP.get(key); 179 if (beanCopier == null) { 180 beanCopier = TypeAwareBeanCopier.instantiate(source.getClass(), targetClass, converter != null); 181 CACHED_COPIER_MAP.put(key, beanCopier); 182 } 183 } 184 } 185 return beanCopier; 186 } 187 188 /** 189 * Gets the custom converter instance. 190 * 191 * @param <T> 192 * the generic type 193 * @param <F> 194 * the generic type 195 * @param customConverterClass 196 * the custom converter class 197 * @return the custom converter instance 198 */ 199 private static <T, F> ObjectConverter getCustomConverterInstance( 200 Class<? extends ObjectConverter> customConverterClass) { 201 if (customConverterClass == null) { 202 return null;// NOSONAR 203 } 204 String key = customConverterClass.getName(); 205 ObjectConverter converter = CACHED_CUSTOM_CONVERTER_MAP.get(key); 206 if (converter == null) { 207 synchronized (CACHED_CUSTOM_CONVERTER_MAP) { 208 try { 209 converter = (ObjectConverter) SpringContextUtil.getBean(customConverterClass); 210 } catch (BeansException e) {// NOSONAR 211 LOGGER.info(customConverterClass.getName() + " is not a component, need new instance.");// NOSONAR 212 } 213 if (converter == null) { 214 try { 215 converter = customConverterClass.newInstance(); 216 CACHED_CUSTOM_CONVERTER_MAP.put(key, converter); 217 } catch (InstantiationException e) { 218 LOGGER.info(e.getMessage(), e); 219 return null; 220 } catch (IllegalAccessException e) { 221 LOGGER.info(e.getMessage(), e); 222 return null; 223 } 224 } 225 } 226 } 227 return converter; 228 } 229 230 }
完整的代码就这么多...
convert一个list,其实就是convert了N个普通对象.
convert 1个对象,其实就是new了一个相同类型的对象,然后copy成员域.
所以核心还是copy方法
1 private static <T, F> void copy(T source, F target, Converter converter, 2 Class<? extends ObjectConverter> customConverterClass) { 3 BeanCopier beanCopier = getBeanCopierInstance(source, target.getClass(), converter); 4 beanCopier.copy(source, target, converter); 5 ObjectConverter customConverter = getCustomConverterInstance(customConverterClass); 6 if (customConverter != null) { 7 if (target.getClass().getName().endsWith("CMP")) { 8 customConverter.convertFromDto(source, target); 9 } else if (target.getClass().getName().endsWith("DTO")) { 10 customConverter.convertToDto(source, target); 11 } 12 } 13 }
copy方法就2个步骤,第一个步骤是调用CGLIB的BeanCopier的copy方法去copy对象,然后再调用自己写的ObjectConverter接口的实现类去做额外的copy..ObjectConverter接口有2个方法,一个是convertFromDto,就是DTO -> Entity的拷贝,另外一个是convertToDto就是Entity -> Dto或者Dto -> Dto的拷贝.
小小的总结
公司的对象间拷贝方法还是比较简单的,同时也蛮好用的,毕竟大多数时候就是收集下前台表单的数据,然后Copy到要保存的entity中,或者数据库查出来Entity,拷贝到前台去展示.
一般String呀,int呀这些都能很方便的拷贝,不过如果成员是List呀,引用对象呀什么的....那就只能自己去实现ObjectConverter接口做额外copy 或者为每个引用对象再掉一次copy方法.
这点是算个小缺陷吧....不过大多数情况下这个简易的转化工具还是超级好用的.