Spring mongo write only field

Kirill Simonov :

I have an abstract class describing a mongo document. There could be different implementations of this class that need to override an abstract method. Here is a simplified example:

@Document
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public abstract class Entity {

    @Id
    private ObjectId id;

    abstract String getSomething();
}

I want getSomething() to be written to the document as a string field. But I don't want to read it back.

I've tried to use the @AccessType annotation:

@AccessType(AccessType.Type.PROPERTY)
abstract String getSomething();

But when I'm reading this document from the db, spring throws UnsupportedOperationException: No accessor to set property. It is trying to find a setter for this field, but I don't want to define a setter for it - the method may return a calculated value and there should be no ability to change it. Although an empty setter could help, it looks more like a workaround, and I would try to avoid it.

So I'm wondering if there is a way to skip this particular property when reading from the db? Something opposite to the @Transient annotation. Or similar to @JsonIgnore in the Jackson library.

Kirill Simonov :

Surprisingly, there is no easy solution to make a field write only.

Although creating an empty setter would solve the problem, I feel that it breaks the least surprise principle: if you have a setter, you expect it to set a value.

So I decided to create my own @WriteOnly annotation and use it to ignore fields I don't want to read from the db:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface WriteOnly {
}

To use it you will need to extend an 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));
    }

}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=323703&siteId=1