Accès simultané à la base de données
Même article sur mon blog (j'aime plus la mise en forme)
J'ai écrit un petit article qui décrit comment sécuriser l'accès à votre thread de base de données Android.
En supposant que vous ayez votre propre SQLiteOpenHelper .
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Vous souhaitez maintenant écrire des données dans la base de données dans des threads séparés.
// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
Vous obtiendrez le message suivant dans votre logcat et l'une de vos modifications ne sera pas écrite.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Cela se produit car chaque fois que vous créez un nouvel objet SQLiteOpenHelper , vous établissez en fait une nouvelle connexion à la base de données. Si vous essayez d'écrire dans la base de données à partir de connexions distinctes réelles en même temps, l'une échouera. (à partir de la réponse ci-dessus)
Pour utiliser la base de données avec plusieurs threads, nous devons nous assurer que nous utilisons une seule connexion à la base de données.
Faisons un gestionnaire de base de données de classe singleton qui contiendra et retournera un seul objet SQLiteOpenHelper .
public class DatabaseManager {
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initialize(..) method first.");
}
return instance;
}
public SQLiteDatabase getDatabase() {
return new mDatabaseHelper.getWritableDatabase();
}
}
Le code mis à jour qui écrit des données dans la base de données dans des threads séparés ressemblera à ceci.
// In your application class
DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
// Thread 1
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
// Thread 2
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
Cela vous apportera un autre crash.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Puisque nous utilisons une seule connexion de base de données, méthode getDatabase () retour même instance de SQLiteDatabase objet pour Thread1 et Thread2 . Ce qui se passe, Thread1 peut fermer la base de données, tandis que Thread2 l' utilise toujours. C'est pourquoi nous avons un crash IllegalStateException .
Nous devons nous assurer que personne n'utilise la base de données et ensuite la fermer. Certaines personnes sur stackoveflow ont recommandé de ne jamais fermer votre SQLiteDatabase . Cela se traduira par le message logcat suivant.
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Échantillon de travail
public class DatabaseManager {
private int mOpenCounter;
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase() {
mOpenCounter++;
if(mOpenCounter == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
mOpenCounter--;
if(mOpenCounter == 0) {
// Closing database
mDatabase.close();
}
}
}
Utilisez-le comme suit.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Chaque fois que vous avez besoin d'une base de données, vous devez appeler la méthode openDatabase () de la classe DatabaseManager . À l'intérieur de cette méthode, nous avons un compteur, qui indique combien de fois la base de données est ouverte. S'il est égal à un, cela signifie que nous devons créer une nouvelle connexion à la base de données, sinon, la connexion à la base de données est déjà créée.
La même chose se produit dans la méthode closeDatabase () . Chaque fois que nous appelons cette méthode, le compteur diminue, chaque fois qu'il passe à zéro, nous fermons la connexion à la base de données.
Vous devriez maintenant pouvoir utiliser votre base de données et vous assurer qu'elle est sécurisée pour les threads.