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";
  }
}
33
задан BalusC 27 February 2010 в 15:23
поделиться

3 ответа

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

Для обработки транзакций следует использовать источник данных с поддержкой транзакций. Я бы не рекомендовал реализовывать это вручную. Используя что-то вроде 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) {}
            }
        }
    }
}
31
ответ дан 27 November 2019 в 19:28
поделиться

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

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

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

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

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

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

0
ответ дан 27 November 2019 в 19:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: