Комната: заменить курсор SQLite в Дао?

У меня есть JobIntentService, который повторно устанавливает ожидающие уведомления о тревоге из базы данных SQLite. Он использует запрос и курсор для получения дат уведомлений из 4 разных столбцов в базе данных. Я конвертирую в базу данных Room и не знаю, как преобразовать курсор в метод Dao. Нужно ли мне использовать @Transaction, если я получаю уведомления из нескольких столбцов в базе данных? Буду признателен за любые идеи или мысли о том, как построить в комнате.

Service

public class Service extends JobIntentService {

static final int JOB_ID = 9;

public static void enqueueWork(Context context, Intent work) {
    enqueueWork(context, RebootService.class, JOB_ID, work);
} 

@Override
protected void onHandleWork(@NonNull Intent intent) {

    AlarmManager alarmManager1;
    Intent brIntent1;
    PendingIntent pendingIntent1;

    SQLiteDB sqLiteDB = SQLiteDB.getInstance(this);
    Calendar cal1 = Calendar.getInstance();

    Cursor cursor = sqLiteDB.resetNotifications(); 

     try {
          if (cursor.getCount() > 0) { 
              cursor.moveToFirst(); 

              int dueDatentimeColumnIndex = cursor.getColumnIndex(ItemContract.ItemEntry.COLUMN_DUEDATENTIME);
              int notifColumnIndex1 = cursor.getColumnIndex(ItemContract.ItemEntry.COLUMN_NOTIFTIME);
              int notif2ColumnIndex2 = cursor.getColumnIndex(ItemContract.ItemEntry.COLUMN_NOTIFTIME2);
              int randColumnIndex1 = cursor.getColumnIndex(ItemContract.ItemEntry.COLUMN_RANDINT);

              while (!cursor.isAfterLast()) {  

                  do {

                      long notifTime1 = cursor.getLong(notifColumnIndex1);
                      int randInt1 = cursor.getInt(randColumnIndex1);
                      cal1.setTime(new Date(notifTime1));

                      // Set up a system AlarmManager to fire a future alarm that sends a Notification
                      // even if the app is in the background or closed.
                      alarmManager1 = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

                      if (cal1.getTimeInMillis() > System.currentTimeMillis()) {                                            
                          brIntent1 = new Intent(this, AlarmReceiver.class);
                       brIntent1.setAction("24Hour");

                       pendingIntent1 = PendingIntent.getBroadcast(this, randInt1, brIntent1,
                                    PendingIntent.FLAG_ONE_SHOT);

                      if (alarmManager1 != null && notifTime1 != -1) {
                          alarmManager1.set(AlarmManager.RTC_WAKEUP, cal1.getTimeInMillis(), pendingIntent1);
                      }
...       
}

SQLiteDB.java

...
public Cursor resetNotifications() {

   SQLiteDatabase db = getReadableDatabase();

   String[] columns = new String[]{
                ItemContract.ItemEntry.COLUMN_NOTIFTIME,
                ItemContract.ItemEntry.COLUMN_NOTIFTIME2,
                ItemContract.ItemEntry.COLUMN_DUEDATENTIME,
                ItemContract.ItemEntry.COLUMN_RANDINT};

        return db.query(
                TABLE_NAME, 
                columns, // The columns to return
                null,      
                null,   
                null,      
                null,       
                null       
        ); 
}

This is the code I came up as a replacement:

public class RebootService extends JobIntentService {

// Unique job ID for this service
static final int JOB_ID = 10000;

// Convenience method for enqueueing work to this service.
public static void enqueueWork(Context context, Intent work) {
    enqueueWork(context, RebootService.class, JOB_ID, work);
}

private QuickcardRepository reposit1;

@Override
protected void onHandleWork(@NonNull Intent intent) {

    reposit1 = new QuickcardRepository(getApplication());

    Bundle extras = intent.getExtras(); // Returns the Intent *that started this Service.*
    if (extras != null) {

        String classname = extras.getString("TAG");

        if (classname != null && classname.equals("bootCompleted")) {

            AlarmManager alarmManager1;
            Intent brIntent1, brIntent2, brIntent3;
            PendingIntent pendingIntent1, pendingIntent2, pendingIntent3;

            Calendar cal1 = Calendar.getInstance();
            Calendar cal2 = Calendar.getInstance();
            Calendar cal3 = Calendar.getInstance();

            List<Quickcard> resetNotificationsList = reposit1.getNotifications();
            // Cycle through the Room database rows to get the Notifications data
            for (Quickcard quickcard: resetNotificationsList) {
                 // Quickcards without a Due date get a Due date in the database of -1.
                 // Therefore, only cycle through and get data for those quickcards that have
                //  a Due data and therefore have Notifications (reminders) where the actual
                //  "Due date" is != -1.
                 if(quickcard.getDuedatentime() != -1) {

                     // Set up the 24-Hour calendar object.
                     long notifTime1 = quickcard.getNotiftime();
                     int randInt1 = quickcard.getRandint();
                     cal1.setTime(new Date(notifTime1));

                     // Set up a system AlarmManager to fire a future alarm that sends a Notification
                     // even if the app is in the background or closed.  Have to add "context" here.
                     alarmManager1 = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

                     // If the stated alarm trigger time is in the past, the alarm will be triggered immediately.
                     // So only set Alarms to fire Notifications for Duedates in the future
                     // (meaning > than the current System time).  Ignore those in the past.
                     if (cal1.getTimeInMillis() > System.currentTimeMillis()) {
                         // For the 24Hour Notifications.
                         // Set up a PendingIntent that will perform broadcast to the BroadcastReceiver.
                         brIntent1 = new Intent(this, AlarmReceiver.class);
                         brIntent1.setAction("24Hour");
                         // Need to use FLAG_ONE_SHOT on the PendingIntent, not FLAG_UPDATE_CURRENT.
                         // A random int is used to be able to set multiple alarms and then to be able to
                         // delete them later (if the user for ex., deletes the quickCards Duedate) using
                         // the same random int.
                         pendingIntent1 = PendingIntent.getBroadcast(this, randInt1, brIntent1,
                                 PendingIntent.FLAG_ONE_SHOT);

                         // Alarms have 3 properties below after "set(...)":
                         // 1) Alarm type:  RTC_WAKEUP type is chosen here to wake the device from sleep.
                         // 2) Trigger time: in this case, 24 hours before the Duedate/Duetime is reached.
                         // 3) Pending Intent:  A future Intent that is sent when the trigger time is reached.
                         int SDK_INT1 = Build.VERSION.SDK_INT;
                         if (SDK_INT1 >= Build.VERSION_CODES.M) {
                             // Wakes up the device in Doze Mode for API Level 23 and higher.
                             // The "... != -1" test only sets up pendingIntents for quickCards that have
                             // a Notification.  quickCards with no Duedate & Duetime are bypassed.
                             if (alarmManager1 != null && notifTime1 != -1) {
                                 alarmManager1.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal1.getTimeInMillis(),
                                         pendingIntent1);
                             }
                         } else if (SDK_INT1 >= Build.VERSION_CODES.KITKAT) {
                             // Wakes up the device in Idle Mode for API Level 19 to 22.
                             // The "... != -1" test only sets up pendingIntents for quickCards that have
                             // a Notification.  quickCards with no Duedate & Duetime are bypassed.
                             if (alarmManager1 != null && notifTime1 != -1) {
                                 alarmManager1.setExact(AlarmManager.RTC_WAKEUP, cal1.getTimeInMillis(), pendingIntent1);
                             }
                         } else {
                             // Old APIs Level 18 and below.
                             // The "... != -1" test only sets up pendingIntents for quickCards that have
                             // a Notification.  quickCards with no Duedate & Duetime are bypassed.
                             if (alarmManager1 != null && notifTime1 != -1) {
                                 alarmManager1.set(AlarmManager.RTC_WAKEUP, cal1.getTimeInMillis(), pendingIntent1);
                             }
                         }
                     }

person AJW    schedule 27.05.2019    source источник


Ответы (1)


Я считаю, что @Transaction оборачивает код в транзакцию. Это уже сделано для всех запросов, кроме @Query (если @Query не является запросом на обновление / удаление (если это запрос на обновление или удаление, он заключен в транзакцию)).

Я считаю, что вопрос о том, следует ли заключать запрос SELECT в транзакцию (@Transaction @Query......), заключается в использовании @Relation. Если это так, то списки связанных / связанных элементов / объектов запускаются как отдельные запросы, и, таким образом, их выполнение в транзакции гарантирует согласованность данных. В противном случае существует вероятность того, что базовые данные могут быть изменены другими транзакциями, и, следовательно, результирующие данные могут быть несовместимыми.

Сказать, что использование @Transaction там, где он не требуется, вряд ли повлияет на него, и даже может оказать положительное влияние, если оно будет закодировано там, где оно может случайно не закодироваться.

Конечно, вы всегда можете вернуть Cursor, используя Room. Возможно, вы захотите взглянуть на Связывание таблиц с помощью Room база данных в Android Studio, в которой есть несколько примеров.

Основываясь на вашем коде, в первую очередь, у вас есть ItemContract с подклассом ItemEntry, тогда Entity для ItemEntry может быть в ItemEntry.java в соответствии с: -

@Entity
public class ItemEntry {

    @PrimaryKey(autoGenerate = true)
    private long id;
    @ColumnInfo(name = COLUMN_NOTIFTIME)
    private long notiftime;
    @ColumnInfo(name = COLUMN_NOTIFTIME2)
    private long notiftime2;
    @ColumnInfo(name = COLUMN_DUEDATENTIME)
    private long duedatentime;
    @ColumnInfo(name = COLUMN_RANDINT)
    public int randint;

    public ItemEntry(){

    }

    @Ignore
    public ItemEntry(long notiftime, long notiftime2, long duedatentime, int randint) {
        this.notiftime = notiftime;
        this.notiftime2 = notiftime2;
        this.duedatentime = duedatentime;
        this.randint = randint;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public long getNotiftime() {
        return notiftime;
    }

    public void setNotiftime(long notiftime) {
        this.notiftime = notiftime;
    }

    public long getNotiftime2() {
        return notiftime2;
    }

    public void setNotiftime2(long notiftime2) {
        this.notiftime2 = notiftime2;
    }

    public long getDuedatentime() {
        return duedatentime;
    }

    public void setDuedatentime(long duedatentime) {
        this.duedatentime = duedatentime;
    }

    public int getRandint() {
        return randint;
    }

    public void setRandint(int randint) {
        this.randint = randint;
    }
} 

Наряду с интерфейсом ItemEntryDao.java как: -

@Dao
interface ItemEntryDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long[] insertItemEntries(ItemEntry... itemEntries);
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long insertItemEntry(ItemEntry itemEntry);
    @Update(onConflict = OnConflictStrategy.IGNORE)
    int updateItemEnrties(ItemEntry... itemEntries);
    @Update(onConflict = OnConflictStrategy.IGNORE)
    int updateItemEntry(ItemEntry itemEntry);
    @Delete
    int deleteItemEntries(ItemEntry... itemEntries);
    @Delete
    int deleteItemEntry(ItemEntry itemEntry);
    @Query("SELECT * FROM ItemEntry")
    List<ItemEntry> resetNotifications();
}
  • @Query эквивалентен Cursor, НО возвращает список объектов ItemEntry.

Вышеупомянутое можно использовать, например (это в основном реплицирует ваш код, но выводит извлеченные данные в журнал), например: -

public void onHandleWork() {

    ItemEntry ie = new ItemEntry();
    ie.setNotiftime(100);
    ie.setNotiftime2(200);
    ie.setDuedatentime(500000);
    ie.setRandint(567);
    mDB.getItemEntryDao().insertItemEntry(ie);
    List<ItemEntry> mylist = mDB.getItemEntryDao().resetNotifications();
    for (ItemEntry itementry: mylist) {
        Log.d("ITEMENTRY",
                "\n\tnotiftime= " + String.valueOf(itementry.getNotiftime()) +
                        "\n\tnotiftime2= " + String.valueOf(itementry.getNotiftime2()) +
                        "\n\tduedatetime= " + String.valueOf(itementry.getDuedatentime()) +
                        "\n\trandint= " + String.valueOf(itementry.getRandint())

        );
    }
}
  • mDB - это построенный объект (т.е. экземпляр класса @Database)

Это приведет к (для первого запуска): -

05-28 14:31:14.587 7211-7211/aso.so56326640 D/ITEMENTRY:  notiftime= 100
      notiftime2= 200
      duedatetime= 500000
      randint= 567
person MikeT    schedule 28.05.2019
comment
Я понимаю. Есть мысли о том, как заменить приведенный выше код курсора методом Dao? - person AJW; 28.05.2019
comment
@AJW Добавил ссылку с примерами. - person MikeT; 28.05.2019
comment
@AJW добавил пример, более конкретный / основанный на вашем коде. - person MikeT; 28.05.2019
comment
Таким образом, в курсоре moveToNext () выполняет итерацию по всей базе данных (по всем строкам). В приведенном выше ответе mDB.getItemEntryDao () ... выполняет итерацию по всей базе данных, чтобы получить данные для 4 столбцов и всех строк? - person AJW; 29.05.2019
comment
Приносим извинения, while () в моем коде выше - это цикл, который выполняет итерацию по курсору базы данных, а не moveToNext (). - person AJW; 29.05.2019
comment
@AJW mDB.getItemEntryDao(), получает соответствующий DAO (объект доступа к данным, то есть класс с запросами / вставками / удалениями / обновлениями). Это List<ItemEntry> mylist = mDB.getItemEntryDao().resetNotifications();, который возвращает список объектов ItemEntry, что эквивалентно получению Cursor. Затем цикл for выполняет итерацию по возвращаемым объектам (если их нет, то тело цикла не вводится). Базовый цикл по базе данных выполняется по комнатам. - person MikeT; 29.05.2019
comment
Понял. У меня есть много дополнительных столбцов в базе данных. Поэтому я не могу использовать @Query (SELECT * FROM ItemEntry). Как мне написать @Query, чтобы просто ВЫБРАТЬ данные базы данных только для 4 указанных столбцов? - person AJW; 29.05.2019
comment
@AJW, почему ты не можешь? Возвращенные объекты ItemEntry будут содержать данные для 4 столбцов плюс другие столбцы. Я не знаю ни одного правила, которое гласит, что вы должны использовать все значения из возвращаемого объекта. Однако получение произвольных данных объясняется здесь android.arch.persistence.room.Query. - person MikeT; 29.05.2019
comment
Извините, я должен был сказать, что предпочел бы не возвращать все данные столбцов, на самом деле из-за управления памятью. Я хочу просто получить данные для 4 столбцов, чтобы повторно установить уведомления о тревогах приложения. - person AJW; 30.05.2019
comment
@AJW, тогда просто следуйте и делайте так, как говорится в ссылке, как в предыдущем комментарии. Если у вас есть проблемы, это будет другой вопрос. Я считаю, что ответил на исходный вопрос, поэтому я считаю, что вы должны отметить это и, возможно, также проголосовать за. - person MikeT; 30.05.2019
comment
Я отвлекся на другие проблемы и теперь наконец возвращаюсь, чтобы решить эту проблему. Ответ принят и одобрен (лучше поздно, чем никогда!). Ваше здоровье. - person AJW; 29.10.2019
comment
Я попробовал рекомендованный вами код (см. Выше, где был добавлен весь новый код), и, к сожалению, уведомления не запускаются после перезагрузки устройства. Есть мысли или идеи? - person AJW; 31.10.2019