2013-11-01 46 views
3

我使用jackson将json字符串映射到我的HTModel类,它基本上是一个简单的Pojo。在数据库中存储嵌套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

我不熟悉HTModel或database.write()。这是由SqlLite提供的某种ORM吗? – Dave

+0

@Dave HTModel是我的基础模型,我的所有模型都从它继承,因此它们具有相同的Type。我删除了database.write语句,它应该只是说明我在数据库中有三列,一个是modelType,例如Post,带有模型id和该模型的json表示。 –

+0

@Dave是不清楚的问题?我应该提供更多的信息还是完成**我正在做什么? –

回答

3

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

让我们以你的Post对象为例。它需要很容易地被表示为一个JSON对象,并且可以从一个JSON字符串创建。另外,它需要能够保存到数据库。我已经打破,我使用基于这两个条件的类heirachy:

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

足够简单,它只是一个LinkedHashMap它代表了JSON对象,它可以通过调用toString(),以及来自使用JsonParser一个JSON字符串创建将很快变成一个JSON字符串我创建的类。

如果您已经在使用像Jackson这样的JSON解析器,那么可能会重做一些使用该API的东西。

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

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

下面是DatabaseObject(修剪下来)的样子。请注意0​​方法,这就是我将回答你的问题的地方。

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

PostDatabaseObject

DatabaseObjectJsonObject,它是LinkedHashMap

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

JsonObject没有做任何事情,只是给出了将LinkedHashMap作为JSON字符串打印的方法。

DatabaseObject指定有关如何保存到数据库,包括在的情况下一个值是另一个DatabaseObject,或逻辑,如果一个值包含DatabaseObject,如用集合。

^- 这意味着你写一次“保存”逻辑。当你拨打Post.save()它会保存这篇文章。当您致电Venue.save()时,它会将场馆栏(如果有)以及Post中的每个人都保存在ArrayList中。

对于额外的乐趣,这里是你的PostVenue对象将是什么样子:

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值并将列值对保存到数据库中,同时考虑集合或可保存的值DatabaseObjects

现在,您只需拨打Venue.save()即可妥善保存所有Post对象。

0

@JsonIdentityInfo/@JsonIdentityReference(序列化帖子为IDS)和定制的解串器(这是应该读取ID从DB帖)可能的解决方案:

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)以使解串器更简单一些。

+0

我没有任何问题反序列化我的模型,例如字符串Venue模型。我的问题是我如何提取嵌套在其他模型中的模型,以将它们单独存储在数据库中。我需要一个方法x,它将得到一个HTModel作为遍历模型的所有字段的参数,检查该字段是否具有HTModel类型,如果是这样,请使用该模型调用x,直到不使用HTModel类型的字段。那个方法x应该把所有单个HTModel的清单交给我。 –

+0

听起来好像可以通过反射来做,但我不确定你为什么要这样做?既然你已经知道你的类的结构,向它们中的每一个添加'saveToDB'方法会更好,这将会保存对象本身并在所有嵌套的'HTModel'实例上调用'saveToDB'(例如'Venue.saveToDB'将迭代'posts'并调用'post.saveToDB')? – alexanderolkhovskiy

+0

你是对的,会更简单。我希望可能有一个简单的方法,所以我不必为每个模态实现手动 –