【问题标题】:One-to-Many relationship and Serialized Object field一对多关系和序列化对象字段
【发布时间】:2013-09-30 07:36:26
【问题描述】:

我有以下具有 1 对 N 关系的 Persistable 类。

@PersistenceCapable
public class Pet {

  @Persistent(primaryKey = "true", valueStrategy = IdGeneratorStrategy.IDENTITY)
  Long id;

  @Persistent
  String name;

  @Element(column = "PET_ID")
  List<Photo> photos;

  // getters and setters

@PersistenceCapable
public class Photo {

  @Persistent(primaryKey = "true", valueStrategy = IdGeneratorStrategy.IDENTITY)
  Long id;

  @Persistent
  String desc;

  @Persistent(serialized="true")
  Object image;

  // getters and setters

  // hash and equal using field id

Field List photos 使用 FK 在 Pet (1) 和 Photo (N) 之间建立 1-N 关系。 Photo中的Object image字段是一个序列化的用来保存图片对象的。

对于数据存储操作,我使用 PetDao,它有以下方法

public final static PersistenceManagerFactory pmf = JDOHelper
            .getPersistenceManagerFactory("datastore");

public void storePet(Pet pet) {
    // get PM and current tx
    try {
       tx.begin();
       pm.makePersistent(pet);
       tx.commit();
    } catch (Exception e) {
       // rollback and close pm
    }           
}

public void storePhoto(Long petId, Photo photo) {
    // get PM and current tx
    try {
       tx.begin();
       Pet pet = pm.getObjectById(Pet.class,petId);
       pet.addPhoto(photo);
       tx.commit();
    } catch (Exception e) {
       // rollback and close pm
    }
}

我将对象创建和持久化为

Pet pet = new Pet();
pet.setName("Nicky");

Photo photo = new Photo();
photo.setDesc("Photo 1");
photo.setImage(new Image("image 1"));
pet.addPhoto(photo);

.... add photo 2 and photo 3

PetDao petDao = new PetDao();       
petDao.storePet(pet);

// i have one more photo so add it directly
photo = new Photo();
photo.setDesc("Photo 4");
photo.setImage(new Image ("image 4"));      

petDao.storePhoto((long)0, photo);

一切都按要求保留,数据存储最终在 PET 表中包含 1 只宠物,在 PHOTO 表中包含 4 张照片。

但是当我分析 petDao.storePhoto((long)0, photo) 代码的 DataNucleus 日志时,我看到 DataNucleus 从数据存储中检索所有图像对象。

Native          [DEBUG] INSERT INTO PHOTO ("DESC",IMAGE,PET_ID,PHOTOS_INTEGER_IDX) VALUES (<'Photo 4'>,<UNPRINTABLE>,<0>,<3>)
Persist         [DEBUG] Execution Time = 70 ms (number of rows = 1) on PreparedStatement "org.datanucleus.store.rdbms.ParamLoggingPreparedStatement@190a0d6"
Persist         [DEBUG] Object "in.m.pet.Photo@10deb5f" was inserted in the datastore and was given strategy value of "3"
Native          [DEBUG] SELECT A0.IMAGE FROM PHOTO A0 WHERE A0.ID = <1>
Retrieve        [DEBUG] Execution Time = 1 ms
Native          [DEBUG] SELECT A0.IMAGE FROM PHOTO A0 WHERE A0.ID = <0>
Retrieve        [DEBUG] Execution Time = 0 ms
Native          [DEBUG] SELECT A0.IMAGE FROM PHOTO A0 WHERE A0.ID = <2>
Retrieve        [DEBUG] Execution Time = 0 ms

使用 INSERT INTO PHOTO... 语句添加“照片 4”后,DataNucleus 通过触发 3 SELECT IMAGE FROM PHOTO 语句来检索前面的三个图像对象。 随着图像对象数量的增加,这些检索可能会非常大,从而导致数据存储区出现不必要的负载,从而影响性能。

如果我使用 pm.getObjectById() 选择宠物并分离 Pet 对象并将照片添加到分离的对象,然后使用 pm.makePersistent(pet) 将其附加回对象图,也会发生同样的事情。 FetchGroup如下

@PersistenceCapable(detachable="true")
@FetchGroup(name="detachPhotos", members={@Persistent(name="photos")})
public class Pet {
   ....
}

并使用 fetchgroup 分离宠物

public Pet getPet(Long id){
    PersistenceManager pm = pmf.getPersistenceManager();
    pm.getFetchPlan().addGroup("detachPhotos");
    Pet pet = pm.getObjectById(Pet.class, id);      
    return pm.detachCopy(pet);  
}

我的问题是如何避免对数据存储区中的对象图像进行这些不必要的重试。

另一个观察:如果我从另一个应用程序调用 petDao.storePhoto((long)0, photo) 或在 PetDao.storePhoto 方法中使用单独的 PMF 实例,那么 DataNucleus 将不要触发 SELECT 来检索图像对象。

【问题讨论】:

