SPRNET-1102 - Improve Northwind NHibernate example
@@ -558,9 +558,9 @@ on exception name ArithmeticException return 12</programlisting>
|
||||
<literal>SimpleLoggingAdvice</literal> programatically.</para>
|
||||
|
||||
<programlisting language="myxml"><object name="loggingAdvice" type="Spring.Aspects.Logging.SimpleLoggingAdvice, Spring.Aop">
|
||||
<property name="logUniqueIdentifier" value="true"/>
|
||||
<property name="logExecutionTime" value="true"/>
|
||||
<property name="logMethodArguments" value="true"/>
|
||||
<property name="LogUniqueIdentifier" value="true"/>
|
||||
<property name="LogExecutionTime" value="true"/>
|
||||
<property name="LogMethodArguments" value="true"/>
|
||||
<property name="LogReturnValue" value="true"/>
|
||||
|
||||
<property name="Separator" value=";"/>
|
||||
@@ -571,10 +571,11 @@ on exception name ArithmeticException return 12</programlisting>
|
||||
<property name="UseDynamicLogger" value="true"/>
|
||||
</object></programlisting>
|
||||
|
||||
<para>The default values for LogUniqueIdentifier, LogExecutionTime,
|
||||
LogMethodArguments and LogReturnValue are false. The default separator
|
||||
<para>The default values for <literal>LogUniqueIdentifier</literal>,
|
||||
<literal>LogExecutionTime</literal>, <literal>LogMethodArguments</literal>
|
||||
and <literal>LogReturnValue</literal> are false. The default separator
|
||||
value is ", " and the default log level is Common.Logging's
|
||||
LogLevel.Trace.</para>
|
||||
<literal>LogLevel.Trace</literal>.</para>
|
||||
|
||||
<para>You can set the name of the logger with the property
|
||||
<property>LoggerName</property>, for example "DataAccessLayer" for a
|
||||
@@ -586,7 +587,12 @@ on exception name ArithmeticException return 12</programlisting>
|
||||
being called, the target type. Since often this is a proxy class with a
|
||||
relatively meaningless name, the property
|
||||
<property>HideProxyTypeNames</property> can be set to true to show the
|
||||
true target type and not the proxy type.</para>
|
||||
true target type and not the proxy type. The
|
||||
<literal>UseDynamicLogger</literal> property determines which
|
||||
<literal>ILog</literal> instance should be used to write log messages for
|
||||
a particular method invocation: a dynamic one for the Type getting called,
|
||||
or a static one for the Type of the trace interceptor. The default is to
|
||||
use a static logger.</para>
|
||||
|
||||
<para>To further extend the functionality of the
|
||||
<literal>SimpleLoggingAdvice</literal> you can subclass
|
||||
@@ -642,11 +648,6 @@ Exiting Bark, 5d2bad47-62cd-435b-8de7-91f12b7f433e, 30453.125 ms, return=4</prog
|
||||
method. If you would like to have an alternate implementation, say to view
|
||||
some values in an array, override the method string
|
||||
GetMethodArgumentAsString(IMethodInvocation invocation).</para>
|
||||
|
||||
<para>The Spring 1.2 release will have an additional logging advice
|
||||
implementation that leverages the Spring Expression Language to further
|
||||
customize the content of the logging messages via simple configuration
|
||||
using similar syntax to the retry and exception handling advice.</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 xml:id="retry-aspect">
|
||||
|
||||
BIN
doc/reference/src/images/nh-quickstart-customer-listing.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
doc/reference/src/images/nh-quickstart-default-screen.png
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
doc/reference/src/images/nh-quickstart-domain.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
doc/reference/src/images/nh-quickstart-edit-customer.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
doc/reference/src/images/nh-quickstart-orders.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
doc/reference/src/images/nh-quickstart-process.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
doc/reference/src/images/nh-quickstart-solution-explorer.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
@@ -28,31 +28,685 @@
|
||||
<section>
|
||||
<title>Introduction</title>
|
||||
|
||||
<para>
|
||||
This QuickStart application uses the all too familiar Northwind
|
||||
database and uses NHibernate browse and edit customers. It It is a very
|
||||
simple application that directly uses the DAO layer in some cases and also
|
||||
has a simple service layer that simulated a fullillment process. See the
|
||||
integration tests as well for insight into how it works. The application
|
||||
uses Spring's declarative transaction management features,
|
||||
standard NHibernate API, and Open Session In View module.
|
||||
<para>This QuickStart application uses the all too familiar Northwind
|
||||
database and uses NHibernate browse and edit customers. It It is a very
|
||||
simple application that directly uses the DAO layer in many use-cases, as
|
||||
it is doing nothing more than table maintenance, but there is also a
|
||||
simple service layer that handles a fullillment process. The application
|
||||
uses Spring's declarative transaction management features, standard
|
||||
NHibernate API, and Open Session In View module. See <xref
|
||||
linkend="orm" /> for information on those features.<note>
|
||||
Even though data access is performed through NHibernate API all Spring.NET provided functionality is still present when using the standard NHibernate API, as Spring transaction managment is integrated into NHibernate extension points and exception translation is provided by AOP advice.
|
||||
</note></para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Getting Started</title>
|
||||
|
||||
<para>The QuickStart application is located in the directory directory
|
||||
<literal><spring-install-dir>\examples\Spring\Spring.Data.NHibernate.Northwind</literal>.
|
||||
Load the application using the VS.NET 2008 solution file
|
||||
<literal>Spring.Northwind.2008.sln</literal>. The application uses the
|
||||
SqlLite database so no additional configuration is needed. To run the
|
||||
application set the Web application as the project that starts and
|
||||
Default.aspx as the start page.</para>
|
||||
|
||||
<para>The application has several layers with each layer represented as
|
||||
one or more VS.NET projects. </para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="images/nh-quickstart-solution-explorer.png"></imagedata>
|
||||
</imageobject>
|
||||
|
||||
<caption>
|
||||
<para>Solution Explorer for NHibernate QuickStart Application</para>
|
||||
</caption>
|
||||
</mediaobject>
|
||||
|
||||
<para>The data access layer consists of two projects, Spring.Northwind.Dao
|
||||
and Spring.Northwind.Dao.NHibernate. The former contains only the DAO
|
||||
(data access object) interfaces and the latter the NHibernate
|
||||
implementation of those interfaces. The project Spring.Northwind.Service
|
||||
contains a simple service that calls into multiple DAO objects in order to
|
||||
satisfy a fulliment process. The Web project is a ASP.NET web application
|
||||
and the Spring.Northwind.IntegrationTests project contains integration
|
||||
tests for the DAO and Service layers.</para>
|
||||
|
||||
<para>When you run the application you will see </para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="images/nh-quickstart-default-screen.png"></imagedata>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>Following the link to the customer listing pages bring up the
|
||||
following screen </para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="images/nh-quickstart-customer-listing.png"></imagedata>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>You can click on the Name of the customer or the Orders link to view
|
||||
that customers orders. Selecting "BOTTM"'s orders brings us to the next
|
||||
page</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="images/nh-quickstart-orders.png"></imagedata>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>Notice that the order 11045 has yet to be shipped. If you select
|
||||
'Process Orders' this will call the Fulliment Service and the order will
|
||||
be processed and shipped.p</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="images/nh-quickstart-process.png"></imagedata>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>You can then go back to the customer list. If you select the name
|
||||
Elizabeth Lincoln, then you can edit the customer details. </para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="images/nh-quickstart-edit-customer.png"></imagedata>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Implementation </title>
|
||||
|
||||
<para>This section discussed the Spring implementation details for each
|
||||
layer.</para>
|
||||
|
||||
<section>
|
||||
<title>The Data Access Layer</title>
|
||||
|
||||
<para>The interface IDao is a generic DAO layer that provides basic
|
||||
retrieval methods. They are located in the Spring.Northwind.Dao
|
||||
project.</para>
|
||||
|
||||
<programlisting language="csharp"> public interface IDao<TEntity, TId>
|
||||
{
|
||||
TEntity Get(TId id);
|
||||
|
||||
IList<TEntity> GetAll();
|
||||
|
||||
}</programlisting>
|
||||
|
||||
<para>The ISupportsSave and ISupportsDeleteDao interfaces provide the
|
||||
rest of the CRUD functionality.</para>
|
||||
|
||||
<programlisting language="csharp"> public interface ISupportsSave<TEntity, TId>
|
||||
{
|
||||
TId Save(TEntity entity);
|
||||
|
||||
void Update(TEntity entity);
|
||||
}
|
||||
|
||||
|
||||
public interface ISupportsDeleteDao<TEntity>
|
||||
{
|
||||
void Delete(TEntity entity);
|
||||
}</programlisting>
|
||||
|
||||
<para>The ICustomerDao interface combines these to manage the
|
||||
persistence of customer objects. </para>
|
||||
|
||||
<programlisting language="csharp"> public interface ICustomerDao : IDao<Customer, string>, ISupportsDeleteDao<Customer>, ISupportsSave<Customer, string>
|
||||
{
|
||||
}</programlisting>
|
||||
|
||||
<para>Similar interfaces are defined to manage
|
||||
<classname>Order</classname> and <classname>Products</classname> in
|
||||
<classname>IOrderDao</classname> and <classname>IProductDao</classname>
|
||||
respectfully.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>The domain objects</title>
|
||||
|
||||
<para>The POCO domain objects, Customer, Order, OrderDetail and Product
|
||||
are defined in the Spring.Northwind.Domain namespace within the
|
||||
Spring.Northwind.Dao project. </para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata fileref="images/nh-quickstart-domain.png"></imagedata>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>NHibernate based DAO implementation</title>
|
||||
|
||||
<para>The NHibernate based DAO implemenation uses the standard
|
||||
NHibernate APIs, retrieving the current session from the SessionFactory
|
||||
and using the session to retrieve or store objects to the database. An
|
||||
abstract base class HibernateDao is used to capture the common
|
||||
ISessionFactory property, provide a convenience property to access the
|
||||
current session, and define a GetAll Method.p</para>
|
||||
|
||||
<programlisting language="csharp"> public abstract class HibernateDao
|
||||
{
|
||||
private ISessionFactory sessionFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Session factory for sub-classes.
|
||||
/// </summary>
|
||||
public ISessionFactory SessionFactory
|
||||
{
|
||||
protected get { return sessionFactory; }
|
||||
set { sessionFactory = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's the current active session. Will retrieve session as managed by the
|
||||
/// Open Session In View module if enabled.
|
||||
/// </summary>
|
||||
protected ISession CurrentSession
|
||||
{
|
||||
get { return sessionFactory.GetCurrentSession(); }
|
||||
}
|
||||
|
||||
protected IList<T> GetAll<T>() where T : class
|
||||
{
|
||||
ICriteria criteria = CurrentSession.CreateCriteria<T>();
|
||||
return criteria.List<T>();
|
||||
}
|
||||
}</programlisting>
|
||||
|
||||
<para>The implementation of <classname>ICustomerDao</classname> is shown
|
||||
below</para>
|
||||
|
||||
<programlisting language="csharp"> [Repository]
|
||||
public class HibernateCustomerDao : HibernateDao, ICustomerDao
|
||||
{
|
||||
|
||||
// Note that the transaction demaraction is here only for the case when
|
||||
// the DAO object is being used directly, i.e. not as part of a service layer
|
||||
// call. This would be commonly only when creating an application that contains
|
||||
// no business logic and is essentially a table maintenance application.
|
||||
// These applications are affectionaly known as 'CRUD' applications, the acronym
|
||||
// refering to Create, Retrieve, Update, And Delete and the only operations
|
||||
// performed by the application.
|
||||
|
||||
// If called from a transactional service layer, typically with the transaction
|
||||
// propagation setting set to REQUIRED, then any DAO operations will use the
|
||||
// same settings as started from the transactional layer.
|
||||
|
||||
[Transaction(ReadOnly = true)]
|
||||
public Customer Get(string customerId)
|
||||
{
|
||||
return CurrentSession.Get<Customer>(customerId);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = true)]
|
||||
public IList<Customer> GetAll()
|
||||
{
|
||||
return GetAll<Customer>();
|
||||
}
|
||||
|
||||
|
||||
[Transaction(ReadOnly = false)]
|
||||
public string Save(Customer customer)
|
||||
{
|
||||
return (string) CurrentSession.Save(customer);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = false)]
|
||||
public void Update(Customer customer)
|
||||
{
|
||||
CurrentSession.SaveOrUpdate(customer);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = false)]
|
||||
public void Delete(Customer customer)
|
||||
{
|
||||
CurrentSession.Delete(customer);
|
||||
}
|
||||
}</programlisting>
|
||||
|
||||
<note>
|
||||
Even though data access is performed through NHibernate API all Spring.NET provided functionality is
|
||||
still present when using the standard NHibernate API, as Spring transaction managment
|
||||
is integrated into NHibernate extension points and exception translation is
|
||||
provided by AOP advice.
|
||||
See the section titled Implementing DAOs based on plain Hibernate 1.2/2.0 API"
|
||||
in the hibernate orm section of the reference docs for more information.
|
||||
<para>As mentioned in the code comments above, as this application has
|
||||
a distinctly CRUD based component, Spring's Transaction attribute is
|
||||
used to ensure that that method exeuctes as a unit of work. Often in
|
||||
more sophisticated applications even the basic of CRUD are handled
|
||||
through a service layer so as to enforce security, auditing, alterting
|
||||
or enforce business rules.</para>
|
||||
</note>
|
||||
To run the application make the Web application the project that
|
||||
starts and set Default.aspx as the start page.
|
||||
|
||||
<para>The <classname>Repository</classname> attribute is used to
|
||||
indicate that this class plays the role of a Repository or a Data Access
|
||||
Object. The term repository comes from modeling terminology popularized
|
||||
by Eric Evan's book Domain Driven Design (DDD). Those familiar with DDD
|
||||
will note that this implementation is very simply and does not expose
|
||||
higher level persistence functionality to the application, for example
|
||||
<literal>FindCustomersWithOpenOrders</literal>. How well the role of
|
||||
Repository applies to this implementation is not relevant, and we will
|
||||
often refer to Repository and DAO intechangable when describing the data
|
||||
access layer. What <emphasis>is</emphasis> relevant is that the
|
||||
<classname>Repository</classname> attribute serves as a marker, a place
|
||||
in the code that can be used to identify methods whose invocation should
|
||||
be intercepted so that additional behavior can be added. In
|
||||
Aspect-Oriented Programming terminology, the Repository attribute
|
||||
represents a pointcut. The behavior that we would like to add to this
|
||||
DAO implementation exception translation. Exception translation from the
|
||||
data access layer to a service layer is important as it shields the
|
||||
service layer from the implementation details of the data access layer.
|
||||
A NHibernate based DAO will throw different exceptions and a ADO.NET
|
||||
based implementation and so on. Spring provides a rich technology
|
||||
neutral data-access exception hierarchy. See <xref linkend="dao" />.
|
||||
</para>
|
||||
|
||||
<para>Instead of adding exception translation code in each data access
|
||||
method, AOP offers a simple solution. Using Spring's
|
||||
<classname>IObjectPostProcessor</classname> extension point, each DAO
|
||||
object that is managed by Spring will be automatically wrapped up in a
|
||||
proxy that adds the exception translation behavior. This is done by
|
||||
adding the following object definition to the Spring application
|
||||
context.</para>
|
||||
|
||||
<programlisting><objects>
|
||||
|
||||
<!-- configure session factory -->
|
||||
|
||||
<!-- Exception translation object post processor -->
|
||||
<object type="Spring.Dao.Attributes.PersistenceExceptionTranslationPostProcessor, Spring.Data"/>
|
||||
|
||||
<!-- Configure transaction management strategy -->
|
||||
<!-- DAO objects go here -->
|
||||
|
||||
|
||||
</objects></programlisting>
|
||||
|
||||
<para>The Spring managed DAO object definitions are shown below,
|
||||
referring to a SessionFactory that is created via Spring's
|
||||
LocalSessionFactoryObject. See the file Dao.xml for more details.</para>
|
||||
|
||||
<programlisting language="myxml"><objects xmlns="http://www.springframework.net"
|
||||
xmlns:db="http://www.springframework.net/database">
|
||||
|
||||
<!-- Referenced by main application context configuration file -->
|
||||
<description>
|
||||
The Northwind object definitions for the Data Access Objects.
|
||||
</description>
|
||||
|
||||
<!-- Database Configuration -->
|
||||
<db:provider id="DbProvider"
|
||||
provider="SQLite-1.0.65"
|
||||
connectionString="Data Source=|DataDirectory|Northwind.db;Version=3;FailIfMissing=True;"/>
|
||||
|
||||
<!-- NHibernate SessionFactory configuration -->
|
||||
<object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21">
|
||||
<property name="DbProvider" ref="DbProvider"/>
|
||||
<property name="MappingAssemblies">
|
||||
<list>
|
||||
<value>Spring.Northwind.Dao.NHibernate</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="HibernateProperties">
|
||||
<dictionary>
|
||||
<entry key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
|
||||
<entry key="dialect" value="NHibernate.Dialect.SQLiteDialect"/>
|
||||
<entry key="connection.driver_class" value="NHibernate.Driver.SQLite20Driver"/>
|
||||
</dictionary>
|
||||
</property>
|
||||
|
||||
<!-- provides integation with Spring's declarative transaction management features -->
|
||||
<property name="ExposeTransactionAwareSessionFactory" value="true" />
|
||||
|
||||
</object>
|
||||
|
||||
<!-- Transaction Management Strategy - local database transactions -->
|
||||
<object id="transactionManager"
|
||||
type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21">
|
||||
<property name="DbProvider" ref="DbProvider"/>
|
||||
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
|
||||
</object>
|
||||
|
||||
<!-- Exception translation object post processor -->
|
||||
<object type="Spring.Dao.Attributes.PersistenceExceptionTranslationPostProcessor, Spring.Data"/>
|
||||
|
||||
<!-- Data Access Objects -->
|
||||
<object id="CustomerDao" type="Spring.Northwind.Dao.NHibernate.HibernateCustomerDao, Spring.Northwind.Dao.NHibernate">
|
||||
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
|
||||
</object>
|
||||
|
||||
<object id="OrderDao" type="Spring.Northwind.Dao.NHibernate.HibernateOrderDao, Spring.Northwind.Dao.NHibernate">
|
||||
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
|
||||
</object>
|
||||
|
||||
|
||||
</objects></programlisting>
|
||||
|
||||
<para><note>
|
||||
<para>It is not required that you use Spring's
|
||||
<classname>[Repository]</classname> attribute. You can specify an
|
||||
attribute type to the
|
||||
<classname>PersistenceExceptionTranslationPostProcessor</classname>
|
||||
via the property <classname>RepositoryAttributeType</classname> to
|
||||
avoid coupling your DAO implementation to Spring. </para>
|
||||
</note></para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>The Service layer</title>
|
||||
|
||||
<para>The service layer is located in the Spring.Northwind.Services
|
||||
project. It defines a single service for the fulliment process</para>
|
||||
|
||||
<programlisting language="csharp"> public interface IFulfillmentService
|
||||
{
|
||||
void ProcessCustomer(string customerId);
|
||||
}</programlisting>
|
||||
|
||||
<para>The implementatiion class is shown below</para>
|
||||
|
||||
<programlisting language="csharp"> public class FulfillmentService : IFulfillmentService
|
||||
{
|
||||
|
||||
|
||||
private IProductDao productDao;
|
||||
|
||||
private ICustomerDao customerDao;
|
||||
|
||||
private IOrderDao orderDao;
|
||||
|
||||
private IShippingService shippingService;
|
||||
|
||||
|
||||
// Properties for the preceding fields omitted for brevity
|
||||
|
||||
[Transaction]
|
||||
public void ProcessCustomer(string customerId)
|
||||
{
|
||||
//Find all orders for customer
|
||||
Customer customer = CustomerDao.Get(customerId);
|
||||
|
||||
foreach (Order order in customer.Orders)
|
||||
{
|
||||
if (order.ShippedDate.HasValue)
|
||||
{
|
||||
log.Warn("Order with " + order.Id + " has already been shipped, skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
//Validate Order
|
||||
Validate(order);
|
||||
log.Info("Order " + order.Id + " validated, proceeding with shipping..");
|
||||
|
||||
//Ship with external shipping service
|
||||
ShippingService.ShipOrder(order);
|
||||
|
||||
//Update shipping date
|
||||
order.ShippedDate = DateTime.Now;
|
||||
|
||||
//Update shipment date
|
||||
OrderDao.Update(order);
|
||||
|
||||
//Other operations...Decrease product quantity... etc
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void Validate(Order order)
|
||||
{
|
||||
//no-op - throw exception on error.
|
||||
}
|
||||
}
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
<para>What is important to note about this method is that it uses two
|
||||
DAO objects, CustomerDao and OrderDao as well as an additional
|
||||
collaborating service, IShippingService. The fact that all of the
|
||||
collaborating objects are interfaced based means that we can write a
|
||||
unit test for the business functionality of the ProcessCustomer method.
|
||||
Also, the use of the [Transaction] attribute will enable this business
|
||||
processing to proceed as a single unit-of-work. Spring's declarative
|
||||
transaction management features make it very easy to mix and match
|
||||
different DAO objects with a service method without having to worry
|
||||
about propagating the transaction/connection or hibernate session to
|
||||
each DAO object.</para>
|
||||
|
||||
<para>The Fullfillment service layer is configured to refer to its
|
||||
collaborating objects as shown below in the configuration file
|
||||
Services.xml</para>
|
||||
|
||||
<programlisting language="myxml"> <!-- Property placeholder configurer for database settings -->
|
||||
<object id="FulfillmentService" type="Spring.Northwind.Service.FulfillmentService, Spring.Northwind.Service">
|
||||
<property name="CustomerDao" ref="CustomerDao"/>
|
||||
<property name="OrderDao" ref="OrderDao"/>
|
||||
<property name="ShippingService" ref="ShippingService"/>
|
||||
</object>
|
||||
|
||||
<object id="ShippingService" type="Spring.Northwind.Service.FedExShippingService, Spring.Northwind.Service"/>
|
||||
|
||||
<tx:attribute-driven/></programlisting>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Integration testing</title>
|
||||
|
||||
<para>Integraiton testing in addition to unit testing can be done before
|
||||
integrating the service and data access layer into the Web application -
|
||||
where automated testing is much more difficult. While coding to
|
||||
interfaces and using an IoC container help enable unit testing, unit
|
||||
tests should not have any Spring dependency. Integration tests however
|
||||
greatly benefit from being able to access the objects that Spring is
|
||||
managing in production. This way the gap between what you testi in QA
|
||||
and what runs is minimized, ideally with only environment specific
|
||||
settings being different. In addition to easily obtaining, say a
|
||||
transactionally aware service object from the Spring IoC container,
|
||||
Spring intergration testing support classes allow you to implicitly
|
||||
start a transaction at the start of test method and rollback at the end.
|
||||
The isolation guaranteed by the database means that multiple developers
|
||||
can run integration tests for their data access layers simultaneously
|
||||
and the rollback ensures that the changes made are not persisted. While
|
||||
in the test method, you have a consistent view of the data and can
|
||||
therefore exercise all the methods of your DAO object.</para>
|
||||
|
||||
<para>The project Spring.Northwind.IntegrationTests shows how this
|
||||
works. As a convenience, an abstract base class is created that in turn
|
||||
inherits from Spring's integration testing class
|
||||
<classname>AbstractTransactionalDbProviderSpringContextTests</classname></para>
|
||||
|
||||
<programlisting language="csharp"> [TestFixture]
|
||||
public abstract class AbstractDaoIntegrationTests : AbstractTransactionalDbProviderSpringContextTests
|
||||
{
|
||||
|
||||
protected override string[] ConfigLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return new string[]
|
||||
{
|
||||
"assembly://Spring.Northwind.Dao.NHibernate/Spring.Northwind.Dao/Dao.xml",
|
||||
"assembly://Spring.Northwind.Service/Spring.Northwind.Service/Services.xml"
|
||||
};
|
||||
}
|
||||
}
|
||||
}</programlisting>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
To follow this NHibernate QuickStart load the solution file
|
||||
found in the directory
|
||||
<literal><spring-install-dir>\examples\Spring\Spring.Data.NHibernate.Northwind</literal>
|
||||
</para>
|
||||
<para>This unit test is NUnit based but there is similar support
|
||||
available for Microsoft MSTest framework.</para>
|
||||
</note>
|
||||
</para>
|
||||
|
||||
<para>The exact same object definition files that will be used in the
|
||||
production application are loaded for the integration test. To test the
|
||||
data access layer, you inherit from AbstractDaoIntegrationTests and
|
||||
expose public properties for each DAO implementation you want to test.
|
||||
Within each test method exercise the API of the DAO. This also tests the
|
||||
NHibernate mappings.</para>
|
||||
|
||||
<programlisting> [TestFixture]
|
||||
public class NorthwindIntegrationTests : AbstractDaoIntegrationTests
|
||||
{
|
||||
private ICustomerDao customerDao;
|
||||
private IOrderDao orderDao;
|
||||
|
||||
private ISessionFactory sessionFactory;
|
||||
|
||||
// These properties will be injected based on type
|
||||
public ICustomerDao CustomerDao
|
||||
{
|
||||
set { customerDao = value; }
|
||||
}
|
||||
|
||||
public IOrderDao OrderDao
|
||||
{
|
||||
set { orderDao = value; }
|
||||
}
|
||||
|
||||
public ISessionFactory SessionFactory
|
||||
{
|
||||
set { sessionFactory = value; }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CustomerDaoTests()
|
||||
{
|
||||
Assert.AreEqual(91, customerDao.GetAll().Count);
|
||||
|
||||
Customer c = new Customer();
|
||||
c.Id = "MPOLL";
|
||||
c.CompanyName = "Interface21";
|
||||
customerDao.Save(c);
|
||||
c = customerDao.Get("MPOLL");
|
||||
Assert.AreEqual(c.Id, "MPOLL");
|
||||
Assert.AreEqual(c.CompanyName, "Interface21");
|
||||
|
||||
//Without flushing, nothing changes in the database:
|
||||
int customerCount = (int)AdoTemplate.ExecuteScalar(CommandType.Text, "select count(*) from Customers");
|
||||
Assert.AreEqual(91, customerCount);
|
||||
|
||||
//Flush the session to execute sql in the db.
|
||||
SessionFactoryUtils.GetSession(sessionFactory, true).Flush();
|
||||
|
||||
//Now changes are visible outside the session but within the same database transaction
|
||||
customerCount = (int)AdoTemplate.ExecuteScalar(CommandType.Text, "select count(*) from Customers");
|
||||
Assert.AreEqual(92, customerCount);
|
||||
|
||||
Assert.AreEqual(92, customerDao.GetAll().Count);
|
||||
|
||||
c.CompanyName = "SpringSource";
|
||||
|
||||
customerDao.Update(c);
|
||||
|
||||
c = customerDao.Get("MPOLL");
|
||||
Assert.AreEqual(c.Id, "MPOLL");
|
||||
Assert.AreEqual(c.CompanyName, "SpringSource");
|
||||
|
||||
customerDao.Delete(c);
|
||||
|
||||
|
||||
SessionFactoryUtils.GetSession(sessionFactory, true).Flush();
|
||||
customerCount = (int)AdoTemplate.ExecuteScalar(CommandType.Text, "select count(*) from Customers");
|
||||
Assert.AreEqual(92, customerCount);
|
||||
|
||||
try
|
||||
{
|
||||
c = customerDao.Get("MPOLL");
|
||||
Assert.Fail("Should have thrown HibernateObjectRetrievalFailureException when finding customer with Id = MPOLL");
|
||||
}
|
||||
catch (HibernateObjectRetrievalFailureException e)
|
||||
{
|
||||
Assert.AreEqual("Customer", e.PersistentClassName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProductDaoTests()
|
||||
{
|
||||
// ommited for brevity
|
||||
}
|
||||
}</programlisting>
|
||||
|
||||
<para>This test uses AdoTemplate to access the database using the
|
||||
standard ADO.NET APIs. It is done to demonstrate that the common
|
||||
configuration of NHibernate is for it not to flush to the database until
|
||||
a commit occurs. If we did not explicitly flush, then no SQL would be
|
||||
sent down to the database and some potential errors would go undetected.
|
||||
Since the test method will rollback the transaction, we don't have to
|
||||
worry about 'dirtying' the database and changing its state.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Web Application</title>
|
||||
|
||||
<para>The Web application uses Dependency Injection on the .aspx pages
|
||||
so that they can access the servcies of the middle tier, for example the
|
||||
FullfillmentService, or in the case of simple table maintenance, the DAO
|
||||
objects directly.</para>
|
||||
|
||||
<para>For example the FullfillmentResult.aspx code behind is shown
|
||||
below</para>
|
||||
|
||||
<programlisting>public partial class FullfillmentResult : Page
|
||||
{
|
||||
private IFulfillmentService fulfillmentService;
|
||||
private ICustomerEditController customerEditController;
|
||||
|
||||
public IFulfillmentService FulfillmentService
|
||||
{
|
||||
set { fulfillmentService = value; }
|
||||
}
|
||||
|
||||
public ICustomerEditController CustomerEditController
|
||||
{
|
||||
set { customerEditController = value; }
|
||||
}
|
||||
|
||||
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
/// code omitted for brevity
|
||||
|
||||
fulfillmentService.ProcessCustomer(customerEditController.CurrentCustomer.Id);
|
||||
|
||||
}
|
||||
|
||||
protected void customerOrders_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetResult("Back");
|
||||
}
|
||||
|
||||
</programlisting>
|
||||
|
||||
<para>The page is configured in Spring as shown below</para>
|
||||
|
||||
<programlisting> <object type="FulfillmentResult.aspx">
|
||||
<property name="FulfillmentService" ref="FulfillmentService" />
|
||||
<property name="CustomerEditController" ref="CustomerEditController" />
|
||||
<property name="Results">
|
||||
<dictionary>
|
||||
<entry key="Back" value="redirect:CustomerOrders.aspx" />
|
||||
</dictionary>
|
||||
</property>
|
||||
</object>
|
||||
</programlisting>
|
||||
|
||||
<para>The page is injected with a reference to the FullfillmentService
|
||||
and also another UI component. While Spring's ASP.NET framework supports
|
||||
DI for standard ASP.NET pages and user controls, you can also inherit
|
||||
from Spring's base page class to get added functionality. In this
|
||||
example the use of externalized page flow, or Result Mapping is shown.
|
||||
The Results property indicates the 'how', 'where' and 'what data' to
|
||||
bring along when moving between different web pages and associates it
|
||||
with a logical name "Back". This avoid hardcoding server side transfers
|
||||
or redirects in your code as well as other ASP.NET page references. See
|
||||
the chapter on Spring's ASP.NET Web Framework for more details.</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
<property name="ConfigSections" value="databaseSettings"/>
|
||||
</object>
|
||||
|
||||
<!-- Database and NHibernate Configuration -->
|
||||
<!-- Database Configuration -->
|
||||
<db:provider id="DbProvider"
|
||||
provider="SQLite-1.0.65"
|
||||
connectionString="Data Source=|DataDirectory|Northwind.db;Version=3;FailIfMissing=True;"/>
|
||||
|
||||
<!-- NHibernate Configuration -->
|
||||
<object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21">
|
||||
<property name="DbProvider" ref="DbProvider"/>
|
||||
<property name="MappingAssemblies">
|
||||
@@ -32,12 +33,13 @@
|
||||
</dictionary>
|
||||
</property>
|
||||
|
||||
<!-- provides integation with Spring's declarative transaction management features -->
|
||||
<property name="ExposeTransactionAwareSessionFactory" value="true" />
|
||||
|
||||
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Transaction Management Strategy - local database transactions -->
|
||||
<object id="transactionManager"
|
||||
type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21">
|
||||
|
||||
@@ -46,6 +48,9 @@
|
||||
|
||||
</object>
|
||||
|
||||
<!-- Exception translation object post processor -->
|
||||
<object type="Spring.Dao.Attributes.PersistenceExceptionTranslationPostProcessor, Spring.Data"/>
|
||||
|
||||
<!-- Data Access Objects -->
|
||||
<object id="CustomerDao" type="Spring.Northwind.Dao.NHibernate.HibernateCustomerDao, Spring.Northwind.Dao.NHibernate">
|
||||
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Spring.Northwind.Dao.NHibernate
|
||||
[Transaction(ReadOnly = true)]
|
||||
public Customer Get(string customerId)
|
||||
{
|
||||
return Session.Get<Customer>(customerId);
|
||||
return CurrentSession.Get<Customer>(customerId);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = true)]
|
||||
@@ -59,22 +59,22 @@ namespace Spring.Northwind.Dao.NHibernate
|
||||
}
|
||||
|
||||
|
||||
[Transaction(ReadOnly = false)]
|
||||
[Transaction]
|
||||
public string Save(Customer customer)
|
||||
{
|
||||
return (string) Session.Save(customer);
|
||||
return (string) CurrentSession.Save(customer);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = false)]
|
||||
[Transaction]
|
||||
public void Update(Customer customer)
|
||||
{
|
||||
Session.SaveOrUpdate(customer);
|
||||
CurrentSession.SaveOrUpdate(customer);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = false)]
|
||||
[Transaction]
|
||||
public void Delete(Customer customer)
|
||||
{
|
||||
Session.Delete(customer);
|
||||
CurrentSession.Delete(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,17 +20,17 @@ namespace Spring.Northwind.Dao.NHibernate
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's the current active session. Uses
|
||||
/// Open Session In View in the background.
|
||||
/// Get's the current active session. Will retrieve session as managed by the
|
||||
/// Open Session In View module if enabled.
|
||||
/// </summary>
|
||||
protected ISession Session
|
||||
protected ISession CurrentSession
|
||||
{
|
||||
get { return sessionFactory.GetCurrentSession(); }
|
||||
}
|
||||
|
||||
protected IList<T> GetAll<T>() where T : class
|
||||
{
|
||||
ICriteria criteria = Session.CreateCriteria<T>();
|
||||
ICriteria criteria = CurrentSession.CreateCriteria<T>();
|
||||
return criteria.List<T>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Spring.Northwind.Dao.NHibernate
|
||||
[Transaction(ReadOnly = true)]
|
||||
public Order Get(int orderId)
|
||||
{
|
||||
return Session.Get<Order>(orderId);
|
||||
return CurrentSession.Get<Order>(orderId);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = true)]
|
||||
@@ -51,19 +51,19 @@ namespace Spring.Northwind.Dao.NHibernate
|
||||
[Transaction]
|
||||
public int Save(Order order)
|
||||
{
|
||||
return (int) Session.Save(order);
|
||||
return (int) CurrentSession.Save(order);
|
||||
}
|
||||
|
||||
[Transaction]
|
||||
public void Update(Order order)
|
||||
{
|
||||
Session.SaveOrUpdate(order);
|
||||
CurrentSession.SaveOrUpdate(order);
|
||||
}
|
||||
|
||||
[Transaction]
|
||||
public void Delete(Order order)
|
||||
{
|
||||
Session.Delete(order);
|
||||
CurrentSession.Delete(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Spring.Northwind.Dao.NHibernate
|
||||
[Transaction(ReadOnly = true)]
|
||||
public Product Get(int productId)
|
||||
{
|
||||
return Session.Get<Product>(productId);
|
||||
return CurrentSession.Get<Product>(productId);
|
||||
}
|
||||
|
||||
[Transaction(ReadOnly = true)]
|
||||
@@ -50,19 +50,19 @@ namespace Spring.Northwind.Dao.NHibernate
|
||||
[Transaction]
|
||||
public int Save(Product product)
|
||||
{
|
||||
return (int) Session.Save(product);
|
||||
return (int) CurrentSession.Save(product);
|
||||
}
|
||||
|
||||
[Transaction]
|
||||
public void Update(Product product)
|
||||
{
|
||||
Session.SaveOrUpdate(product);
|
||||
CurrentSession.SaveOrUpdate(product);
|
||||
}
|
||||
|
||||
[Transaction]
|
||||
public void Delete(Product product)
|
||||
{
|
||||
Session.Delete(product);
|
||||
CurrentSession.Delete(product);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ClassDiagram MajorVersion="1" MinorVersion="1">
|
||||
<Class Name="Spring.Northwind.Domain.Order">
|
||||
<Position X="2.75" Y="0.5" Width="1.75" />
|
||||
<TypeIdentifier>
|
||||
<HashCode>CEECIABBAAAAEAAgIBEAICQDAEAEAgBAAAAEAAgABiA=</HashCode>
|
||||
<FileName>Domain\Order.cs</FileName>
|
||||
</TypeIdentifier>
|
||||
</Class>
|
||||
<Class Name="Spring.Northwind.Domain.OrderDetail">
|
||||
<Position X="4.75" Y="0.5" Width="1.5" />
|
||||
<TypeIdentifier>
|
||||
<HashCode>MAEAABABAAAAAAAAgAAAAAAAAAAAAICAAgAAgCIAAAA=</HashCode>
|
||||
<FileName>Domain\OrderDetail.cs</FileName>
|
||||
</TypeIdentifier>
|
||||
</Class>
|
||||
<Class Name="Spring.Northwind.Domain.Product">
|
||||
<Position X="4.75" Y="4" Width="1.5" />
|
||||
<TypeIdentifier>
|
||||
<HashCode>AAAACAQAAAAAAAAAAABAAAAAABAEAABAAABAgAAAABA=</HashCode>
|
||||
<FileName>Domain\Product.cs</FileName>
|
||||
</TypeIdentifier>
|
||||
</Class>
|
||||
<Class Name="Spring.Northwind.Domain.Customer">
|
||||
<Position X="0.5" Y="0.5" Width="2" />
|
||||
<TypeIdentifier>
|
||||
<HashCode>AAGCQAABgAAAACAIIAAgCCAAAAIAAAAiAAACIAAAAkA=</HashCode>
|
||||
<FileName>Domain\Customer.cs</FileName>
|
||||
</TypeIdentifier>
|
||||
</Class>
|
||||
<Font Name="Tahoma" Size="8.4" />
|
||||
</ClassDiagram>
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.50727</ProductVersion>
|
||||
<ProductVersion>9.0.21022</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{7F45EEA2-50AC-44E2-85A6-2FFB02E38C44}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
@@ -48,6 +48,9 @@
|
||||
<Compile Include="Domain\Product.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Domain\DomainClassDiagram.cd" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Spring.Northwind.Service
|
||||
|
||||
|
||||
|
||||
[Transaction(ReadOnly=false)]
|
||||
[Transaction]
|
||||
public void ProcessCustomer(string customerId)
|
||||
{
|
||||
//Find all orders for customer
|
||||
@@ -117,7 +117,7 @@ namespace Spring.Northwind.Service
|
||||
private void Validate(Order order)
|
||||
{
|
||||
|
||||
//TODO throw exception on error.
|
||||
//no-op - throw exception on error.
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
</object>
|
||||
|
||||
|
||||
|
||||
<tx:attribute-driven/>
|
||||
|
||||
|
||||
|
||||
@@ -32,10 +32,9 @@ using Spring.Northwind.Domain;
|
||||
namespace Spring.Northwind.IntegrationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains tests for
|
||||
/// This class contains tests for the DAO implementations
|
||||
/// </summary>
|
||||
/// <author>Mark Pollack</author>
|
||||
/// <version>$Id:$</version>
|
||||
[TestFixture]
|
||||
public class NorthwindIntegrationTests : AbstractDaoIntegrationTests
|
||||
{
|
||||
@@ -44,6 +43,8 @@ namespace Spring.Northwind.IntegrationTests
|
||||
|
||||
private ISessionFactory sessionFactory;
|
||||
|
||||
|
||||
// These properties will be injected based on type
|
||||
public ICustomerDao CustomerDao
|
||||
{
|
||||
set { customerDao = value; }
|
||||
|
||||