【问题标题】:Embedded Derby in OSGi, creating multiple connection using connection pool在 OSGi 中嵌入 Derby,使用连接池创建多个连接
【发布时间】:2015-02-14 21:11:48
【问题描述】:

我想创建一个可以访问底层 Embedded derby 数据库的类的实例,并使用声明性服务将此类传递给绑定到我的数据库包的每个包。

我在 derby 文档中看到,为多个线程共享一个连接有很多陷阱。所以我想为我正在创建的类的每个实例创建一个连接。由于我只想要一种非常简单的方法来创建多个连接并管理它们,因此使用“MiniConnectionPoolManager”here 似乎是一个不错的选择。 derby的示例代码如下:

org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource dataSource = new org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource();
dataSource.setDatabaseName("c:/temp/testDB");
dataSource.setCreateDatabase("create");
MiniConnectionPoolManager poolMgr = new MiniConnectionPoolManager(dataSource, maxConnections);
...
Connection connection = poolMgr.getConnection();
...
connection.close();

但是文档并没有涵盖很多东西,而且我是使用数据库的初学者。我的问题是:

  1. 当我创建一个需要数据库连接来执行插入、更新和其他操作的新类时。我应该传递 'poolMgr' 并从新创建的类中调用 poolMgr.getConnection() 吗?

  2. 我应该何时关闭此连接?我不知道捆绑(用户)将使用新类多久,所以我应该将新创建的连接保存在私有全局变量中并强制用户执行注销类,然后我可以关闭连接吗?或者我应该在我的数据库包被停用时关闭所有连接。

对于管理访问一个数据库的不同类的其他建议也表示赞赏。提前谢谢你。

编辑:

只要应用程序正在运行,我的数据库包中的主类就始终处于活动状态。请求新类(执行数据库操作)实例的包来来去去。而且由于它将部署在嵌入式系统中,我只能使用占用空间小的应用程序。

