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";
}
}
データベースが頻繁に変更されない場合は、データベースのJDBCドライバーに付属のデータソースを使用して、プロバイダー内のサードパーティライブラリへの呼び出しを分離できます(私の例では、H2 dataabseによって提供されるものを使用しますが、すべてのJDBCプロバイダーには1つ必要です。 )。データソースの別の実装(c3PO、Apache DBCP、またはアプリサーバーコンテナによって提供されるものなど)に変更する場合は、新しいプロバイダー実装を記述して、適切な場所からデータソースを取得できます。ここでは、シングルトンスコープを使用して、それに依存するクラス間で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();
}
}
トランザクションを処理するには、トランザクション対応のデータソースを使用する必要があります。これを手動で実装することはお勧めしません。ワープパーシストやコンテナ提供のトランザクション管理などを使用すると、次のようになります。
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) {}
}
}
}
}
C3poのようなものを使用してデータソースを直接作成します。 ComboPooledDataSourceを使用する場合は、インスタンスのみが必要です(プーリングは内部で行われます)。インスタンスは直接またはプロバイダーを介してバインドできます。
次に、その上にインターセプターを作成します。 @Transactionalを取得し、接続を管理し、コミット/ロールバックします。 Connectionを注入可能にすることもできますが、接続をプールに再度チェックインできるように、接続をどこかで閉じるようにする必要があります。
データソースを挿入するために、URL内の機能に接続しているデータベースがあるため、おそらく単一のデータソースインスタンスにバインドする必要はありません。 Guiceを使用すると、プログラマーにDataSource実装へのバインディングを提供させることができます( link )。このデータソースをConnectionProviderに挿入して、データソースを返すことができます。
接続はスレッドローカルスコープ内にある必要があります。 スレッドローカルスコープ を実装することもできますが、メモリリークを防ぐために、コミットまたはロールバック操作の後に、すべてのスレッドローカル接続を閉じてThreadLocalオブジェクトから削除する必要があります。ハッキングした後、ThreadLocal要素を削除するには、Injectorオブジェクトへのフックが必要であることがわかりました。インジェクターは、GuiceAOPインターセプターに簡単に注入できます。
protected void visitThreadLocalScope(Injector Injector、 DefaultBindingScopingVisitor visitor){ if(injector == null){ return; } for(Map.Entry、Binding> entry: injector.getBindings()。entrySet()){ final Binding binding = entry.getValue(); //まだ戻り値に関心がない。 binding.acceptScopingVisitor(visitor); } } /* * *スレッドローカルスコープを終了するデフォルトの実装。これは *メモリリークをクリーンアップして防止するために不可欠です。 * *
スコープが訪問されるのは、スコープが{@linkThreadLocalScope}のサブクラスまたは *インスタンスである場合のみです。 */ privatestatic final class ExitingThreadLocalScopeVisitor extends DefaultBindingScopingVisitor { @ Override public Void visitScope(Scope scope){ // ThreadLocalScopeはカスタムスコープです。 if(ThreadLocalScope.class.isAssignableFrom(scope.getClass())){ ThreadLocalScope threadLocalScope =(ThreadLocalScope)scope; threadLocalScope.exit(); } nullを返す; } }
メソッドが呼び出されて接続が閉じられた後で、必ずこれを呼び出してください。これを試して、これが機能するかどうかを確認してください。
私が提供したソリューションを確認してください: GuiceとJDBCとのトランザクション-ソリューションの議論
これは非常に基本的なバージョンであり、単純なアプローチです。しかし、GuiceとJDBCとのトランザクションを処理するのは問題なく機能します。