ok, so Ayende recommends always using a transaction, even for read operations.
but supposing I have the following scenario:
public Employee GetEmployeeByName(string name)
{
using (ITransaction tx = CurrentSession.BeginTransaction())
{
return dao.GetEmployeeByName(name);
}
}
public void SaveNewEmployee(Employee employee)
{
using (ITransaction tx = CurrentSession.BeginTransaction())
{
if (GetEmployeeByName(employee.Name) != null)
{
throw new ArgumentException("employee with same name found");
}
CurrentSession.Save(employee);
}
}
this would actually throw an exception, since nhibernate doesn't support nested transactions.
how can I get around this?
EDIT
this is even a better solution than the one I accepted...
Typically you would get around it by using a unit of work pattern in which you can start your transaction at the same time you open your session. That is to say at the beginning of the unit of work. And you would commit it at the end of the unit of work.
Related
I'm using jberet implementation of JSR 352 java batch specs.
Actually I need a separate transaction for doing a singular update, something like this:
class MyItemWriter implements ItemWriter
#Inject
UserTransaction transaction
void resetLastProductsUpdateDate(String uidCli) throws BusinessException {
try {
if (transaction.getStatus() != Status.STATUS_ACTIVE) {
transaction.begin();
}
final Customer customer = dao.findById(id);
customer.setLastUpdate(null);
customer.persist(cliente);
transaction.commit();
} catch (RollbackException | HeuristicMixedException | HeuristicRollbackException | SystemException | NotSupportedException e) {
logger.error("error while updating user products last update");
throw new BusinessException();
}
}
I first tried marking resetLastProductsUpdateDate methoad as #Transactional(REQUIRES_NEW), however it didn't worked.
My question is:
Is there any more elegant way to achieve this singular transaction without manually handle of transaction?
While does UserTransation works, EntityManager.transaction doesn't. I don't get it why.
Class below, which is injected from a Batchlet, works properly; Why I can't get to make work the #Transactional annotation on resetLastProductsUpdateDate method instead?
public class DynamicQueryDAO {
#Inject
EntityManager entityManager;
#Inject
private Logger logger;
#Transactional(Transactional.TxType.REQUIRED)
public void executeQuery(String query) {
logger.info("executing query: {}", query);
final int output = entityManager.createNativeQuery(query).executeUpdate();
logger.info("rows updated: {}", output);
}
}
EDIT
Actually I guess neither usertransaction is a good solution, because it affects entire itemwriter transaction management. Still Don't know how to deal with transaction isolation :(
In general the batch application should avoid directly handling transaction. You can have your batch component to throw some business exceptions upon certain conditions, and configure your job.xml to trigger retry upon this business exception. During retry, each individual data will be processed and committed in its own chunk.
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)
can this code makes some bad things? I found it in one project and do not know if it can be cause of some crazy bugs(deadlocks, timeouts in DB,...). Code like this is executed concurently many times in program even in threads.
Thanks a lot
class first {
void doSomething {
using (ITransaction transaction = session.BeginTransaction){
var foo = new second();
foo.doInNewTransaction(); //inner transaction in new session
transaction.Commit();
}
}
}
class second {
void doInNewTransaction(){
using (Session session = new Session()){
using (ITransaction transaction = session.BeginTransaction){
//do someting in database
transaction.Commit();
}
}
}
}
This should be fine. I'm sure I have done stuff like this in the past. The only thing that you need to be aware of is that if you modify an object in the inner session then these changes will not automatically be reflected in the outer session if the same object has already been loaded.
Having said that, if you do not need to do this then I would avoid it. Normally I would recommend AOP based transaction management when using NHibernate. This would allow your inner component to easily join in with the transaction from the outer component. However, in order to do this you need to be using a DI container that supports this, for example Spring.NET or Castle.
I've just start studying NHibernate 2 days ago, and I'm looking for a CRUD method that I've written based on an tutorial.
My insert method is:
using (ISession session = Contexto.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
session.Save(noticia);
transaction.Commit();
session.Close();
}
The complete code of "Contexto" is here: http://codepaste.net/mrnoo5
My question is: Do i really need to use ITransaction transaction = session.BeginTransaction() and transaction.Commit();?
I'm asking this because I've tested run the web app without those two lines, and I've successfully inserted new records.
If possible, can someone explain me too the purpose of Itransaction and the method Commit?
Thanks
This is the proper generic NHibernate usage pattern:
using (ISession session = sessionFactory.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
//Do the work here
transaction.Commit();
}
All of that is required to ensure everything works as expected (unless you use additional infrastructure)
Closing the session or doing anything with the transaction besides committing is redundant, as the Dispose methods of the session and the transaction takes care of cleanup, including rollback if there are errors.
It's important to note than doing anything with the session after an exception can result in unexpected behavior, which is another reason to limit explicit exception handling inside the block.
ITransaction transaction = session.BeginTransaction() is not necessary as you found out by testing.
But imagine this, what happens when your session throws an exception? how would you get back your changes to the database?
The following is quote from Nhib...
A typical transaction should use the following idiom:
ISession sess = factory.OpenSession();
ITransaction tx;
try
{
tx = sess.BeginTransaction(); //do some work ...
tx.Commit();
}
catch (Exception e)
{
if (tx != null)
tx.Rollback();
throw;
}
finally
{
sess.Close();
}
If the ISession throws an exception, the transaction must be rolled back
and the session discarded. The internal state of the ISession might not be
consistent with the database after the exception occurs.
NHibernate.ISessionFactory
Well you call Commit() on your transaction it saves all the changes to the database.
Do i really need to use ITransaction transaction = session.BeginTransaction() and transaction.Commit();?
yes it is considered good practice using transactions for everything even simple reads.
tested run the web app without those two lines, and I've successfully inserted new records.
that because the session will commit the changes when it is disposed at the end of the using statement.
anyway this is how i would right the save :
using (ISession session = Contexto.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
try
{
session.Save(noticia);
transaction.Commit();
}
catch(HibernateException)
{
transaction.Rollback();
throw;
}
}
}
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.