MyBatis3源码深度解析(二)JDBC API简介

2024-02-23 18:20

本文主要是介绍MyBatis3源码深度解析(二)JDBC API简介,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 第2章 JDBC规范详解
    • 2.1 JDBC API简介
      • 2.1.1 建立数据源连接
        • 2.1.1.1 DriverManager
        • 2.1.1.2 DataSource
      • 2.1.2 执行SQL语句
      • 2.1.3 处理SQL执行结果
        • 2.1.4 使用JDBC操作数据库
    • 2.2 JDBC API中的类与接口
      • 2.2.1 java.sql包
      • 2.2.2 javax.sql包
        • 2.2.2.1 DataSource
        • 2.2.2.2 PooledConnection
        • 2.2.2.3 XAConnection
        • 2.2.2.4 RowSet

前言

MyBatis框架对JDBC做了轻量级的封装,因此在开始学习MyBatis源码之前,有必要全面地了解JDBC规范。本章使用的JDBC版本是4.2。

第2章 JDBC规范详解

2.1 JDBC API简介

JDBC(Java Database Connectivity)是Java语言中提供的访问关系型数据库的接口

使用JDBC API可以执行SQL语句、检索SQL执行结果以及将数据更改写回底层数据源。

JDBC API基于X/Open SQL CLI,是ODBC的基础,为Java程序提供了访问一个或多个数据源的方法。

使用JDBC操作数据源大致需要以下几个步骤:

(1)与数据源建立连接。
(2)执行SQL语句。
(3)检索SQL执行结果。
(4)关闭连接。

2.1.1 建立数据源连接

JDBC API中定义了Connection接口,用来表示与底层数据源的连接。

JDBC应用程序可以使用以下两种方式获取Connection对象:

2.1.1.1 DriverManager

这是一个在JDBC 1.0规范中就已经存在、完全由JDBC API实现的驱动管理类。当应用程序第一次尝试通过URL连接数据源时,DriverManager会自动加载classpath下所有的JDBC驱动。

DriverManager类提供了一系列重载的getConnection()方法,用来获取Connection对象,例如:

// 获取Connection对象
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis", "sa", "");
2.1.1.2 DataSource

这是在DBC 2.0规范中引入的接口。相比DriverManager,它提供了更多底层数据源的细节,而且对应用来说,不需要关注JDBC驱动的实现。

一个DataSource对象的属性被设置后,它就代表一个特定的数据源。当DataSource实例的getConnection()方法被调用后,DataSource实例就会返回一个与数据源建立连接的Connection对象。

在应用程序中修改DataSource对象的属性后,它就代表一个新的数据源,通过getConnection()方法可以获取指向这个新的数据源的Connection对象。同样,数据源的具体实现修改后,不需要修改应用程序代码。

需要注意的是,JDBC API中只提供了DataSource接口,没有提供DataSource的具体实现,DataSource具体的实现由JDBC驱动程序提供。另外,目前一些主流的数据库连接池(例如DBCP、C3P0、Druid等)也提供了DataSource接口的具体实现

MyBatis框架提供了DataSource接口的实现,其实现方法如下:

// 创建DataSource实例
DataSource dataSource = new UnpooledDataSource("org.hsqldb.jdbcDriver","jdbc:hsqldb:mem:mybatis", "sa", "");
// 获取Connection对象
Connection connection = dataSource.getConnection();

MyBatis框架还提供了DataSource的工厂,即DataSourceFactory,支持使用工厂模式创建DataSource实例。代码如下:

// 创建DataSource实例
DataSourceFactory dsf = new UnpooledDataSourceFactory();
Properties properties = new Properties();
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
properties.load(configStream);
dsf.setProperties(properties);
DataSource dataSource = dsf.getDataSource();
// 获取Connection对象
Connection connection = dataSource.getConnection();

另外,JDBC API中定义了两个DataSource接口比较重要的扩展,用于支撑企业级应用。这两个接口分别为:

  • ConnectionPoolDataSource:支持缓存和复用Connection对象,这样能够在很大程度上提升应用性能和伸缩性。
  • XADataSource:该实例返回的Connection对象能够支持分布式事务。

2.1.2 执行SQL语句

