当前位置: 首页 > news >正文

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 实现类。核心逻辑是:

  1. 接口方法名与 XML 中id一一对应;
  2. 调用接口方法时,MyBatis 自动匹配 XML 中的 SQL 并执行;
  3. 结果通过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

四、常见问题与解决方案

  1. 中文乱码:在 JDBC 连接 URL 中添加编码参数:
    jdbc:mysql:///mybatis_db?useUnicode=true&characterEncoding=UTF-8

  2. 模糊查询查不到结果

    • 检查参数是否带%(使用#{}时需手动传入);
    • 确认数据库字符集为 UTF-8,避免中文存储乱码。
  3. 新增后获取自增 ID 失败
    确保selectKey标签中order="AFTER"(MySQL 自增 ID 需在插入后获取)。

  4. 配置文件报错
    检查标签顺序是否符合要求,或 DTD 约束是否正确引入。

http://www.dtcms.com/a/273595.html

相关文章:

  • Netplan 配置网桥(Bridge)的模板笔记250711
  • excel如何只保留前几行
  • 提示工程:解锁大模型潜力的核心密码
  • 基于redis的分布式session共享管理之销毁事件不生效问题
  • 这个方法的目的是检查一个给定的项目ID(projectId)是否在当前数据库中被使用(搜索全库)
  • SortByCustomOrder 根据指定的顺序对任意类型的列表进行排序
  • Python七彩花朵
  • 【实时Linux实战系列】实时系统测试与合规认证指南
  • 二刷 黑马点评 商户查询缓存
  • <script>标签对HTML文件解析过程的影响以及async和defer属性的应用
  • 在 React Three Fiber 中实现 3D 模型点击扩散波效果
  • 车企战略投资项目管理的实践与思考︱中国第一汽车集团进出口有限公司战略部投资管理专家庞博
  • 台球 PCOL:极致物理还原的网页斯诺克引擎(附源码深度解析)
  • 软件设计师中级逻辑公式题
  • Ubuntu 24.04上安装 Intelligent Pinyin 中文输入法
  • Java算法 -蓝桥云课 -卖货
  • 【联合国国家指标 2025:HDI、GDP、POP、面积】数据集countries_metric - Sheet1.csv
  • C++迭代器失效
  • 深入剖析Spring Bean生命周期:从诞生到消亡的全过程
  • 羲和:一款诗词风格的摆件App
  • GitHub Copilot:产品经理提升工作效率的AI助手
  • 销售数据可视化分析项目
  • AI基建还能投多久?高盛:2-3年不是问题,回报窗口才刚开启
  • Lookahead:Trie 树(前缀树)
  • TCP详解——流量控制、滑动窗口
  • 【接口测试】07 Fiddler使用教程(图文详解)
  • Flutter、Vue 3 和 React 在 UI 布局比较
  • 20.缓存问题与解决方案详解教程
  • 【Java】【力扣】102.二叉树层序遍历
  • 前端抓包(不启动前端项目就能进行后端调试)--whistle