在 Android 上使用 ORMLite 保存嵌套的外来对象

Posted

技术标签:

【中文标题】在 Android 上使用 ORMLite 保存嵌套的外来对象【英文标题】:Saving nested foreign objects with ORMLite on Android 【发布时间】:2011-01-20 09:55:05 【问题描述】:

android 上工作时,ORMLite 是否只保存浅层对象?我有一个包含嵌套对象的数据结构,这两个都是新创建的,我希望能够通过一次调用 dao.create() 来保存它们

例如,我有以下父类。

@DatabaseTable
public class Parent 

  @DatabaseField(generatedId=true)
  public int id;

  @DatabaseField
  public String name;

  @DatabaseField
  public Child child;

和下面的子类。

@DatabaseTable
public class Child 

  @DatabaseField(generatedId=true)
  public int id;

  @DatabaseField
  public String name;

我希望能够做到以下几点。

Parent parent = new Parent();
parent.name = "ParentName";

Child child = new Child();
child.name = "ChildName";

parent.child = child;

//  .. get helper and create dao object...
dao.create(parent);

执行此操作时,父对象被持久化,而不是子对象,并且父表中自动生成的child_id 列设置为0。这是正常行为吗?有没有办法让嵌套对象持久化并向上传播主键?

【问题讨论】:

另见this related question。 虽然 Gray 总是参考他的页面,但是如果没有关于 *** 的问题和带有完整示例的直接答案(那些实际上符合他的文档),那么很难通过 ORMLite。没有冒犯灰色 【参考方案1】:

你试过了吗?

@DatabaseField(foreign = true, foreignAutoCreate = true, foreignAutoRefresh = true)
public Child child;

我正在使用 ORMLite 4.35。

【讨论】:

