How to retrieve all the SQL queries generated by Room Android?
My Dao class:
#Dao
public interface MyDao {
#Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
I want to retrieve the content of #Query annotation.
I know I can use an annotation processor but this does not allow me to retrieve the queries used to create database tables and the one generated from #Insert, #Update, and #Delete.
I want to reuse those queries in another app with sqlite3 in Node.JS.
Edit:
I want to retrieve the queries programmatically to prevent writing them twice, once for the android app and once again for the Node.JS app.
You can get all the queries via the generated code yourDao(s) suffixed with _impl and your#DatabaseClass(s) also suffixed with _impl
e.g. For
#Dao
public interface CustomerDao {
#Insert
long insert(Customer customer);
#Query("INSERT INTO Customer (firstname, lastName, dateAdded) VALUES(:firstName,:lastName,:dateAdded)")
long insert(String firstName, String lastName, String dateAdded);
#Query("SELECT * FROM customer WHERE firstName=:firstName AND lastName = :lastName")
Customer getCustomerByName(String firstName, String lastName);
#Query("SELECT * FROM customer")
LiveData<List<Customer>> getAllCustomers();
}
then CustomerDao_Impl is :-
#SuppressWarnings({"unchecked", "deprecation"})
public final class CustomerDao_Impl implements CustomerDao {
private final RoomDatabase __db;
private final EntityInsertionAdapter<Customer> __insertionAdapterOfCustomer;
private final SharedSQLiteStatement __preparedStmtOfInsert;
public CustomerDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfCustomer = new EntityInsertionAdapter<Customer>(__db) {
#Override
public String createQuery() {
return "INSERT OR ABORT INTO `Customer` (`id`,`firstName`,`lastName`,`dateAdded`) VALUES (?,?,?,?)";
}
..........
and for ShopDatabase
#Database(entities = {Customer.class}, version = 1)
//#TypeConverters({Converters.class})
public abstract class ShopDatabase extends RoomDatabase {
private static ShopDatabase instance;
..........
then ShopDatabase_Impl is :-
#SuppressWarnings({"unchecked", "deprecation"})
public final class ShopDatabase_Impl extends ShopDatabase {
private volatile CustomerDao _customerDao;
#Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
#Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `Customer` (`id` INTEGER, `firstName` TEXT, `lastName` TEXT, `dateAdded` TEXT, PRIMARY KEY(`id`))");
_db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
_db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '588832abfd9fd9c6aad8dcebe485e219')");
}
#Override
public void dropAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("DROP TABLE IF EXISTS `Customer`");
if (mCallbacks != null) {
for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
mCallbacks.get(_i).onDestructiveMigration(_db);
}
}
}
..........
your would probably not want the room_master_table as that is room specific and the hash is generated, I believe, from the code so is sensitive to changes being made.
where .......... indicates code omitted for brevity
If somebody else wants to export all queries automatically, what I have done instead of getting them manually in each of my DaoClassName_Impl is to export my room database schema then processing the json file exported. If you want to create a java module in android studio for easy reuse, I suggest to create an annotation processor (even if it does not process any annotation) that will run automatically every time you build your project.
Related
Any thoughts, suggestions, or ideas on best practice or option for the following?
Problem: With Android Room, what is the practical way to approach creating and handling multiple databases in a single Android app?
What I'm trying to do: I have an Android app intended to manage multiple research topics. The idea is an user can create databases specific to subjects or research and able to store sources, attachments and notes on those subjects. For example, an user can have a database specific to the history of modern music, a second database on the subject of the history of hunting, and even subjects as deep as micro-biological research.
My thoughts were to have separate databases vs. one database that stores all this data. Especially since attachments can be stored and take up space quite quickly. And these databases can be shared between the phone / tablet app and a desktop version. There is a Java desktop version of this being used.
What I've Done" I've really only searched here and googled some but appear kind-of vague. I'm familiar with and have migrated changes to a database but wasn't sure if this would always be the best way to create a new database, as well as renaming, etc.
This Android app comes with a predefined and pre-popuated database as a demonstration. This database hasn't changed for 2 years now. So, the idea was possibly having a "template.db" that could be used to create new databases and rename them accordingly.
With Android Room, what is the practical way to approach creating and handling multiple databases in a single Android app?
You can certainly handle multiple databases and multiple databases based upon the same schema.
The issue is how to ascertain what databases there are that can be used. If all the databases were located in the same path (or even multiple paths) then this could be used. Another methodology could be to have a database of the databases.
Here's an example that utilises the databases of databases (the "MasterDatabase") and allows access to x databases.
So first the MasterDatabase which has a simple table with an id column (could be dispensed with) and a column for the database name. The table (#Entity) being named MasterDatabaseList as per :-
#Entity(
indices = { #Index(value = "databaseName", unique = true)
}
)
class MasterDatabaseList {
#PrimaryKey
Long id;
String databaseName;
public MasterDatabaseList() {}
#Ignore
public MasterDatabaseList(String databaseName) {
this.databaseName = databaseName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDatabaseName() {
return databaseName;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
}
Note the unique index on the databaseName column
Accompanying the table is MasterDao an #Dao class :-
#Dao
abstract class MasterDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(MasterDatabaseList masterDatabaseList);
#Query("SELECT * FROM masterdatabaselist")
abstract List<MasterDatabaseList> getAllDatabases();
}
allows rows to be inserted or extracted.
a duplicated database will be ignored and thus not added.
MasterDatabase is the #Database class (which ties the previous classes to the database) and includes a method to get an instance of the database from which the MasterDao can be accessed :-
#Database(
entities = {MasterDatabaseList.class},
version = 1
)
abstract class MasterDatabase extends RoomDatabase {
abstract MasterDao getMasterDao();
static volatile MasterDatabase instance = null;
public static MasterDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,MasterDatabase.class,"master.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
Now the template database, Base???? (a simple single table database for the demo). First the table BaseTable #Entity class:-
#Entity
class BaseTable {
#PrimaryKey
Long id;
String mydata;
public BaseTable(){}
#Ignore
public BaseTable(String myData) {
this.mydata = myData;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMydata() {
return mydata;
}
public void setMydata(String mydata) {
this.mydata = mydata;
}
}
a very simple table with an id column and a column that holds some string data.
Accompanying is the #Dao class BaseDao :-
#Dao
abstract class BaseDao {
#Insert
abstract long insert(BaseTable baseTable);
#Query("SELECT * FROM basetable")
abstract List<BaseTable> getAllBaseTables();
#Update
abstract int update(BaseTable baseTable);
}
with very basic insert, extract and update
and as before the #Database class BaseDatabase :-
#Database(
entities = {BaseTable.class},
version = 1
)
abstract class BaseDatabase extends RoomDatabase {
abstract BaseDao getBaseDao();
public static BaseDatabase getInstance(Context context, String databaseName) {
BaseDatabase instance = null;
if (databaseName != null) {
return Room.databaseBuilder(context, BaseDatabase.class, databaseName)
.allowMainThreadQueries()
.build();
}
return instance;
}
}
note how the database name needs to be passed, that is basically the crux of catering for multiple databases.
With all of that a Demo Activity.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "DBINFO";
MasterDatabase masterDB;
MasterDao masterDao;
/* 3 Lists that need to be synchronised index wise */
/* i.e. each index position should hold the respective name/databaseobject/dao
/* List of the known databases (their names) */
List<MasterDatabaseList> masterDatabaseListList = null;
/* List of the BaseDatabase objects */
ArrayList<BaseDatabase> baseDatabaseList = new ArrayList<>();
/* List of the BaseDao's */
ArrayList<BaseDao> baseDaoList = new ArrayList<>();
/* The current database */
int currentBaseIndex = -1; /* Index into the three Lists */
BaseDatabase currentDB = null;
BaseDao currentDao = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
masterDB = MasterDatabase.getInstance(this);
masterDao = masterDB.getMasterDao();
masterDatabaseListList = masterDao.getAllDatabases();
// Add default db1 if it does not exist
if (masterDatabaseListList.size() < 1) {
addBaseDB("db1");
}
buildBaseLists();
/* Add some data to db1 IF it exists (it should) */
setCurrentIndexDBandDao("db1");
if (currentBaseIndex > -1) {
currentDao.insert(new BaseTable("Blah for db1"));
}
/* Add some data to db2 (it will not exist) */
/* noting that the database will be created if it does not exist */
setCurrentIndexDBandDao("db2");
if (currentBaseIndex == -1) {
addBaseDB("db2");
}
if (currentBaseIndex > -1) {
currentDao.insert(new BaseTable("Blah for db2"));
}
/* Extract and Log Data for ALL the BaseDatabase databases i.e. db1 and db2 */
for(MasterDatabaseList masterdb: masterDao.getAllDatabases()) {
Log.d(TAG,"Database is " + masterdb.getDatabaseName());
setCurrentIndexDBandDao(masterdb.databaseName);
if (currentBaseIndex > -1) {
for(BaseTable bt: currentDao.getAllBaseTables()) {
Log.d(TAG,"Extracted Base Table row where MyData is" + bt.getMydata());
}
}
}
}
/* Add a new Database */
/* Note that it assumes that it will now be the current */
/* so the current values are set */
private void addBaseDB(String baseDBName) {
masterDao.insert(new MasterDatabaseList(baseDBName));
buildBaseLists();
setCurrentIndexDBandDao(baseDBName);
}
/* Build/ReBuild the 3 Lists according to the master database */
/* This could be better managed so as to not rebuild existing database/dao objects */
private void buildBaseLists() {
int ix = 0;
baseDatabaseList.clear();
baseDaoList.clear();
masterDatabaseListList = masterDao.getAllDatabases();
// Loop through the databases defined in the master database adding the database and dao to the respective lists
for (MasterDatabaseList masterDB: masterDao.getAllDatabases()) {
BaseDatabase baseDB = BaseDatabase.getInstance(this, masterDB.getDatabaseName());
baseDatabaseList.add(baseDB);
baseDaoList.add(baseDB.getBaseDao());
ix++;
}
}
/* Set the current trio according to the database name that is:*/
/* 1.the currentBaseIndex for the 3 Lists */
/* 2. the BaseDatabase object */
/* 3. the BaseDao */
/* The index value (currentBaseIndex) is also returned */
private int setCurrentIndexDBandDao(String baseDBName) {
currentBaseIndex = getListIndexByBaseDBName(baseDBName);
if (currentBaseIndex > -1) {
currentDB = baseDatabaseList.get(currentBaseIndex);
currentDao = baseDaoList.get(currentBaseIndex);
}
return currentBaseIndex;
}
/* Get the index according to the database name passed */
/* note -1 signifies not know/found */
private int getListIndexByBaseDBName(String baseDBName) {
masterDatabaseListList = masterDao.getAllDatabases(); // OverKill????
int rv = -1; // default to not found
for(int i=0; i < masterDatabaseListList.size();i++) {
if (masterDatabaseListList.get(i).databaseName.equals(baseDBName)) {
rv = i;
break;
}
}
return rv;
}
/* Output all rows from the BaseTable for data extracted by the BaseDaos getAllBaseTables */
private void logBaseData(List<BaseTable> baseTableList) {
Log.d(TAG,"Current Database Index is " + currentBaseIndex + " DB name is " + masterDatabaseListList.get(currentBaseIndex).getDatabaseName());
for(BaseTable bt: baseTableList) {
Log.d(TAG,"\tMyData value is " + bt.getMydata());
}
}
}
Result
When the above is run for the first time the log includes:-
2021-09-16 11:39:30.262 D/DBINFO: Database is db1
2021-09-16 11:39:30.278 D/DBINFO: Extracted Base Table row where MyData isBlah for db1
2021-09-16 11:39:30.278 D/DBINFO: Database is db2
2021-09-16 11:39:30.284 D/DBINFO: Extracted Base Table row where MyData isBlah for db2
And via Android Studio's App Inspector the databases :-
and for the db2 BaseTable :-
Note The above is only intended to cover the basics of utilising multiple databases in what is intended to be a simplistic explanation, as such the code has been kept short and simple. It would probably be unacceptable, as it is, for an App that would be distribtued.
I am adding new entity FeedItem in my Room database and writing a migration for it.
Problem: I have a Date type in my FeedItem class, which is not primitive type. What is the proper way to write migration in this case?
#Entity(tableName = "FeedItem")
public class FeedItem implements Item, Votable {
private int id;
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "feedItemRowIndex")
private int rowIndex;
private int toId;
private int fromId;
private Date date;
...
my migration currently looks like this.
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE FeedItem (feedItemId INTEGER, " +
"feedItemRowIndex INTEGER, " +
"feedVotes INTEGER" +
"feedVote INTEGER" +
"toId INTEGER" +
"fromId INTEGER" +
"date Date" + // i need to change this row
...
"PRIMARY KEY (feedItemRowIndex))"
and here converter for Date type
public class DateConverter {
#TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}
#TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
thanks!
If room.schemaLocation is set in your build.gradle file, then you can look at the generated schema and copy the exact sql that Room would use to create the table.
Complete example here
This was quite easy. Room generates primitive type appropriate to converter type.
So in my case type is INTEGER. Thanks to #EpicPandaForce for comment
I have 2 databases in the project, one of them created when I open the app, the other is provided with the assets.
When DaoSession is generated the DaoSession is created for all models.
also the Schema in the gradle file is used for both databases
How can I differentiate between the 2 databases and their schemas ?
You need to create two different classes which extends org.greenrobot.greendao.database.DatabaseOpenHelper. These two different classes DevOpenHelperForDatabase1 and DevOpenHelperForDatabase2
will handle db realted I/o. It is versy easy to understand from the below code to create two different database with same and different schema or table or entities:
public class App extends Application {
private DaoSessionForDatabase1 mDaoSessionForDatabase1;
private DaoSessionForDatabase2 mDaoSessionForDatabase2;
#Override
public void onCreate() {
super.onCreate();
//Create Doa session for database1
DevOpenHelperForDatabase1 devOpenHelperForDatabase1 = new DevOpenHelperForDatabase1(this,
"database1-db");
Database databse1 = devOpenHelperForDatabase1.getWritableDb();
mDaoSessionForDatabase1 = new DaoMasterForDatabase1(databse1).newSession();
//Create Doa session for database2
DevOpenHelperForDatabase2 devOpenHelperForDatabase2 = new DevOpenHelperForDatabase2(this,
"database2-db");
Database databse2 = devOpenHelperForDatabase2.getWritableDb();
mDaoSessionForDatabase2 = new DaoMasterForDatabase2(databse2).newSession();
}
public DaoSessionForDatabase1 getDaoSessioForDatabase1() {
return mDaoSessionForDatabase1;
}
public DaoSessionForDatabase2 getDaoSessioForDatabase2() {
return mDaoSessionForDatabase2;
}
}
You can access same and different schema or table or entities as bellow from Activity as an example:
// get the Schema1 DAO for Database1
DaoSessionForDatabase1 daoSessionForDatabase1 = ((App) getApplication()).getDaoSessioForDatabase1();
Schema1Dao schema1Dao = daoSessionForDatabase1.getSchema1Dao();
// get the Schema2 DAO for Database2
DaoSessionForDatabase2 daoSessionForDatabase2 = ((App) getApplication()).getDaoSessioForDatabase2();
Schema2Dao schema2Dao = daoSessionForDatabase2.getSchema2Dao();
Update 2:: The above can be discarded but the approach will be the same. Update done based on the discussion in the comments below:
I made the changes in the example greenDAO -> examples
package org.greenrobot.greendao.example;
import android.app.Application;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.example.DaoMaster.DevOpenHelper;
public class App extends Application {
/** A flag to show how easily you can switch from standard SQLite to the encrypted SQLCipher. */
public static final boolean ENCRYPTED = true;
private DaoSession daoSession;
private DaoSession daoSession1;
#Override
public void onCreate() {
super.onCreate();
DevOpenHelper helper = new DevOpenHelper(this, ENCRYPTED ? "notes-db-encrypted" : "notes-db");
Database db = ENCRYPTED ? helper.getEncryptedWritableDb("super-secret") : helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
DevOpenHelper helper1 = new DevOpenHelper(this, "notes1-db");
Database db1 = helper1.getWritableDb();
daoSession1 = new DaoMaster(db1).newSession();
}
public DaoSession getDaoSession() {
return daoSession;
}
public DaoSession getDaoSession1() {
return daoSession1;
}
}
Now make the following chnages in NoteActivity.java
//Add below class members
private static boolean switchDbBetweenOneAndTwo = false;
private NoteDao noteDao2;
private Query<Note> notesQuery2;
//In on craete add the following as the last statement after notesQuery = noteDao.queryBuilder().orderAsc(NoteDao.Properties.Text).build();
#Override
public void onCreate(Bundle savedInstanceState) {
......
Log.d("Database 1", "notesQuery.list()="+notesQuery.list().toString());
// get the note DAO for Database2
DaoSession daoSessionForDb2 = ((App) getApplication()).getDaoSession1();
noteDao2 = daoSessionForDb2.getNoteDao();
// query all notes, sorted a-z by their text
notesQuery2 = noteDao2.queryBuilder().orderAsc(NoteDao.Properties.Text).build();
Log.d("Database 2", "notesQuery2.list()="+notesQuery2.list().toString());
updateNotes();
}
//Replace updateNotes as
private void updateNotes() {
List<Note> notes = notesQuery.list();
List<Note> notes2 = notesQuery2.list();
notes.addAll(notes2);
notesAdapter.setNotes(notes);
}
//Replace addNote as
private void addNote() {
String noteText = editText.getText().toString();
editText.setText("");
final DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
String comment = "Added on " + df.format(new Date());
Note note = new Note();
note.setText(noteText);
note.setComment(comment);
note.setDate(new Date());
note.setType(NoteType.TEXT);
if(!switchDbBetweenOneAndTwo){
note.setText(noteText + " In database 1");
noteDao.insert(note);
}
else {
note.setText(noteText + " In database 2");
noteDao2.insert(note);
}
Log.d("DaoExample", "Inserted new note, ID: " + note.getId());
switchDbBetweenOneAndTwo = true;
updateNotes();
}
I made no changes in gradle file or added anything as it dosen't make any sense to me.
For Greendao Version 3, it is not possible to have more than 1 schema.
github.com/greenrobot/greenDAO/issues/356
As they wrote on their website also:
Note that multiple schemas are currently not supported when using the
Gradle plugin . For the time being, continue to use your generator
project.
They have added this feature yet to the new GreenDao.
How do I query an object that has another object as a property? I need to get the values of the property as well. Here's my model:
public class Department {
public int DeptId;
public string DeptName;
}
public class Employee {
public int Id;
public string Name;
public int DeptId;
public Department Department;
}
I'm coming from a c# background, and I could do this with c# using Entity Framework. Now it seems like this model works but when I included a sqllite functionality for the objects I'm not sure how to query it.
Here's my first try, but I'm not sure if this is the best way
public List<Employee> getAllEmployeesWithDepartments(){
List<Employee> employees = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor res = db.rawQuery("SELECT * FROM Employee e LEFT JOIN Department d on e.DeptId = d.Id" , null);
if(res.moveToFirst()){
do{
Employee emp = new Employee();
emp.Id = res.getInt(res.getColumnIndex("Id"));
emp.Name = res.getString(res.getColumnIndex("Name"));
Department dep = new Department();
dep.Id = res.getInt(res.getColumnIndex("Id"));
dep.Name = res.getString(res.getColumnIndex("Name"));
emp.Department = dep;
employees.add(emp);
}while (res.moveToNext());
}
return employees;
}
You're confused. SQLite doesn't have objects. Its a table based database (like almost all databases). You can map objects into tables, but you have to create those mappings yourself. Since SQLite doesn't have objects, it definitely doesn't have subobjects. You generally make something like it by joining another table with a foreign key constraint on the main table, but it really is defined by your schema. There is no generic answer for you.
I have a Client bean ,
#DatabaseField(columnName = "client_id",generatedId = true,useGetSet = true)
private Integer clientId;
#DatabaseField(columnName = "client_nom",useGetSet = true)
private String clientNom;
#DatabaseField(columnName = "city_id",foreign = true,useGetSet = true)
private City city;
and a City bean ,
#DatabaseField(columnName = "city_id",generatedId = true,useGetSet = true)
private Integer cityId;
#DatabaseField(columnName = "city_name",useGetSet = true)
private String cityName;
#ForeignCollectionField
private ForeignCollection<Client> clientList;
Those beans are just an example but let's say , I want to delete all the clients having as foreign city cityId when deleting a city.
How is that possible please ?
ORMLite does not support cascading deletes #Majid. That is currently outside of what it considers to be "lite". If you delete the city then you need to delete the clients by hand.
One way to ensure this would be to have a CityDao class that overrides the delete() method and issues the delete through the ClientDao at the same time. Something like:
public class CityDao extends BaseDaoImpl<City, Integer> {
private ClientDao clientDao;
public CityDao(ConnectionSource cs, ClientDao clientDao) {
super(cs, City.class);
this.clientDao = clientDao;
}
...
#Override
public int delete(City city) {
// first delete the clients that match the city's id
DeleteBuilder db = clientDao.deleteBuilder();
db.where().eq("city_id", city.getId());
clientDao.delete(db.prepare());
// then call the super to delete the city
return super.delete(city);
}
...
}
To implement cascading while using ORMLite on Android you need to enable foreign key restraints as described here:
(API level > 16)
#Override
public void onOpen(SQLiteDatabase db){
super.onOpen(db);
if (!db.isReadOnly()){
db.setForeignKeyConstraintsEnabled(true);
}
}
For API level < 16 please read:
Foreign key constraints in Android using SQLite? on Delete cascade
Then use columnDefinition annotation to define cascading deletes. Ex:
#DatabaseField(foreign = true,
columnDefinition = "integer references my_table(id) on delete cascade")
private MyTable table;
This is assuming the table/object name is "my_table", as described here: Creating foreign key constraints in ORMLite under SQLite