35

我希望在学习使用 JDBC 读取/写入 SQL 数据库的 Guice 时创建一个示例项目。然而,在多年使用 Spring 并让它抽象出连接处理和事务之后,我在概念上努力工作。

我想要一个服务来启动和停止事务并调用多个存储库,这些存储库重用相同的连接并参与相同的事务。我的问题是:

  • 我在哪里创建我的数据源?
  • 如何让存储库访问连接?(线程本地?)
  • 管理事务的最佳方式(为注释创建拦截器?)

下面的代码显示了我将如何在 Spring 中执行此操作。注入每个存储库的 JdbcOperations 将有权访问与活动事务关联的连接。

除了显示为事务创建拦截器的教程之外,我还没有找到很多涵盖这一点的教程。

我很高兴继续使用 Spring,因为它在我的项目中运行良好,但我想知道如何在纯 Guice 和 JBBC 中做到这一点(没有 JPA/Hibernate/Warp/Reusing Spring)

@Service
public class MyService implements MyInterface {

  @Autowired
  private RepositoryA repositoryA;
  @Autowired
  private RepositoryB repositoryB;
  @Autowired
  private RepositoryC repositoryC; 

  @Override
  @Transactional
  public void doSomeWork() {
    this.repositoryA.someInsert();
    this.repositoryB.someUpdate();
    this.repositoryC.someSelect();  
  }    
}

@Repository
public class MyRepositoryA implements RepositoryA {

  @Autowired
  private JdbcOperations jdbcOperations;

  @Override
  public void someInsert() {
    //use jdbcOperations to perform an insert
  }
}

@Repository
public class MyRepositoryB implements RepositoryB {

  @Autowired
  private JdbcOperations jdbcOperations;

  @Override
  public void someUpdate() {
    //use jdbcOperations to perform an update
  }
}

@Repository
public class MyRepositoryC implements RepositoryC {

  @Autowired
  private JdbcOperations jdbcOperations;

  @Override
  public String someSelect() {
    //use jdbcOperations to perform a select and use a RowMapper to produce results
    return "select result";
  }
}
4

4 回答 4

33

如果您的数据库不经常更改,您可以使用数据库的 JDBC 驱动程序附带的数据源,并在提供程序中隔离对第 3 方库的调用(我的示例使用 H2 数据库提供的,但所有 JDBC 提供程序都应该有一个)。如果您更改为 DataSource 的不同实现(例如 c3PO、Apache DBCP 或由应用服务器容器提供的实现),您可以简单地编写一个新的 Provider 实现以从适当的位置获取数据源。在这里,我使用单例范围来允许在依赖它的那些类之间共享 DataSource 实例(池化所必需的)。

public class DataSourceModule extends AbstractModule {

    @Override
    protected void configure() {
        Names.bindProperties(binder(), loadProperties());

        bind(DataSource.class).toProvider(H2DataSourceProvider.class).in(Scopes.SINGLETON);
        bind(MyService.class);
    }

    static class H2DataSourceProvider implements Provider<DataSource> {

        private final String url;
        private final String username;
        private final String password;

        public H2DataSourceProvider(@Named("url") final String url,
                                    @Named("username") final String username,
                                    @Named("password") final String password) {
            this.url = url;
            this.username = username;
            this.password = password;
        }

        @Override
        public DataSource get() {
            final JdbcDataSource dataSource = new JdbcDataSource();
            dataSource.setURL(url);
            dataSource.setUser(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    }

    static class MyService {
        private final DataSource dataSource;

        @Inject
        public MyService(final DataSource dataSource) {
            this.dataSource = dataSource;
        }

        public void singleUnitOfWork() {

            Connection cn = null;

            try {
                cn = dataSource.getConnection();
                // Use the connection
            } finally {
                try {
                    cn.close();
                } catch (Exception e) {}
            }
        }
    }

    private Properties loadProperties() {
        // Load properties from appropriate place...
        // should contain definitions for:
        // url=...
        // username=...
        // password=...
        return new Properties();
    }
}

要处理事务,应使用事务感知数据源。我不建议手动执行此操作。使用类似 warp-persist 或容器提供的事务管理的东西,但是它看起来像这样:

public class TxModule extends AbstractModule {

    @Override
    protected void configure() {
        Names.bindProperties(binder(), loadProperties());

        final TransactionManager tm = getTransactionManager();

        bind(DataSource.class).annotatedWith(Real.class).toProvider(H2DataSourceProvider.class).in(Scopes.SINGLETON);
        bind(DataSource.class).annotatedWith(TxAware.class).to(TxAwareDataSource.class).in(Scopes.SINGLETON);
        bind(TransactionManager.class).toInstance(tm);
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), new TxMethodInterceptor(tm));
        bind(MyService.class);
    }

