简体   繁体   English

Guice,JDBC和管理数据库连接

[英]Guice, JDBC and managing database connections

I'm looking to create a sample project while learning Guice which uses JDBC to read/write to a SQL database. 我正在寻找创建一个示例项目,同时学习Guice,它使用JDBC来读/写SQL数据库。 However, after years of using Spring and letting it abstract away connection handling and transactions I'm struggling to work it our conceptually. 然而,经过多年的使用Spring并让它抽象出连接处理和事务,我正在努力从概念上运用它。

I'd like to have a service which starts and stops a transaction and calls numerous repositories which reuse the same connection and participate in the same transaction. 我想要一个启动和停止事务的服务,并调用大量的存储库,这些存储库重用相同的连接并参与同一个事务。 My questions are: 我的问题是:

  • Where do I create my Datasource? 我在哪里创建我的数据源?
  • How do I give the repositories access to the connection? 如何授予存储库访问连接的权限? (ThreadLocal?) (ThreadLocal的?)
  • Best way to manage the transaction (Creating an Interceptor for an annotation?) 管理事务的最佳方法(为注释创建拦截器?)

The code below shows how I would do this in Spring. 下面的代码显示了我将如何在Spring中执行此操作。 The JdbcOperations injected into each repository would have access to the connection associated with the active transaction. 注入每个存储库的JdbcOperations可以访问与活动事务关联的连接。

I haven't been able to find many tutorials which cover this, beyond ones which show creating interceptors for transactions. 除了那些显示为交易创建拦截器的教程之外,我还没有找到许多涵盖这个的教程。

I am happy with continuing to use Spring as it is working very well in my projects, but I'd like to know how to do this in pure Guice and JBBC (No JPA/Hibernate/Warp/Reusing Spring) 我很高兴继续使用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";
  }
}

If your database change infrequently, you could use the data source that comes with the database's JDBC driver and isolate the calls to the 3rd party library in a provider (My example uses the one provided by the H2 dataabse, but all JDBC providers should have one). 如果您的数据库不经常更改,您可以使用数据库的JDBC驱动程序附带的数据源,并隔离对提供程序中第三方库的调用(我的示例使用H2数据库提供的数据库,但所有JDBC提供程序应该有一个)。 If you change to a different implementation of the DataSource (eg c3PO, Apache DBCP, or one provided by app server container) you can simply write a new Provider implementation to get the datasource from the appropriate place. 如果您更改为DataSource的其他实现(例如,c3PO,Apache DBCP或app server容器提供的实现),您只需编写一个新的Provider实现,即可从适当的位置获取数据源。 Here I've use singleton scope to allow the DataSource instance to be shared amongst those classes that depend on it (necessary for pooling). 在这里,我使用单例范围允许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();
    }
}

To handle transactions a Transaction Aware data source should be used. 要处理事务,应使用Transaction Aware数据源。 I wouldn't recommend implementing this manually. 我不建议手动实现。 Using something like warp-persist or a container supplied transaction management, however it would look something like this: 使用像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) {}
            }
        }
    }
}

I would use something like c3po to create datasources directly. 我会使用像c3po这样的东西来直接创建数据源。 If you use ComboPooledDataSource you only need instance (pooling is done under the covers), which you can bind directly or through a provider. 如果您使用ComboPooledDataSource,则只需要实例(池化在封面下完成),您可以直接绑定或通过提供程序绑定。

Then I'd create an interceptor on top of that, one that eg picks up @Transactional, manages a connection and commit/ rollback. 然后我会创建一个拦截器,例如,一个拦截@Transactional,管理连接和提交/回滚。 You could make Connection injectable as well, but you need to make sure you close the connections somewhere to allow them to be checked into the pool again. 您也可以使Connection可注入,但您需要确保在某处关闭连接以允许它们再次进入池中。

  1. To inject a data source, you probably don't need to be bound to a single data source instance since the database you are connecting to features in the url. 要注入数据源,您可能不需要绑定到单个数据源实例,因为您要连接到URL中的功能的数据库。 Using Guice, it is possible to force programmers to provide a binding to a DataSource implementation ( link ) . 使用Guice,可以强制程序员提供对DataSource实现( 链接 )的绑定。 This data source can be injected into a ConnectionProvider to return a data source. 可以将此数据源注入ConnectionProvider以返回数据源。

  2. The connection has to be in a thread local scope. 连接必须位于线程本地范围内。 You can even implement your thread local scope but all thread local connections must be closed & removed from ThreadLocal object after commit or rollback operations to prevent memory leakage. 您甚至可以实现线程本地作用域,但必须在提交或回滚操作之后关闭并从ThreadLocal对象中删除所有线程本地连接,以防止内存泄漏。 After hacking around, I have found that you need to have a hook to the Injector object to remove ThreadLocal elements. 在黑客攻击之后,我发现你需要有一个钩子来注入Injector对象以删除ThreadLocal元素。 An injector can easily be injected into your Guice AOP interceptor, some thing like this: 注射器很容易注入你的Guice AOP拦截器,有些事情如下:

protected  void visitThreadLocalScope(Injector injector, 
                        DefaultBindingScopingVisitor visitor) {
        if (injector == null) {
            return;
        }

        for (Map.Entry, Binding> entry : 
                injector.getBindings().entrySet()) {
            final Binding binding = entry.getValue();
            // Not interested in the return value as yet.
            binding.acceptScopingVisitor(visitor);
        }        
    }

    /**
     * Default implementation that exits the thread local scope. This is 
     * essential to clean up and prevent any memory leakage.
     * 
     * 

The scope is only visited iff the scope is an sub class of or is an * instance of {@link ThreadLocalScope}. */ private static final class ExitingThreadLocalScopeVisitor extends DefaultBindingScopingVisitor { @Override public Void visitScope(Scope scope) { // ThreadLocalScope is the custom scope. if (ThreadLocalScope.class.isAssignableFrom(scope.getClass())) { ThreadLocalScope threadLocalScope = (ThreadLocalScope) scope; threadLocalScope.exit(); } return null; } }

Make sure you call this after the method has been invoked and closing the connection. 确保在调用方法并关闭连接后调用此方法。 Try this to see if this works. 试试看这是否有效。

Please check the solution I provided: Transactions with Guice and JDBC - Solution discussion 请检查我提供的解决方案: 与Guice和JDBC的交易 - 解决方案讨论

it is just a very basic version and simple approach. 它只是一个非常基本的版本和简单的方法。 but it works just fine to handle transactions with Guice and JDBC. 但它可以很好地处理与Guice和JDBC的事务。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM