Save complex objects and their sons entities ORM Lite - android

I'm using ORM Lite on a project , I decided to use the facility to make the persistence part of the Web service once and can reuse it on Android.
But I am suffering a lot because possou complex objects that have multiple ForeignCollectionField and Foreign object , and at the hour of perssistir temenda these data is a headache, because I have to enter one by one of their children , I think the idea of ​​an ORM is make life easier , ie you have to persist the object and father and all the rest is done behind the scenes ...
Well, it is now too late to give up lite ORM , I wonder if there is a way to do what sitei above ..
I found a piece of code here
tried to implement but it seems not work , just keeps saving the parent object .
follows the function I'm trying to use , but do not know whether imports are correct because the code I found in the link above did not have this data
public int create(Object entity, Context context) throws IllegalArgumentException, IllegalAccessException, SQLException, SQLException {
try{
if (entity!=null){
// Class type of entity used for reflection
Class clazz = entity.getClass();
// Search declared fields and save child entities before saving parent.
for(Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
// Inspect annotations
Annotation[] annotations = field.getDeclaredAnnotations();
try{
for(Annotation annotation : annotations) {
// Only consider fields with the DatabaseField annotation
if(annotation instanceof DatabaseField) {
// Check for foreign attribute
DatabaseField databaseField = (DatabaseField)annotation;
if(databaseField.foreign()) {
// Check for instance of Entity
Object object = field.get(entity);
Dao gDao = getDatabase(context).getDao(object.getClass());
gDao.create(object);
}
}else if (annotation instanceof ForeignCollectionField){
Object object = field.get(entity);
for(Object obj : new ArrayList<Object>((Collection<?>)object)){
Class c = obj.getClass();
Dao gDao = getDatabase(context).getDao(obj.getClass());
gDao.create(obj);
}
}
}
}catch (NullPointerException e){
e.printStackTrace();
}
}
// Retrieve the common DAO for the entity class
Dao dao = getDatabase(context).getDao(entity.getClass());
// Persist the entity to the database
return dao.create(entity);
}else
return 0;
}finally {
if (database != null) {
OpenHelperManager.releaseHelper();
database = null;
}
}
}
Leveraging the same post, also need a colução to delete cascade, imagine a situation where I have the following tables:
Company > Category> person> contact> Phone and email
Deleting and now I do as described in the documentation:
public int deleteCascade(Prefeitura prefeitura, Context context){
try{
Dao<Prefeitura, Integer> dao = getDatabase(context).getDao(Prefeitura.class);
DeleteBuilder db = dao.deleteBuilder();
db.where().eq("prefeitura_id", prefeitura.getId());
dao.delete(db.prepare());
// then call the super to delete the city
return dao.delete(prefeitura);
}catch (SQLException e){
e.printStackTrace();
}
return 0;
}
But the objects that are not directly linked the company would still be in the database, how could I do?
But without hacks, I want a clean code ...
I know ORM Lite really is lite, but one that saves the children create and delete cascade is essential for any ORM, hopefully for the next versions it is implemented, it is regrettable not have these features, for simple projects is very good, but in a complex project because a lot of headaches, I'm feeling on the skin.
Any help is welcome!

Related

Android Room - How to migrate when a nested object model is changed?

