MyBatis作为一款优秀的持久层框架,它极大地简化了Java应用程序与数据库的交互过程,它将繁琐的JDBC操作封装起来,让开发者能够更专注于SQL本身,而非繁琐的连接管理和结果集处理,理解MyBatis如何调用数据库,是掌握其核心机制的关键,这个过程可以分解为几个清晰的步骤,从配置初始化到最终执行SQL并返回结果。
核心配置:搭建桥梁的基石
MyBatis的一切操作都始于一个核心配置文件,通常是mybatis-config.xml
,这个文件是MyBatis的全局性配置,它告诉MyBatis如何连接数据库、如何管理事务以及去哪里找到SQL语句。
配置文件中最重要的部分是<environments>
标签,它定义了数据库环境,包括:
- 事务管理器:配置为
JDBC
或MANAGED
。JDBC
表示由MyBatis自身管理事务的提交和回滚,适用于简单的本地应用;MANAGED
则将事务管理交给容器(如Spring),这是在集成环境中的常见选择。 - 数据源:配置数据库连接信息,MyBatis支持多种数据源配置,如
UNPOOLED
(不使用连接池,每次请求都打开新连接)、POOLED
(使用连接池,性能更优,是开发中的首选)和JNDI
(从应用服务器配置的JNDI数据源获取连接),在POOLED
配置中,我们需要提供数据库驱动、URL、用户名和密码等基本信息。
配置文件通过<mappers>
标签注册Mapper XML文件或Mapper接口,将SQL定义与Java代码关联起来,这是MyBatis能够找到并执行SQL的前提。
Mapper接口与XML:定义SQL的契约
现代MyBatis开发推荐使用Mapper接口的方式,这种方式将SQL调用与Java方法绑定,提供了类型安全的API。
Mapper接口:这是一个普通的Java接口,其中定义的方法与要执行的SQL语句一一对应,一个
UserMapper
接口可能包含User findById(Integer id)
方法,这个接口本身不包含任何实现代码,它仅仅是一个契约,定义了调用数据库的规范。Mapper XML文件:这是SQL语句真正存在的地方,每个Mapper接口通常对应一个同名的XML文件,该文件以
<mapper>
为根标签,其namespace
属性必须指向对应的Mapper接口的全限定名(例如com.example.mapper.UserMapper
)。
在<mapper>
标签内部,我们使用一系列标签来定义SQL语句:
<select>
:用于定义查询语句。<insert>
:用于定义插入语句。<update>
:用于定义更新语句。<delete>
:用于定义删除语句。
每个标签都有一个id
属性,这个id
必须与Mapper接口中的方法名完全一致,MyBatis正是通过“接口全限定名 + 方法名”来唯一定位一条SQL语句的。parameterType
指定传入参数的类型,resultType
或resultMap
则指定返回结果的映射规则。
下表小编总结了这些常用标签的用途:
用途 | 关键属性 | |
---|---|---|
<select> | 执行数据库查询操作 | id , parameterType , resultType /resultMap |
<insert> | 执行数据库插入操作 | id , parameterType , useGeneratedKeys , keyProperty |
<update> | 执行数据库更新操作 | id , parameterType |
<delete> | 执行数据库删除操作 | id , parameterType |
SqlSessionFactory与SqlSession:执行SQL的引擎
配置和定义完成后,MyBatis需要一个执行引擎来真正地调用数据库,这个引擎由两个核心组件构成:SqlSessionFactory
和SqlSession
。
SqlSessionFactory:这是一个重量级对象,是创建
SqlSession
的工厂,它的作用是读取并解析mybatis-config.xml
配置文件,将所有配置信息加载到内存中,一个应用通常只需要一个SqlSessionFactory
实例,它应该在应用启动时被创建,并伴随整个应用的生命周期,创建它通常使用SqlSessionFactoryBuilder
。SqlSession:这是一个轻量级、非线程安全的对象,可以将其理解为一次与数据库的会话,它包含了执行SQL命令所需的所有方法,例如
selectOne
,selectList
,insert
,update
,delete
等,它还负责管理事务和获取Mapper接口的代理实例。
SqlSession
的生命周期应该是短期的,通常在一个方法或一个请求的范围内,每次需要与数据库交互时,都从SqlSessionFactory
中获取一个新的SqlSession
实例,使用完毕后必须立即关闭,以释放数据库连接资源。
完整调用流程:从方法到数据库
将以上所有部分串联起来,MyBatis调用数据库的完整流程如下:
- 初始化:应用启动时,通过
SqlSessionFactoryBuilder
读取mybatis-config.xml
,构建一个SqlSessionFactory
实例。 - 创建会话:当业务代码需要操作数据库时,通过
sqlSessionFactory.openSession()
创建一个SqlSession
对象。 - 获取Mapper代理:调用
sqlSession.getMapper(UserMapper.class)
方法,MyBatis会使用动态代理技术,为UserMapper
接口创建一个代理对象,这个代理对象实现了UserMapper
接口。 - 执行方法:业务代码调用代理对象的方法,例如
userMapper.findById(1)
。 - 定位SQL:代理对象拦截到方法调用,它会根据当前Mapper接口的
namespace
和方法名findById
,组合成com.example.mapper.UserMapper.findById
这个唯一的ID,去已加载的Mapper XML中查找对应的<select>
- 参数处理与执行:找到SQL语句后,MyBatis会解析SQL中的占位符(如
#{id}
),将传入的参数1
设置到PreparedStatement
中,通过底层的JDBC执行这条SQL语句。- 结果映射:数据库返回结果集后,MyBatis根据
resultType
或resultMap
的定义,将结果集中的每一行数据映射成一个Java对象(如User
对象)。- 返回结果:将映射好的Java对象返回给业务代码。
- 关闭会话:在
finally
块中调用sqlSession.close()
,关闭会话,释放数据库连接。 - 参数处理与执行:找到SQL语句后,MyBatis会解析SQL中的占位符(如
通过这一系列精巧的设计,MyBatis将对JDBC的复杂操作封装在内部,开发者只需关注业务逻辑和SQL编写,实现了Java代码与SQL语句的解耦,极大地提升了开发效率和代码的可维护性。
相关问答FAQs
Q1: 在MyBatis的SQL中,和有什么区别?
A: 和在MyBatis中是两种截然不同的参数替换方式,核心区别在于安全性和处理时机。
(预编译处理):这是MyBatis推荐的、默认的方式,当MyBatis处理带有的SQL时,它会创建一个
PreparedStatement
对象,然后将中的参数作为占位符()安全地传入,这种方式可以有效防止SQL注入攻击,因为参数值在SQL执行前已经被数据库驱动进行了转义处理,它只会被当作纯粹的数值或字符串,而不会被解释为SQL语法的一部分。SELECT * FROM user WHERE id = #{id}
会被处理成SELECT * FROM user WHERE id = ?
。(字符串替换):这种方式直接将中的内容原封不动地拼接到SQL语句中,它发生在MyBatis创建
PreparedStatement
之前,虽然它在某些场景下很有用,例如动态传入表名、列名或者ORDER BY
排序字段,但存在极大的SQL注入风险,如果传入的参数是恶意构造的SQL片段,它会被直接执行,从而破坏数据库。SELECT * FROM user ORDER BY ${columnName}
,如果columnName
被传入id; DROP TABLE user;
,将会导致灾难性后果。
为了安全,能使用的地方就绝对不要使用,只有在需要动态拼接SQL结构(如表名、列名)且能确保参数来源安全可控时,才应谨慎使用。
Q2: 为什么SqlSession
是非线程安全的?在项目中应该如何管理它的生命周期?
A: SqlSession
被设计为非线程安全的,主要是因为它内部持有了与数据库会话相关的状态信息,这些信息不应该被多个线程共享。
非线程安全的原因:
- 事务管理:
SqlSession
管理着当前会话的事务状态(如自动提交开关、事务是否已提交或回滚),如果多个线程共享一个SqlSession
,一个线程的提交或回滚操作会影响到其他线程正在执行的操作,导致数据不一致。 - 一级缓存:每个
SqlSession
都拥有自己独立的一级缓存(Session级别的缓存),如果在多个线程间共享,一个线程对数据库的修改可能会被另一个线程的缓存所覆盖,导致读取到脏数据。 - 数据库连接:
SqlSession
内部封装了数据库连接对象(Connection
),让多个线程并发使用同一个连接是非常危险的,会导致执行顺序混乱和结果错误。
- 事务管理:
生命周期管理:
正确的做法是,将SqlSession
的作用域限制在方法级别或请求级别。- 在独立应用中:通常在
try-with-resources
或try-finally
代码块中创建和使用,确保在方法执行完毕后能被立即关闭。try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.findById(1); // ... do something with user sqlSession.commit(); // 如果需要 } // sqlSession会自动关闭
- 在Web应用(如与Spring集成):开发者通常不需要手动管理
SqlSession
,MyBatis-Spring集成包会通过SqlSessionTemplate
或MapperScannerConfigurer
,将SqlSession
的生命周期与Spring的事务管理绑定,每个事务或请求都会获得一个独立的SqlSession
实例,并在事务结束时自动关闭,从而保证了线程安全。
- 在独立应用中:通常在
【版权声明】:本站所有内容均来自网络,若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!
发表回复