Mybatis缓存

MyBatis的一级缓存和二级缓存是用于提高查询效率的两种不同的缓存机制,它们的主要区别在于作用范围、存储位置以及生命周期等方面。

一级缓存(Local Cache 或 Session 级别缓存)

  • 作用范围:一级缓存是SqlSession级别的缓存,仅在当前SqlSession的生命周期内有效。
  • 存储位置:存储在SqlSession的内存中。
  • 特点
    • 自动管理:MyBatis自动维护一级缓存,无需手动配置。
    • 生命周期:与SqlSession的生命周期一致,SqlSession关闭后,一级缓存失效。
    • 作用:减少对数据库的访问次数,提高查询效率。
  • 访问顺序
    1. 首先访问本地缓存。
    2. 如果本地缓存没有,则从数据库中获取数据,并将结果放入缓存中。

二级缓存(Mapper 级别缓存)

  • 作用范围:二级缓存是Mapper级别的缓存,作用于整个应用级别,多个SqlSession可以共享二级缓存。
  • 存储位置:存储在Cache接口的实现类中,可以是内存缓存、文件缓存或其他存储方式。
  • 特点
    • 需要手动配置:需要在Mapper XML文件中配置标签开启二级缓存。
    • 生命周期:与应用的生命周期一致,应用关闭后,二级缓存失效。
    • 作用:进一步减少对数据库的访问次数,提高查询效率,适用于多用户共享的场景。
  • 访问顺序
    1. 在commit的时候,把二级缓存中的数据刷新到正在代理的一级缓存中。
    2. 然后直接访问一级缓存中的数据。
    3. 如果一级缓存中没有,则访问数据库获取数据,并更新缓存。

当使用MyBatis时,默认情况下会启用一级缓存。而二级缓存则默认是关闭的,如果需要使用,必须通过配置来开启,并且实体类需要实现Serializable接口以便支持序列化。此外,对于缓存数据的更新机制,无论是哪一级缓存,在执行了C/U/D(创建/更新/删除)操作之后,都会导致相关select查询的缓存被清空。

springboot中如何使用mybatis

在 Spring Boot 中使用 MyBatis 是一种非常常见的开发方式,它结合了 Spring Boot 的自动化配置和 MyBatis 的灵活性,能够快速实现数据库操作。以下是详细的步骤和关键点:


占位符#{}和${}

在MyBatis中,#{} ${}是两种不同的参数占位符,它们用于SQL语句中传递参数值,但它们的工作方式和使用场景有所不同。

#{}

  • 作用#{}被用来创建预编译语句(PreparedStatement)中的参数占位符。MyBatis会将你在#{}中指定的属性名与传递给语句的参数对象的属性进行匹配,并设置相应的参数值。
  • 安全性:由于它使用的是预编译技术,因此可以有效防止SQL注入攻击。这是因为实际的参数值会被当作Java字符串或数字等数据类型处理,而不是作为SQL语句的一部分直接拼接进去。
  • 适用场景:适用于所有需要传递参数的地方,特别是当参数是用户输入或其他不可信来源时。
  • 示例
    SELECT * FROM users WHERE id = #{userId}
    
    这里的#{userId}将会被替换为一个对应的参数值,比如123,并且这个值会被正确地转义以避免SQL注入问题。

${}

  • 作用${}用于执行SQL字符串插值。这意味着它会直接将你提供的变量值插入到生成的SQL语句中。这种方式不会对插入的值进行任何处理或转义。
  • 安全性:因为它是直接将值嵌入到SQL语句中,所以存在SQL注入的风险。因此,在使用${}时必须非常小心,确保插入的值是安全的或者已经经过了严格的验证和清理。
  • 适用场景:主要用于动态表名、列名或者其他SQL结构部分,这些地方不能使用预编译语句的占位符。
  • 示例
    SELECT * FROM ${tableName} WHERE 1=1
    
    在这里${tableName}将会被直接替换为具体的表名,如users,而不会有额外的转义处理。

