MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis几乎消除了JDBC代码和参数的手动设置以及结果集的检索。MyBatis使用简单的XML或注解进行配置,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射到数据库中的记录。
与其他ORM框架不同,MyBatis并没有将Java对象与数据库表关联起来,而是将方法与SQL语句关联。MyBatis让开发人员可以使用更自然的面向对象的方式来操作数据库。
特性 | JDBC | MyBatis | Hibernate |
---|---|---|---|
开发效率 | 低 | 中 | 高 |
灵活性 | 高 | 高 | 中 |
学习曲线 | 陡峭 | 平缓 | 较陡峭 |
SQL控制 | 完全手动 | 自定义 | 自动生成 |
性能 | 依赖优化 | 高 | 中 |
映射关系 | 无 | SQL映射 | 对象-关系映射 |
MyBatis的基本工作流程如下:
// 基本工作流程示例代码
// 1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 打开SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
// 4. 执行SQL语句
User user = session.selectOne("org.mybatis.example.UserMapper.getUserById", 1);
// 5. 处理结果
System.out.println(user.getName());
} finally {
// 6. 关闭SqlSession
session.close();
}
MyBatis框架的核心组件构成了其工作原理的基础。每个组件都有其特定的功能和作用,它们协同工作,完成从Java对象到数据库操作的转换。
SqlSessionFactoryBuilder是MyBatis的入口,用于构建SqlSessionFactory实例。它可以从XML配置文件或Configuration类构建SqlSessionFactory。
// 从XML文件创建SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 从Java配置类创建SqlSessionFactory
Configuration configuration = new Configuration();
// ... 配置属性和映射
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSessionFactoryBuilder的作用就是读取配置信息并创建SqlSessionFactory对象。一旦创建了SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了,它可以被回收或销毁。
SqlSessionFactory是MyBatis的核心接口,它用于创建SqlSession实例。SqlSessionFactory的生命周期应该与应用程序的生命周期相同,通常情况下,我们只需要一个SqlSessionFactory实例。
// 创建默认的SqlSession
SqlSession session = sqlSessionFactory.openSession();
// 创建自动提交的SqlSession
SqlSession autoCommitSession = sqlSessionFactory.openSession(true);
// 创建指定事务隔离级别的SqlSession
SqlSession isolatedSession = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);
// 创建指定执行器类型的SqlSession
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
SqlSessionFactory是线程安全的,可以被多个线程共享。它通常在应用程序启动时创建,并在应用程序结束时销毁。
SqlSession是MyBatis的主要接口,通过它可以执行SQL命令、获取映射器(Mapper)、管理事务。SqlSession是线程不安全的,每个线程应该有自己的SqlSession实例。
SqlSession session = sqlSessionFactory.openSession();
try {
// 执行SQL语句
List<User> users = session.selectList("org.mybatis.example.UserMapper.getAllUsers");
// 插入数据
User newUser = new User("Tom", "[email protected]");
session.insert("org.mybatis.example.UserMapper.insertUser", newUser);
// 更新数据
User userToUpdate = session.selectOne("org.mybatis.example.UserMapper.getUserById", 1);
userToUpdate.setName("Updated Name");
session.update("org.mybatis.example.UserMapper.updateUser", userToUpdate);
// 删除数据
session.delete("org.mybatis.example.UserMapper.deleteUser", 2);
// 提交事务
session.commit();
} catch (Exception e) {
// 回滚事务
session.rollback();
throw e;
} finally {
// 关闭SqlSession
session.close();
}
SqlSession的生命周期应该是请求作用域的,即一个请求一个SqlSession,请求结束后关闭SqlSession。
Mapper接口是MyBatis中用于定义数据库操作的接口。MyBatis会为Mapper接口创建动态代理对象,通过代理对象执行SQL语句。
// 定义Mapper接口
public interface UserMapper {
User getUserById(int id);
List<User> getAllUsers();
void insertUser(User user);
void updateUser(User user);
void deleteUser(int id);
}
// 使用Mapper接口
SqlSession session = sqlSessionFactory.openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
// 查询
User user = userMapper.getUserById(1);
List<User> allUsers = userMapper.getAllUsers();
// 插入
User newUser = new User("Tom", "[email protected]");
userMapper.insertUser(newUser);
// 更新
user.setName("Updated Name");
userMapper.updateUser(user);
// 删除
userMapper.deleteUser(2);
session.commit();
} finally {
session.close();
}
Mapper接口的好处是可以使用Java接口和方法,而不必直接使用字符串调用SQL语句,这样可以提供编译时类型检查,减少运行时错误。
Executor是MyBatis的核心接口之一,负责执行SQL语句。MyBatis有三种内置的Executor类型:
// 在配置文件中设置默认的Executor类型
<settings>
<setting name="defaultExecutorType" value="REUSE" />
</settings>
// 在代码中指定Executor类型
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
虽然Executor是MyBatis内部的接口,但了解它的作用有助于理解MyBatis的工作原理和优化查询性能。
StatementHandler负责处理JDBC Statement的创建、设置参数、执行SQL语句以及获取结果。
// MyBatis内部的StatementHandler接口
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout);
void parameterize(Statement statement);
void batch(Statement statement);
int update(Statement statement);
<E> List<E> query(Statement statement, ResultHandler resultHandler);
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
StatementHandler将Java对象转换为JDBC可以执行的Statement对象,它处理了从Java参数到SQL参数的转换,以及从SQL结果到Java对象的转换。
ParameterHandler负责将用户传入的参数转换为JDBC Statement中需要的参数。
// MyBatis内部的ParameterHandler接口
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps);
}
ParameterHandler处理SQL语句中的占位符(?)和实际参数值之间的映射关系,它将Java对象的属性值设置到JDBC PreparedStatement中。
ResultSetHandler负责将JDBC返回的ResultSet结果集转换为Java对象。
// MyBatis内部的ResultSetHandler接口
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt);
<E> Cursor<E> handleCursorResultSets(Statement stmt);
void handleOutputParameters(CallableStatement cs);
}
ResultSetHandler将数据库查询结果映射为Java对象,它处理了从数据库字段到Java对象属性的转换。
MyBatis的工作流程可以分为初始化阶段和执行阶段两部分。
初始化阶段主要完成以下工作:
// 初始化阶段示例代码
// 1. 读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 2. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 3. 创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(inputStream);
// 至此,初始化阶段完成
在初始化阶段,MyBatis会解析所有XML配置文件,构建内部的Configuration对象,该对象包含了MyBatis运行所需的所有配置信息。
MyBatis会按照以下顺序解析配置文件中的元素:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
properties>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
<typeAliases>
<typeAlias alias="User" type="org.mybatis.example.User"/>
<package name="org.mybatis.example"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="org/mybatis/example/UserMapper.xml"/>
<package name="org.mybatis.example"/>
mappers>
configuration>
映射文件(Mapper.xml)定义了SQL语句和参数映射、结果映射等。MyBatis会解析这些文件,构建内存中的映射关系。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.UserMapper">
<select id="getUserById" resultType="User">
SELECT * FROM users WHERE id = #{id}
select>
<select id="getAllUsers" resultType="User">
SELECT * FROM users
select>
<insert id="insertUser" parameterType="User">
INSERT INTO users (name, email) VALUES (#{name}, #{email})
insert>
<update id="updateUser" parameterType="User">
UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
update>
<delete id="deleteUser" parameterType="int">
DELETE FROM users WHERE id = #{id}
delete>
mapper>
解析过程中,MyBatis会将每个SQL语句解析为一个MappedStatement对象,该对象包含了SQL语句的id、参数映射、结果映射、SQL语句类型等信息。
执行阶段主要完成以下工作:
// 执行阶段示例代码
// 1. 从SqlSessionFactory获取SqlSession
SqlSession session = factory.openSession();
try {
// 2. 获取Mapper接口代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
// 3. 执行SQL语句
User user = userMapper.getUserById(1);
// 4. 处理结果
System.out.println("User found: " + user.getName());
// 执行更新操作
user.setName("New Name");
userMapper.updateUser(user);
// 提交事务
session.commit();
} catch (Exception e) {
// 回滚事务
session.rollback();
} finally {
// 5. 关闭SqlSession
session.close();
}
当我们调用session.getMapper(UserMapper.class)
时,MyBatis会为UserMapper接口创建一个动态代理对象。代理对象会拦截接口方法的调用,转而执行对应的SQL语句。
// MyBatis内部为Mapper创建代理对象的示意代码
public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
// 构造方法等省略...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是Object类方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 从缓存中获取MapperMethod
MapperMethod mapperMethod = methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
// 执行SQL语句
return mapperMethod.execute(sqlSession, args);
}
}
当调用Mapper接口的方法时,MyBatis会执行以下步骤:
// 以查询为例,MyBatis内部执行SQL的简化流程
// 1. 从Configuration获取MappedStatement
MappedStatement ms = configuration.getMappedStatement("org.mybatis.example.UserMapper.getUserById");
// 2. 创建执行器
Executor executor = configuration.newExecutor(transaction, executorType);
// 3. 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
// 4. 准备Statement
Statement stmt = handler.prepare(connection, transactionTimeout);
// 5. 设置参数
handler.parameterize(stmt);
// 6. 执行查询
List<User> users = handler.query(stmt, resultHandler);
// 7. 关闭Statement
stmt.close();
以下是一个完整的查询操作示例,从调用Mapper方法到返回结果的全过程:
// 应用代码
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
// 内部执行流程:
// 1. 获取MapperProxy代理对象
// 2. 调用MapperProxy.invoke方法
// 3. 获取MapperMethod对象
// 4. 调用MapperMethod.execute方法
// 5. 根据方法类型(SELECT)调用SqlSession.selectOne方法
// 6. SqlSession委托给Executor执行查询
// 7. Executor创建StatementHandler
// 8. StatementHandler创建Statement并设置参数
// 9. StatementHandler执行查询并返回结果集
// 10. ResultSetHandler将结果集映射为User对象
// 11. 返回User对象给应用代码
这个流程展示了MyBatis如何将一个简单的接口方法调用转换为复杂的数据库操作,同时隐藏了底层的JDBC细节。
MyBatis的配置文件是其工作的基础,主要包括主配置文件(mybatis-config.xml)和映射文件(Mapper.xml)。
mybatis-config.xml是MyBatis的全局配置文件,它定义了MyBatis的行为方式。
properties元素用于定义属性,这些属性可以在整个配置文件中被引用。
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
properties元素可以从外部文件加载属性,也可以直接在配置文件中定义属性。
settings元素用于更改MyBatis的运行时行为。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" va