我之前的评论说 ORMLite for Android 不支持这些功能,but it does! +1 - 因为在我看来这是正确的答案。这几乎对我有用。我的问题是我从服务器获取了带有 ID 的外来对象,它只会自动创建 id=0 或 null 的外来对象:(我能做些什么来避免手动创建外来对象? 请注意,这种方法可能会影响性能。例如,在 UI 中,您可能会在对象标题中显示少量信息,并且仅根据用户需求查询详细信息。 不幸的是,不适用于@ForeignCollectionField 它运行良好,当我尝试创建父对象时,我的子对象也会创建。但是当我更改子对象中的一个字段的值然后我调用更新父对象时,它不会更新子对象自动地。你有什么线索吗?【参考方案2】:

从 4.27 版开始,ORMlite 支持字段上的 @DatabaseField 注释上的 foreignAutoCreate 和 foreignAutoRefresh 设置:

@DatabaseField(foreign = true, foreignAutoCreate = true, foreignAutoRefresh = true)
public Child child;

这意味着您分配了 child 字段,如果在创建父级时未设置子级上的 id 字段,则将创建它。 foreignAutoRefresh 表示当检索父级时,将进行单独的 SQL 调用以填充 child 字段。

这样做时,父对象被持久化,而不是子对象,并且父表中自动生成的 child_id 列设置为 0。这是正常行为吗?

您还可以通过在创建父对象之前创建子对象来更好地控制 ORMLite 何时调用子对象。

Parent parent = new Parent();
parent.name = "ParentName";

Child child = new Child();
child.name = "ChildName";

parent.child = child;

// this will update the id in child
childDao.create(child);

// this saves the parent with the id of the child
parentDao.create(parent);

还有一点需要注意的是,当您查询 Parent 对象时,如果没有 foreignAutoRefresh = true,您返回的子对象只有会检索其 id 字段。如果 id 是自动生成的 int(例如),则在您对子对象进行更新之前,不会检索上述名称字段。

// assuming the id of the Parent is the name
Parent parent = parentDao.queryForId("ParentName");
System.out.println("Child id should be set: " + parent.child.id);
System.out.println("Child name should be null: " + parent.child.name);

// now we refresh the child object to load all of the fields
childDao.refresh(parent.child);
System.out.println("Child name should now be set: " + parent.child.name);

有关这方面的更多文档,请参阅有关 Foreign Object Fields 的在线页面。

【讨论】:

感谢您的反馈。我想的也差不多。我的问题只给出了一个简单的例子,但我真正想做的是能够存储相当复杂的对象,而不必对对象本身了解太多。我写了一个快速解决方案,我将发布,但我认为它可以改进。 请将其@Chase 发布到 ORMLite 邮件列表。 groups.google.com/group/ormlite-user 当然。我注意到较新版本的 ORMLite 现在支持类似的功能。从那以后,我进行了许多其他改进,我也会发布邮件列表。 @gray 根据 ORMLite 文档,您还应该这样做 parent.child = child;child=parent; 。此外,我在 ChilderDao 之后使用 create ParentDao 所以 childrenDao.create(children); parentDao.create(parent); 它会导致孩子被创建两次。可能是 Collection 有 eager=true 的问题吗? 如果'generatedId = false'有这样的简单解决方案吗?【参考方案3】:

如前所述,精简版似乎不支持此功能。我写了一个简单的递归函数来保存所有引用的对象。我在让泛型发挥得很好时遇到了问题,所以最后我把它们全部删除了。我还为我的数据库对象创建了一个基本实体类。

这就是我写的。如果有人可以获得相同的代码来使用适当的泛型,或者可以改进它,请随时编辑。

    // Debugging identity tag
    public static final String TAG = DatabaseHelper.class.getName();

    // Static map of common DAO objects
    @SuppressWarnings("rawtypes")
    private static final Map<Class, Dao<?, Integer>> sDaoClassMap = new HashMap<Class, Dao<?,Integer>>();

    /**
     * Persist an entity to the underlying database.
     * 
     * @param context
     * @param entity
     * @return boolean flag indicating success
     */
    public static boolean create(Context context, Entity entity) 
        // Get our database manager
        DatabaseHelper databaseHelper = DatabaseHelper.getHelper(context);

        try 
            // Recursively save entity
            create(databaseHelper, entity);

         catch (IllegalArgumentException e) 
            Log.e(TAG, "Object is not an instance of the declaring class", e);
            return false;
         catch (IllegalAccessException e) 
            Log.e(TAG, "Field is not accessible from the current context", e);
            return false;
         catch (SQLException e) 
            Log.e(TAG, "Unable to create object", e);
            return false;
        

        // Release database helper
        DatabaseHelper.release();

        // Return true on success
        return true;
    

    /**
     * Persist an entity to the underlying database.<br><br>
     * For each field that has a DatabaseField annotation with foreign set to true, 
     * and is an instance of Entity, recursive attempt to persist that entity as well. 
     * 
     * @param databaseHelper
     * @param entity
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws SQLException
     */
    @SuppressWarnings("unchecked")
    public static void create(DatabaseHelper databaseHelper, Entity entity) throws IllegalArgumentException, IllegalAccessException, SQLException 
        // Class type of entity used for reflection
        @SuppressWarnings("rawtypes")
        Class clazz = entity.getClass();

        // Search declared fields and save child entities before saving parent. 
        for(Field field : clazz.getDeclaredFields()) 
            // Inspect annotations
            for(Annotation annotation : field.getDeclaredAnnotations()) 
                // Only consider fields with the DatabaseField annotation
                if(annotation instanceof DatabaseField) 
                    // Check for foreign attribute
                    DatabaseField databaseField = (DatabaseField)annotation;
                    if(databaseField.foreign()) 
                        // Check for instance of Entity
                        Object object = field.get(entity);                      
                        if(object instanceof Entity) 
                            // Recursive persist referenced entity
                            create(databaseHelper, (Entity)object);
                        
                    
                
            
        

        // Retrieve the common DAO for the entity class
        Dao<Entity, Integer> dao = (Dao<Entity, Integer>) sDaoClassMap.get(clazz);
        // If the DAO does not exist, create it and add it to the static map
        if(dao == null) 
            dao = BaseDaoImpl.createDao(databaseHelper.getConnectionSource(), clazz);
            sDaoClassMap.put(clazz, dao);
        

        // Persist the entity to the database
        dao.create(entity);
    

【讨论】:

感谢这位老兄。是的,我一直在考虑添加自动创建/更新外国字段的能力。然而,它不会是默认值,它将在另一个数据库事务中,而不是像 hibernate 使用的魔术连接。 其实再想一想,一般孩子都会把父母当成外来物。 Account 存在于具有外部 Account 字段的 Order 对象之前。通常,对象的自动刷新是可能的,但自动创建会很奇怪。小时。 我的理解是,唯一自动生成的列是用于外部对象的列,这意味着子对象不会自动创建对其父对象的引用。考虑到这一点,上面的代码对我来说工作得很好,尽管我已经稍微改进了泛型。最后,我使用 GSON 将 JSON 映射到一个对象,并使用同一个对象使用 ORM Lite 在本地持久化。我必须编写一些自定义 DAO 对象来加载和创建列表实体(一对多关系)。它工作得很好,但有点慢。让它成为一个内置功能会很好:)【参考方案4】:
@DatabaseField(foreign = true,foreignAutoCreate = true,foreignAutoRefresh = true)
public Child child;

关于此解决方案的一些注意事项

    (foreignAutoCreate = true) 仅在根据 ORMlite 文档 http://ormlite.com/javadoc/ormlite-core/com/j256/ormlite/field/DatabaseField.html 未设置 ID 字段(null 或 0)时有效

    foreignAutoCreate: “将此设置为 true(默认为 false),如果未设置 ID 字段(null 或 0),将使用其内部 DAO 自动创建外部字段。”

    这仅在根据ORMlite documentation 将子表的 generatedId 也设置为 true 时才有效。

【讨论】:

以上是关于在 Android 上使用 ORMLite 保存嵌套的外来对象的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 上使用 OrmLite 进行 Proguard

是否可以在带有 h2 或 sybase 数据库的 android 上使用 ormlite?

如何在 android 中使用 ProGuard 和 OrmLite

在 CursorAdapter 中使用带有 ORMLite 的 Android 游标

Android数据库框架-----ORMLite 的基本用法

android ORMlite的应用