mybatis基本配置及执行

MyBatis 基础配置与执行流程全解析(从入门到实践)

MyBatis 作为 Java 生态中轻量级且灵活的持久层框架,其核心是通过配置文件解耦 SQL 与业务代码,同时简化 JDBC 冗余操作。配置细节优化、执行流程原理、工程化实践常见问题排查,帮助你彻底掌握 MyBatis 的基础使用与核心逻辑。

配置文件:从基础到优化

MyBatis 的配置体系分为「全局配置文件(mybatis-config.xml)」和「映射文件(XxxMapper.xml)」,两者分工明确 —— 全局配置负责框架级参数,映射文件负责业务 SQL 与实体映射。

全局配置文件(mybatis-config.xml)深度解读

(1)全局配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 1. 引入外部属性文件(解耦数据库配置,支持多环境切换) -->
    <properties resource="db.properties">
        <!-- 可选:默认值(外部文件未定义时生效) -->
        <property name="jdbc.username" value="default_root"/>
    </properties>

    <!-- 2. 全局设置(控制MyBatis核心行为,必配优化项) -->
    <settings>
        <!-- 开启驼峰命名自动映射(数据库字段user_name → Java属性userName) -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启SQL日志打印(开发环境调试用,生产环境可关闭或改用SLF4J) -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 开启二级缓存(提升查询性能,需配合映射文件<cache>使用) -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 关闭激进延迟加载(按需加载关联数据,避免冗余查询) -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    <!-- 3. 类型别名(简化映射文件中类名书写,避免全类名冗余) -->
    <typeAliases>
        <!-- 方式1:单个类指定别名(alias可自定义,不写则默认类名首字母小写) -->
        <typeAlias type="com.zhanghe.study.mybatis.model.User" alias="User"/>
        <!-- 方式2:批量扫描包(推荐!包下所有类默认别名为类名首字母小写,如User→user) -->
        <package name="com.zhanghe.study.mybatis.model"/>
    </typeAliases>

    <!-- 4. 数据库环境配置(支持多环境,default指定默认环境) -->
    <environments default="development">
        <!-- 开发环境 -->
        <environment id="development">
            <!-- 事务管理器:JDBC(依赖数据库原生事务)/ MANAGED(交给容器如Spring管理) -->
            <transactionManager type="JDBC"/>
            <!-- 数据源:POOLED(连接池,生产推荐)/ UNPOOLED(无池化)/ JNDI(容器数据源) -->
            <dataSource type="POOLED">
                <!-- 引用外部properties文件中的配置(${key}) -->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
                <!-- 连接池优化参数 -->
                <property name="poolMaximumActiveConnections" value="20"/> <!-- 最大活跃连接数 -->
                <property name="poolMaximumIdleConnections" value="5"/>    <!-- 最大空闲连接数 -->
            </dataSource>
        </environment>
        <!-- 生产环境(可单独配置,切换时修改default值) -->
        <environment id="production">
            <transactionManager type="MANAGED"/>
            <dataSource type="POOLED">
                <property name="driver" value="${prod.jdbc.driver}"/>
                <property name="url" value="${prod.jdbc.url}"/>
                <property name="username" value="${prod.jdbc.username}"/>
                <property name="password" value="${prod.jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 5. 映射文件扫描(告诉MyBatis去哪里找SQL映射) -->
    <mappers>
        <!-- 方式1:单个映射文件(resource路径基于classpath,如src/main/resources/mapper/UserMapper.xml) -->
        <mapper resource="mapper/UserMapper.xml"/>
        <!-- 方式2:批量扫描Mapper接口(推荐!需满足「接口与映射文件同包同名」) -->
        <!-- <package name="com.zhanghe.study.mybatis.mapper"/> -->
    </mappers>
