1. MyBatis入门:从零搭建开发环境
作为一名Java开发者,我至今记得第一次接触MyBatis时的困惑与惊喜。当时面对一个遗留的JDBC项目,那些重复的模板代码和难以维护的SQL让我头疼不已。直到同事推荐了MyBatis,这个轻量级的持久层框架彻底改变了我的开发方式。今天,我将带你从零开始,完整走一遍MyBatis的入门之路。
1.1 开发环境准备
在开始之前,我们需要准备以下环境:
- JDK 1.8或更高版本(我推荐使用Amazon Corretto 11)
- Maven 3.6+(项目管理工具)
- IntelliJ IDEA(社区版就足够)
- MySQL 5.7+(或其他你熟悉的数据库)
注意:虽然MyBatis支持多种数据库,但不同数据库的SQL语法可能有差异。本文示例基于MySQL 8.0,如果你使用其他数据库,可能需要调整部分SQL语句。
安装完基础环境后,创建一个新的Maven项目。在pom.xml中添加MyBatis核心依赖:
xml复制<dependencies>
<!-- MyBatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 日志框架(可选) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
</dependencies>
1.2 配置MyBatis核心文件
在resources目录下创建mybatis-config.xml文件,这是MyBatis的主配置文件:
xml复制<?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>
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 打印SQL语句 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 这里稍后添加Mapper文件 -->
</mappers>
</configuration>
1.3 创建第一个实体类和Mapper接口
让我们从一个简单的用户管理示例开始。首先创建User实体类:
java复制public class User {
private Long id;
private String username;
private String password;
private String email;
private Date createTime;
// 省略getter/setter和toString方法
}
接着创建UserMapper接口:
java复制public interface UserMapper {
User selectUserById(Long id);
List<User> selectAllUsers();
int insertUser(User user);
int updateUser(User user);
int deleteUser(Long id);
}
1.4 编写第一个Mapper XML文件
在resources目录下创建mapper/UserMapper.xml:
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">
<resultMap id="userResultMap" type="com.example.entity.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="createTime" column="create_time"/>
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
SELECT * FROM user WHERE id = #{id}
</select>
<select id="selectAllUsers" resultMap="userResultMap">
SELECT * FROM user
</select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, email, create_time)
VALUES (#{username}, #{password}, #{email}, #{createTime})
</insert>
<update id="updateUser">
UPDATE user
SET username = #{username}, password = #{password}, email = #{email}
WHERE id = #{id}
</update>
<delete id="deleteUser">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
别忘了在mybatis-config.xml的
xml复制<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
1.5 初始化SqlSessionFactory并测试
现在我们可以编写测试代码了。创建一个工具类MyBatisUtil:
java复制public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException("初始化SqlSessionFactory失败", e);
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
编写测试代码:
java复制public class MyBatisTest {
public static void main(String[] args) {
// 初始化数据库表
initDatabase();
try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 测试插入
User newUser = new User();
newUser.setUsername("testUser");
newUser.setPassword("123456");
newUser.setEmail("test@example.com");
newUser.setCreateTime(new Date());
int insertCount = userMapper.insertUser(newUser);
System.out.println("插入记录数: " + insertCount);
sqlSession.commit(); // 不要忘记提交事务
// 测试查询
User user = userMapper.selectUserById(newUser.getId());
System.out.println("查询结果: " + user);
// 测试查询所有
List<User> users = userMapper.selectAllUsers();
System.out.println("所有用户: " + users);
}
}
private static void initDatabase() {
try (SqlSession sqlSession = MyBatisUtil.getSqlSession();
Connection conn = sqlSession.getConnection()) {
Statement stmt = conn.createStatement();
stmt.execute("DROP TABLE IF EXISTS user");
stmt.execute("CREATE TABLE user (" +
"id BIGINT PRIMARY KEY AUTO_INCREMENT," +
"username VARCHAR(50) NOT NULL," +
"password VARCHAR(50) NOT NULL," +
"email VARCHAR(100)," +
"create_time DATETIME" +
")");
sqlSession.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MyBatis核心概念深度解析
2.1 SqlSessionFactory与SqlSession
SqlSessionFactory是MyBatis的核心接口,它的主要职责是创建SqlSession实例。每个基于MyBatis的应用都以一个SqlSessionFactory实例为中心,这个实例应该是单例的,通常在整个应用生命周期内只创建一次。
SqlSession代表一次数据库会话,它包含了执行SQL所需的所有方法。你可以把它看作JDBC中的Connection对象,但提供了更高级的抽象。SqlSession不是线程安全的,因此最佳实践是在方法内部创建,使用完毕后立即关闭。
经验分享:在实际项目中,我通常会使用ThreadLocal来管理SqlSession,确保每个线程有自己的SqlSession实例,避免并发问题。
2.2 Mapper接口与XML的绑定机制
MyBatis最强大的特性之一就是它能够将接口方法与XML映射语句自动绑定。这种绑定是通过动态代理实现的:
- 当调用
sqlSession.getMapper(UserMapper.class)时,MyBatis会创建一个动态代理对象 - 这个代理对象会拦截接口方法的调用
- 根据方法名和参数,找到对应的XML映射语句
- 执行SQL并将结果转换为Java对象
这种设计使得我们可以像调用普通Java方法一样执行数据库操作,而不需要编写繁琐的JDBC代码。
2.3 参数传递与结果映射
MyBatis提供了灵活的参数传递方式:
- 基本类型参数:直接使用
#{param}引用 - 对象参数:使用
#{propertyName}引用对象属性 - Map参数:使用
#{key}引用Map中的值 - 集合/数组:在动态SQL中特别有用
结果映射同样强大:
- 自动映射:当数据库列名与Java属性名匹配时(通过驼峰转换),MyBatis可以自动完成映射
- 显式映射:使用
<resultMap>定义复杂的映射关系 - 嵌套映射:处理一对一、一对多等关联关系
2.4 动态SQL详解
MyBatis的动态SQL功能允许我们在XML中编写条件SQL语句。常用的元素包括:
<if>:条件判断<choose>/<when>/<otherwise>:多条件选择<foreach>:遍历集合<where>:智能处理WHERE子句<set>:智能处理UPDATE语句
示例:
xml复制<select id="selectUsersByCondition" resultMap="userResultMap">
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="createTimeStart != null">
AND create_time >= #{createTimeStart}
</if>
<if test="createTimeEnd != null">
AND create_time <= #{createTimeEnd}
</if>
</where>
ORDER BY create_time DESC
</select>
3. MyBatis基础操作实战
3.1 CRUD操作完整示例
让我们完善UserMapper接口和对应的XML映射文件,实现完整的CRUD功能。
首先更新UserMapper接口:
java复制public interface UserMapper {
// 基本CRUD
User selectUserById(Long id);
List<User> selectAllUsers();
int insertUser(User user);
int updateUser(User user);
int deleteUser(Long id);
// 条件查询
List<User> selectUsersByCondition(@Param("username") String username,
@Param("email") String email,
@Param("createTimeStart") Date createTimeStart,
@Param("createTimeEnd") Date createTimeEnd);
// 批量操作
int batchInsertUsers(List<User> users);
int batchUpdateUsers(List<User> users);
int batchDeleteUsers(List<Long> ids);
// 复杂查询
List<User> selectUsersWithPagination(@Param("offset") int offset,
@Param("pageSize") int pageSize);
int selectUserCount();
}
更新UserMapper.xml文件:
xml复制<!-- 添加在原有内容之后 -->
<select id="selectUsersByCondition" resultMap="userResultMap">
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="createTimeStart != null">
AND create_time >= #{createTimeStart}
</if>
<if test="createTimeEnd != null">
AND create_time <= #{createTimeEnd}
</if>
</where>
ORDER BY create_time DESC
</select>
<insert id="batchInsertUsers" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, email, create_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.password}, #{user.email}, #{user.createTime})
</foreach>
</insert>
<update id="batchUpdateUsers">
<foreach collection="list" item="user" separator=";">
UPDATE user
SET
username = #{user.username},
password = #{user.password},
email = #{user.email}
WHERE id = #{user.id}
</foreach>
</update>
<delete id="batchDeleteUsers">
DELETE FROM user WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="selectUsersWithPagination" resultMap="userResultMap">
SELECT * FROM user
ORDER BY id
LIMIT #{offset}, #{pageSize}
</select>
<select id="selectUserCount" resultType="int">
SELECT COUNT(*) FROM user
</select>
3.2 事务管理实践
MyBatis本身不管理事务,它依赖于底层的事务管理器。在mybatis-config.xml中,我们配置了<transactionManager type="JDBC"/>,这意味着MyBatis将使用JDBC的事务管理方式。
基本的事务操作模式:
java复制SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行数据库操作
userMapper.insertUser(user1);
userMapper.updateUser(user2);
// 提交事务
sqlSession.commit();
} catch (Exception e) {
// 回滚事务
sqlSession.rollback();
throw e;
} finally {
// 关闭会话
sqlSession.close();
}
重要提示:在实际项目中,我强烈建议使用Spring框架来管理事务,它提供了声明式事务管理,比手动管理更加方便和安全。
3.3 日志配置与SQL调试
MyBatis可以与多种日志框架集成。我们已经在前面的pom.xml中添加了SLF4J和Logback依赖,现在来配置日志输出。
在resources目录下创建logback.xml:
xml复制<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- MyBatis日志配置 -->
<logger name="com.example.mapper" level="DEBUG"/>
<logger name="org.mybatis" level="INFO"/>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
这样配置后,我们可以在控制台看到执行的SQL语句及其参数,这对调试非常有帮助。
4. 常见问题与性能优化
4.1 解决N+1查询问题
N+1查询问题是ORM框架中常见的性能问题。例如,当我们查询用户列表,然后对每个用户查询其订单时,就会产生N+1条查询(1条查询用户,N条查询订单)。
解决方案是使用关联查询:
xml复制<resultMap id="userWithOrdersResultMap" type="User" extends="userResultMap">
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
<result property="createTime" column="order_create_time"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrdersResultMap">
SELECT
u.*,
o.id as order_id,
o.order_no,
o.amount,
o.create_time as order_create_time
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
4.2 一级缓存与二级缓存
MyBatis提供两级缓存来提高性能:
-
一级缓存(本地缓存):
- 默认开启,作用域为SqlSession
- 同一个SqlSession中相同的查询会直接从缓存返回结果
- 执行insert/update/delete操作或调用clearCache()会清空缓存
-
二级缓存(全局缓存):
- 需要手动开启,作用域为Mapper namespace
- 多个SqlSession可以共享缓存
- 配置方式:
xml复制<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
经验之谈:在实际项目中,我通常谨慎使用二级缓存,因为跨SqlSession的缓存同步可能带来一致性问题。对于读多写少且对实时性要求不高的数据,可以考虑使用Redis等分布式缓存代替MyBatis的二级缓存。
4.3 分页查询优化
MyBatis有多种分页实现方式:
-
内存分页(不推荐):
java复制
List<User> allUsers = userMapper.selectAllUsers(); List<User> pageList = allUsers.stream() .skip(offset) .limit(pageSize) .collect(Collectors.toList()); -
SQL分页(推荐):
xml复制<select id="selectUsersWithPagination" resultMap="userResultMap"> SELECT * FROM user ORDER BY id LIMIT #{offset}, #{pageSize} </select> -
使用PageHelper插件(最方便):
java复制PageHelper.startPage(pageNum, pageSize); List<User> users = userMapper.selectAllUsers(); PageInfo<User> pageInfo = new PageInfo<>(users);
4.4 类型处理器与枚举映射
MyBatis通过类型处理器(TypeHandler)在Java类型和JDBC类型之间进行转换。对于枚举类型,我们可以自定义类型处理器:
java复制public enum UserStatus {
ACTIVE(1), INACTIVE(0), LOCKED(-1);
private final int code;
UserStatus(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public static UserStatus fromCode(int code) {
for (UserStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("未知的状态码: " + code);
}
}
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
UserStatus parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
return UserStatus.fromCode(rs.getInt(columnName));
}
// 其他重载方法...
}
然后在mybatis-config.xml中注册这个类型处理器:
xml复制<typeHandlers>
<typeHandler handler="com.example.handler.UserStatusTypeHandler"
javaType="com.example.enums.UserStatus"/>
</typeHandlers>
4.5 MyBatis与Spring集成
虽然我们可以单独使用MyBatis,但在实际项目中,通常会与Spring框架集成。基本步骤如下:
- 添加Spring和MyBatis-Spring依赖:
xml复制<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.29</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.1</version>
</dependency>
- 配置Spring的applicationContext.xml:
xml复制<!-- 数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.example.entity"/>
</bean>
<!-- Mapper扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
</bean>
<!-- 事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在Service中使用:
java复制@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.selectUserById(id);
}
// 其他业务方法...
}
这种集成方式让我们可以充分利用Spring的依赖注入和事务管理功能,同时保持MyBatis的灵活性。