    private TransactionManager getTransactionManager() {
        // Get the transaction manager
        return null;
    }

    static class TxMethodInterceptor implements MethodInterceptor {

        private final TransactionManager tm;

        public TxMethodInterceptor(final TransactionManager tm) {
            this.tm = tm;
        }

        @Override
        public Object invoke(final MethodInvocation invocation) throws Throwable {
            // Start tx if necessary
            return invocation.proceed();
            // Commit tx if started here.
        }
    }

    static class TxAwareDataSource implements DataSource {

        static ThreadLocal<Connection> txConnection = new ThreadLocal<Connection>();
        private final DataSource ds;
        private final TransactionManager tm;

        @Inject
        public TxAwareDataSource(@Real final DataSource ds, final TransactionManager tm) {
            this.ds = ds;
            this.tm = tm;
        }

        public Connection getConnection() throws SQLException {
            try {
                final Transaction transaction = tm.getTransaction();
                if (transaction != null && transaction.getStatus() == Status.STATUS_ACTIVE) {

                    Connection cn = txConnection.get();
                    if (cn == null) {
                        cn = new TxAwareConnection(ds.getConnection());
                        txConnection.set(cn);
                    }

                    return cn;

                } else {
                    return ds.getConnection();
                }
            } catch (final SystemException e) {
                throw new SQLException(e);
            }
        }

        // Omitted delegate methods.
    }

    static class TxAwareConnection implements Connection {

        private final Connection cn;

        public TxAwareConnection(final Connection cn) {
            this.cn = cn;
        }

        public void close() throws SQLException {
            try {
                cn.close();
            } finally {
                TxAwareDataSource.txConnection.set(null);
            }
        }

        // Omitted delegate methods.
    }

    static class MyService {
        private final DataSource dataSource;

        @Inject
        public MyService(@TxAware final DataSource dataSource) {
            this.dataSource = dataSource;
        }

        @Transactional
        public void singleUnitOfWork() {
            Connection cn = null;

            try {
                cn = dataSource.getConnection();
                // Use the connection
            } catch (final SQLException e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    cn.close();
                } catch (final Exception e) {}
            }
        }
    }
}
于 2010-03-01T03:54:24.147 回答
2

我会使用 c3po 之类的东西直接创建数据源。如果您使用 ComboPooledDataSource 您只需要实例(池在幕后完成),您可以直接绑定或通过提供程序绑定。

然后我会在此之上创建一个拦截器,例如接收@Transactional、管理连接和提交/回滚。您也可以使 Connection 可注入,但您需要确保在某处关闭连接以允许它们再次检入池中。

于 2010-02-28T09:12:49.413 回答
0
  1. 要注入数据源,您可能不需要绑定到单个数据源实例,因为您要连接到 url 中的功能的数据库。使用 Guice,可以强制程序员提供与 DataSource 实现的绑定(链接)。可以将此数据源注入 ConnectionProvider 以返回数据源。

  2. 连接必须在线程本地范围内。您甚至可以实现您的线程本地范围,但必须在提交或回滚操作后关闭所有线程本地连接并从 ThreadLocal 对象中删除,以防止内存泄漏。经过一番折腾,我发现您需要对 Injector 对象有一个钩子才能删除 ThreadLocal 元素。注入器可以很容易地注入到您的 Guice AOP 拦截器中,如下所示:

    protected void visitThreadLocalScope(Injector 注入器,
                        DefaultBindingScopingVisitor 访问者){
        如果(喷油器 == null){
            返回;
        }

        对于(Map.Entry,绑定>条目:
                injector.getBindings().entrySet()) {
            最终绑定 binding = entry.getValue();
            // 目前对返回值不感兴趣。
            binding.acceptScopingVisitor(visitor);
        }        
    }

    /**
     * 退出线程本地范围的默认实现。这是
     *清理和防止任何内存泄漏必不可少。
     *
     *

仅当范围是子类或是 * {@link ThreadLocalScope} 的实例。 */ 私有静态最终类 ExitingThreadLocalScopeVisitor 扩展 DefaultBindingScopingVisitor { @覆盖 公共无效访问范围(范围范围){ // ThreadLocalScope 是自定义范围。 if (ThreadLocalScope.class.isAssignableFrom(scope.getClass())) { ThreadLocalScope threadLocalScope = (ThreadLocalScope) 范围; threadLocalScope.exit(); } 返回空值; } }

确保在调用该方法并关闭连接后调用它。试试这个看看这是否有效。

于 2010-03-23T21:27:10.267 回答
0

请检查我提供的解决方案:使用 Guice 和 JDBC 的事务 - 解决方案讨论

这只是一个非常基本的版本和简单的方法。但它可以很好地处理使用 Guice 和 JDBC 的事务。

于 2015-02-04T21:57:48.997 回答