</configuration>
(2)外部属性文件(db.properties

将数据库配置单独抽取到 src/main/resources/db.properties,便于维护和环境切换:

# 开发环境
jdbc.driver=com.mysql.cj.jdbc.Driver  # MySQL 8.0+需用cj驱动,5.x用com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456

# 生产环境
prod.jdbc.driver=com.mysql.cj.jdbc.Driver
prod.jdbc.url=jdbc:mysql://192.168.1.100:3306/prod_db?useSSL=true&serverTimezone=UTC
prod.jdbc.username=prod_user
prod.jdbc.password=prod_123456

映射文件(UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace必须与Mapper接口全类名一致(绑定接口) -->
<mapper namespace="com.zhanghe.study.mybatis.mapper.UserMapper">
    <!-- 可选:开启当前Mapper的二级缓存(需全局settings中cacheEnabled=true) -->
    <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>

    <!-- 结果映射(ResultMap):解决「表字段与实体属性名不一致」或「复杂关联查询」 -->
    <!-- 若开启驼峰映射(mapUnderscoreToCamelCase=true),简单场景可省略ResultMap -->
    <resultMap id="UserResultMap" type="User"> <!-- type用别名(全局配置中定义) -->
        <id column="user_id" property="userId"/> <!-- 主键映射 -->
        <result column="user_name" property="userName"/> <!-- 普通字段映射 -->
        <result column="user_age" property="userAge"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
    </resultMap>

    <!-- 1. 基础查询:根据ID查用户(使用ResultMap) -->
    <select id="selectUser" parameterType="Integer" resultMap="UserResultMap">
        SELECT user_id, user_name, user_age, create_time 
        FROM users 
        WHERE user_id = #{id}  <!-- #{id}:预编译占位符,防SQL注入 -->
    </select>

    <!-- 2. 多参数查询:按姓名和年龄查用户(用@Param注解) -->
    <select id="selectUserByNameAndAge" resultMap="UserResultMap">
        SELECT * FROM users 
        WHERE user_name = #{name} AND user_age = #{age}
        <!-- 接口方法需加@Param:User selectUserByNameAndAge(@Param("name") String name, @Param("age") Integer age) -->
    </select>

    <!-- 3. 插入:新增用户并返回自增主键 -->
    <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="userId">
        <!-- useGeneratedKeys=true:开启自增主键返回;keyProperty:主键对应实体属性名 -->
        INSERT INTO users (user_name, user_age, create_time)
        VALUES (#{userName}, #{userAge}, #{createTime})
    </insert>
</mapper>
对应的 Mapper 接口(UserMapper.java

接口方法需与映射文件的 id、参数类型、返回值类型完全匹配

package com.zhanghe.study.mybatis.mapper;

import com.zhanghe.study.mybatis.model.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
    // 根据ID查用户(对应映射文件selectUser)
    User selectUser(Integer userId);
    
    // 多参数查询(需@Param注解绑定参数名)
    User selectUserByNameAndAge(@Param("name") String userName, @Param("age") Integer userAge);
    
    // 新增用户(返回自增主键注入到User的userId属性)
    int insertUser(User user);
}

执行流程:从配置加载到 SQL 执行

MyBatis 的执行流程可拆解为「配置加载→核心对象创建→SQL 执行→资源释放」四步,每一步都有明确的职责和生命周期管理要求。

核心流程拆解

(1)步骤 1:加载配置,创建 SqlSessionFactory

SqlSessionFactory 是 MyBatis 的 “工厂”,负责创建 SqlSession,其生命周期是全局唯一(应用启动时创建一次,避免重复创建导致连接泄漏)。

public static SqlSessionFactory createFactory() {
    try {
        // 1. 读取全局配置文件(Resources是MyBatis提供的工具类,简化classpath资源加载)
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 2. 通过SqlSessionFactoryBuilder构建SqlSessionFactory
        // (SqlSessionFactoryBuilder用完即丢,无需长期持有)
        return new SqlSessionFactoryBuilder().build(is);
    } catch (IOException e) {
        throw new RuntimeException("MyBatis配置文件加载失败!", e);
    }
}

关键原理
SqlSessionFactoryBuilder 会解析 mybatis-config.xml 和所有 Mapper.xml,将配置信息封装到 Configuration 对象中,最终基于 Configuration 创建 SqlSessionFactory(默认实现是 DefaultSqlSessionFactory)。

(2)步骤 2:创建 SqlSession

SqlSession 相当于 JDBC 的 Connection,代表一次数据库会话,生命周期是单次业务请求(线程不安全,需及时关闭)。

// 方式1:自动提交事务(openSession(true))
SqlSession session = sqlSessionFactory.openSession(true);

// 方式2:手动提交事务(默认,适合多SQL操作需事务一致性场景)
SqlSession session = sqlSessionFactory.openSession();
try {
    // 执行SQL
    session.commit(); // 手动提交
} catch (Exception e) {
    session.rollback(); // 异常回滚
} finally {
    session.close(); // 关闭会话
}

核心注意事项

  • SqlSession 线程不安全,不可跨线程共享;
  • 必须在 finally 块中关闭 SqlSession,避免数据库连接泄漏;
  • 手动提交模式下,未调用 commit() 会导致事务未提交,数据不生效。
(3)步骤 3:执行 SQL(新旧版本对比)

MyBatis 提供两种执行 SQL 的方式,新版本的「Mapper 接口代理」是主流(类型安全、可读性强)。

执行方式 代码示例 优缺点对比
老版本(SqlSession 直接调用) User user = session.selectOne("com.zhanghe.study.mybatis.mapper.UserMapper.selectUser", 2); 优点:无需定义接口; 缺点:字符串硬编码(易写错)、无类型校验、可读性差。
新版本(Mapper 接口代理) UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectUser(2); 优点:类型安全(编译期校验)、代码简洁、可维护性强; 缺点:需定义接口。

原理补充
MyBatis 会为 Mapper 接口动态生成代理对象(基于 JDK 动态代理),调用接口方法时,代理对象会解析 namespace + methodName 找到对应的 SQL,执行后返回结果。

(4)步骤 4:资源释放

无论 SQL 执行成功或失败,都需关闭 SqlSession,释放数据库连接(归还到连接池):

SqlSession session = null;
try {
    session = sqlSessionFactory.openSession(true);
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectUser(2);
    System.out.println(user);
} finally {
    if (session != null) {
        session.close(); // 关闭SqlSession,释放连接
    }
}

工程化优化:工具类封装

为避免重复代码,推荐封装 MyBatisUtil 工具类,统一管理 SqlSessionFactorySqlSession

package com.zhanghe.study.mybatis.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {
    // 静态变量:确保SqlSessionFactory全局唯一
    private static SqlSessionFactory sqlSessionFactory;

    // 静态代码块:应用启动时初始化SqlSessionFactory
    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream is = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        } catch (IOException e) {
            throw new ExceptionInInitializerError("MyBatis初始化失败!", e);
        }
    }

    // 获取SqlSession(自动提交事务,简化单SQL操作)
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession(true);
    }

    // 关闭SqlSession
    public static void closeSqlSession(SqlSession session) {
        if (session != null) {
            session.close();
        }
    }
}

