Skip to content

Instantly share code, notes, and snippets.

@fuxingloh
Last active December 25, 2019 17:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fuxingloh/26b2fdd3d35cfb60c34739869e4fde71 to your computer and use it in GitHub Desktop.
Save fuxingloh/26b2fdd3d35cfb60c34739869e4fde71 to your computer and use it in GitHub Desktop.
How to patch entity in JPA Persistence. Json & RESTful API.

Patching JPA Entity

Motivation

When building an API Server, patching JPA Entity is actually quite tiresome. You can't just merge untrusted fields as you can't trust the request body. You can't easily manipulate the Entity without causing JPA to freak out because JPA is managing the object.

Solution

A Builder pattern with the ability to manually patch each acceptable fields. You start the patcher with EntityPatcher.with(entityManager, entity, body) and you chain the fields you want to patch. Only fields that are chained like this patcher.patch("name", Entity::setName) will be considered for patching. Fields that are present will only be patched, {"name": null} will set it to null, {} will not change the name, {"name": "ABC"} will replace the name to "ABC". When all the fields are registered in the builder, use patcher.persist() to persist the object.

JsonBody

Patching Entity with JsonNode from the jackson library.

public Entity patch(EntityManager entityManager, Entity entity, JsonNode body) {
    return EntityPatcher.with(entityManager, entity, body)
        .lock()
        .patch("status", EntityStatus.class, Entity::setStatus)
        .patch("text", Entity::setText)
        .persist();
}
import com.fasterxml.jackson.databind.JsonNode;
import dev.fuxing.utils.JsonUtils;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import java.util.function.Consumer;
/**
* Created by: Fuxing
*/
public final class EntityPatcher {
public static <T> JsonBody<T> with(EntityManager entityManager, T entity, JsonNode body) {
return new JsonBody<>(entityManager, entity, body);
}
public static class JsonBody<T> extends Body<T> {
private final JsonNode json;
JsonBody(EntityManager entityManager, T entity, JsonNode json) {
super(entityManager, entity);
this.json = json;
}
public JsonBody<T> lock() {
return lock(LockModeType.WRITE);
}
public JsonBody<T> lock(LockModeType type) {
entityManager.lock(entity, type);
return this;
}
public <E> JsonBody<T> patch(String name, Class<E> enumClass, EnumConsumer<T, E> consumer) {
return patch(name, (NodeConsumer<T>) (content, node) -> {
// Because my Enum are created with @JsonCreator, I need to use the ObjectMapper to convert.
E type = JsonUtils.toObject(node, enumClass);
consumer.accept(content, type);
});
}
public JsonBody<T> patch(String name, StringConsumer<T> consumer) {
return patch(name, (NodeConsumer<T>) (content, node) -> {
consumer.accept(content, node.asText());
});
}
public JsonBody<T> patch(String name, LongConsumer<T> consumer) {
return patch(name, (NodeConsumer<T>) (content, node) -> {
consumer.accept(content, node.asLong());
});
}
public JsonBody<T> patch(String name, IntegerConsumer<T> consumer) {
return patch(name, (NodeConsumer<T>) (content, node) -> {
consumer.accept(content, node.asInt());
});
}
public JsonBody<T> patch(String name, NodeConsumer<T> consumer) {
return patch(name, (json) -> {
consumer.accept(entity, json);
});
}
public JsonBody<T> patch(String name, Consumer<JsonNode> consumer) {
if (json.has(name)) {
consumer.accept(json.path(name));
}
return this;
}
public interface NodeConsumer<T> {
void accept(T t, JsonNode json);
}
public interface StringConsumer<T> {
void accept(T t, String string);
}
public interface LongConsumer<T> {
void accept(T t, Long aLong);
}
public interface IntegerConsumer<T> {
void accept(T t, Integer integer);
}
public interface EnumConsumer<T, E> {
void accept(T t, E aEnum);
}
}
public static abstract class Body<T> {
protected final EntityManager entityManager;
protected final T entity;
protected Body(EntityManager entityManager, T entity) {
this.entityManager = entityManager;
this.entity = entity;
}
public T persist() {
entityManager.persist(entity);
return entity;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment