После успешно пройденного этапа «Hello World» под Android, решил написать для интереса простенькое приложение под Android, основной функционал которого сводился к хранению некоторого набора данных на устройстве. И очень мне не хотелось работать c SQL. Привык как-то уже работать с объектами. По-этому порыскав по интернету в поисках совместимых с Android решений нашёл только одно — Berkeley DB, встраиваемая БД.
Причём документация от Oracle показывала значительно лучшие показатели по производительности по сравнению с SQlite.По этому для своего приложения (дальше моего телефона оно так и не ушло) я выбрал этот формат хранения данных.
Класс являющийся ядром работы с БД сделан по шаблону Singleton, и получился следующим:
Проблемы этого класса достаточно прозаичны, перед тем как получить доступ к сущности, её надо инициализировать, что можно забыть. Плюс, выскочила проблема создания/закрытия транзакции. Транзакция открывается в одном классе, а закрывается в другом, что так же выглядит не самым лучшим образом с точки зрения разработки. Пока эту «оплошность» я не смог «красиво» исправить. Особенно криво это смотрится в свете того, что транзакции используются для того, чтобы получить следующее значение идентификатора для сохраняемой сущности.
На более высоком уровне были созданы классы доступа к данным DataAccess.
Здесь надо обратить внимание на создание индексов, по которым потом осуществляется поиск и фильтрация. Т.е. если появляется необходимость искать и фильтровать данные по другому набору полей, то надо будет создавать дополнительный индекс.
Ещё одной особенностью работы с Berkley DB было написание классов-сущностей, которые используются для хранения информации. По задумке была реализована возможность Berkley DB хранить иерархию объектов.
В классах сущностей, через аннотации передаётся информация:
Лично мне такой подход к организации хранения данных понравился. Т.к. для отображения мне нужны не столько данные, которые хранятся в БД, а логически обработанные, что проще делать именно с объектами.
Осталось только инициализировать DatabaseConfig, и здесь вообще ни каких сложностей нет.
В плюсы к работе с SQlite можно отнести привычный и более развитый инструментарий доступа к данным в виде SQL.
В плюсы к работе с Berkley Db можно отнести прямые CRUD операции над объектами, что облегчает последующую логическую работу с данными. Для меня это имело больший вес, нежели привычный интерфейс выдачи данных.
P.S.
Ссылка для скачивания. Нужна версия
Berkeley DB Java. Внутри архива найдёте библиотеку для Android.
Причём документация от 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;
....
}
@Entity(version = 1)
public class FuelItem extends SpentItem {
private double count;
private double price;
private boolean full;
.....
}
В классах сущностей, через аннотации передаётся информация:
- о версии структуры объектов, которая сейчас должна храниться в БД. Если меняется структура объекта, то надо писать транслятор, который переведёт структуру данных из более ранней версии в текущую. Я решил проблему миграции через try/catch блок в конструкторе FuelItemDA.
- Primary и Secondory ключах, по которым потом строятся индексы, которые у меня определяются на уровне DataAccess
Лично мне такой подход к организации хранения данных понравился. Т.к. для отображения мне нужны не столько данные, которые хранятся в БД, а логически обработанные, что проще делать именно с объектами.
Осталось только инициализировать DatabaseConfig, и здесь вообще ни каких сложностей нет.
public class Calc extends Activity {
private void setup() throws DatabaseException {
File envDir = new File(android.os.Environment.getExternalStorageDirectory(), "data");
envDir = new File(envDir, "autoexpence");
if (!envDir.exists()) {
if (!envDir.mkdirs()) {
Log.e("TravellerLog :: ", "Problem creating Image folder");
}
}
DatabaseConfig.init(envDir);
}
}
В плюсы к работе с SQlite можно отнести привычный и более развитый инструментарий доступа к данным в виде SQL.
В плюсы к работе с Berkley Db можно отнести прямые CRUD операции над объектами, что облегчает последующую логическую работу с данными. Для меня это имело больший вес, нежели привычный интерфейс выдачи данных.
P.S.
Ссылка для скачивания. Нужна версия
Berkeley DB Java. Внутри архива найдёте библиотеку для Android.