使用GSON和Hibernate存储任意数据 [英] Storing arbitrary data with GSON and Hibernate

查看:113
本文介绍了使用GSON和Hibernate存储任意数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我只想保留一些与客户端相关的数据。我在这里故意忽略数据库规范化,因为数据在服务器端非常没用。



我可以通过使客户端将其转换为JSON并在请求中包含JSON发送中的字符串。这感觉很不对。



我想要的是什么:



鉴于

  class MyEntity {
String someString;
int someInt;
@Lob字符串clientData;

$ / code $ / pre

和一个输入

< pre $ {
someString:答案,
someInt:43,
clientData:{
x:[1,1,2, 3,5,8,13],$ b $由:[1,1,2,6,24,120],
tonsOfComplicatedStuff:{stuff:stuff}
}
}

clientData 打包为JSON柱。请注意,我不想为 MyEntity 编写适配器,因为列数很多。我需要一个适用于单列的适配器。列类型不需要是字符串( Serializable 或其他任何东西都可以,因为服务器真的不在意)。

@JsonAdapter 注释,允许指定一个JSON(de)序列化器,类型适配器,甚至是一个类型适配器工厂。注释看起来很适合注释 MyEntity 中的 clientData 字段:

final class MyEntity {

String someString;

int someInt;

@Lob
@JsonAdapter(PackedJsonTypeAdapterFactory.class)
字符串clientData;


类型适配器工厂可能如下所示:

final class PackedJsonTypeAdapterFactory
implements TypeAdapterFactory {

// Gson can实例化它本身
private PackedJsonTypeAdapterFactory(){
}

@Override
public< T> TypeAdapter< T>创建(final Gson gson,final TypeToken< T> typeToken){
@SuppressWarnings(unchecked)
final TypeAdapter< T> typeAdapter =(TypeAdapter< T>)new PackedJsonTypeAdapter(gson);
返回typeAdapter;
}

private static final class PackedJsonTypeAdapter
extends TypeAdapter< String> {

private final Gson gson;

私人PackedJsonTypeAdapter(最终Gson gson){
this.gson = gson;

$ b @Override
public void write(final JsonWriter out,final String json){
final JsonElement jsonElement = gson.fromJson(json,JsonElement.class) ;
gson.toJson(jsonElement,out);
}

@Override
public String read(final JsonReader in){
final JsonElement jsonElement = gson.fromJson(in,JsonElement.class);
返回jsonElement!= null? jsonElement.toString():null;
}

}

}



<请注意,此转换器策略是作为类型适配器工厂实现的,因为这是访问我已知的 Gson 实例的唯一方式,而 JsonSerializer / JsonDeserializer 似乎不能通过序列化上下文进行良好的解析。这里的另一个缺陷是这个实现是基于树的,需要将JSON树完全存储在内存中。理论上,可能有一个很好的面向流的实现,如 gson.fromJson(jsonReader) - > JsonReader JsonReader - > Reader 装饰器被重定向到<$ c $例如c> StringWriter ,但我找不到任何替代品。

  public static void main(final String ... args){
final Gson gson = new Gson();

out.println(deserialization:);
final String incomingJson ={someString:\答案\,someInt:43,clientData:{x:[1,1,2,3,5,8,13],y:[1 ,1,2,6,24,120],tonsOfComplicatedStuff:{东西:东西}}};
final MyEntity myEntity = gson.fromJson(incomingJson,MyEntity.class);
out.println(\t+ myEntity.someString);
out.println(\ t+ myEntity.someInt);
out.println(\ t+ myEntity.clientData);

out.println(serialization:);
final String outgoingJson = gson.toJson(myEntity);
out.println(\ t+ outgoingJson);

out.println(equal check:);
out.println(\ t+ areEqual(gson,incomingJson,outgoingJson));

$ b private static boolean areEqual(final Gson gson,final String incomingJson,final String outgoingJson){
final JsonElement incoming = gson.fromJson(incomingJson,JsonElement.class);
final JsonElement outgoing = gson.fromJson(outgoingJson,JsonElement.class);
返回incoming.equals(传出);





输出:

 反序列化:
答案
43
{x:[1,1,2,3,5,8,13], y:[1,1,2,6,24,120],tonsOfComplicatedStuff:{stuff:stuff}}
序列化:
{someString:答案, someInt :43, clientData :{ × :[1,1,2,3,5,8,13], Y :[1,1,2,6,24,120], tonsOfComplicatedStuff: {stuff:stuff}}}
相等检查:
true



<不过,不知道它是否可以很好地与Hibernate一起玩。






编辑



尽管JSON包装的字符串被收集到内存中,但由于各种原因,流式传输可能更便宜并且可以节省一些内存。流媒体的另一个优点是,这样一个JSON包装类型的适配器不再需要类型适配器工厂,因此 Gson
实例保留了JSON流,但仍然有一些规范化如 {stuff:stuff} - > {stuff:stuff} 。例如:
$ b

@JsonAdapter(PackedJsonStreamTypeAdapter.class)
String clientData;



  Final class PackedJsonStreamTypeAdapter 
扩展了TypeAdapter< String> {

private PackedJsonStreamTypeAdapter(){
}

@Override
public void write(final JsonWriter out,final String json)
throws IOException {
@SuppressWarnings(resource)
final Reader reader = new StringReader(json);
writeNormalizedJsonStream(new JsonReader(reader),out);

$ b @Override
public String read(final JsonReader in)
throws IOException {
@SuppressWarnings(resource)
final Writer writer = new StringWriter();
writeNormalizedJsonStream(in,new JsonWriter(writer));
返回writer.toString();
}

}



  final类JsonStreams {

private JsonStreams(){
}

static void writeNormalizedJsonStream(final JsonReader reader,final JsonWriter writer )
抛出IOException {
writeNormalizedJsonStream(reader,writer,true);
}

@SuppressWarnings(resource)
static void writeNormalizedJsonStream(final JsonReader reader,final JsonWriter writer,final boolean isLenient)
throws IOException {
int level = 0;
for(JsonToken token = reader.peek(); token!= null; token = reader.peek()){
switch(令牌){
case BEGIN_ARRAY:
reader .beginArray();
writer.beginArray();
++级别;
休息;
case END_ARRAY:
reader.endArray();
writer.endArray();
if(--level == 0&& isLenient){
return;
}
break;
case BEGIN_OBJECT:
reader.beginObject();
writer.beginObject();
++级别;
休息;
case END_OBJECT:
reader.endObject();
writer.endObject();
if(--level == 0&& isLenient){
return;
}
break;
case NAME:
final String name = reader.nextName();
writer.name(name);
休息;
case STRING:
final String s = reader.nextString();
writer.value(s);
休息;
case NUMBER:
final String rawN = reader.nextString();
final数字n;
final Long l = Longs.tryParse(rawN);
if(l!= null){
n = 1;
} else {
final Double d = Doubles.tryParse(rawN);
if(d!= null){
n = d;
} else {
抛出新的AssertionError(rawN); // must must happen
}
}
writer.value(n);
休息;
case BOOLEAN:
final boolean b = reader.nextBoolean();
writer.value(b);
休息;
case NULL:
reader.nextNull();
writer.nullValue();
休息;
case END_DOCUMENT:
//不做任何操作
break;
默认值:
抛出新的AssertionError(token);
}
}
}

}

这个解析并分别生成相同的输入和输出。 Longs.tryParse Doubles.tryParse 方法取自Google Guava。


I want to persist some data relevant for the client only. I'm going to intentionally ignore database normalization here, as the data is pretty useless on the server side.

I can do it trivially by making the client convert it to JSON and include the String in the JSON send in the request. This feels pretty wrong. I tried to do something smarter and failed badly.

What I'd like to have:

Given

class MyEntity {
    String someString;
    int someInt;
    @Lob String clientData;
}

and an input

{
    someString: "The answer",
    someInt: 43,
    clientData: {
        x: [1, 1, 2, 3, 5, 8, 13],
        y: [1, 1, 2, 6, 24, 120],
        tonsOfComplicatedStuff: {stuff: stuff}
    }
}

store the clientData packed as JSON in a single column. Note that I don't want to write an adapter for MyEntity as there are many columns. I need an adapter for the single column. The column type needn't be a String (Serializable or anything else would do, as the server really doesn't care).

解决方案

Gson supports the @JsonAdapter annotation allowing to specify a JSON (de)serializer, type adapter, or even a type adapter factory. And the annotation looks like a good candidate to annotate the clientData field in MyEntity:

final class MyEntity {

    String someString;

    int someInt;

    @Lob
    @JsonAdapter(PackedJsonTypeAdapterFactory.class)
    String clientData;

}

The type adapter factory may look as follows:

final class PackedJsonTypeAdapterFactory
        implements TypeAdapterFactory {

    // Gson can instantiate this itself
    private PackedJsonTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new PackedJsonTypeAdapter(gson);
        return typeAdapter;
    }

    private static final class PackedJsonTypeAdapter
            extends TypeAdapter<String> {

        private final Gson gson;

        private PackedJsonTypeAdapter(final Gson gson) {
            this.gson = gson;
        }

        @Override
        public void write(final JsonWriter out, final String json) {
            final JsonElement jsonElement = gson.fromJson(json, JsonElement.class);
            gson.toJson(jsonElement, out);
        }

        @Override
        public String read(final JsonReader in) {
            final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
            return jsonElement != null ? jsonElement.toString() : null;
        }

    }

}

Note that this converter strategy is implemented as a type adapter factory, since this is the only way of accessing the Gson instance known to me, and JsonSerializer/JsonDeserializer do not seem to make good parsing via the serialization context. Another pitfall here is that this implementation is tree-based requiring JSON trees to be stored in memory completely. In theory, there could be a nice stream-oriented implementation like gson.fromJson(jsonReader) -> JsonReader or a JsonReader->Reader decorator to be redirected to a StringWriter for example, but I couldn't find any alternative for really long time.

public static void main(final String... args) {
    final Gson gson = new Gson();

    out.println("deserialization:");
    final String incomingJson = "{someString:\"The answer\",someInt:43,clientData:{x:[1,1,2,3,5,8,13],y:[1,1,2,6,24,120],tonsOfComplicatedStuff:{stuff:stuff}}}";
    final MyEntity myEntity = gson.fromJson(incomingJson, MyEntity.class);
    out.println("\t" + myEntity.someString);
    out.println("\t" + myEntity.someInt);
    out.println("\t" + myEntity.clientData);

    out.println("serialization:");
    final String outgoingJson = gson.toJson(myEntity);
    out.println("\t" + outgoingJson);

    out.println("equality check:");
    out.println("\t" + areEqual(gson, incomingJson, outgoingJson));
}

private static boolean areEqual(final Gson gson, final String incomingJson, final String outgoingJson) {
    final JsonElement incoming = gson.fromJson(incomingJson, JsonElement.class);
    final JsonElement outgoing = gson.fromJson(outgoingJson, JsonElement.class);
    return incoming.equals(outgoing);
}

The output:

deserialization:  
    The answer  
    43  
    {"x":[1,1,2,3,5,8,13],"y":[1,1,2,6,24,120],"tonsOfComplicatedStuff":{"stuff":"stuff"}}  
serialization:  
    {"someString":"The answer","someInt":43,"clientData":{"x":[1,1,2,3,5,8,13],"y":[1,1,2,6,24,120],"tonsOfComplicatedStuff":{"stuff":"stuff"}}}  
equality check:  
    true  

Don't know if it can play with Hibernate nicely, though.


Edit

Despite JSON-packed strings are collected into the memory, streaming may be cheaper for various reasons and can save some memory. Another advantage of streaming is that such a JSON-packing type adapter does not need a type adapter factory anymore and Gson instances therefore keeping a JSON stream as-is, however still making some normalizations like {stuff:stuff} -> {"stuff":"stuff"}. For example:

@JsonAdapter(PackedJsonStreamTypeAdapter.class)
String clientData;

final class PackedJsonStreamTypeAdapter
        extends TypeAdapter<String> {

    private PackedJsonStreamTypeAdapter() {
    }

    @Override
    public void write(final JsonWriter out, final String json)
            throws IOException {
        @SuppressWarnings("resource")
        final Reader reader = new StringReader(json);
        writeNormalizedJsonStream(new JsonReader(reader), out);
    }

    @Override
    public String read(final JsonReader in)
            throws IOException {
        @SuppressWarnings("resource")
        final Writer writer = new StringWriter();
        writeNormalizedJsonStream(in, new JsonWriter(writer));
        return writer.toString();
    }

}

final class JsonStreams {

    private JsonStreams() {
    }

    static void writeNormalizedJsonStream(final JsonReader reader, final JsonWriter writer)
            throws IOException {
        writeNormalizedJsonStream(reader, writer, true);
    }

    @SuppressWarnings("resource")
    static void writeNormalizedJsonStream(final JsonReader reader, final JsonWriter writer, final boolean isLenient)
            throws IOException {
        int level = 0;
        for ( JsonToken token = reader.peek(); token != null; token = reader.peek() ) {
            switch ( token ) {
            case BEGIN_ARRAY:
                reader.beginArray();
                writer.beginArray();
                ++level;
                break;
            case END_ARRAY:
                reader.endArray();
                writer.endArray();
                if ( --level == 0 && isLenient ) {
                    return;
                }
                break;
            case BEGIN_OBJECT:
                reader.beginObject();
                writer.beginObject();
                ++level;
                break;
            case END_OBJECT:
                reader.endObject();
                writer.endObject();
                if ( --level == 0 && isLenient ) {
                    return;
                }
                break;
            case NAME:
                final String name = reader.nextName();
                writer.name(name);
                break;
            case STRING:
                final String s = reader.nextString();
                writer.value(s);
                break;
            case NUMBER:
                final String rawN = reader.nextString();
                final Number n;
                final Long l = Longs.tryParse(rawN);
                if ( l != null ) {
                    n = l;
                } else {
                    final Double d = Doubles.tryParse(rawN);
                    if ( d != null ) {
                        n = d;
                    } else {
                        throw new AssertionError(rawN); // must never happen
                    }
                }
                writer.value(n);
                break;
            case BOOLEAN:
                final boolean b = reader.nextBoolean();
                writer.value(b);
                break;
            case NULL:
                reader.nextNull();
                writer.nullValue();
                break;
            case END_DOCUMENT:
                // do nothing
                break;
            default:
                throw new AssertionError(token);
            }
        }
    }

}

This one parses and generates the same input and output respectively. The Longs.tryParse and Doubles.tryParse methods are taken from Google Guava.

这篇关于使用GSON和Hibernate存储任意数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