全部笔记All notes

Web程序设计笔记11——第七章:MyBatis核心配置

阅读 11m 02s11m 02s read

MyBatis 核心配置详解

概述

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。配置文件的顶层结构如下:

configuration(配置)
  ├─ properties(属性)
  ├─ settings(设置)
  ├─ typeAliases(类型别名)
  ├─ typeHandlers(类型处理器)
  ├─ objectFactory(对象工厂)
  ├─ plugins(插件)
  ├─ environments(环境配置)
  │   ├─ environment(环境变量)
  │   │   ├─ transactionManager(事务管理器)
  │   │   └─ dataSource(数据源)
  ├─ databaseIdProvider(数据库厂商标识)
  └─ mappers(映射器)

⚠️ 注意:配置文件中各元素的顺序必须严格遵循上述顺序,否则会抛出异常。

核心对象

1. SqlSessionFactory

SqlSessionFactory 是 MyBatis 的核心对象,它是单例的,负责创建 SqlSession 对象。

public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    
    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    // 获取 SqlSession
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
    
    // 获取自动提交的 SqlSession
    public static SqlSession getSqlSession(boolean autoCommit) {
        return sqlSessionFactory.openSession(autoCommit);
    }
}

2. SqlSession

SqlSession 实例不是线程安全的,因此不能被共享。最佳的作用域是请求或方法作用域。

// 正确的使用方式
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
    CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
    Customer customer = mapper.findCustomerById(1);
    // 其他操作...
    sqlSession.commit();
} catch (Exception e) {
    e.printStackTrace();
    // 发生异常时回滚
}

3. Mapper

Mapper 是绑定映射语句的接口,其实例从 SqlSession 中获取。

// 获取 Mapper
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);

// 使用 Mapper
Customer customer = mapper.findCustomerById(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="username" value="dev_user"/>
        <property name="password" value="F2Fa3!33TYyg"/>
    </properties>
    
    <!-- 2. 全局设置 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
        <setting name="cacheEnabled" value="true"/>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="defaultStatementTimeout" value="25"/>
        <setting name="defaultFetchSize" value="100"/>
    </settings>
    
    <!-- 3. 类型别名 -->
    <typeAliases>
        <typeAlias alias="Customer" type="com.example.entity.Customer"/>
        <package name="com.example.entity"/>
    </typeAliases>
    
    <!-- 4. 类型处理器 -->
    <typeHandlers>
        <typeHandler handler="com.example.handler.MyDateTypeHandler"/>
    </typeHandlers>
    
    <!-- 5. 插件 -->
    <plugins>
        <plugin interceptor="com.example.plugin.PageInterceptor">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
    
    <!-- 6. 环境配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <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="10"/>
                <property name="poolMaximumIdleConnections" value="5"/>
                <property name="poolMaximumCheckoutTime" value="20000"/>
                <property name="poolTimeToWait" value="20000"/>
            </dataSource>
        </environment>
        
        <environment id="production">
            <transactionManager type="MANAGED"/>
            <dataSource type="JNDI">
                <property name="data_source" value="java:comp/env/jdbc/myDB"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 7. 数据库厂商标识 -->
    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="MySQL" value="mysql"/>
        <property name="Oracle" value="oracle"/>
    </databaseIdProvider>
    
    <!-- 8. 映射器 -->
    <mappers>
        <mapper resource="mapper/CustomerMapper.xml"/>
        <mapper class="com.example.mapper.UserMapper"/>
        <package name="com.example.mapper"/>
    </mappers>
</configuration>

各配置元素详解

1. properties(属性)

这些属性可以在外部进行配置,并可以进行动态替换。

<!-- 方式一:引入外部配置文件 -->
<properties resource="db.properties"/>

<!-- 方式二:内部定义属性 -->
<properties resource="db.properties">
    <property name="username" value="dev_user"/>
    <property name="password" value="F2Fa3!33TYyg"/>
</properties>

<!-- 使用属性 -->
<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

db.properties 示例:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=password

2. settings(设置)

这是 MyBatis 中极为重要的调整设置,会改变 MyBatis 的运行时行为。

设置名描述有效值默认值
mapUnderscoreToCamelCase驼峰命名自动映射true/falsefalse
lazyLoadingEnabled延迟加载全局开关true/falsefalse
aggressiveLazyLoading积极加载true/falsefalse
cacheEnabled二级缓存开关true/falsetrue
logImpl日志实现SLF4J/LOG4J/LOG4J2…未设置
defaultExecutorType默认执行器SIMPLE/REUSE/BATCHSIMPLE
defaultStatementTimeout超时时间(秒)正整数无
jdbcTypeForNullNULL 值 JDBC 类型JdbcType 枚举OTHER
callSettersOnNulls空值调用 settertrue/falsefalse
useGeneratedKeys使用自动生成主键true/falsefalse
<settings>
    <!-- 开启驼峰命名自动映射 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 关闭积极加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
    <!-- 指定日志实现 -->
    <setting name="logImpl" value="LOG4J"/>
    <!-- 设置超时时间 -->
    <setting name="defaultStatementTimeout" value="25"/>
    <!-- 设置每次获取数据的条数 -->
    <setting name="defaultFetchSize" value="100"/>
</settings>

3. typeAliases(类型别名)

类型别名可为 Java 类型设置一个缩写名字。

<!-- 单个别名定义 -->
<typeAliases>
    <typeAlias alias="Customer" type="com.example.entity.Customer"/>
    <typeAlias alias="User" type="com.example.entity.User"/>
</typeAliases>

<!-- 包扫描(推荐) -->
<typeAliases>
    <package name="com.example.entity"/>
</typeAliases>

<!-- 使用注解定义别名 -->
@Alias("customer")
public class Customer {
    // ...
}

内建类型别名:

别名映射的类型
_bytebyte
_shortshort
_intint
_longlong
_floatfloat
_doubledouble
_booleanboolean
stringString
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection

4. typeHandlers(类型处理器)

用于在 JDBC 类型和 Java 类型之间转换。

// 自定义类型处理器
@MappedTypes({Date.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class MyDateTypeHandler extends BaseTypeHandler<Date> {
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, 
                                   Date parameter, JdbcType jdbcType) 
                                   throws SQLException {
        ps.setString(i, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                         .format(parameter));
    }
    
    @Override
    public Date getNullableResult(ResultSet rs, String columnName) 
                                 throws SQLException {
        String dateStr = rs.getString(columnName);
        if (dateStr != null) {
            try {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                           .parse(dateStr);
            } catch (ParseException e) {
                throw new SQLException(e);
            }
        }
        return null;
    }
    
    // 其他方法省略...
}
<!-- 注册类型处理器 -->
<typeHandlers>
    <typeHandler handler="com.example.handler.MyDateTypeHandler"/>
    <!-- 包扫描 -->
    <package name="com.example.handler"/>
</typeHandlers>

5. objectFactory(对象工厂)

用于创建结果对象的新实例。

public class MyObjectFactory extends DefaultObjectFactory {
    @Override
    public <T> T create(Class<T> type) {
        System.out.println("创建对象:" + type);
        return super.create(type);
    }
}
<objectFactory type="com.example.factory.MyObjectFactory">
    <property name="someProperty" value="100"/>
</objectFactory>

6. plugins(插件)

MyBatis 允许在映射语句执行过程中的某一点进行拦截调用。

// 分页插件示例
@Intercepts({
    @Signature(type = Executor.class, 
               method = "query", 
               args = {MappedStatement.class, Object.class, 
                      RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 分页逻辑
        return invocation.proceed();
    }
}
<plugins>
    <plugin interceptor="com.example.plugin.PageInterceptor">
        <property name="dialect" value="mysql"/>
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

7. environments(环境配置)

MyBatis 可以配置成适应多种环境。

<environments default="development">
    <!-- 开发环境 -->
    <environment id="development">
        <transactionManager type="JDBC">
            <property name="autoCommit" value="false"/>
        </transactionManager>
        <dataSource type="POOLED">
            <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="10"/>
            <property name="poolMaximumIdleConnections" value="5"/>
            <property name="poolMaximumCheckoutTime" value="20000"/>
            <property name="poolTimeToWait" value="20000"/>
            <property name="poolPingEnabled" value="true"/>
            <property name="poolPingQuery" value="SELECT 1"/>
            <property name="poolPingConnectionsNotUsedFor" value="10000"/>
        </dataSource>
    </environment>
    
    <!-- 生产环境 -->
    <environment id="production">
        <transactionManager type="MANAGED"/>
        <dataSource type="JNDI">
            <property name="data_source" value="java:comp/env/jdbc/myDB"/>
        </dataSource>
    </environment>
</environments>
事务管理器类型
  • JDBC:使用 JDBC 的提交和回滚设置
  • MANAGED:让容器来管理事务的整个生命周期
数据源类型
  • UNPOOLED:每次请求时打开和关闭连接
  • POOLED:使用连接池
  • JNDI:使用容器的 JNDI 数据源

8. databaseIdProvider(数据库厂商标识)

支持多数据库厂商的 SQL 语句。

<databaseIdProvider type="DB_VENDOR">
    <property name="SQL Server" value="sqlserver"/>
    <property name="DB2" value="db2"/>
    <property name="Oracle" value="oracle"/>
    <property name="MySQL" value="mysql"/>
    <property name="PostgreSQL" value="postgresql"/>
    <property name="H2" value="h2"/>
</databaseIdProvider>

使用示例:

<!-- 针对不同数据库的 SQL -->
<select id="selectCustomer" resultType="Customer">
    SELECT * FROM customer
</select>

<select id="selectCustomer" databaseId="mysql" resultType="Customer">
    SELECT * FROM customer LIMIT 10
</select>

<select id="selectCustomer" databaseId="oracle" resultType="Customer">
    SELECT * FROM customer WHERE ROWNUM <= 10
</select>

9. mappers(映射器)

告诉 MyBatis 去哪里找映射文件。

<mappers>
    <!-- 使用相对路径的资源引用 -->
    <mapper resource="mapper/CustomerMapper.xml"/>
    <mapper resource="mapper/OrderMapper.xml"/>
    
    <!-- 使用完全限定资源定位符(URL) -->
    <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mapper class="com.example.mapper.BlogMapper"/>
    
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <package name="com.example.mapper"/>
</mappers>

⚠️ 注意:使用 package 方式时,要求:

  1. Mapper 接口和 Mapper XML 文件必须在同一个包下
  2. Mapper 接口和 Mapper XML 文件的名字必须一致

Mapper 映射配置

1. Mapper 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">
        
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 配置内容 -->
</mapper>
核心元素
元素说明使用场景
<select>查询语句数据查询
<insert>插入语句新增数据
<update>更新语句修改数据
<delete>删除语句删除数据
<resultMap>结果映射复杂结果集映射
<sql>可重用的 SQL 片段SQL 复用
<cache>二级缓存配置性能优化

2. ResultMap 高级映射

<!-- 复杂的结果映射 -->
<resultMap id="userResultMap" type="User">
    <!-- 主键映射 -->
    <id property="id" column="user_id" />
    <!-- 普通属性映射 -->
    <result property="username" column="user_name" />
    <result property="email" column="user_email" />
    <!-- 一对一关联 -->
    <association property="profile" javaType="UserProfile">
        <id property="profileId" column="profile_id" />
        <result property="nickname" column="nickname" />
    </association>
    <!-- 一对多关联 -->
    <collection property="orders" ofType="Order">
        <id property="orderId" column="order_id" />
        <result property="orderNo" column="order_no" />
        <result property="amount" column="order_amount" />
    </collection>
</resultMap>

<!-- 使用 ResultMap -->
<select id="selectUserWithDetails" resultMap="userResultMap">
    SELECT 
        u.user_id,
        u.user_name,
        u.user_email,
        p.profile_id,
        p.nickname,
        o.order_id,
        o.order_no,
        o.order_amount
    FROM user u
    LEFT JOIN user_profile p ON u.user_id = p.user_id
    LEFT JOIN orders o ON u.user_id = o.user_id
    WHERE u.user_id = #{userId}
</select>

3. 动态 SQL 配置

<!-- 条件查询 -->
<select id="findUsers" resultType="User">
    SELECT * FROM user
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</select>

<!-- 批量操作 -->
<insert id="batchInsert" parameterType="list">
    INSERT INTO user (username, email, status) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.email}, #{user.status})
    </foreach>
</insert>

<!-- 更新操作 -->
<update id="updateUser" parameterType="User">
    UPDATE user
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="email != null">email = #{email},</if>
        <if test="status != null">status = #{status},</if>
    </set>
    WHERE id = #{id}
</update>

缓存配置

1. 一级缓存(SqlSession 级别)

一级缓存是 MyBatis 的默认缓存,作用域是 SqlSession。

// 一级缓存示例
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    // 第一次查询,从数据库获取
    User user1 = mapper.findById(1);
    System.out.println("第一次查询:" + user1);
    
    // 第二次查询,从一级缓存获取
    User user2 = mapper.findById(1);
    System.out.println("第二次查询:" + user2);
    
    // user1 == user2 为 true
    System.out.println("同一个对象:" + (user1 == user2));
}

2. 二级缓存(Mapper 级别)

二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享。

开启二级缓存
<!-- 在 mybatis-config.xml 中开启 -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
Mapper 配置
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache 
        eviction="LRU"
        flushInterval="60000"
        size="512"
        readOnly="true"/>
    
    <!-- 或使用自定义缓存 -->
    <cache type="com.example.cache.MyCache">
        <property name="cacheFile" value="/tmp/my-cache"/>
    </cache>
</mapper>
缓存属性说明
属性说明可选值默认值
eviction缓存回收策略LRU/FIFO/SOFT/WEAKLRU
flushInterval刷新间隔(毫秒)正整数不清空
size缓存对象数量正整数1024
readOnly只读缓存true/falsefalse
blocking阻塞缓存true/falsefalse

3. 自定义缓存

public class MyCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new ConcurrentHashMap<>();
    
    public MyCache(String id) {
        this.id = id;
    }
    
    @Override
    public String getId() {
        return id;
    }
    
    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }
    
    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }
    
    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }
    
    @Override
    public void clear() {
        cache.clear();
    }
    
    @Override
    public int getSize() {
        return cache.size();
    }
}

