Lo que necesitamos:
Necesitamos poder adjuntar las bases de datos externas a la Room-Database interna sin cambiar la ruta en la que están almacenadas. Después de agregarlas, queremos poder usar las bases de datos con los objetos Room Entity, Dao y Database. ¿Hay alguna forma posible de lograr esto?
Puede ser más fácil no adjuntarlo, ya que podría utilizar una instancia de Room Database separada. Sin usted, necesitaría tener DAO separados para atender el nombre del esquema adjunto (creo). Diciendo que el siguiente ejemplo (basado en algo con lo que estaba jugando para alguien y por lo tanto los nombres de columna bastante confusos) .
p.ej. asumiendo ATTACH DATABASE .... AS other
(siendo el esquema adjunto otro ) luego en lugar de (para la base de datos principal)
@Query("SELECT * FROM AllUsers")
List<AllUsers> getAllAllUsers();
Necesitarías un regalo :-
@SkipQueryVerification
@Query("SELECT * FROM other.AllUsers")
List<AllUsers> getOtherAllAllUsers();
etc.
Sin embargo, si tuviera algo como (para el principal):-
mLPDB = Room.databaseBuilder(this,LoanPaymentDatabase.class,"mydb").allowMainThreadQueries().build();
mLPDB_DAO = mLPDB.mDao();
Junto con (para el otro) :-
mOtherDB = Room.databaseBuilder(this,LoanPaymentDatabase.class,OtherDatabaseHelper.DBNAME).allowMainThreadQueries().build();
mOtherDAO = mOtherDB.mDao();
Luego puede acceder a ambos usando el mismo DAO.
- Nota por supuesto, lo anterior supone que el esquema es complementario (no necesariamente exacto).
¿No necesariamente exacto?
También cubriendo el comentario :-
Primero debe migrar los datos a la sala.
-
Jugando un poco, puedes evitar el tener que migrar engañando a Room estableciendo user_version en 0. En cuyo caso, Room establece el número de versión (prueba limitada). Sin embargo, no estoy seguro de lo que GreenDao o su Servidor harían con esto (su tarea).
-
Mi prueba limitada fue para un problema común al migrar el de tener una columna con CLAVE PRIMARIA INTEGER, es decir, sin INCREMENTO AUTOMÁTICO. Room, si migra, se quejará de que el esquema no coincide. Así que intencionalmente no codifiqué AUTOINCREMENT, configuré user_version en 0 y no tuve quejas al acceder a la base de datos a través de Room. También se usó un tipo de columna de rumplestilskin y ninguna queja.
Como tal, creo que puede solucionar los problemas de migración con el temido esperado/encontrado configurando user_version en 0 (y, por lo tanto, creo que elude la migración). Obviamente, aunque los nombres de las columnas deben coincidir, si se definen en una Entidad y no se ignoran.
- También intenté agregar una columna no definida a la Entidad y usar los resultados anteriores sin ninguna queja (estas pruebas deberían ser obvias en el código a continuación).
Ejemplo(s)
El siguiente es un ejemplo de una base de datos de sala de 2 entidades y con el fin de probar el otro una base de datos construida fuera de la sala que coincida suficientemente con la base de datos de la sala para poder usarse, es decir, los nombres de las columnas de la entidad coinciden.
La otra base de datos
La otra base de datos que no es de sala se crea a través de un SQLiteOpenHelper subclase según OtherDatabaseHelper.java :-
public class OtherDatabaseHelper extends SQLiteOpenHelper {
public static final String DBNAME = "lpolddb";
public static final int DBVERSION = 1;
public static final String ALLUSERS_TBL = "AllUsers";
public static final String PAIDUNPAID_TBL = "PaidUnpaid";
/*
@PrimaryKey(autoGenerate = true)
private long auid;
private String Name;
private int Loan;
private int TimeInMonths;
*/
public static final String ALLUSERS_COL_AUID = "auid";
public static final String ALLUSERS_COL_NAME = "Name";
public static final String ALLUSERS_COL_LOAN = "Loan";
public static final String ALLUSERS_COL_TIMEINMONTHS = "TimeInMonths";
private static final String crt_allusers_table_sql =
"CREATE TABLE IF NOT EXISTS " + ALLUSERS_TBL + "(" +
//ALLUSERS_COL_AUID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
ALLUSERS_COL_AUID + " INTEGER PRIMARY KEY," +
ALLUSERS_COL_NAME + " TEXT, " +
ALLUSERS_COL_LOAN + " INTEGER, " +
"someothercolumnnotdefineinroom TEXT, " + //!!!!!!!!!! not a column in an entity
ALLUSERS_COL_TIMEINMONTHS + " INTEGER" +
")";
/*
@PrimaryKey(autoGenerate = true)
private long puid;
private int TimeInMonths;
private String PaidUnpaid;
@ForeignKey(
entity = AllUsers.class,
parentColumns = {"auid"},
childColumns = {"AllUsersReference"},
onUpdate = ForeignKey.CASCADE, onDelete = ForeignKey.CASCADE)
private long AllUsersReference;
*/
public static final String PAIDUNPAID_COL_PUID = "puid";
public static final String PAIDUNPAID_TIMEINMONTHS = ALLUSERS_COL_TIMEINMONTHS;
public static final String PAIDUNPAID_COL_PAIDUNPAID = "PaidUnpaid";
public static final String PAIDUNPAID_COL_ALLUSERSREFERENCE = "AllUsersReference";
public static final String crt_paidunpaid_table_sql =
"CREATE TABLE IF NOT EXISTS " + PAIDUNPAID_TBL + "(" +
PAIDUNPAID_COL_PUID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
PAIDUNPAID_TIMEINMONTHS + " rumplestilskin, " + // !!!!!!!!!!!
PAIDUNPAID_COL_PAIDUNPAID + " TEXT," +
PAIDUNPAID_COL_ALLUSERSREFERENCE + " INTEGER " +
" REFERENCES " + ALLUSERS_TBL + "(" + ALLUSERS_COL_AUID + ") " +
"ON UPDATE CASCADE ON DELETE CASCADE" +
")";
SQLiteDatabase mDB;
public OtherDatabaseHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
mDB = this.getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(crt_allusers_table_sql);
db.execSQL(crt_paidunpaid_table_sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public long insertAllUsers(String name, int loanamount, int periodofloan) {
ContentValues cv = new ContentValues();
cv.put(ALLUSERS_COL_NAME,name);
cv.put(ALLUSERS_COL_LOAN,loanamount);
cv.put(ALLUSERS_COL_TIMEINMONTHS,periodofloan);
return mDB.insert(ALLUSERS_TBL,null,cv);
}
public long insertPaidUnpaid(int formonth, String status, long allUserreferenced) {
ContentValues cv = new ContentValues();
cv.put(PAIDUNPAID_TIMEINMONTHS,formonth);
cv.put(PAIDUNPAID_COL_PAIDUNPAID,status);
cv.put(PAIDUNPAID_COL_ALLUSERSREFERENCE,allUserreferenced);
return mDB.insert(PAIDUNPAID_TBL,null,cv);
}
}
- ver comentarios sobre rarezas/discrepancias añadidas a propósito
- esto se completa, se accede a través de una base de datos de sala alternativa y a través de una base de datos adjunta en MainActivity.java debajo
La base de datos de salas
Las 2 Entidades :-
Todos los usuarios.java
@Entity
public class AllUsers {
@PrimaryKey(autoGenerate = true)
private long auid;
private String Name;
private int Loan;
private int TimeInMonths;
public AllUsers() {
}
@Ignore
public AllUsers(String Name, int Loan, int TimeInMonths) {
this.Name = Name;
this.Loan = Loan;
this.TimeInMonths = TimeInMonths;
}
public long getAuid() {
return auid;
}
public void setAuid(long auid) {
this.auid = auid;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public int getLoan() {
return Loan;
}
public void setLoan(int loan) {
Loan = loan;
}
public int getTimeInMonths() {
return TimeInMonths;
}
public void setTimeInMonths(int timeInMonths) {
TimeInMonths = timeInMonths;
}
}
y PagadoNoPagado.java :-
@Entity
public class PaidUnpaid {
@PrimaryKey(autoGenerate = true)
private long puid;
private int TimeInMonths;
private String PaidUnpaid;
@ForeignKey(
entity = AllUsers.class,
parentColumns = {"auid"},
childColumns = {"AllUsersReference"},
onUpdate = ForeignKey.CASCADE, onDelete = ForeignKey.CASCADE)
private long AllUsersReference;
public PaidUnpaid() {
}
@Ignore
public PaidUnpaid(int TimeInMonths, String PaidUnpaid, long AllUsersreference) {
this.TimeInMonths = TimeInMonths;
this.PaidUnpaid = PaidUnpaid;
this.AllUsersReference = AllUsersreference;
}
public long getPuid() {
return puid;
}
public void setPuid(long puid) {
this.puid = puid;
}
public int getTimeInMonths() {
return TimeInMonths;
}
public void setTimeInMonths(int timeInMonths) {
TimeInMonths = timeInMonths;
}
public String getPaidUnpaid() {
return PaidUnpaid;
}
public void setPaidUnpaid(String paidUnpaid) {
PaidUnpaid = paidUnpaid;
}
public long getAllUsersReference() {
return AllUsersReference;
}
public void setAllUsersReference(long allUsersReference) {
AllUsersReference = allUsersReference;
}
}
Una clase POJO adicional, AllUsersAndPaidUnpaidsList.java con el que se jugaba tan incorporado y usado :-
public class AllUsersAndPaidUnpaidsList {
@Embedded
AllUsers allUsers;
@Ignore
@PrimaryKey
long auid;
@Ignore
@Relation(entity = PaidUnpaid.class,parentColumn = "auid",entityColumn = "puid")
List<PaidUnpaid> paidUnpaidList;
@Ignore
public AllUsersAndPaidUnpaidsList(AllUsers au, List<PaidUnpaid> pulist) {
this.allUsers = au;
this.paidUnpaidList = pulist;
}
public List<PaidUnpaid> getPaidUnpaidList() {
return this.paidUnpaidList;
}
public void setPaidUnpaidList(List<PaidUnpaid> paidUnpaidList) {
this.paidUnpaidList = paidUnpaidList;
}
public AllUsers getAllUsers() {
return allUsers;
}
public void setAllUsers(AllUsers allUsers) {
this.allUsers = allUsers;
}
public void outputToLog(String tag) {
StringBuilder sb = new StringBuilder("AllUsersName = ")
.append(this.allUsers.getName())
.append(" TimeInMonths = ")
.append(String.valueOf(this.allUsers.getTimeInMonths()))
;
for (PaidUnpaid pu: this.getPaidUnpaidList()) {
sb.append("\n\t TimeInMonths = ")
.append(String.valueOf(pu.getTimeInMonths()))
.append(" Paid/Unpaid = ")
.append(pu.getPaidUnpaid());
}
Log.d(tag,sb.toString());
}
}
Una sola interfaz Dao.java :-
@androidx.room.Dao
public interface Dao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
long[] insertAllUsers(AllUsers... allUsers);
@Insert(onConflict = OnConflictStrategy.IGNORE)
long insertAllUsers(AllUsers allUsers);
@Insert(onConflict = OnConflictStrategy.IGNORE)
long[] insertPaidUnpaid(PaidUnpaid... paidUnpaids);
@Insert(onConflict = OnConflictStrategy.IGNORE)
long insertPaidUnpaid(PaidUnpaid paidUnpaid);
@Update(onConflict = OnConflictStrategy.IGNORE)
int updateAllUsers(AllUsers... allUsers);
@Update(onConflict = OnConflictStrategy.IGNORE)
int updateAllUsers(AllUsers allUsers);
@Update(onConflict = OnConflictStrategy.IGNORE)
int updatePaidUnpaid(PaidUnpaid... paidUnpaids);
@Update(onConflict = OnConflictStrategy.IGNORE)
int updatePaidUnpaid(PaidUnpaid paidUnpaid);
@Delete
int deleteAllUsers(AllUsers... allUsers);
@Delete
int deleteAllUsers(AllUsers allUsers);
@Delete
int deletePaidUnpaid(PaidUnpaid... paidUnpaids);
@Delete
int deletePaidUnpaid(PaidUnpaid paidUnpaid);
@Query("SELECT * FROM AllUsers")
List<AllUsers> getAllAllUsers();
@Query("SELECT * FROM AllUsers WHERE auid = :id")
List<AllUsers> getOneAllUsersById(long id);
@Query("SELECT * FROM PaidUnpaid")
List<PaidUnpaid> getAllPaidUnpaids();
@Query("SELECT * FROM PaidUnpaid WHERE puid = :id")
List<PaidUnpaid> getOnePaidUnpaidById(long id);
@Query("SELECT * FROM PaidUnpaid WHERE AllUsersReference = :allUsersid")
List<PaidUnpaid> getPaidUnpaidsForAllUsersId(long allUsersid);
/*************
* Some Additional DAO's for attached not required for alternative helper
* in practice you would likely need attached versions of all
************/
@Query("SELECT * FROM other.PaidUnpaid WHERE AllUsersReference = :allUsersid")
@SkipQueryVerification
List<PaidUnpaid> getOtherPaidUnpaidForAllUsersId(long allUsersid);
@SkipQueryVerification
@Query("SELECT * FROM other.AllUsers")
List<AllUsers> getOtherAllAllUsers();
}
La clase Room Database LoanPaymentDatabase.java
@Database(entities = {AllUsers.class,PaidUnpaid.class},exportSchema = false,version = 1)
public abstract class LoanPaymentDatabase extends RoomDatabase {
public abstract Dao mDao();
}
Poniéndolo todo junto
Finalmente una actividad que :-
-
Crea y rellena el otro base de datos (fuera de la sala) (si no existe) para realizar pruebas. Estableciendo user_version (versión de la base de datos en Android Talk) en 0.
-
Crea, si es necesario, la versión Room de la base de datos.
- Agrega un par de filas a la versión Room.
- Envía los datos de la versión de la sala al registro.
- Crea una RoomDatabase alternativa usando la otra base de datos.
- Envía los datos en el otro base de datos a través de Room.
- Adjunta el otro base de datos a la versión Room.
- Envía los datos de ambos a través de la base de datos de habitaciones original accediendo a la otra adjunta base de datos a través de las interfaces DAO adicionales que incluyen other.???? .
Actividad principal.java
public class MainActivity extends AppCompatActivity {
LoanPaymentDatabase mLPDB;
Dao mLPDB_DAO;
LoanPaymentDatabase mOtherDB;
Dao mOtherDAO;
Random rnd = new Random();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
manageOtherDatabase();
mLPDB = Room.databaseBuilder(this,LoanPaymentDatabase.class,"mydb").allowMainThreadQueries().build();
mLPDB_DAO = mLPDB.mDao();
// Add some(2) AllUsers
mLPDB_DAO.insertAllUsers(new AllUsers("Fred",5000,5));
mLPDB_DAO.insertAllUsers(new AllUsers("Mary", 4000,6));
// Add Some PaidUnpaid's for each AllUsers
// Random amount with random paid or unpaid
// This is just for demonstration and doesn't reflect what would typically be done
List<AllUsers> allusers = mLPDB_DAO.getAllAllUsers();
for (AllUsers au: allusers) {
int lc = rnd.nextInt(4) + 1;
int month = 1;
for (int i=0; i < lc; i++) {
String paid = "Paid";
if((rnd.nextInt(2) % 2) > 0 ) {
paid = "Unpaid";
}
mLPDB_DAO.insertPaidUnpaid(new PaidUnpaid(month++, paid, au.getAuid()));
}
}
//Extract all AllUsersAndPaidUnpaid (i.e each AllUsers with the related PaidUnpaid for the AllUsers)
ArrayList<AllUsersAndPaidUnpaidsList> aupulist = new ArrayList<>();
for (AllUsers au: allusers) {
List<PaidUnpaid> pulist = mLPDB_DAO.getPaidUnpaidsForAllUsersId(au.getAuid());
aupulist.add(new AllUsersAndPaidUnpaidsList(au,pulist));
}
// Output the results
for (AllUsersAndPaidUnpaidsList aupu: aupulist) {
aupu.outputToLog("INITALAUPU");
}
//Use separate openHelper rather than ATTACH
mOtherDB = Room.databaseBuilder(this,LoanPaymentDatabase.class,OtherDatabaseHelper.DBNAME).allowMainThreadQueries().build();
mOtherDAO = mOtherDB.mDao();
ArrayList<AllUsersAndPaidUnpaidsList> otheraupulist = new ArrayList<>();
for (AllUsers oau: mOtherDAO.getAllAllUsers() ) {
otheraupulist.add(new AllUsersAndPaidUnpaidsList(oau,mOtherDAO.getPaidUnpaidsForAllUsersId(oau.getAuid())));
}
for (AllUsersAndPaidUnpaidsList aupu: otheraupulist) {
aupu.outputToLog("ALTDBAUPU");
}
// User Attach
SupportSQLiteDatabase main_sdb = mLPDB.getOpenHelper().getWritableDatabase();
SupportSQLiteDatabase other_sdb = mOtherDB.getOpenHelper().getWritableDatabase();
main_sdb.execSQL("ATTACH DATABASE '" + other_sdb.getPath() + "' AS other");
ArrayList<AllUsersAndPaidUnpaidsList> attachaupulist = new ArrayList<>();
for (AllUsers aau: mLPDB_DAO.getAllAllUsers()) {
attachaupulist.add(new AllUsersAndPaidUnpaidsList(aau,mLPDB_DAO.getPaidUnpaidsForAllUsersId(aau.getAuid())));
}
for (AllUsers aauother: mLPDB_DAO.getOtherAllAllUsers()) {
attachaupulist.add(new AllUsersAndPaidUnpaidsList(aauother,mLPDB_DAO.getOtherPaidUnpaidForAllUsersId(aauother.getAuid())));
}
for (AllUsersAndPaidUnpaidsList aupu: attachaupulist) {
aupu.outputToLog("ATTACHEDAUPU");
}
mLPDB.close();
}
/*********
* For testing purposes - Populate the OTHER database to be used
*********/
private void manageOtherDatabase() {
OtherDatabaseHelper mODBHlpr = new OtherDatabaseHelper(this);
SQLiteDatabase db = mODBHlpr.getWritableDatabase();
db.execSQL("PRAGMA user_version = 0");
if (DatabaseUtils.queryNumEntries(db,OtherDatabaseHelper.ALLUSERS_TBL) > 0) {
db.close();
mODBHlpr.close();
return;
}
db.beginTransaction();
for (int i= 0; i < 5; i++) {
long auid = mODBHlpr.insertAllUsers("AU" + String.valueOf(i),10000 + 1,5 + i);
for(int ii = 0; ii < 5; ii++) {
mODBHlpr.insertPaidUnpaid(ii,"Paid",auid);
}
}
db.setTransactionSuccessful();
db.endTransaction();
db.close();
mODBHlpr.close();
}
}