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/false | false |
| lazyLoadingEnabled | 延迟加载全局开关 | true/false | false |
| aggressiveLazyLoading | 积极加载 | true/false | false |
| cacheEnabled | 二级缓存开关 | true/false | true |
| logImpl | 日志实现 | SLF4J/LOG4J/LOG4J2… | 未设置 |
| defaultExecutorType | 默认执行器 | SIMPLE/REUSE/BATCH | SIMPLE |
| defaultStatementTimeout | 超时时间(秒) | 正整数 | 无 |
| jdbcTypeForNull | NULL 值 JDBC 类型 | JdbcType 枚举 | OTHER |
| callSettersOnNulls | 空值调用 setter | true/false | false |
| useGeneratedKeys | 使用自动生成主键 | true/false | false |
<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 {
// ...
}
内建类型别名:
| 别名 | 映射的类型 |
|---|---|
| _byte | byte |
| _short | short |
| _int | int |
| _long | long |
| _float | float |
| _double | double |
| _boolean | boolean |
| string | String |
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| boolean | Boolean |
| date | Date |
| decimal | BigDecimal |
| bigdecimal | BigDecimal |
| object | Object |
| map | Map |
| hashmap | HashMap |
| list | List |
| arraylist | ArrayList |
| collection | Collection |
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 方式时,要求:
- Mapper 接口和 Mapper XML 文件必须在同一个包下
- 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/WEAK | LRU |
| flushInterval | 刷新间隔(毫秒) | 正整数 | 不清空 |
| size | 缓存对象数量 | 正整数 | 1024 |
| readOnly | 只读缓存 | true/false | false |
| blocking | 阻塞缓存 | true/false | false |
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:缓存失效问题
问题描述:二级缓存没有生效
解决方案:
- 确认全局配置开启缓存
- 实体类实现 Serializable 接口
- SqlSession 正确关闭
- 检查事务隔离级别
// 实体类必须序列化
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 核心配置是构建高效持久层的基础,本章详细介绍了:
🎯 核心要点
- 配置文件结构:严格遵循元素顺序,合理组织配置
- 属性配置:支持外部化配置,便于环境切换
- 类型系统:灵活的类型别名和类型处理器机制
- 映射器配置:多种注册方式,满足不同需求
- 缓存机制:一级、二级缓存提升查询性能
- 插件系统:强大的拦截器机制,扩展框架功能
📊 配置建议
- 开发环境:开启详细日志,关闭缓存,便于调试
- 生产环境:合理配置缓存,优化连接池,监控 SQL 执行
- 安全考虑:使用参数化查询,防止 SQL 注入
- 性能优化:批量操作、懒加载、合理使用缓存