35

JDBC を使用して SQL データベースの読み取り/書き込みを行う Guice を学習しながら、サンプル プロジェクトを作成しようとしています。しかし、Spring を何年も使用し、接続処理とトランザクションを抽象化した後、私はそれを概念的に扱うのに苦労しています。

トランザクションを開始および停止し、同じ接続を再利用して同じトランザクションに参加する多数のリポジトリを呼び出すサービスが必要です。私の質問は次のとおりです。

  • データソースはどこで作成できますか?
  • リポジトリに接続へのアクセスを許可するにはどうすればよいですか? (スレッドローカル?)
  • トランザクションを管理する最良の方法 (注釈のインターセプターを作成しますか?)

以下のコードは、Spring でこれを行う方法を示しています。各リポジトリに注入された JdbcOperations は、アクティブなトランザクションに関連付けられた接続にアクセスできます。

トランザクションのインターセプターの作成を示すものを超えて、これをカバーする多くのチュートリアルを見つけることができませんでした。

Spring は私のプロジェクトで非常にうまく機能しているので、引き続き使用することに満足していますが、純粋な Guice と JBBC (JPA/Hibernate/Warp/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";
  }
}
4

4 に答える 4

33

データベースが頻繁に変更されない場合は、データベースの JDBC ドライバーに付属のデータ ソースを使用して、サード パーティ ライブラリへの呼び出しをプロバイダーに分離することができます (私の例では、H2 データベースによって提供されるものを使用しますが、すべての JDBC プロバイダーには 1 つが必要です)。 )。DataSource の別の実装 (c3PO、Apache DBCP、またはアプリ サーバー コンテナーによって提供される実装など) に変更する場合は、適切な場所からデータ ソースを取得する新しい Provider 実装を作成するだけです。ここでは、singleton スコープを使用して、それに依存するクラス間で 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) {}
            }
        }
    }
}
于 2010-03-01T03:54:24.147 に答える
2

c3po のようなものを使用して、データソースを直接作成します。ComboPooledDataSource を使用する場合は、直接またはプロバイダーを介してバインドできるインスタンス (プールは内部で行われます) のみが必要です。

次に、その上にインターセプターを作成します。たとえば、@Transactional を取得し、接続を管理し、コミット/ロールバックします。接続を注入可能にすることもできますが、接続をどこかで閉じて、プールに再度チェックインできるようにする必要があります。

于 2010-02-28T09:12:49.413 に答える
0
  1. データソースを挿入するために、URL内の機能に接続しているデータベースがあるため、おそらく単一のデータソースインスタンスにバインドする必要はありません。Guiceを使用すると、プログラマーにDataSource実装(リンク)へのバインディングを提供するように強制することができます。このデータソースをConnectionProviderに挿入して、データソースを返すことができます。

  2. 接続はスレッドローカルスコープ内にある必要があります。スレッドローカルスコープを実装することもできますが、メモリリークを防ぐために、コミットまたはロールバック操作の後に、すべてのスレッドローカル接続を閉じてThreadLocalオブジェクトから削除する必要があります。ハッキングした後、ThreadLocal要素を削除するには、Injectorオブジェクトへのフックが必要であることがわかりました。インジェクターは、GuiceAOPインターセプターに簡単に注入できます。

    保護されたvoidvisitThreadLocalScope(インジェクターインジェクター、
                        DefaultBindingScopingVisitorビジター){
        if(injector == null){
            戻る;
        }

        for(Map.Entry、Binding> entry:
                インジェクター.getBindings()。entrySet()){
            最終的なバインディングバインディング=entry.getValue();
            //まだ戻り値には関心がありません。
            binding.acceptScopingVisitor(visitor);
        }        
    }

    / **
     *スレッドローカルスコープを終了するデフォルトの実装。これは
     *クリーンアップしてメモリリークを防ぐために不可欠です。
     *
     *

スコープが訪問されるのは、スコープがのサブクラスであるか、 *{@linkThreadLocalScope}のインスタンス。 * / プライベート静的最終クラスExitingThreadLocalScopeVisitor DefaultBindingScopingVisitor{を拡張します @オーバーライド public Void visitScope(スコープスコープ){ //ThreadLocalScopeはカスタムスコープです。 if(ThreadLocalScope.class.isAssignableFrom(scope.getClass())){ ThreadLocalScope threadLocalScope =(ThreadLocalScope)スコープ; threadLocalScope.exit(); } nullを返します。 } }

メソッドが呼び出されて接続が閉じられた後で、必ずこれを呼び出してください。これを試して、これが機能するかどうかを確認してください。

于 2010-03-23T21:27:10.267 に答える
0

私が提供した解決策を確認してください: Guice と JDBC を使用したトランザクション - 解決策の議論

これは非常に基本的なバージョンであり、単純なアプローチです。しかし、Guice と JDBC を使用してトランザクションを処理するには問題なく動作します。

于 2015-02-04T21:57:48.997 に答える