总的来说,推荐尽可能使用#{}来保证安全性,减少SQL注入的风险。只有在确实需要的时候,才谨慎地使用${}。例如,当你需要构建动态查询语句且涉及到数据库对象名称(如表名或列名)时,可能不得不使用${}。不过,在这种情况下,请确保对传入的值进行了充分的验证和清理。

ResultMap和ResultType的区别

ResponseMapResponseType 这两个术语并不是标准的MyBatis概念,但它们听起来像是在讨论处理返回类型时可能涉及的概念。基于常见的编程实践和框架使用,我可以推测你可能是在问关于MyBatis中的结果映射(Result Map)与返回类型(Result Type)的区别。如果是这样的话,以下是对这两个概念的解释:

ResultMap(结果映射)

  • 定义ResultMap 是 MyBatis 中用于描述数据库查询结果如何映射到 Java 对象的一个复杂机制。它允许您手动定义从列到Java对象属性的详细映射规则。
  • 用途:当数据库表结构和实体类不完全对应,或者需要进行复杂的转换时(例如处理多表联合查询的结果),可以使用ResultMap来精确地控制结果集到对象的映射过程。
  • 优点
    • 提供了更高的灵活性和控制力。
    • 支持一对一、一对多等复杂关系映射。
  • 示例
    <resultMap id="userResultMap" type="User">
        <id property="id" column="user_id"/>
        <result property="username" column="user_name"/>
        <!-- 更多映射规则 -->
    </resultMap>
    <select id="selectUser" resultMap="userResultMap">
    SELECT user_id, uname, pwd FROM users WHERE user_id = #{id}
      </select>
    

ResultType(返回类型)

  • 定义ResultType 是一个更简单直接的方式,用来指定查询结果应该映射成哪种类型的Java对象。在这种情况下,MyBatis会尝试自动将每一行的数据按名称匹配的原则映射到相应的Java对象属性上。
  • 用途:适用于简单的查询场景,其中数据库列名与Java对象属性名相同或可以通过驼峰命名法自动匹配。
  • 优点
    • 简单易用,不需要额外配置。
    • 当列名与属性名相匹配时,能够快速实现数据映射。
  • 示例
    <select id="selectUsers" resultType="User">
        SELECT user_id, user_name FROM users
    </select>
    

总结来说,如果你的应用场景相对简单,并且数据库字段名可以直接或通过简单的命名约定映射到Java对象的属性上,那么使用ResultType会更加方便快捷。然而,对于更复杂的映射需求,如不同名的属性映射、关联对象映射等,则应选择ResultMap以获得更大的灵活性和控制力。

使用

1. 添加依赖

pom.xml 文件中添加必要的依赖项:

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- MyBatis Spring Boot Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.1</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Spring Boot JDBC Starter(可选,用于数据库连接池) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!-- Lombok(可选,用于简化实体类代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2. 配置数据库连接信息

application.propertiesapplication.yml 文件中配置数据库连接信息。

application.properties 示例

# 数据库连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=your_password

# MyBatis 配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true

application.yml 示例

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC
    username: root
    password: your_password

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

3. 创建实体类

创建一个与数据库表对应的实体类。

package com.example.model;

import lombok.Data;

@Data
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private Integer gender;
    private String phone;
}

4. 创建 Mapper 接口

创建一个接口,并使用 @Mapper 注解将其标记为 MyBatis 的 Mapper。

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {

    // 查询所有用户
    @Select("SELECT * FROM user")
    List<User> findAll();

    // 根据 ID 查询用户
    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(Integer id);
}

或者,你也可以将 SQL 写在 XML 文件中。


5. 编写 Mapper XML 文件(可选)

如果你不想使用注解,可以将 SQL 语句写在 XML 文件中。

src/main/resources/mapper 目录下创建 UserMapper.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="findAll" resultType="com.example.model.User">
        SELECT * FROM user
    </select>

    <!-- 根据 ID 查询用户 -->
    <select id="findById" parameterType="int" resultType="com.example.model.User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

6. 开启 Mapper 扫描(可选)

如果项目中有多个 Mapper 接口,可以在主类上使用 @MapperScan 注解来扫描 Mapper 接口所在的包。

package com.example;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.mapper") // 指定 Mapper 接口所在的包
public class MyBatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisApplication.class, args);
    }
}

