Pertanyaan Guice, JDBC dan mengelola koneksi basis data


Saya mencari untuk membuat proyek sampel sambil mempelajari Guice yang menggunakan JDBC untuk membaca / menulis ke database SQL. Namun, setelah bertahun-tahun menggunakan Spring dan membiarkannya mengabaikan penanganan koneksi dan transaksi, saya berjuang untuk mengerjakannya secara konseptual.

Saya ingin memiliki layanan yang memulai dan menghentikan transaksi dan memanggil banyak repositori yang menggunakan kembali koneksi yang sama dan berpartisipasi dalam transaksi yang sama. Pertanyaan saya adalah:

  • Di mana saya membuat Sumber Data saya?
  • Bagaimana cara memberi repositori akses ke koneksi? (ThreadLocal?)
  • Cara terbaik untuk mengelola transaksi (Membuat Interceptor untuk anotasi?)

Kode di bawah ini menunjukkan bagaimana saya melakukan ini di Musim Semi. The JdbcOperations disuntikkan ke setiap repositori akan memiliki akses ke koneksi yang terkait dengan transaksi aktif.

Saya belum dapat menemukan banyak tutorial yang mencakup ini, di luar yang menunjukkan pembuatan pencegat untuk transaksi.

Saya senang dengan terus menggunakan Spring karena bekerja sangat baik dalam proyek-proyek saya, tetapi saya ingin tahu bagaimana melakukan ini dalam Guice murni dan JBBC (No 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";
  }
}

32
2018-02-27 13:02


asal


Jawaban:


Jika perubahan basis data Anda tidak sering, Anda bisa menggunakan sumber data yang datang dengan driver JDBC database dan mengisolasi panggilan ke pustaka pihak ke-3 dalam penyedia (contoh saya menggunakan pustaka yang disediakan oleh H2 dataabse, tetapi semua penyedia JDBC harus memiliki satu ). Jika Anda mengubah ke implementasi yang berbeda dari DataSource (mis. C3PO, Apache DBCP, atau yang disediakan oleh penampung server aplikasi), Anda dapat dengan mudah menulis implementasi Penyedia baru untuk mendapatkan sumber data dari tempat yang sesuai. Di sini saya telah menggunakan ruang lingkup tunggal untuk memungkinkan contoh DataSource dibagikan di antara kelas-kelas yang bergantung padanya (diperlukan untuk pengumpulan).

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();
    }
}

Untuk menangani transaksi sumber data Sadar Transaksi harus digunakan. Saya tidak merekomendasikan menerapkan ini secara manual. Menggunakan sesuatu seperti warp-persist atau wadah yang disediakan manajemen transaksi, namun akan terlihat seperti ini:

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) {}
            }
        }
    }
}

28
2018-03-01 03:54



Saya akan menggunakan sesuatu seperti c3po untuk membuat datasources secara langsung. Jika Anda menggunakan ComboPooledDataSource, Anda hanya perlu instance (pooling dilakukan di bawah penutup), yang dapat Anda ikat secara langsung atau melalui penyedia.

Lalu saya akan membuat pencegat di atasnya, yang misalnya mengambil @ Transaksional, mengelola koneksi dan melakukan / rollback. Anda juga bisa menyambungkan Sambungan, tetapi Anda harus memastikan bahwa Anda menutup koneksi di suatu tempat untuk memungkinkan mereka diperiksa ke dalam kolam lagi.


2
2018-02-28 09:12



  1. Untuk menyuntikkan sumber data, Anda mungkin tidak perlu terikat ke satu sumber data contoh sejak database Anda terhubung ke fitur di url. Menggunakan Guice, adalah mungkin untuk memaksa pemrogram untuk memberikan pengikatan pada implementasi DataSource (link). Sumber data ini dapat disuntikkan ke ConnectionProvider untuk mengembalikan sumber data.

  2. Sambungan harus berada dalam lingkup lokal thread. Anda bahkan dapat menerapkan lingkup lokal thread tetapi semua koneksi lokal benang harus ditutup & dihapus dari objek ThreadLocal setelah komit atau kembalikan operasi untuk mencegah kebocoran memori. Setelah meretas, saya telah menemukan bahwa Anda harus memiliki hook ke objek Injector untuk menghapus elemen-elemen ThreadLocal. Sebuah injektor dapat dengan mudah disuntikkan ke interceptor Guice AOP Anda, beberapa hal seperti ini:

    kunjungan void dilindungiThreadLocalScope (Injector injector,
                        Pengunjung DefaultBindingScopingVisitor) {
        if (injector == null) {
            kembali;
        }

        untuk (Map.Entry, Binding> entry:
                injector.getBindings (). entrySet ()) {
            Final Binding binding = entry.getValue ();
            // Belum tertarik dengan nilai kembali.
            binding.acceptScopingVisitor (pengunjung);
        }
    }

    / **
     * Implementasi default yang keluar dari lingkup lokal thread. Ini adalah
     * penting untuk membersihkan dan mencegah kebocoran memori.
     *
     * 

Ruang lingkup hanya dikunjungi jika ruang lingkup adalah sub kelas dari atau merupakan suatu      * contoh {@link ThreadLocalScope}.      * /     kelas final swasta ExitingThreadLocalScopeVisitor             extends DefaultBindingScopingVisitor {         @Mengesampingkan         public Void visitScope (Ruang lingkup lingkup) {                         // ThreadLocalScope adalah ruang lingkup khusus.             if (ThreadLocalScope.class.isAssignableFrom (scope.getClass ())) {                 ThreadLocalScope threadLocalScope = (ThreadLocalScope) lingkup;                 threadLocalScope.exit ();             }             kembali nol;         }     }

Pastikan Anda memanggil ini setelah metode telah dipanggil dan menutup koneksi. Coba ini untuk melihat apakah ini berfungsi.


0
2018-03-23 21:27



Silakan periksa solusi yang saya berikan: Transaksi dengan Guice dan JDBC - Diskusi Solusi

ini hanyalah versi dasar dan pendekatan sederhana. tetapi berfungsi dengan baik untuk menangani transaksi dengan Guice dan JDBC.


0
2018-02-04 21:57