工具类使用示例

// 业务代码中使用工具类
SqlSession session = null;
try {
    session = MyBatisUtil.getSqlSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectUser(2);
    System.out.println(user);
} finally {
    MyBatisUtil.closeSqlSession(session);
}

常见问题与解决方案

1. 映射文件找不到(Invalid bound statement (not found)

  • 原因 1mybatis-config.xml<mappers> 配置路径错误(如 resource 路径不是 classpath 下的路径);
  • 原因 2:Mapper 接口与映射文件不同包同名(如接口在 com.mapper,映射文件需在 resources/com/mapper 下且名为 UserMapper.xml);
  • 解决方案:使用 <package name="com.zhanghe.study.mybatis.mapper"/> 批量扫描 Mapper 接口,确保接口与映射文件同包同名。

2. 驼峰命名不生效(数据库字段 user_name → 实体属性 userNamenull

  • 原因:未开启全局设置 mapUnderscoreToCamelCase

  • 解决方案:在mybatis-config.xml的<settings>中添加:

    <setting name="mapUnderscoreToCamelCase" value="true"/>

3. SQL 注入风险(使用 ${} 占位符)

  • 原因${} 是字符串直接拼接(如 ${id} → 替换为参数值,无预编译),#{} 是预编译占位符(推荐);
  • 解决方案:除动态表名 / 排序字段(如 ORDER BY ${field})外,全部使用 #{}

4. SqlSession 未关闭导致连接泄漏

  • 原因:未在 finally 块中关闭 SqlSession,异常时资源未释放;
  • 解决方案:使用工具类的 closeSqlSession 方法,在 finally 中强制关闭。

总结

MyBatis 的基础使用核心是 “配置 + 接口 + 映射”:

  1. 配置文件:全局配置管理数据库和框架参数,映射文件定义 SQL 和实体映射;
  2. 执行流程SqlSessionFactory(全局唯一)→ SqlSession(单次会话)→ Mapper 代理(执行 SQL);
  3. 最佳实践:封装工具类管理核心对象、开启驼峰映射简化配置、使用接口代理避免硬编码