【问题标题】:Is there a way to override Android Room Entities Constructor?有没有办法覆盖 Android Room Entities 构造函数?
【发布时间】:2019-07-24 16:55:14
【问题描述】:

我正在尝试覆盖 Android Room 实体对象的创建。

我想在从数据库实例化对象时初始化其他属性。我关注了Android Room Entity Documentation

我正在使用 Room 版本:room-runtime:2.1.0

我尝试在 setter 和构造函数中记录消息,但没有消息出现在我的 LogCat 中。

@Entity
// EDIT AFTER SOLUTION. You have to do this in the class that room is using to query the data, in my case it was a viewModel.
public class Client /* or ImportantInformationsClientViewModel */ {

    @SerializedName("azEMail")
    private String azEMail;

    @SerializedName("azFirstName")
    private String azFirstName;

    @SerializedName("azMobile")
    private String azMobile;

    @Ignore
    private MyMobileObject;

    public Client(String azEMail, String azFirstName, String azMobile) {
        Log.d("CLIENT_LOG", "Constructor is instanciated"); // Never logged
        this.azEMail = azEMail;
        this.azFirstName = azFirstName;
        this.azMobile = azMobile;
        // I would like to instantiate MyMobileObject each time this constructor (or the setter) is called
    }

    public String getAzEMail() {
        return azEMail;
    }

    public void setAzEMail(String azEMail) {
        Log.d("CLIENT_LOG", "Setter is called"); // Never Logged
        this.azEMail = azEMail;
    }

    public String getAzFirstName() {
        return azFirstName;
    }

    public void setAzFirstName(String azFirstName) {
        this.azFirstName = azFirstName;
    }

    public String getAzMobile() {
        return azMobile;
    }

    public void setAzMobile(String azMobile) {
        this.azMobile = azMobile;
        // I would like to instantiate MyMobileObject each time this setter (or the constructor) is called
    }

    public String getAzName() {
        return azName;
    }

    public void setAzName(String azName) {
        this.azName = azName;
    }

    public void setupObject() {
        // One ugly way to fix the problem is to call this method when my object is created. I want to avoid this. 
    }
}

我发现解决此问题的一种方法是在对象中创建一个setupObject 方法,并在我从查询中获得对象后调用此方法。它确实有效,但这有点难看,而且它毫无意义地增加了更多代码和复杂性。我试图避免这种情况。

是否可以添加将在 android-room 创建对象时调用的特定代码?比如 AzMobile 设置器中的例子?

房间如何实例化对象呢?这些属性是私有的,访问它的唯一方法是通过在 LogCat 中似乎没有调用的设置器。

回答后编辑

房间的棘手之处在于了解我们实体的实现是如何工作的。

我的实体是一个客户端对象,但是当我从数据库中进行查询时,我正在使用 ViewModel 进行查询(类似于 ImportInformationsClientViewModel) 我认为,由于 Room 只知道客户端实体,它会将我的 ViewModel 包装在实体中并从实体神奇地构建它(这不是那么愚蠢..这对我来说很有意义..)

在检查了我的 android-room 生成的 DAO 实现 (ScheduleDao_Impl) 后,我看到这个房间实际上是在直接构建 ViewModel 对象。我只是在 ViewModel 中移动了我的属性和功能,一切正常。

如果我必须列出要知道的重要事项:

  • android-room 仅使用@Entity 构建SQLite 数据库中对象的模型,不使用@Entity 构建查询对象

  • android-room会在你构建应用的时候生成一个YourDao_Impl.java对象,你可以通过CTRL + MAJ + F访问它

  • android-room 需要 ctor 或 setter 或两者(它只需要访问所有属性)

  • 花几个小时检查您的所有ApplicationDatabase_Impl 文件将帮助您了解 android-room 的工作原理以及所有内容是如何组合在一起的。

【问题讨论】:

  • FWIW,同时添加您正在使用的 Room 版本。

标签: java android android-room


【解决方案1】:

更新(澄清后)

