Skip to content

NH-4011 - transaction scope failures #627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Sep 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
7adde5b
NH-2176 - Add test case for consecutive transaction scopes.
griffitd Apr 15, 2010
f0bd141
NH-4011 - Fix current system transaction handling.
fredericDelaporte Jul 3, 2017
c0767c3
NH-4018 - Option for enlisting in system transaction.
fredericDelaporte May 28, 2017
683b342
NH-4011 - Moving DtcFailures to system transaction tests, and complet…
fredericDelaporte Jun 28, 2017
a61dbc7
NH-4011 - MSDTC only tests, for grounding NHibernate enlistment imple…
fredericDelaporte Jun 28, 2017
25e5c48
NH-4011 - Adjusting transaction handling according to MSDTC tests fin…
fredericDelaporte Jun 29, 2017
7f478eb
NH-2928 - Option for not requiring connection availability from commi…
fredericDelaporte Jun 29, 2017
cdb969d
NH-3023 - Test case for pool corruption after deadlock
fredericDelaporte Jul 2, 2017
8e0e467
NH-3023 - Cease closing connection from transaction scope completion …
fredericDelaporte Jul 2, 2017
6141f79
Cleaning up transaction patterns in documentation, some more links, t…
fredericDelaporte Jul 3, 2017
fa5b0a2
NH-4011 - Documenting transaction handling changes.
fredericDelaporte Jul 3, 2017
6ae4a1b
NH-4011 - Allowing easier customization of transaction factories for …
fredericDelaporte Jul 3, 2017
913a848
One more try in test teardown for avoiding hiding exception...
fredericDelaporte Jul 4, 2017
90a2bfe
NH-4011 - Move from transaction completion event to second phase noti…
fredericDelaporte Jul 18, 2017
75d2c9f
NH-4018 - Testing manual join in distributed cases too.
fredericDelaporte Jul 18, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 78 additions & 70 deletions doc/reference/modules/batch.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
look like this:
</para>

<programlisting><![CDATA[ISession session = sessionFactory.OpenSession();
ITransaction tx = session.BeginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.Save(customer);
}
tx.Commit();
session.Close();]]></programlisting>
<programlisting><![CDATA[using (ISession session = sessionFactory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
for (int i = 0; i < 100000; i++)
{
Customer customer = new Customer(.....);
session.Save(customer);
}
tx.Commit();
}]]></programlisting>

<para>
This would fall over with an <literal>OutOfMemoryException</literal> somewhere
Expand Down Expand Up @@ -56,21 +58,24 @@ session.Close();]]></programlisting>
the first-level cache.
</para>

<programlisting><![CDATA[ISession session = sessionFactory.openSession();
ITransaction tx = session.BeginTransaction();

for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.Save(customer);
if ( i % 20 == 0 ) { //20, same as the ADO batch size
//flush a batch of inserts and release memory:
session.Flush();
session.Clear();
<programlisting><![CDATA[using (ISession session = sessionFactory.openSession())
using (ITransaction tx = session.BeginTransaction())
{
for (int i = 0; i < 100000; i++)
{
Customer customer = new Customer(.....);
session.Save(customer);
// 20, same as the ADO batch size
if (i % 20 == 0)
{
// flush a batch of inserts and release memory:
session.Flush();
session.Clear();
}
}
}

tx.Commit();
session.Close();]]></programlisting>

tx.Commit();
}]]></programlisting>

</sect1>

Expand All @@ -90,20 +95,21 @@ session.Close();]]></programlisting>
to data aliasing effects, due to the lack of a first-level cache. A stateless
session is a lower-level abstraction, much closer to the underlying ADO.
</para>

<programlisting><![CDATA[IStatelessSession session = sessionFactory.OpenStatelessSession();
ITransaction tx = session.BeginTransaction();

var customers = session.GetNamedQuery("GetCustomers")
.Enumerable<Customer>();
while ( customers.MoveNext() ) {
Customer customer = customers.Current;
customer.updateStuff(...);
session.Update(customer);
}

tx.Commit();
session.Close();]]></programlisting>

<programlisting><![CDATA[using (IStatelessSession session = sessionFactory.OpenStatelessSession())
using (ITransaction tx = session.BeginTransaction())
{
var customers = session.GetNamedQuery("GetCustomers")
.Enumerable<Customer>();
while (customers.MoveNext())
{
Customer customer = customers.Current;
customer.updateStuff(...);
session.Update(customer);
}

tx.Commit();
}]]></programlisting>

<para>
Note that in this code example, the <literal>Customer</literal> instances returned
Expand Down Expand Up @@ -176,17 +182,17 @@ session.Close();]]></programlisting>
<literal>IQuery.ExecuteUpdate()</literal> method:
</para>

<programlisting><![CDATA[ISession session = sessionFactory.OpenSession();
ITransaction tx = session.BeginTransaction();

string hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or string hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.CreateQuery( hqlUpdate )
.SetString( "newName", newName )
.SetString( "oldName", oldName )
<programlisting><![CDATA[using (ISession session = sessionFactory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
string hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or string hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.CreateQuery(hqlUpdate)
.SetString("newName", newName)
.SetString("oldName", oldName)
.ExecuteUpdate();
tx.Commit();
session.Close();]]></programlisting>
tx.Commit();
}]]></programlisting>

