Пользователь
0,0
рейтинг
23 июля 2013 в 12:57

Разработка → Пишем свой Orm под Android с канастой и сеньоритами, Часть 2-ая

Вступление


Некоторый интерес сообщества к моей первой статье, заставил меня усиленно поработать над orm. Мне еще не все в нем нравится (где-то код не оптимизирован; где-то реализация не такая, как я хотел; не хватает проверок и возможно стабильности), но он выполняет все необходимые в текущей момент мне функции. И так, встречайте: UcaOrm!

Использование


Работу с orm будем рассматривать на примере следующей модели (подробнее о модели в 1-ой части):
public class BaseEntity extends OrmEntity {
 
       @Column(primaryKey = true, inherited = true)
       private Long id;
}
 
@Table(name = "car_type", cashedList = true)
public class CarType extends BaseEntity  {
 
       @Column
       private String code;
}
 
@Table(rightJoinTo = {Truck.class})
public class Car extends BaseEntity {
 
       @Column(name = "car_type")
       private CarType type;
 
       @Column
       private List<Wheel> wheels;
 
       @Column(name = "engine_power")
       private int enginePower;
 
       @Column(name = "doors_count")
       private int doorsCount;
}
 
@Table
public class Wheel extends BaseEntity {
 
       @Column(name = "car_id")
       private Car car;
 
       @Column
       private String manufacturer;
}
 
@Table(leftJoinTo = Car.class)
public class Truck extends Car {
 
       @Column(name = "is_tipper")
       private boolean isTipper;
}

Настройка

После добавления библиотеки в проект (в дальнейшем я планирую выкладывать собранный jar, а пока вы можете самостоятельно собрать его из исходников), необходимо создать Helper унаследованный от OrmHelper:
public class DataBaseHelper extends OrmHelper {

    public DataBaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    protected void onCreate() {
    }

    @Override
    protected void onUpgrade(int oldVersion, int newVersion) {
    }

    @Override
    public void getDefaultValues(Class<? extends OrmEntity> entityClass, ArrayList<String> columns, ArrayList<ContentValues> valueList) {        
    }
}


Метод getDefaultValues позволяет добавлять в создаваемые таблицы начальные данные. Его реализация пока не очень удобна, и в будущем подвергнется переработке.
Теперь нам надо зарегистрировать helper. Делается это через наследника Application:
public class MyApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
       try {
            OrmFactory.SetHelper(DataBaseHelper.class, getApplicationContext());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onTerminate() {
        OrmFactory.ReleaseHelper();
        super.onTerminate();
    }
}


По умолчанию, имя базы формируется из Label приложения, а версия будет 1-ой. Чтобы это изменить, добавим в manifest следующие строки:
<meta-data android:name="UO_DB_NAME" android:value="Cars" />
<meta-data android:name="UO_DB_VERSION" android:value="1" />

Создание таблиц

Таблицы создаются в методе onCreate, а обновление в методе onUpdate, однако обновление еще пока не поддерживается в orm.
И так, создадим необходимые таблицы:
protected void onCreate() {
        try {
            OrmUtils.CreateTable(CarType.class);
            OrmUtils.CreateTable(Car.class);
            OrmUtils.CreateTable(Wheel.class);
            OrmUtils.CreateTable(Truck.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

И заполним таблицу CarType начальными данными:
public void getDefaultValues(Class<? extends OrmEntity> entityClass, ArrayList<String> columns, ArrayList<ContentValues> valueList) {
        ContentValues values;
        if (entityClass.equals(CarType.class)) {
            values = new ContentValues();
            values.put(columns.get(0), "Passenger");
            valueList.add(values);
            values = new ContentValues();
            values.put(columns.get(0), "Truck");
            valueList.add(values);
        }
      }

Как уже говорил, реализация getDefaultValues хромает.

Простая выборка

Так как мы уже наполнили таблицу CarType данными, то предлагаю сначала рассмотреть простую выборку, а потом уже создание экземпляров и выборку с Where.
Давайте создадим экземпляр типа «Легковой автомобиль». Для этого нам понадобится сначала получить все доступные нам типы. Поскольку все сущности, с которыми работает orm должны наследоваться от OrmEntity, воспользуемся его статическим методам getAllEntities. Для красоты, мы можем перекрыть его в классе CarType:
public static List<CarType> getAllCarTypes(){
        try {
            return OrmEntity.getAllEntities(CarType.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

Создание сущностей

Создам экземпляр Car:
	Car car = new Car();
	car.setType(CarType. getAllCarTypes().get(0));
	car.setEnginePower(116);
	car.setDoorsCount(4);
	Wheel whell = new Whell();
	whell.setCar(car);
	whell.setManufacturer("Michrelli");
	car.addWheel(whell);

Конечно whell.setCar лучше дернуть внутри addWheel как whell.setCar(this), но приведу его здесь для наглядности.
И просто дергаем метод alter:
	car.alter();

Немного изменим экземпляр:
	car.setEnginePower(120);
	car.getWheels().get(0).setManufacturer("Pirlin");


И снова просто вызываем, alter:
	car.alter();

Теперь создадим «Грузовик»:

	Truck truck = new Truck();
	truck.setType(CarType.getAllCarTypes().get(1));
	truck.setEnginePower(220);
	truck.setDoorsCount(2);
	Wheel whell = new Whell();
	whell.setCar(truck);
	whell.setManufacturer("Michrelli");
	truck.addWheel(whell);
	truck.setTipper(true);
	truck.alter();

Причем нам не важен тип переменной, мы можем объявить её и как Car, главное, что в ней лежит.

Выборка

Реализуем в Car два метода:
public static List<Car> getAllCars() {
        try {
            return OrmEntity.getAllEntities(CarType.class, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
}

public static List<Car> getCarsWithoutTrucks() {
        try {
            return OrmEntity.getAllEntities(CarType.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
}

Разница лишь в вызове getAllEntities, однако getAllCars вернет две записи, а getCarsWithoutTrucks только одну: второй параметр указывает, выбирать ли записи связанные с дочерними таблицами, или же только самодостаточные.
Важно, заметить следующее: допустим мы создадим второй экземпляр с типом Passenger. При выборке, так как класс CarType помечен как CashedList, оба экземпляра Car будут ссылаться на один и тот же экземпляр CarType.

Выборка с Where

Ну, и самое интересное!
В OrmEntity есть статический метод Where который принимает класс сущности для выборки. Для красоты перекроем его в Car:
    public static OrmWhere Where(){
        return Where(Car.class);
    }

И попытаемся найти машины с мощностью 120 л.с:

	List<Car> cars = Car.Where().Equals("engine_power", 120).Select();

Или машины с мощностью 120 л.с. и у которых четыре двери:

	List<Car> cars = Car.Where().Equels(“engine_power”, 120).And().Equals("doors_count", 4).Select();

Или одну из машин у которой есть колесо от Pirlin:

	Car car = Car.Where().FindChild(Wheel.class, new OrmWhere(Wheel.class).Equals("manufacturer", "Pirlin")).SelectFirst();

Выборку с включение из дочерних таблиц пока сделать нельзя (хотя orm её поддерживает и, все что надо, это добавить еще один метод Select с параметром includeLeftChild в OrmWhere).

Заключение


Вот почти и все на что пока способен мой orm. Сейчас, номер версии, что я ему присвоил бы – 0.1. Как я уже говорил, он реализует все, что нужно моему приложению в данный момент, и поэтому, такие мелочи, как например, Less или Great в OrmWhere – пока не реализованы. Не реализована возможность обновления или удаления таблиц (хотя можно добавлять новые через тот же CreateTable). Из-за left и right join таблиц пришлось отказаться от вызова query с параметрами и дергать rawQuery у SQLiteDatabase, а так же затраты на выборку всех записей кэшированных списков возросли с O(n-k) (где n – кол-во всех записей в таблице, а k – кол-во уже выбранных записей) до O(n). Поддерживаемые сейчас типы, это: Int, Long, Double, Date, String, Drawable и Document.
Возможно, что-то забыл упомянуть, но в любом случае буду рад любым вопросам и, особенно, предложениям по развитию UcaOrm.
@Scogun
карма
6,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (4)

  • +1
    Equels → equals, а вообще, чем вызвана необходимость писать имена методов заглавными буквами?

    List<Car> cars = Car.Where().Equels(“engine_power”, 120).And().Equels("doors_count", 4).Select();

    Вложенные условия, типа, a AND (b = c OR d = e), я так понимаю, не предусмотрены?

    Как мне подсказывает опыт работы с ORM, пока запросы достаточно простые, все отличненько, но стоит захотеть чего-нибудь нестандартного, и приходится извращаться со всяками хаками и хитростями.

    Какие плюсы Вы планируете получить от использования своего orm вместо обычного jdbc?
    • 0
      Equels → equals
      Подправил, спасибо!
      а вообще, чем вызвана необходимость писать имена методов заглавными буквами?
      Привычка из Delphi и .Net'а! Стараюсь в Java её не переносить, но как видно, не всегда получается.
      Вложенные условия, типа, a AND (b = c OR d = e), я так понимаю, не предусмотрены?
      Пока нет. В задумке что-то вроде этого:
      And(new OrmWhere().Equals("b", "c").Or().Equals("d","e"))
      
      Как мне подсказывает опыт работы с ORM, пока запросы достаточно простые, все отличненько, но стоит захотеть чего-нибудь нестандартного, и приходится извращаться со всяками хаками и хитростями.
      Избежать хаков и хитростей планирую, дав возможность делать выборку прямо запросом, но детали пока не продумывал. Еще есть идея сделать все же третий тип аннотации @ViewColumn для свойств выбираемых из view, ну и плюс все сопутствующую архитектуру их (view) создания и поддержки.
      Какие плюсы Вы планируете получить от использования своего orm вместо обычного jdbc?
      Ну, я уже их получил: пишу приложение под Android и использую свой же orm, благодаря чему как раз и избегаю пока
      всяких хаков и хитростей
      • 0
        Проблема составных запросов решается вводом объектов типа Query (.where() возвращает пустой Query) и методов их комбинирования .and(other), .or(other) и т.д.
        • 0
          Может быть я что-то не правильно понял, но по-моему у меня так и сделано?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.