我很惊讶你没有看到这些电话。因为通过检查生成的 DAO,我可以看到 ROOM 在做什么。它通过 SQLite 进行查询,并使用 Cursor 在结果中移动,调用 CTOR。

DAO 是在编译时生成的,因此在您构建项目后,如果您在 macOS(或您的 Android 工作室用于查找的任何快捷方式)中,请点击“ctrl-shift-F”或“cmd”并尝试找到你的 DAO 的名称。你会看到YourDaoYourDao_Impl() -> 这是自动生成的。 :) 打开那个。

这是我的一个 DAO 实现的简化复制/粘贴:

“模型”是RealTimeData。 Dao 的方法是loadAll(),它显然返回一个List<RealTimeData>

这是方法(删除了最不相关的东西):我添加了内联的 cmets。

  @Override
  public List<RealTimeData> loadAll() {
    /// PREPARE THE QUERY, SQL, AND CURSOR.
    final String _sql = "SELECT * FROM realtime_data";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    final Cursor _cursor = __db.query(_statement);
    try {
      // OBTAIN THE COLUMN NAMES FROM THE TABLE DEFINITION
      final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
      final int _cursorIndexOfJsonData = _cursor.getColumnIndexOrThrow("json_data");
      final int _cursorIndexOfIsSent = _cursor.getColumnIndexOrThrow("is_sent");
      final int _cursorIndexOfDeviceId = _cursor.getColumnIndexOrThrow("device_id");
      final int _cursorIndexOfDateCreated = _cursor.getColumnIndexOrThrow("date_created");

      // THIS WILL STORE THE RESULTS
      final List<RealTimeData> _result = new ArrayList<RealTimeData>(_cursor.getCount());

      // ITERATE IT, CREATE A "RealTimeData" AND POPULATE IT.
      while(_cursor.moveToNext()) {
        final RealTimeData _item;
        final String _tmpId;
        _tmpId = _cursor.getString(_cursorIndexOfId);
        final String _tmpJsonData;
        _tmpJsonData = _cursor.getString(_cursorIndexOfJsonData);
        final Date _tmpDateCreated;
        final Long _tmp;
        // SOME THINGS NEED EXTRA CHECKS, THIS IS A DATE FIELD, STORED AS "long", SO NULL MUST BE CHECKED OR THE DATE CONVERTER WOULD THROW NPE
        if (_cursor.isNull(_cursorIndexOfDateCreated)) {
          _tmp = null;
        } else {
          _tmp = _cursor.getLong(_cursorIndexOfDateCreated);
        }
        // IT'S A DATE, SO CALL THE DATE CONVERTER (supplied via the @TypeConverter() annotation)
        _tmpDateCreated = DateConverter.toDate(_tmp);


        // BAM: INVOKE THE CTOR
        _item = new RealTimeData(_tmpId,_tmpJsonData,_tmpDateCreated);

        // NOW USE SETTERS FOR THE "OTHERS" 
        final boolean _tmpIsSent;
        final int _tmp_1;
        _tmp_1 = _cursor.getInt(_cursorIndexOfIsSent);
        _tmpIsSent = _tmp_1 != 0;
        _item.setSent(_tmpIsSent);
        final String _tmpDeviceId;
        _tmpDeviceId = _cursor.getString(_cursorIndexOfDeviceId);
        _item.setDeviceId(_tmpDeviceId);

         // AND ADD IT TO THE RESULTS...
        _result.add(_item);
      }

      // YOU GET THIS ONE :p
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }

“本例中的模型”看起来完全像这样:

Entity(tableName = "realtime_data")
public class RealTimeData {

    @PrimaryKey
    @NonNull
    private String id;

    @ColumnInfo(name = "json_data")
    private String jsonData;

    @ColumnInfo(name = "is_sent")
    private boolean isSent;

    @ColumnInfo(name = "device_id")
    private String deviceId;

    @ColumnInfo(name = "date_created")
    private Date dateCreated;

    @Ignore
    RealTimeData(@NonNull final String jsonData, @NonNull final Date dateCreated) {
        id = UUID.randomUUID().toString();
        this.jsonData = jsonData;
        this.dateCreated = dateCreated;
    }

    RealTimeData(@Nonnull final String id, final String jsonData, final Date dateCreated) {
        this.id = id;
        this.jsonData = jsonData;
        this.dateCreated = dateCreated;
    }

    String getJsonData() {
        return jsonData;
    }

    @Nonnull
    public String getId() {
        return id;
    }

    public boolean isSent() {
        return isSent;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public Date getDateCreated() {
        return dateCreated;
    }

    public void setSent(final boolean sent) {
        isSent = sent;
    }

    public void setDeviceId(final String deviceId) {
        this.deviceId = deviceId;
    }
}

所以你的意思是当 ROOM 实例化这个时,你的 ctor 没有被调用?

更新结束

对于什么是有价值的,您可以拥有额外的构造函数(前提是它们不会影响空的公共构造函数和/或占用所有字段的构造函数)。添加 @Ignore 属性。

例如:(为了保持一致性,我从他的答案中窃取了 Hardik 的样本)

@Entity
 public class User {
   @PrimaryKey
   private final int uid;
   private String name;
   @ColumnInfo(name = "last_name")
   private String lastName;

   public User(int uid) {
       this.uid = uid;
   }
   public String getLastName() {
       return lastName;
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
   }

   @Ignore
   User(String firstName, String lastName) {
      this.lastName = lastname;
      this.name = firstName;
   }
 }

这可行,但请记住,如果您不使用“自动生成”主键,则需要先为该字段分配一个,然后 Room 才会接受它以进行插入或类似操作。

这是你想要做的吗?

【讨论】:

  • 不,我认为在我的帖子中已经很清楚了,但我会编辑。我正在尝试在 android-room 构造函数或设置器中添加一些东西。每次我想进行查询时,我都想使用 android-room 用来在里面添加一些东西的构造函数/设置器。我添加了注释代码来解释我想在构造函数/设置器中做什么
  • 好的,有道理。您是否查看过为您的班级生成的 DAO?我会更新我的答案。
  • 不,我没有看生成的 Dao,我实际上不知道它在哪里,它是如何工作的,以及它是做什么的。这可能是我回答的关键。但是,如果您谈论我为进行查询而创建的 DAO.. 它确实存在并且可以工作(但我认为您不是在谈论这个)
  • 有趣。我看了我的 Dao_impl。我的实体比我在问题中显示的要大得多(大约 40 个属性..)我正在使用 ViewModels 只获取我感兴趣的数据。由于我的数据建模是在我的 @Entity 对象中完成的,我想@ 987654331@ 会先将其包裹在@Entity 对象中,然后再包裹在视图模型中,但似乎它只调用了ViewModel。这实际上很有意义。我将编辑我的答案以阐明 android-room 的工作原理,并在明天接受您的答案。谢谢!
  • 好吧,这可能是有道理的,因为这个项目仍在使用 Room 1.1.1(AndroidX 之前)版本并且它没有使用 LiveData,所以这可能会使事情变得更加复杂。查看 Room 生成代码的“复杂性”以了解幕后真正发生的事情仍然是一个好主意。祝您狩猎顺利,不要犹豫添加评论或新问题;也许拥有更多 Room XP 的人可以插话并添加一些我不知道的东西。 o/(如果您尝试使用具有几个字段的简单实体,可能会更容易......)
【解决方案2】:

每个实体必须要么有一个无参数构造函数,要么有一个参数匹配字段的构造函数(基于类型和名称)。构造函数不必接收所有字段作为参数,但如果字段未传递给构造函数,则它应该是公共的或具有公共设置器。如果匹配的构造函数可用,Room 将始终使用它。

例如

@Entity
 public class User {
   @PrimaryKey
   private final int uid;
   private String name;
   @ColumnInfo(name = "last_name")
   private String lastName;

   public User(int uid) {
       this.uid = uid;
   }
   public String getLastName() {
       return lastName;
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
   }
 }

【讨论】:

  • 是的,我明白这一点。但是我的设置器或构造器日志如何以及为什么从未被调用?
猜你喜欢
  • 2010-09-10
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-01
相关资源
最近更新 更多