db4o Developer Community

db4o open source object database, native to Java and .NET
Welcome to db4o Developer Community Sign in | Join
in Search
More Search Options

Could someone examine my test? Servlet-based example

Last post 01-25-2006, 05:16 PM by zambizzi. 10 replies.
Sort Posts: Previous Next
  •  01-24-2006, 08:08 PM 22050

    Could someone examine my test? Servlet-based example

    I have created a simple test in the last few minutes to see how db4o works in a Servlet environment w/ multiple concurrent users.  This example works and I am successfully release the server & client "connections" after each request in a servlet.  However, I'm sure someone might look at this and tell me it is not 'best practices' or that I'm doing something sub-optimal...which is what I'm here to hear!  If not...then great...db4o is even easier than I thought.

    I wanted to simulate a very basic 3-tier design in which db4o is not called directly in the servlet but in a class somewhere in the middle-tier.

    Here are some code snippets:

    doGet in the servlet (simply listing contents of ObjectSet):

            Bridge br = new Bridge();
            ObjectSet set = br.getAllCars();
           
            while (set.hasNext())
            {
                Car car = (Car)set.next();
                response.getWriter().println(car.getMake() + "<br />");
            }

    'getAllCars()' method of  'Bridge' class being called in servlet (see above):

        public ObjectSet getAllCars()
        {
            ObjectContainer client = Db4oServer.getClient(); //custom class (see next below)
           
            ObjectSet cars = client.get(new Car());
           
            return cars;
        }

    my custom 'Db4oServer' class:

    public class Db4oServer
    {
        private static ObjectServer server;
        private static ObjectContainer client;
       
        public Db4oServer()
        {
        }
       
        public static ObjectContainer getClient()
        {
            Db4o.configure().activationDepth(3);
            Db4o.configure().blockSize(8);
            Db4o.configure().callConstructors(true);
           
            server = Db4o.openServer("C:\\db4oTest.yap", 0);
            client = server.openClient();
            return client;
        }
       
        public static void close()
        {
            client.close();
            server.close();
        }
    }

    I call the 'close()' method of the above class in a servlet Filter like so:

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
        {
            chain.doFilter(request, response);   
            response.getWriter().print("Filter was called!");
            Db4oServer.close();
        }

    So, while this may not be the best practice, it seems to work.  The client & server is closed on each request as seen in the server output:

    [db4o 5.0.018   2006-01-24 11:47:46]
     'C:\db4oTest.yap' close request
    [db4o 5.0.018   2006-01-24 11:47:47]
     'C:\db4oTest.yap' closed


    This was inspired by my Hibernate experience where I would open the Hibernate Session in a DAO and close it in a Filter...very similar to what I did here.

    Any thoughts?  Criticisms?  Enhancements?  Hate mail?  Let me hear what you guys think.

    -v
  •  01-24-2006, 09:08 PM 22053 in reply to 22050

    Re: Could someone examine my test? Servlet-based example

    There's a short article in the db4o-knowledge-base on using db4o in servlet-environments. Search for servlet or use this link:
    http://na1.salesforce.com/sol/public/searchdetail.jsp?ps=1&retURL=%2Fsol%2Fpublic%2Fsearch.jsp%3Fsearch%3Dservlet%26orgId%3D00D300000000PmR&orgId=00D300000000PmR&id=501300000001hyr

    Some suggestions: Your code works, but it is far from being optimal. You open/close the ObjectServer with every request, which is really useless and will kill any performance.

    Open/close the ObjectServer at the containter-startup/shutdown (use a ContextListener) and store a reference to the ObjectServer in a class-variable. If you need an ObjectClient, get the reference to the ObjectServer, open up a client, do your work and close the client again. That will give you much more performance and transaction-safety.

    If you don't need to rollback any transactions at database-level, you can even open up a db4o-database with OpenFile at container-startup/shutdown and share ObjectContainer for all your requests. This way you'll get maximum performance with lowest memory requirements from db4o in a servlet-environment (only one, not many ObjectContainers with already activated objects).

    HTH, Maik

    http://db4o.blogspot.com/
  •  01-24-2006, 10:05 PM 22056 in reply to 22053

    Re: Could someone examine my test? Servlet-based example

    Yes, this approach makes more sense performance-wise.  My only complaint about this implementation is that db4o is directly tied to my presentation-layer when I would really prefer it be removed and reside on (or *as*) my data-access layer.

    I've heard all of the arguements before about how db4o isn't really a database and should just be considered an extension of my application environment...and it's OK for the presentation-layer to be aware of it but I'm not in agreement w/ that arguement.

    With this approach the server object would reside directly in the servlet context and I would need to pass it into my object in order for it to create a client, return records, and then the ContextListener would be responsible for closing the server...I'm not crazy about this design...it essentially voids the architecture that was keeping db4o and data-access logic hidden.
  •  01-24-2006, 10:23 PM 22057 in reply to 22056

    Re: Could someone examine my test? Servlet-based example

     zambizzi wrote:

    With this approach the server object would reside directly in the servlet context and I would need to pass it into my object in order for it to create a client, return records, and then the ContextListener would be responsible for closing the server...I'm not crazy about this design...it essentially voids the architecture that was keeping db4o and data-access logic hidden.


    You can still hide nearly all db4o-specifics from your presentation layer. Think of the ContextListener just as a trigger which calls an appropriate method in your persistence layer to open up or close the ObjectServer along with your application-startup/shutdown. The reference to your ObjectServer shouldn't be stored in the ServletContext, but in a static class-variable of the persistence layer. This way you can reuse your persistence layer in non-web-applications as well.

    Calling something like YourPersistenceManager.openDatabase() / closeDatabase() from the ContextListener should not worry you about the "clean" design of a layered application...;)

    BTW: Hibernate usually uses a ContextListener to initialize itself in a servlet-environment.

    Cheers, Maik

    http://db4o.blogspot.com/
  •  01-24-2006, 10:54 PM 22058 in reply to 22057

    Re: Could someone examine my test? Servlet-based example

    OK, that makes perfect sense, the light went on.  I'm not all that familiar w/ ContextListeners so this is farily new to me.

    I did exactly that and I'm getting a whole slew of errors when the application starts in Tomcat (5.5.x)

    Here's the new layout:

    Servlet doGet:

            Bridge br = new Bridge();
            ObjectSet set = br.getAllCars();
           
            while (set.hasNext())
            {
                Car car = (Car)set.next();
                response.getWriter().println(car.getMake() + "<br />");
            }


    ...which calls my 'Bridge' class, getAllCars method:

        public ObjectSet getAllCars()
        {
            ObjectContainer client = Db4oServer.getClient();
           
            ObjectSet cars = client.get(new Car());
           
            return cars;
        }


    ...which calls the custom Db4oServer class

    public class Db4oServer
    {
        private static ObjectServer server;
        private static ObjectContainer client;
       
        public static void openServer()
        {
            Db4o.configure().activationDepth(3);
            Db4o.configure().blockSize(8);
            Db4o.configure().callConstructors(true);       
           
            if (server == null)
                server = Db4o.openServer("C:\\db4oTest.yap", 0);       
        }
       
        public static void closeServer()
        {
            if (server != null)
                server.close();
        }
       
        public static ObjectContainer getClient()
        {       
            return server.openClient();
        }
       
        public static void closeClient()
        {
            if (client != null)
                client.close();
        }
    }


    I close each client in the filter like so:

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
        {
            chain.doFilter(request, response);   
            Db4oServer.closeClient();
        }


    And...last but not least, here's the ContextListener:

    public class db4oListener implements ServletContextListener
    {
        public void contextInitialized(ServletContextEvent evt)
        {
            Db4oServer.openServer();
        }

        public void contextDestroyed(ServletContextEvent evt)
        {
            Db4oServer.closeServer();
        }
    }


    I see a these errors in the log when Tomcat starts:

    java.lang.NullPointerException
            at com.db4oTest.DAL.Bridge.getAllCars(Bridge.java:39)
            at com.db4oTest.servlet.Test.doGet(Test.java:34)
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252)
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
            at com.db4oTest.servlet.db4oFilter.doFilter(db4oFilter.java:39)
            ...

    com.db4o.ext.DatabaseFileLockedException
            at com.db4o.JDK_1_4.lock(Unknown Source)
            at com.db4o.Platform4.lock(Unknown Source)
            at com.db4o.io.RandomAccessFileAdapter.<init>(Unknown Source)
            at com.db4o.io.RandomAccessFileAdapter.open(Unknown Source)
            at com.db4o.YapRandomAccessFile.open(Unknown Source)
            at com.db4o.YapRandomAccessFile.<init>(Unknown Source)
            at com.db4o.Sessions.open(Unknown Source)
            at com.db4o.Db4o.openFile(Unknown Source)
            at com.db4o.Db4o.openServer(Unknown Source)
            at com.db4oTest.data.Db4oServer.getClient(Db4oServer.java:34)
            at com.db4oTest.DAL.Bridge.getAllCars(Bridge.java:32)
            at com.db4oTest.servlet.Test.doGet(Test.java:34)
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252)
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
            at com.db4oTest.servlet.db4oFilter.doFilter(db4oFilter.java:39)
            ...

    java.lang.RuntimeException: 'Client Connection embedded client1' is closed. close() was called or open() failed.
            at com.db4o.inside.Exceptions4.throwRuntimeException(Unknown Source)
            at com.db4o.inside.Exceptions4.throwRuntimeException(Unknown Source)
            at com.db4o.YapStream.checkClosed(Unknown Source)
            at com.db4o.QResultClient.next(Unknown Source)
            at com.db4o.inside.query.ObjectSetFacade.next(Unknown Source)
            at com.db4oTest.servlet.Test.doGet(Test.java:38)
            ...

    java.lang.NullPointerException
            at com.db4oTest.data.Db4oServer.closeServer(Db4oServer.java:54)
            at com.db4oTest.servlet.db4oListener.contextDestroyed(db4oListener.java:48)
            at org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:3733)
            at org.apache.catalina.core.StandardContext.stop(StandardContext.java:4306)
            at org.apache.catalina.manager.ManagerServlet.undeploy(ManagerServlet.java:1283)
            at org.apache.catalina.manager.ManagerServlet.doGet(ManagerServlet.java:372)
            ...

    java.lang.RuntimeException: 'Client Connection embedded client1' is closed. close() was called or open() failed.
            at com.db4o.inside.Exceptions4.throwRuntimeException(Unknown Source)
            at com.db4o.inside.Exceptions4.throwRuntimeException(Unknown Source)
            at com.db4o.YapStream.checkClosed(Unknown Source)
            at com.db4o.YapStream.checkTransaction(Unknown Source)
            at com.db4o.YapStream.setExternal(Unknown Source)
            at com.db4o.YapStream.set(Unknown Source)
            at com.db4oTest.DAL.Bridge.getAllCars(Bridge.java:39)
            at com.db4oTest.servlet.Test.doGet(Test.java:34)
            ...


    ...and it goes on like this.  I'm sure it's my misunderstanding of the ContextListener...but what am I doing wrong?

    Thanks again!  Your help is *really* appreciated!!
  •  01-25-2006, 01:57 PM 22067 in reply to 22058

    Re: Could someone examine my test? Servlet-based example

     zambizzi wrote:

        public static void openServer()
        {
            Db4o.configure().activationDepth(3);
            Db4o.configure().blockSize(8);
            Db4o.configure().callConstructors(true);       
           
            if (server == null)
                server = Db4o.openServer("C:\\db4oTest.yap", 0);       
        }
       
        public static void closeServer()
        {
            if (server != null)
                server.close();
        }



    The server is closed, but not set to null, so it will never be reopened.

     zambizzi wrote:

        public static ObjectContainer getClient()
        {       
            return server.openClient();
        }
       
        public static void closeClient()
        {
            if (client != null)
                client.close();
        }
    }



    This 'singleton' client approach will get into trouble with concurrent requests.

     zambizzi wrote:

    I close each client in the filter like so:

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
        {
            chain.doFilter(request, response);   
            Db4oServer.closeClient();
        }



    I'm not too familiar with servlet filters, but this doesn't look healthy to me - IMO resources should always be released in the same scope they are acquired (the 'resource acquisition is initialization' idiom). And again, this doesn't look stable in the presence of concurrent requests.

     zambizzi wrote:

    I see a these errors in the log when Tomcat starts:


    Prior to any request? No, right?

     zambizzi wrote:

    com.db4o.ext.DatabaseFileLockedException
            [...]
            at com.db4o.Db4o.openServer(Unknown Source)
            at com.db4oTest.data.Db4oServer.getClient(Db4oServer.java:34)



    *puzzled* There's no openServer() call in getClient(), AFAICS.

     zambizzi wrote:

    ...and it goes on like this.  I'm sure it's my misunderstanding of the ContextListener...but what am I doing wrong?


    The problem is not the context listener. I can't put my finger on it right now (probably some code or configuration information is missing?), but in general I'd suggest to get rid of the singleton client approach and the filter and provide a client per request that's acquired and released 'in the same place'.

    Best regards,
    Patrick
  •  01-25-2006, 02:47 PM 22070 in reply to 22067

    Re: Could someone examine my test? Servlet-based example

     patrick_roemer wrote:
     zambizzi wrote:

    com.db4o.ext.DatabaseFileLockedException
            [...]
            at com.db4o.Db4o.openServer(Unknown Source)
            at com.db4oTest.data.Db4oServer.getClient(Db4oServer.java:34)



    *puzzled* There's no openServer() call in getClient(), AFAICS.

    Db4oServer is a class that the user wrote and it does contain an #openServer() call.

     

  •  01-25-2006, 03:15 PM 22071 in reply to 22070

    Re: Could someone examine my test? Servlet-based example

     Carl Rosenberger wrote:

     patrick_roemer wrote:
     zambizzi wrote:

    com.db4o.ext.DatabaseFileLockedException
            [...]
            at com.db4o.Db4o.openServer(Unknown Source)
            at com.db4oTest.data.Db4oServer.getClient(Db4oServer.java:34)



    *puzzled* There's no openServer() call in getClient(), AFAICS.

    Db4oServer is a class that the user wrote and it does contain an #openServer() call.



    *even more puzzled* But not in its getClient() method, right?

    Best regards,
    Patrick
  •  01-25-2006, 03:30 PM 22072 in reply to 22058

    Re: Could someone examine my test? Servlet-based example

     zambizzi wrote:

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
        {
            chain.doFilter(request, response);   
            Db4oServer.closeClient();
        }



    Ouch... lots of trouble ahead... The singleton approach will only work for the ObjectServer, but not for the client... You have only one ObjectServer (shared by all users/requests), but many clients (a fresh one for every user/request), so you can't reuse one client via a singleton approach. You'll need a fresh client for every request, otherwise you'll run into concurrent problems.

    If you want to use a servlet-filter, then require a ObjectClient before executing the chain, store it in a request-attribute (not request-parameter!) and release this client after the return of the chain. Something like this should work:

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
        {
           ObjectContainer db4oClient = Db4oServer.openClient();
            request.setAttribute("Db4oClient",
    db4oClient)
            chain.doFilter(request, response);   
            db4oClient.close();
        }


    Now you can take the Db4oClient in your web-application-controller from the request-attribute and inject it into your DAO-Layer (or whatever else) for handling a single user-request.

    Cheers, Maik

    http://db4o.blogspot.com/
  •  01-25-2006, 05:04 PM 22078 in reply to 22067

    Re: Could someone examine my test? Servlet-based example

    You were absolutely correct, removing the singleton on the ObjectServer completely cleared all of the errors that Tomcat was reporting when starting (yes, before any requests.)  So, obviously, that was causing some weirdness w/ the ContextListener...but since I'm not familiar w/ the ContextListener (until now) I'm not sure if I'm in for more problems - it wouldn't appear so, the app actually runs quite well now!

    Thanks a ton!

    -v
  •  01-25-2006, 05:16 PM 22079 in reply to 22072

    Re: Could someone examine my test? Servlet-based example

     mjablonski wrote:

    Ouch... lots of trouble ahead... The singleton approach will only work for the ObjectServer, but not for the client... You have only one ObjectServer (shared by all users/requests), but many clients (a fresh one for every user/request), so you can't reuse one client via a singleton approach. You'll need a fresh client for every request, otherwise you'll run into concurrent problems.

    If you want to use a servlet-filter, then require a ObjectClient before executing the chain, store it in a request-attribute (not request-parameter!) and release this client after the return of the chain. Something like this should work:

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
        {
           ObjectContainer db4oClient = Db4oServer.openClient();
            request.setAttribute("Db4oClient",
    db4oClient)
            chain.doFilter(request, response);   
            db4oClient.close();
        }


    Now you can take the Db4oClient in your web-application-controller from the request-attribute and inject it into your DAO-Layer (or whatever else) for handling a single user-request.

    Cheers, Maik


    Actually, I was only using a singleton on the server, not the client - so once I removed it from the server, all is good! 

    Also, you're correct...a client would be required before the filter could close the client....which in this case would always be true because the client is being opened in the DAO, utilized, and then closed in the filter.

    Anyhow, I'm very pleased!  Db4o is truly powerful and *insanely* easy to use...

    You've all been very, very helpful, thanks again!

    -v
View as RSS news feed in XML