33 votes

Guice, JDBC et gestion des connexions aux bases de données

Je suis à la recherche pour créer un exemple de projet lors de l'apprentissage de Guice qui utilise JDBC pour lire/écrire dans une base de données SQL. Cependant, après des années d'utilisation de Printemps et de le laisser s'abstraire la gestion des connexions et des transactions, et j'ai du mal à travailler il notre sur le plan conceptuel.

J'aimerais avoir un service qui démarre et arrête l'opération et les appels de nombreux dépôts de réutiliser la même connexion et de participer dans la même transaction. Mes questions sont les suivantes:

  • Où dois-je créer ma source de données?
  • Comment puis-je donner les dépôts à l'accès à la connexion? (ThreadLocal?)
  • Meilleure façon de gérer la transaction (la Création d'un Intercepteur pour une annotation?)

Le code ci-dessous montre comment je pourrais le faire au Printemps. Le JdbcOperations injecté dans chaque référentiel d'avoir accès à la connexion associée à la transaction active.

Je n'ai pas été en mesure de trouver de nombreux tutoriels qui couvrent ce, au-delà de celles qui montrent la création d'intercepteurs pour les transactions.

Je suis heureux de continuer à utiliser le Printemps comme il se travaille très bien dans mes projets, mais j'aimerais savoir comment le faire, dans le plus pur Guice et JBBC (Pas de JPA/Hibernate/Warp/Réutilisation de Printemps)

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

31voto

Michael Barker Points 8234

Si votre base de données changent rarement, vous pouvez utiliser la source de données est livré avec la base de données du pilote JDBC et d'isoler les appels à la 3ème partie de la bibliothèque dans un fournisseur (Mon exemple utilise celui fourni par le H2 dataabse, mais tous les fournisseurs JDBC devrait en avoir un). Si vous changez de mise en œuvre de la source de données (par exemple, c3PO, Apache DBCP, ou celui fourni par le serveur d'application conteneur), vous pouvez simplement écrire un nouveau Fournisseur de services de mise en œuvre pour obtenir la source de données à partir de l'endroit le plus approprié. Ici j'ai utiliser un singleton de portée pour permettre à la source de données de l'instance, à partager entre les classes qui dépendent de lui (nécessaire pour la mise en commun).

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

Pour gérer les transactions une Transaction Conscient de la source de données doit être utilisé. Je ne recommande pas la mise en œuvre de cette opération manuellement. En utilisant quelque chose comme warp-persistent ou un conteneur fourni la gestion des transactions, cependant, il ressemblerait à quelque chose comme ceci:

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

2voto

Eelco Points 1411

Je voudrais utiliser quelque chose comme c3po pour créer des sources de données directement. Si vous utilisez ComboPooledDataSource vous avez seulement besoin de l'instance (la mise en commun est faite sous les couvertures), que vous pouvez lier directement ou par l'intermédiaire d'un fournisseur.

Ensuite, j'ai créer un intercepteur en plus de cela, celui qui par exemple ramasse @Transactional, gère une connexion et commit/ rollback. Vous pourriez faire la Connexion injectable en tant que bien, mais vous devez assurez-vous de fermer les connexions quelque part pour leur permettre d'être vérifié dans la piscine à nouveau.

0voto

Kartik Points 862
  1. Pour injecter une source de données, vous n'avez probablement pas besoin d'être lié à une source de données unique instance depuis la base de données vous connecter à des fonctions dans l'url. À l'aide de Guice, il est possible de forcer les programmeurs à fournir une liaison à une source de données de mise en œuvre (lien) . Cette source de données peut être injecté dans une ConnectionProvider de retour d'une source de données.

  2. La connexion doit être dans un thread de portée locale. Vous pouvez même mettre en place votre fil portée locale , mais de tous les threads des connexions locales doivent être fermés & supprimé de ThreadLocal objet après commit ou rollback pour éviter une fuite de mémoire. Après le piratage autour, j'ai trouvé que vous avez besoin d'un crochet à l'Injecteur de l'objet pour le supprimer ThreadLocal éléments. Un injecteur peut facilement être injecté dans votre Guice AOP interceptor, quelque chose comme ceci:

 protected void visitThreadLocalScope(Injecteur injecteur, 
 DefaultBindingScopingVisitor visiteur) {
 si (injecteur == null) {
retour;
}

 (Carte.D'entrée, la Liaison de l'entrée>: 
 injecteur.getBindings().entrySet()) {
 finale de Liaison de liaison = entrée.getValue();
 // Pas intéressé dans la valeur de retour encore.
de liaison.acceptScopingVisitor(visiteur);
 } 
}

/**
 * Défaut de mise en œuvre qui sort le fil portée locale. C'est 
 * il est essentiel de les nettoyer et de prévenir toute fuite de mémoire.
 * 
 * 

Le champ d'application est seulement visité le forum le champ d'application est une sous-classe ou un * instance de {@link ThreadLocalScope}. */ private static final class ExitingThreadLocalScopeVisitor s'étend DefaultBindingScopingVisitor { @Override public Void visitScope(Portée) { // ThreadLocalScope est la portée personnalisée. si (ThreadLocalScope.classe.isAssignableFrom(portée.getClass())) { ThreadLocalScope threadLocalScope = (ThreadLocalScope) champ d'application; threadLocalScope.exit(); } return null; } }

Assurez-vous d'appeler cette après la méthode a été invoquée et la fermeture de la connexion. Essayer ce pour voir si cela fonctionne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X