`

hsqldb源码分析系列3 执行引擎分析 插入过程分析

 
阅读更多

  

   我们看看insert语句的执行过程,

  如果cs.isTransactionStatement()是true,则表示启动事务执行,

      

 public Result executeCompiledStatement(Statement cs, Object[] pvals) {

        Result r;

        if (abortTransaction) {
            rollback(false);

            return Result.newErrorResult(Error.error(ErrorCode.X_40001));
        }

        if (sessionContext.depth > 0) {
            if (sessionContext.noSQL.booleanValue()
                    || cs.isAutoCommitStatement()) {
                return Result.newErrorResult(Error.error(ErrorCode.X_46000));
            }
        }

        if (cs.isAutoCommitStatement()) {
            if (isReadOnly()) {
                return Result.newErrorResult(Error.error(ErrorCode.X_25006));
            }

            try {

                /** special autocommit for backward compatibility */
                commit(false);
            } catch (HsqlException e) {
                database.logger.logInfoEvent("Exception at commit");
            }
        }

        sessionContext.currentStatement = cs;

        boolean isTX = cs.isTransactionStatement();

        if (!isTX) {
            if (database.logger.getSqlEventLogLevel()
                    >= SimpleLog.LOG_NORMAL) {
                sessionContext.setDynamicArguments(pvals);
                database.logger.logStatementEvent(this, cs, pvals,
                                                  SimpleLog.LOG_NORMAL);
            }

            r                               = cs.execute(this);
            sessionContext.currentStatement = null;

            return r;
        }

        while (true) {
            actionIndex = rowActionList.size();

            database.txManager.beginAction(this, cs);

            cs = sessionContext.currentStatement;

            if (cs == null) {
                return Result.newErrorResult(Error.error(ErrorCode.X_07502));
            }

            if (abortTransaction) {
                rollback(false);

                sessionContext.currentStatement = null;

                return Result.newErrorResult(Error.error(ErrorCode.X_40001));
            }

            try {
                latch.await();
            } catch (InterruptedException e) {
                abortTransaction = true;
            }

            if (abortTransaction) {
                rollback(false);

                sessionContext.currentStatement = null;

                return Result.newErrorResult(Error.error(ErrorCode.X_40001));
            }

            database.txManager.beginActionResume(this);

            //        tempActionHistory.add("sql execute " + cs.sql + " " + actionTimestamp + " " + rowActionList.size());
            sessionContext.setDynamicArguments(pvals);

            if (database.logger.getSqlEventLogLevel()
                    >= SimpleLog.LOG_NORMAL) {
                database.logger.logStatementEvent(this, cs, pvals,
                                                  SimpleLog.LOG_NORMAL);
            }

            r             = cs.execute(this);
            lockStatement = sessionContext.currentStatement;

            //        tempActionHistory.add("sql execute end " + actionTimestamp + " " + rowActionList.size());
            endAction(r);

            if (abortTransaction) {
                rollback(false);

                sessionContext.currentStatement = null;

                return Result.newErrorResult(Error.error(r.getException(),
                        ErrorCode.X_40001, null));
            }

            if (redoAction) {
                redoAction = false;

                try {
                    latch.await();
                } catch (InterruptedException e) {
                    abortTransaction = true;
                }
            } else {
                break;
            }
        }

        if (sessionContext.depth == 0
                && (sessionContext.isAutoCommit.booleanValue()
                    || cs.isAutoCommitStatement())) {
            try {
                if (r.mode == ResultConstants.ERROR) {
                    rollback(false);
                } else {
                    commit(false);
                }
            } catch (Exception e) {
                sessionContext.currentStatement = null;

                return Result.newErrorResult(Error.error(ErrorCode.X_40001,
                        e));
            }
        }

        sessionContext.currentStatement = null;

        return r;
    }

   database.txManager.beginAction(this, cs); 事务执行开始阶段, 这里事务处理用简单的方式进行处理,

   事务处理有几种实现,MVCC,2PL两阶段提交,

   

   /**
     * add session to the end of queue when a transaction starts
     * (depending on isolation mode)
     */
    public void beginAction(Session session, Statement cs) {

        if (session.isTransaction) {
            return;
        }

        if (cs == null) {
            return;
        }

        writeLock.lock();

        try {
            if (cs.getCompileTimestamp()
                    < database.schemaManager.getSchemaChangeTimestamp()) {
                cs = session.statementManager.getStatement(session, cs);
                session.sessionContext.currentStatement = cs;

                if (cs == null) {
                    return;
                }
            }

            session.isPreTransaction = true;

            if (!isLockedMode && !cs.isCatalogLock()) {
                return;
            }

            beginActionTPL(session, cs);
        } finally {
            writeLock.unlock();
        }
    }

 锁整个表其实就是在当前的TractionManager对象中保存每个表的session对象,也就是每个表操作的每个session对象,这样就是session当前操作需要对这个表加锁,有读锁,写锁。

    void lockTablesTPL(Session session, Statement cs) {

        if (cs == null || session.abortTransaction) {
            return;
        }

        HsqlName[] nameList = cs.getTableNamesForWrite();

        for (int i = 0; i < nameList.length; i++) {
            HsqlName name = nameList[i];

            if (name.schema == SqlInvariants.SYSTEM_SCHEMA_HSQLNAME) {
                continue;
            }

            tableWriteLocks.put(name, session);
        }

        nameList = cs.getTableNamesForRead();

        for (int i = 0; i < nameList.length; i++) {
            HsqlName name = nameList[i];

            if (name.schema == SqlInvariants.SYSTEM_SCHEMA_HSQLNAME) {
                continue;
            }

            tableReadLocks.put(name, session);
        }
    }

  执行过程 r             = cs.execute(this);

   中间主要部分是StatementInsert类的这个方法

  

 Result getResult(Session session) {

        Result          resultOut          = null;
        RowSetNavigator generatedNavigator = null;
        PersistentStore store              = baseTable.getRowStore(session);
        int             count;

        if (generatedIndexes != null) {
            resultOut = Result.newUpdateCountResult(generatedResultMetaData,
                    0);
            generatedNavigator = resultOut.getChainedResult().getNavigator();
        }

        if (isSimpleInsert) {
            Type[] colTypes = baseTable.getColumnTypes();
            Object[] data = getInsertData(session, colTypes,
                                          insertExpression.nodes[0].nodes);

            return insertSingleRow(session, store, data);
        }

        RowSetNavigator newDataNavigator = queryExpression == null
                                           ? getInsertValuesNavigator(session)
                                           : getInsertSelectNavigator(session);

        count = newDataNavigator.getSize();

        if (count > 0) {
            insertRowSet(session, generatedNavigator, newDataNavigator);
        }

        if (baseTable.triggerLists[Trigger.INSERT_AFTER].length > 0) {
            baseTable.fireTriggers(session, Trigger.INSERT_AFTER,
                                   newDataNavigator);
        }

        if (resultOut == null) {
            resultOut = new Result(ResultConstants.UPDATECOUNT, count);
        } else {
            resultOut.setUpdateCount(count);
        }

        if (count == 0) {
            session.addWarning(HsqlException.noDataCondition);
        }

        return resultOut;
    }

 PersistentStore store              = baseTable.getRowStore(session);就是获取这个表对应的内存存储

    我们用的是内存数据库,则这里store是org.hsqldb.persist.RowStoreAVLMemory@681070这个对象,待会我们再分析,

     终于来到 insertSingleRow插入一条记录操作了

      

 

  好吧,插入一条记录没那么简单,需要触发触发器操作和外键约束检测

   

 Result insertSingleRow(Session session, PersistentStore store,
                           Object[] data) {

        if (baseTable.triggerLists[Trigger.INSERT_BEFORE_ROW].length > 0) {
            baseTable.fireTriggers(session, Trigger.INSERT_BEFORE_ROW, null,
                                   data, null);
        }

        baseTable.insertSingleRow(session, store, data, null);
        performIntegrityChecks(session, baseTable, null, data, null);

        if (session.database.isReferentialIntegrity()) {
            for (int i = 0, size = baseTable.fkConstraints.length; i < size;
                    i++) {
                baseTable.fkConstraints[i].checkInsert(session, baseTable,
                                                       data, true);
            }
        }

        if (baseTable.triggerLists[Trigger.INSERT_AFTER_ROW].length > 0) {
            baseTable.fireTriggers(session, Trigger.INSERT_AFTER_ROW, null,
                                   data, null);
        }

        if (baseTable.triggerLists[Trigger.INSERT_AFTER].length > 0) {
            baseTable.fireTriggers(session, Trigger.INSERT_AFTER,
                                   (RowSetNavigator) null);
        }

        return Result.updateOneResult;
    }

 

  /**
     *  Mid level method for inserting single rows. Performs constraint checks and
     *  fires row level triggers.
     */
    Row insertSingleRow(Session session, PersistentStore store, Object[] data,
                        int[] changedCols) {

        if (identityColumn != -1) {
            setIdentityColumn(session, data);
        }

        if (hasGeneratedValues) {
            setGeneratedColumns(session, data);
        }

        if (hasDomainColumns || hasNotNullColumns) {
            enforceRowConstraints(session, data);
        }

        if (isView) {

            // may have domain column
            return null;
        }

        Row row = (Row) store.getNewCachedObject(session, data, true);

        session.addInsertAction(this, store, row, changedCols);

        return row;
    }

 如果有递增列,则要处理加1操作

  

    /**
     * If there is an identity column in the table, sets
     * the value and/or adjusts the identiy value for the table.
     */
    protected void setIdentityColumn(Session session, Object[] data) {

        if (identityColumn != -1) {
            Number id = (Number) data[identityColumn];

            if (identitySequence.getName() == null) {
                if (id == null) {
                    id = (Number) identitySequence.getValueObject();
                    data[identityColumn] = id;
                } else {
                    identitySequence.userUpdate(id.longValue());
                }
            } else {
                if (id == null) {
                    id = (Number) session.sessionData.getSequenceValue(
                        identitySequence);
                    data[identityColumn] = id;
                }
            }

            if (session != null) {
                session.setLastIdentity(id);
            }
        }
    }

 setGeneratedColumns 处理有表达式的列数据生成规则,

    public void setGeneratedColumns(Session session, Object[] data) {

        if (hasGeneratedValues) {
            for (int i = 0; i < colGenerated.length; i++) {
                if (colGenerated[i]) {
                    Expression e = getColumn(i).getGeneratingExpression();
                    RangeIteratorBase range =
                        session.sessionContext.getCheckIterator(
                            getDefaultRanges()[0]);

                    range.currentData = data;
                    data[i]           = e.getValue(session, colTypes[i]);
                }
            }
        }
    }

 enforceRowConstraints(session, data); 处理列的约束,如果整数是否超过限制等

  

    /**
     *  Enforce max field sizes according to SQL column definition.
     *  SQL92 13.8
     */
    public void enforceRowConstraints(Session session, Object[] data) {

        for (int i = 0; i < columnCount; i++) {
            Type         type = colTypes[i];
            ColumnSchema column;

            if (hasDomainColumns && type.isDomainType()) {
                Constraint[] constraints =
                    type.userTypeModifier.getConstraints();

                column = getColumn(i);

                for (int j = 0; j < constraints.length; j++) {
                    constraints[j].checkCheckConstraint(session, this, column,
                                                        (Object) data[i]);
                }
            }

            if (colNotNull[i] && data[i] == null) {
                String     constraintName;
                Constraint c = getNotNullConstraintForColumn(i);

                if (c == null) {
                    if (ArrayUtil.find(this.primaryKeyCols, i) > -1) {
                        c = this.getPrimaryConstraint();
                    }
                }

                constraintName = c == null ? ""
                                           : c.getName().name;
                column         = getColumn(i);

                String[] info = new String[] {
                    constraintName, tableName.statementName,
                    column.getName().statementName
                };

                throw Error.error(null, ErrorCode.X_23502,
                                  ErrorCode.COLUMN_CONSTRAINT, info);
            }
        }
    }

 

Row row = (Row) store.getNewCachedObject(session, data, true); 申请内存空间

  

    public CachedObject getNewCachedObject(Session session, Object object,
                                           boolean tx) {

        int id;

        synchronized (this) {
            id = rowIdSequence++;
        }

        Row row = new RowAVL(table, (Object[]) object, id, this);

        if (tx) {
            RowAction action = new RowAction(session, table,
                                             RowAction.ACTION_INSERT, row,
                                             null);

            row.rowAction = action;
        }

        return row;
    }

             RowAction action = new RowAction(session, table,

                                             RowAction.ACTION_INSERT, row,

                                             null);

    表操作Represents the chain of insert / delete / rollback / commit actions on a row.

 

    插入之前做的事情太多了,session.addInsertAction(this, store, row, changedCols); 终于来到操作插入的过程了。

     

    public void addInsertAction(Session session, Table table,
                                PersistentStore store, Row row,
                                int[] changedColumns) {

        RowAction action = row.rowAction;

        if (action == null) {
/*
            System.out.println("null insert action " + session + " "
                               + session.actionTimestamp);
*/
            throw Error.runtimeError(ErrorCode.GENERAL_ERROR,
                                     "null insert action ");
        }

        store.indexRow(session, row);
        session.rowActionList.add(action);

        row.rowAction = null;
    }

      用AVL tree保存表的数据信息,索引而且每个节点保存数据,普通的AVL数操作,插入过程还加了写锁

     

  /**
     * Insert a node into the index
     */
    public void insert(Session session, PersistentStore store, Row row) {

        NodeAVL        n;
        NodeAVL        x;
        boolean        isleft        = true;
        int            compare       = -1;
        final Object[] rowData       = row.getData();
        boolean        compareRowId  = !isUnique || hasNulls(session, rowData);
        boolean        compareSimple = isSimple;

        writeLock.lock();

        try {
            n = getAccessor(store);
            x = n;

            if (n == null) {
                store.setAccessor(this, ((RowAVL) row).getNode(position));

                return;
            }

            while (true) {
                Row currentRow = n.row;

                compare = 0;

                if (compareSimple) {
                    compare =
                        colTypes[0].compare(session, rowData[colIndex[0]],
                                            currentRow.getData()[colIndex[0]]);

                    if (compare == 0 && compareRowId) {
                        compare = compareRowForInsertOrDelete(session, row,
                                                              currentRow,
                                                              compareRowId, 1);
                    }
                } else {
                    compare = compareRowForInsertOrDelete(session, row,
                                                          currentRow,
                                                          compareRowId, 0);
                }

                // after the first match and check, all compares are with row id
                if (compare == 0 && session != null && !compareRowId
                        && session.database.txManager.isMVRows()) {
                    if (!isEqualReadable(session, store, n)) {
                        compareRowId = true;
                        compare = compareRowForInsertOrDelete(session, row,
                                                              currentRow,
                                                              compareRowId,
                                                              colIndex.length);
                    }
                }

                if (compare == 0) {
                    if (isConstraint) {
                        Constraint c =
                            ((Table) table).getUniqueConstraintForIndex(this);

                        throw c.getException(row.getData());
                    } else {
                        throw Error.error(ErrorCode.X_23505,
                                          name.statementName);
                    }
                }

                isleft = compare < 0;
                x      = n;
                n      = isleft ? x.nLeft
                                : x.nRight;

                if (n == null) {
                    break;
                }
            }

            x = x.set(store, isleft, ((RowAVL) row).getNode(position));

            balance(store, x, isleft);
        } finally {
            writeLock.unlock();
        }
    }

 

    

 /**
     * Balances part of the tree after an alteration to the index.
     */
    void balance(PersistentStore store, NodeAVL x, boolean isleft) {

        while (true) {
            int sign = isleft ? 1
                              : -1;

            switch (x.iBalance * sign) {

                case 1 :
                    x.iBalance = 0;

                    return;

                case 0 :
                    x.iBalance = -sign;
                    break;

                case -1 :
                    NodeAVL l = isleft ? x.nLeft
                                       : x.nRight;

                    if (l.iBalance == -sign) {
                        x.replace(store, this, l);
                        x.set(store, isleft, l.child(store, !isleft));
                        l.set(store, !isleft, x);

                        x.iBalance = 0;
                        l.iBalance = 0;
                    } else {
                        NodeAVL r = !isleft ? l.nLeft
                                            : l.nRight;

                        x.replace(store, this, r);
                        l.set(store, !isleft, r.child(store, isleft));
                        r.set(store, isleft, l);
                        x.set(store, isleft, r.child(store, !isleft));
                        r.set(store, !isleft, x);

                        int rb = r.iBalance;

                        x.iBalance = (rb == -sign) ? sign
                                                   : 0;
                        l.iBalance = (rb == sign) ? -sign
                                                  : 0;
                        r.iBalance = 0;
                    }

                    return;
            }

            if (x.nParent == null) {
                return;
            }

            isleft = x.nParent == null || x == x.nParent.nLeft;
            x      = x.nParent;
        }
    }

 

   我们看下数据,当前有两条记录的数据

   

 

 

     执行完之后   endAction(r); 事务处理结束,出错则回滚,成功则提交

    

    public void endAction(Result result) {

//        tempActionHistory.add("endAction " + actionTimestamp);
        sessionData.persistentStoreCollection.clearStatementTables();

        if (result.mode == ResultConstants.ERROR) {
            sessionData.persistentStoreCollection.clearResultTables(
                actionTimestamp);
            database.txManager.rollbackAction(this);
        } else {
            sessionContext
                .diagnosticsVariables[ExpressionColumn.idx_row_count] =
                    result.mode == ResultConstants.UPDATECOUNT
                    ? Integer.valueOf(result.getUpdateCount())
                    : ValuePool.INTEGER_0;

            database.txManager.completeActions(this);
        }

//        tempActionHistory.add("endAction ends " + actionTimestamp);
    }

 

    成功的话当前就是清楚transationManager保存的session需要加锁的表信息了

    

void endActionTPL(Session session) {

        if (session.isolationLevel == SessionInterface.TX_REPEATABLE_READ
                || session.isolationLevel
                   == SessionInterface.TX_SERIALIZABLE) {
            return;
        }

        if (session.sessionContext.currentStatement == null) {

            // after java function / proc with db access
            return;
        }

        if (session.sessionContext.depth > 0) {

            // routine or trigger
            return;
        }

        HsqlName[] readLocks =
            session.sessionContext.currentStatement.getTableNamesForRead();

        if (readLocks.length == 0) {
            return;
        }

        writeLock.lock();

        try {
            unlockReadTablesTPL(session, readLocks);

            final int waitingCount = session.waitingSessions.size();

            if (waitingCount == 0) {
                return;
            }

            boolean canUnlock = false;

            // if write lock was used for read lock
            for (int i = 0; i < readLocks.length; i++) {
                if (tableWriteLocks.get(readLocks[i]) != session) {
                    canUnlock = true;

                    break;
                }
            }

            if (!canUnlock) {
                return;
            }

            canUnlock = false;

            for (int i = 0; i < waitingCount; i++) {
                Session current = (Session) session.waitingSessions.get(i);

                if (current.abortTransaction) {
                    canUnlock = true;

                    break;
                }

                Statement currentStatement =
                    current.sessionContext.currentStatement;

                if (currentStatement == null) {
                    canUnlock = true;

                    break;
                }

                if (ArrayUtil.containsAny(
                        readLocks, currentStatement.getTableNamesForWrite())) {
                    canUnlock = true;

                    break;
                }
            }

            if (!canUnlock) {
                return;
            }

            resetLocks(session);
            resetLatchesMidTransaction(session);
        } finally {
            writeLock.unlock();
        }
    }

 

  • 大小: 210.3 KB
  • 大小: 140.4 KB
0
1
分享到:
评论

相关推荐

    Hsqldb的缓存分析及调试步骤

    HSQLDB(HyperSQL Database)是一个轻量级、高性能的关系型数据库引擎,常用于Java应用程序和测试环境中。它的缓存机制和数据存储方式对于理解其性能和操作至关重要。以下是对HSQLDB的缓存分析和调试步骤的详细解释...

    HSQLDB

    由于HSQLDB是开源的,开发者可以深入研究其源码,理解数据库的内部工作原理,如查询解析、执行计划生成、事务管理等,这对提升数据库相关的技术能力非常有帮助。 总之,HSQLDB作为一个轻量级、高性能的数据库,广泛...

    开源数据库软件hsqldb

    3. **高性能**:HSQldb在处理大量数据时表现出良好的性能,得益于其高效的查询引擎和内存管理机制。 4. **支持SQL标准**:HSQldb兼容大部分SQL92和SQL99标准,提供丰富的SQL语句支持。 5. **嵌入式数据库**:...

    hsqldb使用(转载)

    3. **HSQldb管理工具** HSQldb自带的管理工具是`DatabaseManagerSwing`(还有`DatabaseManager`),它是一个基于Swing的GUI工具,可以用来管理HSQldb数据库。通过JDBC连接到运行在Server模式下的HSQldb,可以创建、...

    HSQLDB-Research:HSQLDB数据库源代码学习研究(包括代码注释,文档,用于代码分析的测试用例)

    src:HSQLDB数据库的最新源代码,在源代码中附加了轻松分析理解代码的注释 把代码引入Eclipse 运行mvn eclipse:eclipse生成Eclipse项目,打开Eclipse,选择File-&gt; Import-&gt; Existing Projects into Workspace 运行...

    <转>HSQLDB 安装与使用

    主要的类包括`org.hsqldb.Server`(服务器进程)、`org.hsqldb.jdbc.JDBCConnection`(JDBC连接)以及`org.hsqldb.Statement`(SQL语句执行)等。 ### 工具集成 HSQLDB常被用作开发和测试环境中的数据库,因为它...

    HSQLDB 1.8.0

    《HSQLDB 1.8.0:轻量级数据库引擎的深度剖析》 HSQLDB,全称为HyperSQL Database,是一款开源、轻量级、高性能的关系型数据库管理系统,广泛应用于嵌入式系统和测试环境。HSQLDB 1.8.0是该数据库引擎的一个重要...

    hsqldb实例源代码

    4. **插入数据**:使用`PreparedStatement`执行`INSERT INTO`语句,将数据插入到表中。 5. **关闭资源**:操作完成后,记得关闭`Statement`、`Connection`等资源,以释放系统资源。 接下来,我们讨论如何使用HSQldb...

    HSQLDB快速连接数据库

    - **约束和索引**:详细阐述了如何在HSQLDB中定义和使用主键、唯一性约束、唯一性索引以及外键约束,并分析了这些约束和索引如何影响查询性能。 - **类型和算术操作**:介绍了HSQLDB支持的各种数据类型,包括整型、...

    HSQLDB安装与使用-转自http://www.cnblogs.com/wllyy189/archive/2008/11/15/1334002.html

    **HSQLDB简介** HSQLDB(HyperSQL Database)是一个开源、轻量级、高性能的关系型数据库管理系统,适用于...了解并掌握HSQLDB的安装、使用和源码分析,能帮助开发者更好地理解和应用关系型数据库技术,提升项目效率。

    hsqldb管理工具

    3. 数据库操作:连接成功后,用户可以执行SQL语句,创建表、插入数据、查询数据、更新数据和删除数据。HSQldb支持大部分标准SQL语法,同时也有自己的特有功能,如内存数据库模式和文件数据库模式。 4. 管理与维护:...

    hsqldb-2.3.3.zip

    《HSQldb 2.3.3:轻量级数据库引擎深度解析》 HSQldb,全称为HyperSQL Database,是一款开源、纯Java语言编写的轻量级关系型数据库管理系统,广泛应用于测试环境、嵌入式系统以及小型应用中。HSQldb 2.3.3是其稳定...

    HSQLDB的使用

    **源码分析** HSQLDB是用Java编写的,因此其源码可读性较高,对于学习数据库原理和实现有很高的价值。通过阅读源码,开发者可以深入理解SQL的执行流程、事务管理、索引构建等核心概念。 **工具支持** HSQLDB提供...

    hsqldb-2.3.4

    3. **存储引擎**:HSQldb提供了内存存储和磁盘存储两种方式,内存存储适合小型应用,磁盘存储则适用于大型数据集。 4. **强大的查询能力**:HSQldb支持复杂的SQL查询,包括子查询、联接操作、视图等。 5. **自动备份...

    HSQLDB_guide

    3. 高性能:HSQLDB采用优化的查询引擎,能快速处理大量数据,提供高效的读写性能。 4. SQL兼容性:HSQLDB支持SQL:2008标准,提供丰富的SQL语句支持,包括事务、索引、视图、存储过程等。 5. 多种模式:HSQLDB支持单...

    hsqldb demo

    6. **触发器与存储过程**:HSQldb 支持创建触发器和存储过程,扩展了数据库的功能。 7. **Unicode支持**:HSQldb 全面支持Unicode字符集,满足多语言需求。 **HSQldb 示例** 在"hsqldb demo"中,可能包含了一个...

    hsqldb-2.2.8数据库

    在性能方面,HSQldb以其小巧的体积和快速的执行速度著称。由于它可以完全基于内存运行,对于需要快速读写大量数据的应用来说,HSQldb是一个理想的选择。此外,HSQldb也支持磁盘存储,以满足更大型或持久性的数据存储...

    hsqldb 2.25

    3. **内存数据库**:HSQldb可以作为内存数据库运行,这意味着数据存储在内存中,启动速度快,适用于临时数据存储或测试环境。 4. **文件数据库**:除了内存模式,HSQldb还可以将数据存储在磁盘文件中,适合于长期...

Global site tag (gtag.js) - Google Analytics