I have a Room database which is stored in assets/database with its preloaded data. I am creating an updated version with more contents in the database for the next update.
Currently, if I add new contents to the database with no schema changes and reinstall the app, these new contents do not show up. The only way I can see the changes is if I uninstall and reinstall the app. However, I need to merge the user's data with the database with the new contents since I need to get the "favorites" of the user which is an integer column of a table with the item contents.
Is this possible?
This is how I create my database.
public static AppDatabase getInMemoryDatabase(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "app_database.db")
.createFromAsset("database/QuotesDB.db")
.addMigrations(MIGRATION_1_2)
.build();
}
}
}
return INSTANCE;
}
I tried to migrate with the following code but it still doesn't update the contents.
/**
* Migrate from:
* version 1 - initial contents.
* to
* version 2 - updated database contents (no schema changes)
*/
#VisibleForTesting
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// I need to tell Room that it should use the data
// from version 1 ( with the user's favorites ) to version 2.
}
};
Is this possible?
Yes. However it is a little complex.
In short, you can actually do it the other way round. Rather than use the new database from the asset and try to retrive the previous data (is complicated if using Room Migration as you have to effectiviely swap to the newly created/copied database which is further complicated as you're inside a transaction when migrating).
If however you do the schema changes to the database in use rather than the asset database, you can then get the asset database and copy the new non-user data (would be greatly complicated if the user's data were intermingled with the non-user data).
Even this isn't that simple. However, here's a simple exmaple/scanario based upon a slight expansion of your code to :-
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase db) {
final String TAG = "MIGRATE_1_2";
Log.d(TAG,"Database Version when called is " + db.getVersion());
// I need to tell Room that it should use the data
// from version 1 ( with the user's favorites ) to version 2.
// "CREATE TABLE IF NOT EXISTS `userdata` (`userId` INTEGER DEFAULT uid, `name` TEXT, PRIMARY KEY(`userId`))"
//db.execSQL("CREATE TABLE IF NOT EXISTS `userdata_saveuserdata` (`userId` INTEGER, `name` TEXT, PRIMARY KEY(`userId`))");
//db.execSQL("INSERT INTO `userdata_saveuserdata` SELECT * FROM `userdata`");
db.execSQL("ALTER TABLE `otherdata` ADD COLUMN `column2` TEXT");
Log.d(TAG,"Checking Context");
if (sContext != null) {
applyAssetDB(db);
} else {
Log.d(TAG,"Context is null!!!!");
}
}
};
As you can see this changes the otherdata table (not the users table) by adding a new column.
It then checks to see if sContext isn't null.
A valid Context is used rather then hard coding paths.
Then the applyAssetDB is invoked, which is :-
private static void applyAssetDB(SupportSQLiteDatabase sdb) {
String TAG = "APPLYASSETDB";
String mainDatabaseName = (new File(sdb.getPath()).getName());
String assetDatabaseName = mainDatabaseName + "_asset";
String asset_schema = "asset_schema";
Log.d(TAG,"Attempting application of asset data to database."
+ "\n\tActual Database = " + mainDatabaseName
+ "\n\tAsset Database will be " + assetDatabaseName
+ "\n\tSchema for attached database will be " + asset_schema
);
copyDatabaseFromAssets(AppDatabase.sContext,MainActivity.ASSETNAME,assetDatabaseName);
/*
if (sdb.isWriteAheadLoggingEnabled()) {
setAssetDBToWALMode(sContext.getDatabasePath(assetDatabaseName).getPath());
}
Log.d(TAG,"Attempting to ATTACH asset database " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
sdb.execSQL("ATTACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "' AS " + asset_schema);
Log.d(TAG,"Attempting INSERTING NEW DATA using\n\t" + "INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema);
sdb.execSQL("INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema);
Log.d(TAG,"Attempting to DETACH " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
sdb.execSQL("DETACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
*/
int insertRows = 0;
SQLiteDatabase assetdb = SQLiteDatabase.openDatabase(sContext.getDatabasePath(assetDatabaseName).getPath(),null,SQLiteDatabase.OPEN_READONLY);
Cursor assetCursor = assetdb.query("`otherdata`",null,null,null,null,null,null);
ContentValues cv = new ContentValues();
while (assetCursor.moveToNext()) {
cv.clear();
for (String c: assetCursor.getColumnNames()) {
if (assetCursor.getType(assetCursor.getColumnIndex(c)) == Cursor.FIELD_TYPE_BLOB) {
cv.put(c,assetCursor.getBlob(assetCursor.getColumnIndex(c)));
} else {
cv.put(c,assetCursor.getString(assetCursor.getColumnIndex(c)));
}
}
if (sdb.insert("`otherdata`", OnConflictStrategy.IGNORE,cv) > 0 ) insertRows++;
}
Log.d(TAG,"Inserted " + insertRows + " from the Asset Database");
assetCursor.close();
Log.d(TAG,"Deleting " + sContext.getDatabasePath(assetDatabaseName).getPath());
if ((new File(sContext.getDatabasePath(assetDatabaseName).getPath())).delete()) {
Log.d(TAG,"Copied AssetDatabase successfully deleted.");
} else {
Log.d(TAG,"Copied Asset Database file not deleted????");
}
Log.d(TAG,"Finished");
}
commented out code left in intentionally as issue were encountered when trying to ATTACH the database copied from the assets, so reverted to using seperate connetions.
This copies the database from the asset to the defauly database location via the copyDatabaseFromAssets method (as below). It the extracts all of the non user's data from the asset database and inserts it into the original (but altered according to the changed schema) database relying upon the OnConflictStrategy.IGNORE to only insert new rows. The userdata table is untouched, so the user's data is retianed.
Obviously this would not cater for changed rows.
Here's copyDatabaseFromAssets
private static void copyDatabaseFromAssets(Context context, String assetName, String databaseName) {
String TAG = "COPYDBFROMASSET";
int bufferSize = 1024 * 4, length = 0, read = 0, written = 0, chunks = 0;
byte[] buffer = new byte[bufferSize];
try {
Log.d(TAG,"Attempting opening asset " + assetName + " as an InputFileStream.");
InputStream is = context.getAssets().open(assetName);
Log.d(TAG,"Attempting opening FileOutputStream " + context.getDatabasePath(databaseName).getPath());
OutputStream os = new FileOutputStream(context.getDatabasePath(databaseName));
Log.d(TAG,"Initiating copy.");
while((length = is.read(buffer)) > 0) {
read += length;
os.write(buffer,0,length);
written += length;
chunks++;
}
Log.d(TAG,"Read " + read + "bytes; Wrote " + written + " bytes; in " + chunks);
Log.d(TAG,"Finalising (Flush and Close output and close input)");
os.flush();
os.close();
is.close();
Log.d(TAG,"Finalised");
} catch (IOException e) {
throw new RuntimeException("Error copying Database from Asset " + e.getMessage());
}
}
Here's an example Activity MainActivity that puts this all together (noting that for convenience I've used allowMainThreadQueries ) :-
public class MainActivity extends AppCompatActivity {
//public static final int DBVERSION = 1; //!!!!! ORIGINAL
public static final int DBVERSION = 2;
public static final String DBNAME = "app_database.db";
public static final String ASSETNAME = "database/QuotesDB.db";
AppDatabase appDB;
AllDao adao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appDB.setContext(this);
appDB = Room.databaseBuilder(this,AppDatabase.class,DBNAME)
.allowMainThreadQueries()
.createFromAsset(ASSETNAME)
.addCallback(AppDatabase.CALLBACK)
.addMigrations(AppDatabase.MIGRATION_1_2)
.build();
adao = appDB.allDao();
appDB.logDBInfo();
if (adao.getUserDataRowCount() == 3) {
adao.insertOneUserData(new UserData("ADDEDU100"));
adao.insertOneUserData(new UserData("ADDEDU200"));
adao.insertOneUserData(new UserData("ADDEDU300"));
}
appDB.logDBInfo();
}
}
When run (after changing the relevant code for the new schema and increasing the version) the result in the log is :-
2019-11-30 10:56:38.768 12944-12944/a.roommigrationwithassets D/MIGRATE_1_2: Database Version when called is 1
2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/MIGRATE_1_2: Checking Context
2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Attempting application of asset data to database.
Actual Database = app_database.db
Asset Database will be app_database.db_asset
Schema for attached database will be asset_schema
2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Attempting opening asset database/QuotesDB.db as an InputFileStream.
2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Attempting opening FileOutputStream /data/user/0/a.roommigrationwithassets/databases/app_database.db_asset
2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Initiating copy.
2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Read 12288bytes; Wrote 12288 bytes; in 3
2019-11-30 10:56:38.771 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Finalising (Flush and Close output and close input)
2019-11-30 10:56:38.772 12944-12944/a.roommigrationwithassets D/COPYDBFROMASSET: Finalised
2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Inserted 3 from the Asset Database
2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Deleting /data/user/0/a.roommigrationwithassets/databases/app_database.db_asset
2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Copied AssetDatabase successfully deleted.
2019-11-30 10:56:38.780 12944-12944/a.roommigrationwithassets D/APPLYASSETDB: Finished
2019-11-30 10:56:38.815 12944-12944/a.roommigrationwithassets D/ONOPEN: Database Version when called is 2
2019-11-30 10:56:38.816 12944-12944/a.roommigrationwithassets D/ONOPEN: Database Version after Super call is 2
2019-11-30 10:56:38.819 12944-12944/a.roommigrationwithassets D/DBINFO: UserData rowcount = 6
ID = 1 NAME = OU1
ID = 2 NAME = OU2
ID = 3 NAME = OU3
ID = 4 NAME = ADDEDU100
ID = 5 NAME = ADDEDU200
ID = 6 NAME = ADDEDU300
OtherData rowcount = 3
ID = 1Column1 = OD1
ID = 2Column1 = OD2
ID = 3Column1 = OD3
2019-11-30 10:56:38.821 12944-12944/a.roommigrationwithassets D/DBINFO: UserData rowcount = 6
ID = 1 NAME = OU1
ID = 2 NAME = OU2
ID = 3 NAME = OU3
ID = 4 NAME = ADDEDU100
ID = 5 NAME = ADDEDU200
ID = 6 NAME = ADDEDU300
OtherData rowcount = 3
ID = 1Column1 = OD1
ID = 2Column1 = OD2
ID = 3Column1 = OD3
The complete code for the AppDatabase class (noting that this includes some redundant code) is :-
#Database(version = MainActivity.DBVERSION, exportSchema = false,entities = {UserData.class,OtherData.class})
abstract class AppDatabase extends RoomDatabase {
abstract AllDao allDao();
static Context sContext;
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase db) {
final String TAG = "MIGRATE_1_2";
Log.d(TAG,"Database Version when called is " + db.getVersion());
// I need to tell Room that it should use the data
// from version 1 ( with the user's favorites ) to version 2.
// "CREATE TABLE IF NOT EXISTS `userdata` (`userId` INTEGER DEFAULT uid, `name` TEXT, PRIMARY KEY(`userId`))"
//db.execSQL("CREATE TABLE IF NOT EXISTS `userdata_saveuserdata` (`userId` INTEGER, `name` TEXT, PRIMARY KEY(`userId`))");
//db.execSQL("INSERT INTO `userdata_saveuserdata` SELECT * FROM `userdata`");
db.execSQL("ALTER TABLE `otherdata` ADD COLUMN `column2` TEXT");
Log.d(TAG,"Checking Context");
if (sContext != null) {
applyAssetDB(db);
} else {
Log.d(TAG,"Context is null!!!!");
}
}
};
static final RoomDatabase.Callback CALLBACK = new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
Log.d("ONCREATE","Database Version when called is " + db.getVersion());
super.onCreate(db);
Log.d("ONCREATE","Database Version after Super call is " + db.getVersion());
}
#Override
public void onOpen(#NonNull SupportSQLiteDatabase db) {
Log.d("ONOPEN","Database Version when called is " + db.getVersion());
super.onOpen(db);
Log.d("ONOPEN","Database Version after Super call is " + db.getVersion());
}
#Override
public void onDestructiveMigration(#NonNull SupportSQLiteDatabase db) {
Log.d("ONDESTRMIG","Database Version when called is " + db.getVersion());
super.onDestructiveMigration(db);
Log.d("ONDESTRMIG","Database Version after Super call is " + db.getVersion());
}
};
public void logDBInfo() {
AllDao adao = this.allDao();
List<UserData> allUserDataRows = adao.getAllUserDataRows();
StringBuilder sb = new StringBuilder().append("UserData rowcount = ").append(allUserDataRows.size());
for (UserData u: allUserDataRows) {
sb.append("\n\tID = ").append(u.getId()).append(" NAME = " + u.getName());
}
List<OtherData> allOtherDataRows = adao.getAllOtherDataRows();
sb.append("\n\nOtherData rowcount = ").append(allOtherDataRows.size());
for (OtherData o: allOtherDataRows) {
sb.append("\n\tID = ").append(o.getOtherDataId()).append("Column1 = ").append(o.getColumn1());
}
Log.d("DBINFO",sb.toString());
}
static void setContext(Context context) {
sContext = context;
}
private static void applyAssetDB(SupportSQLiteDatabase sdb) {
String TAG = "APPLYASSETDB";
String mainDatabaseName = (new File(sdb.getPath()).getName());
String assetDatabaseName = mainDatabaseName + "_asset";
String asset_schema = "asset_schema";
Log.d(TAG,"Attempting application of asset data to database."
+ "\n\tActual Database = " + mainDatabaseName
+ "\n\tAsset Database will be " + assetDatabaseName
+ "\n\tSchema for attached database will be " + asset_schema
);
copyDatabaseFromAssets(AppDatabase.sContext,MainActivity.ASSETNAME,assetDatabaseName);
/*
if (sdb.isWriteAheadLoggingEnabled()) {
setAssetDBToWALMode(sContext.getDatabasePath(assetDatabaseName).getPath());
}
Log.d(TAG,"Attempting to ATTACH asset database " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
sdb.execSQL("ATTACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "' AS " + asset_schema);
Log.d(TAG,"Attempting INSERTING NEW DATA using\n\t" + "INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema);
sdb.execSQL("INSERT OR IGNORE INTO `otherdata` SELECT * FROM `otherdata`." + asset_schema);
Log.d(TAG,"Attempting to DETACH " + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
sdb.execSQL("DETACH DATABASE '" + sContext.getDatabasePath(assetDatabaseName).getPath() + "." + asset_schema);
*/
int insertRows = 0;
SQLiteDatabase assetdb = SQLiteDatabase.openDatabase(sContext.getDatabasePath(assetDatabaseName).getPath(),null,SQLiteDatabase.OPEN_READONLY);
Cursor assetCursor = assetdb.query("`otherdata`",null,null,null,null,null,null);
ContentValues cv = new ContentValues();
while (assetCursor.moveToNext()) {
cv.clear();
for (String c: assetCursor.getColumnNames()) {
if (assetCursor.getType(assetCursor.getColumnIndex(c)) == Cursor.FIELD_TYPE_BLOB) {
cv.put(c,assetCursor.getBlob(assetCursor.getColumnIndex(c)));
} else {
cv.put(c,assetCursor.getString(assetCursor.getColumnIndex(c)));
}
}
if (sdb.insert("`otherdata`", OnConflictStrategy.IGNORE,cv) > 0 ) insertRows++;
}
Log.d(TAG,"Inserted " + insertRows + " from the Asset Database");
assetCursor.close();
Log.d(TAG,"Deleting " + sContext.getDatabasePath(assetDatabaseName).getPath());
if ((new File(sContext.getDatabasePath(assetDatabaseName).getPath())).delete()) {
Log.d(TAG,"Copied AssetDatabase successfully deleted.");
} else {
Log.d(TAG,"Copied Asset Database file not deleted????");
}
Log.d(TAG,"Finished");
}
private static void copyDatabaseFromAssets(Context context, String assetName, String databaseName) {
String TAG = "COPYDBFROMASSET";
int bufferSize = 1024 * 4, length = 0, read = 0, written = 0, chunks = 0;
byte[] buffer = new byte[bufferSize];
try {
Log.d(TAG,"Attempting opening asset " + assetName + " as an InputFileStream.");
InputStream is = context.getAssets().open(assetName);
Log.d(TAG,"Attempting opening FileOutputStream " + context.getDatabasePath(databaseName).getPath());
OutputStream os = new FileOutputStream(context.getDatabasePath(databaseName));
Log.d(TAG,"Initiating copy.");
while((length = is.read(buffer)) > 0) {
read += length;
os.write(buffer,0,length);
written += length;
chunks++;
}
Log.d(TAG,"Read " + read + "bytes; Wrote " + written + " bytes; in " + chunks);
Log.d(TAG,"Finalising (Flush and Close output and close input)");
os.flush();
os.close();
is.close();
Log.d(TAG,"Finalised");
} catch (IOException e) {
throw new RuntimeException("Error copying Database from Asset " + e.getMessage());
}
}
private static void setAssetDBToWALMode(String assetDBPath) {
SQLiteDatabase db = SQLiteDatabase.openDatabase(assetDBPath,null,SQLiteDatabase.OPEN_READWRITE);
db.enableWriteAheadLogging();
db.close();
}
}
Yes, It is possible.
The one precondition is your pre-shipped database and app database version must be the same. For example, your first release will have both databases as version 1. Now for the second release with new data update the pre-shipped and app database version to 2 and your database builder code will look like this:
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "app_database.db")
.createFromAsset("database/QuotesDB.db")
.fallbackToDestructiveMigration()
.build();
Prepopulate your Room database Docs
This medium article explains this nicely
Yes it is possible!
increase your SQLite VERSION and add .fallbackToDestructiveMigration() in your Room.databaseBuilder()
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "app_database.db")
.createFromAsset("database/QuotesDB.db")
.fallbackToDestructiveMigration()
.build();
Related
We have a Dataflow pipeline, everytime the side input refresh we have seen the table who cache data and during the refresh there are not all data inside. Obviously the rest of pipeline continue to work with raw data. Is there a possibility to fix it?
to not lose the data
this is the script
PCollection<TableRow> sideInput=pipeline
.apply(GenerateSequence.from(0).withRate(1, Duration.standardSeconds(180)))
.apply(Window.<Long>into(new GlobalWindows()) .triggering(Repeatedly.forever(AfterProcessingTime.pastFirstElementInPane())).discardingFiredPanes())
.apply("REFRESH-SIDEINPUT", ParDo.of(new SideInputQuery(psToBqOptions.getBQProjectSideInput())));
PCollectionView<Map<String, String>> sideInputView = sideInput
.apply("To map", ParDo.of(new TableRowToMap()))
.apply(View.<String, String>asMap());
public class SideInputQuery extends DoFn<Long, TableRow> {
private ValueProvider<String> ProjectIdRefresh;
PubSubToBqOptions psToBqOptions;
private static final Logger LOG = LoggerFactory.getLogger(SideInputQuery.class);
public SideInputQuery(ValueProvider<String> ProjectIdRefresh)
{
this.ProjectIdRefresh = ProjectIdRefresh;
}
#ProcessElement
public void processElement(ProcessContext c) throws Exception {
BigQuery bigQuery = BigQueryOptions.getDefaultInstance().getService();
QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder("SELECT distinct storeId as storeID_rebuild, "
+ "cast(C.code as INT64) as storeId "
+ "FROM `" + ProjectIdRefresh.get() + ".{dataser}.{table}` s INNER JOIN UNNEST(s.{field}) as C on (C.type='type') "
+ "where 1=1 ").build();
TableResult results = bigQuery.query(queryConfig);
System.out.println(results);
for(FieldValueList fvl:results.iterateAll())
{
TableRow tr = new TableRow();
tr.set("storeId", fvl.get("storeId").getNumericValue());
tr.set("storeID_rebuild", fvl.get("storeID_rebuild").getNumericValue());
LOG.info("storeId" + fvl.get("storeId").getNumericValue() + " storeID_rebuild" + fvl.get("storeID_rebuild").getNumericValue());
c.output(tr);
}
}
}
I am getting this error:NumberFormatException: null when I am trying to add score/points to my app.
I created separated table for this because I need multiple tables .
I have no clue what the problem is so thanks to you all.
if(count==4) {
my_db=new DBHelper(this);
sqdb = my_db.getWritableDatabase();
Cursor c_oldPoints= sqdb.query(DBHelper.TABLE_NAME2,null,DBHelper.NICKNAME+"=?",new String[]{Username},null,null,null);
int col_Points=c_oldPoints.getColumnIndex(DBHelper.POINTS);
c_oldPoints.moveToFirst();
while (!c_oldPoints.isAfterLast())
{
OldPoints=c_oldPoints.getString(col_Points);
c_oldPoints.moveToNext();
}
sqdb.close();
int OldP = Integer.parseInt(OldPoints);
OldP+=countPoints;
String SoldP = Integer.toString(OldP);
my_db=new DBHelper(this);
sqdb = my_db.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(my_db.POINTS,SoldP);
Cursor c = sqdb.query(DBHelper.TABLE_NAME2,null,DBHelper.NICKNAME+"=?",new String[]{Username},null,null,null);
c.moveToFirst();
while (!c.isAfterLast())
{
sqdb.update(DBHelper.TABLE_NAME2,cv, DBHelper.POINTS+"=?",new String[]{OldPoints});
c.moveToNext();
}
sqdb.close();
countPoints=0;
}
This is the logcat :-
2019-05-15 18:18:14.101 8513-8513/com.example.user.soundsequ E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.user.soundsequ, PID: 8513
java.lang.NumberFormatException: null
at java.lang.Integer.parseInt(Integer.java:483)
at java.lang.Integer.parseInt(Integer.java:556)
at com.example.user.soundsequ.Game.onClick(Game.java:353)
at android.view.View.performClick(View.java:5637)
at android.view.View$PerformClick.run(View.java:22429)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
The error appears to be in this line
int OldP = Integer.parseInt(OldPoints);
The error is saying that the value of OldPoints is not a string that can be converted to an integer e.g. if it were A or null;
As such either a value extracted from the POINTS column is not a numeric or the value of Username does not match the column NICKNAME in a row. In which case OldPoints will be whatever value it has been set to before the loop.
As the data itself is not available you need to ascertain which of the two situations is causing the issue.
I'd suggest adding some Logging in to determine which.
e.g. by using something like :-
OldPoints = "my debugging value";
Log.d("MYDEBUGGING","OldPoints, before doing anything is " + OldPoints);
Cursor c_oldPoints= sqdb.query(DBHelper.TABLE_NAME2,null,DBHelper.NICKNAME+"=?",new String[]{Username},null,null,null);
int col_Points=c_oldPoints.getColumnIndex(DBHelper.POINTS);
c_oldPoints.moveToFirst();
while (!c_oldPoints.isAfterLast())
{
OldPoints=c_oldPoints.getString(col_Points);
c_oldPoints.moveToNext();
Log.d("MYDEBUGGING","Extracted the value " + OldPoints + " from position + String.valueOf(c_oldPoints.getPosition());
}
sqdb.close();
Log.d("MYDEBUGGING","Trying to convert the value " + OldPoints + " to an integer");
int OldP = Integer.parseInt(OldPoints);
You could also not make the above changes and add a breakpoint (on the line initially indicated) and then use Run/Debug App and inspect the variables (or use multiple breakpoints at suitable places). You may find this useful in regard to debugging Debug your app.
The following code protects against the exception and also protects against an attempt being made to update a non-existent user :-
if (count == 4) {
SQLiteDatabase sqdb = my_db.getWritableDatabase();
Cursor c_oldPoints= sqdb.query(
DBHelper.TABLE_NAME2,null,
DBHelper.NICKNAME+"=?",
new String[]{Username},
null,null,null
);
int col_Points=c_oldPoints.getColumnIndex(DBHelper.POINTS);
if (c_oldPoints.moveToFirst()) {
Oldpoints = c_oldPoints.getString(col_Points);
//Oldpoints = "oops";
int OldP = 0;
boolean can_convert_to_int = true;
try {
OldP = Integer.parseInt(Oldpoints) + countPoints;
can_convert_to_int = true;
} catch (NumberFormatException e) {
e.printStackTrace(); //TODO not necessary probably remove. just for checking the log
}
if (can_convert_to_int) {
ContentValues cv = new ContentValues();
cv.put(DBHelper.POINTS,OldP);
sqdb.update(DBHelper.TABLE_NAME2,cv, DBHelper.NICKNAME + "=?", new String[]{Username});
}
} else {
Log.d("NICKNAMENOTFOUND","No row was found when attemtping to get the old score for User " + Username);
}
}
However
I would suggest that you add a couple of methods to your DBHelper class, these being :-
public int increasePoints(String user, int points_to_add) {
SQLiteDatabase db = this.getWritableDatabase();
SQLiteStatement sql = db.compileStatement(
"UPDATE " + TABLE_NAME2 +
" SET " + POINTS + "=" + POINTS + " +? " +
"WHERE "+ NICKNAME + "=?"
);
sql.bindLong(1,points_to_add);
sql.bindString(2,user);
return sql.executeUpdateDelete();
}
public int getPoints(String user) {
SQLiteDatabase db = this.getWritableDatabase();
int rv = -1;
String whereclause = NICKNAME + "=?";
String[] whereargs = new String[]{user};
Cursor csr = db.query(TABLE_NAME2,new String[]{POINTS},whereclause,whereargs,null,null,null);
if (csr.moveToFirst()) {
rv = csr.getInt(csr.getColumnIndex(POINTS));
}
csr.close();
return rv;
}
The first method increasePoints performs the change to the points via an UPDATE sql statement and does away for the need to convert the points extracted as a string to an integer. It returns the number of rows that have been updated (1 if the NICKNAME column is always a unique value, 0 if nothing was updated).
The second method getPoints does as it says, it gets the points for the given user, if the user doesn't exist it will return -1.
Your code could then be :-
if (count == 4) {
boolean updated = false; //TODO remove when happy
int old_points = my_db.getPoints(Username); //TODO remove when happy
if (my_db.increasePoints(Username,countPoints) > 0) {
updated = true;
}
int new_points = my_db.getPoints(Username); //TODO remove when happy
//TODO remove following code when happy
String result = "The result of the attempt to update the points for user " + Username;
if (updated) {
result = result + " was successful. ";
} else {
result = result + " was unsuccessful.";
}
Log.d("POINTSINCREASE",result +
" Points were " + String.valueOf(old_points) + " points are now " + String.valueOf(new_points));
}
Note where //TODO remove when happy is coded the lines are just for testing, so the above could be :-
if (count == 4) {
my_db.increasePoints(Username,countPoints);
}
I am trying to create nodes and relations in neo4j demo.db folder using this code .Its just creating the blank demo.db folder .When i open this db folder in neo4j it showing zero nodes an relations. I am providing the relations.xls file.
public class TestAut {
private static final File DB_PATH = new File("databases/demo.db");
private static GraphDatabaseService graphDb;
private static String [] r1={"PARTNERS_JV_WITH","EXEC_JOINS","EXEC_QUITS","INVESTS_IN_TECH_IP","ACQUIRES","LAUNCHES_NEW_PRODUCT_SERVICE","LAUNCHES",
"ACQUIRE_TALENT","DOWNSIZES_TALENT","ENTER_NEW_MARKET","DELIVERS_VALUE","OPENS_NEW_CENTER"};
private static String [] r2={"PARTNERS_JV_WITH","EXEC_JOINS","EXEC_QUITS","INVESTS_IN_TECH_IP","ACQUIRES","LAUNCHES_NEW_PRODUCT_SERVICE","LAUNCHES",
"ACQUIRE_TALENT","DOWNSIZES_TALENT","ENTER_NEW_MARKET","DELIVERS_VALUE","OPENS_NEW_CENTER"};
private static Relations relations;
public static void main(String args[]) {//throws FileNotFoundException {
String fileName = "relations.xls";
Workbook workbook;
startDb();
relations=new Relations(r1,r2);
System.out.println (fileName);
BufferedReader br;
try {
br = new BufferedReader( new InputStreamReader( new FileInputStream(fileName)));
br.close();
workbook = Workbook.getWorkbook(new File(fileName));
for(int i=0;i<workbook.getNumberOfSheets();i++) {
Sheet sheet=workbook.getSheet(i);
for(int j=0;j<sheet.getRows();j++) {
Cell cell[]=sheet.getRow(j);
for(int k=0;k<cell.length;k++)
System.out.print(cell[k].getContents()+" ");
System.out.print("\n");
createNodesAndRelationship(cell[0].getContents(),cell[1].getContents(),
cell[2].getContents(),cell[3].getContents(),
cell[4].getContents(),cell[5].getContents(),cell[6].getContents(),cell[7].getContents());
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BiffException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
stopDb();
System.out.println("Done!!");
successfully.... ");
}
public static void startDb() {
graphDb = new GraphDatabaseFactory().newEmbeddedDatabase(DB_PATH);
}
public static void stopDb() {
graphDb.shutdown();
}
public static void createNodesAndRelationship(String subject,String subjecttype,String object,
String objecttype,String relationship,String headline,String newslink,String date) {
Transaction tx = graphDb.beginTx();
try
{
Result result;
result=graphDb.execute("match ("+subjecttype+"{name:\""+subject+"\"}) return "+subjecttype+".name;");
if(result.toString().equals("empty iterator")) {
//Query="create (a:"+subjecttype+"{name:\""+subject+"\"}) return a;";
result=graphDb.execute("create (a:"+subjecttype+"{name:\""+subject+"\"}) return a;");
System.out.println(result.toString());
}
//Query="match ("+objecttype+"{name:\""+object+"\"}) return "+objecttype+".name;";
result=graphDb.execute("match ("+objecttype+"{name:\""+object+"\"}) return "+objecttype+".name;");
if(result.toString().equals("empty iterator")) {
result=graphDb.execute("create (a:"+objecttype+"{name:\""+object+"\"}) return a;");
System.out.println(result.toString());
}
result=graphDb.execute("match (a:"+subjecttype+"{name:\""+subject+"\"}) "
+ "match(b:"+objecttype+"{name:\""+object+"\"}) "
+ "match (a)-[:"+relationship+"]->"
+ "(b) return a.name,b.name;");
if(result.toString().equals("empty iterator")&&relations.contains(relationship)) {
result=graphDb.execute("match (a:"+subjecttype+"{name:\""+subject+"\"}) "
+ "match(b:"+objecttype+"{name:\""+object+"\"}) "
+ "create (a)-[r:"+relationship+"{headlines:\""+
headline+"\",newslink:\""+newslink+ "\",date:\""+date+"\""+ "}]->(b) return r;");
System.out.println(result.toString());
}
tx.success();
}
finally {
tx.close();
}
}
}
This is the console output after executing this code ....
relations.xls
Southwestern Bell Corporation Company Warner Communications Company ACQUIRES TIMELINE: AT&T's Merger With Time Warner Follows Decades Of Industry Deals http://www.npr.org/sections/thetwo-way/2016/10/22/498996253/timeline-at-ts-merger-with-time-warner-follows-decades-of-industry-deals?utm_medium=RSS&utm_campaign=technology 24/10/16
Verizon Company Yahoo Company ACQUIRES TIMELINE: AT&T's Merger With Time Warner Follows Decades Of Industry Deals http://www.npr.org/sections/thetwo-way/2016/10/22/498996253/timeline-at-ts-merger-with-time-warner-follows-decades-of-industry-deals?utm_medium=RSS&utm_campaign=technology 24/10/16
AOL Company Time Warner Inc. Company ACQUIRES TIMELINE: AT&T's Merger With Time Warner Follows Decades Of Industry Deals http://www.npr.org/sections/thetwo-way/2016/10/22/498996253/timeline-at-ts-merger-with-time-warner-follows-decades-of-industry-deals?utm_medium=RSS&utm_campaign=technology 24/10/16
Comcast Company The Walt Disney Company Company ACQUIRES TIMELINE: AT&T's Merger With Time Warner Follows Decades Of Industry Deals http://www.npr.org/sections/thetwo-way/2016/10/22/498996253/timeline-at-ts-merger-with-time-warner-follows-decades-of-industry-deals?utm_medium=RSS&utm_campaign=technology 24/10/16
SBC Corporation Company Southwestern Bell Corporation Company ACQUIRES TIMELINE: AT&T's Merger With Time Warner Follows Decades Of Industry Deals http://www.npr.org/sections/thetwo-way/2016/10/22/498996253/timeline-at-ts-merger-with-time-warner-follows-decades-of-industry-deals?utm_medium=RSS&utm_campaign=technology 24/10/16
Comcast Company NBC Universal Company ACQUIRES TIMELINE: AT&T's Merger With Time Warner Follows Decades Of Industry Deals http://www.npr.org/sections/thetwo-way/2016/10/22/498996253/timeline-at-ts-merger-with-time-warner-follows-decades-of-industry-deals?utm_medium=RSS&utm_campaign=technology 24/10/16
sss Company sdadasfd Company ACQUIRES bndfhfdhedr http://www.npr.org/sections/thetwo-way/2016/10/22/498996253/timeline-at-ts-merger-with-time-warner-follows-decades-of-industry-deals?utm_medium=RSS&utm_campaign=technology 24/10/16
Done!!
Actually the if condition is returning false all the time thats why its not creating any node and relations.I just changed my if condition and now its working fine.
try
{
Result result;
result = graphDb.execute("merge (a:" + subjecttype + "{name:\"" + subject + "\"}) return a;");
result = graphDb.execute("merge (a:" + objecttype + "{name:\"" + object + "\"}) return a;");
result = graphDb.execute("merge (a:" + subjecttype + "{name:\"" + subject + "\"}) " + "merge(b:"
+ objecttype + "{name:\"" + object + "\"}) " + "merge (a)-[r:" + relationship + "{headlines:\""
+ headline + "\",newslink:\"" + newslink + "\",date:\"" + date + "\"" + "}]->(b) return r;");
tx.success();
}
finally {
tx.close();
}
Sorry to say this, but this code is really messy! Besides, we can't reproduce your results, since we don't have the data and the code is far from being a minimal example. We can't really debug for you: isolate each step, see if does anything, etc.
Here' a few tips and remarks, though.
Testing absent results
if (result.toString().equals("empty iterator"))
Really? Please, use the API instead of a string conversion, which is never a stable interface (it's not part of any contract):
if (!result.hasNext())
Variable or label?
Do the values of subjecttype and objecttype represent a variable name or the node of a label? The former doesn't really make sense (why should the query change when it's functionally the same), but the latter isn't properly used:
result=graphDb.execute("match ("+subjecttype+"{name:\""+subject+"\"}) return "+subjecttype+".name;");
subjecttype is used as a variable in the return clause, but looks like a label in the match clause, except it's missing a leading colon:
result=graphDb.execute("match (n:"+subjecttype+"{name:\""+subject+"\"}) return n.name");
(The final semi-colon is unnecessary)
You're actually using it correctly for the matching create:
result=graphDb.execute("create (a:"+subjecttype+"{name:\""+subject+"\"}) return a;");
Query parameters
Also, your query is vulnerable to "Cypher injection" (a relative of SQL injection), if subject contains quotes. Use query parameters instead:
result = graphDb.execute("match (n:" + subjecttype + " {name:{name}}) return n.name",
Collections.<String, Object>singletonMap("name", subject));
It has the added benefit of making the query generic, which means it's not parsed and its execution plan is not computed for each line (only once per label).
Use MERGE
You could replace your logic by simply using MERGE instead of MATCH + CREATE:
result = graphDb.execute("merge (n:" + subjecttype + " {name:{name}}) return n",
Collections.<String, Object>singletonMap("name", subject));
Power to Cypher
Your multiple queries could actually be reduced to a single one, except for the filter on relationship being contained in relations:
Map<String, Object> params = new HashMap<>();
params.put("subject", subject);
params.put("object", object);
params.put("headline", headline);
params.put("newslink", newslink);
params.put("date", date);
graphDb.execute(
"MERGE (a:" + subjecttype + " {name: {subject}}) " +
"MERGE (b:" + objecttype + " {name: {object}}) " +
"MERGE (a)-[r:" + relationship + "]->(b) " +
"ON CREATE SET r.headlines = {headline}, " +
" r.newslink = {newslink}, " +
" r.date = {date}",
params);
With the filter, it's actually 3 queries:
Map<String, Object> params = new HashMap<>();
params.put("subject", subject);
params.put("object", object);
params.put("headline", headline);
params.put("newslink", newslink);
params.put("date", date);
graphDb.execute("MERGE (a:" + subjecttype + " {name: {subject}})", params);
graphDb.execute("MERGE (b:" + objecttype + " {name: {object}})", params);
if (relations.contains(relationship)) {
graphDb.execute(
"MATCH (a:" + subjecttype + " {name: {subject}}) " +
"MATCH (b:" + objecttype + " {name: {object}}) " +
"MERGE (a)-[r:" + relationship + "]->(b) " +
"ON CREATE SET r.headlines = {headline}, " +
" r.newslink = {newslink}, " +
" r.date = {date}",
params);
}
Try-with-resources
Transaction is AutoCloseable, which means you should use a try-with-resources instead of managing it manually. Instead of
Transaction tx = graphDb.beginTx();
try {
// ...
} finally {
tx.close();
}
just do
try (Transaction tx = graphDb.beginTx()) {
// ...
}
I am working on a project, using cassandra 1.2, hadoop 1.2
I have created my normal cassandra mapper and reducer, but I want to create my own Input format class, which will read the records from cassandra, and I'll get the desired column's value, by splitting that value using splitting and indexing ,
so, I planned to create custom Format class. but I'm confused and not able to know, how would I make it? What classes are to be extend and implement, and how I will able to fetch the row key, column name, columns value etc.
I have my Mapperclass as follow:
public class MyMapper extends
Mapper<ByteBuffer, SortedMap<ByteBuffer, IColumn>, Text, Text> {
private Text word = new Text();
MyJDBC db = new MyJDBC();
public void map(ByteBuffer key, SortedMap<ByteBuffer, IColumn> columns,
Context context) throws IOException, InterruptedException {
long std_id = Long.parseLong(ByteBufferUtil.string(key));
long newSavePoint = 0;
if (columns.values().isEmpty()) {
System.out.println("EMPTY ITERATOR");
sb.append("column_N/A" + ":" + "N/A" + " , ");
} else {
for (IColumn cell : columns.values()) {
name = ByteBufferUtil.string(cell.name());
String value = null;
if (name.contains("int")) {
value = String.valueOf(ByteBufferUtil.toInt(cell.value()));
} else {
value = ByteBufferUtil.string(cell.value());
}
String[] data = value.toString().split(",");
// if (data[0].equalsIgnoreCase("login")) {
Long[] dif = getDateDiffe(d1, d2);
// logics i want to perform inside my custominput class , rather here, i just want a simple mapper class
if (condition1 && condition2) {
myhits++;
sb.append(":\t " + data[0] + " " + data[2] + " "+ data[1] /* + " " + data[3] */+ "\n");
newSavePoint = d2;
}
}
sb.append("~" + like + "~" + newSavePoint + "~");
word.set(sb.toString().replace("\t", ""));
}
db.setInterval(Long.parseLong(ByteBufferUtil.string(key)), newSavePoint);
db.setHits(Long.parseLong(ByteBufferUtil.string(key)), like + "");
context.write(new Text(ByteBufferUtil.string(key)), word);
}
I want to decrease my Mapper Class logics, and want to perform same calculations on my custom input class.
Please help, i wish for the positive r4esponse from stackies...
You can do the intended task by moving the Mapper logic to your custom input class (as you have indicated already)
I found this nice post which explains a similar problem statement as you have. I think it might solve your problem.
I am trying to write a test for a dropbox crawler.
I have setup a Fixture which loads data into the database on load.
Fixtures.deleteDatabase();
Fixtures.loadModels("testusers.yaml");
The problem occurs when the code
da = da.merge(); //exceptions gets thrown here
is reached. It throws an JdbcSQLException:
org.h2.jdbc.JdbcSQLException: Timeout trying to lock table "USER"; SQL statement:
select user0_.id as id7_0_, user0_.activationSent as activati2_7_0_,
user0_.confirmationCode as confirma3_7_0_, user0_.email as email7_0_,
user0_.first_name as first5_7_0_, user0_.isAdmin as isAdmin7_0_,
user0_.lastLogin as lastLogin7_0_, user0_.last_name as last8_7_0_,
user0_.passwordHash as password9_7_0_,
user0_.recoverPasswordCode as recover10_7_0_,
user0_.referralCode as referra11_7_0_,
user0_.signupDate as signupDate7_0_ from User user0_ where user0_.id=? [50200-149]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:327)
at org.h2.message.DbException.get(DbException.java:167)
at org.h2.message.DbException.get(DbException.java:144)
at org.h2.table.RegularTable.doLock(RegularTable.java:499)
at org.h2.table.RegularTable.lock(RegularTable.java:433)
at org.h2.table.TableFilter.lock(TableFilter.java:140)
at org.h2.command.dml.Select.queryWithoutCache(Select.java:571)
at org.h2.command.dml.Query.query(Query.java:257)
at org.h2.command.dml.Query.query(Query.java:227)
at org.h2.command.CommandContainer.query(CommandContainer.java:78)
at org.h2.command.Command.executeQuery(Command.java:178)
at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.java:96)
at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208)
at org.hibernate.loader.Loader.getResultSet(Loader.java:1953)
at org.hibernate.loader.Loader.doQuery(Loader.java:802)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
at org.hibernate.loader.Loader.loadEntity(Loader.java:2037)
... 36 more
UpdateDropboxRowJob.java where the exception gets thrown
package mashpan.crawl.jobs;
import play.Logger;
import play.jobs.*;
import models.DropboxAuthentication;
public class UpdateDropboxRowJob extends Job {
private DropboxAuthentication da;
private long count;
private boolean crawled;
public UpdateDropboxRowJob(DropboxAuthentication da, long count, boolean crawled) {
Logger.debug("[UpdateDropboxRowJob] ctor: "
+",[da=" + (da == null ? "null" : da.toString()) + "]"
+", count=[" + count + "]"
+", crawled=[" + crawled + "]"
);
this.da = da;
this.count = count;
this.crawled = crawled;
}
#Override
public void doJob() throws Exception {
Logger.debug("[UpdateDropboxRowJob] doJob: "
+"da=[" + (da == null ? "null" : da.toString()) + "]"
+"count=[" + count + "]"
+"crawled=[" + crawled + "]"
+"da.count"+(da == null ? "da==null" : da.count) + "]"
);
da = da.merge(); //exceptions gets thrown here
da.count = this.count;
da.crawled = this.crawled;
da.save();
}
}
#Entity
public class DropboxAuthentication extends Model {
public String type;
//public String email;
public String token_key;
public String token_secret;
public long count;
public boolean crawled;
public byte[] cipher;
public byte[] iv;
public long lastCrawlTime;
#OneToOne
public User user;
public DropboxAuthentication(String type, byte[] cipher, byte[] iv, String token_key, String token_secret, User u, Date issueDate)
{
this.type = type;
this.cipher = cipher;
this.iv = iv;
this.token_key = token_key;
this.token_secret = token_secret;
this.user = u;
}
#Override
public String toString() {
return "DropboxAuthentication [type=" + type + ", token_key="
+ token_key + ", token_secret=" + token_secret + ", count="
+ count + ", crawled=" + crawled + ", cipher="
+ Arrays.toString(cipher) + ", iv=" + Arrays.toString(iv)
+ ", lastCrawlTime=" + lastCrawlTime + ", user=" + user + "]";
}
}
I suppose that there is a problem with transactions, I tried to do
JPA.em().getTransaction().commit();
but I gives me another exception that the transaction is not activated. Suprisingly
JPA.em().getTransaction().begin();
JPA.em().getTransaction().commit();
throws an exception that there is already a transaction running.
So probably the main question is how play handles transactions in test mode
play framework: 1.2.3
*********** UPDATE *************
I changed my application.conf from
%db=mem
to
%test.db=mysql://USER:PASSWORD#localhost/development
In the following I get a different exception (at the same line as before) now:
javax.persistence.EntityNotFoundException: Unable to find models.User with id 1
at org.hibernate.ejb.Ejb3Configuration$Ejb3EntityNotFoundDelegate.handleEntityNotFound(Ejb3Configuration.java:133)
at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:233)
at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:285)
at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
at org.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1038)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:630)
at org.hibernate.type.EntityType.resolve(EntityType.java:438)
at org.hibernate.type.EntityType.replace(EntityType.java:298)
at org.hibernate.type.AbstractType.replace(AbstractType.java:176)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:212)
at org.hibernate.event.def.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:600)
at org.hibernate.event.def.DefaultMergeEventListener.mergeTransientEntity(DefaultMergeEventListener.java:337)
at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:303)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:258)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:84)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:867)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:851)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:855)
at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:686)
at play.db.jpa.GenericModel.merge(GenericModel.java:211)
at mashpan.crawl.jobs.UpdateDropboxRowJob.doJob(UpdateDropboxRowJob.java:33)
at play.jobs.Job.doJobWithResult(Job.java:50)
at play.jobs.Job.call(Job.java:146)
However, looking in the db I see that the user is actually persisted there.
Is it possible that the job is called concurrently? or there is some other server code that is also writing to that DropboxAuthentication table?
If so, maybe you could try increasing the lock timeout for the H2 database by doing this to see if it makes a difference.