    标签: jdo datanucleus


    【解决方案1】:

    如果 1-N 关系可能变得很大,您可能需要考虑以关系方式映射它,即映射 Photo.pet,而不是映射 Pet.photos。这将阻止您在没有查询的情况下以 OO 方式从 Pet 导航到 Photo,但会阻止您关注的 SQL 语句。

    您的 storePhoto 将如下所示,并且不会获取 1-N。

    public void storePhoto(Photo photo) {
        // get PM and current tx
        try {
           tx.begin();
           pm.makePersistent(photo); // assuming pet was already set
           tx.commit();
        } catch (Exception e) {
           // rollback and close pm
        }
    }
    

    【讨论】:

    • 我给出了 PET/照片的例子只是为了便于理解。这个问题更多的是关于 JDO 优化和延迟加载,而不是对象建模。 1-to-N 构造是一种常用的关系,JDO/DataNucleus 通过在应用程序实际访问对象时延迟加载对象来很好地处理它。正如我在上次观察中提到的,当从不同的 PMF 发射照片对象时,DataNucleus 不会加载它们。所以,我更想知道为什么 DataNucleus 会在应用程序代码未访问照片对象(即照片对象)时获取它们。
    • Pet.addPhoto() 不可避免地会访问 pet.photos 列表添加一个,这将触发获取列表中的所有元素。我不知道使用列表映射有任何解决方法。
    • 是的,我同意,它必须获取列表的所有元素。因此,它将加载列表中的所有照片,但它必须仅检索 Photo.id 和 Photo.desc 字段(因为它们属于默认提取组)并延迟加载 Photo.image 字段(JDO 的延迟加载功能)直到图像被应用程序代码访问,因为它不属于默认提取组。当您检索 Pet 并一张一张访问图像时,Photo.image 字段的延迟加载完全符合预期。仅当您持久保存新 Pet 然后在同一会话(或 PMF)中调用 Pet.addPhoto() 时,才会出现急切加载图像的问题。
    • 我明白了。对不起,我错过了你特别关心的图像字段检索。我同意您的看法,默认情况下不会获取这些内容。很难说 DN 为什么要获取这些。它可能与二级缓存管理有关。您可以尝试将 datanucleus.cache.level2.type=none 设置为测试。
    【解决方案2】:

    DataNucleus Performance Tuning 中得到答案,如果应用不需要可达性,建议设置 datanucleus.persistenceByReachabilityAtCommit=false。将此设置为 false,可以解决图像检索问题,而不会对 Pet/Photo 产生任何其他副作用。

    引用DN doc

    DataNucleus 验证新持久化的对象是否可访问内存 提交时,如果不是,则将它们从数据库中删除。这个 进程镜像垃圾收集,其中未引用对象 被垃圾收集或从内存中删除。可达性是 昂贵,因为它遍历整个对象树并且可能需要 从数据库重新加载数据。如果您不需要可达性 应用程序,您应该禁用它。

    检查您的应用是否需要可达性,然后再将其设置为 false。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-10-05
      • 2015-08-13
      • 2020-02-11
      • 2013-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多