7. 测试 Mapper 方法

src/test/java 下编写测试类,验证 Mapper 是否正常工作。

package com.example;

import com.example.mapper.UserMapper;
import com.example.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class MyBatisTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testFindAll() {
        List<User> users = userMapper.findAll();
        users.forEach(System.out::println);
    }

    @Test
    public void testFindById() {
        User user = userMapper.findById(1);
        System.out.println(user);
    }
}

8. 使用事务管理

Spring Boot 默认集成了事务管理功能,你可以通过 @Transactional 注解来管理事务。

package com.example.service;

import com.example.mapper.UserMapper;
import com.example.model.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    private final UserMapper userMapper;

    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Transactional
    public void addUser(User user) {
        userMapper.insert(user);
        // 如果抛出异常,事务会回滚
        if (user.getName().equals("test")) {
            throw new RuntimeException("模拟异常");
        }
    }
}

9. 常见问题

1. Mapper 接口未被扫描到

  • 确保在主类上添加了 @MapperScan 或者在每个 Mapper 接口上添加了 @Mapper 注解。
  • 确保 Mapper 接口的包路径正确。

2. SQL 映射文件未生效

  • 确保 mybatis.mapper-locations 配置正确。
  • 确保 XML 文件中的 namespace 与 Mapper 接口的全限定名一致。

3. 数据库字段与实体类属性不匹配

  • application.properties 中启用驼峰命名映射:mybatis.configuration.map-underscore-to-camel-case=true

主键返回

在使用 MyBatis 进行数据库操作时,插入数据后获取主键是一个常见的需求。例如,当你插入一条记录时,数据库会自动生成主键(如 AUTO_INCREMENT),你需要将这个生成的主键返回给应用程序。

MyBatis 提供了多种方式来实现主键返回,以下是详细的实现方法和示例:


1. 使用 useGeneratedKeyskeyProperty

适用场景

  • 数据库支持自增主键(如 MySQL 的 AUTO_INCREMENT)。
  • 插入数据后需要获取生成的主键值。

配置方式

<insert> 标签中设置以下属性:

  • useGeneratedKeys="true":启用主键返回功能。
  • keyProperty="id":指定主键字段对应的实体类属性。

Mapper XML 示例

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

Java 实体类

@Data
public class User {
    private Integer id;   // 主键
    private String name;
    private Integer age;
    private Integer gender;
    private String phone;
}

测试代码

@Test
public void testInsertAndGetId() {
    User user = new User();
    user.setName("张三");
    user.setAge(25);
    user.setGender(1);
    user.setPhone("12345678901");

    userMapper.insertUser(user);

    System.out.println("插入后的主键:" + user.getId());
}

2. 使用 <selectKey>

适用场景

  • 数据库不支持自增主键(如 Oracle 的序列)。
  • 需要在插入前或插入后获取主键值。

配置方式

通过 <selectKey> 标签显式指定主键生成逻辑。

