Timeout trying to lock table when trying to merge - java

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.

Related

Merge user's Android Room database when migrating database

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();

Date-value is two days behind inserted value after sending via REST and saving with JPA

I have a Angular Frontend with Java Backend and I'd like to use PrimeNG's DateInput to receive and save a Date. I got the following code for that:
<p-calendar [(ngModel)]="enddate" dateFormat="dd.mm.yy" class="medium-field"></p-calendar>
and in my component:
enddate: Date;
When sending this value via REST, I have the following code (including check):
createPSP(project_num: string, financialItem: FinancialItem) {
this.logger.info("Startdate is " + financialItem.startDate);
this.logger.info("Enddate is " + financialItem.endDate);
let order_num = financialItem.orderNumber;
let psp_num = financialItem.pspNumber;
return this.http.post(`${httpBaseUrl}/project/${project_num}/order/${order_num}/addFinancialItem/${psp_num}`, financialItem).pipe();
}
Output (which is right):
Then I save it in my backend in a LocalDate-variable (which converts to date-type in MySQL). Now what happens is that I insert 31.12.2018 and I get (in my Converter in backend) 30.12.2018 and in MySQL 29.12.2018. Why is that the case?
Edit: When I change LocalDate to LocalDateTime I am just getting 30.12.2018 in MySQL instead of 29.12.2018 which is obviously still wrong.
Some more code:
I defined my MySQL-Column like this (in my Entity):
#Entity
class FinancialItemDto {
//...
#Column(name = "ENDDATE", nullable = false)
private LocalDate endDate;
}
In the Controller:
public ResponseEntity addFinancialItem(#PathVariable String project_num, #PathVariable String order_num,
#PathVariable String psp_num, #RequestBody FinancialItemDto financialItemDto) {
try {
this.financialItemService.saveItemGivenProjectAndOrderNumber(financialItemDto, order_num);
} catch (NoSuchEntityException e) {
this.logger.error(e.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getUserMessage());
}
return ResponseEntity.ok(HttpStatus.OK);
}
In the Service:
#Transactional
#Override
public void saveItemGivenProjectAndOrderNumber(FinancialItemDto financialItemDto, String orderNumber)
throws NoSuchEntityException {
OrderEntity order = this.orderRepository.findByOrderNumber(orderNumber).orElseThrow(
() -> new NoSuchEntityException("Order with number " + orderNumber + " could not be found.",
"Der Abruf wurde nicht gefunden."));
OrdertypesEntity ordertype = this.ordertypesRepository.findByShorthand(financialItemDto.getOrderType())
.orElseThrow(() -> new NoSuchEntityException("Ordertype " + financialItemDto.getOrderType()
+ " for creating FI to order " + orderNumber + " could not be found.",
"Der Abruftyp wurde nicht gefunden."));
FinancialItemEntity financialItemEntity = FinancialItemConverter.dtoToEntity(financialItemDto, ordertype,
order);
this.entityManager.persist(financialItemEntity);
}
The Dto on TS-Side defines the date like follows:
export class FinancialItem {
endDate: Date;
//...
}
My Converter just passes on:
public static FinancialItemEntity dtoToEntity(FinancialItemDto financialItemDto, OrdertypesEntity ordertype, OrderEntity order) {
FinancialItemEntity financialItemEntity = new FinancialItemEntity( (...), financialItemDto.getEndDate(), (...));
LoggerFactory.getLogger(FinancialItemConverter.class).info("Got Date Value: " + financialItemDto.getEndDate()); //gives: Got Date Value: 2018-12-30 instead of value 31.12.2018
return financialItemEntity;
}
Update:
A workaround for the REST-Service to "loose" one day was saving the date in long-format and then pass this and convert it back. Sadly, when calling repository.save()-function, the logger calls that when I inserted 2018-12-01 the date-value is also 2018-12-01, but MySQL says it is 2018-11-30 in my database. I cannot make out what is happening there:
this.logger.info("Startdate is " + financialItemDto.getStartDate()); //2018-12-01
this.logger.info("Startdate is " + financialItemEntity.getStartDate()); //2018-12-01
this.financialItemRepository.save(financialItemEntity);
this.logger.info("Startdate (received) is " + this.financialItemRepository.getFinancialItemByPSPNumber(financialItemDto.getPspNumber()).get().getStartDate()); //2018-12-01

Not creating any node and relations in neo4j db folder just creates the blank .db folder using java

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()) {
// ...
}

How to write a VanillaChronicle

