I'm defining table/field names thru usual annotations, like:
#DatabaseTable(tableName = "M")
public class Headers {
//android requires primary key as _ID
#DatabaseField(generatedId = true, columnName = "_ID", dataType = DataType.INTEGER_OBJ)
private Integer id;
#DatabaseField(
columnName = "MM2",
uniqueCombo = true,
canBeNull = true, //null means root folder
dataType = DataType.INTEGER_OBJ
)
private Integer parentId;
}
Meantime somewhere deep in my code, I need to make some queries, like:
QueryBuilder<Headers, Integer> qb = headersDao.queryBuilder();
qb.where().eq("_ID", headerId); //_ID field name
This kind of code looks ugly, since field name (in this case _ID) is hardcoded (even if would be declared as final static String)
Question: is it normal OrmLite coding practice? I'd be expecting that I could use instead of hardcoded _ID field some small code. For sure OrmLite "knows" all field names. Anyone can help me?
Define a constant for the field name, like
private static final String FIELD_ID = "_ID";
// Or make it public as Gray said into comment
public static final String FIELD_ID = "_ID";
and use it as like
//android requires primary key as _ID
#DatabaseField(generatedId = true, columnName = FIELD_ID, dataType = DataType.INTEGER_OBJ)
private Integer id;
And into your query
QueryBuilder<Headers, Integer> qb = headersDao.queryBuilder();
qb.where().eq(FIELD_ID, headerId); //_ID field name
If you look at documentation
They also follow the same
QueryBuilder<Account, String> qb = accountDao.queryBuilder();
Where where = qb.where();
// the name field must be equal to "foo"
where.eq(Account.NAME_FIELD_NAME, "foo");
// and
where.and();
// the password field must be equal to "_secret"
where.eq(Account.PASSWORD_FIELD_NAME, "_secret");
PreparedQuery<Account, String> preparedQuery = qb.prepareQuery();
Related
I have ran into a situation where I have to create a db with large number of tables. When I wrote the code for creating the same, it became complex and confusing. I was wondering, whether it will be a good approach to create an sqlite db and ship it with the apk.
Which approach do you think would be good and why?
PS: on upgrading, i am dropping all the tables and creating new ones. No data is being retained.
Thanks a lot
If you only creating tables for the database without predefined data, you can create the tables when you need them. But if you have more than 1 MB data (not BLOB) in your databases, you should create the database outside then ship it in your apk. Because inserting 1 MB data takes times and the Android system will complaining with "Application Not Responding" (ANR) dialog. User don't like waiting too long to use the application.
You can use Android SQLiteAssetHelper to ship your SQLite database in your apk.
A few additional/alternative approaches.
My personal approach, which is an alternative to the two mentioned although an extension of creating the tables within the App, has been to create classes that a) simplifies table creation and b) caters for the limited dynamic addition of tables and columns.
It has the advantage of a single naming point (what i see as a pitfall of defining a database outside of the App) for the columns, tables and indexes.
It has the advantage of being directly re-usable. Although it has the disadvantage of the time taken to create the underlying classes (this to me was a benefit as I learnt a great deal being new to both Java and Android).
It does not require the complexities of dealing with copying/managing the database from the assets folder (you say there are none, my observation is that one of the more frequently occurring Android SQlite issues on SO is in regards to problems copying databases from the assets folder).
Some questions that could be asked, how does the App cope with deleting the App's data? If you keep a copy of the database to cater for this scenario is tying up the additional space (I believe it could be around 36k for just a database plus 4k per table (that is just from observations with a single database/table, so not investigated at any depth)). I doubt that the space for the extra code would be as much, but perhaps something that could be investigated.
It does not as yet cater for foreign keys/constraints or triggers
(never used any as of yet).
There are 5 classes DBColumn, DBTable, DBIndex, DBDatabase and SQLKWORDS.
SQLKWORDS contains definitions of commonly used SQL KEYWORDS e.g.
public class SQLKWORD {
public static final String SQLTABLE = " TABLE ";
public static final String SQLCREATE = " CREATE ";
public static final String SQLDROP = " DROP ";
public static final String SQLINDEX = " INDEX ";
public static final String SQLUNIQUE = " UNIQUE ";
.....
}
DBColumn is used for defining columns
These are used to progressively build a DBDatabase, which is basically the required schema plus various methods e.g. actionBuildSQL which will add any tables that don't exist, a complimentary actionAlterSQL caters for the addition of new columns.
For example to define a table (4 columns first 3 make up the primary index (3rd parameter true if to be part of the PRIMARY INDEX, else false)):-
import mjt.dbcolumn.DBColumn;
import mjt.dbtable.DBTable;
import static mjt.sqlwords.SQLKWORD.*;
public class DBCardPayeeLinkTableConstants {
static final String CARDPAYEELINK_TABLE = "cardpayeelink";
private static final String CARDREF_COL = "cardref";
private static final String PAYEEREFCOL_COL = "payeeref";
private static final String USERREF_COL = "userref";
private static final String USAGE_COUNT_COL = "usagecount";
// DBCOlumns
static final DBColumn CARDREF = new DBColumn(
CARDREF_COL,
SQLINTEGER,
true,
""
);
static final DBColumn PAYEEREF = new DBColumn(
PAYEEREFCOL_COL,
SQLINTEGER,
true,
""
);
static final DBColumn USERREF = new DBColumn(
USERREF_COL,
SQLINTEGER,
true,
""
);
static final DBColumn USAGECOUNT = new DBColumn(
USAGE_COUNT_COL,
SQLINTEGER,
false,
"0"
);
static final ArrayList<DBColumn> CARDPAYEELINKCOLUMNS =
new ArrayList<>(
Arrays.asList(
CARDREF,
PAYEEREF,
USERREF,
USAGECOUNT
)
);
static final DBTable CARDPAYEELINKS = new DBTable(
CARDPAYEELINK_TABLE,
CARDPAYEELINKCOLUMNS
);
// define full column names i.e. table.columnname
public static final String CARDREF_FCOL =
CARDREF.getFullyQualifiedDBColumnName();
public static final String PAYEEREF_FCOL =
PAYEEREF.getFullyQualifiedDBColumnName();
public static final String USEREF_FCOL =
USERREF.getFullyQualifiedDBColumnName();
public static final String USAGECOUNT_FCOL =
USAGECOUNT.getFullyQualifiedDBColumnName();
}
There are a number of DBCOlumn constructors e.g. DBColumn(true) will create the commonly used _id column. As can be seen from the above there are also various methods.
DBTable takes a list of DBColumns and DBDatabase takes a list of DBTables (and if required DBIndexes which themselves are take a DBTable and a list of DBColumns). DBDatabase's methods such as actionAlterSQL compares the required schema against the actual current schema.
I will then have a class such as the following:-
class DBConstants {
/*
Define the database name
*/
static final String DATABASENAME = "cardoniser";
static final int DATABASEVERSION = 1;
/*
Define the DBDatabase instance (base schema),
it consists of the DBTable instances which are
comproised of DBColumns (and optionally DBindex instances)
*/
static final DBDatabase cardonsier = new DBDatabase(DATABASENAME,
new ArrayList<>(Arrays.asList(
DBCardsTableConstants.CARDS,
DBUsertableConstants.USERS,
DBPreftableConstants.PREFS,
DBPayeestableContants.PAYEES,
DBCategoriestableConstants.CATEGORIES,
DBCardCategoryLinkTableConstants.CARDCATEGORYLINKS,
DBCardPayeeLinkTableConstants.CARDPAYEELINKS,
DBTransactionsTableConstants.TRANSACTIONS
))
);
}
in my DBHelper I have (nothing done in onUpgrade, onCreate simply calls onExpand) :-
#Override
public void onCreate(SQLiteDatabase db) {
usable = this.onExpand(db,false);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldversion, int newversion) {
}
public boolean onExpand(SQLiteDatabase db, boolean buildandexpand) {
boolean rv = true;
if (db == null) {
db = instance.getWritableDatabase();
}
// Is the Database definition valid to use?
if (DBConstants.cardonsier.isDBDatabaseUsable()) {
ArrayList<String> buildsql = DBConstants.cardonsier.generateDBBuildSQL(db);
// Anything to build
// will not be if database is unchanged rather than
// if no definitions/valid definitions as this would
// be detected by isDBDatabaseUsable
if (!buildsql.isEmpty()) {
// YES so build the Database
DBConstants.cardonsier.actionDBBuildSQL(db);
}
if (buildandexpand) {
ArrayList<String> altersql = DBConstants.cardonsier.generateDBAlterSQL(db);
if (!altersql.isEmpty()) {
DBConstants.cardonsier.generateExportSchemaSQL();
}
}
}
else {
rv = false;
}
return rv;
}
public boolean isDBUsable() {
return usable;
}
In the initial Activity I have, which will apply any table or row additions and additionally index additions, deletions or alterations(delete and recreate index) :-
DBDAO dbdao = new DBDAO(this);
DBHelper.getHelper(this).onExpand(dbdao.getDB(),true);
In addition to this I use a single class per table (actually two as I also have a separate class for table specific methods).
All in all there are few complexity issues and for myself, having done the groundwork, is the better of all three options.
Another option that could be considered.
There are plenty of SQLite Database tools e.g. SQLite Manager (browser extension) that would allow you to create a database and export the SQL.
e.g. the above database (well close to it) was retrieved from SQL Manager as per :-
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE cardcategorylink (cardref INTEGER , categoryref INTEGER , usagecount INTEGER DEFAULT 0 , PRIMARY KEY (cardref, categoryref) );
CREATE TABLE cardpayeelink (cardref INTEGER , payeeref INTEGER , usagecount INTEGER DEFAULT 0 , PRIMARY KEY (cardref, payeeref) );
CREATE TABLE cards (_id INTEGER PRIMARY KEY, cardname TEXT , cardtyperef INTEGER DEFAULT 0 , cardnumber TEXT );
CREATE TABLE categories (_id INTEGER PRIMARY KEY, categoryname TEXT );
CREATE TABLE payees (_id INTEGER PRIMARY KEY, payeename TEXT );
CREATE TABLE prefs (_id INTEGER PRIMARY KEY, prefname TEXT , preftype TEXT , prefvalue TEXT , prefshortdesc TEXT , preflongdesc TEXT );
CREATE TABLE transactions (_id INTEGER PRIMARY KEY, timestamp INTEGER DEFAULT 0 , cardref INTEGER DEFAULT 0 , payeeref INTEGER DEFAULT 0 , categoryref INTEGER DEFAULT 0 , amount REAL DEFAULT 0.00 , flags INTEGER DEFAULT 0 );
CREATE TABLE users (_id INTEGER PRIMARY KEY, username TEXT , userhash TEXT , usersalt TEXT );
(note the above database was actually copied into SQLite Manager via Android Device Manager, so it was really generated using the Approach that I have adopted).
If you source is MySQL or others then I believe that PHPMyAdmin has similar functionality.
I created an entity which need to identity by Short only.
Here is my generated code:
public Source(Short id, String name) {
this.id = id;
this.name = name;
}
TestCode DatabaseHelperTest.java:
public void testInsertAndLoad(){
Source source = new Source((short) 0, "TestSource");
SourceDao sourceDao = daoSession.getSourceDao(); //#line 26
sourceDao.insert(source);
Short id = source.getId();
assertNotNull(id);
}
When I run test, I got the ClassCastException:
Running tests
Test running started
java.lang.ClassCastException: java.lang.Short cannot be cast to java.lang.Long
at de.greenrobot.dao.identityscope.IdentityScopeLong.put(IdentityScopeLong.java:31)
at de.greenrobot.dao.AbstractDao.attachEntity(AbstractDao.java:695)
at de.greenrobot.dao.AbstractDao.updateKeyAfterInsertAndAttach(AbstractDao.java:362)
at de.greenrobot.dao.AbstractDao.executeInsert(AbstractDao.java:355)
at de.greenrobot.dao.AbstractDao.insert(AbstractDao.java:293)
at com.tuanchau.DatabaseHelperTest.testInsertAndLoad(DatabaseHelperTest.java:26)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:190)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:175)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1584)
So, does GreenDAO allow to make short become primary key?
And, how can I deal with this exception.
Thanks
Update:
DB Generation code
Entity source = schema.addEntity("Source");
Entity category = schema.addEntity("Category");
source.addShortProperty("id").primaryKey().getProperty();
source.addStringProperty("name").notNull();
category.addIntegerProperty("id").primaryKey().getProperty();
category.addStringProperty("name").notNull();
Property csid = category.addLongProperty("sid").notNull().getProperty();
category.addToOne(source, csid);
Source Properties
public static class Properties {
public final static Property Id = new Property(0, Short.class, "id", true, "ID");
public final static Property Name = new Property(1, String.class, "name", false, "NAME");
};
Category Properties
public static class Properties {
public final static Property Id = new Property(0, Integer.class, "id", true, "ID");
public final static Property Name = new Property(1, String.class, "name", false, "NAME");
public final static Property Sid = new Property(2, short.class, "sid", false, "SID");
};
From greenDao website:
Current primary key (PK) restrictions: Currently, entities must have a long or Long property as their primary key. This is recommended practice for Android and SQLite. greenDAO is prepared to handle any primary key scenario in the future, but not everything is implemented completely yet. To work around this issue, you can use a long primary key and use an unique index for the intended “key” properties.
You could try using something like this:
source.addIdProperty();
source.addShortProperty("shortId").unique().getProperty();
source.addStringProperty("name").notNull();
I have a problem with a foreign key in ORMLite I have 2 classes QuestionDb and ResponsesDb which are the following :
public class ResponsesDb {
public static final String FIELD_ID = "id";
#DatabaseField(generatedId = true,columnName=FIELD_ID)
private int id;
#DatabaseField(canBeNull = false, foreign = true, foreignColumnName=QuestionDb.FIELD_REF)
private QuestionDb question;
#DatabaseField(canBeNull = false)
private String answer;
}
And :
#DatabaseTable(tableName = "question")
public class QuestionDb implements Serializable {
public static final String FIELD_REF = "ref";
private static final long serialVersionUID = 4106020204304605623L;
#DatabaseField(generatedId = true)
private int id;
#DatabaseField(canBeNull = false, unique = true, columnName=FIELD_REF, index=true)
private String ref;
#ForeignCollectionField(foreignFieldName = "question", eager = true)
private ForeignCollection<ResponsesDb> responses;
}
My problem is when i do that :
QueryBuilder<QuestionDb, Integer> questionQuery = helper
.getQuestionDao().queryBuilder();
QueryBuilder<ResponsesDb, Integer> responseQuery = helper
.getResponseDao().queryBuilder();
responseQuery = responseQuery.join(questionQuery);
I recieve that :
05-27 12:00:01.577: W/System.err(7272): java.sql.SQLException: Could not find a foreign class model.ormlite.tableClass.ResponsesDb field in class model.ormlite.tableClass.QuestionDb or vice versa
But if I remove the field foreignColumnName=QuestionDb.FIELD_REF from question field's annotation in ResponsesDb, the query works.
The fact is that as my program update the database, the id field can change so I want that the foreign key is ref. Do you have any idea how I can fix this problem ?
You can use a string as a foreign key. What you cannot do is define a foreign key with a foreignColumnName that is not the key of the other object.
From the example in the ORMLite documentation:
With foreign objects, just the id field from the Account is persisted
to the Order table as the column "account_id".
In this case, you have in QuestionDb:
#DatabaseField(generatedId = true)
private int id;
The generatedId annotation means that this is the id of the table. Having marked ref as unique will create a unique index, but it does not make it a candidate key. Thus it cannot be used as a foreign key from another table.
In short: if you need a string foreign key, then you can. Just define a string primary key in the referenced table. (i.e. remove the id field and put #DatabaseField(id = true) in the ref field.
Going further back, howerver, I don't understand why you claim:
The fact is that as my program update the database, the id field can
change
The generatedId value will not change for a row after inserting it. It's perfect for use as a foreign key! :)
I'm new to Android and I have problem with ORMLITE.
For example let's say I have this table:
#DatabaseTable(tableName = "accounts")
public class Account {
#DatabaseField(id = true)
private int id;
#DatabaseField(canBeNull = false)
private String name;
…
and I want to add new data into my table without setting id.
I tried this way:
#DatabaseTable(tableName = "accounts")
public class Account {
#DatabaseField(generatedId = true,allowGeneratedIdInsert=true)
private int id;
#DatabaseField(canBeNull = false)
private String name;
…
> Account acc = new Account();
>
> acc.setName("Example");
>
> AccountDao.createOrupdate(acc);
Here I can't insert acc into my database because acc id is zero. I want to generate id. Can I use autoincrement?
Here i can't insert acc into my database because acc id is zero. I want to generate id. Can i use autoincrement ?
So to quote the javadocs for the allowGeneratedIdInsert field in #DatabaseField:
If this is set to true then inserting an object with the ID field already set will not override it with a generated-id. This is useful when you have a table where items sometimes have IDs and sometimes need them generated. This only works if the database supports this behavior and if generatedId() is also true for the field.
So if you have acc.id set to a non-0 value, it should be inserted into the database with the id from acc. If you want acc to get an auto-generated id then you should just set acc.id to be 0.
For an example, you could take a look at the ORMLite Android test class. Search for the testCreateWithAllowGeneratedIdInsert() method which has code like:
AllowGeneratedIdInsert foo = new AllowGeneratedIdInsert();
assertEquals(1, dao.create(foo));
AllowGeneratedIdInsert result = dao.queryForId(foo.id);
assertEquals(foo.id, result.id);
...
AllowGeneratedIdInsert foo3 = new AllowGeneratedIdInsert();
foo3.id = 10002;
assertEquals(1, dao.create(foo3));
result = dao.queryForId(foo3.id);
assertEquals(foo3.id, result.id);
NOTE: the docs mention that this only works if the database supports it but Sqlite is one of those databases.
Use Wrapper class instead of int
#DatabaseField(id = true)
private Integer id;
I am using OrmLite 4.46 to manage my database for my android application.
And i have one problem:
I have the following code for my model:
public class Item extends Model {
#DatabaseField(generatedId = true)
private long id;
#DatabaseField(columnName = "item_name", defaultValue = "")
private String name;
#DatabaseField(columnName = "item_count", defaultValue = "0")
private int count;
public Item() {
super();
}
}
And the problem is here :
#DatabaseField(columnName = "item_name", defaultValue = "")
private String name;
When i am creating a new Item() with no arguments and i save it in the database, normally in the column item_name it should save an empty String.
But when i am retrieving the item from the database and i try
String itemName = item.getName().trim() I get a NullPointerException
So it seems that the name is null.
Also i checked the created table from the above model in the sqlite db file and when i set defaultValue="" the column is created with no default value.
Does anybody know of any solution to this problem?
You can initialize name field to empty string at class level like:
#DatabaseField(columnName = "item_name", defaultValue = "")
private String name = "";
This will initialize name field to empty string when you create object of class Item and save it to database.
When you retrieve it from database the name field will be empty ("") instead of null.
When i am creating a new Item() with no arguments and i save it in the database, normally in the column item_name it should save an empty String.
Although #Rakesh's answer is correct, this turns out to be a bug in ORMLite. The code is supposed to insert the defaultValue during a create if the field is null. However, there was an errant .equals("") that was stopping this.
I've submitted the following bug report and have fixed it in trunk. It will be in 4.47.