Skip to content

MyBatis

基础

什么是MyBatis?

答案: MyBatis是一个优秀的持久层框架,支持自定义SQL、存储过程和高级映射。

特点

  • SQL与代码分离:SQL写在XML或注解中
  • 灵活:可以编写复杂SQL
  • 半自动ORM:需要手动编写SQL
  • 缓存机制:一级缓存、二级缓存
  • 动态SQL:if、choose、foreach等标签

MyBatis和Hibernate的区别?

答案:

特性MyBatisHibernate
ORM半自动全自动
SQL手动编写自动生成
灵活性
学习成本
性能优化容易困难
适用场景复杂SQL、性能要求高简单CRUD

MyBatis的核心组件?

答案:

1. SqlSessionFactory

  • 创建SqlSession的工厂
  • 全局唯一,应用启动时创建

2. SqlSession

  • 执行SQL的会话
  • 线程不安全,用完即关闭

3. Mapper接口

  • 定义数据库操作方法

4. Mapper XML

  • 编写SQL语句
java
// 1. 创建SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
    .build(Resources.getResourceAsStream("mybatis-config.xml"));

// 2. 获取SqlSession
SqlSession session = factory.openSession();

// 3. 获取Mapper
UserMapper mapper = session.getMapper(UserMapper.class);

// 4. 执行SQL
User user = mapper.selectById(1);

// 5. 关闭会话
session.close();

配置

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>
  <!-- 属性 -->
  <properties resource="db.properties"/>

  <!-- 设置 -->
  <settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>

  <!-- 类型别名 -->
  <typeAliases>
    <typeAlias type="com.example.User" alias="User"/>
    <package name="com.example.entity"/>
  </typeAliases>

  <!-- 类型处理器 -->
  <typeHandlers>
    <typeHandler handler="com.example.MyTypeHandler"/>
  </typeHandlers>

  <!-- 环境配置 -->
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <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"/>
    <package name="com.example.mapper"/>
  </mappers>
</configuration>

Spring Boot整合MyBatis?

答案:

1. 添加依赖

xml
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2. 配置数据源

yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true

3. 创建Mapper接口

java
@Mapper
public interface UserMapper {
    User selectById(Long id);
    List<User> selectAll();
    int insert(User user);
    int update(User user);
    int delete(Long id);
}

4. 创建Mapper 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">

  <select id="selectById" resultType="User">
    SELECT * FROM user WHERE id = #{id}
  </select>

  <select id="selectAll" resultType="User">
    SELECT * FROM user
  </select>

  <insert id="insert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user (name, age) VALUES (#{name}, #{age})
  </insert>

  <update id="update">
    UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}
  </update>

  <delete id="delete">
    DELETE FROM user WHERE id = #{id}
  </delete>

</mapper>

动态SQL

MyBatis的动态SQL标签?

答案:

1. if标签

xml
<select id="selectByCondition" resultType="User">
  SELECT * FROM user
  WHERE 1=1
  <if test="name != null">
    AND name = #{name}
  </if>
  <if test="age != null">
    AND age = #{age}
  </if>
</select>

2. where标签(自动处理AND/OR)

xml
<select id="selectByCondition" resultType="User">
  SELECT * FROM user
  <where>
    <if test="name != null">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
  </where>
</select>

3. set标签(自动处理逗号)

xml
<update id="update">
  UPDATE user
  <set>
    <if test="name != null">name = #{name},</if>
    <if test="age != null">age = #{age},</if>
  </set>
  WHERE id = #{id}
</update>

4. choose/when/otherwise(类似switch)

xml
<select id="selectByCondition" resultType="User">
  SELECT * FROM user
  WHERE 1=1
  <choose>
    <when test="name != null">
      AND name = #{name}
    </when>
    <when test="age != null">
      AND age = #{age}
    </when>
    <otherwise>
      AND status = 1
    </otherwise>
  </choose>
</select>

5. foreach标签(批量操作)

