JAVA25-MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

ORM

什么是ORM

Object Relationship Mapping

  • 对象关系映射
    • 自动完成对象到数据库的映射
  • Association
    • 自动装配对象

MyBatis

MyBatis是什么

  • 一个ORM框架
  • 国内基本都在用
  • 简单,方便

MyBatis入门

引入MyBatis

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中

1
2
3
4
5
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

创建MyBatis配置文件

在resources目录下创建配置文件db/mybatis/mybatis-config.xml 这个路径可以改,不是固定的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?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="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="org.h2.Driver"/> <!-- 指定你的数据库驱动 -->
                <property name="url" value="jdbc:h2:file:./target/test"/> <!-- 指定你的数据库连接字符串 -->
                <property name="username" value="root"/> <!-- 数据库用户名 -->
                <property name="password" value="Jxi1Oxc92qSj"/> <!-- 数据库密码 -->
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="db/mybatis/mybatis-mapper.xml"/> <!-- sql映射文件 -->
    </mappers>
</configuration>

配置日志框架

mybatis-config.xml添加settings标签,并设置日志框架为 LOG4J
注意<settings>标签必须放在<configuration>标签之后,也就是<environments>标签的上面

1
2
3
4
5
<configuration>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
</configuration>

引入LOG4J

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

配置LOG4J,在resources目录下新建log4j.properties ,并写入如下配置,具体配置可以去LOG4J官网 查看

1
2
3
4
5
6
7
8
# 全局日志配置,设置日志级别为DEBUG级别
log4j.rootLogger=DEBUG, stdout
# 为指定的 MyBatis Mapper 设置日志
log4j.logger.db.mybatis.mybatis-mapper=TRACE
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

创建Mapper(Sql关系映射) db/mybatis/mybatis-mapper.xml,这个文件位置也是可以改的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?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.github.wjinlei.mapper"> <!-- 命名空间,用于定位,这个名字随便取 -->
    <!-- 简单的SELECT语句
    id 用于表示这个sql配置,通过命名空间+id可以唯一的定位到一个sql配置
    resultType 表示返回值类型
    -->
    <select id="selectUsers" resultType="HashMap">
        SELECT * FROM User
    </select>
</mapper>

获取 SqlSessionFactory 对象

1
2
3
String resource = "db/mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

简单使用

1
2
3
4
5
6
@Test
public void selectTest() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        System.out.println(sqlSession.selectList("com.github.wjinlei.mapper.selectUsers"));
    }
}

MyBatis的结果集类型(resultType)

resultType指定了返回的结果集类型,我们可以直接给定一个类,它会将结果直接映射到这个类的对象上,这就是ORM(对象关系映射),我们只需要把resultType改成对应的类的全限定类名即可$用于区分一个内部类,因为我们的User是个内部类

1
2
3
<select id="selectUsers" resultType="com.github.hcsp.sql.Sql$User">
    SELECT * FROM User
</select>

MyBatis的类型别名

<typeAliases>可以给类的全限定类名设置一个别名,它在mybatis-config.xml文件中配置,且必须在<settings>标签的后面

1
2
3
<typeAliases>
  <typeAlias alias="User" type="com.github.hcsp.sql.Sql$User"/>
</typeAliases>

设置了类名后,上面的调用就不需要写全限定类名了

1
2
3
<select id="selectUsers" resultType="User">
    SELECT * FROM User
</select>

MyBatis SQL参数传递#{}${}

总是使用#{}而不要使用${},${}不会预编译SQL,容易引起SQL注入

xml 指定一个名为id的参数#{id}

1
2
3
<select id="selectUserById" parameterType="int" resultType="User">
    SELECT * FROM User WHERE id = #{id}
</select>

java 传递参数的第一种方法给它一个map,里面的key是参数的名字id,因为上面xml指定了参数#{id}

1
2
3
4
5
6
7
8
9
@Test
public void selectTest() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        HashMap<String, Integer> param = new HashMap<>();
        param.put("id", 1);
        Sql.User user = sqlSession.selectOne("com.github.wjinlei.mapper.selectUserById", param);
        System.out.println(user);
    }
}

java 第二种方法,直接传递User对象,设置id为1,它内部实现原理是调用对象的getId方法,如果没有就看有没有id字段

1
2
3
4
5
6
7
8
9
@Test
public void selectTest() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        Sql.User user = new Sql.User();
        user.id = 1;
        Sql.User result = sqlSession.selectOne("com.github.wjinlei.mapper.selectUserById", user);
        System.out.println(result);
    }
}

MyBatis的动态SQL

动态SQL是MyBatis的灵魂,详细的可以参考官方文档的动态SQL章节

if

1
2
3
4
5
6
7
<!-- 如果传递了name参数,则拼接 WHERE name LIKE #{name} 否则就不拼接 -->
<select id="selectUserByName" resultType="User">
    SELECT * FROM User
    <if test="name != null">
        WHERE name LIKE #{name}
    </if>
</select>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Test
public void selectTest() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        HashMap<String, String> param = new HashMap<>();
        param.put("name", "wangwu");
        Sql.User user = sqlSession.selectOne("com.github.wjinlei.mapper.selectUserByName", param);
        System.out.println(user);
        
        List<Sql.User> users = sqlSession.selectList("com.github.wjinlei.mapper.selectUserByName");
        System.out.println(users);
    }
}

choose,when,otherwise

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!-- 类似选择,
当 name == wangwu 时拼接,WHERE name = 'wangwu',
当 name == 'lisi' 时执行,WHERE name = 'lisi',
否则执行 WHERE name = 'zhangsan' -->
<select id="chooseUserByName" resultType="User">
    SELECT * FROM User
    <choose>
        <when test="name == 'wangwu'">
            WHERE name = 'wangwu'
        </when>
        <when test="name == 'lisi'">
            WHERE name = 'lisi'
        </when>
        <otherwise>
            WHERE name = 'zhangsan'
        </otherwise>
    </choose>
</select>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Test
public void selectTest() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        HashMap<String, String> param1 = new HashMap<>();
        param1.put("name", "wangwu");
        Sql.User wangWu = sqlSession.selectOne("com.github.wjinlei.mapper.chooseUserByName", param1);
        System.out.println(wangWu);
        
        HashMap<String, String> param2 = new HashMap<>();
        param2.put("name", "lisi");
        Sql.User liSi = sqlSession.selectOne("com.github.wjinlei.mapper.chooseUserByName", param2);
        System.out.println(liSi);
    }
}

foreach

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- 循环拼接 -->
<select id="foreachUserByIds" resultType="User">
    SELECT * FROM User WHERE id in
    <!-- index 是当前迭代的序号,item 的值是本次迭代获取到的元素 -->
    <!-- collection指定从哪个参数中获取循环数据 open表示要拼接的开始字符,close表示结束字符,separator表示分隔符 -->
    <!-- 为什么open,close,separator要这么设置呢? 因为SQL语句子查询语法就是这样的 -->
    <!-- SELECT * FROM User WHERE id in (1,4,8,5) -->
    <foreach item="item" index="index" collection="ids"
             open="(" separator="," close=")">
        #{item}
    </foreach>
</select>
1
2
3
4
5
6
7
8
9
@Test
public void selectTest() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        HashMap<String, Object> param = new HashMap<>();
        param.put("ids", Arrays.asList(1, 4, 5, 9, 3, 6));
        List<Sql.User> users = sqlSession.selectList("com.github.wjinlei.mapper.foreachUserByIds", param);
        System.out.println(users);
    }
}

注意有些情况下你可能不需要 open 或 close 属性

1
2
3
4
5
6
7
8
9
<insert id="batchInsertUsers">
    INSERT INTO User (name, tel, address) VALUE
    <foreach item="user" index="index" collection="users" separator=",">
    <!-- foreach标签体中的item为一个整体 -->
    <!-- 对于这个SQL而言 INSERT INTO User (name, tel, address) VALUE (?, ?, ?), (?, ?, ?) ... 我们不需要open和close -->
    <!-- 因为加上了open和close会变成这样 INSERT INTO User (name, tel, address) VALUE ((?, ?, ?)), ((?, ?, ?)) ...-->
        (#{user.name}, #{user.tel}, #{user.address})
    </foreach>
</insert>

where

1
2
3
4
5
6
7
8
9
<select id="selectUserByName" resultType="User">
    SELECT * FROM User
    <!-- where 只会在有if条件被满足时才会插入 "WHERE" 子句 --> 
    <!-- 而且,若子句的开头为 "AND" 或 "OR",where 元素也会将它们去除 -->
    <where>
        <if test="name == 'wangwu'">WHERE name = 'wangwu'</if>
        <if test="name == 'lisi'">WHERE name = 'lisi'</if>
    </where>
</select>

set

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<update id="updateUser">
    UPDATE User
    <!-- 和where标签类似,只会在有if条件被满足时才会插入 "SET" 子句-->
    <!-- set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号 -->
    <set>
        <if test="name != null">name=#{name},</if>
        <if test="tel != null">tel=#{tel},</if>
        <if test="address != null">address=#{address},</if>
    </set>
    WHERE id = #{id}
</update>

resultMap 高级结果集映射

如果结果resutType的类包含了类成员变量,resultMap可以给它们都映射上值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- resultType 换成 resultMap, 指定resultMap的id, 如下面id为Order的resultMap -->
<select id="getInnerJoinOrders" resultMap="Order">
    SELECT o.id                          AS o_id,
           u.id                          AS u_id,
           u.name                        AS u_name,
           u.tel                         AS u_tel,
           u.address                     AS u_address,
           g.id                          AS g_id,
           g.name                        AS g_name,
           g.price                       as goods_price,
           (o.goods_num * o.goods_price) AS total_price
    FROM "ORDER" o
             INNER JOIN USER u ON u.id = o.user_id
             INNER JOIN GOODS g ON g.id = o.goods_id
</select>

<!-- type 指定你要返回的resultType类 -->
<resultMap id="Order" type="com.github.hcsp.mybatis.entity.Order">
    <result property="id" column="o_id"/> <!-- 把上面select到的o_id映射到这个Order类里面的id属性 -->
    <result property="totalPrice" column="total_price"/> <!-- 把上面select到的total_price映射到这个Order类里面的totalPrice属性 -->
    <association property="user" javaType="User"> <!-- 把User类映射到这个Order类里面的user属性 -->
        <result property="id" column="u_id"/> <!-- 把上面select到的u_id映射到这个User类里面的id属性 -->
        <result property="name" column="u_name"/> <!-- 把上面select到的u_name映射到这个User类里面的name属性 -->
        <result property="tel" column="u_tel"/> <!-- 把上面select到的u_tel映射到这个User类里面的tel属性 -->
        <result property="address" column="u_address"/> <!-- 把上面select到的u_address映射到这个User类里面的address属性 -->
    </association>
    <association property="goods" javaType="Goods"> <!-- 把Goods类映射到这个Order类里面的goods属性 -->
        <result property="id" column="g_id"/> <!-- 把上面select到的g_id映射到这个Goods类里面的id属性 -->
        <result property="name" column="g_name"/> <!-- 把上面select到的g_name映射到这个Goods类里面的name属性 -->
        <result property="price" column="goods_price"/> <!-- 把上面select到的goods_price映射到这个Goods类里面的price属性 -->
    </association>
</resultMap>
  • Order类
1
2
3
4
5
6
7
/** 一个订单 */
public class Order {
    private Integer id;
    private User user;
    private Goods goods;
    private BigDecimal totalPrice;
}
  • User类
1
2
3
4
5
6
7
8
package com.github.hcsp.mybatis.entity;
/** 一个用户 */
public class User {
    private Integer id;
    private String name;
    private String tel;
    private String address;
}
  • Goods类
1
2
3
4
5
6
/** 一件商品 */
public class Goods {
    private Integer id;
    private String name;
    private BigDecimal price;
}

通过代理模式执行SQL

这种模式适合sql比较简单的情况,复杂的sql还是老老实实写xml文件吧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
 * 删除一个用户。
 *
 * @param id 待删除的用户ID
 */
public void deleteUserById(Integer id) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
        DeleteUserByIdMapper mapper = sqlSession.getMapper(DeleteUserByIdMapper.class);
        mapper.deleteUserById(id);
    }
}

interface DeleteUserByIdMapper {
    @Delete("DELETE FROM user WHERE ID = #{id}")
    void deleteUserById(@Param("id") Integer id);
}

还要在mybatis-config.xml中配置<mapper>

1
2
3
4
<mappers>
    <!-- 指定你那个接口的全限定名称 -->
    <mapper class="com.github.hcsp.mybatis.UserDao$DeleteUserByIdMapper" />
</mappers>
updatedupdated2025-03-012025-03-01