Все бы ничего когда это надо сделать одни раз, но когда приложение развивается, то часто приходится менять базу.
И когда это размазано по нескольким классам — то возникают проблемы, где-то забыл добавить/удалить колонку, изменить тип и прочее. Еще и копипаст «помогает»: добавлял колонку — забыл поставить запятую.
Как раз для решения этих проблем и была придумана эта библиотека.
AnnotatedSQL — библиотека которая сгенерит код для создания базы данных по аннотациям. Аннотации не runtime, а обрабатываются препроцессором во время компиляции. Тем самым мы никак не афектим проект и конечный apk.
Собственно либа и состоит из двух кусков: jar с аннотациями и препроцессора.
Аннотации кладем в папку libs проекта.
Ну а использование препроцессор зависит от IDE и способа сборки проекта.
Если вы юзаете Eclipse — то копируем плагин в папку plugins и перезапускаем eclipse если надо. далее идем в настройки проекта Java Compiler -> Annotation Processing и выбираем там папку куда генерить код. Очевидно надо поставить стандартную папку gen. Далее идем в Factory Path и выбираем наш плагин. ну вот и все.
Для IDEA плагин не собирал, сори
Для использования с ant — надо просто добавить препроцессор в classpath делаем это примерно так
ant clean release -cp ../com.annotatedsql.AnnotatedSQL_1.0.12.jar
Использование
Перейдем к более техническим вещам. Итак мы знаем как подключить либу, но что же она делает?
Как я уже говорил — по аннотациям генерит базу, точнее создает класс с помошью которого будет генерится база.
Как обычно бывает, для использования в коде мы описываем таблички как интерфейсы с названием таблицы и колонками в ней.
Например у нас будет приложение которое должно выводить данные о результатах спортивных соревнований. Возьмем пока футбольный матч.
Очевидно, у нас должно быть несколько табличек в базе. Это — команда, результат и чемпионат.Опишем их.
Я обычно описываю все интерфейсы внутри одного класса назовем его FStore, например. Кроме описания табличек он содержит название базы, ее версию и еще пару служебных методов.
public class FStore { public static final String DB_NAME = "fmanager"; public static final int DB_VERSION = 34; .......... public static interface TeamTable{ String TABLE_NAME = "team_table"; String ID = "_id"; String TITLE = "title"; String CHEMP_ID = "chemp_id"; String IS_FAV = "is_fav"; } public static interface ChempTable{ String TABLE_NAME = "chemp_table"; String CONTENT_PATH = "chemps"; String ID = "_id"; String TITLE = "title"; } public static interface ResultsTable{ String CONTENT_PATH = "results"; String PATH_VIEW = "results_view"; String TABLE_NAME = "result_table"; String ID = "_id"; String TEAM_ID = "team_id"; String POINTS = "points"; String CHEMP_ID = "chemp_id"; String GAMES = "games"; String WINS = "wins"; String TIE = "tie"; String LOSE = "lose"; String BALLS = "balls"; String GOALS = "goals"; } ........ }
Пока не обращаем внимание на CONTENT_PATH и всякие PATH_VIEW. Это константы для доступа в контент провайдер.
Итак, мы представляем объем ручной работы для создания схемы.
В добавок, что бы получить результат в читаемой форме нам надо заджойнить таблички друг на друга. Это можно сделать в контент провайдере, но я предпочитаю юзать View, вот еще большой sql кусок для написания.
Для облегчения нашей работы и была написана эта либа. Итак приступим.
Schema
FStore — помечаем аннотацией Schema(«SqlSchema») и задаем имя класса который будет содержать код. класс будет сгенерен в том же пакете, где лежит FStore
@Schema("SqlSchema") public class FStore {
Table, Index, PrimaryKey
Описание табличек помечаем аннотацией Table и задаем имя таблицы
@Table(ChempTable.TABLE_NAME) public static interface ChempTable{ ................ @Table(TeamTable.TABLE_NAME) public static interface TeamTable{ ............... @Table(ResultsTable.TABLE_NAME) @Index(name = "chemp_index", columns = ResultsTable.CHEMP_ID) @PrimaryKey(collumns = {ResultsTable.TEAM_ID, ResultsTable.CHEMP_ID}) public static interface ResultsTable{
Как видим на таблицу мы можем повесить создание индекса, и сложного ключа. Тут вроде все просто и не требует объяснения
Column, PrimaryKey, Autoincrement, NotNull
Эти аннотации предназначены для полей, и очевидны в использовании тоже
@Table(TeamTable.TABLE_NAME) public static interface TeamTable{ String TABLE_NAME = "team_table"; @PrimaryKey @Column(type = Type.INTEGER) String ID = "_id"; @NotNull @Column(type = Type.TEXT) String TITLE = "title"; @Column(type = Type.INTEGER) @NotNull String CHEMP_ID = "chemp_id"; @Column(type = Type.INTEGER, defVal="0") String IS_FAV = "is_fav"; }
SimpleView
И последний, очень важный, элемент системы и не совсем тривиальный это SimpleView.
Он предоставляет базовый функционал для создания простых вьюх. Тут пока есть INNER JOIN, но я добавлю и другие.
@SimpleView(ResultView.VIEW_NAME) public static interface ResultView{ String VIEW_NAME = "result_view"; @From(ResultsTable.TABLE_NAME) String TABLE_RESULT = "table_result"; @Join(srcTable = TeamTable.TABLE_NAME, srcColumn = TeamTable.ID, destTable = ResultView.TABLE_RESULT, destColumn = ResultsTable.TEAM_ID) String TABLE_TEAM = "table_team"; @Join(srcTable = ChempTable.TABLE_NAME, srcColumn = ChempTable.ID, destTable = ResultView.TABLE_RESULT, destColumn = ResultsTable.CHEMP_ID) String TABLE_CHEMP = "table_chemp"; }
Рассмотрим аннотации внутри нашей вьюхи:
From — это табличка из которой будем делать from 🙂 Важно — далее при джойнах надо использовать не имя таблицы, а именно эту константу.
Join — собственно таблицы джойна. В нашем случае надо заджойнится на таблицу команды и чемпионата.
srcTable — это исходная таблица.
destTable — это новое название таблицы from/join во вьюхе. В нашем случае
String TABLE_RESULT = "table_result";
Еще очень важное замечание — во вьюхе имена полей генерятся по следующему паттерну:
<variable_name>_<column_name>
Исключение — поле _id из таблицы From, что бы юзать cursor в адаптере.
Следовательно, что бы найти индекс колонки надо юзаьть нечто вроде
columnPoints = cursor.getColumnIndex(ResultView.TABLE_RESULT + "_" + ResultsTable.POINTS);
немного неудобно, но это делается один раз в
public void changeCursor(Cursor cursor) {
можно еще заюзать такой хелпер
public class ColumnMappingHelper { private HashMap<String, HashMap<String, Integer>> indexes = new HashMap<String, HashMap<String, Integer>>(); public int getColumn(Cursor c, String table, String column){ HashMap<String, Integer> columns = indexes.get(table); if(columns != null){ Integer index = columns.get(column); if(index != null) return index; } if(columns == null){ columns = new HashMap<String, Integer>(); indexes.put(table, columns); } int index = c.getColumnIndex(table + "_" + column); columns.put(column, index); return index; } }
и юзаем его так
mappingHelper.getColumn(cursor, ResultView.TABLE_RESULT, ResultsTable.POINTS);
Результат
Сгенеренный файлик SqlSchema.java
public class SqlSchema{ private static final String SQL_CREATE_RESULT_TABLE = "create table result_table( balls INTEGER, chemp_id INTEGER NOT NULL, games INTEGER NOT NULL, goals INTEGER, _id INTEGER, lose INTEGER, points INTEGER NOT NULL, team_id INTEGER NOT NULL, tie INTEGER, wins INTEGER, PRIMARY KEY( team_id, chemp_id))"; private static final String SQL_CREATE_CHEMP_TABLE = "create table chemp_table( _id INTEGER PRIMARY KEY, title TEXT)"; private static final String SQL_CREATE_TEAM_TABLE = "create table team_table( chemp_id INTEGER NOT NULL, _id INTEGER PRIMARY KEY, is_fav INTEGER DEFAULT (0), title TEXT NOT NULL)"; private static final String SQL_CREATE_CHEMP_INDEX = "create index idx_chemp_index on result_table( chemp_id)"; private static final String SQL_CREATE_RESULT_VIEW = "CREATE VIEW result_view AS SELECT table_chemp._id as table_chemp__id, table_chemp.title as table_chemp_title, table_result.balls as table_result_balls, table_result.chemp_id as table_result_chemp_id, table_result.games as table_result_games, table_result.goals as table_result_goals, table_result._id, table_result.lose as table_result_lose, table_result.points as table_result_points, table_result.team_id as table_result_team_id, table_result.tie as table_result_tie, table_result.wins as table_result_wins, table_team.chemp_id as table_team_chemp_id, table_team._id as table_team__id, table_team.is_fav as table_team_is_fav, table_team.title as table_team_title FROM result_table AS table_result JOIN chemp_table AS table_chemp ON table_chemp._id = table_result.chemp_id JOIN team_table AS table_team ON table_team._id = table_result.team_id"; public static void onCreate(final SQLiteDatabase db) { db.execSQL(SQL_CREATE_RESULT_TABLE); db.execSQL(SQL_CREATE_CHEMP_TABLE); db.execSQL(SQL_CREATE_TEAM_TABLE); db.execSQL(SQL_CREATE_SCORE_TABLE); db.execSQL(SQL_CREATE_CHEMP_INDEX); db.execSQL(SQL_CREATE_RESULT_VIEW); db.execSQL(SQL_CREATE_SCORE_VIEW); } public static void onDrop(final SQLiteDatabase db){ db.execSQL("drop table if exists result_table"); db.execSQL("drop table if exists chemp_table"); db.execSQL("drop table if exists team_table"); db.execSQL("drop table if exists score_table"); db.execSQL("drop view if exists result_view"); db.execSQL("drop view if exists score_view"); } }
Использование констант из описания табличек не требуется, т.к. файл сгенерен и четко следует тому, что вы написали в объявлении таблиц
Использование SqlSchema
private class AnnotationSql extends SQLiteOpenHelper { public AnnotationSql(Context context) { super(context, FStore.DB_NAME, null, FStore.DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { SqlSchema.onCreate(db); init(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { SqlSchema.onDrop(db); onCreate(db); } }
Планы
1. Добавить разные типы джойнов
2. Добавить аннотацию Columns для джойна, что бы выгребать только нужные поля
Ссылки
Бинарники: github.com/hamsterksu/Android-AnnotatedSQL-binaries
Исходники: github.com/hamsterksu/Android-AnnotatedSQL
Лицензия: MIT
Всем спасибо!
ссылка на оригинал статьи http://habrahabr.ru/post/156283/
Добавить комментарий