我使用 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 缓存和索引这些模型。
我的问题是,如果模型包含其他模型的字段,我不想将 field 模型作为一个整体存储在数据库中。 ArrayList Venue.posts 中的每个帖子都应该单独保存。
最好的方法是什么?
最佳答案
在使用 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字符串,以及创建使用我创建的 JsonParser
类从 JSON 字符串中提取。
如果您已经在使用像 Jackson 这样的 JSON 解析器,您可能需要重新做一些事情才能使用该 API。
接下来是 Post
的内容,DatabaseObject
赋予 Post
与数据库通信的功能。在我的实现中,Database
对象只是一个抽象类。我指定 Database
如何将 DatabaseObjects
保存到别处,无论是通过 JDBC 还是 JSON over HTTP。
请记住,我们使用 Map
来表示我们的对象。对于您的帖子
,这意味着您拥有三个“属性”(我在文档中称其为关键值):ID、内容和作者。
这是 DatabaseObject
(精简后)的样子。请注意 save()
方法,我将在此处回答您的问题。
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();
}
对于 TL;DR 版本:
Post
是一个 DatabaseObject
。
DatabaseObject
是一个JsonObject
,它是一个LinkedHashMap
。
LinkedHashMap
设定了存储键值对的标准。键 = 列名,值 = 列值。
JsonObject
什么都不做,只是提供了一种将 LinkedHashMap
打印为 JSON 字符串的方法。
DatabaseObject
指定有关如何保存到数据库的逻辑,包括在值是另一个 DatabaseObject
的情况下,或者如果值包含 DatabaseObject
,例如集合。
^ -- 这意味着您只需编写一次“保存”逻辑。当您调用 Post.save()
时,它会保存帖子。当您调用 Venue.save()
时,它会保存地点列(如果有的话),以及 ArrayList
中的每个单独的 Post
。
为了额外的乐趣,下面是您的 Post
和 Venue
对象的样子:
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";
}
}
请注意,我们不再声明类变量,只声明存储值的列名。我们在 getter/setter 方法中定义变量的类。
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 "";
}
}
额外的终极 TL;DR 版本:
使用 Map
来存储您的变量,而不是在您的类中定义它们。
创建一个 save()
方法逻辑,迭代 Map
值并将列值对保存到数据库,同时考虑集合或可保存的值数据库对象
。
现在您所要做的就是调用 Venue.save()
,您所有的 Post
对象也将被适本地保存。
关于java - 将嵌套的 Pojo 对象作为单独的对象存储在数据库中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19726894/