После успешно пройденного этапа «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 операции над объектами, что облегчает последующую логическую работу с данными. Для меня это имело больший вес, нежели привычный интерфейс выдачи данных.
Автор: gubber