Подтвердить что ты не робот

Guice, JDBC и управление соединениями с базой данных

Я хочу создать образец проекта, изучая Guice, который использует JDBC для чтения/записи в базу данных SQL. Однако, после нескольких лет использования Spring и позволяя ему абстрагироваться от обработки соединений и транзакций, я изо всех сил стараюсь работать над этим.

Я хотел бы иметь службу, которая запускает и останавливает транзакцию, и вызывает множество репозиториев, которые повторно используют одно и то же соединение и участвуют в одной транзакции. Мои вопросы:

  • Где я могу создать свой источник данных?
  • Как предоставить репозиториям доступ к соединению? (ThreadLocal?)
  • Лучший способ управления транзакцией (создание перехватчика для аннотации?)

В приведенном ниже коде показано, как я буду делать это в Spring. JdbcOperations, введенные в каждый репозиторий, будут иметь доступ к соединению, связанному с активной транзакцией.

Мне не удалось найти много учебников, которые охватывают это, помимо тех, которые показывают создание перехватчиков для транзакций.

Я доволен продолжением использования Spring, поскольку он очень хорошо работает в моих проектах, но я хотел бы знать, как это сделать в чистом Guice и JBBC (нет 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";
  }
}
4b9b3361

Ответ 1

Если ваша база данных изменяется редко, вы можете использовать источник данных, который поставляется с драйвером JDBC базы данных, и изолировать вызовы к сторонней библиотеке в провайдере (в моем примере используется тот, который предоставляется H2 dataabse, но все поставщики JDBC должен иметь один). Если вы перейдете на другую реализацию DataSource (например, c3PO, Apache DBCP или один, предоставленный контейнером сервера приложений), вы можете просто написать новую реализацию поставщика, чтобы получить источник данных из соответствующего места. Здесь я использую Singleton scope, чтобы позволить экземпляру 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) {}
            }
        }
    }
}

Ответ 2

Я бы использовал нечто вроде c3po для создания источников данных напрямую. Если вы используете ComboPooledDataSource, вам нужен только экземпляр (объединение выполняется под обложками), с которым вы можете напрямую связываться или через поставщика.

Тогда я бы создал перехватчик поверх этого, тот, который, например, выбирает @Transactional, управляет соединением и фиксацией/откатом. Вы также можете сделать инъекцию Connection, но вам нужно убедиться, что вы где-то закроете соединения, чтобы снова проверить их в пуле.

Ответ 3

  • Чтобы внедрить источник данных, вам, вероятно, не нужно привязываться к одному экземпляру источника данных, поскольку база данных, к которой вы подключаетесь к функциям в URL-адресе. Используя Guice, можно заставить программистов обеспечить привязку к реализации DataSource (ссылка). Этот источник данных может быть введен в ConnectionProvider для возврата источника данных.

  • Соединение должно быть в локальной области потока. Вы даже можете реализовать свою локальную область потока, но все потоковые локальные соединения должны быть закрыты и удалены из объекта ThreadLocal после операций фиксации или отката для предотвращения утечки памяти, После взлома я обнаружил, что для удаления объектов ThreadLocal вам нужно привязать объект Injector. Инжектор можно легко вводить в ваш перехватчик Guice AOP, что-то вроде этого:

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

Убедитесь, что вы вызываете это после вызова метода и закрытия соединения. Попробуйте это, чтобы увидеть, работает ли это.

Ответ 4

Пожалуйста, проверьте предоставленное мной решение: Транзакции с Guice и JDBC - Обсуждение решений

это просто базовая версия и простой подход. но он отлично работает, чтобы обрабатывать транзакции с Guice и JDBC.