JDBC与数据源建立连接后,接下来是要对数据源进行查询、新增、更新或删除等操作,即执行SQL语句。

执行SQL语句使用的是Statement接口,该接口是JDBC API中提供的SQL语句执行器,调用Statement接口中定义的executeQuery()方法执行查询操作,调用executeUpdate()方法执行更新操作,另外还可以调用executeBatch()方法执行批量处理操作。

甚至,当不确定SQL语句的类型时,可以通过调用execute()方法进行统一的操作,根据返回值来判断SQL语句类型,通过Statement接口提供的getResultSet()方法来获取查询结果集,或者通过getUpdateCount()方法来获取更新操作影响的行数.

下面是一个通过Statement执行查询操作的案例:

Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from user");

2.1.3 处理SQL执行结果

SQL语句执行完毕后,解析来要处理SQL执行结果。例如执行一条SELECT语句后,需要获取查询的结果,执行UPDATE或者INSERT语句后,需要通过影响的记录数来确定是否更新成功。

DBC API中提供了ResultSet接口,该接口的实现类封装SQL查询的结果,对ResultSet对象进行遍历,通过其提供的一系列getXXX()方法(例如getString)获取查询结果集。

下面是一个遍历ResultSet的案例:

ResultSet resultSet = statement.executeQuery("select * from user");
// 遍历ResultSet
ResultSetMetaData metaData = resultSet.getMetaData();
int columCount = metaData.getColumnCount();
while (resultSet.next()) {for (int i = 1; i <= columCount; i++) {String columName = metaData.getColumnName(i);String columVal = resultSet.getString(columName);System.out.println(columName + ":" + columVal);}System.out.println("--------------------------------------");
}
2.1.4 使用JDBC操作数据库

将以上关键步骤整合起来,得到以下单元测试:

@Test
public void testJdbc() {// 初始化数据DbUtils.initData();try {// 加载驱动Class.forName("org.hsqldb.jdbcDriver");// 获取Connection对象Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis", "sa", "");// 获取Statement对象Statement statement = connection.createStatement();// 执行SQL语句,得到ResultSetResultSet resultSet = statement.executeQuery("select * from user");// 遍历ResultSetResultSetMetaData metaData = resultSet.getMetaData();int columCount = metaData.getColumnCount();while (resultSet.next()) {for (int i = 1; i <= columCount; i++) {String columName = metaData.getColumnName(i);String columVal = resultSet.getString(columName);System.out.println(columName + ":" + columVal);}System.out.println("--------------------------------------");}// 关闭连接IOUtils.closeQuietly(statement);IOUtils.closeQuietly(connection);} catch (Exception e) {e.printStackTrace();}
}

代码中包含两个自定义的工具类DbUtils和IOUtils,方便后续测试使用。

public abstract class DbUtils {public static Connection initData() {Connection conn = null;// 加载HSQLDB驱动try {Class.forName("org.hsqldb.jdbcDriver");// 获取Connection对象conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis", "sa", "");// 使用Mybatis的ScriptRunner工具类执行数据库脚本ScriptRunner scriptRunner = new ScriptRunner(conn);// 不输出sql日志scriptRunner.setLogWriter(null);scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));scriptRunner.runScript(Resources.getResourceAsReader("init-data.sql"));} catch (Exception e) {e.printStackTrace();}return conn;}
}public abstract class IOUtils {public static void closeQuietly(Closeable closeable) {if(closeable != null) {try {closeable.close();} catch (IOException e) {e.printStackTrace();}}}public static void closeQuietly(AutoCloseable closeable) {if(closeable != null) {try {closeable.close();} catch (Exception e) {e.printStackTrace();}}}
}

执行该单元测试,会查询出user表中的所有记录并输出到控制台,如下:

ID:0
CREATE_TIME:2024-02-21 10:24:30
NAME:User1
PASSWORD:pwd
PHONE:18705464523
NICK_NAME:User1
---------------------------------------
ID:1
CREATE_TIME:2024-02-21 10:24:30
NAME:User2
PASSWORD:pwd
PHONE:18705464523
NICK_NAME:User2
---------------------------------------
ID:2
......

2.2 JDBC API中的类与接口

查阅官方文档可知,JDBC API由java.sql和javax.sql两个包构成。

2.2.1 java.sql包

java.sql包中涵盖JDBC最核心的API,包括数据类型、枚举、API相关、驱动相关、异常等:

#数据类型
java.sql.Array
java.sql.Blob
java.sql.Clob
java.sql.Date
java.sql.NClob
java.sql.Ref
java.sql.Struct
java.sql.Time
java.sql.RowId
java.sql.Timestamp
java.sql.SQLData
java.sql.SQLInput
java.sql.SQLOutput
java.sql.SQLXML#枚举
java.sql.SQLType
java.sql.JDBCType
java.sql.Types
java.sql.RowIdLifetime
java.sql.PseudoColumnUsage
java.sql.ClientInfoStatus#API相关
java.sql.Wrapper
java.sql.Connection
java.sql.Statement
java.sql.CallableStatement
java.sql.PreparedStatement
java.sql.DatabaseMetaData
java.sql.ParameterMetaData
java.sql.ResultSet
java.sql.ResultSetMetaData#驱动相关
java.sql.Driver
java.sql.DriverAction
java.sql.DriverManager
java.sql.DriverPropertyInfo
java.sql.SQLPermission
java.sql.Savepoint#异常
java.sql.BatchUpdateException
java.sql.DataTruncation
java.sql.SQLClientInfoException
java.sql.SQLDataException
java.sql.SQLException
java.sql.SQLFeatureNotSupportedException
java.sql.SQLIntegrityConstraintViolationException
java.sql.SQLInvalidAuthorizationSpecException
java.sql.SQLNonTransientConnectionException
java.sql.SQLNonTransientException
java.sql.SQLRecoverableException
java.sql.SQLSyntaxErrorException
java.sql.SQLTimeoutException
java.sql.SQLTransactionRollbackException
java.sql.SQLTransientConnectionException
java.sql.SQLTransientException
java.sql.SQLWarning

其中API相关的几个接口(Connection、Statement、ResultSet等)是开发中会实际操作的。这些接口都继承了java.sql.Wrapper接口。

Connection、Statement、ResultSet之间的关系如图所示:

Connection、Statement、ResultSet之间的关系
许多JDBC驱动程序会提供超越传统JDBC的扩展,驱动厂商可能会在其原始类型的基础上进行包装,使之符合JDBC API规范(如Oracle数据库驱动的OracleStatement),而Wrapper接口提供了访问这些原始类型的功能,从而可以使用JDBC驱动中一些非标准的特性。

public interface Wrapper {<T> T unwrap(java.lang.Class<T> iface) throws java.sql.SQLException;boolean isWrapperFor(java.lang.Class<?> iface) throws java.sql.SQLException;
}

由java.sql.Wrapper的源码可知,该接口提供了两个方法:

  • unwrap:返回未经过包装的JDBC驱动原始类型实例。
  • isWrapperFor:判断当前实例是否是JDBC驱动中某一类型的包装类型。

例如,Oracle数据库驱动中提供了一些非JDBC标准的方法,如果需要使用这些非标准JDBC的方法,则可用调用Wrapper接口的unwrap方法获取到Oracle驱动的原始类型(如OracleStatement),然后调用原始类型提供的非标准方法就可以访问Oracle数据库特有的一些特性了。

2.2.2 javax.sql包

javax.sql包中的类和接口最早是由JDBC 2.0版本的可选包提供的,内容不多:

#数据源
javax.sql.DataSource
javax.sql.CommonDataSource#连接池相关
javax.sql.ConnectionPoolDataSource
javax.sql.PooledConnection
javax.sql.ConnectionEvent
javax.sql.ConnectionEventListener
javax.sql.StatementEvent
javax.sql.StatementEventListener#ResultSet扩展
javax.sql.RowSet
javax.sql.RowSetEvent
javax.sql.RowSetInternal
javax.sql.RowSetListener
javax.sql.RowSetMetaData
javax.sql.RowSetReader
javax.sql.RowSetWriter#分布式扩展
javax.sql.XAConnection
javax.sql.XADataSource
2.2.2.1 DataSource

在JDBC 1.0中,使用DriverManager类来产生一个与数据源连接的Connection对象;JDBC 2.0提供的DataSource接口则是一种更好的连接数据源的方式。主要理由是:

  • 避免硬编码:使用DataSource不需要像使用DriverManager一样对加载的数据库驱动程序信息进行硬编码,而可以选择使用JNDI注册这个数据源对象,然后在程序中使用一个逻辑名称来引用它,JNDI会自动根据给出的名称找到与这个名称绑定的DataSource对象,然后就可以使用这个DataSource对象来建立和具体数据库的连接。
  • 提高程序效率:DataSource接口支持连接池和分布式事务。连接池支持对连接的复用,而不是每次需要操作数据源时都新建一个物理连接,以此来显著地提高程序的效率,适用于任务繁忙、负担繁重的企业级应用。
2.2.2.2 PooledConnection

An object that provides hooks for connection pool management. A PooledConnectionobject represents a physical connection to a data source. The connection can be recycled rather than being closed when an application is finished with it, thus reducing the number of connections that need to be made.
PooledConnection是为连接池管理提供钩子的对象。一个PooledConnection对象表示到数据源的物理连接。当应用程序完成连接时,可以回收连接,而不是关闭连接,从而减少需要建立的连接数量。

An application programmer does not use the PooledConnectioninterface directly; rather, it is used by a middle tier infrastructure that manages the pooling of connections.
开发者不会直接使用PooledConnection接口;相反,它由管理连接池的中间层基础设施使用。

When an application calls the method DataSource.getConnection, it gets back a Connectionobject. If connection pooling is being done, that Connectionobject is actually a handle to a PooledConnectionobject, which is a physical connection.
当应用程序调用DataSource的getConnection方法时,它会返回一个Connection对象。如果连接池已经准备就绪,这个Connection对象实际上是PooledConnection对象的句柄,这是一个物理连接。

The connection pool manager, typically the application server, maintains a pool of PooledConnectionobjects. If there is a PooledConnectionobject available in the pool, the connection pool manager returns a Connectionobject that is a handle to that physical connection.
连接池管理器,通常是应用服务器,维护着一个PooledConnection对象池。如果连接池中有一个可用的PooledConnection对象,连接池管理器返回一个Connection对象,它是该物理连接的句柄。

If no PooledConnectionobject is available, the connection pool manager calls the ConnectionPoolDataSourcemethod getPoolConnectionto create a new physical connection. The JDBC driver implementing ConnectionPoolDataSourcecreates a new PooledConnectionobject and returns a handle to it.
如果没有可用的PooledConnection对象,连接池管理器会调用ConnectionPoolDataSource的getPoolConnection方法来创建一个新的物理连接。实现ConnectionPoolDataSource的JDBC驱动程序创建了一个新的PooledConnection对象,并返回了它的句柄。

When an application closes a connection, it calls the Connectionmethod close. When connection pooling is being done, the connection pool manager is notified because it has registered itself as a ConnectionEventListenerobject using the ConnectionPoolmethod addConnectionEventListener. The connection pool manager deactivates the handle to the PooledConnectionobject and returns the PooledConnectionobject to the pool of connections so that it can be used again. Thus, when an application closes its connection, the underlying physical connection is recycled rather than being closed.
当应用程序关闭连接时,它会调用Connection的close方法。当连接池就绪时,连接池管理器会收到通知,因为它已经使用ConnectionPool的addConnectionEventListener方法将自己注册为ConnectionEventListener对象。连接池管理器停用PooledConnection对象的句柄,并将PooledConnection对象返回到连接池,以便它可以再次使用。因此,当应用程序关闭其连接时,底层的物理连接将被回收而不是关闭。

The physical connection is not closed until the connection pool manager calls the PooledConnectionmethod close. This method is generally called to have an orderly shutdown of the server or if a fatal error has made the connection unusable.
直到连接池管理器调用PooledConnection的close方法,物理连接才会关闭。这个方法通常用于有序地关闭服务器,或者在发生致命错误导致连接不可用时调用。

A connection pool manager is often also a statement pool manager, maintaining a pool of PreparedStatementobjects. When an application closes a prepared statement, it calls the PreparedStatementmethod close. When Statementpooling is being done, the pool manager is notified because it has registered itself as a StatementEventListenerobject using the ConnectionPoolmethod addStatementEventListener. Thus, when an application closes its PreparedStatement, the underlying prepared statement is recycled rather than being closed.
连接池管理器通常也是一个Statement池管理器,维护一个PreparedStatement对象池。当一个应用程序关闭一个PreparedStatement时,它会调用PreparedStatement的close方法。当Statement池就绪时,池管理器会收到通知,因为它已经使用ConnectionPool的方法addStatementEventListener将自己注册为StatementEventListener对象。因此,当应用程序关闭PreparedStatement时,底层的PreparedStatement将被回收而不是关闭。

PooledConnection的javadoc内容很多,详细地说明了PooledConnection的用法,总结起来为以下几点:

  1. PooledConnection对象代表连接池到数据源的物理连接;
  2. 应用服务器一般充当连接池管理器,维护一个PooledConnection对象池,调用getConnection方法时,如果连接池中有可用的PooledConnection对象,则返回PooledConnection对象的句柄;如果没有,则调用ConnectionPoolDataSource的getPoolConnection方法来创建一个PooledConnection对象,并返回它的句柄。
  3. 由于连接池管理器已经被注册为ConnectionEventListener对象,因此应用程序关闭连接时会收到通知,进而停用PooledConnection对象的句柄,并回收PooledConnection对象(减少与数据源建立连接的次数,提升性能)。
  4. 连接池管理器处理维护PooledConnection对,还可以维护Statement对象。同样,当应用程序关闭PreparedStatement时,底层的PreparedStatement对象将被回收而不是关闭。
2.2.2.3 XAConnection

javax.sql包中还包含XADataSource、XAResource和XAConnection接口,这些接口提供了分布式事务的支持,具体由JDBC驱动来实现。

XAConnection接口继承了PooledConnection接口,因此它具有所有PooledConnection的特性。

public interface XAConnection extends PooledConnection
2.2.2.4 RowSet
public interface RowSet extends ResultSet

javax.sql包下的RowSet接口,继承自java.sql包下的ResultSet接口,其关系如图。

RowSet与ResultSet的关系

A RowSet object may make a connection with a data source and maintain that connection throughout its life cycle, in which case it is called a connected rowset. A rowset may also make a connection with a data source, get data from it, and then close the connection. Such a rowset is called a disconnected rowset. A disconnected rowset may make changes to its data while it is disconnected and then send the changes back to the original source of the data, but it must reestablish a connection to do so.

RowSet对象可以与数据源建立连接,并在其生命周期中维护该连接,在这种情况下该对象被称为连接RowSet
RowSet对象还可以与数据源建立连接,从数据源获取数据,然后关闭连接,这样的RowSet对象称为非连接RowSet
非连接RowSet可以在连接断开时更改其数据,然后将这些更改发送回原始数据源,不过它必须重新建立连接才能完成此操作。

换句话说,RowSet的数据操作都是在应用程序的内存中进行的,在建立连接后批量提交到数据源。RowSet的这种离线操作能够有效地利用计算机越来越充足的内存、减轻数据库服务器的负担,提升灵活性和性能。

RowSet默认是一个可滚动、可更新、可序列化的结果集,而且它作为一个JavaBean组件,可以方便地在网络间传输,用于两端的数据同步。

通俗来讲,RowSet就相当于数据库表数据在应用程序内存中的映射,所有的操作都可以直接与RowSet对象交互。RowSet与数据库之间的数据同步,作为开发人员不需要关注。

本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

这篇关于MyBatis3源码深度解析(二)JDBC API简介的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/739510

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元,传统键盘输入方式正悄然进化。以往,面对实体键盘,我们常需目光游离于屏幕与键盘之间,以确认指尖下的精准位置。而屏幕键盘虽直观可见,却常因占据屏幕空间,迫使我们在操作与视野间做出妥协,频繁调整布局以兼顾输入与界面浏览。 幸而,韦季李输入法的横空出世,彻底颠覆了这一现状。它不仅对输入界面进行了革命性的重构,更巧妙地将鼠标这一传统外设融入其中,开创了一种前所未有的交互体验。 想象