业务代码中先处理业务最后存储数据
背景说明:
在处理复杂业务的时候,特别是研发自测期间,经常会产生很多不必要的垃圾数据。
技术原理:
先将要存入数据库的数据放在缓存中,等所有业务代码执行完后,再统一保存;
代码如下:
@Slf4j public class BaseService<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements IService<T> { /** * 构建查询 * @param e * @return */ @SneakyThrows public <E extends Object> EntityWrapper<E> wrapperTable(E e, String tableName, Boolean like) { EntityWrapper<E> wrapper = new EntityWrapper<>(); for (String field: JSON.parseObject(JSON.toJSONString(e)).keySet()) { Field ld = findField(e.getClass(), field); if(ld == null || !checkField(ld)) continue; String _s = columnName(ld); String m =null; if(_s.contains("_")){ m = StrUtil.toCamelCase("get_"+_s); }else {m=StrUtil.genGetter(_s);} Object o = e.getClass().getMethod(m).invoke(e); if(ObjectUtils.isEmpty(o)) continue; if(!ObjectUtils.isEmpty(tableName)) { _s = tableName+"."+_s; } if(like) wrapper.like(_s,o.toString()); else wrapper.eq(_s , o); } return wrapper; } /** * 构建查询 * @param e * @return */ @SneakyThrows public <E extends Object> EntityWrapper<E> wrapper(E e, Boolean like) { return wrapperTable(e,null,like); } /** * 构建查询 * @param e * @return */ @SneakyThrows public <E extends Object> EntityWrapper<E> wrapper(E e) { return wrapperTable(e,null,false); } private boolean checkField(Field field) { if(!field.isAnnotationPresent(TableId.class) && !field.isAnnotationPresent(TableField.class)) return false; if(field.isAnnotationPresent(TableField.class) && !field.getAnnotation(TableField.class).exist()) return false; return true; } private Field findField(Class c,String field) { if(c == Object.class) return null; try { return c.getDeclaredField(field); } catch (NoSuchFieldException e) { return findField(c.getSuperclass(),field); } } private String columnName(Field field) { if(field.isAnnotationPresent(TableId.class) && ObjectUtils.isNotEmpty(field.getAnnotation(TableId.class).value())) return field.getAnnotation(TableId.class).value(); if(field.isAnnotationPresent(TableField.class) && field.getAnnotation(TableField.class).exist() && ObjectUtils.isNotEmpty(field.getAnnotation(TableField.class).value())) return field.getAnnotation(TableField.class).value(); return StrUtil.toUnderlineCase(field.getName()); } @Transactional(rollbackFor = Exception.class) @Override public boolean insertOrUpdate(T entity){ if(entity instanceof TransacationalEntity) { //截流所有 TransacationalEntity 下的新增 if(((TransacationalEntity) entity).getSqlStatus().equalsIgnoreCase("begin")) { return TransactionalUtils.add(this,((TransacationalEntity) entity)); } else if(((TransacationalEntity) entity).getSqlStatus().equalsIgnoreCase("commit")) { return TransactionalUtils.commit(((TransacationalEntity) entity).getSqlVersion()); } else { return super.insertOrUpdate(entity); } } else { return super.insertOrUpdate(entity); } } @SneakyThrows @Transactional(rollbackFor = Exception.class) @Override public boolean insertOrUpdateBatch(List<T> entityList) { try { this.baseMapper.getClass().getMethod("insertOrUpdateBatch", List.class).invoke(this.baseMapper,entityList); } catch (NoSuchMethodException e) { log.warn("mapper类{},不存在方法{},替换为service执行",this.baseMapper.getClass(),"insertOrUpdateBatch"); super.insertOrUpdateBatch(entityList); } return true; } }
其他方法可以忽略,都是懒得写xml但是版本又不允许升级,偷懒写的sql条件辅助工具。
主要要关注以下两个方法:
第一个,用于截流原来的保存方法,将要保存的数据存入缓存中。
@Transactional(rollbackFor = Exception.class) @Override public boolean insertOrUpdate(T entity){ if(entity instanceof TransacationalEntity) { //截流所有 TransacationalEntity 下的新增 if(((TransacationalEntity) entity).getSqlStatus().equalsIgnoreCase("begin")) { return TransactionalUtils.add(this,((TransacationalEntity) entity)); } else if(((TransacationalEntity) entity).getSqlStatus().equalsIgnoreCase("commit")) { return TransactionalUtils.commit(((TransacationalEntity) entity).getSqlVersion()); } else { return super.insertOrUpdate(entity); } } else { return super.insertOrUpdate(entity); } }
第二个,用于最后统一处理保存,我这里偷懒了,使用了代码生成器,所以xml中已经有了 insertOrupdataBatch.
@SneakyThrows @Transactional(rollbackFor = Exception.class) @Override public boolean insertOrUpdateBatch(List<T> entityList) { try { this.baseMapper.getClass().getMethod("insertOrUpdateBatch", List.class).invoke(this.baseMapper,entityList); } catch (NoSuchMethodException e) { log.warn("mapper类{},不存在方法{},替换为service执行",this.baseMapper.getClass(),"insertOrUpdateBatch"); super.insertOrUpdateBatch(entityList); } return true;
@Data @AllArgsConstructor @NoArgsConstructor public class TransacationalEntity { /** * begin null commit */ @TableField(exist = false) @ApiModelProperty(hidden = true) private String sqlStatus; /** * begin null commit */ @TableField(exist = false) @ApiModelProperty(hidden = true) private String sqlVersion; }
public class TransactionalUtils { static volatile ConcurrentHashMap<String, ConcurrentHashMap<BaseService, CopyOnWriteArrayList<Object>>> cMap = new ConcurrentHashMap<>(); static volatile ConcurrentHashMap<String,Long> tMap = new ConcurrentHashMap<>(); public static <E extends TransacationalEntity> boolean add(BaseService service, E e) { long millis = System.currentTimeMillis(); synchronized(service.getClass()) { ConcurrentHashMap<BaseService, CopyOnWriteArrayList<Object>> map = cMap .getOrDefault(e.getSqlVersion(), new ConcurrentHashMap<>()); CopyOnWriteArrayList<Object> list = map.getOrDefault(service, new CopyOnWriteArrayList<>()); list.add(e); map.put(service, list); cMap.put(e.getSqlVersion(), map); tMap.put(e.getSqlVersion(),tMap.getOrDefault(e.getSqlVersion(), 0L)+System.currentTimeMillis()-millis) ; } return true; } @SneakyThrows public static boolean commit(String key) { long millis = System.currentTimeMillis(); int size = 0; for (Map.Entry<BaseService, CopyOnWriteArrayList<Object>> entry : cMap.remove(key).entrySet()) { entry.getKey().insertOrUpdateBatch(entry.getValue()); size+=entry.getValue().size(); } System.out.println("批量业务入队总量" + size); System.out.println("批量业务入队耗时" + tMap.remove(key)); System.out.println("批量业务提交耗时" + (System.currentTimeMillis()-millis)); return true; } }
以下是调用示例:
String tranId = IdUtil.simpleUUID(); Map<String, Object> userMap = new HashMap(); userMap.put("sqlStatus","begin"); userMap.put("sqlVersion", tranId); propretyDye(beans,userMap); insertOrUpdate(Arrays.asList(beans)); TransactionalUtils.commit(tranId);
先给要截流的数据加上版本号和标识,其实只要一个版本号就可以了,sqlStatus这个字段懒得删除了。
版本号主要用来在缓存中分组,隔离其他业务提交。
附带一个参数处理的小工具,有时候要重复的一层层赋值,太懒了,所以写个工具,让机器自己给我处理所有数据的值,用法如上面的示例,没仔细检查,偶尔会发生栈溢出,但是不影响,应该是项目的全局配置不够造成的:
/** * 属性染色 * 如果对象 o 包含 propreties 的key,则设置key对应的值 * 处理对象和对象中所有的子对象 * @param o * @param propreties * @return */ @SneakyThrows public static Object propretyDye(Object o, Map<String,Object> propreties) { if(o instanceof Collection) for (Object object : (Collection<?>) o) { propretyDye(object,propreties); } Class<?> aClass = o.getClass(); while (aClass instanceof Object) { for (Field field : aClass.getDeclaredFields()) { field.setAccessible(true); if(propreties.containsKey(field.getName())) field.set(o,propreties.get(field.getName())); if(field.get(o) instanceof Collection) for (Object object : (Collection<?>) field.get(o)) { propretyDye(object,propreties); } if(field.get(o) instanceof Object && BeanUtil.isBean(field.get(o).getClass())) propretyDye(field.get(o),propreties); } aClass = aClass.getSuperclass(); } return o; }