<para>
HQL <literal>UPDATE</literal> statements, by default do not effect the
Expand All @@ -198,15 +204,17 @@ session.Close();]]></programlisting>
This is achieved by adding the <literal>VERSIONED</literal> keyword after the <literal>UPDATE</literal>
keyword.
</para>
<programlisting><![CDATA[ISession session = sessionFactory.OpenSession();
ITransaction tx = session.BeginTransaction();
string hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.CreateQuery( hqlUpdate )
.SetString( "newName", newName )
.SetString( "oldName", oldName )

<programlisting><![CDATA[using (ISession session = sessionFactory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
string hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.CreateQuery(hqlUpdate)
.SetString("newName", newName)
.SetString("oldName", oldName)
.ExecuteUpdate();
tx.Commit();
session.Close();]]></programlisting>
tx.Commit();
}]]></programlisting>

<para>
Note that custom version types (<literal>NHibernate.Usertype.IUserVersionType</literal>)
Expand All @@ -218,16 +226,16 @@ session.Close();]]></programlisting>
method:
</para>

<programlisting><![CDATA[ISession session = sessionFactory.OpenSession();
ITransaction tx = session.BeginTransaction();

String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.CreateQuery( hqlDelete )
.SetString( "oldName", oldName )
<programlisting><![CDATA[using (ISession session = sessionFactory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
string hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.CreateQuery(hqlDelete)
.SetString("oldName", oldName)
.ExecuteUpdate();
tx.Commit();
session.Close();]]></programlisting>
tx.Commit();
}]]></programlisting>

<para>
The <literal>int</literal> value returned by the <literal>IQuery.ExecuteUpdate()</literal>
Expand Down Expand Up @@ -302,14 +310,14 @@ session.Close();]]></programlisting>
An example HQL <literal>INSERT</literal> statement execution:
</para>

<programlisting><![CDATA[ISession session = sessionFactory.OpenSession();
ITransaction tx = session.BeginTransaction();

var hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.CreateQuery( hqlInsert )
<programlisting><![CDATA[using (ISession session = sessionFactory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
var hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.CreateQuery(hqlInsert)
.ExecuteUpdate();
tx.Commit();
session.Close();]]></programlisting>
tx.Commit();
}]]></programlisting>

</sect1>

Expand Down
14 changes: 8 additions & 6 deletions doc/reference/modules/collection_mapping.xml
Original file line number Diff line number Diff line change
Expand Up @@ -622,12 +622,14 @@ HashedSet hs = (HashedSet) cat.Kittens; //Error!]]></programlisting>
However, if the application tries something like this:
</para>

