Guice、JDBC 和管理数据库连接
Posted
技术标签:
【中文标题】Guice、JDBC 和管理数据库连接【英文标题】:Guice, JDBC and managing database connections 【发布时间】:2011-01-21 18:54:26 【问题描述】:我希望在学习使用 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";
【问题讨论】:
【参考方案1】:我会使用 c3po 之类的东西直接创建数据源。如果您使用 ComboPooledDataSource,您只需要实例(池在幕后完成),您可以直接绑定或通过提供程序绑定。
然后我会在此之上创建一个拦截器,例如拿起@Transactional,管理连接和提交/回滚。您也可以使 Connection 可注入,但您需要确保在某处关闭连接以允许它们再次检入池中。
【讨论】:
【参考方案2】:如果您的数据库不经常更改,您可以使用数据库的 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)
【讨论】:
这让我明白了很多。关于何时将连接从数据源中取出并在存储库之间共享,然后安全关闭它的问题仍然存在。 只能在需要的时间段内从存储库中获取连接。 IE。通常在事务处于活动状态时。 在您的代码中,您应该始终以相同的方法获取和关闭连接。对于事务,您应该依赖于事务感知的数据源,并将在内部将连接与本地线程关联以提供事务支持。【参考方案3】:要注入数据源,您可能不需要绑定到单个数据源实例,因为您要连接到 url 中的功能的数据库。使用 Guice,可以强制程序员提供对 DataSource 实现的绑定 (link)。可以将此数据源注入到 ConnectionProvider 中以返回数据源。
连接必须在线程本地范围内。您甚至可以实现您的thread local scope,但必须在提交或回滚操作后关闭所有线程本地连接并从 ThreadLocal 对象中删除,以防止内存泄漏。在四处寻找之后,我发现您需要对 Injector 对象进行挂钩才能删除 ThreadLocal 元素。注入器可以很容易地注入到您的 Guice AOP 拦截器中,如下所示:
只有当作用域是一个子类或者是一个 * @link ThreadLocalScope 的实例。 */ 私有静态最终类 ExitingThreadLocalScopeVisitor 扩展 DefaultBindingScopingVisitor @覆盖 公共无效访问范围(范围范围) // ThreadLocalScope 是自定义范围。 if (ThreadLocalScope.class.isAssignableFrom(scope.getClass())) ThreadLocalScope threadLocalScope = (ThreadLocalScope) 范围; threadLocalScope.exit(); 返回空值;
确保在调用该方法并关闭连接后调用它。试试这个看看是否可行。
【讨论】:
【参考方案4】:请查看我提供的解决方案:Transactions with Guice and JDBC - Solution discussion
这只是一个非常基本的版本和简单的方法。但它可以很好地处理使用 Guice 和 JDBC 的事务。
【讨论】:
以上是关于Guice、JDBC 和管理数据库连接的主要内容,如果未能解决你的问题,请参考以下文章
Java数据库连接——JDBC调用存储过程,事务管理和高级应用
Java数据库连接——JDBC调用存储过程,事务管理和高级应用