Spring mongo 只写字段
Spring mongo write only field
我有一个描述 mongo 文档的摘要 class。这个 class 可能有不同的实现需要覆盖抽象方法。这是一个简化的例子:
@Document
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public abstract class Entity {
@Id
private ObjectId id;
abstract String getSomething();
}
我希望 getSomething()
作为字符串字段写入文档。但是我不想读回去。
我尝试使用 @AccessType
注释:
@AccessType(AccessType.Type.PROPERTY)
abstract String getSomething();
但是当我从数据库中读取此文档时,spring 抛出 UnsupportedOperationException: No accessor to set property
。它试图为这个字段找到一个 setter,但我不想为它定义一个 setter - 该方法可能 return 一个计算值并且应该没有能力更改。尽管空 setter 可能有所帮助,但它看起来更像是一种解决方法,我会尽量避免它。
所以我想知道是否有办法在从数据库读取时跳过这个特定的 属性?与 @Transient
注释相反的东西。或者类似于 Jackson 库中的 @JsonIgnore
。
您可以尝试添加 @Transient
属性 以便在映射字段时忽略它。
参考this问题。
另一方面,如果 属性 只是在阅读时被忽略,那么您可以指定一个空的 setter
方法。这将避免 exception of no accessor method
.
或者创建自定义注解,将所有需要空 setter 方法的属性放在单独的 class 中,并使用新注解对 class 进行注解。这个 class 然后可以在需要的地方扩展。您可以查看 lombok 项目,了解他们如何为 getter 和 setter 方法创建注释。
您遇到的问题是方法名称中包含单词 "get"。
通过 "get" 这个词,Spring 认为有一个名为 "something" 的属性,并以此检查它是否具有相应的 getter 和 setter方法。
将您的方法从 "getSomething" 重命名为 "calculatedInformation"。
令人惊讶的是,没有简单的解决方案可以使字段只写。
虽然创建一个空 setter 可以解决问题,但我觉得它破坏了 least surprise principle:如果你有一个 setter,你希望它设置一个值。
所以我决定创建自己的 @WriteOnly
注释并用它来忽略我不想从数据库中读取的字段:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface WriteOnly {
}
要使用它,您需要扩展 AbstractMongoEventListener:
@Service
public class WriteOnlyListener extends AbstractMongoEventListener<Entity> {
@Override
public void onAfterLoad(AfterLoadEvent<Entity> event) {
Document doc = event.getDocument();
if (doc == null) return;
for (Field field: getWriteOnly(event.getType(), Class::getDeclaredFields)) {
doc.remove(field.getName());
}
for (Method method: getWriteOnly(event.getType(), Class::getDeclaredMethods)) {
doc.remove(getFieldName(method.getName()));
}
}
private <T extends AccessibleObject> List<T> getWriteOnly(Class<?> type,
Function<Class<?>, T[]> extractor) {
List<T> list = new ArrayList<>();
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
list.addAll(Arrays.stream(extractor.apply(c))
.filter(o -> o.isAnnotationPresent(WriteOnly.class))
.collect(Collectors.toList()));
}
return list;
}
private static String getFieldName(String methodName) {
return Introspector.decapitalize(methodName.substring(methodName.startsWith("is") ? 2 :
methodName.startsWith("get") ? 3 : 0));
}
}
我有一个描述 mongo 文档的摘要 class。这个 class 可能有不同的实现需要覆盖抽象方法。这是一个简化的例子:
@Document
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public abstract class Entity {
@Id
private ObjectId id;
abstract String getSomething();
}
我希望 getSomething()
作为字符串字段写入文档。但是我不想读回去。
我尝试使用 @AccessType
注释:
@AccessType(AccessType.Type.PROPERTY)
abstract String getSomething();
但是当我从数据库中读取此文档时,spring 抛出 UnsupportedOperationException: No accessor to set property
。它试图为这个字段找到一个 setter,但我不想为它定义一个 setter - 该方法可能 return 一个计算值并且应该没有能力更改。尽管空 setter 可能有所帮助,但它看起来更像是一种解决方法,我会尽量避免它。
所以我想知道是否有办法在从数据库读取时跳过这个特定的 属性?与 @Transient
注释相反的东西。或者类似于 Jackson 库中的 @JsonIgnore
。
您可以尝试添加 @Transient
属性 以便在映射字段时忽略它。
参考this问题。
另一方面,如果 属性 只是在阅读时被忽略,那么您可以指定一个空的 setter
方法。这将避免 exception of no accessor method
.
或者创建自定义注解,将所有需要空 setter 方法的属性放在单独的 class 中,并使用新注解对 class 进行注解。这个 class 然后可以在需要的地方扩展。您可以查看 lombok 项目,了解他们如何为 getter 和 setter 方法创建注释。
您遇到的问题是方法名称中包含单词 "get"。
通过 "get" 这个词,Spring 认为有一个名为 "something" 的属性,并以此检查它是否具有相应的 getter 和 setter方法。
将您的方法从 "getSomething" 重命名为 "calculatedInformation"。
令人惊讶的是,没有简单的解决方案可以使字段只写。
虽然创建一个空 setter 可以解决问题,但我觉得它破坏了 least surprise principle:如果你有一个 setter,你希望它设置一个值。
所以我决定创建自己的 @WriteOnly
注释并用它来忽略我不想从数据库中读取的字段:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface WriteOnly {
}
要使用它,您需要扩展 AbstractMongoEventListener:
@Service
public class WriteOnlyListener extends AbstractMongoEventListener<Entity> {
@Override
public void onAfterLoad(AfterLoadEvent<Entity> event) {
Document doc = event.getDocument();
if (doc == null) return;
for (Field field: getWriteOnly(event.getType(), Class::getDeclaredFields)) {
doc.remove(field.getName());
}
for (Method method: getWriteOnly(event.getType(), Class::getDeclaredMethods)) {
doc.remove(getFieldName(method.getName()));
}
}
private <T extends AccessibleObject> List<T> getWriteOnly(Class<?> type,
Function<Class<?>, T[]> extractor) {
List<T> list = new ArrayList<>();
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
list.addAll(Arrays.stream(extractor.apply(c))
.filter(o -> o.isAnnotationPresent(WriteOnly.class))
.collect(Collectors.toList()));
}
return list;
}
private static String getFieldName(String methodName) {
return Introspector.decapitalize(methodName.substring(methodName.startsWith("is") ? 2 :
methodName.startsWith("get") ? 3 : 0));
}
}