我使用jackson将json字符串映射到我的HTModel类,这基本上是一个简单的Pojo。
class HTModel{}
public class Post extends HTModel {
public String id;
public String content;
public String author;
}
即使这些类嵌套在一起也能很好地工作。
public class Venue extends HTModel {
public ArrayList<Post> posts;
}
我设置了一个简单的SqlLite模式来缓存和索引这些模型的类型和ID。
我的问题是,如果模型包含其他模型的字段,我不希望将Venue模型整体存储在数据库中。 ArrayList Venue.posts中的每个帖子都应单独保存。
最好的办法是什么?
答案 0 :(得分:3)
我在创建自己的数据库时遇到了类似的问题 - &gt;使用JSON实现POJO。这就是我解决这个问题的方法,对我来说效果很好。
我们以您的Post
对象为例。它需要轻松表示为JSON对象,并从JSON字符串创建。此外,它需要能够保存到数据库。我已根据这两个条件细分了我使用的类:
Post
-> DatabaseObject
-> JsonObject
-> LinkedHashMap
从最基本的表示开始,JsonObject
,这是一个扩展的LinkedHashMap
。由于键值映射,Maps
可以很好地表示JSON对象。这是JsonObject
类:
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A <code>JsonObject</code> represents a JSON object, which begins and ends
* with curly braces '{' '}' and contains key-value pairs separated by a
* colon ':'.
* <p>
* In implementation, this is simply an extended <code>LinkedHashMap</code> to
* represent key-value pairs and to preserve insert order (which may be
* required by some JSON implementations, though is not a standard).
* <p>
* Additionally, calling <code>toString()</code> on the <code>JsonObject</code>
* will return a properly formatted <code>String</code> which can be posted as
* a value JSON HTTP request or response.
* @author Andrew
* @param <V> the value class to use. Note that all keys for a
* <code>JsonObject</code> are <code>Strings</code>
*/
public class JsonObject<V> extends LinkedHashMap<String, V> {
/**
* Creates a new empty <code>JsonObject</code>.
*/
public JsonObject() {
}
/**
* Creates a new <code>JsonObject</code> from the given HTTP response
* <code>String</code>.
* @param source HTTP response JSON object
* @throws IllegalArgumentException if the given <code>String</code> is not
* a JSON object, or if it is improperly formatted
* @see JsonParser#getJsonObject(java.lang.String)
*/
public JsonObject(String source) throws IllegalArgumentException {
this(JsonParser.getJsonObject(source));
}
/**
* Creates a new <code>JsonObject</code> from the given <code>Map</code>.
* @param map a <code>Map</code> of key-value pairs to create the
* <code>JsonObject</code> from
*/
public JsonObject(Map<? extends String, ? extends V> map) {
putAll(map);
}
/**
* Returns a JSON formatted <code>String</code> that properly represents
* this JSON object.
* <p>
* This <code>String</code> may be used in an HTTP request or response.
* @return JSON formatted JSON object <code>String</code>
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
Iterator<Map.Entry<String, V>> iter = entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, V> entry = iter.next();
sb.append(JsonParser.toJson(entry.getKey()));
sb.append(':');
V value = entry.getValue();
sb.append(JsonParser.toJson(value));
if (iter.hasNext()) {
sb.append(',');
}
}
sb.append("}");
return sb.toString();
}
}
简单来说,它只是一个代表JSON对象的LinkedHashMap
,它可以通过调用toString()
快速转换为JSON字符串,也可以使用{{从JSON字符串创建1}}我创建的类。
如果您已经在使用像Jackson这样的JSON解析器,可能会重做一些事情来使用该API。
接下来是JsonParser
的内容,Post
赋予DatabaseObject
功能以与数据库通信。在我的实现中,Post
对象只是一个抽象类。我指定Database
如何在其他地方保存Database
,无论是通过JDBC还是通过HTTP的JSON。
请注意,我们使用DatabaseObjects
来表示我们的对象。对于Map
,这意味着您有三个“属性”(我称之为文档中的键值):ID,内容和作者。
以下是Post
(修剪过的)的样子。请注意DatabaseObject
方法,我将回答您的问题。
save()
对于TL; DR版本:
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The <code>DatabaseObject</code> represents a single row of data from a
* specific table within a database.
* <p>
* The object should implement getters and setters for each column, and is
* responsible for registering the correct table name and column names, as
* well as default values for those columns, in case a default value is
* not supported by the database table.
* <p>
* The <code>DatabaseObject</code> works with key-value pairs as an
* extended <code>LinkedHashMap</code>. It defines one property,
* <code>DatabaseObject.ROW_ID</code> which represents the unique
* identifier column for a table row. This column should always be an
* integer value. (Future implementations may allow for long values, but
* <code>Integer.MAX_VALUE</code> is well suited for most databases as a maximum
* row count per table).
* <p>
* The key and value pairs should be accessed by implementing getter and
* setter methods, not by the get and put methods provided by the
* <code>LinkedHashMap</code>. This is to ensure proper <code>Class</code>
* type casting for each value.
* <p>
* A <code>DatabaseObject</code> itself is also an extension of a
* <code>JsonObject</code>, and <code>toString()</code> may be called on
* it to provide a JSON notated <code>DatabaseObject</code>.
* <p>
* When using JSON however, keep in mind that the keys may not correspond
* exactly with the table column names, even though that is the recommendation.
* The <code>DatabaseObject</code> should be converted back into its
* implementing object form and saved when using web services.
* <p>
* The parameter <code>T</code> should be set to the class of the implementing
* <code>DatabaseObject</code>. This will allow proper class casting when
* returning instances of the implementation, such as in the <code>load()</code>
* methods.
* @param <T> the type of <code>DatabaseObject</code>
* @author Andrew
*/
public abstract class DatabaseObject<T extends DatabaseObject>
extends JsonObject<Object> implements Cloneable{
/**The property for the row ID*/
public final static String ROW_ID = "rowId";
/**
* Creates a new empty <code>DatabaseObject</code>.
*/
public DatabaseObject() {
}
/**
* {@inheritDoc }
* <p>
* This get method will additionally check the <code>Class</code> of
* the returned value and cast it if it is a <code>String</code> but
* matches another <code>Class</code> type such as a number.
* @see #doGet(java.lang.String, boolean)
*/
@Override
public Object get(Object key) {
//From here you can specify additional requirements before retrieving a value, such as class checking
//This is optional of course, and doGet() calls super.get()
return doGet(String.valueOf(key), true);
}
/**
* {@inheritDoc }
* <p>
* This get method will additionally check the <code>Class</code> of
* the given value and cast it if it is a <code>String</code> but
* matches another <code>Class</code> type such as a number.
* @see #doPut(java.lang.String, java.lang.Object, boolean)
*/
@Override
public Object put(String key, Object value) {
//Like doGet(), doPut() goes through additional checks before returning a value
return doPut(key, value, true);
}
//Here are some example getter/setter methods
//DatabaseObject provides an implementation for the row ID column by default
/**
* Retrieves the row ID of this <code>DatabaseObject</code>.
* <p>
* If the row ID could not be found, -1 will be returned. Note that
* a -1 <i>may</i> indicate a new <code>DatabaseObject</code>, but it
* does not always, since not all <code>Databases</code> support
* retrieving the last inserted ID.
* <p>
* While the column name might not correspond to "rowId", this
* method uses the <code>DatabaseObject.ROW_ID</code> property. All
* objects must have a unique identifier. The name of the column
* should be registered in the constructor of the object.
* @return the <code>DatabaseObject's</code> row ID, or -1 if it is not set
*/
public int getRowId() {
//getProperty(), while not included, simply returns a default specified value
//if a value has not been set
return getProperty(ROW_ID, -1);
}
/**
* Sets the row ID of this <code>DatabaseObject</code>.
* <p>
* <b>Note: this method should rarely be used in implementation!</b>
* <p>
* The <code>DatabaseObject</code> will automatically set its row ID when
* retrieving information from a <code>Database</code>. If the row ID
* is forcibly overriden, this could overwrite another existing row entry
* in the database table.
* @param rowId the row ID of the <code>DatabaseObject</code>
*/
public void setRowId(int rowId) {
//And again, setProperty() specifies some additional conditions before
//setting a key-value pair, but its not needed for this example
setProperty(ROW_ID, rowId);
}
//This is where your question will be answered!!
//Since everything in a DatabaseObject is set as a key-value pair in a
//Map, we don't have to use reflection to look up values for a
//specific object. We can just iterate over the key-value entries
public synchronized void save(Database database) throws SaveException {
StringBuilder sql = new StringBuilder();
//You would need to check how you want to save this, let's assume it's
//an UPDATE
sql.append("UPDATE ").append(getTableName()).append(" SET ");
Iterator<String, Object> iter = entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Object> entry = iter.next();
String property = entry.getKey();
Object value = entry.getValue();
if (value instanceof DatabaseObject) {
((DatabaseObject)value).save();
}
else if (value instanceof Collection) {
for (Object v : (Collection)value) {
((DatabaseObject)value).save();
}
}
//However many other checks you want, such as Maps, Arrays, etc
else {
sql.append(property); //Assuming the property equals the column name
sql.append("='").append(value).append("'");
}
if (iter.hasNext()) {
sql.append(", ");
}
}
//getIdColumn() would retrieve which column is being used as the identifier
sql.append("WHERE ").append(getIdColumn()).append("=").append(getRowId());
//Finally, take our SQL string and save the value to the Database
//For me, I could simply call database.update(sql), and
//the abstracted Database object would determine how to
//send that information via HTTP as a JSON object
//Of course, your save() method would vary greatly, but this is just a quick
//and dirty example of how you can iterate over a Map's
//key-value pairs and take into account values that are
//DatabaseObjects themselves that need to be saved, or
//a Collection of DatabaseObjects that need to be saved
}
/**
* Retrieves the name of the database table that this
* <code>DatabaseObject</code> pulls its information from.
* <p>
* It is recommended to declare a final and static class variable that
* signifies the table name to reduce resource usage.
* @return name of the database table
*/
public abstract String getTableName();
}
是Post
。
DatabaseObject
是DatabaseObject
,是JsonObject
。
LinkedHashMap
设置存储键值对的标准。 Key =列名,值=列值。
LinkedHashMap
除了提供一种方法可以将JsonObject
打印为JSON字符串。
LinkedHashMap
指定了如何保存到数据库的逻辑,包括值是另一个DatabaseObject
的情况,或者值是否包含DatabaseObject
的情况,例如收藏集
^ - 这意味着您只需编写一次“保存”逻辑。当您致电DatabaseObject
时,会保存帖子。当您致电Post.save()
时,它会保存会场列(如果有)以及Venue.save()
中的每个Post
个人。
为了获得额外的乐趣,以下是您的ArrayList
和Post
对象的外观:
Venue
请注意,我们不再声明类变量,只是声明存储值的列名。我们在getter / setter方法中定义变量的类。
public class Post extends DatabaseObject<Post> {
//The column names
public final static String POST_ID = "PostID";
public final static String CONTENT = "Content";
public final static String AUTHOR = "Author";
public Post() {
//Define default values
}
public int getPostId() {
return (Integer)get(POST_ID);
}
public void setPostId(int id) {
put(POST_ID, id);
}
public String getContent() {
return (String)get(CONTENT);
}
public void setContent(String content) {
put(CONTENT, content);
}
public String getAuthor() {
return (String)getAuthor();
}
public void setAuthor(String author) {
put(AUTHOR, author);
}
@Override
public String getTableName() {
return "myschema.posts";
}
}
额外终极TL; DR版本:
使用import java.util.ArrayList;
import java.util.List;
public class Venue extends DatabaseObject<Venue> {
//This is just a key property, not a column
public final static String POSTS = "Posts";
public Venue() {
setPosts(new ArrayList());
}
public List<Post> getPosts() {
return (List<Post>)get(POSTS);
}
public void setPosts(List<Post> posts) {
put(POSTS, posts);
}
public void addPost(Post post) {
getPosts().add(post);
}
@Override
public String getTableName() {
//Venue doesn't have a table name, it's just a container
//In your DatabaseObject.save() method, you can specify logic to
//account for this condition
return "";
}
}
存储变量,而不是在班级中定义它们。
创建一个Map
方法逻辑,迭代save()
值,并将列值对保存到数据库,同时考虑集合或可保存的值Map
。
现在您所要做的就是致电DatabaseObjects
,所有Venue.save()
个对象也会被妥善保存。
答案 1 :(得分:0)
使用@JsonIdentityInfo
/ @JsonIdentityReference
(将帖子序列化为ID)和自定义反序列化器(可以通过ID读取数据库中的帖子)的可能解决方案:
public class SerializationTest {
static class HTModel{}
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public static class Post extends HTModel {
public int id;
public String content;
public String author;
}
static class Venue extends HTModel {
@JsonIdentityReference(alwaysAsId=true)
@JsonDeserialize(using = VenuePostsDeserializer.class)
public ArrayList<Post> posts;
}
static class VenuePostsDeserializer extends JsonDeserializer<ArrayList<Post>> {
@Override
public ArrayList<Post> deserialize(JsonParser parser, DeserializationContext context) throws IOException,
JsonProcessingException {
ArrayList<Post> posts = new ArrayList<Post>();
if (parser.getCurrentToken() != JsonToken.START_ARRAY) {
return posts;
}
parser.nextValue();
try {
do {
int postId = parser.getIntValue();
// FIXME: fetch the corresponding post from DB instead of creating a stub
Post post = new Post();
post.id = postId;
post.content = String.format("Post #%d content", postId);
post.author = String.format("Post #%d author", postId);
posts.add(post);
parser.nextValue();
} while(parser.getCurrentToken() != JsonToken.END_ARRAY);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
return posts;
}
}
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
Venue venue = new Venue() {{
posts = new ArrayList<Post>() {{
add(new Post() {{ id = 2; }});
add(new Post() {{ id = 42; }});
}};
}};
try {
String serializedVenue = mapper.writeValueAsString(venue);
System.out.println("Serialized venue: " + serializedVenue);
Venue deserializedVenue = mapper.readValue(serializedVenue, Venue.class);
System.out.println("Deserialized venue:");
for (Post post : deserializedVenue.posts) {
System.out.println(String.format("Post: id=%d, content=%s, author=%s", post.id, post.content, post.author));
}
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
}
输出:
Serialized venue: {"posts":[2,42]}
Deserialized venue:
Post: id=2, content=Post #2 content, author=Post #2 author
Post: id=42, content=Post #42 content, author=Post #42 author
编辑:我将Post.id
更改为int
(来自String
),以便在示例中使用更简单的解串器。