Использование Berkeley DB в Android приложении

от автора

После успешно пройденного этапа «Hello World» под Android, решил написать для интереса простенькое приложение под Android, основной функционал которого сводился к хранению некоторого набора данных на устройстве. И очень мне не хотелось работать c SQL. Привык как-то уже работать с объектами. По-этому порыскав по интернету в поисках совместимых с Android решений нашёл только одно — Berkeley DB, встраиваемая БД.
Причём документация от Oracle показывала значительно лучшие показатели по производительности по сравнению с SQlite.По этому для своего приложения (дальше моего телефона оно так и не ушло) я выбрал этот формат хранения данных.
Класс являющийся ядром работы с БД сделан по шаблону Singleton, и получился следующим:

public class DatabaseConfig {     private static DatabaseConfig ourInstance;      private Environment envmnt;     private EntityStore store;      public static DatabaseConfig getInstance() {         if (ourInstance == null)             throw new IllegalArgumentException("You need initialize database config previously!");         return ourInstance;     }      public static void init(File envDir) {         ourInstance = new DatabaseConfig(envDir);     }      private DatabaseConfig(File envDir) {         EnvironmentConfig envConfig = new EnvironmentConfig();         StoreConfig storeConfig = new StoreConfig();          envConfig.setTransactional(true);         envConfig.setAllowCreate(true);         storeConfig.setAllowCreate(true);         storeConfig.setTransactional(true);         envmnt = new Environment(envDir, envConfig);         try {             store = new EntityStore(envmnt, "autocalc", storeConfig);         } catch (IncompatibleClassException e) {             //todo: реализовать преобразования данных.         }     }      public static void shutdown() {         if (ourInstance != null) {             ourInstance.close();         }     }      private void close() {         store.close();         envmnt.close();      }      public EntityStore getStore() {         return store;     }      public Transaction startTransaction() {         return envmnt.beginTransaction(null, null);     }  }

Проблемы этого класса достаточно прозаичны, перед тем как получить доступ к сущности, её надо инициализировать, что можно забыть. Плюс, выскочила проблема создания/закрытия транзакции. Транзакция открывается в одном классе, а закрывается в другом, что так же выглядит не самым лучшим образом с точки зрения разработки. Пока эту «оплошность» я не смог «красиво» исправить. Особенно криво это смотрится в свете того, что транзакции используются для того, чтобы получить следующее значение идентификатора для сохраняемой сущности.

На более высоком уровне были созданы классы доступа к данным DataAccess.

public class FuelItemDA {      private PrimaryIndex<Long, FuelItem> prIndex;     private SecondaryIndex<Long, Long, FuelItem> odometerIndex;     private SecondaryIndex<Date, Long, FuelItem> dateIndex;      private DatabaseConfig dbConfig;      public FuelItemDA() {         dbConfig = DatabaseConfig.getInstance();          prIndex = dbConfig.getStore().getPrimaryIndex(                 Long.class, FuelItem.class);          odometerIndex = dbConfig.getStore().getSecondaryIndex(                 prIndex, Long.class, "odometer");         dateIndex = dbConfig.getStore().getSecondaryIndex(                 prIndex, Date.class, "operationDate");      }      public void save(FuelItem item) {         Transaction tx = dbConfig.startTransaction();         try {             if (item.getId() == 0) {                 long id = dbConfig.getStore().getSequence("SPENT_ID").get(tx, 1);                 item.setId(id);             }             prIndex.put(tx, item);             tx.commit();         } catch (Exception e) {             e.printStackTrace();             if (tx != null) {                 tx.abort();                 tx = null;             }         }     }      public FuelItem load(long id) {         return prIndex.get(id);     }      public List<FuelItem> getItemsInDates(Date bDate, Date eDate) {         List<FuelItem> result = new LinkedList<FuelItem>();         EntityCursor<FuelItem> cursor = dateIndex.entities(bDate, true, eDate, true);         for (Iterator<FuelItem> iterator = cursor.iterator(); iterator.hasNext(); ) {             FuelItem spentItem = iterator.next();             result.add(spentItem);         }         cursor.close();         return result;     }       public void removeFuelItem(long id) {         try {             prIndex.delete(id);         } catch (DatabaseException e) {             e.printStackTrace();             prIndex.delete(id);         }     } } 

Здесь надо обратить внимание на создание индексов, по которым потом осуществляется поиск и фильтрация. Т.е. если появляется необходимость искать и фильтровать данные по другому набору полей, то надо будет создавать дополнительный индекс.

Ещё одной особенностью работы с Berkley DB было написание классов-сущностей, которые используются для хранения информации. По задумке была реализована возможность Berkley DB хранить иерархию объектов.

@Persistent(version = 1) public class SpentItem implements Item{      @PrimaryKey(sequence="SPENT_ID")     private long id;     @SecondaryKey(relate= Relationship.MANY_TO_ONE)     private long odometer;     @SecondaryKey(relate= Relationship.MANY_TO_ONE)     private Date operationDate;     private double sum;      public long getId() {         return id;     }      public void setId(long id) {         this.id = id;     }      public long getOdometer() {         return odometer;     }      public void setOdometer(long odometer) {         this.odometer = odometer;     }      public Date getOperationDate() {         return operationDate;     }      public void setOperationDate(Date operationDate) {         this.operationDate = operationDate;     }      public double getSum() {         return sum;     }      public void setSum(double sum) {         this.sum = sum;     } }  @Entity(version = 1) public class FuelItem extends SpentItem {      private double count;     private double price;     private boolean full;      public double getCount() {         return count;     }      public void setCount(double count) {         this.count = count;     }      public boolean isFull() {         return full;     }      public void setFull(boolean full) {         this.full = full;     }      public double getPrice() {         return price;     }      public void setPrice(double price) {         this.price = price;     } } 

В классах сущностей, через аннотации передаётся информация:

  • о версии структуры объектов, которая сейчас должна храниться в БД. Если меняется структура объекта, то надо писать транслятор, который переведёт структуру данных из более ранней версии в текущую. Я решил проблему миграции через try/catch блок в конструкторе FuelItemDA.
  • Primary и Secondory ключах, по которым потом строятся индексы, которые у меня определяются на уровне DataAccess

Лично мне такой подход к организации хранения данных понравился. Т.к. для отображения мне нужны не столько данные, которые хранятся в БД, а логически обработанные, что проще делать именно с объектами.

В плюсы к работе с SQlite можно отнести привычный и более развитый инструментарий доступа к данным в виде SQL.
В плюсы к работе с Berkley Db можно отнести прямые CRUD операции над объектами, что облегчает последующую логическую работу с данными. Для меня это имело больший вес, нежели привычный интерфейс выдачи данных.

ссылка на оригинал статьи http://habrahabr.ru/post/172777/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *