工作中用到的数据库是 MongoDB,持久层采用的是 Spring Data MongoDB,在需要使用 MongoTemplate 书写特定逻辑时,常常只能将字段名用字符串写死,在遇到字段名发生变更时,会比较麻烦。参考 MyBatis-Plus,可以不把字段写死。

1. 介绍

参考 MyBatis-Plus 的写法,示例:

userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getUsername, username));

当字段名发生变更时,只需要借助 IDE 工具,即可一瞬间修改完,同时也避免了可能将字段写错的问题。

2. 实现

Func.java

import java.io.Serializable;  
import java.util.function.Function;  
  
@FunctionalInterface  
public interface Func<T, R> extends Function<T, R>, Serializable {}

La.java

import java.lang.invoke.SerializedLambda;  
import java.lang.reflect.Field;  
import java.lang.reflect.Method;  
import java.util.Locale;  
import java.util.Map;  
import java.util.concurrent.ConcurrentHashMap;  
import org.springframework.util.ClassUtils;  
import org.springframework.util.ReflectionUtils;  
  
public final class La {  
  
  private static final Map<String, String> CACHE_MAP = new ConcurrentHashMap<>();  
  
  private La() {}  
  
  /**  
   * 获取字段名  
   *  
   * @param func 字段  
   * @return 字段名  
   */  
  public static <T> String toC(Func<T, ?> func) {  
    String column = CACHE_MAP.get(func.getClass().getName());  
    if (column != null) {  
      return column;  
    }  
  
    return CACHE_MAP.computeIfAbsent(  
        func.getClass().getName(),  
        key -> {  
          Method method = ReflectionUtils.findMethod(func.getClass(), "writeReplace");  
          method.setAccessible(true);  
          SerializedLambda serializedLambda =  
              (SerializedLambda) ReflectionUtils.invokeMethod(method, func);  
          String implMethodName = serializedLambda.getImplMethodName();  
          String property = methodToProperty(implMethodName);  
          Field field =  
              ReflectionUtils.findField(nameToClass(serializedLambda.getImplClass()), property);  
          org.springframework.data.mongodb.core.mapping.Field annotation =  
              field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);  
          if (annotation != null) {  
            return annotation.value();  
          }  
          return property;  
        });  
  }  
  
  /**  
   * 获取字段名  
   *  
   * @param func1 字段1  
   * @param func2 字段2  
   * @return 字段名  
   */  
  public static <T, K> String toC(Func<T, ?> func1, Func<K, ?> func2) {  
    return toC(func1) + "." + toC(func2);  
  }  
  
  /**  
   * 获取字段名  
   *  
   * @param name 字段名  
   * @return 字段名  
   */  
  private static String methodToProperty(String name) {  
    if (name.startsWith("is")) {  
      name = name.substring(2);  
    } else if (name.startsWith("get") || name.startsWith("set")) {  
      name = name.substring(3);  
    } else {  
      throw new RuntimeException(  
          "Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");  
    }  
  
    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {  
      name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);  
    }  
  
    return name;  
  }  
  
  /**  
   * 将 implClass 转换为 Class  
   *   * @param name implClass  
   * @return Class   */  private static Class<?> nameToClass(String name) {  
    try {  
      String className = name.replace("/", ".");  
      return ClassUtils.forName(className, null);  
    } catch (ClassNotFoundException e) {  
      throw new RuntimeException("Error parsing class name '" + name + "'.", e);  
    }  
  }  
}

参考了 MyBatis-Plus 的实现代码,复制了一部分逻辑并针对 MongoDB 的注解做了处理,这样就初步实现了字段不写死的目的,示例代码:

  private final MongoTemplate mongoTemplate;

  @Override
  public void delUser(String uid) {
    Query query = new Query();
    query.addCriteria(Criteria.where(La.toC(User::getUid)).is(uid).and(DEL_FLAG).is(false));
    Update update = new Update();
    update.set(La.toC(User::getStatus), 99);
    update.set(La.toC(DEL_FLAG), true);
    update.set(La.toC(User::getUpdateTime), LocalDateTime.now());
    mongoTemplate.updateFirst(query, update, User.class);
  }

MongoDB 可以嵌套对象,平常使用中,只会嵌套一层,工具类也能支持,示例代码:

  private final MongoTemplate mongoTemplate;

  @Override
  public void delUserWallet(String uid) {
    Query query = new Query();
    query.addCriteria(Criteria.where("user.uid").is(uid).and(DEL_FLAG).is(false));
    Update update = new Update();
    update.set(La.toC("status"), 99);
    update.set(La.toC(DEL_FLAG), true);
    update.set(La.toC("updateTime"), LocalDateTime.now());
    mongoTemplate.updateFirst(query, update, Wallet.class);
  }

  @Override
  public void delUserWalletLa(String uid) {
    Query query = new Query();
    query.addCriteria(Criteria.where(La.toC(Wallet::getUser, User::getUid)).is(uid).and(DEL_FLAG).is(false));
    Update update = new Update();
    update.set(La.toC(Wallet::getStatus), 99);
    update.set(La.toC(DEL_FLAG), true);
    update.set(La.toC(Wallet::getUpdateTime), LocalDateTime.now());
    mongoTemplate.updateFirst(query, update, Wallet.class);
  }

参考资料

最后修改:2023 年 01 月 18 日
如果觉得我的文章对你有用,请随意赞赏