将嵌套的Pojo对象存储为数据库中的单个对象 [英] Store nested Pojo Objects as individuall Objects in Database

查看:86
本文介绍了将嵌套的Pojo对象存储为数据库中的单个对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用jackson将json字符串映射到我的HTModel类,这基本上是一个简单的Pojo。

Im using jackson to map json strings to my HTModel class which is basically a simple Pojo.

class HTModel{}

public class Post extends HTModel {
    public String id;
    public String content;
    public String author;
}

即使这些类嵌套在一起也能很好地工作。

That works quite well even the classes are nested together.

public class Venue extends HTModel {
    public ArrayList<Post> posts;
}

我设置了一个简单的SqlLite模式来缓存和索引这些模型的类型和他们的身份。

I setup a simple SqlLite schema to cache and index these Models by their type and their id.

我的问题是,如果模型包含其他模型的字段,我不想存储整个数据库中的Venue模型。 ArrayList Venue.posts中的每个帖子都应单独保存。

My problem is that i don´t want to store, for example the Venue Model, as whole in the database if the Model contains fields with other Models. Each post in the ArrayList Venue.posts should be saved individually.

最好的办法是什么?

推荐答案

我'我在使用JSON创建自己的数据库 - > POJO实现时遇到了类似的问题。这就是我解决这个问题的方法,对我来说效果很好。

I've faced a similar problem when creating my own Database -> POJO implementation using JSON. This is how I solved the issue, and it works quite well for me.

让我们把你的 Post 对象作为一个例子。它需要很容易地表示为JSON对象,并从JSON字符串创建。此外,它需要能够保存到数据库。我根据以下两个条件细分了我使用的类:

Let's take your Post object as an example. It needs to easily be represented as a JSON object and be created from a JSON string. Additionally, it needs to be able to save to a database. I've broken down the heirachy of classes that I use based on these two conditions:

Post
  -> DatabaseObject
    -> JsonObject
      -> LinkedHashMap

从最基本的表示开始, JsonObject ,这是一个扩展的 LinkedHashMap Maps 由于其键值映射而可以很好地表示JSON对象。这是 JsonObject 类:

Starting with the most basic representation, a JsonObject, which is an extended LinkedHashMap. Maps work well for representing JSON objects due to their key-value mapping. Here's the JsonObject class:

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();
    }
}

简单来说,它只是 LinkedHashMap 表示JSON对象,可以通过调用 toString()快速转换为JSON字符串,也可以从使用我创建的 JsonParser 类的JSON字符串。

Simply enough, it's just a LinkedHashMap which represents the JSON object, and it can be quickly turned into a JSON string by calling toString(), as well as created from a JSON string using the JsonParser class that I created.

可能如果您已经使用像Jackson那样的JSON解析器,你可以修改一些东西来使用那个API。

Likely if you're already using a JSON parser like Jackson, you could rework a few things to use that API.

接下来是 Post 的内容, DatabaseObject ,它提供 Post 功能以与数据库通信。在我的实现中,数据库对象只是一个抽象类。我指定数据库如何在其他地方保存 DatabaseObjects ,无论是通过JDBC还是通过HTTP的JSON。

Next is the meat of the Post, the DatabaseObject which gives the Post functionality to communicate with a database. In my implementation, the Database object is simply an abstract class. I specify how the Database saves DatabaseObjects elsewhere, be it through JDBC or JSON over HTTP.

请记住,我们使用 Map 来表示我们的对象。对于发布,这意味着您有三个属性(我称之为文档中的键值):ID,内容和作者。

Keep in mind that we're using a Map to represent our object. For your Post, that means you have three "properties" (as I call the key values in my documentation): the ID, content, and author.

以下是 DatabaseObject (修剪过的)的样子。请注意 save()方法,我会回答你的问题。

Here's what the DatabaseObject (trimmed down) looks like. Note the save() method, that's where I'll answer your question.

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版本:

For the TL;DR version:

发布 DatabaseObject

DatabaseObject 是一个 JsonObject ,这是一个 LinkedHashMap

DatabaseObject is a JsonObject, which is a LinkedHashMap.

LinkedHashMap 设置存储键值对的标准。 Key =列名,值=列值。

The LinkedHashMap set the standard of storing key-value pairs. Key = column name, Value = column value.

JsonObject 除了打印<$之外什么都不做c $ c> LinkedHashMap 作为JSON字符串。

JsonObject does nothing but give a way to print the LinkedHashMap as a JSON string.

DatabaseObject 指定逻辑如何保存到您的数据库,包括值是另一个 DatabaseObject 的情况,或者值是否包含 DatabaseObject ,例如使用Collections。

DatabaseObject specifies the logic on how to save to your database, including in the case where a value is another DatabaseObject, or if a value contains a DatabaseObject, such as with Collections.

^ - 这意味着您只需编写一次保存逻辑。当你打电话给 Post.save()时,它会保存帖子。当你打电话给 Venue.save()时,它会保存场地列(如果有的话),以及每个帖子 ArrayList

^ -- This means you write the "save" logic once. When you call Post.save() it saves the post. When you call Venue.save(), it saves the venue columns (if any), as well as each individual Post in the ArrayList.

为了额外的乐趣,这是你的帖子 Venue 对象看起来像:

For extra fun, here is what your Post and Venue objects would look like:

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方法中定义变量的类。

Note that we are no longer declaring class variables, just the column names in which the values are stored. We define the class of the variables in the getter/setter methods.

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版本:

Extra ultimate TL;DR version:

使用 Map 来存储变量,而不是在类中定义它们。

Use a Map to store your variables instead of defining them in your class.

创建一个 save()方法逻辑,迭代 Map 值并保存列数值对数据库,考虑到集合或可保存的值 DatabaseObjects

Create a save() method logic that iterates over the Map values and saves the column-value pairs to the database, taking into account values which are Collections or saveable DatabaseObjects.

现在你们所有人要做的就是调用 Venue.save(),并且所有 Post 对象也将被适当保存。

Now all you have to do is call Venue.save() and all your Post objects will be appropriately saved as well.

这篇关于将嵌套的Pojo对象存储为数据库中的单个对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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