示例 1:MySQL 自增主键
<insert id="insertUser">
    <selectKey keyProperty="id" resultType="int" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO user (name, age, gender, phone)
    VALUES (#{name}, #{age}, #{gender}, #{phone})
</insert>
示例 2:Oracle 序列
<insert id="insertUser">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
        SELECT user_seq.NEXTVAL FROM dual
    </selectKey>
    INSERT INTO user (id, name, age, gender, phone)
    VALUES (#{id}, #{name}, #{age}, #{gender}, #{phone})
</insert>

3. 使用注解方式

如果你使用的是 MyBatis 注解,也可以通过 @Options 注解实现主键返回。

示例代码

@Mapper
public interface UserMapper {

    @Insert("INSERT INTO user (name, age, gender, phone) VALUES (#{name}, #{age}, #{gender}, #{phone})")
    @Options(useGeneratedKeys = true, keyProperty = "id") // 指定主键返回
    int insertUser(User user);
}

4. 返回主键的工作原理

  1. 自增主键

    • 数据库在插入记录时会自动生成主键(如 MySQL 的 AUTO_INCREMENT)。
    • MyBatis 通过 JDBC 的 getGeneratedKeys() 方法获取生成的主键值。
  2. 序列主键

    • 在插入前通过查询序列(如 Oracle 的 NEXTVAL)获取主键值。
    • 将该值作为参数插入到数据库中。
  3. 手动设置主键

    • 如果主键由应用程序生成(如 UUID),可以在插入前手动设置主键值。

5. 注意事项

  1. 主键冲突

    • 确保插入的数据不会违反主键约束(如重复插入相同的主键值)。
  2. 事务管理

    • 如果插入操作在一个事务中,确保事务提交后再获取主键值。
  3. 多主键支持

    • 如果表有多个主键,可以使用 keyProperty="id,anotherKey" 来同时返回多个主键。
  4. 性能优化

    • 对于批量插入,可以结合 useGeneratedKeys<selectKey> 使用,但需要注意性能问题。

总结

MyBatis 提供了多种方式来实现插入后返回主键的功能,主要分为以下两种:

  1. 自增主键:通过 useGeneratedKeyskeyProperty 属性。
  2. 非自增主键:通过 <selectKey> 标签或手动设置主键。

mybatis 缓存

MyBatis 提供了两种类型的缓存来提高查询效率:一级缓存和二级缓存。

一级缓存

  • 默认开启,是SqlSession级别的缓存。在操作数据库时,数据会被先从数据库中取出放入到SqlSession的一级缓存中,在该SqlSession没有被关闭的情况下,再次读取相同的数据时会直接从缓存中获取;当执行了更新、插入、删除等操作后,SqlSession会清空缓存。

二级缓存

  • 是Mapper级别的缓存,默认情况下是关闭的,需要手动开启。二级缓存可以跨SqlSession使用,即不同的SqlSession对象访问同一个Mapper下的sql语句,都可以共享二级缓存的数据。二级缓存是建立在一级缓存之上的,作用域比一级缓存大。

    开启二级缓存的方法是在MyBatis配置文件中加入<setting name="cacheEnabled" value="true"/>,然后在对应的Mapper XML文件中添加<cache/>标签即可开启当前Mapper下的二级缓存。

缓存回收策略

  1. 基于最近最少使用的回收算法(LRU - Least Recently Used)
  2. 基于最不经常使用的回收算法(LFU - Least Frequently Used)
  3. 基于时间的回收算法(TTL - Time To Live)

你可以通过设置cache元素的属性来选择适合你应用的回收策略:

<cache eviction="FIFO" size="512" readOnly="true"/>

在这个例子中,eviction指定了回收策略为先进先出(FIFO),size定义了缓存的最大条目数,readOnly表示缓存是否为只读。

注意事项

  • 使用缓存时要注意事务隔离的问题,避免脏读问题的发生。
  • 当表中的数据有变化时,要确保缓存能够及时更新或失效,以免读取到过期的数据。

MyBatis延迟加载

MyBatis 的延迟加载(Lazy Loading)是一种优化技术,用于在需要时才加载关联数据,而不是在查询主对象时立即加载所有关联数据。延迟加载可以有效减少数据库查询次数,提高性能,特别是在处理复杂对象关系时。

延迟加载的原理

延迟加载的核心思想是:只有在真正访问某个属性或关联数据时,才会触发对应的 SQL 查询。如果没有访问到这些数据,就不会执行额外的查询。

在 MyBatis 中,延迟加载主要通过动态代理实现。当查询主对象时,MyBatis 会返回一个代理对象,而不会立即加载关联数据。只有当调用代理对象的相关方法时,才会触发实际的 SQL 查询。


延迟加载的配置

要启用延迟加载,需要进行以下配置:

1. 全局配置

mybatis-config.xml 文件中,设置全局延迟加载相关的参数:

<settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 关闭积极加载(即默认不加载关联数据) -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
  • lazyLoadingEnabled: 是否开启延迟加载,默认值为 false
  • aggressiveLazyLoading: 是否启用积极加载(即是否在加载主对象时自动加载所有关联数据)。如果设置为 true,则延迟加载无效;建议设置为 false
2. Mapper 配置

在映射文件中,使用 associationcollection 标签来定义关联对象,并指定 fetchType 属性为 lazy

<resultMap id="userMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 定义延迟加载的关联对象 -->
    <association property="address" javaType="Address" fetchType="lazy">
        <id property="id" column="address_id"/>
        <result property="city" column="city"/>
    </association>
</resultMap>

<select id="selectUserWithAddress" resultMap="userMap">
    SELECT u.id, u.name, a.id AS address_id, a.city
    FROM users u
    LEFT JOIN addresses a ON u.address_id = a.id
    WHERE u.id = #{id}
</select>
  • fetchType: 指定加载策略,取值为 eager(立即加载)或 lazy(延迟加载)。

延迟加载的工作流程

  1. 查询主对象

    • 当执行查询主对象的 SQL 时,MyBatis 只会加载主对象的数据,而不会加载关联对象。
    • 如果关联对象被定义为延迟加载,MyBatis 会返回一个代理对象。
  2. 访问关联对象

    • 当代码中第一次访问关联对象(例如调用 getAddress() 方法)时,MyBatis 会触发对应的 SQL 查询,加载关联数据。
  3. 缓存机制

    • 一旦关联数据被加载,MyBatis 会将其缓存起来,避免重复查询。

示例代码

数据库表结构

假设我们有两张表:

  • users: 用户表,包含用户的基本信息。
  • addresses: 地址表,包含用户的地址信息。
Java 实体类
public class User {
    private Integer id;
    private String name;
    private Address address; // 关联的地址对象

    // Getters and Setters
}

public class Address {
    private Integer id;
    private String city;

    // Getters and Setters
}
Mapper XML 配置
<resultMap id="userMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="address" javaType="Address" fetchType="lazy">
        <id property="id" column="address_id"/>
        <result property="city" column="city"/>
    </association>
</resultMap>

<select id="selectUserWithAddress" resultMap="userMap">
    SELECT u.id, u.name, a.id AS address_id, a.city
    FROM users u
    LEFT JOIN addresses a ON u.address_id = a.id
    WHERE u.id = #{id}
</select>
测试代码
public static void main(String[] args) {
    SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 查询用户(此时不会加载地址)
    User user = userMapper.selectUserWithAddress(1);
    System.out.println("User Name: " + user.getName());

    // 访问地址时才会触发加载
    Address address = user.getAddress();
    System.out.println("City: " + address.getCity());

    sqlSession.close();
}

运行结果:

  1. 第一次打印用户名称时,只会执行一条 SQL 查询(查询用户表)。
  2. 当访问 getAddress() 方法时,才会执行第二条 SQL 查询(查询地址表)。

注意事项

  1. 事务隔离与延迟加载

    • 延迟加载依赖于 SqlSession 的生命周期。如果在延迟加载之前关闭了 SqlSession,会导致延迟加载失败。
    • 解决方案:可以在业务层管理事务,确保 SqlSession 在延迟加载完成后再关闭。
  2. N+1 查询问题

    • 如果主对象的数量较多,且每个主对象都需要触发延迟加载,可能会导致大量的 SQL 查询(即 N+1 查询问题)。
    • 解决方案:可以通过批量加载(Batch Loading)或预加载(Eager Loading)来优化。
  3. 代理对象限制

    • 延迟加载的对象是代理对象,不能直接序列化或传递给远程服务。
    • 如果需要传递延迟加载的对象,可以先触发加载,或者将数据转换为普通对象。
  4. 调试困难

    • 延迟加载的 SQL 查询会在运行时动态触发,可能增加调试难度。
    • 可以通过日志记录 SQL 查询,观察延迟加载的行为。

总结

MyBatis 的延迟加载是一种强大的优化工具,特别适合处理复杂的对象关系和大数据量场景。但在使用时需要注意事务管理、性能优化以及潜在的问题(如 N+1 查询)。合理地结合延迟加载和立即加载,才能充分发挥 MyBatis 的性能优势。

预编译

预编译(Prepared Statements)是一种用于数据库查询的技术,它能够提高性能并且有助于防止SQL注入攻击。MyBatis作为一种持久层框架,充分利用了预编译语句来优化数据库交互过程。

预编译的优点

  1. 提高性能:当执行多次相似的SQL语句时,数据库可以重用已经解析和编译好的SQL模板,只需替换参数值即可,这大大减少了重复分析、编译SQL语句的时间开销。

  2. 防止SQL注入:由于预编译语句将SQL逻辑与数据分离,因此即使用户输入恶意代码,也会被视为普通参数而非可执行代码,从而有效避免SQL注入风险。

在MyBatis中使用预编译

在MyBatis中,默认情况下,所有的<select>, <insert>, <update>, 和 <delete>标签都支持预编译。你只需要通过占位符#{}来指定参数的位置,MyBatis就会自动为你的SQL语句使用预编译技术。

例如:

<select id="findUser" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>

在这个例子中,#{id}是一个占位符,MyBatis会将其替换为传入的实际参数值,并确保这条SQL语句被预编译。

使用注意事项

  • 不要直接拼接SQL字符串:避免使用${}这样的表达式来构建SQL语句,因为这种方式不会进行预编译,容易导致SQL注入漏洞。

    <!-- 不推荐 -->
    <select id="findUserByName" parameterType="string" resultType="User">
        SELECT * FROM users WHERE name = '${name}'
    </select>
    

    推荐的方式是使用#{}

    <!-- 推荐 -->
    <select id="findUserByName" parameterType="string" resultType="User">
        SELECT * FROM users WHERE name = #{name}
    </select>
    
  • 批量操作时的考虑:对于批量插入等场景,虽然MyBatis也支持预编译,但需要注意不同数据库对批量操作的支持程度不同,可能需要根据实际情况调整配置或采用其他策略。

动态SQL

动态SQL是MyBatis框架中一个非常强大的功能,它允许你根据不同的条件构建灵活的SQL语句。这对于处理复杂的查询逻辑尤其有用,比如根据不同条件选择不同的查询字段、连接条件或是排序规则等。MyBatis提供了几种标签来帮助开发者实现这一目标。

常用的动态SQL标签

  1. :用于单条件判断。

    <select id="findActiveBlogWithTitleLike" resultType="Blog">
        SELECT * FROM BLOG 
        WHERE state = 'ACTIVE'
        <if test="title != null">
            AND title like #{title}
        </if>
    </select>
    

    这个例子展示了如何使用<if>标签来添加一个可选的查询条件。

  2. , , :类似Java中的switch语句,适用于多分支选择。

    <select id="findActiveBlog" resultType="Blog">
        SELECT * FROM BLOG WHERE state = 'ACTIVE'
        <choose>
            <when test="title != null">
                AND title like #{title}
            </when>
            <when test="author != null and author.name != null">
                AND author_name like #{author.name}
            </when>
            <otherwise>
                AND featured = 1
            </otherwise>
        </choose>
    </select>
    
  3. :自动处理WHERE关键字,并且会智能地去掉多余的ANDOR

    <select id="findActiveBlog" resultType="Blog">
        SELECT * FROM BLOG
        <where>
            <if test="state != null">
                state = #{state}
            </if>
            <if test="title != null">
                AND title like #{title}
            </if>
        </where>
    </select>
    
  4. :用于更新语句,可以智能地处理SET关键字,并去除最后一个逗号。

    <update id="updateAuthorIfNecessary">
        update AUTHOR
        <set>
            <if test="username != null">username=#{username},</if>
            <if test="password != null">password=#{password},</if>
            <if test="email != null">email=#{email},</if>
            <if test="bio != null">bio=#{bio}</if>
        </set>
        where id=#{id}
    </update>
    
  5. :用于遍历集合,常用于IN子句。

    <select id="selectPostIn" resultType="domain.blog.Post">
        SELECT *
        FROM POST P
        WHERE ID in
        <foreach item="item" index="index" collection="list"
                 open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>
    

动态SQL的优势

  • 灵活性:可以根据传入的参数动态生成SQL语句,减少硬编码SQL的数量。
  • 维护性:将SQL逻辑集中管理,便于后期维护和修改。
  • 性能优化:通过合理的条件判断,避免不必要的全表扫描或其他低效操作。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