Пишем свой 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.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 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
            Может быть я что-то не правильно понял, но по-моему у меня так и сделано?

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