工作中用到的数据库是 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);
}