2012年1月10日火曜日

Guice3 + MyBatis3

前回のSpring3 + MyBatis3のSpringをGuiceに置き換えてみます。

User.java、UserDao.java、UserDao.xml、UserService.javaは前回と同じ物をそのまま利用します。

Guice-MyBatisプラグインの標準のトランザクションインターセプターではreadOnlyの設定ができない(rollbackOnlyは可能)ため、 readOnlyに対応したトランザクションインターセプターを自作します。
ソースコードはGuice-MyBatisプラグインの標準のトランザクションインターセプターを参考にしています。

※PooledDataSourceProviderではなく、BasicDataSourceProviderを使用すればdefaultReadOnlyの設定が可能でした。
ただし、その場合には標準のトランザクションインターセプターではreadOnlyをfalseに設定しなおす事がないようで、更新系の処理ができません。

TransactionalMethodInterceptor.java
package com.brightgenerous.extension.ibatis;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

import javax.inject.Inject;

import com.brightgenerous.extension.logging.Log;
import com.brightgenerous.extension.logging.LogFactory;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionManager;
import org.apache.ibatis.session.TransactionIsolationLevel;

public final class TransactionalMethodInterceptor implements MethodInterceptor {

    private static final Class<?>[] CAUSE_TYPES = new Class[] { Throwable.class };

    private static final Class<?>[] MESSAGE_CAUSE_TYPES = new Class[] {
            String.class, Throwable.class };

    private static final Log log = LogFactory
            .getLog(TransactionalMethodInterceptor.class);

    @Inject
    private SqlSessionManager sqlSessionManager;

    private final boolean readOnly;

    private final ExecutorType executorType;

    private final TransactionIsolationLevel isolationLevel;

    private final boolean rollbackOnly;

    private final boolean force;

    private final Class<? extends Throwable> rethrowExceptionsAs;

    private final String exceptionMessage;

    public TransactionalMethodInterceptor() {
        this(false);
    }

    public TransactionalMethodInterceptor(boolean readOnly) {
        this(readOnly, ExecutorType.SIMPLE, null, false, false,
                Exception.class, "");
    }

    public TransactionalMethodInterceptor(boolean readOnly,
            ExecutorType executorType,
            TransactionIsolationLevel isolationLevel, boolean rollbackOnly,
            boolean force, Class<? extends Throwable> rethrowExceptionsAs,
            String exceptionMessage) {
        this.readOnly = readOnly;
        this.executorType = executorType;
        this.isolationLevel = isolationLevel;
        this.rollbackOnly = rollbackOnly;
        this.force = force;
        this.rethrowExceptionsAs = rethrowExceptionsAs;
        this.exceptionMessage = exceptionMessage;
    }

