Причём документация от 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/
Добавить комментарий