问题
I'm using DBFlow to save into the database and Retrofit to call my web service. My retrofit class is the same class as my Database table. But I have a problem when 2 threads or more are launched at the same time to save my data into the table. The insert into my table duplicate my data and the primary key is duplicated too. Then my thread is stopped because it crashes.
Have you a solution for that ?
Class for Retrofit and DBFlow
@Table(database = LocalDB.class)
@Root(name = "picture_infos")
public class PictureInfos extends BaseModel {
@PrimaryKey
@Element(name = "id_picture")
private int idPicture;
@Column
@Element(name = "id_account")
private String idAccount;
@Column
@Element(name = "folder_path")
private String folderPath;
@Column
@Element(name = "filename")
private String filename;
@Column
@Element(name = "legend", required = false)
private String legend;
public int getIdPicture() {
return idPicture;
}
public void setIdPicture(int idPicture) {
this.idPicture = idPicture;
}
public String getIdAccount() {
return idAccount;
}
public void setIdAccount(String idAccount) {
this.idAccount = idAccount;
}
public String getFolderPath() {
return folderPath;
}
public void setFolderPath(String folderPath) {
this.folderPath = folderPath;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getLegend() {
return legend;
}
public void setLegend(String legend) {
this.legend = legend;
}
}
Thread in retrofit response
public void onResponse(Call<AdminPictures> call, Response<AdminPictures> response) {
AdminPictures apResponse = response.body();
final List<PictureInfos> pictureInfos = apResponse.getPicturesList();
new Thread(new Runnable() {
@Override
public void run() {
try {
for (PictureInfos infos : pictureInfos) {
// This save duplicate when I've 2 or more threads
synchronized (infos){
if(!infos.exists()){
infos.save();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
Stacktrace
03-18 12:01:18.950 15696-19086/com.vigizen.client.kiosqueadmin E/SQLiteLog: (1555) abort at 12 in [INSERT INTO `PictureInfos`(`idPicture`,`idAccount`,`folderPath`,`filename`,`legend`) VALUES (?,?,?,?,?)]: UNIQUE constraint failed: PictureInfos.idPicture
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: PictureInfos.idPicture (code 1555)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:788)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at com.raizlabs.android.dbflow.structure.database.AndroidDatabaseStatement.executeInsert(AndroidDatabaseStatement.java:77)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at com.raizlabs.android.dbflow.sql.SqlUtils.insert(SqlUtils.java:370)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at com.raizlabs.android.dbflow.sql.SqlUtils.save(SqlUtils.java:327)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at com.raizlabs.android.dbflow.structure.ModelAdapter.save(ModelAdapter.java:60)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at com.raizlabs.android.dbflow.structure.BaseModel.save(BaseModel.java:52)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at com.vigizen.client.kiosqueadmin.GalleryActivity$1$1.run(GalleryActivity.java:188)
03-18 12:01:18.951 15696-19086/com.vigizen.client.kiosqueadmin W/System.err: at java.lang.Thread.run(Thread.java:818)
回答1:
What you are doing here is the first "dont' do" in the docs of DBFlow. What you should use here is a transaction to store the data. This locks the database for an exclusive batch operation and should be a lot faster than iterating over all you models and save them one by one.
Basically, you want to use FastStoreModelTransaction.saveBuilder()
here which basically does a INSERT OR UPDATE
.
Usage would be like that:
public void onResponse(Call<AdminPictures> call, Response<AdminPictures> response) {
AdminPictures apResponse = response.body();
final List<PictureInfos> pictureInfos = apResponse.getPicturesList();
FastStoreModelTransaction transaction = FastStoreModelTransaction.saveBuilder(FlowManager.getModelAdapter(PictureInfos.class))
.addAll(pictureInfos)
.build();
database.executeTransaction(transaction);
}
Please note that this would automatically runs in the background, so no need to wrap it in an extra thread. If you need a listener on the storing process, use the ProcessModelTransaction
instead.
回答2:
Reading database can be done in parallel without interrupting each other, but writing in to database should be done in a synchronized way if you have keys.
You should write the save
within a synchronized method. Synchronize will allow only one thread to do changes at a time.
Note:
By default android SQLiteDatabase
Object is safe for multithreading. DBFlow
is also. Make sure you are using the same instances of helpers in multiple threads.
Details
来源:https://stackoverflow.com/questions/36081015/android-sqlite-multi-threading-save-data-with-dbflow-retrofit