    public void setSqlSessionManager(SqlSessionManager sqlSessionManager) {
        this.sqlSessionManager = sqlSessionManager;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method interceptedMethod = invocation.getMethod();

        String debugPrefix = null;
        if (log.isDebugEnabled()) {
            debugPrefix = String.format("[Intercepted method: %s]",
                    interceptedMethod.toGenericString());
        }

        boolean isSessionInherited = sqlSessionManager
                .isManagedSessionStarted();

        if (isSessionInherited) {
            if (log.isDebugEnabled()) {
                log.debug(String.format(
                        "%s - SqlSession already set for thread: %s",
                        debugPrefix, Thread.currentThread().getId()));
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug(String
                        .format("%s - SqlSession not set for thread: %s, creating a new one",
                                debugPrefix, Thread.currentThread().getId()));
            }

            sqlSessionManager.startManagedSession(executorType, isolationLevel);
            sqlSessionManager.getConnection().setReadOnly(readOnly);
        }

        Object object = null;
        try {
            object = invocation.proceed();

            if (!isSessionInherited && !rollbackOnly) {
                sqlSessionManager.commit(force);
            }
        } catch (Throwable t) {
            sqlSessionManager.rollback(force);

            for (Class<?> exceptionClass : interceptedMethod
                    .getExceptionTypes()) {
                if (exceptionClass.isAssignableFrom(t.getClass())) {
                    throw t;
                }
            }

            if (rethrowExceptionsAs.isAssignableFrom(t.getClass())) {
                throw t;
            }

            String errorMessage;
            Object[] initargs;
            Class<?>[] initargsType;

            if (exceptionMessage.length() != 0) {
                errorMessage = String.format(exceptionMessage,
                        invocation.getArguments());
                initargs = new Object[] { errorMessage, t };
                initargsType = MESSAGE_CAUSE_TYPES;
            } else {
                initargs = new Object[] { t };
                initargsType = CAUSE_TYPES;
            }

            Constructor<? extends Throwable> exceptionConstructor = getMatchingConstructor(
                    rethrowExceptionsAs, initargsType);
            Throwable rethrowEx = null;
            if (exceptionConstructor != null) {
                try {
                    rethrowEx = exceptionConstructor.newInstance(initargs);
                } catch (Exception e) {
                    errorMessage = String
                            .format("Impossible to re-throw '%s', it needs the constructor with %s argument(s).",
                                    rethrowExceptionsAs.getName(),
                                    Arrays.toString(initargsType));
                    log.error(errorMessage, e);
                    rethrowEx = new RuntimeException(errorMessage, e);
                }
            } else {
                errorMessage = String
                        .format("Impossible to re-throw '%s', it needs the constructor with %s or %s argument(s).",
                                rethrowExceptionsAs.getName(),
                                Arrays.toString(CAUSE_TYPES),
                                Arrays.toString(MESSAGE_CAUSE_TYPES));
                log.error(errorMessage);
                rethrowEx = new RuntimeException(errorMessage);
            }

            throw rethrowEx;
        } finally {
            if (!isSessionInherited) {
                if (rollbackOnly) {
                    if (log.isDebugEnabled()) {
                        log.debug(debugPrefix + " - SqlSession of thread: "
                                + Thread.currentThread().getId()
                                + " was in rollbackOnly mode, rolling it back");
                    }

                    sqlSessionManager.rollback(true);
                }

                if (log.isDebugEnabled()) {
                    log.debug(String
                            .format("%s - SqlSession of thread: %s terminated its life-cycle, closing it",
                                    debugPrefix, Thread.currentThread().getId()));
                }

                sqlSessionManager.close();
            } else if (log.isDebugEnabled()) {
                log.debug(String
                        .format("%s - SqlSession of thread: %s is inherited, skipped close operation",
                                debugPrefix, Thread.currentThread().getId()));
            }
        }

        return object;
    }

    private static <E extends Throwable> Constructor<E> getMatchingConstructor(
            Class<E> type, Class<?>[] argumentsType) {
        Class<? super E> currentType = type;
        while (Object.class != currentType) {
            for (Constructor<?> constructor : currentType.getConstructors()) {
                if (Arrays.equals(argumentsType,
                        constructor.getParameterTypes())) {
                    return (Constructor<E>) constructor;
                }
            }
            currentType = currentType.getSuperclass();
        }
        return null;
    }
}

Guiceでは、Spring3 + MyBatis3で使用したapplicationContext.xml、sqlMapConfig.xml、database.propertiesが不要になります。
かわりに、Guiceの場合はソースコードに設定を記述します。
以下が実行するテストコードです。beforeメソッド内の処理が設定になります。

UserServiceTestGuice.java
package com.brightgenerous.sample.application.service;

import java.lang.reflect.Method;
import java.util.Properties;
import java.util.Set;

import com.brightgenerous.extension.ibatis.TransactionalMethodInterceptor;
import com.brightgenerous.sample.application.bean.User;
import com.brightgenerous.sample.application.dao.UserDao;

import org.aopalliance.intercept.MethodInterceptor;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.Before;
import org.junit.Test;
import org.mybatis.guice.MyBatisModule;
import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider;
import org.mybatis.guice.transactional.Transactional;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Scopes;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.name.Names;

public class UserServiceTestGuice {

    private Injector injector;

    @Before
    public void before() {

        injector = Guice.createInjector(new MyBatisModule() {

            @Override
            protected void initialize() {

                MethodInterceptor updateInterceptor = new TransactionalMethodInterceptor();
                binder().requestInjection(updateInterceptor);
                binder().bindInterceptor(new AbstractMatcher<Class<?>>() {

                    @Override
                    public boolean matches(Class<?> x_arg0) {
                        return x_arg0.getName().matches("^.*Service$");
                    }
                }, new AbstractMatcher<Method>() {

                    @Override
                    public boolean matches(Method x_arg0) {
                        return (x_arg0.getAnnotation(Transactional.class) == null)
                                && x_arg0.getName().matches(
                                        "^(regist|update|delete).*$");
                    }
                }, updateInterceptor);
                MethodInterceptor readInterceptor = new TransactionalMethodInterceptor(
                        true);
                binder().requestInjection(readInterceptor);
                binder().bindInterceptor(new AbstractMatcher<Class<?>>() {

                    @Override
                    public boolean matches(Class<?> x_arg0) {
                        return x_arg0.getName().matches("^.*Service$");
                    }
                }, new AbstractMatcher<Method>() {

                    @Override
                    public boolean matches(Method x_arg0) {
                        return (x_arg0.getAnnotation(Transactional.class) == null)
                                && !x_arg0.getName().matches(
                                        "^(regist|update|delete).*$");
                    }
                }, readInterceptor);

                environmentId("test");

                bindDataSourceProviderType(PooledDataSourceProvider.class);
                bindTransactionFactoryType(JdbcTransactionFactory.class);

                Properties properties = new Properties();
                properties.setProperty("JDBC.url",
                        "jdbc:postgresql://127.0.0.1:5432/scheme");
                properties.setProperty("JDBC.driver", "org.postgresql.Driver");
                properties.setProperty("JDBC.username", "username");
                properties.setProperty("JDBC.password", "password");
                properties.setProperty("JDBC.autoCommit", "false");
                Names.bindProperties(binder(), properties);

                useCacheEnabled(true);
                lazyLoadingEnabled(true);
                aggressiveLazyLoading(true);
                autoMappingBehavior(AutoMappingBehavior.PARTIAL);
                useColumnLabel(true);
                useGeneratedKeys(false);
                failFast(true);
                multipleResultSetsEnabled(true);

                addSimpleAliases("com.brightgenerous.sample.application.bean");

                addMapperClasses(new ResolverUtil<Object>().find(
                        new ResolverUtil.Test() {

                            @Override
                            public boolean matches(Class<?> x_arg0) {
                                return x_arg0.getName().matches("^.*Dao$");
                            }
                        }, "com.brightgenerous.sample.application.dao")
                        .getClasses());
            }
        },

        new AbstractModule() {

            @Override
            protected void configure() {
                Set<Class<? extends Object>> services = new ResolverUtil<Object>()
                        .find(new ResolverUtil.Test() {

                            @Override
                            public boolean matches(Class<?> x_arg0) {
                                return x_arg0.getName().matches("^.*Service$");
                            }
                        }, "com.brightgenerous.sample.application.service")
                        .getClasses();
                for (Class<? extends Object> service : services) {
                    bind(service).in(Scopes.SINGLETON);
                }
            }
        });
    }

    @Test
    public void test01() {
        UserService service = injector.getInstance(UserService.class);
        User user = new User();
        user.setId(Integer.valueOf(1));
        user = service.get(user);
        System.out.println(user.getName());
        user.setName(user.getName() + "-");
        service.update(user);
        System.out.println(service.get(user).getName());
        System.out.println(service.equals(injector
                .getInstance(UserService.class)));
        System.out.println(injector.getInstance(UserDao.class).equals(
                injector.getInstance(UserDao.class)));
    }
}

使ってみると、Guiceもかなり便利でした。
今回は、あえてSpringの場合と同じ設定にするためにトランザクションインターセプターを自作しましたが、 標準のトランザクションインターセプターとTransactionalアノテーションを使う方法が一般的だと思います。

設定をソースコードに記述するフレームワークとして、Wicketと相性が良さそうです。

2012年1月9日月曜日

Spring3 + MyBatis3

MyBatis3がMyBatis2に比べて結構使いやすくなっています。
特に実装クラスを必要としないMapperの機能が便利です。
他には、XMLでognl式が使えたり、使いやすいタグが増えています。

Spring3 + MyBatis3の実装例で試してみます。

まず、データクラスを作成します。

User.java
package com.brightgenerous.sample.application.bean;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = -1056270813521861315L;

    private Integer m_id;

    private String m_name;

    private String m_email;

    public Integer getId() {
        return m_id;
    }

    public void setId(Integer x_id) {
        m_id = x_id;
    }

    public String getName() {
        return m_name;
    }

    public void setName(String x_name) {
        m_name = x_name;
    }

    public String getEmail() {
        return m_email;
    }

    public void setEmail(String x_email) {
        m_email = x_email;
    }
}

次に、Dao(Mapper)インターフェースを作成します。

UserDao.java
package com.brightgenerous.sample.application.dao;

import com.brightgenerous.sample.application.bean.User;

public interface UserDao {

    User select(User x_user);

    int insert(User x_user);

    int update(User x_user);

    int delete(User x_user);
}

そして、UserDao.javaと同じパッケージにUserDao.xmlを置きます。(拡張子以外のファイル名を同じにする)
それだけでインターフェースとXMLファイルが関連付けられます。

UserDao.xml





 

 
  
  
  
 

 

 
  
   select coalesce((select max(id) from t_user),0) + 1 as id
  
  insert into
   t_user (
    id,
    name,
    email
   ) values (
    #{id},
    #{name},
    #{email}
   )
 

 
  update
   t_user
  set
   name = #{name},
   email = #{email}
  where
   id = #{id}
 

 
  delete from
   t_user
  where
   id = #{id}
 


最後に、トランザクション境界となるServiceクラスを作成します。

UserService.java
package com.brightgenerous.sample.application.service;

import javax.inject.Inject;

import com.brightgenerous.sample.application.bean.User;
import com.brightgenerous.sample.application.dao.UserDao;

public class UserService {

    @Inject
    private UserDao m_userDao;

    public User get(User x_user) {
        return m_userDao.select(x_user);
    }

    public void update(User x_user) {
        m_userDao.update(x_user);
    }
}

あとは設定ファイルを書くだけです。
注入先フィールドのInjectアノテーションはjavax.inject.Injectです。
実は、上記のすべてのクラスでMyBatisやSpringに関するインポートがないのです。

applicationContext.xml



 
 
  
 

 
 
  
  
  
  
  
  
 

 
 
  
 
 
  
   
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRED,readOnly
   
  
 
 
  
  
 
 
  
  
 

 
 
  
  
 

 
 
  
 
 
  
 


applicationContext.xmlには com.brightgenerous.extension.spring3.FqcnBeanNameGeneratorというクラスがありますが、
これは、ただFQCNを返すだけのSpringのBeanNameGenerator実装です。
database.propertiesファイルは特にここで書くほどでもないので省略します。

sqlMapConfig.xml





 
  
  
  
  
  
  
  
  
  
  
 

 
  
 


実行方法は以下のようになります。

UserServiceTestSpring
package com.brightgenerous.sample.application.service;

import com.brightgenerous.sample.application.bean.User;
import com.brightgenerous.sample.application.dao.UserDao;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceTestSpring {

    private ApplicationContext context;

    @Before
    public void before() {
        context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }

    @Test
    public void test01() {
        UserService service = context.getBean(UserService.class);
        User user = new User();
        user.setId(Integer.valueOf(1));
        user = service.get(user);
        user.setName(user.getName() + "-");
        service.update(user);
    }
}

これ以上ソースコードの記述量を減らせないんじゃないのかと思えるほど、簡単になりました。
設定ファイルは一度書いてしまえば、以降は編集しないので、 実際に作成するファイルはデータクラス、Mapperインターフェース、MapperのXMLファイル、サービスクラスの4つです。

次回は、SpringをGuiceに置き換えて同じ機能を実現してみようと思います。
具体的には、Springでの設定ファイルにあたる部分をソースで書くだけで、Mapperやサービスクラスには手を加えません。

2012年1月5日木曜日

Servlet3.0 と Guice

最近、Servlet3.0のアノテーションによるゼロコンフィグを試していましたところ、 ふと、これってGuiceと相性いいんんじゃね?と思い至りました。
サクっと書いてみたServlet3.0 + Guice + JDBCってのを紹介します。

Servlet3.0からは、アノテーションでServletを登録できる他に、ServletContext#addServlet()を呼び出しての登録も可能になっています。
つまり、Guiceを利用して生成したServletインスタンスをマッピングさせることが可能だということです。
これをやってみようかと思います。

まず、トランザクション担当のクラスの親となるクラスを作成します。

AbstractService.java
package com.brightgenerous.sample;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.Connection;

public abstract class AbstractService {

    @Target({ ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface Transactional {
    }

    private final ThreadLocal<Holder<Connection>> thlocal = new ThreadLocal<Holder<Connection>>() {

        @Override
        protected Holder<Connection> initialValue() {
            return new Holder<Connection>();
        }
    };

    protected Connection getConnection() {
        return thlocal.get().m_object;
    }

    private void setConnection(Connection x_connection) {
        thlocal.get().m_object = x_connection;
    }

    static class Holder<T> {

        T m_object;
    }
}

次に、そのトランザクションの実装クラスを作成します。
インターフェースと実装クラスは1つずつなので、まとめて書いてしまいます。
便利なのでapache-commons-dbutilsを使用しています。

UserService.java
package com.brightgenerous.sample.service;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import com.brightgenerous.sample.AbstractService;

public interface UserService {

    List<Map<String, Object>> findAll();

    void add(Map<String, Object> x_data);

    class UserServiceImpl extends AbstractService implements UserService {

        @Transactional
        @Override
        public List<Map<String, Object>> findAll() {
            StringBuilder sql = new StringBuilder();
            sql.append("SELECT id, name, email FROM t_user ORDER BY id");
            try {
                return new QueryRunner().query(getConnection(), sql.toString(),
                        new MapListHandler());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        @Transactional
        @Override
        public void add(Map<String, Object> x_data) {
            x_data.put("id", nextId());
            StringBuilder sql = new StringBuilder();
            sql.append("INSERT INTO t_user(id, name, email) VALUES (?, ?, ?)");
            List<Object> params = new ArrayList<Object>();
            params.add(x_data.get("id"));
            params.add(x_data.get("name"));
            params.add(x_data.get("email"));
            try {
                new QueryRunner().update(getConnection(), sql.toString(),
                        params.toArray());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        private Number nextId() {
            StringBuilder sql = new StringBuilder();
            sql.append("SELECT COALESCE((SELECT MAX(id) FROM t_user), 0) + 1");
            try {
                return (Number) new QueryRunner().query(getConnection(),
                        sql.toString(), new ScalarHandler());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

いったん、Servletの抽象クラスを作成しておきます。

AbstractHttpServlet.java
package com.brightgenerous.sample.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

abstract class AbstractHttpServlet extends HttpServlet {

    private static final long serialVersionUID = 2788992165026478005L;

    @Override
    protected void doGet(HttpServletRequest x_request,
            HttpServletResponse x_response) throws ServletException,
            IOException {

        doService(x_request, x_response);
    }

    @Override
    protected void doPost(HttpServletRequest x_request,
            HttpServletResponse x_response) throws ServletException,
            IOException {

        doService(x_request, x_response);
    }

    abstract protected void doService(HttpServletRequest x_request,
            HttpServletResponse x_response) throws ServletException,
            IOException;
}

そして、Servletの実装クラスを作成します。
ここでも、インターフェースと実装クラスは1つずつなので、まとめて書いてしまいます。

UserAddServlet.java
package com.brightgenerous.sample.servlet;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import com.brightgenerous.sample.service.UserService;
import com.google.inject.Inject;

public interface UserAddServlet extends Servlet {

    class UserAddServletImpl extends AbstractHttpServlet implements
            UserAddServlet {

        private static final long serialVersionUID = -4751580520972958252L;

        @Inject
        private UserService service;

        @Override
        protected void doService(HttpServletRequest x_request,
                HttpServletResponse x_response) throws ServletException,
                IOException {

            List<Map<String, Object>> datas = service.findAll();
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("name", x_request.getParameter("name"));
            data.put("email", x_request.getParameter("email"));

            service.add(data);

            StringBuilder sb = new StringBuilder();
            sb.append("<html><head><title>servlet3 + guice</title></head><body>");
            sb.append("<a href=\"" + UserFormServlet.class.getSimpleName()
                    + "\">back</a>");
            sb.append("<table border=\"1\">");
            sb.append("<tr><th>name</th><th>e-mail</th></tr>");
            // ignore SQL injection...
            for (Map<String, Object> d : datas) {
                sb.append("<tr><td>" + d.get("name") + "</td>");
                sb.append("<td>" + d.get("email") + "</td></tr>");
            }
            sb.append("<tr><th>name</th><th>e-mail</th></tr>");
            sb.append("<tr><td>" + data.get("name") + "</td>");
            sb.append("<td>" + data.get("email") + "</td></tr>");

            sb.append("</table>");
            sb.append("</body></html>");

            ServletOutputStream out = x_response.getOutputStream();
            out.print(sb.toString());
            out.flush();
        }
    }
}

最後に、ServletContextListener実装クラスを作成します。
このクラスにweb.xmlに対応する処理を記述します。

InitialContextListener.java
package com.brightgenerous.sample;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;


import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import com.brightgenerous.sample.AbstractService.Transactional;
import com.brightgenerous.sample.service.UserService;
import com.brightgenerous.sample.service.UserService.UserServiceImpl;
import com.brightgenerous.sample.servlet.UserAddServlet;
import com.brightgenerous.sample.servlet.UserFormServlet;
import com.brightgenerous.sample.servlet.UserAddServlet.UserAddServletImpl;
import com.brightgenerous.sample.servlet.UserFormServlet.UserFormServletImpl;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.matcher.Matchers;

@WebListener
public class InitialContextListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent x_sce) {
    }

    @Override
    public void contextInitialized(ServletContextEvent x_sce) {

        Injector injector = Guice.createInjector(new AbstractModule() {

            @Override
            protected void configure() {
                bind(UserAddServlet.class).to(UserAddServletImpl.class);
                bind(UserService.class).to(UserServiceImpl.class);
                bindInterceptor(Matchers.subclassesOf(AbstractService.class),
                        Matchers.annotatedWith(Transactional.class),
                        new MethodInterceptorImpl());
            }
        });

        ServletContext context = x_sce.getServletContext();
        context.addServlet(UserAddServlet.class.getSimpleName(),
                injector.getInstance(UserAddServlet.class)).addMapping(
                "/" + UserAddServlet.class.getSimpleName());
    }

    static class MethodInterceptorImpl implements MethodInterceptor {

        private static final String URL = "jdbc:postgresql://127.0.0.1:5432/db";

        private static final String USERNAME = "username";

        private static final String PASSWORD = "password";

        private static final Method getter;

        private static final Method setter;

        static {
            try {
                Class.forName("org.postgresql.Driver");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            try {
                getter = AbstractService.class
                        .getDeclaredMethod("getConnection");
                if (!getter.isAccessible()) {
                    getter.setAccessible(true);
                }
                setter = AbstractService.class.getDeclaredMethod(
                        "setConnection", Connection.class);
                if (!setter.isAccessible()) {
                    setter.setAccessible(true);
                }
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public Object invoke(MethodInvocation x_arg0) throws Throwable {
            Object object = x_arg0.getThis();
            if (getter.invoke(object) != null) {
                return x_arg0.proceed();
            }

            Connection connection = DriverManager.getConnection(URL, USERNAME,
                    PASSWORD);
            if (connection.getAutoCommit()) {
                connection.setAutoCommit(false);
            }

            setter.invoke(object, connection);

            Object result = null;
            try {

                result = x_arg0.proceed();
                connection.commit();

            } catch (Throwable e) {
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
                throw e;
            } finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                try {

                    setter.invoke(object, (Connection) null);

                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
            return result;
        }
    }
}

起動すると、以下の順に実行されます
1.WebListenerアノテーションによってInitialContextListenerをServletContextListenerとして登録
2.contextInitializedが実行される
2-1.GuiceでAbstractServiceのサブクラスのTransactionalアノテーションが付いているメソッドに対してMethodInterceptorImplを設定しておく
2-2.GuiceがUserAddServletのインスタンスを生成する際にUserServiceを実装したクラスのインスタンスを注入
2-3.UserAddServletのインスタンスをServletとして登録

http://[SERVER_NAME]:[PORT_NUMBER]/[CONTEXT_ROOT]/UserAddServlet?name=brigen_name&email=brigen_mail
にRequestを送ると、サーブレットが実行されます。

かなり簡単にServletが書けました。
実際に活用することを考えたらトランザクションの実装はもっと細かくすべきでしょうが。

もちろん、GlassFishなどのJavaEE環境ではServlet3.0 + EJB Lite + JPAの選択が最適でしょう。