I have a database schema set up using Android Room, Dao, and Entity classes set up as POJOs. Except the POJO entity isn't so "plain" and that it actually holds a reference to another object. I thought this was a great idea at the time as it allowed me more flexibility in changing the object and using it in other places in the app and only saving to the database as needed.
The problem I'm facing now is that the migration guideline only mentions how to migrate the database by altering the SQL, but I changed the object itself. My typeconverter class simply converts the object to and from a string.
Because it's being saved as a long string I know I essentially have to do a simple REPLACE(string, old_string, new_string) in the SQL
migration code block with the updated object being the new string. How can I retrieve the old objects and update values before running the replace SQL command in the migration block?
UPDATE: I'm using GSON in my typeconverter class to change the object to a string, so the solution that comes to mind is to simply download the old object and upload the new one with the added fields. Only problem is that you can't access the database and download the json, convert it to the object, add the new data fields, then reconvert to a new json string.
I'm lucky I'm not at scale yet because this would be a tricky thing to do for so many users. (So I recommend that anyone reading this not do what I did and implement object nesting. It's easier to convert the Entry objects to the other portable objects instead of nesting when it comes to updating the data you want saved.)
I think if you already did what I did and can't go back, the best bet is to simply create the new portable object and make new typeconverter functions for that one and add the SQL COLUMN for the new object. The problem then lies in how you then retrieve those objects from the Entry Dao, which will cause a lot more code to write and possible errors to debug if not done carefully.
Long story short, if anyone is reading this, DO NOT nest objects in Room DBs on Android unless you are 100% sure it's a final form of your model... but is there such a thing anyways?
I just ran into this issue, but fortunately I only needed to add a new key/value pair to a "flat" object model. So hopefully my answer can be expanded on to fully answer #Mr.Drew question.
Assuming you have a table town with a column star_citizen that is the object model being typeconverted:
{"name":"John", "age":30, "car":false}
and you want to update the object to have an extra property "house": true
you could add a migration to your App's Room Database class like this (Kotlin example):
#Database(entities = [Town::class], version = 2, exportSchema = true)
#TypeConverters(DataConverters::class)
abstract class AppDatabase : RoomDatabase() {
abstract val sharedDao : SharedDao
companion object {
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val cursor = database.query("SELECT * FROM `town`")
// iterate through each row in `town`, and update the json
// of the StarCitizen object model
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val colIdIdx = cursor.getColumnIndex("id")
val id = cursor.getInt(colIdIdx)
val colStarCitizenIdx = cursor.getColumnIndex("star_citizen")
val rawJson = cursor.getString(colStarCitizenIdx)
val updatedRawJson = starCitizenModelV1ToV2(rawJson)
database.execSQL("""UPDATE town SET star_citizen ='${updatedRawJson}' WHERE ID = $id""")
cursor.moveToNext()
}
}
}
//[...]
private fun starCitizenModelV1ToV2(rawJson: String): String {
val rawJsonOpenEnded = rawJson.dropLast(1)
val newProperty = "\"house\":true"
return "$rawJsonOpenEnded,$newProperty}"
}
}
}

Not able to update sqlite_sequence table using RoomDatabase.query

