2013-11-01 5 views
3

기본적으로 간단한 Pojo 인 json 문자열을 json 문자열을 내 HTModel 클래스에 매핑하기 위해 jackson을 사용합니다.중첩 된 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 Model을 저장하고 싶지 않다는 것입니다. ArrayList Venue.posts의 각 게시물은 개별적으로 저장해야합니다.

가장 좋은 방법은 무엇입니까?

+0

HTModel 또는 database.write()에 익숙하지 않습니다. SqlLite에서 제공하는 ORM일까요? – Dave

+0

@Dave HTModel은 내 모든 모델에서 상속 한 기본 모델이므로 모두 동일한 유형을 갖습니다. database.write 문을 제거하여 데이터베이스에 3 개의 열 (모델 ID 예 : Post, 모델 ID 및 해당 모델의 json 표현)이 하나만 있음을 보여야합니다. –

+0

@Dave는 명확하지 않은 질문입니까? 나는 더 많은 정보를 제공해야합니까, 아니면 ** 내가하고있는 일을 완전하게해야합니까? –

답변

3

JSON을 사용하여 데이터베이스 -> POJO 구현을 만들 때 비슷한 문제가 발생했습니다. 이것이 내가이 문제를 해결 한 방법이며, 그것은 나를 위해 아주 잘 작동합니다.

Post 개체를 예로 들어 보겠습니다. JSON 객체로 쉽게 표현할 수 있어야하며 JSON 문자열로 만들어야합니다. 또한 데이터베이스에 저장할 수 있어야합니다.

Post 
    -> DatabaseObject 
    -> JsonObject 
     -> LinkedHashMap 

가장 기본적인 표현을 시작으로하는 JsonObject, 확장 LinkedHashMap이다 : 나는이 두 가지 조건에 따라 사용할 클래스의 heirachy 세분화했습니다. Maps은 키 - 값 매핑으로 인해 JSON 객체를 나타내는 데 적합합니다. 간단하게 충분히

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()를 호출뿐만 아니라 JsonParser를 사용하여 JSON 문자열에서 생성하여 JSON 문자열로 설정할 수 있습니다 : 여기 JsonObject 클래스입니다 내가 만든 클래스.

이미 Jackson과 같은 JSON 파서를 사용하고 있다면 API를 사용하기 위해 몇 가지 사항을 다시 고칠 수 있습니다.

다음은 Post의 고기입니다. DatabaseObjectPost의 기능을 데이터베이스와 통신합니다. 내 구현에서 Database 개체는 단순히 추상 클래스입니다. Database이 다른 곳에서 DatabaseObjects을 저장하는 방법을 지정합니다. HTTP를 통해 JDBC 또는 JSON을 사용합니다.

우리는 객체를 나타내는 데 Map을 사용하고 있습니다.Post의 경우 ID, 콘텐츠 및 작성자라는 세 가지 속성 (내 설명서의 키 값을 호출 함)이 있음을 의미합니다.

여기에 DatabaseObject (내림차순) 모양이 있습니다. save() 방법을 참고하십시오. 여기서 질문에 답변 해 드리겠습니다. 는 TL 들어

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

; DR 버전 :

PostDatabaseObject이다.

DatabaseObjectLinkedHashMapJsonObject입니다.

LinkedHashMap은 키 - 값 쌍을 저장하는 표준을 설정합니다. 키 = 열 이름, 값 = 열 값.

JsonObject 아무 것도하지 않고 LinkedHashMap을 JSON 문자열로 인쇄하는 방법을 제공합니다.

DatabaseObject가 값이 다른 DatabaseObject, 또는 어디에 경우를 포함하여 데이터베이스에 저장하는 방법에 대한 논리를 지정하는 값은 컬렉션과 같은 DatabaseObject를 포함합니다.

^- 이것은 "저장"논리를 한 번 쓰는 것을 의미합니다. Post.save()으로 전화하면 게시물이 저장됩니다. Venue.save()으로 전화하면 회장 열 (있을 경우)과 각 PostArrayList에 저장합니다. 우리는 더 이상 클래스 변수, 값이 저장되는 단지 열 이름을 선언하는

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"; 
    } 

} 

주 : 여분의 재미를 들어

는 여기 PostVenue 물체의 모습 것입니다. 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() 메서드 논리를 만들고 컬렉션 또는 저장 가능한 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을 (String)으로 변경하여 좀 더 간단한 디시리얼라이저를 만들었습니다.

+0

내 모델을 문자열에서 Venue 모델과 같이 deserialize하는 데 아무런 문제가 없습니다. 내 질문은 다른 모델에 중첩 된 모델을 추출하여 데이터베이스에 개별적으로 저장하는 방법입니다. 모델의 모든 필드를 반복하는 인수로 HTModel을 가져올 메소드 x가 필요합니다. 필드에 HTModel 유형이 있는지 검사합니다. HTModel 유형의 필드가 없을 때까지 해당 모델에서 x를 호출하십시오. 이 메소드 x는 모든 개별 HTModels의 목록을 돌려 주어야합니다. –

+0

리플렉션을 통해 할 수있는 것처럼 들리지만, 왜 이것을하고 싶지는 모르겠습니까? 클래스의 구조를 알고 있기 때문에 객체 자체를 저장하고 모든 중첩 된'HTModel' 인스턴스 (예 :'Venue.saveToDB')에 대해'saveToDB'를 호출하는'saveToDB' 메소드를 추가하는 것이 더 좋지 않을까요? 'posts'를 반복하고'post.saveToDB'를 호출 할 것입니다)? – alexanderolkhovskiy

+0

당신이 옳다면 더 간단 할 것입니다. 쉬운 방법이 있기를 바래서 각 모달에 대해 수동으로 구현하지 않아도됩니다. –

관련 문제