xml
<!-- IN查询 -->
<select id="selectByIds" resultType="User">
  SELECT * FROM user
  WHERE id IN
  <foreach collection="ids" item="id" open="(" separator="," close=")">
    #{id}
  </foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsert">
  INSERT INTO user (name, age) VALUES
  <foreach collection="list" item="user" separator=",">
    (#{user.name}, #{user.age})
  </foreach>
</insert>

6. trim标签(自定义前缀后缀)

xml
<select id="selectByCondition" resultType="User">
  SELECT * FROM user
  <trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="name != null">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
  </trim>
</select>

高级特性

MyBatis的缓存机制?

答案:

一级缓存(SqlSession级别)

  • 默认开启
  • 同一个SqlSession内有效
  • 执行增删改或手动清空会失效
java
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);

User user1 = mapper.selectById(1);  // 查询数据库
User user2 = mapper.selectById(1);  // 从缓存获取

session.close();

二级缓存(Mapper级别)

  • 需要手动开启
  • 同一个Mapper下的所有SqlSession共享
  • 需要实体类实现Serializable
xml
<!-- 开启二级缓存 -->
<cache/>

<!-- 或配置参数 -->
<cache
  eviction="LRU"
  flushInterval="60000"
  size="512"
  readOnly="true"/>
yaml
# application.yml
mybatis:
  configuration:
    cache-enabled: true

缓存失效

  • 执行增删改操作
  • 手动清空:session.clearCache()
  • 配置flushCache="true"

MyBatis的ResultMap?

答案: ResultMap用于复杂的结果映射。

基础映射

xml
<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id"/>
  <result property="name" column="user_name"/>
  <result property="age" column="user_age"/>
</resultMap>

<select id="selectById" resultMap="userResultMap">
  SELECT user_id, user_name, user_age FROM user WHERE user_id = #{id}
</select>

一对一映射(association):

xml
<resultMap id="userResultMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <association property="card" javaType="Card">
    <id property="id" column="card_id"/>
    <result property="number" column="card_number"/>
  </association>
</resultMap>

<select id="selectById" resultMap="userResultMap">
  SELECT u.*, c.id as card_id, c.number as card_number
  FROM user u
  LEFT JOIN card c ON u.id = c.user_id
  WHERE u.id = #{id}
</select>

一对多映射(collection):

xml
<resultMap id="userResultMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <collection property="orders" ofType="Order">
    <id property="id" column="order_id"/>
    <result property="orderNo" column="order_no"/>
  </collection>
</resultMap>

<select id="selectById" resultMap="userResultMap">
  SELECT u.*, o.id as order_id, o.order_no
  FROM user u
  LEFT JOIN `order` o ON u.id = o.user_id
  WHERE u.id = #{id}
</select>

MyBatis的延迟加载?

答案: 延迟加载(懒加载):需要时才加载关联对象。

配置

yaml
mybatis:
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: false
xml
<!-- 延迟加载 -->
<resultMap id="userResultMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <association property="card" javaType="Card"
               select="selectCardByUserId"
               column="id"
               fetchType="lazy"/>
</resultMap>

<select id="selectById" resultMap="userResultMap">
  SELECT * FROM user WHERE id = #{id}
</select>

<select id="selectCardByUserId" resultType="Card">
  SELECT * FROM card WHERE user_id = #{userId}
</select>

插件

MyBatis的插件机制?

答案: MyBatis允许拦截以下方法:

  • Executor(执行器)
  • ParameterHandler(参数处理)
  • ResultSetHandler(结果集处理)
  • StatementHandler(SQL语句处理)

自定义插件

java
@Intercepts({
    @Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class}
    )
})
public class MyPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("执行前");
        Object result = invocation.proceed();
        System.out.println("执行后");
        return result;
    }
}

常用插件

  • PageHelper:分页插件
  • MyBatis-Plus:增强工具

练习题

  1. #{}和${}的区别?
  2. MyBatis如何防止SQL注入?
  3. MyBatis如何实现批量操作?
  4. MyBatis-Plus有哪些优势?

Released under the MIT License.