Mapping JSON object to Hibernate entity

2019-04-26 16:04发布

I'm going to start a project of a REST application managed with Spring and with Hibernate for my model.

I know that Spring allows you to get Java object from the HTTP Request (with @Consumes(JSON) annotation). Is there any conflict if this Java object is also a Hibernate entities? And is nested object working (like @ManyToOne relation)?

5条回答
Luminary・发光体
2楼-- · 2019-04-26 16:25

Yes, this wouldn't be a problem and is actually a fairly common practice.

In the recent years I have come to realize that sometimes, however, it is not a good idea to always build your views based on your domain directly. You can take a look at this post:

http://codebetter.com/jpboodhoo/2007/09/27/screen-bound-dto-s/

It is also known as "Presentation Model":

http://martinfowler.com/eaaDev/PresentationModel.html

The idea behind that is basically the following:

Imagine you have the domain entry User, who looks like that :

@Entity
@Data
public class User {
     @Id private UUID userId;
     private String username;
     @OneToMany private List<Permission> permissions;
}

Let's now imagine you have a view where you wanna display that user's name, and you totally don't care about the permissions. If you use your approach of immediately returning the User to the view, Hibernate will make an additional join from the Permissions table because event though the permissions are lazily loaded by default, there is no easy way to signal to the jackson serializer or whatever you are using, that you don't care about them in this particular occasion, so jackson will try to unproxy them (if your transaction is still alive by the time your object is put for json serialization, otherwise you get a nasty exception). Yes, you can add a @JsonIgnore annotation on the permissions field, but then if you need it in some other view, you are screwed.

That a very basic example, but you should get the idea that sometimes your domain model can't be immediately used to be returned to the presentation layer, due to both code maintainability and performance issues.

查看更多
The star\"
3楼-- · 2019-04-26 16:26

You can map the json request without using any library at REST web-services (Jersy)
this sample of code:

This hibernate entity called book:

   @Entity
@Table(name = "book", schema = "cashcall")
public class Book implements java.io.Serializable {

   private int id;
   private Author author; // another hibernate entity 
   private String bookName;

//setter and getters
}

This web-services function

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public String addBook(Book book) {


    String bookName=book.getName();

    return bookName;
}

This is sample json request:

{
            "bookName" : "Head First Java" 
            ,
            "author" : {
            "id" : 1
            }

}

查看更多
何必那么认真
4楼-- · 2019-04-26 16:42

We were using such approach to simplify design and get rid of many dtos (we were abusing them too much). Basically, it worked for us.

However, in our REST model we were trying to do not expose other relations for an object as you can always create another REST resources to access them.

So we just put @JsonIgnore annotations to relations mappings like @OneToMany or @ManyToOnemaking them transient.

Another problem I see that if you still like to return these relations you would have to use Join.FETCH strategy for them or move transaction management higher so that transaction still exists when a response is serialized to JSON (Open Session In View Pattern). On my opinion these two solutions are not so good.

查看更多
祖国的老花朵
5楼-- · 2019-04-26 16:42

As I explained in this article, it's very easy to persist JSON object using Hibernate.

You don’t have to create all these types manually, you can simply get them via Maven Central using the following dependency:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version> 
</dependency> 

For more info, check out the hibernate-types open-source project.

I wrote an article about how you can map JSON objects on both PostgreSQL and MySQL.

For PostgreSQL, you need to send the JSON object in a binary form:

public class JsonBinaryType
    extends AbstractSingleColumnStandardBasicType<Object> 
    implements DynamicParameterizedType {

    public JsonBinaryType() {
        super( 
            JsonBinarySqlTypeDescriptor.INSTANCE, 
            new JsonTypeDescriptor()
        );
    }

    public String getName() {
        return "jsonb";
    }

    @Override
    public void setParameterValues(Properties parameters) {
        ((JsonTypeDescriptor) getJavaTypeDescriptor())
            .setParameterValues(parameters);
    }

}

The JsonBinarySqlTypeDescriptor looks like this:

public class JsonBinarySqlTypeDescriptor
    extends AbstractJsonSqlTypeDescriptor {

    public static final JsonBinarySqlTypeDescriptor INSTANCE = 
        new JsonBinarySqlTypeDescriptor();

    @Override
    public <X> ValueBinder<X> getBinder(
        final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicBinder<X>(javaTypeDescriptor, this) {
            @Override
            protected void doBind(
                PreparedStatement st, 
                X value, 
                int index, 
                WrapperOptions options) throws SQLException {
                st.setObject(index, 
                    javaTypeDescriptor.unwrap(
                        value, JsonNode.class, options), getSqlType()
                );
            }

            @Override
            protected void doBind(
                CallableStatement st, 
                X value, 
                String name, 
                WrapperOptions options)
                    throws SQLException {
                st.setObject(name, 
                    javaTypeDescriptor.unwrap(
                        value, JsonNode.class, options), getSqlType()
                );
            }
        };
    }
}

and the JsonTypeDescriptor like this:

public class JsonTypeDescriptor
        extends AbstractTypeDescriptor<Object> 
        implements DynamicParameterizedType {

    private Class<?> jsonObjectClass;

    @Override
    public void setParameterValues(Properties parameters) {
        jsonObjectClass = ( (ParameterType) parameters.get( PARAMETER_TYPE ) )
            .getReturnedClass();

    }

    public JsonTypeDescriptor() {
        super( Object.class, new MutableMutabilityPlan<Object>() {
            @Override
            protected Object deepCopyNotNull(Object value) {
                return JacksonUtil.clone(value);
            }
        });
    }

    @Override
    public boolean areEqual(Object one, Object another) {
        if ( one == another ) {
            return true;
        }
        if ( one == null || another == null ) {
            return false;
        }
        return JacksonUtil.toJsonNode(JacksonUtil.toString(one)).equals(
                JacksonUtil.toJsonNode(JacksonUtil.toString(another)));
    }

    @Override
    public String toString(Object value) {
        return JacksonUtil.toString(value);
    }

    @Override
    public Object fromString(String string) {
        return JacksonUtil.fromString(string, jsonObjectClass);
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        if ( String.class.isAssignableFrom( type ) ) {
            return (X) toString(value);
        }
        if ( Object.class.isAssignableFrom( type ) ) {
            return (X) JacksonUtil.toJsonNode(toString(value));
        }
        throw unknownUnwrap( type );
    }

    @Override
    public <X> Object wrap(X value, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        return fromString(value.toString());
    }

}

Now, you need to declare the new type on either class level or in a package-info.java package-level descriptior:

@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)

And the entity mapping will look like this:

@Type(type = "jsonb")
@Column(columnDefinition = "json")
private Location location;

That's it!

查看更多
姐就是有狂的资本
6楼-- · 2019-04-26 16:42

Since you are just starting, perhaps you could use Spring Data REST?

This is the project: http://projects.spring.io/spring-data-rest/

And here are some simple examples:

As you can see in the examples, there are no extra DTOs beyond the @Entity annotated POJOs.

查看更多
登录 后发表回答