如何创建一个 TypeConverter 将 LocalDate 转换为 Room 可以 understand/save 的格式?
How to create a TypeConverter that converts LocalDate to format that Room can understand/save?
我正在使用 Room
。我需要关于如何将 LocalDate
从 java.time
转换为一种格式的指导(可能是 Long
、TimeStamp
、sql.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
转换成什么。是 Long
、TimeStamp
、sql.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()
}
}
我正在使用 Room
。我需要关于如何将 LocalDate
从 java.time
转换为一种格式的指导(可能是 Long
、TimeStamp
、sql.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
转换成什么。是 Long
、TimeStamp
、sql.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()
}
}