<programlisting><![CDATA[s = sessions.OpenSession();
ITransaction tx = sessions.BeginTransaction();
User u = (User) s.Find("from User u where u.Name=?", userName, NHibernateUtil.String)[0];
IDictionary permissions = u.Permissions;
tx.Commit();
s.Close();
<programlisting><![CDATA[IDictionary permissions;
using (s = sessions.OpenSession())
using (ITransaction tx = sessions.BeginTransaction())
{
User u = (User) s.Find("from User u where u.Name=?", userName, NHibernateUtil.String)[0];
permissions = u.Permissions;
tx.Commit();
}

int accessLevel = (int) permissions["accounts"]; // Error!]]></programlisting>

Expand Down
2 changes: 1 addition & 1 deletion doc/reference/modules/configuration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ ISession session = sessions.OpenSession(conn);
</entry>
<entry>
The class name of a custom <literal>ITransactionFactory</literal> implementation,
defaults to the built-in <literal>AdoNetWithDistributedTransactionFactory</literal>.
defaults to the built-in <literal>AdoNetWithSystemTransactionFactory</literal>.
<para>
<emphasis role="strong">eg.</emphasis>
<literal>classname.of.TransactionFactory, assembly</literal>
Expand Down
21 changes: 12 additions & 9 deletions doc/reference/modules/manipulating_data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -780,15 +780,18 @@ sess.Lock(pk, LockMode.Upgrade);]]></programlisting>
</para>

<programlisting><![CDATA[sess = sf.OpenSession();
ITransaction tx = sess.BeginTransaction();
sess.FlushMode = FlushMode.Commit; //allow queries to return stale state
Cat izi = (Cat) sess.Load(typeof(Cat), id);
izi.Name = "iznizi";
// execute some queries....
sess.Find("from Cat as cat left outer join cat.Kittens kitten");
//change to izi is not flushed!
...
tx.Commit(); //flush occurs]]></programlisting>
using (ITransaction tx = sess.BeginTransaction())
{
// allow queries to return stale state
sess.FlushMode = FlushMode.Commit;
Cat izi = (Cat) sess.Load(typeof(Cat), id);
izi.Name = "iznizi";
// execute some queries....
sess.Find("from Cat as cat left outer join cat.Kittens kitten");
// change to izi is not flushed!
...
tx.Commit(); // flush occurs
}]]></programlisting>

</sect1>

Expand Down
61 changes: 32 additions & 29 deletions doc/reference/modules/performance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,33 +132,25 @@
fetching for single-valued associations. These defaults make sense for almost
all associations in almost all applications.
</para>

<!--
<para>
<emphasis>Note:</emphasis> if you set
<literal>hibernate.default_batch_fetch_size</literal>, NHibernate will use the
batch fetch optimization for lazy fetching (this optimization may also be enabled
at a more granular level).
</para>
-->


<para>
However, lazy fetching poses one problem that you must be aware of. Access to a
lazy association outside of the context of an open NHibernate session will result
in an exception. For example:
</para>

<programlisting><![CDATA[s = sessions.OpenSession();
Transaction tx = s.BeginTransaction();

User u = (User) s.CreateQuery("from User u where u.Name=:userName")
.SetString("userName", userName).UniqueResult();
IDictionary permissions = u.Permissions;

tx.Commit();
s.Close();
<programlisting><![CDATA[IDictionary permissions;
using (var s = sessions.OpenSession())
using (Transaction tx = s.BeginTransaction())
{
User u = (User)s.CreateQuery("from User u where u.Name=:userName")
.SetString("userName", userName).UniqueResult();
IDictionary permissions = u.Permissions;

tx.Commit();
}

int accessLevel = (int) permissions["accounts"]; // Error!]]></programlisting>
int accessLevel = (int)permissions["accounts"]; // Error!]]></programlisting>

<para>
Since the <literal>permissions</literal> collection was not initialized
Expand Down Expand Up @@ -262,8 +254,9 @@ int accessLevel = (int) permissions["accounts"]; // Error!]]></programlisting>
</para>

<para>
A completely different way to avoid problems with N+1 selects is to use the
second-level cache.
A completely different way to avoid problems with N+1 selects is to use the
<link linkend="performance-cache">second-level cache</link>, or to enable
<link linkend="performance-fetching-batch">batch fetching</link>.
</para>

</sect2>
Expand Down Expand Up @@ -310,20 +303,24 @@ int accessLevel = (int) permissions["accounts"]; // Error!]]></programlisting>
instance of <literal>DomesticCat</literal>:
</para>

<programlisting><![CDATA[Cat cat = (Cat) session.Load(typeof(Cat), id); // instantiate a proxy (does not hit the db)
if ( cat.IsDomesticCat ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
<programlisting><![CDATA[// instantiate a proxy (does not hit the db)
Cat cat = (Cat) session.Load(typeof(Cat), id);
// hit the db to initialize the proxy
if ( cat.IsDomesticCat ) {
DomesticCat dc = (DomesticCat) cat; // Error!
....
}]]></programlisting>

<para>
Secondly, it is possible to break proxy <literal>==</literal>.
</para>

<programlisting><![CDATA[Cat cat = (Cat) session.Load(typeof(Cat), id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.Load(typeof(DomesticCat), id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false]]></programlisting>
<programlisting><![CDATA[// instantiate a Cat proxy
Cat cat = (Cat)session.Load(typeof(Cat), id);
DomesticCat dc =
// acquire new DomesticCat proxy!
(DomesticCat)session.Load(typeof(DomesticCat), id);
Console.WriteLine(cat == dc); // false]]></programlisting>

<para>
However, the situation is not quite as bad as it looks. Even though we now have two references
Expand Down Expand Up @@ -561,6 +558,12 @@ ICat fritz = (ICat) iter.Current;]]></programlisting>
<emphasis>materialized path</emphasis> might be a better option for read-mostly trees.)
</para>

<para>
<emphasis>Note:</emphasis> if you set <literal>default_batch_fetch_size</literal>
in configuration, NHibernate will configure the batch fetch optimization for lazy fetching
globally. Batch sizes specified at more granular level take precedence.
</para>

</sect2>

<sect2 id="performance-fetching-subselect">
Expand Down
15 changes: 8 additions & 7 deletions doc/reference/modules/query_criteria.xml
Original file line number Diff line number Diff line change
Expand Up @@ -294,15 +294,16 @@ IList results = session.CreateCriteria(typeof(Cat))
The <literal>DetachedCriteria</literal> class lets you create a query outside the scope
of a session, and then later execute it using some arbitrary <literal>ISession</literal>.
</para>

<programlisting><![CDATA[DetachedCriteria query = DetachedCriteria.For(typeof(Cat))
.Add( Expression.Eq("sex", 'F') );

ISession session = ....;
ITransaction txn = session.BeginTransaction();
IList results = query.GetExecutableCriteria(session).SetMaxResults(100).List();
txn.Commit();
session.Close();]]></programlisting>

using (ISession session = ....)
using (ITransaction txn = session.BeginTransaction())
{
IList results = query.GetExecutableCriteria(session).SetMaxResults(100).List();
txn.Commit();
}]]></programlisting>

<para>
A <literal>DetachedCriteria</literal> may also be used to express a sub-query. ICriterion
Expand Down
Loading