SQLite不喜欢反应式编程?

SQLite不喜欢反应式编程?,sqlite,reactive-programming,rx-java,Sqlite,Reactive Programming,Rx Java,我已经在Netflix上玩了几个月了。反应式编程改变了我的整个编程方法。它真正带来了最好的函数式编程 然而,我想使用SQLite的反应式编程。David Moten编写了一篇关于JDBC集成RxJava的文章。但SQLite似乎有一个问题。它不像一个查询推送一个ResultSet,其中每个记录迭代都被转换成一个对象,并驱动另一个查询沿着链向下 假设我有两张桌子 CREATE TABLE TABLE_ONE ( ID INTEGER PRIMARY KEY

我已经在Netflix上玩了几个月了。反应式编程改变了我的整个编程方法。它真正带来了最好的函数式编程

然而,我想使用SQLite的反应式编程。David Moten编写了一篇关于JDBC集成RxJava的文章。但SQLite似乎有一个问题。它不像一个查询推送一个
ResultSet
,其中每个记录迭代都被转换成一个对象,并驱动另一个查询沿着链向下

假设我有两张桌子

CREATE TABLE TABLE_ONE (
    ID    INTEGER PRIMARY KEY
                  NOT NULL,
    VALUE INTEGER NOT NULL
);

CREATE TABLE TABLE_TWO (
    ID         INTEGER NOT NULL
                       PRIMARY KEY,
    FOREIGN_ID INTEGER NOT NULL
                       REFERENCES TABLE_ONE ([KEY]),
    VALUE      INTEGER NOT NULL
); 
我创建了一个monad,它执行一些插入父对象/选择父对象/插入子对象/选择子对象之类的操作

import com.github.davidmoten.rx.jdbc.ConnectionProviderFromUrl;
import com.github.davidmoten.rx.jdbc.Database;
import rx.Observable;

import java.sql.Connection;

public final class Test {

    public static void main(String[] args) {

        Connection con = new ConnectionProviderFromUrl("jdbc:sqlite:C:/Users/Thomas/test.db").get();
        Database db = Database.from(con);

        Observable<Integer> inputs = Observable.just(100,200,300);

        db.update("INSERT INTO TABLE_ONE (VALUE) VALUES (?)")
                .parameters(inputs)
                .returnGeneratedKeys()
                .getAs(Integer.class)
                .flatMap(k -> db.select("SELECT * FROM TABLE_ONE WHERE ID = ?")
                                .parameter(k)
                                .get(rs -> new Type1(rs.getInt("ID"), rs.getInt("VALUE")))
                ).flatMap(t1 -> db.update("INSERT INTO TABLE_TWO (FOREIGN_ID,VALUE) VALUES (?,?)")
                                .parameter(t1.id)
                                .parameter(t1.value)
                                .returnGeneratedKeys()
                                .getAs(Integer.class)
                ).flatMap(k -> db.select("SELECT * FROM TABLE_TWO WHERE ID = ?")
                                .parameter(k)
                                .get(rs -> new Type2(rs.getInt("ID"), rs.getInt("FOREIGN_ID"), rs.getInt("VALUE")))
                ).subscribe(System.out::println, Throwable::printStackTrace);

        db.close();
    }
    private static final class Type1 {
        private final int id;
        private final int value;
        private Type1(int id, int value) {
            this.id = id;
            this.value = value;
        }
    }
    private static final class Type2 {
        private final int id;
        private final int foreignId;
        private final int value;
        private Type2(int id, int foreignKey, int value) {
            this.id = id;
            this.foreignId = foreignKey;
            this.value = value;
        }
        @Override
        public String toString() {
            return "Type2{" +
                    "id=" + id +
                    ", foreignId=" + foreignId +
                    ", value=" + value +
                    '}';
        }
    }
}
每100、200、300个值都会自动执行这些操作,并且该链中的每个值都会执行4次更新/查询

但是,我得到一个
SQLITE\u中断
错误

java.sql.SQLException: [SQLITE_INTERRUPT]  Operation terminated by sqlite3_interrupt() (interrupted)
    at org.sqlite.core.DB.newSQLException(DB.java:890)
    at org.sqlite.core.DB.newSQLException(DB.java:901)
    at org.sqlite.core.DB.throwex(DB.java:868)
    at org.sqlite.jdbc3.JDBC3ResultSet.next(JDBC3ResultSet.java:93)
    at com.github.davidmoten.rx.jdbc.QuerySelectProducer.processRow(QuerySelectProducer.java:112)
    at com.github.davidmoten.rx.jdbc.QuerySelectProducer.requestSome(QuerySelectProducer.java:75)
但是第一项成功地推过了链,插入到两个表中,并打印,尽管我还有两个值(200和300)

我的理论是,当每个发出的项
O
从一个查询推送到下一个查询时,它会中断并取消上一个查询的迭代,如
X

QUERY OP 4----------------------O-
QUERY OP 3----------------O-----X-
QUERY OP 2------------O---X-------
QUERY OP 1-------O----X-----------
因此,第一个发出的项会通过,但它会留下一条中断查询的线索,以驱动当前项,因为查询迭代被终止,所以无法让下一个项成为
onNext()
'd

SQLite和RxJava的朋友们,你们中的任何一个能想出一种方法来解决这个问题吗?我是否可以配置SQLite设置来停止此中断?或者有没有一个RxJava组合技巧可以用来防止中断

我还通过上面的测试创建了一个简单的Git回购。

似乎与以下事实有关:使用SQLite,您不能同时为SELECT和ALL COMMIT设置一个未完成的游标,这在默认的自动提交模式下的INSERT和UPDATE之后是隐式的。这可以防止通过光标在SELECT生成的行上循环并执行插入。解决方法是简单地获取所有结果,然后循环它们。我不喜欢它


我不知道这个库,但是在查看了它的某些部分之后,我确信它使用了一个游标来懒散地获取结果,这就是导致问题的原因。您可以通过强制它获取每个选择的所有结果来解决这个问题。

虽然这肯定不理想,但丹的解释似乎证实了我所担心的。幸运的是,有办法解决这个问题。即使像用于此目的的
toList()
这样的操作符在某种程度上是Rx的反模式,它也比根本不使用Rx要好得多

在这里,我使用
toList().concatMap(Observable::from)
收集所有排放物,并在每个阶段之间再次将其展平

import com.github.davidmoten.rx.jdbc.ConnectionProviderFromUrl;
import com.github.davidmoten.rx.jdbc.Database;
import rx.Observable;

import java.sql.Connection;

public final class Test {

    public static void main(String[] args) {

        Connection con = new ConnectionProviderFromUrl("jdbc:sqlite:C:/Users/Thomas/test.db").get();
        Database db = Database.from(con);

        Observable<Integer> inputs = Observable.just(100,200,300);

        db.update("INSERT INTO TABLE_ONE (VALUE) VALUES (?)")
                .parameters(inputs)
                .returnGeneratedKeys()
                .getAs(Integer.class)
                .toList().concatMap(Observable::from)
                .concatMap(k -> db.select("SELECT * FROM TABLE_ONE WHERE ID = ?")
                                .parameter(k)
                                .get(rs -> new Type1(rs.getInt("ID"), rs.getInt("VALUE")))
                )
                .toList().concatMap(Observable::from)
                .concatMap(t1 -> db.update("INSERT INTO TABLE_TWO (FOREIGN_ID,VALUE) VALUES (?,?)")
                                .parameter(t1.id)
                                .parameter(t1.value)
                                .returnGeneratedKeys()
                                .getAs(Integer.class)
                ).toList().concatMap(Observable::from)
                .concatMap(k -> db.select("SELECT * FROM TABLE_TWO WHERE ID = ?")
                                .parameter(k)
                                .get(rs -> new Type2(rs.getInt("ID"), rs.getInt("FOREIGN_ID"), rs.getInt("VALUE")))
                ).subscribe(System.out::println, Throwable::printStackTrace);

        db.close();
    }
    private static final class Type1 {
        private final int id;
        private final int value;
        private Type1(int id, int value) {
            this.id = id;
            this.value = value;
        }
    }
    private static final class Type2 {
        private final int id;
        private final int foreignId;
        private final int value;
        private Type2(int id, int foreignKey, int value) {
            this.id = id;
            this.foreignId = foreignKey;
            this.value = value;
        }
        @Override
        public String toString() {
            return "Type2{" +
                    "id=" + id +
                    ", foreignId=" + foreignId +
                    ", value=" + value +
                    '}';
        }
    }
}
导入com.github.davidmoten.rx.jdbc.ConnectionProviderFromUrl;
导入com.github.davidmoten.rx.jdbc.Database;
进口接收。可观察;
导入java.sql.Connection;
公开期末考试{
公共静态void main(字符串[]args){
Connection con=new ConnectionProviderFromUrl(“jdbc:sqlite:C:/Users/Thomas/test.db”).get();
数据库db=数据库.from(con);
可观察的输入=可观察的刚度(100200300);
db.update(“在表中插入一个(值)值(?))
.参数(输入)
.returnGeneratedKeys()
.getAs(Integer.class)
.toList().concatMap(可观察::来自)
.concatMap(k->db.select(“从表中选择*,其中ID=?”)
.参数(k)
.get(rs->newtype1(rs.getInt(“ID”)、rs.getInt(“VALUE”))
)
.toList().concatMap(可观察::来自)
.concatMap(t1->db.update(“在表中插入两个(外来ID,值)值(?)”)
.参数(t1.id)
.参数(t1.值)
.returnGeneratedKeys()
.getAs(Integer.class)
).toList().concatMap(可观察::来自)
.concatMap(k->db.select(“从表中选择*,其中ID=?”)
.参数(k)
.get(rs->newtype2(rs.getInt(“ID”)、rs.getInt(“FOREIGN_ID”)、rs.getInt(“VALUE”))
).subscribe(System.out::println,Throwable::printStackTrace);
db.close();
}
私有静态最终类Type1{
私有最终int id;
私有最终整数值;
私有类型1(int-id,int-value){
this.id=id;
这个值=值;
}
}
私有静态最终类Type2{
私有最终int id;
私密的终审法院;
私有最终整数值;
私有类型2(int-id、int-foreignKey、int-value){
this.id=id;
this.foreignd=foreignKey;
这个值=值;
}
@凌驾
公共字符串toString(){
返回“Type2{”+
“id=”+id+
“,异化=“+异化+
“,value=“+value+
'}';
}
}
}

是的,如果我使用RxJava的
toList()
last()
,或者其他一些在进入下一阶段之前收集所有记录的阻塞操作符,这就是我在GitHub问题中讨论的解决方法。问题是,这是被动世界中的一种反模式,人们对此表示不满。我可以这样做,但希望有一个非阻塞的方式来实现这一点。这就是为什么我不喜欢它。但修复它需要对SQLite的工作方式进行深刻的改变。它需要能够为每个查询保留一个数据库副本,以便SELECT可以在COMMIT替换它时继续对其进行迭代。这几乎相当于MVCC/快照隔离的顺序
QUERY OP 4----------------------O-
QUERY OP 3----------------O-----X-
QUERY OP 2------------O---X-------
QUERY OP 1-------O----X-----------
import com.github.davidmoten.rx.jdbc.ConnectionProviderFromUrl;
import com.github.davidmoten.rx.jdbc.Database;
import rx.Observable;

import java.sql.Connection;

public final class Test {

    public static void main(String[] args) {

        Connection con = new ConnectionProviderFromUrl("jdbc:sqlite:C:/Users/Thomas/test.db").get();
        Database db = Database.from(con);

        Observable<Integer> inputs = Observable.just(100,200,300);

        db.update("INSERT INTO TABLE_ONE (VALUE) VALUES (?)")
                .parameters(inputs)
                .returnGeneratedKeys()
                .getAs(Integer.class)
                .toList().concatMap(Observable::from)
                .concatMap(k -> db.select("SELECT * FROM TABLE_ONE WHERE ID = ?")
                                .parameter(k)
                                .get(rs -> new Type1(rs.getInt("ID"), rs.getInt("VALUE")))
                )
                .toList().concatMap(Observable::from)
                .concatMap(t1 -> db.update("INSERT INTO TABLE_TWO (FOREIGN_ID,VALUE) VALUES (?,?)")
                                .parameter(t1.id)
                                .parameter(t1.value)
                                .returnGeneratedKeys()
                                .getAs(Integer.class)
                ).toList().concatMap(Observable::from)
                .concatMap(k -> db.select("SELECT * FROM TABLE_TWO WHERE ID = ?")
                                .parameter(k)
                                .get(rs -> new Type2(rs.getInt("ID"), rs.getInt("FOREIGN_ID"), rs.getInt("VALUE")))
                ).subscribe(System.out::println, Throwable::printStackTrace);

        db.close();
    }
    private static final class Type1 {
        private final int id;
        private final int value;
        private Type1(int id, int value) {
            this.id = id;
            this.value = value;
        }
    }
    private static final class Type2 {
        private final int id;
        private final int foreignId;
        private final int value;
        private Type2(int id, int foreignKey, int value) {
            this.id = id;
            this.foreignId = foreignKey;
            this.value = value;
        }
        @Override
        public String toString() {
            return "Type2{" +
                    "id=" + id +
                    ", foreignId=" + foreignId +
                    ", value=" + value +
                    '}';
        }
    }
}