如何创建一个 TypeConverter 将 LocalDate 转换为 Room 可以 understand/save 的格式?

How to create a TypeConverter that converts LocalDate to format that Room can understand/save?

我正在使用 Room。我需要关于如何将 LocalDatejava.time 转换为一种格式的指导(可能是 LongTimeStampsql.Date 或者我什至不知道还有什么)这样我就可以将日期保存到数据库中。

我有一个图书实体:

@Entity
data class Book(
    @ColumnInfo(name = "date") val date: LocalDate,  
    @ColumnInfo(name = "count") val count: Int,
    @PrimaryKey(autoGenerate = true)
    val id: Int? = null
)

我也创建了 Book DAO:

@Dao
interface BookDao {
    @Query("SELECT * FROM book WHERE :date >= book.date")
    fun getBooks(date: LocalDate): List<Book>
}

现在,我不确定如何创建将 LocalDate 转换为 . . . (同样,我什至不知道我应该将我的 LocalDate 转换成什么。是 LongTimeStampsql.Date 还是其他任何东西)。

认为 我应该将 LocalDate 转换为 sql.Date 以便 Room 可以保存它。所以,我这样创建了我的转换器:

Converters.kt

class Converters {
    @TypeConverter
    fun convertLocalDateToSqlDate(localDate: LocalDate?): Date? {
        return localDate?.let { Date.valueOf(localDate.toString()) }
    }

    @TypeConverter
    fun convertSqlDateToLocalDate(sqlDate: Date?): LocalDate? {
        val defaultZoneId = systemDefault()
        val instant = sqlDate?.toInstant()
        return instant?.atZone(defaultZoneId)?.toLocalDate()
    }
}

但是,我收到错误消息:

error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
    private final java.time.LocalDate date = null;

您遇到的问题是 TypeConverter 应该转换 from/to Room 支持的一组特定类型 insertion/extraction 数据。

一些比较常见的是 Int、Long Byte、Byte[]、Float、Double、Decimal、String、BigInt、BigDecimal。

  • 可能还有其他类型,但可以使用的类型有限

消息说请转换您的 LocalDate 或 Date,因为它无法处理它们。

我建议将日期存储为 unix 日期(除非你想要微秒精度,因为你可以用毫秒存储时间(这在 sqlite 中使用 date/time 函数时可能有点尴尬,可能需要转换) ) 即作为 Long 或 Int.

这样做你:

  • 将最小化用于存储值的存储量,
  • 将可以利用 SQLite Date and Time functions,
  • 可以轻松地将它们提取为非常灵活的格式,
  • 您可以将它们原样提取出来,然后使用class函数进行格式转换,
  • 以上内容不需要 TypeConverters。

下面是一个展示部分功能的示例,将它们存储为 unix 时间戳

首先是使用默认日期的图书实体版本:-

@Entity
data class Book(
    @ColumnInfo(name = "date", defaultValue = "(strftime('%s','now','localtime'))")
    val date: Long? = null,
    @ColumnInfo(name = "count")
    val count: Int,
    @PrimaryKey(autoGenerate = true)
    val id: Int? = null
)
  • 有关上述内容的说明,请参阅 SQLITE 日期和时间函数。简而言之,上面的内容允许您插入一行并将日期和时间设置为当前日期时间
  • 请参阅 insertBook 函数以仅使用提供的计数进行插入。

伴随书实体书道:-

@Dao
interface BookDao {
    @Insert /* insert via a book object */
    fun insert(book: Book): Long
    @Query("INSERT INTO BOOK (count) VALUES(:count)")

    /* insert supplying just the count id and date will be generated */
    fun insertBook(count: Long): Long
    @Query("SELECT * FROM book")

    /* get all the books  as they are stored into a list of book objects*/
    fun getAllFromBook(): List<Book>

    /* get the dates as a list of dates in yyyy-mm-dd format using the SQLite date function */
    @Query("SELECT date(date, 'unixepoch', 'localtime') AS mydateAsDate FROM book")
    fun getDatesFromBook(): List<String>

    /* get the date + 7 days */
    @Query("SELECT date(date,'unixepoch','localtime','+7 days') FROM book")
    fun getDatesFromBook1WeekLater(): List<String>
}
  • 查看评论(以及 activity 和结果)

activity(非常标准的 BookDatabase class,因此不包括在内)

class MainActivity : AppCompatActivity() {
    lateinit var db: BookDatabase
    lateinit var dao: BookDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = BookDatabase.getInstance(this)
        dao = db.getBookDao()

        /* Insert two rows */
        dao.insert(Book(System.currentTimeMillis() / 1000,10)) /* don't want millisecs */
        dao.insertBook(20) /* use the date columns default value (similar to the above) */

        /* get and log the date objects */
        for(b: Book in dao.getAllFromBook()) {
            Log.d("BOOKINFO_BOOK", " Date = ${b.date} Count = ${b.count} ID = ${b.id}")
        }

        /* get the dates in yyy-mm-dd format */
        for(s: String in dao.getDatesFromBook()) {
            Log.d("BOOKINFO_DATE",s)
        }

        /* get the dates but with a week added on */
        for(s: String in dao.getDatesFromBook1WeekLater()) {
            Log.d("BOOKINFO_1WEEKLATER",s)
        }
    }
}

运行之后的日志:-

2021-04-16 14:28:50.225 D/BOOKINFO_BOOK:  Date = 1618547330 Count = 10 ID = 1
2021-04-16 14:28:50.225 D/BOOKINFO_BOOK:  Date = 1618583330 Count = 20 ID = 2
2021-04-16 14:28:50.226 D/BOOKINFO_DATE: 2021-04-16
2021-04-16 14:28:50.227 D/BOOKINFO_DATE: 2021-04-17
2021-04-16 14:28:50.228 D/BOOKINFO_1WEEKLATER: 2021-04-23
2021-04-16 14:28:50.228 D/BOOKINFO_1WEEKLATER: 2021-04-24
  • (注意是本地时间的调整(虽然用错了所以往前跳))
  • Book 对象和数据库中存储的前 2 行数据
  • 接下来 2 行日期由 SQLite Date/Time 函数
  • 转换为 YYYY-MM-DD 格式
  • 最后 2 行存储日期后一周的日期转换为 YYYY-MM-DD

根据数据库检查器的数据库:-

如果你想使用转换器,你可以按照文档的方式进行。在您的示例中,您尝试转换为 SQLdate,但 Android 建议转换为 Long。

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}

然后将@TypeConverters注解添加到AppDatabase。

对于 LocalDate,它应该是这样的:

class Converters {
    @TypeConverter
    fun fromTimestamp(value: Long?): LocalDate? {
        return value?.let { LocalDate.ofEpochDay(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: LocalDate?): Long? {
        val zoneId: ZoneId = ZoneId.systemDefault()
        return date?.atStartOfDay(zoneId)?.toEpochSecond()
    }
}

source