Having a very simple poc such as this one:
IndexedChronicle chronicle = getChronicle("basic");
ExcerptAppender appender = chronicle.createAppender();
appender.startExcerpt();
appender.writeObject(new MessageKey("type", 123L));
appender.finish();
ExcerptTailer tailer = chronicle.createTailer();
while(tailer.nextIndex()) {
MessageKey key = (MessageKey) tailer.readObject();
System.out.println("key " + key);
}
VanillaChronicle vcron = getVainllaChronicle("vanilla");
VanillaAppender app = vcron.createAppender();
app.startExcerpt();
app.writeObject(new MessageKey("type", 123L));
app.finish();
ExcerptTailer vtail = vcron.createTailer();
while(vtail.nextIndex()) {
MessageKey key = (MessageKey) vtail.readObject();
System.out.println("key " + key);
}
Gives me an IndexOutOfBoundsException in the writeObject method on the VanillaAppender.
However, there is little difference, and nothing exceptionally different in the docs
Can anyone suggest how it should be used?
Update:
I re-arranged the code so it became identical to peters (copied it in, actually), but I still get this exception:
Exception in thread "main" java.lang.IndexOutOfBoundsException: position is beyond the end of the buffer 372 > -190495716
at net.openhft.lang.io.NativeBytes.checkEndOfBuffer(NativeBytes.java:518)
at net.openhft.lang.io.AbstractBytes.writeObject(AbstractBytes.java:1897)
at main.ChronicleTest.main(ChronicleTest.java:31)
The version imported is 3.2.1
<dependency>
<groupId>net.openhft</groupId>
<artifactId>chronicle</artifactId>
<version>3.2.1</version>
</dependency>
When I try this with Chronicle 3.2.1
public class SO25623856Main {
public static void main(String[] args) throws IOException {
Chronicle vcron = new VanillaChronicle("vanilla");
ExcerptAppender app = vcron.createAppender();
app.startExcerpt();
app.writeObject(new MessageKey("type", 123L));
app.finish();
ExcerptTailer vtail = vcron.createTailer();
while (vtail.nextIndex()) {
MessageKey key = (MessageKey) vtail.readObject();
System.out.println("key " + key);
}
vcron.close();
}
}
class MessageKey implements Serializable {
private String type;
private long l;
public MessageKey(String type, long l) {
this.type = type;
this.l = l;
}
#Override
public String toString() {
return "MessageKey{" +
"type='" + type + '\'' +
", l=" + l +
'}';
}
}
it prints
key MessageKey{type='type', l=123}
BTW I suggest you use Externalizable or ByteMarshallable for improved performance and smaller messages.

How to use resultset.next method after executing result.beforeFirst in java using mysql

I need help on how to scroll back to the next record on the resultset returned by java. I'm using mysql database.
Here is the code inside the formshow event. Where I load the first resultset that is being returned:
if (rs.next()) {
jLabel5.setText(rs.getString("Question"));
jRadioButton1.setText("A. " + rs.getString("A"));
jRadioButton2.setText("B. " + rs.getString("B"));
jRadioButton3.setText("C. " + rs.getString("C"));
jRadioButton4.setText("D. " + rs.getString("D"));
}
And here's the button which is supposed to be used to scroll forward through the database.
I need to execute rs.beforeFirst because the things that are displayed on the jFrame doesn't match with the variable that I'm trying to validate:
try {
rs.beforeFirst();
if (rs.next()) {
jLabel5.setText(rs.getString("Question"));
jRadioButton1.setText("A. " + rs.getString("A"));
jRadioButton2.setText("B. " + rs.getString("B"));
jRadioButton3.setText("C. " + rs.getString("C"));
jRadioButton4.setText("D. " + rs.getString("D"));
if (jRadioButton1.isSelected()) {
rval = jRadioButton1.getText().charAt(0);
if (String.valueOf(rval).equalsIgnoreCase(rs.getString("Answer"))) {
JOptionPane.showMessageDialog(null, "Correct! Your answer is " + rval + " answer is: " + rs.getString("Answer"));
} else {
JOptionPane.showMessageDialog(null, "Wrong! your answer is " + rval + " answer is: " + rs.getString("Answer"));
}
}
}
}catch (Exception e) {
e.printStackTrace();
}
My question is how do I continue on scrolling through the database. Because the resultset doesn't progress when I use the rs.beforeFirst() before the rs.next()
I also tried doing:
while(rs.next()){...}
It worked but it didn't let me choose what radioButton I want. And it continued to execute until the end of the result set even without manually clicking on the button(for scrolling) multiple times. Please help me figure out what's the solution to this. If you need more details just ask. Thanks.
You shouldn't be mingling database access logic with presentation logic. That only leads to tight coupled code where the both concerns only collides with each other. The database job needs to be done as soon as possible.
You need to separate the concerns.
First create a class which represents a single row of the database.
public class Question {
private String text;
private String answer;
private String optionA;
private String optionB;
private String optionC;
private String optionD;
// Add/generate c'tors/getters/setters/equals/hashcode and other boilerplate.
}
(a bit decent IDE like Eclipse can autogenerate them)
Then create a class which does the following JDBC job:
public List<Question> list() throws SQLException {
List<Question> questions = new ArrayList<Question>();
// ...
try {
// ...
while (resultSet.next()) {
Question question = new Question();
question.setText(resultSet.getString("Question"));
question.setAnswer(resultSet.getString("Answer"));
question.setOptionA(resultSet.getString("A"));
question.setOptionB(resultSet.getString("B"));
question.setOptionC(resultSet.getString("C"));
question.setOptionD(resultSet.getString("D"));
questions.add(question);
}
} finally {
// ...
}
return questions;
}
Finally just work with List<Question> the straightforward way.
List<Question> questions = questionDAO.list();
int size = questions.size();
JOptionPane.showMessageDialog(null, "There are " + size + " questions!");
for (Question question : questions) {
jLabel5.setText(question.getText());
jRadioButton1.setText("A. " + question.getOptionA());
jRadioButton2.setText("B. " + question.getOptionB());
jRadioButton3.setText("C. " + question.getOptionC());
jRadioButton4.setText("D. " + question.getOptionD());
// ...
}
No need to massage the resultset forth and back.

Categories

Resources