ventajas desventajas conexion cerrar abrir java database jdbc guice

desventajas - cerrar conexion mysql java



Guice, JDBC y la gestiĆ³n de conexiones de bases de datos (4)

Estoy buscando crear un proyecto de muestra mientras aprendo Guice que usa JDBC para leer / escribir en una base de datos SQL. Sin embargo, después de años de utilizar Spring y dejar que se abstraiga el manejo de la conexión y las transacciones, estoy luchando por trabajarlo conceptualmente.

Me gustaría tener un servicio que inicia y detiene una transacción y llama a numerosos repositorios que reutilizan la misma conexión y participan en la misma transacción. Mis preguntas son:

  • ¿Dónde creo mi Datasource?
  • ¿Cómo le doy a los repositorios acceso a la conexión? (ThreadLocal?)
  • La mejor forma de administrar la transacción (¿Cómo crear un Interceptor para una anotación?)

El siguiente código muestra cómo haría esto en primavera. Las JdbcOperations inyectadas en cada repositorio tendrían acceso a la conexión asociada con la transacción activa.

No he podido encontrar muchos tutoriales que cubran esto, más allá de los que muestran la creación de interceptores para las transacciones.

Estoy contento de seguir utilizando Spring, ya que está funcionando muy bien en mis proyectos, pero me gustaría saber cómo hacerlo en Guice y JBBC puros (sin 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. Para inyectar una fuente de datos, probablemente no necesite estar vinculado a una sola instancia de origen de datos dado que la base de datos a la que se está conectando tiene funciones en la url. Con Guice, es posible obligar a los programadores a proporcionar un enlace a una implementación de DataSource ( link ). Esta fuente de datos se puede inyectar en un ConnectionProvider para devolver una fuente de datos.

  2. La conexión debe estar en un alcance local de subprocesos. Incluso puede implementar su alcance local de subprocesos, pero todas las conexiones locales de subprocesos deben cerrarse y eliminarse del objeto ThreadLocal después de las operaciones de confirmación o retrotracción para evitar pérdidas de memoria. Después de piratear, he encontrado que necesitas tener un gancho para el objeto Injector para eliminar los elementos de ThreadLocal. Se puede inyectar fácilmente un inyector en su interceptor Guice AOP, algo como esto:

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

Asegúrese de llamar a esto después de que se haya invocado el método y de cerrar la conexión. Prueba esto para ver si esto funciona.



Si su base de datos cambia con poca frecuencia, podría usar la fuente de datos que viene con el controlador JDBC de la base de datos y aislar las llamadas a la biblioteca de terceros en un proveedor (Mi ejemplo usa el proporcionado por H2 dataabse, pero todos los proveedores JDBC deben tener uno ) Si cambia a una implementación diferente de DataSource (p. Ej., C3PO, Apache DBCP, o una proporcionada por el contenedor del servidor de aplicaciones), puede simplemente escribir una nueva implementación de Proveedor para obtener la fuente de datos del lugar apropiado. Aquí he usado el alcance de singleton para permitir que la instancia de DataSource se comparta entre las clases que dependen de ella (necesaria para la puesta en común).

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

Para manejar transacciones, se debe usar una fuente de datos de Transaction Aware. No recomendaría implementar esto manualmente. Usando algo como warp-persist o una gestión de transacciones suministrada por un contenedor, se vería algo como esto:

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


Usaría algo como c3po para crear fuentes de datos directamente. Si usa ComboPooledDataSource, solo necesita una instancia (la agrupación se realiza bajo las cubiertas), a la que puede vincular directamente oa través de un proveedor.

Luego crearía un interceptor además de eso, uno que, por ejemplo, selecciona @Transactional, administra una conexión y confirma / revierte. También puede hacer que Connection sea inyectable, pero debe asegurarse de cerrar las conexiones en algún lugar para permitir que se vuelvan a registrar en el grupo.