We try to update sqlite_sequence with the following code.
WeNoteRoomDatabase weNoteRoomDatabase = WeNoteRoomDatabase.instance();
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'attachment'"));
However, it has no effect at all. I exam the sqlite_sequence table content using SQLite browser. The counter is not reset to 0.
If we try to run the same query manually using SQLite browser on same SQLite file, it works just fine.
Our Room database is pretty straightforward.
#Database(
entities = {Attachment.class},
version = 6
)
public abstract class WeNoteRoomDatabase extends RoomDatabase {
private volatile static WeNoteRoomDatabase INSTANCE;
private static final String NAME = "wenote";
public abstract AttachmentDao attachmentDao();
public static WeNoteRoomDatabase instance() {
if (INSTANCE == null) {
synchronized (WeNoteRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
WeNoteApplication.instance(),
WeNoteRoomDatabase.class,
NAME
)
.build();
}
}
}
return INSTANCE;
}
}
Any idea what we had missed out?
Additional information : clearing sqlite_sequence is not working in android room
Room doesn't use SQLiteDatabase - but it uses SupportSQLiteDatabase, while it's source code states, that it delegates all calls to an implementation of {#link SQLiteDatabase}... I could even dig further - but I'm convinced, that this is a consistency feature and not a bug.
SQLiteDatabase.execSQL() still works fine, but with SupportSQLiteDatabase.execSQL() the same UPDATE or DELETE queries against internal tables have no effect and do not throw errors.
my MaintenanceHelper is available on GitHub. it is important that one initially lets Room create the database - then one can manipulate the internal tables with SQLiteDatabase.execSQL(). while researching I've came across annotation #SkipQueryVerification, which could possibly permit UPDATE or DELETE on table sqlite_sequence; I've only managed to perform a SELECT with Dao... which in general all hints for the internal tables are being treated as read-only, from the perspective of the publicly available API; all manipulation attempts are being silently ignored.
i think query is wrong, you should try below query
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = attachment"));
I'm using room database version 2.2.5
Here I'm unable to execute this query using Room Dao structure, so make one simple class and access method as like this and I got successful outcomes so this one is tested result. I'm using RxJava and RxAndroid for same.
public class SqlHelper {
private static SqlHelper helper = null;
public static SqlHelper getInstance() {
if (helper == null) {
helper = new SqlHelper();
}
return helper;
}
public Completable resetSequence(Context context) {
return Completable.create(emitter -> {
try {
AppDatabase.getDatabase(context)
.getOpenHelper()
.getWritableDatabase()
.execSQL("DELETE FROM sqlite_sequence WHERE name='<YOUR_TABLE_NAME>'");
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
}
}
Execute:
SqlHelper.getInstance()
.resetQuizSequence(context)
.subscribeOn(Schedulers.io()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {}, error -> {});
Table sql_sequence is not managed by Room, so you need to edit it using a SupportSQLiteDatabase.
Try this:
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
weNoteRoomDatabase().getOpenHelper().getWritableDatabase().execSQL(sqlQuery);
This works for me - Room 2.2.6
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
<YourDatabase>.getInstance(mContext).getOpenHelper().getWritableDatabase().execSQL(sqlQuery);

greendao string primary keys - how to use

In the greendao FAQs it says "Starting from greenDAO there’s limited support for String primary keys." http://greendao-orm.com/documentation/technical-faq/
I can't find anywhere that says how to do this.
I am using Guids as my primary key in a server application, and want to be able to generate new data remotely from an android device and upload this back to the server. The database on the android device is in sqlite and uses greenDAO to generate POJOs and data access layer. I am using Guids to avoid primary key collisions when data is uploaded to the server. I am storing the Guids as strings.
There is some more advice on the greendao website that says I should create a secondary field holding the string and still use the long primary key favoured by greendao, but this means that I have to reconnect all my database relationships when I import data from the server to the app which is a pain. Would much rather just continue to use the string primary keys if that is possible.
Can anybody tell me how to do this?
Here is some example code...
In my generator (I've removed most of the fields for clarity):
private static void addTables(Schema schema)
{
Entity unit = addUnit(schema);
Entity forSale = addForSale(schema);
Property unitIntId = forSale.addLongProperty("unitIntId").getProperty();
forSale.addToOne(unit, unitIntId);
}
private static Entity addForSale(Schema schema)
{
Entity thisEntity = schema.addEntity("ForSale");
thisEntity.addIdProperty();
thisEntity.addStringProperty("forSaleId");
thisEntity.addFloatProperty("currentPriceSqFt");
thisEntity.addStringProperty("unitId");
return thisEntity;
}
private static Entity addUnit(Schema schema)
{
Entity thisEntity = schema.addEntity("Unit");
thisEntity.addIdProperty();
thisEntity.addStringProperty("unitId");
thisEntity.addStringProperty("name");
return thisEntity;
}
In my android application I download all the data from the server. It has relationships based on the GUID id's. I have to reattach these to the int Id's I created in the generator like this:
//Add relations based on GUID relations
//ForSale:Units
for(ForSale forSale:Globals.getInstance().forSales)
{
if (forSale.getUnitId() != null && forSale.getUnit() == null)
{
for(Unit unit:Globals.getInstance().units)
{
if (forSale.getUnitId().equals(unit.getUnitId()))
{
forSale.setUnit(unit);
break; //only need the first one
}
}
}
}
So I end up having two sets of Id's linking everything, the int one for greendao and the string (guid) one that will work when it gets uploaded back to the server. Must be an easier way!
Try this:
private static void addTables(Schema schema) {
Entity unit = addUnit(schema);
Entity forSale = addForSale(schema);
Property unitId = forSale.addStringProperty("unitId").getProperty();
forSale.addToOne(unit, unitId);
}
private static Entity addForSale(Schema schema) {
Entity thisEntity = schema.addEntity("ForSale");
thisEntity.addStringProperty("forSaleId").primaryKey();
thisEntity.addFloatProperty("currentPriceSqFt");
return thisEntity;
}
private static Entity addUnit(Schema schema) {
Entity thisEntity = schema.addEntity("Unit");
thisEntity.addStringProperty("unitId").primaryKey();
thisEntity.addStringProperty("name");
return thisEntity;
}
I don't know if the ToOne-Mapping will work with strings, though. If it doesn't you can add some methods for getting the related objects in the KEEP-SECTIONS.

How to support one to many relationships with Azure Mobile Services Android

I just recently starting using Azure Mobile Services. I am in the process of setting up my database. However I am not sure how I am going to handle one to many/many to many relationships. Azure Mobile has not out of the box support for this so I am not sure what else to do. One approach would be to just store the foreign key id in a particular entity and then run a query on that table to get all items that match a particular id. I think that might be a bit too time consuming. Not sure. Any suggestions or insights on how this can be done.
Would I have to resort to creating some form of logic on the backend with JavaScript to create the actual relationships on the server side?
One way to support one to many relationships using the AMS for Android is to create an ArrayList and store the foreign keys/ids inside the the particular table. That ArrayList then contains the ids of the relevant entities that you want to retrieve so all you would have to do is retrieve the array and extract the ids. AWS does not support serialization and deserialization for ArrayList at the moment therefore would you have to write a custom serializer to achieve this. This can be done by using GSON. Here is an example.
public class CollectionSerializer<E> implements JsonSerializer<Collection<E>>,
JsonDeserializer<Collection<E>> {
public JsonElement serialize(Collection<E> collection, Type type,
JsonSerializationContext context) {
JsonArray result = new JsonArray();
for(E item : collection){
result.add(context.serialize(item));
}
return new JsonPrimitive(result.toString());
}
#SuppressWarnings("unchecked")
public Collection deserialize(JsonElement element, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonArray items = (JsonArray) new JsonParser().parse(element.getAsString());
ParameterizedType deserializationCollection = ((ParameterizedType) type);
Type collectionItemType = deserializationCollection.getActualTypeArguments()[0];
Collection list = null;
try {
list = (Collection)((Class<?>) deserializationCollection.getRawType()).newInstance();
for(JsonElement e : items){
list.add((E)context.deserialize(e, collectionItemType));
}
} catch (InstantiationException e) {
throw new JsonParseException(e);
} catch (IllegalAccessException e) {
throw new JsonParseException(e);
}
return list;
}
}
After this is complete you then add it to the MobileServiceClient instance you created.
mClient.registerSerializer(ArrayList.class,new CollectionSerializer<Object>());
An alternative to what Joel mentioned is that you can have "regular" relationships on the client side, and then denormalize them - not only the keys, but the whole values - prior to inserting them on the server. The post at http://blogs.msdn.com/b/carlosfigueira/archive/2013/08/29/complex-types-and-azure-mobile-services-android-version.aspx shows some ways where this can be done.

OrmLite: Advanced where logic

I have these tables in an Android based application where I'm using OrmLite for the database management.
What I want to have an x number of array list depending on how many of the product type FOLDER I have.
So in this case I want to a list of products where the productId equals parentId.
So I want a list where
if(productType = FOLDER) {
if(productId = parentId){
//add product
}
}
Basically what I want to end up with, in this case three lists with each containing a list of products where parentId is the same for every product.
I've tried many things, and some works better than others, but a code I want to run actually throws a nullpointer.
DatabaseHelper dbHelper = getHelper();
List<Product> productsParents = null;
try {
Dao<Product, Integer> dao = dbHelper.getDao();
PreparedQuery<Product> prepQu = dao.queryBuilder().where()
.eq("parentId", dao.queryBuilder().selectColumns("productId").where()
.eq("productType", ProductType.FOLDER).prepare()).prepare();
productsParents = dao.query(prepQu);
} catch (SQLException e) {
...
}
This code isn't working because productParents returns null, and it does not do what I want, even though it's a slight hint. If someone know how to do this in code that would be sufficient also, or more likely a mix of java and ormlite.
Have you had a chance to RTFM around building queries? The ORMLite docs are pretty extensive:
http://ormlite.com/docs/query-builder
Your problem is that a prepared query cannot be an argument to the eq(...) method. Not sure where you saw an example of that form.
So there are a couple ways you can do this. The easiest way is to do a different query for each productType:
Where<Product, Integer> where = dao.queryBuilder().where();
where.eq("parentId", parentId).and().eq("productType", ProductType.FOLDER);
productsParents = where.query();
// then do another similar query again with ProductType.PRODUCT, ...
If you want to do just one query then you can get all products that match the parentId and then separate them using code:
Where<Product, Integer> where = dao.queryBuilder().where();
where.eq("parentId", parentId);
productsParents = where.query();
List<Product> productFolders = new ArrayList<Product>();
List<Product> productProducts = new ArrayList<Product>();
...
for (Product product : productsParents) {
if (product.getProductType() == ProductType.FOLDER) {
productFolders.add(product);
} else if (product.getProductType() == ProductType.PRODUCT) {
productProducts.add(product);
} else ...
}

Categories

Resources