diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java index f09f9e88..f8ac85b7 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java @@ -27,6 +27,7 @@ import com.mybatisflex.core.table.TableInfoFactory; import com.mybatisflex.core.util.*; import org.apache.ibatis.annotations.*; import org.apache.ibatis.builder.annotation.ProviderContext; +import org.apache.ibatis.cursor.Cursor; import java.io.Serializable; import java.util.*; @@ -442,6 +443,17 @@ public interface BaseMapper { return list; } + /** + * 根据 query 来构建条件查询游标数据 Cursor + * 该方法必须在事务中才能正常使用,非事务下无法获取数据 + * + * @param queryWrapper 查询条件 + * @return 游标数据 Cursor + */ + @SelectProvider(type = EntitySqlProvider.class, method = "selectListByQuery") + Cursor selectCursorByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper); + + /** * 根据 query 来构建条件查询数据列表,要求返回的数据为 asType diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java index 5c700fa0..ce58b321 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexConfiguration.java @@ -34,6 +34,7 @@ import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.*; import org.apache.ibatis.session.*; @@ -93,6 +94,17 @@ public class FlexConfiguration extends Configuration { } } + + @Override + public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement + , RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { +// ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, +// resultHandler, boundSql, rowBounds); + ResultSetHandler resultSetHandler = new FlexResultSetHandler(executor, mappedStatement, parameterHandler, + resultHandler, boundSql, rowBounds); + return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); + } + /** * 替换为 FlexStatementHandler,主要用来为实体类的多主键做支持、和数据审计 * FlexStatementHandler 和 原生的 RoutingStatementHandler 对比,没有任何性能影响 diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexResultSetHandler.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexResultSetHandler.java new file mode 100644 index 00000000..44c1b49f --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexResultSetHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mybatisflex.core.mybatis; + +import com.mybatisflex.core.transaction.TransactionContext; +import org.apache.ibatis.cursor.Cursor; +import org.apache.ibatis.cursor.defaults.DefaultCursor; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.resultset.DefaultResultSetHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Iterator; + +public class FlexResultSetHandler extends DefaultResultSetHandler { + + public FlexResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql, RowBounds rowBounds) { + super(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); + } + + @Override + public Cursor handleCursorResultSets(Statement stmt) throws SQLException { + return new FlexCursor<>(super.handleCursorResultSets(stmt)); + } + + static class FlexCursor extends DefaultCursor { + + private Cursor originalCursor; + + public FlexCursor(Cursor cursor) { + super(null, null, null, null); + this.originalCursor = cursor; + TransactionContext.holdCursor(cursor); + } + + @Override + public void close() { + //非事务场景下,通过 releaseCursor 对 cursor 进行 close + if (TransactionContext.getXID() == null) { + TransactionContext.releaseCursor(); + } + //else 在事务的场景下,由事务主动关闭 + } + + @Override + public boolean isOpen() { + return originalCursor.isOpen(); + } + + @Override + public boolean isConsumed() { + return originalCursor.isConsumed(); + } + + @Override + public int getCurrentIndex() { + return originalCursor.getCurrentIndex(); + } + + @Override + public Iterator iterator() { + try { + return originalCursor.iterator(); + } catch (IllegalStateException e) { + if (TransactionContext.getXID() == null) { + throw new IllegalStateException(e.getMessage() + " Cursor must use in transaction."); + } + throw e; + } + } + } +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexSqlSessionFactoryBuilder.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexSqlSessionFactoryBuilder.java index 2502e236..9a0f43d8 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexSqlSessionFactoryBuilder.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/mybatis/FlexSqlSessionFactoryBuilder.java @@ -37,7 +37,7 @@ public class FlexSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder { @Override public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { - // 需要 mybaits v3.5.13+ + // 需要 mybatis v3.5.13+ // https://github.com/mybatis/mybatis-3/commit/d7826d71a7005a8b4d4e0e7a020db0f6c7e253a4 XMLConfigBuilder parser = new XMLConfigBuilder(FlexConfiguration.class, reader, environment, properties); return build(parser.parse()); @@ -59,7 +59,7 @@ public class FlexSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder { @Override public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { - // 需要 mybaits v3.5.13+ + // 需要 mybatis v3.5.13+ // https://github.com/mybatis/mybatis-3/commit/d7826d71a7005a8b4d4e0e7a020db0f6c7e253a4 XMLConfigBuilder parser = new XMLConfigBuilder(FlexConfiguration.class, inputStream, environment, properties); return build(parser.parse()); diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionContext.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionContext.java index 599da7a6..4c0920b1 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionContext.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionContext.java @@ -16,11 +16,16 @@ package com.mybatisflex.core.transaction; +import org.apache.ibatis.cursor.Cursor; + +import java.io.IOException; + public class TransactionContext { private TransactionContext() {} private static final ThreadLocal XID_HOLDER = new ThreadLocal<>(); + private static final ThreadLocal> CURSOR_HOLDER = new ThreadLocal<>(); public static String getXID() { return XID_HOLDER.get(); @@ -28,10 +33,29 @@ public class TransactionContext { public static void release() { XID_HOLDER.remove(); + releaseCursor(); } - public static void hold(String xid) { + public static void releaseCursor() { + try { + Cursor cursor = CURSOR_HOLDER.get(); + if (cursor != null){ + try { + cursor.close(); + } catch (IOException e) { + } + } + }finally { + CURSOR_HOLDER.remove(); + } + } + + public static void holdXID(String xid) { XID_HOLDER.set(xid); } + public static void holdCursor(Cursor cursor) { + CURSOR_HOLDER.set(cursor); + } + } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionalManager.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionalManager.java index 85b91198..f5cb0389 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionalManager.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/transaction/TransactionalManager.java @@ -113,7 +113,7 @@ public class TransactionalManager { } finally { //恢复上一级事务 if (currentXID != null) { - TransactionContext.hold(currentXID); + TransactionContext.holdXID(currentXID); } } } @@ -147,7 +147,7 @@ public class TransactionalManager { public static String startTransactional() { String xid = UUID.randomUUID().toString(); - TransactionContext.hold(xid); + TransactionContext.holdXID(xid); return xid; } diff --git a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java index aa5e02d2..96e1b7fa 100644 --- a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java +++ b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java @@ -46,7 +46,7 @@ public class FlexTransactionManager extends AbstractPlatformTransactionManager { @Override protected void doResume(Object transaction, Object suspendedResources) throws TransactionException { String xid = (String) suspendedResources; - TransactionContext.hold(xid); + TransactionContext.holdXID(xid); } @Override diff --git a/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/EntityTestStarter.java b/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/EntityTestStarter.java index aae6fe96..350b0b58 100644 --- a/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/EntityTestStarter.java +++ b/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/EntityTestStarter.java @@ -19,13 +19,16 @@ import com.mybatisflex.core.MybatisFlexBootstrap; import com.mybatisflex.core.audit.AuditManager; import com.mybatisflex.core.audit.ConsoleMessageCollector; import com.mybatisflex.core.audit.MessageCollector; -import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.row.Db; +import org.apache.ibatis.cursor.Cursor; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import javax.sql.DataSource; +import java.util.function.Supplier; + import static com.mybatisflex.test.table.AccountTableDef.ACCOUNT; import static com.mybatisflex.test.table.ArticleTableDef.ARTICLE; @@ -138,8 +141,30 @@ public class EntityTestStarter { // // List articleDTOS = accountMapper.selectListByQueryAs(asWrapper, ArticleDTO.class); // System.out.println(articleDTOS); - Page paginate = accountMapper.paginateAs(Page.of(1, 10), asWrapper, ArticleDTO01.class); - System.out.println(paginate); +// Page paginate = accountMapper.paginateAs(Page.of(1, 10), asWrapper, ArticleDTO01.class); +// System.out.println(paginate); + + Db.tx(new Supplier() { + @Override + public Boolean get() { + Cursor accounts = accountMapper.selectCursorByQuery(asWrapper); + System.out.println(accounts.isOpen()); + for (Account account : accounts) { + System.out.println(accounts.isOpen()); + System.out.println(account); + } + System.out.println(accounts.isOpen()); + return true; + } + }); + +// Cursor accounts = accountMapper.selectCursorByQuery(asWrapper); +// System.out.println(accounts.isOpen()); +// for (Account account : accounts) { +// System.out.println(accounts.isOpen()); +// System.out.println(account); +// } +// System.out.println(accounts.isOpen()); // QueryWrapper queryWrapper = new QueryWrapper();