I am getting an error that begins as follows below. Is this something to do with the way my cascades are set up? Where is the proper place to start investigating? Right now I have a Role which hasMany RoleDuty. RoleDuty belongsTo Role. RoleDuty only has one field, duty, which is a nullable String. Role has a mapping of all-delete-orphan for the RoleDuties list.
06.12.2010 10:02:17 *ERROR* AssertionFailure: an assertion failure occured (this may indicate a bug in Hibernate, but is
more likely due to unsafe use of the session) (AssertionFailure.java, line 47)
org.hibernate.AssertionFailure: null id in RoleDuty entry (don't flush the Session after an exception occurs)
at org.hibernate.event.def.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:78)
at org.hibernate.event.def.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:187)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:14
3)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListen
er.java:99)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:49)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:390)
at org.codehaus.groovy.grails.orm.hibernate.support.GrailsOpenSessionInViewInterceptor.flushIfNecessary(GrailsOp
enSessionInViewInterceptor.java:116)
at org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor.postHandle(OpenSessionInViewIntercept
or.java:181)
at org.codehaus.groovy.grails.orm.hibernate.support.GrailsOpenSessionInViewInterceptor.postHandle(GrailsOpenSess
ionInViewInterceptor.java:66)
at org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter.postHandle(WebRequestHandlerInter
ceptorAdapter.java:61)
at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:303)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
Class definitions:
class RoleDuty
{
static belongsTo = [role:Role]
static constraints = {
duty(nullable: true)
}
static mapping = {
duty type:"text"
}
String duty;
boolean _deleted
static transients = ['_deleted']
#XmlValue
public String getDuty(){
return duty;
}
String toString()
{
return duty;
}
}
public class Role implements Comparable
{
static belongsTo = [project:Project]
static hasMany = [ roleDuties:RoleDuty]
static mapping = {
roleDuties cascade:"all-delete-orphan", lazy:false
}
List<RoleDuty> roleDuties = new ArrayList<RoleDuty>()
}
Hibernate's error message "don't flush the Session after an exception occurs" suggests that a Hibernate-related exception occurred on this Session but your code caught the exception and continued to use the Session. Hibernate's API states that if Hibernate throws an exception, you must roll back the transaction/close the Session and do whatever you want in a new Session.
It's hard to tell exactly what the issue is from just this stack trace and debugging Hibernate errors involves trial and error. I suggest you debug your code while looking for an exception prior to this one and figure out how to avoid it.
Related
Conditionally I want to switch the circuit breaker switch off/on by setting spring.cloud.circuitbreaker.resilience4j.enabled=false. My logic should stay intact from circuit-breaker logic.
I tried using the below demo example to extend to my requirements, I am trying to bind circuit breaker call on target method based on circuit breaker flag spring.cloud.circuitbreaker.resilience4j.enabled=true in application.property, true and false case. There could be a simpler way to achieve this, help me if any other solution than what I tried.
Example:
spring cloud circuit-breaker-resiliency4j example
Tried calling happy path - Work fine when there is no exception [response comes within 3 seconds as time limiter set to 3seconds in bean creation]
application.properties:
spring.cloud.circuitbreaker.resilience4j.enabled=true
spring.cloud.config.enabled=false
spring.cloud.config.import-check.enabled=false
spring.main.allow-bean-definition-overriding=true
Controller:
#GetMapping("/delay/{seconds}")
public Map delay(#PathVariable int seconds) {
return mockService.delay(seconds);
}
MockService:
#ApplyCircuitBreaker
public Map delay(int seconds) {
return rest.getForObject("https://httpbin.org/delay/" + seconds, Map.class);
}
Config class:
#Configuration
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled"}, matchIfMissing = true)
public class ResiliencyConfig {
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build())
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.build());
}
}
ApplyCircuitBreaker - Custom annotation to Apply circuit breaker only for required methods:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ApplyCircuitBreaker {
}
AOP: CircuitBreakerAroundAspect:
#Aspect
#Component
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
"spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)
public class CircuitBreakerAroundAspect {
#Autowired
CircuitBreakerFactory circuitBreakerFactory;
#Around("#annotation(com.ravibeli.circuitbreaker.aspects.ApplyCircuitBreaker)")
public Object aroundAdvice(final ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Arguments passed to method are: {}", Arrays.toString(joinPoint.getArgs()));
AtomicReference<Map<String, String>> fallback = new AtomicReference<>();
Object proceed = circuitBreakerFactory.create(joinPoint.getSignature().toString())
.run(() -> {
try {
log.info("Inside CircuitBreaker logic in Aspect");
return joinPoint.proceed();
} catch (Throwable t) {
log.error(t.getMessage());
}
return null;
}, Throwable::getMessage);
log.info("Result from method is: {}", proceed);
return proceed;
}
}
My requirement:
circuitBreakerFactory.create(joinPoint.getSignature().toString()) .run(() -> ....) at this line, when target method throws exception, controll should go to fallback mechanism call. Since joinPoint.proceed() throws exception, it is forcing to handle exception - So I am doing wrong here, need suggestion to fix this to solve the requirement.
Error log:
{
"timestamp": "2021-07-10T01:33:10.558+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')\r\n\tat com.ravibeli.circuitbreaker.service.MockService$$EnhancerBySpringCGLIB$$3e293bd0.delay(<generated>)\r\n\tat com.ravibeli.circuitbreaker.controllers.DemoController.delay(DemoController.java:53)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:655)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:228)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1723)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:834)\r\n",
"message": "class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')",
"path": "/delay/3"
}
You seem to be asking a couple different questions here.
The title seems to be asking why the aspect is still present when
spring.cloud.circuitbreaker.resilience4j.enabled=false
The problem is with your conditional
#ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
"spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)
It's simply requiring the property be present, it's not checking what it's set to. You need to set havingValue=true as well.
That said, I would strongly suggest not making your own pointcut for circuit breakers. Use the annotations provided by Resiliancy4j and just specify the fallback method there. I would expect that to clear up any other issues you're having with fallbacks.
#Bulkhead(name = 'myService', fallbackMethod = "myFallback")
#CircuitBreaker(name = 'myService', fallbackMethod = "myFallback")
#RateLimiter(name = 'myService', fallbackMethod = "myFallback")
#TimeLimiter(name = 'myService', fallbackMethod = "myFallback")
For enabling the circuit breaker dynamically you can use Profiles or Externalized Configuration (preferred approach would be to use Profiles and you can google more about them)
As far as your aspect's code goes, it looks and runs fine for me. Link to Code. It would be better if you could share the link to the code-base so that the issue can be investigated a bit further. Nevertheless, it seems a minor issue.
Thanks, guys for your comments, got the simple idea to fix this.
I resolved it with a custom factory implementation to make enable/disable feature working.
My GitHub example code: spring-cloud-resiliency4j
I use nHibernate for ORM and Ninject for IoC.
I create nHibernate sessions per some custom scope (which you can assume is per request).
I begin the transaction onActivation.
I commit the transaction onDeactivation.
The problem is that if an exception happens during the request I want to rollback the transaction rather than committing it. Any idea how to detect (in a clean way, most probably using Ninject Context) that an exception has happened?
Note: I am not concerned about the exceptions that can happen on commit which I can catch in the following code and role back easily.
protected void BindWithSessionWrapper<T>(Func<IContext, T> creationFunc) where T : ISessionWrapper
{
Bind<T>().ToMethod(creationFunc)
.InScope(x => new NinjectCustomScope()) // work in progress !!!
.OnActivation(t => t.Session.BeginTransaction(IsolationLevel.ReadCommitted))
.OnDeactivation((c, t) =>
{
t.Session.Transaction.Commit();
t.Session.Dispose();
});
}
Update:
I followed the suggestion by #BatteryBackupUnit.
So I added the following to the Error EventHandler:
Error += (s, e) =>
{
HttpContext.Current.Items["ErrorRaised"] = true;
};
And I modified the OnDeactivation to look like this:
OnDeactivation(t =>
{
if ((bool?)HttpContext.Current.Items["ErrorRaised"] == true)
t.Session.Transaction.Rollback();
else
t.Session.Transaction.Commit();
t.Session.Dispose();
});
It works fine, but that would be better if Ninject would take care of this by setting a flag in the Context if an exception happened :)
How about implementing an IHTTPModule and subscribing to the Error event?
Like described here
In the Error event handler, use System.Web.Mvc.DependencyResolver.Current.GetService(typeof (ISession)) to retrieve the current session and rollback the transaction.
Note, however, that in case the request did not use a session, this will create one, which is quite superfluous.
You might do something like checking whether a transaction was started and only then rolling it back. But you'd still create a session unnecessarily.
You could further improve that by using the Error event handler to set a flag on HttpContext.Current.Items, like
HttpContext.Current.Items["RollbackTransaction"] = true;
and then use it in the OnDeactivation of the session like:
.OnDeactivation((c, t) =>
{
if(HttpContext.Current.Items.Contains("RollbackTransaction"])
{
t.Session.Transaction.Rollback();
}
else
{
t.Session.Transaction.Commit();
}
t.Session.Dispose();
});
Please note that HttpContext is thread local, that means when you switch threads it may be null or -worst case - it might even be another HttpContext.
Please also note that i was unable to try it out so it may not work. Feedback appreciated.
Passing the state through HttpContext is not acceptable to me for 2 reasons.
HttpContext issue: https://stackoverflow.com/a/12219078/656430)
Passing state seems like passing a global state (https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil)
After a lot of trial and error, I think this should be one solution:
Assuming we are working on WebApi project, having rollback transaction for all actions once hit exception, with Ninject:
install Ninject.Extension.Factory (https://www.nuget.org/packages/Ninject.Extensions.Factory/), this is very important step as to inject ISession in request scope into filters.
use the following configuration for binding ISessionFactory and ISession (I made use of this example: Need a simple example of using nhibernate + unit of work + repository pattern + service layer + ninject), plus ISessionInRequestScopeFactory
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
Bind<ISession>()
.ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession())
.InRequestScope(); // notice that we don't need to call `BeginTransaction` at this moment
Bind<ISessionInRequestScopeFactory>().ToFactory(); // you don't need to make your implementation, the Ninject.Extension.Factory extension will help you so.
the code for interface ISessionInRequestScopeFactory:
public interface ISessionInRequestScopeFactory
{
ISession CreateSessionInRequestScope(); // return ISession in the request scope
}
Make use of ninject filter injection to add Transaction behaviour to every action (https://github.com/ninject/Ninject.Web.WebApi/wiki/Dependency-injection-for-filters):
Kernel.BindHttpFilter<ApiTransactionFilter>(System.Web.Http.Filters.FilterScope.Action)
.WhenControllerHas<ApiTransactionAttribute>();
add [ApiTransaction] attribute into controller:
[ApiTransaction]
public class YourApiController{ /* ... */}
So we are now binding the ApiTransactionFilter into YourApiController which are having [ApiTransaction] Attribute
Inside ApiTransactionFilter, you should extends AbstractActionFilter and inject the factory ISessionInRequestScopeFactory for getting the correct request scope session:
public class ApiTransactionFilter : AbstractActionFilter{
private readonly ISessionInRequestScopeFactory factory;
public ApiTransactionFilter(ISessionInRequestScopeFactory factory){
this.factory = factory;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
ISession session = factory.CreateSessionInRequestScope(); // get the request scope session through factory
session.BeginTransaction(); // session can begin transaction here ...
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ISession session = factory.CreateSessionInRequestScope(); // get the request scope session through factory
if (actionExecutedContext.Exception == null) // NO EXCEPTION!
{
session.Transaction.Commit();// session commit here ... may be you like to have try catch here
}
else
{
session.Transaction.Rollback(); // session rollback here ...
}
base.OnActionExecuted(actionExecutedContext);
}
}
I have an ErrorHandler (DefaultErrorHandler) into which I have provided an onRedelivery ref. By default the ErrorHandler retries unlimited times. However, if a certain condition exists (currently determined by the onRedelivery ref) I would like to exit the redelivery loop and execute a different route.
My initial thought was to have the onRedelivery ref throw and exception and have an appropriate onException direct this to the appropriate route. However, I find that the RedeliveryErrorHandler catches this exception and keeps looping.
I have also found that I can set the Exchange.REDELIVERY_EXHAUSTED to true which will exit the redelivery loop but will not direct me to my recovery route.
Any suggestions?
Edit
So I have found that if I add the original exception type to the onException of the exception type in the RouteBuilder in which I have my ErrorHandler, and if I set the Exchange.REDELIVERY_EXHAUSTED to true, the original exception will be thrown to the RouteBuilder scope and caught by the onException. However, I would really prefer to throw and catch a new exception type so that the handling is explicit to this case.
Answer
So Peter's suggestion of using retryWhile was great in that it allows me to programmatically determine when to stop retrying. It's too. It only went half way thought. The second part was to send the failing exchange to a new / different route for error handling. This is accomplished by using the DeadLetterChannel instead of the DefaultErrorHandler.
Use retryWhile in combination with deadLetterChannel:
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() {
errorHandler(deadLetterChannel("direct:special")
.retryWhile(method(new MyPredicate())));
from("direct:start")
.log("Starting...")
.throwException(new Exception("dummy"));
from("direct:special")
.log("...Special");
}
}
public class MyPredicate implements Predicate {
#Override
public boolean matches(final Exchange exchange) {
AtomicInteger counter = exchange.getProperty("myCounter");
if (counter == null) {
counter = new AtomicInteger(0);
exchange.setProperty("myCounter", counter);
}
int count = counter.incrementAndGet();
LOG.info("Count = {}", count);
return count < 3; // or whatever condition is suitable
}
}
This prints:
INFO Starting...
INFO Count = 1
INFO Count = 2
INFO Count = 3
INFO ...Special
I've got a WCF operation conceptually like this:
[OperationBehavior(TransactionScopeRequired = true)]
public void Foo()
{
try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); }
catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
}
If something goes wrong (say, foreign key constraint violaion) in executing the DAL code, control passes to the catch block as I'd expect. But when the method returns, it seems the transaction scope has sniffed out that the transaction failed, and it decides it better throw an exception to make sure to notify the caller about it.
In turn my client application does not get the receipt I want to return, but rather an exception:
System.ServiceModel.FaultException:
The transaction under which this method call was executing was asynchronously aborted.
What is wrong with my design?
I could have the service not catch anything, but this has it's own problems as the service needs to use exception shielding and the client (a batch tool internal to the system) needs to log the error information. The service logs errors too, but not in the same way and to the same place as the batch.
Be careful here! If you set TransactionAutoComplete=true then if the service returns normally the transaction will be committed. Only if there is an unhandled exception (which for the most part you don't have because you are catching exceptions and returning a receipt message) will the transaction be rolled back. See http://msdn.microsoft.com/en-us/library/system.servicemodel.operationbehaviorattribute.transactionautocomplete.aspx.
Think about a scenario where you successfully executed some DAL calls but some other exception (e.g. NullReferenceException) occurs. Now the transaction will be committed when the method completes because no unhandled exception has occurred but the client receives an ErrorReceipt.
For your scenario, I think you will have to manage the transactions yourself. For example:
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo()
{
// Create TransactionScope using the ambient transaction
using (var scope = new TransactionScope() )
{
try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); scope.Complete(); }
catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
}
}
You could eliminate boilerplate code by creating a helper method that wraps it all within the transaction or you could use policy injection/interception/aspects to manage transactions.
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo()
{
return ProcessWithTransaction(() =>
{
DAL.Foo();
return Receipt.CreateSuccessReceipt();
}
, (ex) =>
{
return Receipt.CreateErrorReceipt(ex);
}
);
}
T ProcessWithTransaction<T>(Func<T> processor, Func<Exception, T> exceptionHandler)
{
using (var scope = new TransactionScope())
{
try
{
T returnValue = processor();
scope.Complete();
return returnValue;
}
catch (Exception e)
{
return exceptionHandler(e);
}
}
}
You mention that you need to use exception shielding. If you are not averse to throwing faults when an error occurs then you could use Enterprise Library Exception Handling Block's exception shielding which also lets you log the information on the way out (if you desire).
If you decided to go that route your code would look something like this:
[OperationBehavior(TransactionScopeRequired = true)]
public void Foo()
{
// Resolve the default ExceptionManager object from the container.
ExceptionManager exManager = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();
exManager.Process(() =>
{
DAL.Foo();
return Receipt.CreateSuccessReceipt();
},
"ExceptionShielding");
}
Enterprise Library (via configuration) would then catch any exceptions and replace them with a new FaultException that is returned to the client.
[OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
Presumably because the transaction is now rolled back as soon as the error occurs, rather than asynchronously when the scope goes out of scope :D, this behaves like I expected things to behave originally, and I can leave my design as it is.
(I had already written up the question when trying this occured to me. Hopefully posting it Q&A style will be more helpful than not posting the question at all.)
First, some background: I'm new to ASP.NET MVC 2 and NHibernate. I'm starting my first application and I want to use NHibernate, because I come from JSP + Struts 1 + Hibernate web applications.
No one seems to be talking about this, so I guess it must be pretty obvious. Still I scratch my head because I can't find a solution that accomplish the following things:
1) I want to use the "session per request" strategy. So, everytime a user makes a request, he gets an Nhibernate session, starts a transaction, and when the request is over, the transaction commits, and the NHibernate session closes (and returns to the pool if there is one). This guarantees that my transactions are atomic.
2) When a database exception occurs (PK violation, unique violation, whatever) I want to capture that exception, rollback my transaction and give the user a explicit message: if it was PK violation, then that message, and the same with all integrity errors.
So, what is my problem? I come from the Java World, where I used a Filter to open the session, start the transaction, process the request, then commit the transaction and close the session. This works, except when an DB exception occurs, and by the time you are in the filter there's no way to change the destination page because the response is already committed.
So the user sees the success page when in reality the transaction was rollbacked. To avoid this I have to write a lot of data integrity checks in Java in order to prevent all integrity exceptions, because I could not handle them correctly. This is bad because I'm doing the work instead of leaving it to the database (or maybe I'm wrong and I always have to write all this data integrity code in my app?).
So I've found the IHttpModule interface which I'm guessing is pretty much the same concept as a javax.servlet.Filter (correct me if I'm wrong), so I'm guessing I could have the same problem again.
Where should I put my commits in order to make sure that my transactions are atomic, and when they throw exceptions I can capture them and change my destination page and give the user a comprehensive message?
So far the only possible solution I've come up with is to keep my IHttpModule to start and close the transaction, and put the commit calls in the last line of my controllers methods, thus being able to capture exceptions there and then return an appropiate view with the message. Now I would have to copy those commit and exception handling lines into all my controller methods that require commits. And there is the separation of concerns issue, that my controllers have to know about DB, which I don't like at all.
Is there a better way?
If you're using ASP.NET MVC, you could use an ActionFilter to achieve the same effect.
Something like (this is hacked together from difference pieces of my architecture):
public class TransactionalAttribute : ActionFilterAttribute, IAuthorizationFilter, IExceptionFilter
{
ITransaction transaction = NullTransaction.Instance;
public IsolationLevel IsolationLevel { get; set; }
public TransactionalAttribute()
{
IsolationLevel = IsolationLevel.ReadCommitted;
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
try
{
transaction.Commit();
transaction = NullTransaction.Instance;
}
catch (Exception exception)
{
Log.For(this).FatalFormat("Problem trying to commit transaction {0}", exception);
}
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (transaction == NullTransaction.Instance) transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result != null) return;
transaction.Commit();
transaction = NullTransaction.Instance;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel);
}
public void OnException(ExceptionContext filterContext)
{
try
{
transaction.Rollback();
transaction = NullTransaction.Instance;
}
catch (Exception exception)
{
Log.For(this).FatalFormat("Problem trying to rollback transaction {0}", exception);
}
}
private class NullTransaction : ITransaction
{
public static ITransaction Instance { get { return Singleton<NullTransaction>.Instance; } }
public void Dispose()
{
}
public void Commit()
{
}
public void Rollback()
{
}
}
}
Well after thinking about it and discussed it with coworkers I've come up with a solution that meets almost all my requirements.
I implemented the solution with my Java projects and it worked great. I'll just pust the idea so everybody can use it within any framework.
The solution consist in putting the commit call in the last line of the controller method, inside a try-catch block. If a constraint exception occurs you can get the name of the violated constraint. With the name you can tell the user exactly what went wrong. I used a properties file to store the message to show to the user wich constraint was violated. The keys of the properties file are the constraints names and the values are the constraint violation messages.
Yo can refactor the commit-handle_exception-find_constraint_message to a method, that's what I did.
For now it solves my problem of writing code to check database integrity and I believe it's pretty elegant with the constraint violation messages in a properties file. Now, I still don't like the idea that my controllers need to call the commit, but that's way better than writing integrity checks that the database already does.
I will continue to use a filter just like David Kemp said, just that the filter will only open the (n)hibernate session and the transaction, and then, at the end of the request, close the session.
Comments are more than welcome. Thanks.