MyBatis 从入门到实战:代理 Dao 模式下的 CRUD 全解析
MyBatis 作为一款优秀的持久层框架,凭借其简洁的配置和灵活的 SQL 编写能力,成为 Java 开发者处理数据库交互的首选工具。本文将从 MyBatis 的核心概念出发,手把手教你搭建环境、实现代理 Dao 模式下的 CRUD 操作,并深入解析参数配置与核心配置文件,帮你快速掌握 MyBatis 的使用技巧。
一、MyBatis 框架概述:为什么选择 MyBatis?
MyBatis 是一款基于 Java 的持久层框架,它封装了 JDBC 的底层细节,让开发者无需关注连接创建、Statement 对象生成等冗余代码,只需专注于 SQL 语句本身。其核心优势在于:
- 轻量化:相比 Hibernate 等全 ORM 框架,MyBatis 更灵活,开发者可直接编写 SQL,避免 "黑箱操作";
- 低侵入性:通过 XML 或注解配置映射关系,无需修改实体类结构;
- ORM 思想:实现 Java 对象与数据库表的映射,简化数据交互;
- 动态 SQL:支持灵活的 SQL 拼接,适应复杂查询场景。
二、MyBatis 入门:环境搭建与第一个程序
2.1 准备工作:数据库与表结构
首先创建测试数据库和用户表,执行以下 SQL:
create database mybatis_db;
use mybatis_db;CREATE TABLE `user` (`id` int(11) NOT NULL auto_increment,`username` varchar(32) NOT NULL COMMENT '用户名称',`birthday` datetime default NULL COMMENT '生日',`sex` char(1) default NULL COMMENT '性别',`address` varchar(256) default NULL COMMENT '地址',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- 插入测试数据
insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values
(1,'老王','2018-02-27 17:47:08','男','北京'),
(2,'熊大','2018-03-02 15:09:37','女','上海'),
(3,'熊二','2018-03-04 11:34:34','女','深圳'),
(4,'光头强','2018-03-04 12:04:06','男','广州');
2.2 搭建 Maven 项目:引入核心依赖
创建普通 Java Maven 项目,在pom.xml
中添加以下依赖:
<dependencies><!-- MyBatis核心包 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><!-- JUnit单元测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope></dependency><!-- 日志(可选,用于打印SQL) --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
</dependencies>
2.3 核心组件编写:从实体类到测试
步骤 1:编写实体类User
实体类需与数据库表字段对应,使用包装类型便于处理 null 值:
package cn.tx.domain;import java.io.Serializable;
import java.util.Date;public class User implements Serializable {private static final long serialVersionUID = 525400707336671154L;private Integer id; // 对应表中id字段private String username; // 用户名private Date birthday; // 生日private String sex; // 性别private String address; // 地址// 省略getter、setter和toString方法
}
serializable网络中是以流的方式传递,通过serializable可以给每个流标一个序号
步骤 2:定义 Mapper 接口UserMapper
MyBatis 通过接口代理实现 CRUD,接口方法名需与 XML 中 SQL 的id
一致:
package cn.tx.mapper;import java.util.List;
import cn.tx.domain.User;public interface UserMapper {// 查询所有用户List<User> findAll();// 根据ID查询用户User findById(Integer userId);// 新增用户void insert(User user);// 修改用户void update(User user);// 删除用户void delete(Integer userId);// 模糊查询(根据用户名)List<User> findByName(String username);// 统计用户总数Integer findByCount();
}
步骤 3:编写 Mapper.xml 配置文件
该文件用于映射接口方法与 SQL 语句,需注意namespace
必须与接口全路径一致:
<?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="cn.tx.mapper.UserMapper"><!-- 查询所有用户 --><select id="findAll" resultType="cn.tx.domain.User">select * from user</select><!-- 根据ID查询 --><select id="findById" resultType="cn.tx.domain.User" parameterType="int">select * from user where id = #{id};</select><!-- 新增用户(返回自增ID) --><insert id="insert" parameterType="cn.tx.domain.User"><!-- 插入后获取自增ID,赋值给User对象的id属性 --><selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">select last_insert_id();</selectKey>insert into user (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})</insert><!-- 修改用户 --><update id="update" parameterType="cn.tx.domain.User">update user set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} where id = #{id}</update><!-- 删除用户 --><delete id="delete" parameterType="Integer">delete from user where id = #{id}</delete><!-- 模糊查询(推荐使用#{}避免SQL注入) --><select id="findByName" resultType="cn.tx.domain.User" parameterType="string">select * from user where username like #{username}</select><!-- 统计总数 --><select id="findByCount" resultType="int">select count(*) from user</select>
</mapper>
步骤 4:编写主配置文件SqlMapConfig.xml
该文件是 MyBatis 的核心配置,包含数据库连接、环境、映射文件等信息:
<?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><!-- 数据库连接信息(外部配置文件) --><properties resource="jdbc.properties"></properties><!-- 类型别名(简化全类名书写) --><typeAliases><!-- 扫描包下所有类,别名默认为类名(不区分大小写) --><package name="cn.tx.domain"/></typeAliases><!-- 环境配置(支持多环境) --><environments default="mysql"><environment id="mysql"><!-- 事务管理:JDBC(依赖数据库事务) --><transactionManager type="JDBC"/><!-- 连接池:POOLED(使用连接池) --><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></environment></environments><!-- 加载Mapper映射文件 --><mappers><mapper resource="mapper/UserMapper.xml"/></mappers>
</configuration>
environments多个环境。default写死选择的库,pooled 连接池
配套jdbc.properties
文件(放在resources
目录下):
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis_db?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root
步骤 5:编写测试类UserTest
使用 JUnit 测试 CRUD 方法,注意 MyBatis 的事务默认不自动提交,新增 / 修改 / 删除需手动提交:
package cn.tx.test;import java.io.InputStream;
import java.util.Date;
import java.util.List;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import cn.tx.domain.User;
import cn.tx.mapper.UserMapper;public class UserTest {private InputStream in;private SqlSession session;private UserMapper mapper;// 初始化:加载配置文件,创建代理对象@Beforepublic void init() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);session = factory.openSession(); // 默认不自动提交事务mapper = session.getMapper(UserMapper.class); // 获取代理对象}// 资源释放@Afterpublic void destroy() throws Exception {session.close();in.close();}// 测试查询所有@Testpublic void testFindAll() {List<User> list = mapper.findAll();for (User user : list) {System.out.println(user);}}// 测试根据ID查询@Testpublic void testFindById() {User user = mapper.findById(1);System.out.println(user); // 输出id=1的用户信息}// 测试新增用户@Testpublic void testInsert() {User user = new User();user.setUsername("张三");user.setBirthday(new Date());user.setSex("男");user.setAddress("杭州");mapper.insert(user);session.commit(); // 手动提交事务System.out.println("新增用户ID:" + user.getId()); // 自增ID会回填}// 测试修改用户@Testpublic void testUpdate() {User user = mapper.findById(1);user.setUsername("老王更新");mapper.update(user);session.commit();}// 测试删除用户@Testpublic void testDelete() {mapper.delete(5); // 删除id=5的用户session.commit();}// 测试模糊查询(推荐方式:参数带%)@Testpublic void testFindByName() {// 方式1:参数直接带%(推荐,避免SQL注入)List<User> list = mapper.findByName("%王%");// 方式2:XML中写死%(不推荐,使用#{}时需拼接)// List<User> list = mapper.findByName("王"); for (User user : list) {System.out.println(user);}}// 测试统计总数@Testpublic void testFindByCount() {Integer count = mapper.findByCount();System.out.println("总用户数:" + count);}
}
结果:
findAll();
findById
insert
selectkey 查询当前新增数据的id值
update
delete
findByName
findByCount
findByVo
QueryVo实体类
package com.qcby.domain;import javax.management.relation.Role;
import java.io.Serializable;public class QueryVo implements Serializable {// 自己属性private String name;// user属性private User user;// role属性private Role role;public String getName() {return name;}public void setName(String name) {this.name = name;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}public Role getRole() {return role;}public void setRole(Role role) {this.role = role;}
}
resultmap
修改字段名通过resultmap经行映射
增删改需要手动提交是因为在配置环境里配置了jdbc手动提交事务
#{XXX } 代替jdbc的占位符
${ XX}字符串拼接
通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
通过$可以将传入的内容拼接在中且不进行类型转换,${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value
三、核心知识点解析
3.1 代理 Dao 模式的原理
MyBatis 通过动态代理为UserMapper
接口生成实现类,无需手动编写 Dao 实现类。核心逻辑是:
- 接口方法名与 XML 中
id
一一对应; - 调用接口方法时,MyBatis 自动匹配 XML 中的 SQL 并执行;
- 结果通过
resultType
映射为 Java 对象。
3.2 #{}与${}
的区别(模糊查询重点)
#{}
:预编译占位符,会自动添加引号,避免 SQL 注入,推荐使用。
例:where username like #{name}
,传入"%王%"
即可查询包含 "王" 的用户。${}
:字符串拼接,不会添加引号,有 SQL 注入风险,仅在特殊场景使用(如动态表名)。
例:where username like '%${value}%'
(value
是单个参数的固定别名)。
3.3 核心配置文件SqlMapConfig.xml
详解
(1)properties
标签:外部化配置
通过外部jdbc.properties
文件管理数据库信息,便于维护:
<!-- 加载外部配置文件 -->
<properties resource="jdbc.properties"></properties>
<!-- 使用${key}引用 -->
<property name="driver" value="${jdbc.driver}"/>
(2)typeAliases
标签:简化类名
为实体类定义别名,避免重复书写全类名:
<typeAliases><!-- 方式1:单个类别名 --><typeAlias type="cn.tx.domain.User" alias="user"/><!-- 方式2:包扫描(推荐) --><package name="cn.tx.domain"/> <!-- 所有类可直接用类名做别名 -->
</typeAliases>
使用时直接写别名:resultType="user"
(等价于cn.tx.domain.User
)。
(3)配置顺序要求
MyBatis 对SqlMapConfig.xml
中子标签顺序有严格要求,必须按以下顺序排列:
properties
→ settings
→ typeAliases
→ typeHandlers
→ objectFactory
→ objectWrapperFactory
→ reflectorFactory
→ plugins
→ environments
→ databaseIdProvider
→ mappers
四、常见问题与解决方案
中文乱码:在 JDBC 连接 URL 中添加编码参数:
jdbc:mysql:///mybatis_db?useUnicode=true&characterEncoding=UTF-8
模糊查询查不到结果:
- 检查参数是否带
%
(使用#{}时
需手动传入); - 确认数据库字符集为 UTF-8,避免中文存储乱码。
- 检查参数是否带
新增后获取自增 ID 失败:
确保selectKey
标签中order="AFTER"
(MySQL 自增 ID 需在插入后获取)。配置文件报错:
检查标签顺序是否符合要求,或 DTD 约束是否正确引入。