我正在学习使用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";
}
}
答案 0 :(得分:31)
如果您的数据库不经常更改,您可以使用数据库的JDBC驱动程序附带的数据源,并隔离对提供程序中第三方库的调用(我的示例使用H2数据库提供的数据源,但所有JDBC提供程序应该有一个)。如果您更改为DataSource的其他实现(例如,c3PO,Apache DBCP或app server容器提供的实现),则只需编写新的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();
}
}
要处理事务,应使用Transaction Aware数据源。我不建议手动实现。使用像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) {}
}
}
}
}
答案 1 :(得分:2)
我会使用像c3po这样的东西来直接创建数据源。如果你使用ComboPooledDataSource,你只需要实例(池化是在幕后完成的),你可以直接绑定或通过提供者绑定。
然后我会在其上创建一个拦截器,例如选择@Transactional,管理连接并提交/回滚。您也可以使Connection可注入,但您需要确保在某处关闭连接以允许它们再次进入池中。
答案 2 :(得分:0)
要注入数据源,您可能不需要绑定到单个数据源实例,因为您要连接到URL中的功能的数据库。使用Guice,可以强制程序员提供对DataSource实现的绑定(link)。可以将此数据源注入ConnectionProvider以返回数据源。
连接必须位于线程本地范围内。您甚至可以实现thread local scope,但必须关闭所有线程本地连接。在提交或回滚操作之后从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; } }
确保在调用方法并关闭连接后调用此方法。试试看这是否有效。
答案 3 :(得分:0)
请检查我提供的解决方案:Transactions with Guice and JDBC - Solution discussion
它只是一个非常基本的版本和简单的方法。但它可以很好地处理与Guice和JDBC的事务。