При разработке приложения под Android мы часто пишем руками скрипты для создания схемы.
Все бы ничего когда это надо сделать одни раз, но когда приложение развивается, то часто приходится менять ��азу.
И когда это размазано по нескольким классам — то возникают проблемы, где-то забыл добавить/удалить колонку, изменить тип и прочее. Еще и копипаст «помогает»: добавлял колонку — забыл поставить запятую.
Как раз для решения этих проблем и была придумана эта библиотека.
AnnotatedSQL — библиотека которая сгенерит код для создания базы данных по аннотациям. Аннотации не runtime, а обрабатываются препроцессором во время компиляции. Тем самым мы никак не афектим проект и конечный apk.
Собственно либа и состоит из двух кусков: jar с аннотациями и препроцессора.
Аннотации кладем в папку libs проекта.
Ну а использование препроцессор зависит от IDE и способа сборки проекта.
Если вы юзаете Eclipse — то копируем плагин в папку plugins и перезапускаем eclipse если надо. далее идем в настройки проекта Java Compiler -> Annotation Processing и выбираем там папку куда генерить код. Очевидно надо поставить стандартную папку gen. Далее идем в Factory Path и выбираем наш плагин. ну вот и все.
Для IDEA плагин не собирал, сори
Для использования с ant — надо просто добавить препроцессор в classpath делаем это примерно так
Перейдем к более техническим вещам. Итак мы знаем как подключить либу, но что же она делает?
Как я уже говорил — по аннотациям генерит базу, точнее создает класс с помошью которого будет генерится база.
Как обычно бывает, для использования в коде мы описываем таблички как интерфейсы с названием таблицы и колонками в ней.
Например у нас будет приложение которое должно выводить данные о результатах спортивных соревнований. Возьмем пока футбольный матч.
Очевидно, у нас должно быть несколько табличек в базе. Это — команда, результат и чемпионат.Опишем их.
Я обычно описываю все интерфейсы внутри одного класса назовем его FStore, например. Кроме описания табличек он содержит название базы, ее версию и еще пару служебных методов.
Пока не обращаем внимание на CONTENT_PATH и всякие PATH_VIEW. Это константы для доступа в контент провайдер.
Итак, мы представляем объем ручной работы для создания схемы.
В добавок, что бы получить результат в читаемой форме нам надо заджойнить таблички друг на друга. Это можно сделать в контент провайдере, но я предпочитаю юзать View, вот еще большой sql кусок для написания.
Для облегчения нашей работы и была написана эта либа. Итак приступим.
FStore — помечаем аннотацией Schema(«SqlSchema») и задаем имя класса который будет содержать код. класс будет сгенерен в том же пакете, где лежит FStore
Описание табличек помечаем аннотацией Table и задаем имя таблицы
Как видим на таблицу мы можем повесить создание индекса, и сложного ключа. Тут вроде все просто и не требует объяснения
Эти аннотации предназначены для полей, и очевидны в использовании тоже
И последний, очень важный, элемент системы и не совсем тривиальный это SimpleView.
Он предоставляет базовый функционал для создания простых вьюх. Тут пока есть INNER JOIN, но я добавлю и другие.
Рассмотрим аннотации внутри нашей вьюхи:
From — это табличка из которой будем делать from :) Важно — далее при джойнах надо использовать не имя таблицы, а именно эту константу.
Join — собственно таблицы джойна. В нашем случае надо заджойнится на таблицу команды и чемпионата.
srcTable — это исходная таблица.
destTable — это новое название таблицы from/join во вьюхе. В нашем случае
Еще очень важное замечание — во вьюхе имена полей генерятся по следующему паттерну:
Исключение — поле _id из таблицы From, что бы юзать cursor в адаптере.
Следовательно, что бы найти индекс колонки надо юзаьть нечто вроде
немного неудобно, но это делается один раз в
можно еще заюзать такой хелпер
и юзаем его так
Сгенеренный файлик SqlSchema.java
Использование констант из описания табличек не требуется, т.к. файл сгенерен и четко следует тому, что вы написали в объявлении таблиц
1. Добавить разные типы джойнов
2. Добавить аннотацию Columns для джойна, что бы выгребать только нужные поля
Бинарники: github.com/hamsterksu/Android-AnnotatedSQL-binaries
Исходники: github.com/hamsterksu/Android-AnnotatedSQL
Лицензия: MIT
Всем спасибо!
Update #1: насчет обновления схемы
OpenHelper я не генерирую, вы его пишите сами. так что никто не мешает написать там сложную логику, а генеренный скрипт будет работать для onCreate.
В onUpgrade можно написать добавление/удаление/изменение полей совсем просто — имена таблиц и полей у вас есть.
В схеме я сделаю мемберы открытыми — тогда вы сможете получить доступ к ним и после изменения таблиц — пересоздать вьюшки
Все бы ничего когда это надо сделать одни раз, но когда приложение развивается, то часто приходится менять ��азу.
И когда это размазано по нескольким классам — то возникают проблемы, где-то забыл добавить/удалить колонку, изменить тип и прочее. Еще и копипаст «помогает»: добавлял колонку — забыл поставить запятую.
Как раз для решения этих проблем и была придумана эта библиотека.
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
Всем спасибо!
Update #1: насчет обновления схемы
OpenHelper я не генерирую, вы его пишите сами. так что никто не мешает написать там сложную логику, а генеренный скрипт будет работать для onCreate.
В onUpgrade можно написать добавление/удаление/изменение полей совсем просто — имена таблиц и полей у вас есть.
В схеме я сделаю мемберы открытыми — тогда вы сможете получить доступ к ним и после изменения таблиц — пересоздать вьюшки