插件配置

1. 插件原理

MyBatis 允许在映射语句执行过程中的某些点进行拦截:

  • Executor:执行器
  • ParameterHandler:参数处理器
  • ResultSetHandler:结果集处理器
  • StatementHandler:语句处理器

2. 分页插件示例

@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, 
                RowBounds.class, ResultHandler.class}
    )
})
public class PageInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        
        // 判断是否需要分页
        if (rowBounds != RowBounds.DEFAULT) {
            // 获取原始 SQL
            BoundSql boundSql = ms.getBoundSql(parameter);
            String sql = boundSql.getSql();
            
            // 获取总数
            String countSql = "SELECT COUNT(*) FROM (" + sql + ") tmp";
            // 执行 count 查询...
            
            // 构建分页 SQL
            String pageSql = sql + " LIMIT " + rowBounds.getOffset() 
                           + ", " + rowBounds.getLimit();
            
            // 重新设置 SQL
            // ...
        }
        
        return invocation.proceed();
    }
    
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    
    @Override
    public void setProperties(Properties properties) {
        // 设置插件属性
    }
}

3. 注册插件

<plugins>
    <plugin interceptor="com.example.plugin.PageInterceptor">
        <property name="dialect" value="mysql"/>
        <property name="reasonable" value="true"/>
    </plugin>
    
    <!-- SQL 执行时间统计插件 -->
    <plugin interceptor="com.example.plugin.PerformanceInterceptor">
        <property name="maxTime" value="100"/>
    </plugin>
</plugins>

高级配置

1. 多数据源配置

@Configuration
public class MyBatisConfig {
    
    @Bean
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(
            @Qualifier("primaryDataSource") DataSource dataSource) 
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        bean.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/primary/*.xml")
        );
        return bean.getObject();
    }
    
    @Bean
    public SqlSessionFactory secondarySqlSessionFactory(
            @Qualifier("secondaryDataSource") DataSource dataSource) 
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setConfigLocation(new ClassPathResource("mybatis-config-secondary.xml"));
        bean.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/secondary/*.xml")
        );
        return bean.getObject();
    }
}

2. 自定义类型处理器

// 处理 JSON 类型
@MappedTypes(JSONObject.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonTypeHandler extends BaseTypeHandler<JSONObject> {
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, 
                                   JSONObject parameter, 
                                   JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.toJSONString());
    }
    
    @Override
    public JSONObject getNullableResult(ResultSet rs, String columnName) 
                                       throws SQLException {
        String json = rs.getString(columnName);
        return json != null ? JSON.parseObject(json) : null;
    }
    
    // 其他方法实现...
}

3. 全局配置增强

@Configuration
public class MyBatisConfiguration {
    
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
            // 设置驼峰命名
            configuration.setMapUnderscoreToCamelCase(true);
            // 设置默认枚举处理器
            configuration.setDefaultEnumTypeHandler(EnumOrdinalTypeHandler.class);
            // 添加类型处理器
            configuration.getTypeHandlerRegistry()
                        .register(JSONObject.class, new JsonTypeHandler());
            // 添加拦截器
            configuration.addInterceptor(new PerformanceInterceptor());
        };
    }
}
    <mapper resource="CustomerMapper.xml"/>
</mappers>

最佳实践

1. 配置文件管理

环境隔离
<!-- mybatis-config-dev.xml -->
<environments default="development">
    <environment id="development">
        <dataSource type="POOLED">
            <property name="url" value="jdbc:mysql://localhost:3306/dev_db"/>
        </dataSource>
    </environment>
</environments>

<!-- mybatis-config-prod.xml -->
<environments default="production">
    <environment id="production">
        <dataSource type="POOLED">
            <property name="url" value="jdbc:mysql://prod-server:3306/prod_db"/>
        </dataSource>
    </environment>
</environments>
属性外部化
# database.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=${DB_URL:jdbc:mysql://localhost:3306/mybatis}
jdbc.username=${DB_USER:root}
jdbc.password=${DB_PASSWORD:password}
jdbc.pool.maxActive=20
jdbc.pool.maxIdle=10

2. 性能优化建议

SQL 优化
<!-- 避免 N+1 查询问题 -->
<select id="selectUsersWithOrders" resultMap="userOrderMap">
    SELECT 
        u.*, 
        o.order_id, 
        o.order_no, 
        o.amount
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.status = 1
</select>

<!-- 使用批量操作 -->
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user (username, email) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.email})
    </foreach>
</insert>
缓存策略
public class CacheConfig {
    // 查询频繁但修改少的数据适合缓存
    @Cacheable("users")
    public User findById(Long id) {
        return userMapper.findById(id);
    }
    
    // 修改数据时清除缓存
    @CacheEvict(value = "users", key = "#user.id")
    public void updateUser(User user) {
        userMapper.update(user);
    }
}

3. 安全性配置

SQL 注入防护
<!-- 正确:使用 #{} 参数化查询 -->
<select id="findByUsername" resultType="User">
    SELECT * FROM user WHERE username = #{username}
</select>

<!-- 错误:使用 ${} 可能导致 SQL 注入 -->
<select id="findByUsernameUnsafe" resultType="User">
    SELECT * FROM user WHERE username = '${username}'
</select>

<!-- 动态表名的安全处理 -->
<select id="selectFromTable" resultType="map">
    SELECT * FROM 
    <choose>
        <when test="tableName == 'user'">user</when>
        <when test="tableName == 'order'">orders</when>
        <otherwise>user</otherwise>
    </choose>
    WHERE id = #{id}
</select>

4. 日志配置

<!-- 开发环境详细日志 -->
<settings>
    <setting name="logImpl" value="LOG4J2"/>
    <setting name="logLevel" value="DEBUG"/>
</settings>

<!-- log4j2.xml -->
<Configuration>
    <Loggers>
        <!-- MyBatis 日志 -->
        <Logger name="com.example.mapper" level="DEBUG"/>
        <Logger name="org.apache.ibatis" level="DEBUG"/>
        <Logger name="java.sql" level="DEBUG"/>
    </Loggers>
</Configuration>

常见问题

问题 1:配置文件找不到

错误信息:

java.io.IOException: Could not find resource mybatis-config.xml

解决方案:

// 确保配置文件在 classpath 下
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

// 或使用绝对路径
InputStream inputStream = new FileInputStream("/path/to/mybatis-config.xml");

问题 2:类型转换异常

错误信息:

TypeException: Could not set parameters for mapping

解决方案:

<!-- 注册自定义类型处理器 -->
<typeHandlers>
    <typeHandler handler="com.example.handler.MyTypeHandler"
                 javaType="com.example.MyType"
                 jdbcType="VARCHAR"/>
</typeHandlers>

问题 3:缓存失效问题

问题描述:二级缓存没有生效

解决方案:

  1. 确认全局配置开启缓存
  2. 实体类实现 Serializable 接口
  3. SqlSession 正确关闭
  4. 检查事务隔离级别
// 实体类必须序列化
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // ...
}

问题 4:多数据源事务问题

解决方案:

@Transactional(transactionManager = "primaryTransactionManager")
public void primaryOperation() {
    // 主数据源操作
}

@Transactional(transactionManager = "secondaryTransactionManager")
public void secondaryOperation() {
    // 副数据源操作
}

问题 5:延迟加载异常

错误信息:

LazyInitializationException: could not initialize proxy

解决方案:

<settings>
    <!-- 全局启用延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 按需加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 触发延迟加载的方法 -->
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

总结

MyBatis 核心配置是构建高效持久层的基础,本章详细介绍了:

🎯 核心要点

  1. 配置文件结构:严格遵循元素顺序,合理组织配置
  2. 属性配置:支持外部化配置,便于环境切换
  3. 类型系统:灵活的类型别名和类型处理器机制
  4. 映射器配置:多种注册方式,满足不同需求
  5. 缓存机制:一级、二级缓存提升查询性能
  6. 插件系统:强大的拦截器机制,扩展框架功能

📊 配置建议

  • 开发环境:开启详细日志,关闭缓存,便于调试
  • 生产环境:合理配置缓存,优化连接池,监控 SQL 执行
  • 安全考虑:使用参数化查询,防止 SQL 注入
  • 性能优化:批量操作、懒加载、合理使用缓存

相关文章

前后章节导航