- Room + Time
- SQLite + date/time
- Back to the app
- Understanding migrations with Room
- Performing database migrations with the SQLite API always made me feel like I was defusing a bomb — as if I was one…
- Sorting the Room out
- SQLite date time functions
- How to use DateTime datatype in SQLite Using Room
- Date and Time Datatype in SQLite
- Using type converters
- 1.SQLite Query to select data between two dates
- Room: Хранение данных на Android для всех и каждого
- Использование Room
- Преимущества использования Room
- Самое большое ограничение в Room: взаимосвязи
- Стоит ли использовать Room?
Room + Time
If you’ve started using Room (and you should if you haven’t), there’s a high probability that you will need to store + retrieve some kind of date/time. Room does not provide any support for that out of the box, instead it provides the extensible @TypeConverter annotation, which allows you to provide mappings from arbitrary objects to types Room understands, and vice-versa.
The canonical example in the docs for that API is in fact for date/time:
I used this exact code in my app, and while it technically works, it has two big issues. The first is that it uses the Date class, which should be avoided in nearly all instances. The main issue with Date is the fact that it does not support timezones. At all.
The second issue is that it persists the value as a simple Long , which again can’t store any timezone information.
So let’s say we use the above converters to persist a Date instance to the database, and then later retrieve it. How do you know what timezone the original value is from? The simple answer is that you can’t know. The best you can do is to try and make sure that all Date instances use a common timezone such as UTC. While this allows you to compare different retrieved values against each other (i.e. for sorting), you can never find out the original time zone.
I decided to spend an hour attempting to fix the timezone issue in my app.
SQLite + date/time
The first thing I investigated was SQLite’s support for date and time values, and indeed it does support them. As you’re using Room, it controls which SQL data types your class values map to. For instance String will map to TEXT , Int to INTEGER , etc. But how do we tell Room to map our object date + time values? Well the simple answer is that we don’t need to.
SQLite is a loosely typed database system and stores all values as one of: NULL , INTEGER , TEXT , REAL or BLOB . You’ll notice that there is no special date or time type like you may find in other database systems. Instead they provides the following documentation on how to store date/time values:
SQLite does not have a storage class set aside for storing dates and/or times. Instead, the built-in Date And Time Functions of SQLite are capable of storing dates and times as TEXT, REAL, or INTEGER values
It is these date and time functions which will allow us to store high-fidelity date-time values with minimal/no accuracy loss, specifically using the TEXT type since that support ISO 8601 strings.
Thus we just need to save our values as specially formatted text which contains all of the information we need. We can then use the mentioned SQLite functions to convert our text to a date/time in SQL if needed. The only thing we need to do is make sure our code is using the correct format.
Back to the app
So we know that SQLite supports what we need, but we need to decide how we’re going to represent this in our app.
I’m using ThreeTen-BP in my app, which is a backport of the JDK 8 date and time library (JSR-310) but works on JDK 6+. This library supports timezones, so we’re going to use one of its classes to represent date + times in the app: OffsetDateTime. This class is an immutable representation of both a time and date within a specific offset from UTC/GMT.
So when we look at one of my entities, we now use OffsetDateTime instead of Date:
That’s the entities updated, but now we have to update our TypeConverters so that Room understands how to persist/restore the OffsetDateTime values:
Instead of our previous mapping of Date to/from Long , we’re now mapping OffsetDateTime to/from String .
The methods are pretty simple to look at: one formats a OffsetDateTime to a String, and the other parses a String into an OffsetDateTime. The key puzzle here is making sure that we use the correct String format. Thankfully ThreeTen-BP provides a compatible one for us as DateTimeFormatter. ISO_OFFSET_DATE_TIME .
You might not be using this library though so lets take a look at an example formatted string: 2013-10-07T17:23:19.540-04:00 . Hopefully you can see what date this represents: 7th October 2013, 17:23:19.540 UTC-4. As long as you format/parse to a string like that, SQLite will be able to understand it.
So at this point, we’re nearly done. If you run the app, with an appropriate database version increase + migration, you’ll see that everything should be working nicely.
For more information on migrations with Room, see Florina Muntenescu’s post:
Understanding migrations with Room
Performing database migrations with the SQLite API always made me feel like I was defusing a bomb — as if I was one…
Sorting the Room out
The one thing we haven’t yet fixed is querying on date columns in SQL. The previous Date/Long mapping had an implicit benefit in that numbers are extremely efficient to sort and query. Moving to a String somewhat breaks that though, so let’s fix it.
Say we previously had a query which return all users ordered by their join date. You would probably have had something like this:
Since joined_date was a number ( long , remember), SQLite would do a simple number comparison and return the results. If you run the same query with the new text implementation, you’ll probably notice that the results look the same, but are they?
Well the answer is yes, most of the time. With the text implementation, SQLite is doing a text sort rather than a number sort, which for the majority of cases will be correct. Lets look at some example data:
A simple left-to-right String sort works here, since all of the components of the string are in descending order (year, then month, then day, and so on). The issues comes with the last component of the string, the timezone offset. Lets tweak the data slightly and see what happens:
You can see that the timezone for the 3rd row has changed from UTC to UTC-2. This results in its joined time actually being 09:01:12 in UTC, thus it should actually be sorted as the 2nd row. The returned list contained the same order as before though. This is because we’re still using string ordering, which does not take the timezone into account.
SQLite date time functions
So how do we fix it? Remember those SQLite date/time functions? We just need to make sure we use them when interacting with any date/time columns in SQL. There are 5 functions which SQLite provides:
- date(. ) returns just the date.
- time(. ) returns just the time.
- datetime(. ) returns both the date and time.
- julianday(. ) returns the Julian Day.
- strftime(. ) returns a value formatted with your given format string. The first four can be thought of as variations of strftime with a pre-defined format.
Since we want to sort on both the date and time, we can use the datetime(. ) function. If we go back to our DAO, the query now becomes:
Easy enough right? After we’ve made this change we now get the correct semantic ordering:
And that was my hour* of work complete! We now support timezoned date/times in Room.
Источник
How to use DateTime datatype in SQLite Using Room
One of the most interesting and confusing data types that SQLite not supports is Date and Time. I see more questions in online public discussion forums about this type than any other. In this article, I shed light on some very confusing issues regarding select query using Date.
Date and Time Datatype in SQLite
SQLite does not have a storage class for storing dates and/or times. Instead, the built-in Date and Time Functions of SQLite are capable of storing dates and times as TEXT , REAL , or INTEGER values:
- TEXT as ISO8601 strings (“YYYY-MM-DD HH:MM:SS.SSS”).
- REAL as Julian day numbers, the number of days since noon in Greenwich on November 24, 4714 B.C. according to the proleptic Gregorian calendar.
- INTEGER as Unix Time, the number of seconds since 1970-01-01 00:00:00 UTC.
Applications can chose to store dates and times in any of these formats and freely convert between formats using the built-in date and time functions.
Using type converters
Sometimes, your app needs to use a custom data type, like DateTime whose value you would like to store in a single database column. To add this kind of support for custom types, you provide a TypeConverter , which converts a custom class to and from a known type that Room can persist. For example, if we want to persist instances of Date , we can write the following TypeConverter to store the equivalent Text in the database:
The preceding example defines 2 functions, one that converts a Date object to a String object and another that performs the inverse conversion, from String to Date . Next, you add the @TypeConverters annotation to the Field of class so that Room can use the converter that you’ve defined for each Row in the entity.
Note:You can also limit the @TypeConverters to different scopes, including individual entities, DAOs, and DAO methods.
1.SQLite Query to select data between two dates
I have a start_date and end_date.I want to get the list of dates in between these two dates. Put those two dates between single quotes like.
Источник
Room: Хранение данных на Android для всех и каждого
Room — это новый способ сохранить данные приложений в Android-приложении, представленный в этом году на Google I/O. Это часть новойAndroid Architecture, группа библиотек от Google, которые поддерживают уместную архитектуру приложений. Room предлагается в качестве альтернативы Realm, ORMLite, GreenDao и многим другим.
Room — это высокоуровневый интерфейс для низкоуровневых привязок SQLite, встроенных в Android, о которых вы можете узнать больше в документации. Он выполняет большую часть своей работы во время компиляции, создавая API-интерфейс поверх встроенного SQLite API, поэтому вам не нужно работать с Cursor или ContentResolver.
Использование Room
Во-первых, добавьте Room в свой проект. После этого вам нужно будет передать в Room, как выглядят ваши данные. Предположим, имеется простой класс модели, который выглядит следующим образом:
Чтобы рассказать Room о классе Person, добавляем аннотицию Entity к классу и @PrimaryKey к ключу:
Благодаря этим двум аннотациям Room теперь знает, как создать таблицу для хранения экземпляров Person.
Важная вещь, которую следует учитывать при настройке ваших моделей: каждое поле, которое хранится в базе данных, должно быть общедоступным или иметь геттер и сеттер в стандартном стиле Java Beans (например, getName () и setName (имя строки)).
В классе Person теперь есть вся информация, которая требуется Room для создания таблиц, но у вас нет способа фактически добавлять, запрашивать или удалять данные из базы данных. Вот почему вам нужно будет сделать объект доступа к данным (DAO). DAO предоставляет интерфейс в самой базе данных и занимается манипулированием хранимыми данными Person.
Вот простой интерфейс DAO для класса Person:
Первое, что нужно заметить, это то, что PersonDao — это интерфейс, а не класс. Другая интересная деталь — это инструкции SQL в аннотациях Query (). Операторы SQL говорят Room, какую информацию вы хотите получить из базы данных. Они также проверяются во время компиляции. Поэтому, если вы измените подпись метода List getAllPeopleWithFavoriteColor ( название цвета ) на List getAllPeopleWithFavoriteColor ( int color ), Room выдаст ошибку во время компиляции:
И если вы сделаете опечатку в выражении SQL, например, напишите favoriteColors ( множественное число ) вместо favoriteColor ( единственное число ), Room также выдаст ошибку компиляции:
Вы не можете получить экземпляр PersonDao, потому что это интерфейс. Чтобы иметь возможность использовать классы DAO, вам необходимо создать класс базы данных. За кулисами этот класс будет отвечать за ведение самой базы данных и предоставление экземпляров DAO.
Вы можете создать свой класс базы данных всего за пару строк:
Это лишь описание структуры базы данных, но сама база данных будет жить в одном файле. Чтобы получить экземпляр AppDatabase, сохраненный в файле с именем populus-database, вы должны написать:
Если вы хотите получить все данные обо всех Person, которые находятся в базе данных, вы могли бы написать:
Преимущества использования Room
В отличие от большинства ORM, Room использует обработчик аннотации для выполнения всей своей манеры сохранения данных. Это означает, что ни ваши классы приложений, ни классы моделей не должны ничего расширять в Room, в отличие от многих других ORM, включая Realm и SugarORM. Как вы видели при ошибках с аннотациями Query () выше, вы также получаете возможность проверки корректности SQL-запросов во время компиляции, что может сэкономить вам много хлопот.
Room также позволяет вам наблюдать за изменениями данных, интегрируя их как с API LiveData Архитектурных Компонентов, так и с RxJava 2. Это означает, что если у вас сложная схема, где изменения в базе данных должны появляться в нескольких местах вашего приложения, Room делает уведомления об изменениях. Это мощное дополнение может быть включено одной строкой. Все, что вам нужно сделать, это изменить тип возвращаемых значений.
Например, этот метод:
Самое большое ограничение в Room: взаимосвязи
Самым большим ограничением в Room является то, что он не будет обрабатывать отношения с другими типами сущностей для вас автоматически, как и другие ORM. Это означает, что если вы хотите отслеживать домашних животных:
То Room выдаст ошибку компиляци, так как не знает, как сохранить отношения между Person и Pet:
Ошибка при компиляции предлагает конвертер типов, который преобразует объекты в примитивы, которые могут быть непосредственно сохранены в SQL. Поскольку List нельзя свести к примитиву, вам нужно сделать что-то другое. Это отношения «один ко многим», где у одного Person может быть много Pet. Room не может моделировать такие отношения, но она может справиться с обратными отношениями — у каждого Pet есть один Person. Чтобы смоделировать это, удалите поле для Pet в Person и добавьте поле ownerId в класс Pet:
Это приведет к тому, что Room обеспечит ограничение внешнего ключа между объектами. Room не будет вызывать отношения «один-ко-многим» и «много-к-одному», но она дает вам инструменты для выражения этих отношений.
Чтобы получить всех домашних животных, принадлежащих конкретному человеку, вы можете использовать запрос, который находит всех домашних животных с данным идентификатором владельца. Например, вы можете добавить в свой DAO следующий метод:
Стоит ли использовать Room?
Если вы уже настроили сохранение данных в своем приложении и довольны им, то ничего не изменяйте. Каждая ORM и встроенная реализация SQLite будут продолжать работать так же, как и раньше. Room — это всего лишь еще один вариант сохранения данных.
Если вы используете SQLite или собираетесь использовать его, вы должны попробовать Room. Он обладает всеми возможностями, необходимыми для выполнения расширенных запросов, одновременно устраняя необходимость писать SQL-запросы для поддержки базы данных самостоятельно.
Источник