Pregunta Guice, JDBC y la gestión de conexiones de bases de datos


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

32
2018-02-27 13:02


origen


Respuestas:


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 (por ejemplo, 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) {}
            }
        }
    }
}

28
2018-03-01 03:54



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 p. selecciona @Transactional, gestiona 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.


2
2018-02-28 09:12



  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. Usando Guice, es posible obligar a los programadores a proporcionar un enlace a una implementación de DataSource (enlazar) 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 puedes implementar tu Enrutar alcance local 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 fugas 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 (Inyector de inyector,
                        DefaultBindingScopingVisitor visitante) {
        if (inyector == nulo) {
            regreso;
        }

        for (Map.Entry, Binding> entry:
                injector.getBindings (). entrySet ()) {
            enlace vinculante final = entry.getValue ();
            // No estoy interesado en el valor de retorno todavía.
            binding.acceptScopingVisitor (visitante);
        }
    }

    / **
     * Implementación predeterminada que sale del alcance local del subproceso. Esto es
     * esencial para limpiar y prevenir cualquier fuga de memoria.
     *
     * 

El alcance solo se visita si el alcance es una subclase de o es un      * instancia de {@link ThreadLocalScope}.      * /     clase final privada estática ExitingThreadLocalScopeVisitor             extiende DefaultBindingScopingVisitor {         @Anular         public Void visitScope (Alcance del alcance) {                         // ThreadLocalScope es el alcance personalizado.             if (ThreadLocalScope.class.isAssignableFrom (scope.getClass ())) {                 ThreadLocalScope threadLocalScope = (ThreadLocalScope) scope;                 threadLocalScope.exit ();             }             devolver nulo;         }     }

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.


0
2018-03-23 21:27



Por favor, compruebe la solución que proporcioné: Transacciones con Guice y JDBC - Discusión sobre la solución

es solo una versión muy básica y un enfoque simple. pero funciona perfectamente para manejar transacciones con Guice y JDBC.


0
2018-02-04 21:57