Для новичков стоит сделать отступление и рассказать что такое ORM вообще. ORM — object-ralational mapping. Объектно-реляционное отображение означает, что программисту гораздо удобнее оперировать с объектами, которые он использует в своём приложении, нежели чем с таблицами, в которых хранятся данные в реляционных базах данных. Для связи реляционных СУБД с объектной моделью приложения используются ORM-технологии. Так для решения задач объектно-реляционного отображения в Android используют один из сторонних фреймворков. GreenDAO и ORMLite — являются библиотеками с открытым кодом.
GreenDAO
Фреймворк от немецких разработчиков, который они используют в своих приложениях.
Преимущества:
Не использует отражения (Reflection) для работы
Максимальная производительность для Android ORM
Минимальное использование памяти
Маленький размер библиотеки, не оказывает большого влияния на Android проект.
Недостатки:
- Отсутствует механизм для переноса данных при обновлении схемы.
- Невозможность аннотирования классов, а необходимость описания алгоритма построения новых сущностей.
ORMLite
Популярный фреймворк для Java, имеющий адаптированную версию под Android.
Преимущества:
- Мощная и в тоже время простая аннотация
- Богатый функционал
- Поддержка многих БД, в т.ч. SQLite
- Архитектура в соответсвии с принципом KISS
Недостатки:
… хм… я их не находил) всё что кажется не возможным, реализуется при более глубоком изучении доков.
OrmLite я использую в нескольких и проектах и знаком с ним достаточно тесно. Поэтому рассажу как раз о нем.
Простая аннотация классов
Каждый класс, отображение в базу данных (сохрание в БД) которого мы хотим сделать должен быть аннотирован. (можно использовать class-configuration, но это совсем другая история)Стоит заметить, что у класса должен обязательно присутствовать конструктор без аргументов. Пример аннотации:
@DatabaseTable(tableName = "goals") public class Goal{ public final static String GOAL_NAME_FIELD_NAME = "name"; @DatabaseField(generatedId = true) private int Id; @DatabaseField(canBeNull = false, dataType = DataType.STRING, columnName = GOAL_NAME_FIELD_NAME) private String name; @DatabaseField(dataType = DataType.DATE) private Date lastEditDate; @DatabaseField() private String notes; public Goal(){ scheduleList = new ArrayList<Shedule>(); priorities = new ArrayList<PrioritySchedule>(); } }
Первая аннотация
@DatabaseTable(tableName = "goals") указывает название таблицы, куда будут отображены объекты этого класса. Перед каждым полем должна быть аннотация
@DatabaseField с различными аргументами. (также можно не указывать аргументов — всё установится по умолчанию, а название столбца в таблице будет совпадать с названием поля). У поля name три аргумента. canBeNull = false означает, что в таблице этот столбец не может быть пустым. dataType = DataType.STRING принудительно обязывает приводить тип столбца к типу String. (доступные типы можно посмотреть здесь). columnName = GOAL_NAME_FIELD_NAME — принудительное указание имени столбца в таблице поможет нам в дальнейшем при построении select-запросов для получения данных из БД.Первичным ключем может служить любое поле — достаточно указать у него
id = true, но рекомендуется сделать автогенерируемое значение id и поставить ему generatedId = true. ORMLite сама назначит ему уникальный номер.Для индексации столбца в БД указывается
index = trueПолное описание всех доступных агументов аннотации полей можно посмотреть здесь
Также помимо аннотаций ORMLite можно использовать привычные некоторым javax.persistence аннотации
Подключение к SQLite в андроид
Существует несколько способов получения доступа к данным БД с помощью ORMLite в андроид. Можно наследовать каждую activity от ORMLiteBaseActivity и тогда нам не придется следить за жизненным циклом соединения с БД, но при этом мы не сможем получить доступа из других классов. (см примеры)
Гораздо предпочтительнее подход, рассмотренный далее.
Нужно создать класс, который будет инстанцировать помощника в создании и работе с БД:
public class HelperFactory{ private static DatabaseHelper databaseHelper; public static DatabaseHelper getHelper(){ return databaseHelper; } public static void setHelper(Context context){ databaseHelper = OpenHelperManager.getHelper(context, DatabaseHelper.class); } public static void releaseHelper(){ OpenHelperManager.releaseHelper(); databaseHelper = null; } }
Обращение к нему будет происходить во время начала и конца жизни приложения:
Это предотвратит утечки памяти из-за незакрытого соединения с БД
public class MyApplication extends Application{ @Override public void onCreate() { super.onCreate(); HelperFactory.setHelper(getApplicationContext()); } @Override public void onTerminate() { HelperFactory.releaseHelper(); super.onTerminate(); } }
Так же не забываем описать наш класс наследник Application в манифесте:
<application android:name=".MyApplication" android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
Создадим класс DataBaseHelper, который будет отвечать за создание БД и за получение ссылок на DAO:
public class DatabaseHelper extends OrmLiteSqliteOpenHelper{ private static final String TAG = DatabaseHelper.class.getSimpleName(); //имя файла базы данных который будет храниться в /data/data/APPNAME/DATABASE_NAME.db private static final String DATABASE_NAME ="myappname.db"; //с каждым увеличением версии, при нахождении в устройстве БД с предыдущей версией будет выполнен метод onUpgrade(); private static final int DATABASE_VERSION = 1; //ссылки на DAO соответсвующие сущностям, хранимым в БД private GoalDAO goalDao = null; private RoleDAO roleDao = null; public DatabaseHelper(Context context){ super(context,DATABASE_NAME, null, DATABASE_VERSION); } //Выполняется, когда файл с БД не найден на устройстве @Override public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource){ try { TableUtils.createTable(connectionSource, Goal.class); TableUtils.createTable(connectionSource, Role.class); } catch (SQLException e){ Log.e(TAG, "error creating DB " + DATABASE_NAME); throw new RuntimeException(e); } } //Выполняется, когда БД имеет версию отличную от текущей @Override public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVer, int newVer){ try{ //Так делают ленивые, гораздо предпочтительнее не удаляя БД аккуратно вносить изменения TableUtils.dropTable(connectionSource, Goal.class, true); TableUtils.dropTable(connectionSource, Role.class, true); onCreate(db, connectionSource); } catch (SQLException e){ Log.e(TAG,"error upgrading db "+DATABASE_NAME+"from ver "+oldVer); throw new RuntimeException(e); } } //синглтон для GoalDAO public GoalDAO getGoalDAO() throws SQLException{ if(goalDao == null){ goalDao = new GoalDAO(getConnectionSource(), Goal.class); } return goalDao; } /синглтон для RoleDAO public RoleDAO getRoleDAO() throws SQLException{ if(roleDao == null){ roleDao = new RoleDAO(getConnectionSource(), Role.class); } return roleDao; } //выполняется при закрытии приложения @Override public void close(){ super.close(); goalDao = null; roleDao = null; } }
Как видно в примере, нужно описать создание полей и таблицы, а также реализовать получение ссылки на синглтон с DAO-объектом. Теперь рассмотрим непосредственно сами DAO.
DAO
Самой простой реализацией является следующий класс
public class RoleDAO extends BaseDaoImpl<Role, Integer>{ protected RoleDAO(ConnectionSource connectionSource, Class<Role> dataClass) throws SQLException{ super(connectionSource, dataClass); } public List<Role> getAllRoles() throws SQLException{ return this.queryForAll(); } }
Здесь создан лишь один дополнительный метод для получения коллекции всех объектов класса Role.
Основные методы create, update, delete реализованы в предке BaseDaoImpl.
Для обращения к методом класса RoleDao в любом классе приложения достаточно обратиться к классу-фабрике:
RoleDAO roleDao = HelperFactory.GetHelper().getRoleDAO();Создание запросов
Для построения специфичных запросов можно создавать свои методы в классе DAO.
public List<Goal> getGoalByName(String name) throws SQLException{ QueryBuilder<Goal, String> queryBuilder = queryBuilder(); queryBuilder.where().eq(Goal.GOAL_NAME_FIELD_NAME, "First goal"); PreparedQuery<Goal> preparedQuery = queryBuilder.prepare(); List<Goal> goalList =query(preparedQuery); return goalList; }
Как видно, здесь производился поиск по объектам с соответствующим полем имени.
При построении более сложных запросов помимо
eq (что означает equals) есть и другие вроде gt(greater), ge(greater and equals) и остальных соответсвующих стандартным where-конструкцииям в SQL (полный список здесь)Для построения сложного запроса можно добавлять
and:queryBuilder.where().eq(Goal.GOAL_NAME_FIELD_NAME, "First goal").and().eq(Goal.GOAL_NOTES_NAME_FIELD_NAME,”aaa”);Соответсвенно помимо запросов из БД подобным образом можно удалять и обновлять таблицы.
Работа с вложенным сущностями
Рассмотрим отношения вида много к одному. Допустим объект Goal имеет поле указывающее на Role.
Соответственно в классе Goal поле должно быть аннотированно
@DatabaseField(foreign = true) private Role role; public void setRole(Role value){ this.role = value; } public Role getRole(){ return role; }
Теперь мы можем сохранить наши объекты в БД
Goal g = new Goal(); g.setName(“asd”); Role r = new Role(); g.setRole(r); HelperFactory.getHelper.getRoleDAO().create(r); HelperFactory.getHelper.getGoalDAO().create(g);
Чтобы получить доступ к объекту класса Role, который связан с объектом типа Goal:
Goal g = HelperFactory.getHelper.getRoleDAO().getGoalByName(“asd”); Role r = g.getRole(); HelperFactory.getHelper.getRolelDAO().refresh(g);
refresh() необходимо выполнять, чтобы r получил все поля из БД соответвующие этому объекту.При хранинии ссылки на коллекцию подход немного отличается:
в классе Goal:
@ForeignCollectionField(eager = true) private Collection<Role> roleList; public addRole(Role value){ value.setGoal(this); HelperFactory.GetHelper().getRoleDAO().create(value); roleList.add(value); } public void removeRole(Role value){ roleList.remove(value); HelperFactory.GetHelper().getRoleDAO().delete(value); }
Аргумент в аннотации
eager означает, что все объекты из roleList будут получаться из БД вместе с извлечением объекта типа Goal. Обязательной является ссылка на Goal в объекте Role. А так же нужно отдельно сохранять с помощью RoleDAO каждый из объектов Role.Соответственно в классе Role должна быть аннотация:
@DatabaseField(foreign = true, foreignAutoRefresh = true) private Goal goal;
Если не ставить
eager=true, то будет осуществлена lazy-инициализация, т.е. при запросе объекта Goal объекты соотвествующие коллекции roleList не будут извлечены. Для их извлечения нужно будет произвести их итерацию:Iterator<Role> iter = state.goal.getRoleList().iterator(); while (iter.hasNext()) { Role r = iter.next(); }
Ссылка на библиотеку.
Ссылка на туториалы.