【问题讨论】:

    标签: java database osgi database-connection derby


    【解决方案1】:

    您应该在需要时从连接池中获取连接,并尽快关闭连接。重用连接是连接池的工作,而不是你的。

    换句话说:在您的消费者捆绑包被停用之前,不要让连接保持活动状态。

    连接池通常实现 DataSource 接口,您应该通过它使用连接池。在这种情况下,您可以轻松替换池实现而无需更改代码。例如:

    @Component
    public class MyComponent {
    
        // Connection pool based DataSource
        @Reference
        DataSource dataSource;
    
        public void myFunction() {
            try (Connection c = dataSource.getConnection()) {
                // Database operations
            } catch (SQLException e) {
                // TODO
            }
        }
    }
    

    当您发现自己多次重复相同的代码(获取连接、捕获 SQLException)时,您可以编写一个接受函数式接口的简单组件。例如:

    @Component
    @Service
    public class SQLHelper {
    
        @Reference // This is a connection pool DataSource
        private DataSource dataSource;
    
        public <R> R execute(Callback<R> callback) {
            try (Connection c = dataSource.getConnection()) {
                return callback.call(c);
            } catch (SQLException e) {
                throw new UncheckedSQLException(e);
            }
        }
    }
    

    您的功能界面如下所示:

    public interface Callback<R> {
    
        R call(Connection connection);
    
    }
    

    你会这样使用它:

    sqlHelper.execute((Connection c) -> {
        // Do some stuff with the connection
    });
    

    使用交易

    如果你想使用原子事务,我建议你应该使用 org.apache.derby.jdbc.EmbeddedXADataSourceorg.apache.commons.dbcp.managed.BasicManagedDataSource 来自 commons-dbcp。之后就可以通过JTA处理事务了。

    很难直接使用 JTA API。您应该选择一个可以帮助您传播交易的库。

    基于声明式服务的小指南:

    • 将 derby jar 安装到您的 OSGi 容器中
    • 同时安装pax-derby 包!通过这样做,您将拥有一个 DataSourceFactory OSGi 服务
    • 安装 everit-dsf-bundle 及其依赖项!您将看到两个新的 DS 组件。通过 web 控制台为名为 XADataSource 的配置创建一个配置!所有配置选项都有说明。
    • 在 OSGi 容器中安装 JTA 事务管理器!你有几个选择。我
    • 安装 everit-commons-dbcp-component 及其依赖项!您将看到两个新的 DS 组件。在 webconsole 中配置 Managed 并将之前创建的 XADataSource 设置为目标!如果您在同一事务的范围内请求并关闭连接,事务池将负责提供相同的连接。 通常使用嵌入 Geronimo TM 的 Aries 事务管理器。
    • everit-transaction-helper 安装到您的OSGi 容器中!您将看到一个带有 TransactionHelper 接口的新 OSGi 服务(由可配置的 DS 组件提供)。

    现在您可以编写代码了。您的组件类似于以下内容:

    @Component
    @Service
    public class MyComponent {
    
        @Reference
        private DataSource dataSource;
    
        @Reference
        private TransactionHelper th;
    
        public void myFunction() {
            th.required(() -> {
                try (Connection c = dataSource.getConnection()) {
                    // My SQL statements
                } catch (SQLException e) {
                    // TODO
                }
            }
        }
     }
    

    如果您不需要事务处理,您可以:

    • 使用标准 EmbeddedDataSource
    • 使用任何非事务性连接池
    • 跳过安装 TransactionManager 和 TransactionHelper 包
    • 从代码中跳过 TransactionHelper 的使用

    http://cookbook.everit.org/persistence/index.html 上提供了更复杂的指南(还负责创建模式并使用基于 OO 的查询)。

    更新

    您不必为每个 SQL 语句都建立一个连接。您应该获得一个连接,在“瞬间”内执行尽可能多的 SQL 语句,然后在连接上调用 close。

    • 如果你必须同时运行三个 SQL 语句,你应该请求一个连接,执行三个 SQL 语句,然后在连接上调用 close
    • 如果您在从池中请求的同一函数中关闭请求的连接,则您可能做对了。您可以调用将连接作为参数传递的其他函数,但它们应该只使用它来运行 SQL 语句而不是返回。
    • 您不应该保持连接并等待另一个用户操作。这就是连接池的工作。当您对池提供的连接调用 close 时,该连接不会物理关闭,而只会检索到池中。
    • 您应该将连接对象保存在局部变量中。如果你为你的连接对象使用一个成员变量,你应该怀疑你的代码有问题(唯一的例外是如果你将连接传递给一个存在很短时间的对象并且该对象将连接保存在一个成员中变量以获得更清晰的代码)。
    • 请注意,如果您使用 Java 6 或更早版本,则应在 finally 块中关闭连接以避免未关闭的连接。

    MiniConnectionPoolManager 可能是嵌入式设备的绝佳解决方案,因为它确实是“迷你”的。唯一的问题是它没有实现 DataSource 接口,因此您的业务代码应该直接使用 MiniCPM 类。通过这样做,如果您发现错误或以后需要更复杂的池,则切换到其他连接池将变得更加困难。

    如果您决定使用 MiniCPM,我建议您应该编写一个实现 DataSource 并将 getConnection() 函数委托给 MiniCPM 实例的组件。例如:

    @Component
    @Service
    public class MiniCPMDataSourceComponent implements DataSource {
    
        @Reference
        protected ConnectionPoolDataSource cpDataSource;
    
        private MiniConnectionPoolManager wrapped;
    
        @Activate
        public void activate() {
            this.wrapped = new MiniConnectionPoolManager(cpDataSource);
        }
    
        @Override
        public Connection getConnection() {
            return wrapped.getConnection();
        }
    
        @Override
        public Connection getConnection(String user, String password) {
            throw new UnsupportedOperationException();
        }
    
        @Deactivate
        public void deactivate() {
            wrapped.dispose();
        }
    }
    

    您可以使用最大连接数和超时(MiniCPM 支持)等配置可能性来装饰此组件。如果您使用该组件提供的服务,您将能够在不更改业务代码的情况下切换连接池。此外,您的业务捆绑包不会直接连接到 MiniCPM。

    【讨论】:

    • 感谢您的快速回复。但我对你的回答有点困惑。假设新类将有单独的方法来创建表,选择一行,显示内容,删除......,我是否应该在每个方法调用中从池中获取一个连接并在方法结束时关闭连接?还在每个方法中创建语句并关闭它们?
    • 关于池管理器,您是否建议我不应该使用 MiniConnectionPoolManager?因为目前 DataSource EmbeddedConnectionPoolDataSource 已传递给 MPManager 并且正在池管理器中处理创建连接。正如我在编辑中添加的那样,由于它将部署在嵌入式设备中,我试图避免使用更复杂的解决方案。还有关于使用 Lambda 表达式的建议。
    • 感谢您的更新,这回答了我的问题。而且我认为您提到的将应用程序和池管理器实现分开是非常重要的。所以我还不如添加一个包装器组件。因此,如果我猜对了,我应该提供一个带有接口“DataSource”的服务和包装组件类作为实现。并将此接口绑定到我正在创建的类的新实例中,并使用 DataSource 接口创建连接。
    • 我正在使用 Equinox,所以我不知道 felix 注释 @Reference 在那里做了什么,但我想也许可以在主类中添加一个 getEmbeddedConnectionPoolDataSource() 方法,然后我可以从包装器组件以获取 ConnectionPoolDataSource 的值。 (主要方法是最初创建数据库和数据源的方法)。我只是想知道你从哪里得到 cpDataSource 的值。顺便说一句,您的回答对更好地了解该主题有很大帮助,非常感谢!
    • 您好Zsoldos先生,我暂时把已回答的标记去掉,看看您是否会收到关于我在您更新后添加的最后两个问题的通知。我稍后会把标记放回去,
    猜你喜欢
    • 2014-10-28
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 2015-01-09
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多