I know that SchemaExport should be my friend. But I am using liquibase and want to execute the DDL - pure sql statements - generated from liquibase to recreate the database before every test method.
Do you see problems with the following code? I wonder that this seems to be so complicated ...
public static int executeScript(String sqlFileOnClasspath) {
Session sess = getSessionFactory().openSession();
Transaction ta = sess.beginTransaction();
int sqlCmd = 0;
try {
BufferedReader bReader = new BufferedReader(new InputStreamReader(
Util.class.getResourceAsStream(sqlFileOnClasspath), "UTF-8"));
String line;
while ((line = bReader.readLine()) != null) {
if (line.startsWith("--") || line.trim().isEmpty())
continue;
final String tmp = line;
sess.doWork(new Work() {
#Override
public void execute(Connection connection) throws SQLException {
connection.createStatement().execute(tmp);
}
});
sqlCmd++;
}
} catch (Exception ex) {
log.error("Couldn't execute " + sqlFileOnClasspath, ex);
} finally {
ta.commit();
sess.close();
}
return sqlCmd;
}
BTW: For liquibase you will need to do:
// remove all hibernate managed tables
SchemaExport schemaTool = new SchemaExport(getConfiguration());
schemaTool.drop(false, true);
// DROP TABLE DATABASECHANGELOGLOCK;
// DROP TABLE DATABASECHANGELOG;
executeScript("/drop-none-hib.sql");
// now execute the DDL statements from liquibase
int sqlCmd = executeScript("/schema.sql");
log.info("Executed " + sqlCmd + " sql commands");
Security
Directly executing unprepared statements may lead to SQL injection. However, you seem to read DDL statements from a static file, so this shouldn't be a problem, just wanted to note that.
Exception Handling
Missing entirely, e.g. properly closing the input streams, the connections etc. Also, it misses having detailed information on which of the statements failed if any of the SQLExceptions are being thrown. sqlCmd variable also counts failed statements, so I doubt the usefulness of this counter.
Parsing SQL/DDL
You're testing for -- style comments, but not for /* */ comments. That'll bite you some day. Also, the parser assumes a SQL statement per line. Longer statements may be multiline and end with semicolons which need to be trimmed. Not sure about how liquibase generates the statements though, so this may not be a real problem.
Transaction Handling
DDL statements cannot be rolled back anyway, so not sure about the transactions being helpful. Please correct me if i'm wrong.
Otherwise, as it's for testing, i looks fine to me too.
Why don't you simply use the hbm2ddl.auto configuration?
Or use transactional tests that roll the database back to its original state after each and every test?
Related
Quick question.. In a function that checks if a certain Terminal Id is available, is it okay for me to do it as below?
using (var tx = session.BeginTransaction())
{
return ((new TerminalDAO(sm.Session)).Get(tid) == null) ? true : false;
}
Or is it advisable to do it with the Commit()?
Terminal terminal = null;
using (var tx = session.BeginTransaction())
{
terminal = (new TerminalDAO(session)).Get(tid);
tx.Commit();
}
return (terminal == null) ? true : false;
For readonly operations - I would firstly appreciate you, that you did included transaction even for readonly operation - and also suggest to use a rollback and in fact the explicit rollback.
Please check these:
Is it a better practice to explicitly call transaction rollback or let an exception trigger an implicit rollback?
Do I need to call rollback if I never commit?
My reason for explicit Rollback() would be:
Why rollback? We know it is READ, any accidental WRITE is not intended
Why explicit? Anyone coming later can see it. Self-describing code is the way. Relying on defaults could later lead to unexpected behaviour (e.g. vendor changed the defaults)
I have a problem with Session.Clear(), as I understand it this statement should completely reset all changes made in the UOW, the cache etc. The problem is that is does not. My scenario is illustrated in the test below. I add to items, there is a dependency between them I then try to delete the item which the other item is dependent upon. This will cause an exception which is correct. I then clear the session. Finally I try add a new item to the database, when flushing NHibernate will once again try to execute the failing delete statement. Am I misunderstanding the use of Session.Clear()? Or am I missing something else here?
[Fact]
public void Verify_Cant_Clear_Delete()
{
var session = SessionFactory.OpenSession();
var category = new ManufacturerCategory { Name = "category" };
var man = new Manufacturer { Category = category, Name = "man" };
session.Save(category);
session.Save(man);
session.Flush();
try
{
// this will cause
// NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available]
// ---> System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "ManufacturerCategoryId".
// The conflict occurred in database "LabelMaker-Tests", table "dbo.Manufacturers", column 'Category_id'.
session.Delete(category);
session.Flush();
}
catch (Exception ex)
{
// This should clear the session
session.Clear();
}
try
{
var category2 = new ManufacturerCategory { Name = "category 2" };
session.Save(category2);
session.Flush();
}
catch(Exception ex)
{
// this will cause ONCE AGAIN cause
// NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available]
// ---> System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "ManufacturerCategoryId".
// The conflict occurred in database "LabelMaker-Tests", table "dbo.Manufacturers", column 'Category_id'.
Assert.True(false);
}
}
You should be using transactions and rollback the transaction on an hibernate exception because...
Certain methods of ISession will not leave the session in a consistent state.
http://nhibernate.info/doc/nh/en/index.html#manipulatingdata-exceptions
or let the transaction be implicitly rolled back via dispose()...
using (ISession sess = factory.OpenSession())
using (ITransaction tx = sess.BeginTransaction())
{
// do some work
...
tx.Commit();
}
I'd guess that the Delete() method is one of the inconsistent state methods hinted at in the above quote...
Your assumptions about ISession.Clear() are not correct.
In particular, as soon as an exception happens, you must discard the session. Clear() will not fix the state (check out 9.8 Exception Handling)
Also, as dotjoe mentioned, you should be using a transaction to do your work.
I just test PetaPoco Transaction in a multithread way...
I have a simple test case :
-- Simple value object call it MediaDevice
-- Insert a record an update it for 1000 times
void TransactionThread(Object object)
{
Database db = (Database) object;
for(int i= 0; i < 1000;i++)
{
Transaction transaction = db.GetTransaction();
MediaDevice device = new MediaDevice();
device.Name = "Name";
device.Brand = "Brand";
db.Insert(device);
device.Name = "Name_Updated";
device.Brand = "Brand_Updated";
db.Update(device);
transaction.Complete();
}
long count = db.ExecuteScalar<long>("SELECT Count(*) FROM MediaDevices");
Console.WriteLine("Number of all records:" + count);
}
And I call this in two threads like this:[ Single Database object for both threads]
void TransactionTest()
{
Database db = GetDatabase();
Thread tThread1 = ... // thread for TransactionTest()
Thread tThread2 = ... // thread for TransactionTest()
tThread1.Start(db); // pass Database to TransactionTest()
tThread2.Start(db); // pass same Database to TransactionTest()
}
I get Null error or sometimes Object disposed error for Database..
But when i supply two Database instance,
void TransactionTest()
{
Database db = GetDatabase();
Database db2 = GetDatabase();
Thread tThread1 = ... // thread for TransactionTest()
Thread tThread2 = ... // thread for TransactionTest()
tThread1.Start(db); // pass Database instance db to TransactionTest()
tThread2.Start(db2); // pass Database intance db2 to TransactionTest()
}
Everthing is OK...
Well When I check PetaPoco source code at transaction I see that at transaction.Complete
public virtual void Complete()
{
_db.CompleteTransaction();
_db = null;
}
My question is that to able to use transaction from multiple threads Do I have to use new copy of Database object? Or what am i doing wrong?
And to make it thread safe do i have to open and close NEW database at every data update-query?
Yes, you need a separate PetaPoco Database instance per-thread. See this quote from the PetaPoco documentation:
Note: for transactions to work, all operations need to use the same
instance of the PetaPoco database object. So you'll probably want to
use a per-http request, or per-thread IOC container to serve up a
shared instance of this object. Personally StructureMap is my
favourite for this.
I bolded the phrase that gives the clue. It is saying that one instance of the PetaPoco database object should be used per-thread.
Hi use with nolock in select query because the table may be locked. long count = db.ExecuteScalar("SELECT Count(*) with nolock FROM MediaDevices");
sorry dude.. yes you are right. they change the object to be null. so you cannot use the same object to threading. you have to use they use described like db=GetDataBase() ; db2=GetDataBase();
otherwise you can change the source code for your requirement. i think their license allow it. but i am not sure.
I am using Datanucleus JDO on top of HSqlDb.
I would like to execute the following SQL statement to tell HsqlDb to set the write delay to 0:
"SET WRITE_DELAY 0"
Is there a way I can do this from a JDO PersistenceManager or a PersistenceManagerFactory?
On a sidenote: I have tried to modify write_delay by using the following connection URL:
jdbc:hsqldb:file:data/hsqldb/dbbench;write_delay=false
It didn't work. I debugged the HsqlDb sources and I could still see the write delay being set to 10 seconds.
I think I have found a solution that will work for me:
public PersistenceManager getPersistenceManager() {
PersistenceManager persistenceManager =
_persistenceManagerFactory.getPersistenceManager();
JDOConnection dataStoreConnection =
persistenceManager.getDataStoreConnection();
Object nativeConnection = dataStoreConnection.getNativeConnection();
if(! (nativeConnection instanceof Connection) ){
return persistenceManager;
}
Connection connection = (Connection) nativeConnection;
try {
Statement statement = connection.createStatement();
statement.executeUpdate("SET WRITE_DELAY 0");
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
return persistenceManager;
}
You can write a startup script, dbbench.script in this example, and put the SQL in there.
See: http://best-practice-software-engineering.ifs.tuwien.ac.at/technology/tech-hsqldb.html
I think this page
http://www.datanucleus.org/products/accessplatform/jdo/datastore_connection.html
tells all needed. No ?
How can i force Nhibernate transaction to fail(from the calling code) so i can make sure that the failing behavior is working properly?
I can't modify the source code i just need to make it fail!
example :
public void DoSomething(/*Some parameters*/){
using (var tx = _session.BeginTransaction())
{
try
{
//Do something
tx.Commit();
}
catch (Exception)
{
if (tx != null) tx.Rollback();
throw;
}
} }
Throw an exception.
Throw an exception:
using(var sess = sf.OpenSession())
using(var tx = sess.BeginTransaction())
throw new Exception();
Close the connection:
using(var sess = sf.OpenSession())
using(var tx = sess.BeginTransaction())
sess.Connection.Close();
Rollback the transaction:
using(var sess = sf.OpenSession())
using(var tx = sess.BeginTransaction())
tx.Rollback();
If you have to have the exception occur within the tx.Commit(), then maybe insert/update a record with invalid data (e.g. a string that's too long for the db column).
One idea would be while you're debugging stop at the line immediately before the query execution will be begin and turn off the database then let the code run. However I assume there is a better and more programmatic way to test this.
Set some global variable (I know people had global variables but this is a good use) that the transaction internal code reads and if it sees the variable set throw an exception.
You can write a unit test to verify the expected behaviour of your code.
One way to do it is:
[Test]
public void VerifyExceptionIsThrown()
{
Assert.Throws<NHibernate.HibernateException>(
() =>
{
using (var tx = _session.BeginTransaction())
{
_session.Update(new Entity());
tx.Commit();
}
});
}
An attempt to update a transient entity will throw an exeption.
Alternatively if you use the NHibernateValidator you can explicitly fail a validation you have set, say you entity's Name property should no more than 10 characters long.
If you populate your entity's Name property with a string longer than 10 characters and you try to Save it, the tx.Commit() will throw you an exception.