java Guice、JDBC 和管理数据库连接
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2347384/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Guice, JDBC and managing database connections
提问by pledge
I'm looking to create a sample project while learning Guice which uses JDBC to read/write to a SQL database. However, after years of using Spring and letting it abstract away connection handling and transactions I'm struggling to work it our conceptually.
我希望在学习使用 JDBC 读取/写入 SQL 数据库的 Guice 的同时创建一个示例项目。然而,在使用 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?)
- Best way to manage the transaction (Creating an Interceptor for an annotation?)
- 我在哪里创建我的数据源?
- 如何授予存储库访问连接的权限?(线程本地?)
- 管理事务的最佳方式(为注释创建拦截器?)
The code below shows how I would do this in Spring. The JdbcOperations injected into each repository would have access to the connection associated with the active transaction.
下面的代码显示了我将如何在 Spring 中执行此操作。注入每个存储库的 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";
}
}
采纳答案by Michael Barker
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). If you change to a different implementation of the DataSource (e.g. 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. Here I've use singleton scope to allow the DataSource instance to be shared amongst those classes that depend on it (necessary for pooling).
如果您的数据库不经常更改,您可以使用数据库的 JDBC 驱动程序附带的数据源,并在提供程序中隔离对第 3 方库的调用(我的示例使用 H2 数据库提供的那个,但所有 JDBC 提供程序都应该有一个)。如果您更改为数据源的不同实现(例如 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();
}
}
To handle transactions a Transaction Aware data source should be used. 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) {}
}
}
}
}
回答by Eelco
I would use something like c3po to create datasources directly. If you use ComboPooledDataSource you only need instance (pooling is done under the covers), which you can bind directly or through a provider.
我会使用类似 c3po 的东西直接创建数据源。如果您使用 ComboPooledDataSource,您只需要实例(池化是在幕后完成的),您可以直接或通过提供程序进行绑定。
Then I'd create an interceptor on top of that, one that e.g. picks up @Transactional, manages a connection and commit/ rollback. 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.
然后我会在此基础上创建一个拦截器,例如接收@Transactional、管理连接和提交/回滚的拦截器。您也可以使连接可注入,但您需要确保在某处关闭连接以允许它们再次检入池中。
回答by Kartik
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. Using Guice, it is possible to force programmers to provide a binding to a DataSource implementation (link) . This data source can be injected into a ConnectionProvider to return a data source.
The connection has to be in a thread local scope. You can even implement your thread local scopebut all thread local connections must be closed & removed from ThreadLocal object after commit or rollback operations to prevent memory leakage. After hacking around, I have found that you need to have a hook to the Injector object to remove ThreadLocal elements. An injector can easily be injected into your Guice AOP interceptor, some thing like this:
要注入数据源,您可能不需要绑定到单个数据源实例,因为您要连接到 url 中的功能的数据库。使用 Guice,可以强制程序员提供到 DataSource 实现(链接)的绑定。可以将此数据源注入 ConnectionProvider 以返回数据源。
连接必须在线程局部范围内。您甚至可以实现您的线程本地范围,但必须在提交或回滚操作后关闭所有线程本地连接并从 ThreadLocal 对象中删除,以防止内存泄漏。经过一番摸索,我发现您需要对 Injector 对象设置一个钩子才能删除 ThreadLocal 元素。一个注入器可以很容易地注入到你的 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.
确保在调用该方法并关闭连接后调用它。试试这个,看看这是否有效。
回答by honghai
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 的事务。

