如何使数据访问对象非阻塞?

Posted

技术标签:

【中文标题】如何使数据访问对象非阻塞?【英文标题】:How to make Data Access Objects non-blocking? 【发布时间】:2016-06-17 17:26:06 【问题描述】:

我正在学习数据访问对象模式,它提供对数据源(例如数据库)的访问。 This answer另一个问题提供了以下示例:

interface EmployeeDAO 
    List<Employee> findAll();
    List<Employee> findById();
    List<Employee> findByName();
    boolean insertEmployee(Employee employee);
    boolean updateEmployee(Employee employee);
    boolean deleteEmployee(Employee employee);

我在互联网上的其他答案和文章中看到了类似的例子。让我感到困惑的是,读取和写入数据库通常需要一段时间,在这种情况下,据我所知,这些示例(尤其是 find...() 的示例)不会很实用。也就是说,在find...() 调用期间阻塞可能不是我们想要的行为。

我认为使用诸如void EmployeeFound(Employee employee) 之类的方法创建一个Listener 接口(EmployeeDAO.Listener) 可能是有意义的,但我很惊讶我之前在DAO 示例中没有看到这一点。我想知道我是否只是不了解数据访问对象和/或是否缺少更明显的方法。

【问题讨论】:

【参考方案1】:

通常有许多不同的选择/方法:

    阻止就像您展示的 API。这是一个使用起来非常简单的 API,并且仍然可以通过在多线程应用程序中调用 API 来实现并行性。

    接收/注册处理程序在异步操作中。这有时与阻塞 API 一起提供(事实上,可以通过生成后台线程然后在最后调用处理程序来实现阻塞 API)。

    返回 Future 或 ListenableFuture 对象,这使得接口更符合 Java 风格(通过在返回类型位置返回数据),但代表最终结果,而不是立即可用的结果。 Future 可用于阻塞或非阻塞。

我个人的建议是:

interface EmployeeDatabase 
   interface StringWhereClause 
     ListQueryBuilder is(String value);
     ListQueryBuilder contains(String value);
     ListQueryBUilder matchesRegex(String regex);
   

   interface IntWhereClause 
     ListQueryBuilder is(int value);
     ListQueryBuilder isInRange(int min, int max);
     ListQueryBuilder isGreaterThan(int value);
     ListQueryBUilder isLessThan(int value);
     ListQueryBuilder isGreaterThanOrEqualTo(int value);
     ListQueryBUilder isLessThanOrEqualTo(int value);
   
   // ... matchers for other types of properties ...

   interface ListQueryBuilder 
      // Generic restrict methods
      StringWhereClause whereStringProperty(String propertyName);
      IntWhereClause whereIntProperty(String propertyName);
      // ...

      // Named restrict methods
      StringWhereClause whereName();
      StringWhereClause whereJobTitle();
      IntWhereClause whereEmployeeNumber();
      // ...

      ListQueryBuilder limit(long maximumSize);
      ListQueryBuilder offset(long index);

      ResultSet<Employee> fetch();
   

   ListQueryBuilder list();
   ListenableFuture<Employee> getById(Key key);
   ListenableFuture<KeyOrError> add(Employee employee);
   ListenableFuture<Status> update(Key key, Employee employee);
   ListenableFuture<Status> delete(Key key);

与:

 interface ResultSet<T> 
    Iterable<T> asIterable();
    // ... other methods ...
 

 interface KeyOrError 
    boolean isError();
    boolean isKey();
    Key getKey();
    Throwable getError();
 

 interface Status 
   boolean isOk();
   boolean isError();
   Throwable getError();
   void verifyOk();
 

基本上,这个想法是插入到数据库中返回一个 Key 对象(如果不成功,则返回一个错误)。该键可用于检索、删除或更新数据库中的条目。这些操作(添加、更新、删除和 getById)都有一个结果,在这种情况下,使用 ListenableFuture&lt;T&gt; 而不是类型 T;这个未来对象允许您阻塞(通过在未来对象上调用.get())或异步检索对象(通过注册一个回调以在结果准备好时调用)。

对于 list-y 操作,可以通过多种不同方式对列表进行过滤、子选择、排序等。为了防止各种不同重载的组合爆炸,我们使用 builder pattern 来允许这些不同的限制以多种组合应用。简而言之,在调用fetch() 导致执行列表查询并返回@987654329 之前,构建器接口提供了一种方法来添加零个或多个选项(排序、过滤器、限制等)以应用检索操作@。此操作返回ResultSet 而不是ListenableFuture,因为结果不是一次全部返回(例如,它们可能以流方式从数据库返回); ResultSet 实际上是一个与ListenableFuture 具有相似行为的接口,但用于项目列表(其中项目可能在不同时间准备好)。为方便起见,重要的是有一种方法可以轻松地迭代 ResultSet 的内容(例如,通过为 ResultSet 提供一个Iterable 适配器);但是,您可能还想添加其他方法,允许您对 ResultSet 执行其他类型的异步处理;例如,您可能希望 ListenableFuture&lt;T&gt; reduce(T initialValue, ReduceFunction&lt;T&gt; reducer) 聚合结果集中的元素并提供代表最终完成的未来对象。

【讨论】:

谢谢!您介意在您的示例中添加几行解释吗?【参考方案2】:

您在上面的接口中实现的方法将是简单的 sql 查询。

查找所有内容将是“从表中选择 *” 按 id 查找将是“从 id = :id 的表中选择 *”(这是表的主键 - 并已编入索引)。

我在一个应用程序中工作,我们每天执行数百万条插入语句,而且我们不担心阻塞。如果您使用 java 并使用 Spring 框架,那么有一些库可以为您处理所有这些。查看 java.persistence 中的 EntityManager 和 TransactionManager。

【讨论】:

以上是关于如何使数据访问对象非阻塞?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 UdpClient 非阻塞

Lua 对空 FIFO 的非阻塞读访问

IO模型介绍 以及同步异步阻塞非阻塞的区别

java基础篇之nio与aio

Netty知识点总结

简单测试Java线程安全中阻塞同步与非阻塞同步性能