MyBatis学习笔记(上)

目录

MyBatis介绍

Mybatis简单示例

MyBatis编写流程

Mybatis参数

一.MyBatis参数详解

二.resultType

SqlMapConfig.xml配置文件

常见问题排查


MyBatis介绍

MyBatis 是一款轻量级 Java 持久层框架,核心价值是封装 JDBC 繁琐操作(如加载驱动、创建连接、处理结果集),让开发者聚焦 SQL 逻辑而非重复的 JDBC 代码。

核心设计思想

  1. 配置驱动:通过 XML 或注解定义 SQL 语句,避免硬编码。
  2. 参数映射 :自动将 Java 对象与 SQL 动态参数绑定(如 #{username})。
  3. 结果映射:执行 SQL 后,自动将数据库结果集转换为 Java 对象(ORM 思想)。
  4. 低侵入性:不强制依赖复杂配置,可灵活集成到各类项目中。

Mybatis简单示例

1.创建库和表结构:

sql 复制代码
-- 1. 创建数据库
create database mybatis_db;
use mybatis_db;

-- 2. 创建用户表
CREATE TABLE `user` (
        `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID(自增)',
        `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;

-- 3. 插入测试数据
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.所需依赖:

XML 复制代码
<dependencies>
    <!-- 1. MyBatis 核心依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version> <!-- 稳定版本,兼容性好 -->
    </dependency>

    <!-- 2. MySQL 驱动(适配 MySQL 5.x;若用 MySQL 8.x,需改为 8.0+ 版本,驱动类为 com.mysql.cj.jdbc.Driver) -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>

    <!-- 3. 单元测试(JUnit 4,简化测试代码) -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope> <!-- 仅测试环境生效 -->
    </dependency>

    <!-- 4. 日志(Log4j,打印 SQL 执行日志,便于调试) -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

补充:Log4j 配置(可选)

resources 目录下创建 log4j.properties,开启 SQL 日志打印:

XML 复制代码
# 日志输出级别:DEBUG(打印 SQL 细节)、INFO(仅关键信息)
log4j.rootLogger=DEBUG, Console

# 控制台输出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
# 日志格式:时间 | 线程 | 级别 | 类名 | 日志内容
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %m%n

# MyBatis 日志细化(可选)
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG

3.编写User实现类:

java 复制代码
package com.mszlu.domain;

import java.util.Date;

/**
 * 用户实体类(对应数据库 user 表)
 */
public class User {
    private Integer id;         // 对应表 id 列(自增)
    private String username;    // 对应表 username 列
    private Date birthday;      // 对应表 birthday 列(MyBatis 自动转换 datetime ↔ Date)
    private String sex;         // 对应表 sex 列
    private String address;     // 对应表 address 列

    // 无参构造器(MyBatis 反射创建对象时必须)
    public User() {}

    // 有参构造器(便于手动创建对象)
    public User(String username, Date birthday, String sex, String address) {
        this.username = username;
        this.birthday = birthday;
        this.sex = sex;
        this.address = address;
    }

    // Getter + Setter(MyBatis 需通过 Setter 注入结果集数据)
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public Date getBirthday() { return birthday; }
    public void setBirthday(Date birthday) { this.birthday = birthday; }
    public String getSex() { return sex; }
    public void setSex(String sex) { this.sex = sex; }
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }

    // toString(便于打印对象信息)
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

4.编写userMapper以及userMapper.xml

userMapper:定义接口

userMapper.xml( 在resources目录下,创建mapper文件夹。编写UserMapper.xml的配置文件**):定义sql语句**

5.编写主配置文件 SqlMapConfig.xml,SqlMapConfig.xml是MyBatis框架的核心配置文件。 在resources目录下创建SqlMapConfig.xml的配置文件(名称可以任意),导入对应的约束,编写主配置文件。

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>
    <!-- 配置环境们 -->
    <environments default="mysql">
        <!-- 配置具体的环境 -->
        <environment id="mysql">
            <!-- 配置事务管理类型 -->
            <transactionManager type="JDBC"/>
            <!-- 配置是否需要使用连接池,POOLED使用,UNPOOLED不使用 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis_db"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 加载映射的配置文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

6.编写测试方法

java 复制代码
public class testDemo1 {
    @Test
    public void testFindAll() throws IOException {
        // 加载主配置文件,目的是构建SqlSessionFactory的对象
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 使用SqlSessionFactory工厂对象创建SqlSession对象
        SqlSession session = factory.openSession();
        // 通过session创建UserMapper接口的代理对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        // 调用查询所有的方法
        //第一种方式:
        List<User> list = mapper.findAll();
        //第二种方式
//        List<User> userList = session.selectList("com.mszlu.mapper.UserMapper.findAll");
        // 遍历集合
        for (User user : list) {
            System.out.println(user);
        }
        // 释放资源
        session.close();
        in.close();
    }
}

MyBatis编写流程

MyBatis 推荐使用 "接口 + XML" 的方式定义 SQL:Mapper 接口定义方法签名,Mapper XML 定义具体 SQL 语句,两者通过全限定名 + 方法名关联(无需手动实现接口,MyBatis 动态生成代理对象)。

使用Dao进行增删改查

1.创建userMapper接口

java 复制代码
package com.mszlu.mapper;

import com.mszlu.domain.QueryVo;
import com.mszlu.domain.User;
import java.util.List;

/**
 * User 表 Mapper 接口(定义 SQL 方法签名)
 */
public interface UserMapper {
    // 1. 查询所有用户
    List<User> findAll();

    // 2. 测试列名与属性名不一致(需用 resultMap 映射)
    List<User> findAll2();

    // 3. 按 ID 查询用户(参数为简单类型:Integer)
    User findById(Integer userId);

    // 4. 新增用户(返回自增 ID,参数为 POJO:User)
    void insert(User user);

    // 5. 修改用户(参数为 POJO:User)
    void update(User user);

    // 6. 删除用户(参数为简单类型:Integer)
    void delete(Integer userId);

    // 7. 模糊查询用户(参数为简单类型:String)
    List<User> findByName(String username);

    // 8. 查询用户总数(返回简单类型:Integer)
    Integer findByCount();

    // 9. 按包装类查询(参数为包装类:QueryVo)
    List<User> findByVo(QueryVo queryVo);
}

2.在resources/mapper 目录下创建 UserMapper.xml,编写 SQL 语句与结果映射:

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">
<!-- 
    namespace:必须与 Mapper 接口的全限定名一致(关联接口与 XML)
    例:com.mszlu.mapper.UserMapper
 -->
<mapper namespace="com.mszlu.mapper.UserMapper">

    <!-- 1. 结果映射(resultMap):解决"表列名与实体类属性名不一致"问题 -->
    <!-- 
        id:resultMap 的唯一标识(供 select 标签的 resultMap 属性引用)
        type:映射的实体类类型(可直接用别名 user,对应 com.mszlu.domain.User)
     -->
    <resultMap id="userMap" type="user">
        <!-- 
            id 标签:映射主键列(性能优化,可选但推荐)
            result 标签:映射普通列
            property:实体类属性名
            column:表列名(或 SQL 查询的列别名)
         -->
        <id property="id" column="id"/> <!-- 若列名与属性名一致,可省略该配置 -->
        <result property="username" column="_username"/> <!-- 列别名为 _username,对应属性 username -->
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    </resultMap>

    <!-- 2. 查询所有用户(列名与属性名一致,用 resultType) -->
    <!-- 
        id:必须与 Mapper 接口的方法名一致(findAll)
        resultType:返回结果类型(实体类别名或全限定名,MyBatis 自动映射)
     -->
    <select id="findAll" resultType="user">
        select * from user;
    </select>

    <!-- 3. 查询所有用户(列名与属性名不一致,用 resultMap) -->
    <!-- SQL 中 username 列取别名为 _username,需通过 userMap 映射到属性 username -->
    <select id="findAll2" resultMap="userMap">
        select id, username _username, birthday, sex, address from user;
    </select>

    <!-- 4. 按 ID 查询用户(简单参数,用 #{占位符}) -->
    <!-- 
        parameterType:参数类型(简单类型可省略,MyBatis 自动推断)
        #{id}:占位符,对应方法参数(简单类型时,占位符名称可任意,如 #{userId} 也可)
     -->
    <select id="findById" parameterType="int" resultType="user">
        select * from user where id = #{id};
    </select>

    <!-- 5. 新增用户(返回自增 ID) -->
    <!-- 
        <selectKey>:获取新增后的自增 ID(MySQL 用 last_insert_id() 函数)
        keyProperty:将自增 ID 注入到实体类的哪个属性(User.id)
        order:执行时机(AFTER:插入后执行,BEFORE:插入前执行)
        resultType:返回 ID 的类型
     -->
    <insert id="insert" parameterType="user">
        <selectKey keyProperty="id" order="AFTER" resultType="int">
            select last_insert_id(); <!-- MySQL 系统函数:返回当前会话最后一次插入的自增 ID -->
        </selectKey>
        insert into user (username, birthday, sex, address) 
        values (#{username}, #{birthday}, #{sex}, #{address});
    </insert>

    <!-- 6. 修改用户 -->
    <update id="update" parameterType="user">
        update user 
        set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} 
        where id=#{id};
    </update>

    <!-- 7. 删除用户 -->
    <delete id="delete" parameterType="int">
        delete from user where id = #{id};
    </delete>

    <!-- 8. 模糊查询用户(两种方式:#{} 与 ${}) -->
    <!-- 方式1:#{}(推荐,防 SQL 注入,需手动加 %) -->
    <select id="findByName" parameterType="string" resultType="user">
        select * from user where username like #{username}; 
        <!-- 调用时需传参数:"%王%"(如 mapper.findByName("%王%")) -->
    </select>
    <!-- 方式2:${}(不防 SQL 注入,直接拼接字符串,占位符只能是 ${value}) -->
    <!-- 
    <select id="findByName" parameterType="string" resultType="user">
        select * from user where username like '${value}';
        <!-- 调用时需传参数:"%王%" 或 "王%"(如 mapper.findByName("%王%")) -->
    </select>
    -->

    <!-- 9. 查询用户总数(返回简单类型) -->
    <select id="findByCount" resultType="int">
        select count(*) from user; <!-- count(*) 返回 int 类型 -->
    </select>

    <!-- 10. 按包装类查询(嵌套参数,用 #{对象.属性}) -->
    <!-- parameterType:包装类类型(QueryVo),通过 #{user.username} 引用嵌套对象的属性 -->
    <select id="findByVo" parameterType="queryVo" resultType="user">
        select * from user where username = #{user.username};
    </select>

</mapper>

3.测试代码...

java 复制代码
import com.mszlu.domain.QueryVo;
import com.mszlu.domain.User;
import com.mszlu.mapper.UserMapper;
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 java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;

public class UserTest {
    private InputStream in;
    private SqlSession session;
    private UserMapper mapper;
    @Before
    public void init() throws IOException {
        in = Resources.getResourceAsStream("SqlMapConfig.xml");

        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in) ;
        session = factory.openSession();
        mapper = session.getMapper(UserMapper.class);
    }
    @After
    public void destory() throws IOException {
        session.close();
        in.close();
    }
    @Test
    public void testFindAll() throws IOException {
        List<User> userList = mapper.findAll();
        for(User user : userList){
            System.out.println(user);
        }
        in.close();
    }

    @Test
    public void testFindAll2() throws IOException {
        List<User> userList = mapper.findAll2();
        for(User user : userList){
            System.out.println(user);
        }
        in.close();
    }
    @Test
    public void testFindById() throws Exception {
        User user = mapper.findById(4);
        System.out.println(user);
        in.close();
    }
    @Test
    public void testInsert() throws Exception {
        User user = new User();
        user.setUsername("美美2");
        user.setBirthday(new Date());
        user.setSex("男");
        user.setAddress("顺义");
        mapper.insert(user);
        session.commit();
        System.out.println(user.getId());
    }

    @Test
    public void testUpdate() throws Exception {
        User user = mapper.findById(4);
        user.setUsername("小凤");
        mapper.update(user);
        session.commit();
    }

    @Test
    public void testDelete() throws Exception {
        mapper.delete(5);
        session.commit();
    }

    // 第一种
    @Test
    public void testFindByName() throws Exception {
        List<User> list = mapper.findByName("%王%");
        for (User user : list) {
            System.out.println(user);
        }
    }
    // 第二种
    @Test
    public void testFindByName2() throws Exception {
        List<User> list = mapper.findByName("王");
        for (User user : list) {
            System.out.println(user);
        }
    }

    @Test
    public void testFindByCount() throws Exception {
        Integer count = mapper.findByCount();
        System.out.println("总记录数:"+count);
    }

    //包装类测试
    @Test
    public void testFindByVo() throws Exception {
        QueryVo queryVo = new QueryVo();
        User user = new User();
        user.setUsername("熊大");
        queryVo.setUser(user);
        List<User> userList = mapper.findByVo(queryVo);
        for (User user1 : userList) {
            System.out.println(user1);
        }
    }
}

模糊查询符号使用的区别

通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

通过可以将传入的内容拼接在中且不进行类型转换,{}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, {}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,{}括号中只能是value。

|----------|------------------------------|-------------------|
| 特性 | #{} | **{}** | | 本质 | PreparedStatement 占位符(`?` ) | 字符串直接拼接 | | 类型转换 | 自动进行 Java 类型 ↔ JDBC 类型转换 | 不转换,直接拼接字符串 | | SQL 注入防护 | 支持(占位符不直接拼接 SQL) | 不支持(直接拼接,有注入风险) | | 简单类型参数名称 | 可任意(如 `#{id}` 、`#{userId}` ) | 只能是 `{value}` |
| 适用场景 | 普通参数传递(推荐) | 动态表名、动态排序字段(谨慎使用) |


Mybatis参数

一.MyBatis参数详解

1、简单数据类型:java八大类+String(mybatis把String归为简单数据类型)

简单的写法:java.lang.Integer --> int integer Int Integer 都可以,框架提供简写的方式。

2、POJO(JavaBean实体类)对象类型,默认不能简写,可以配置

例子如:User对象

3、POJO包装对象类型,包含更多的实体类(即对象中引用其他对象)

**如在QueryVo实体类中:**包含了User对象

在mapper接口中,加入这个函数

复制代码
public List<User> findByVo(QueryVo queryVo);

实现:

参数类型是QueryVo类,我们可以直接使用二级子类,比如user,name,role等,然后使用user里的属性再进行查询

XML 复制代码
<!--    根据封装类型来查询-->
    <select id="findByVo" resultType="com.mszlu.domain.User" parameterType="com.mszlu.domain.QueryVo" >
        select * from user where username = #{user.username}
    </select>

测试类:

java 复制代码
//包装类测试
@Test
public void testFindByVo() throws Exception {
    QueryVo queryVo = new QueryVo();
    User user = new User();
    user.setUsername("熊大");
    queryVo.setUser(user);
    List<User> userList = mapper.findByVo(queryVo);
    for (User user1 : userList) {
        System.out.println(user1);
    }
}

二.resultType

1.返回简单数据类型

int double long(Java八大基本类型) String

2.返回POJO数据类型

返回User对象类型

3.resultMap结果类型

resultType 用法

直接指定一个实体类(比如 User),查询结果会自动装进这个类的对象里。

但要求:数据库查出来的列名 (比如表的 username 列)和 实体类的属性名(比如 User 类的 username 属性)必须一模一样,才能对应上。

resultMap 用法

列名和属性名不一样时 (比如表列是 user_name,类属性是 userName),用它来手动指定 "列名对应哪个属性名",帮它们搭个桥。

另外,它还能处理复杂的情况:比如实体类里不仅有基本属性,还包含另一个实体类(一对一)或者一个列表(一对多),也能正确映射进去。

复制代码
<!--
        配置resultMap,用来进行数据封装
        id="唯一的名称,用来被引用的"
        type="进行封装数据的类型"
-->
<resultMap id="userMap" type="com.qcbyjy.domain.User">
  <!--
      property="JavaBean中的属性"
      column="表中的字段"
  -->
  <result property="id" column="_id"/>
  <result property="username" column="_username" />
  <result property="birthday" column="_birthday" />
  <result property="sex" column="_sex" />
  <result property="address" column="_address" />
</resultMap>

对于sql语句:

复制代码
select id, username _username, birthday, sex, address from user;

实际查询的字段是username,但返回的结果的字段是别名_username

所以当我数据库表结构如下:

而查询的sql语句以及resultMap映射是上面所写的时

sql语句返回的结果集中,所对应字段名字是id,_username,birthday,sex,address,在结果集中查找,只有_username可以对上,所以只有_username才有结果,其他都为空


SqlMapConfig.xml配置文件

1.1. 定义properties标签的方式管理数据库的信息

即把数据库信息定义properties标签中的方法,然后dataSource再使用

1.2 在项目中定义jdbc.properties属性文件,存储数据库相关的信息,统一管理

1.2.1jdbc.properties属性文件

XML 复制代码
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis_db
jdbc.username=root
jdbc.password=123456

1.2.2 编写sql配置文件

这样少了前面的<properties>标签中的配置

2、类型别名定义

  1. MyBatis自已有类型别名的注册类,编写int或者integer通过注册可以找到java.lang.Integer
  2. 我们自己也可以进行别名的注册
  1. SqlMapConfig.xml的配置文件

    <typeAliases> <typeAlias type="com.mszlu.domain.User" alias="user"/> <package name="com.mszlu.domain"/> </typeAliases>

(注意xml中顺序, MyBatis 配置文件的标签顺序不符合规范 。MyBatis 对 <configuration> 内部子标签的顺序有严格要求,必须按照官方指定的顺序排列,否则会报格式错误。)

官方规定的 <configuration> 子标签顺序为(按先后排列):

复制代码
properties? → settings? → typeAliases? → typeHandlers? → objectFactory? → objectWrapperFactory? → reflectorFact

此时就可以成功修改了。

常见问题排查

  1. ClassNotFoundException**(类找不到)** :检查 Mapper 接口全限定名、实体类路径是否与 XML 中 namespaceresultType 一致。
  2. BindingException**(接口与 XML 绑定失败)** :检查 XML 中 id 是否与接口方法名一致,namespace 是否与接口全限定名一致。
  3. SQLSyntaxErrorException**(SQL 语法错误)**:打印 SQL 日志(Log4j),检查生成的 SQL 是否正确(如参数拼接、表名 / 列名是否存在)。
  4. MySQL 8.x 驱动报错 :需将驱动版本改为 8.0+,驱动类改为 com.mysql.cj.jdbc.Driver,URL 加时区参数 ?serverTimezone=UTC