第二期:[特殊字符] 深入理解MyBatis[特殊字符]MyBatis基础CRUD操作详解[特殊字符]

前言 🌟

在掌握了 MyBatis 的基本配置与环境搭建之后,接下来的重点便是深入理解其核心功能------CRUD 操作(增删改查)。💻

数据库操作是任何应用开发中不可或缺的一环,而 MyBatis 正是通过灵活的 SQL 映射机制,极大地简化了这些操作的实现过程。本篇将围绕最常见的数据库基本操作展开,带你从传统 JDBC 的冗杂代码,迈向 MyBatis 优雅高效的开发方式。

我们将通过实际案例,结合注解与 XML 两种方式,逐一讲解如何使用 MyBatis 实现:

  • 新增数据 🆕

  • 删除数据 ❌

  • 更新数据 🔁

  • 查询数据 🔍

同时,还将分析每种实现方式的优劣,帮助你根据项目需求做出合理的选择。

无论你是初学者,还是希望进一步深入理解 MyBatis 的开发者,都能从这篇内容中收获实用技巧与开发灵感。🚀


基本操作回顾 🔁

MyBatis 的核心用途之一就是对数据库的 增删改查(CRUD) 操作进行简化和优化。在传统 JDBC 中,每次都需要写大量重复性的代码来完成这些操作,而在 MyBatis 中,你可以通过 注解XML 映射 两种方式优雅地完成同样的工作。

下面我们分别回顾这四种操作的基本语法,并通过两个方式(注解和 XML)进行展示。📘

1. 增、删、改、查的基本语法和实现方式 🔄

在数据库操作中,通常有四种基本操作:增(Insert)💾、删(Delete)🗑️、改(Update)🔧和查(Select)🔍。每种操作的基本语法如下:

  • 增(Insert) 🆕

    复制代码
    INSERT INTO table_name (column1, column2, column3, ...)
    VALUES (value1, value2, value3, ...);
  • 删(Delete)

    复制代码
    DELETE FROM table_name WHERE condition;
  • 改(Update) 🔄

    复制代码
    UPDATE table_name
    SET column1 = value1, column2 = value2, ...
    WHERE condition;
  • 查(Select) 👀

    复制代码
    SELECT column1, column2, column3, ...
    FROM table_name
    WHERE condition
    ORDER BY column1;

这些语法是进行数据库操作的基础,通常会结合特定的框架和技术来简化实现。🚀

2. SQL映射的定义(注解与XML方式) 📝

在 Java 中,使用 MyBatis 框架时,通常需要将 SQL 操作映射到 Java 方法。MyBatis 提供了两种方式来定义这些 SQL 映射:注解方式XML方式

注解方式 💡

在 MyBatis 中,你可以通过注解在接口方法上定义 SQL 语句。以下是常用的注解:

  • @Insert ✍️:用于插入数据。

  • @Update 🔄:用于更新数据。

  • @Delete 🗑️:用于删除数据。

  • @Select 🔍:用于查询数据。

示例:

复制代码
public interface UserMapper {
  
    @Insert("INSERT INTO users (name, age) VALUES (#{name}, #{age})")
    void insertUser(User user);
  
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectUserById(int id);
  
    @Update("UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}")
    void updateUser(User user);
  
    @Delete("DELETE FROM users WHERE id = #{id}")
    void deleteUser(int id);
}
XML方式 📝

在 XML 映射文件中,SQL 语句被定义在 <mapper> 标签中。每个 SQL 操作通过不同的标签(如 <insert>, <select>, <update>, <delete>)进行映射。

示例:

UserMapper.xml

复制代码
<mapper namespace="com.example.UserMapper">
  
    <insert id="insertUser" parameterType="User">
        INSERT INTO users (name, age)
        VALUES (#{name}, #{age})
    </insert>
  
    <select id="selectUserById" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>
  
    <update id="updateUser" parameterType="User">
        UPDATE users
        SET name = #{name}, age = #{age}
        WHERE id = #{id}
    </update>
  
    <delete id="deleteUser" parameterType="int">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>
🧩 注解 vs XML 映射方式对比
方式 优点 🌟 缺点 ⚠️ 适用场景 📌
注解方式 简洁直观,代码集中在一处 不适合复杂 SQL 语句 简单的增删改查
XML方式 结构清晰,适合复杂 SQL & 动态 SQL 映射文件和接口分离,维护略复杂 SQL 语句复杂、需灵活配置时
选择注解还是 XML? 🤔
  • 注解方式 ✅:简洁,适合小型项目或简单的 SQL 操作。

  • XML方式 📂:更灵活,适合复杂查询,且可以分离 SQL 和 Java 代码,提高可维护性。

一般来说,如果项目较小,且 SQL 操作较为简单,可以使用注解;而如果 SQL 语句复杂,或者希望保持 Java 代码与 SQL 的分离,XML 方式更加合适。🔧

在 MyBatis 中,插入操作(Insert)可以通过注解或 XML 配置两种方式实现。此外,利用 @Options 注解或 XML 配置中的 useGeneratedKeys 属性,可以实现插入数据后返回自增主键的功能。以下是对这两种方式的详细介绍和示例代码。


🆕 增操作(Insert)

在 MyBatis 中,执行插入操作主要有两种方式:使用 @Insert 注解 和 使用 XML 映射中的 <insert> 标签。两者都可以完成插入任务,但在处理返回主键时,写法稍有不同。

@Insert 注解与 XML 中 <insert> 标签的差异

1. 使用 @Insert 注解

@Insert 注解用于在 Mapper 接口中直接编写 SQL 语句,适用于简单的插入操作。例如:

复制代码
@Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
int addUser(User user);

这种方式简洁明了,便于快速开发。然而,当 SQL 语句较为复杂或需要动态构建时,注解方式的可读性和维护性会降低。

2. 使用 XML 中的 <insert> 标签

在 XML 配置中,使用 <insert> 标签定义插入操作,适用于复杂的 SQL 语句和动态 SQL 的构建。例如:

复制代码
<insert id="addUser" parameterType="User">
    INSERT INTO users(name, age)
    VALUES(#{name}, #{age})
</insert>

XML 配置方式提供了更高的灵活性和可维护性,特别是在处理复杂逻辑和动态 SQL 时更为合适。


利用 Options 配置返回自增主键

在插入数据后获取数据库生成的自增主键,可以通过以下两种方式实现:

1. 注解方式

在使用 @Insert 注解的同时,结合 @Options 注解配置返回主键的相关属性。例如:

复制代码
@Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int addUser(User user);

  • useGeneratedKeys = true:表示使用数据库自动生成的主键。

  • keyProperty = "id":指定将生成的主键值赋给 User 对象的 id 属性。

  • keyColumn = "id":指定数据库表中的主键列名。

这样配置后,插入操作执行完毕后,User 对象的 id 属性将自动填充为数据库生成的主键值。

2. XML 配置方式

在 XML 中的 <insert> 标签中设置相关属性,实现相同的功能。例如:

复制代码
<insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    INSERT INTO users(name, age)
    VALUES(#{name}, #{age})
</insert>

配置说明与注解方式相同,执行插入操作后,User 对象的 id 属性将被自动赋值为生成的主键。


示例代码

以下是一个完整的示例,展示如何使用注解方式插入数据并返回自增主键:

复制代码
public interface UserMapper {
    @Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    int addUser(User user);
}

在调用 addUser 方法后,User 对象的 id 属性将被自动填充为数据库生成的主键值。

如果使用 XML 配置方式,Mapper 接口方法可以简化为:

复制代码
int addUser(User user);

对应的 XML 配置如下:

复制代码
<insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    INSERT INTO users(name, age)
    VALUES(#{name}, #{age})
</insert>

这样配置后,同样可以在插入数据后自动获取并设置主键值。


❌ 删操作(Delete) & 🔁 改操作(Update)

在 MyBatis 中,删除(Delete)和更新(Update)操作可以通过注解或 XML 配置两种方式实现。在使用这些操作时,参数传递和绑定是关键,尤其是在方法参数较多或参数名与 SQL 中不一致时,合理使用 @Param 注解至关重要,除了掌握基本语法外,参数传递与绑定方式 是非常关键的一环,尤其是当方法中传入多个参数或参数名与 SQL 占位符不一致时,可能导致绑定失败或运行报错。

一.🧠 参数传递注意点

  1. 单个参数

    • 当方法只接受一个参数时,MyBatis 可以自动识别参数名,无需使用 @Param 注解。

    • 例如:

      复制代码
      @Delete("DELETE FROM users WHERE id = #{id}")
      int deleteUserById(int id);
  2. 多个参数

    • 当方法接受多个参数时,MyBatis 默认将参数命名为 param1param2 等,可能导致 SQL 中的参数名与方法参数不一致。

    • 为避免这种情况,建议使用 @Param 注解为每个参数指定名称。

    • 例如:

      复制代码
      @Update("UPDATE users SET name = #{name} WHERE id = #{id}")
      int updateUser(@Param("id") int id, @Param("name") String name);
  3. 使用 JavaBean 作为参数

    • 如果方法参数是一个 JavaBean 对象,MyBatis 会根据对象的属性名进行参数绑定,无需使用 @Param 注解。

    • 例如:

      复制代码
      @Update("UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}")
      int updateUser(User user);
  4. 使用 Map 作为参数

    • 当方法参数为 Map 类型时,SQL 中的参数名应与 Map 的键一致。

    • 例如:

      复制代码
      @Delete("DELETE FROM users WHERE id = #{userId}")
      int deleteUser(Map<String, Object> params);

二、注解方式实现删除和更新操作

1. 删除操作

  • 单个参数

    复制代码
    @Delete("DELETE FROM users WHERE id = #{id}")
    int deleteUserById(int id);

  • 多个参数(使用 @Param)

    复制代码
    @Delete("DELETE FROM users WHERE id = #{id} AND status = #{status}")
    int deleteUser(@Param("id") int id, @Param("status") String status);

2. 更新操作

  • 使用 JavaBean 作为参数

    复制代码
    @Update("UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}")
    int updateUser(User user);

  • 多个参数(使用 @Param)

    复制代码
    @Update("UPDATE users SET name = #{name} WHERE id = #{id}")
    int updateUser(@Param("id") int id, @Param("name") String name);


三、XML 方式实现删除和更新操作

1. 删除操作

复制代码
<delete id="deleteUserById" parameterType="int">
    DELETE FROM users WHERE id = #{id}
</delete>

2. 更新操作

  • 使用 JavaBean 作为参数

    复制代码
    <update id="updateUser" parameterType="User">
        UPDATE users
        SET name = #{name}, age = #{age}
        WHERE id = #{id}
    </update>

  • 多个参数(使用 @Param)

    复制代码
    <update id="updateUser" parameterType="map">
        UPDATE users
        SET name = #{name}
        WHERE id = #{id}
    </update>


四、使用 @Param 注解的最佳实践

  • 多个参数时使用 @Param :当方法有多个参数时,使用 @Param 注解为每个参数指定名称,以确保 SQL 中的参数名与方法参数一致。

  • **避免使用 {}** :在 SQL 中使用 `{}会直接拼接字符串,可能导致 SQL 注入风险。建议使用#{}` 进行参数绑定。

  • 参数名与 SQL 中一致 :确保方法参数名与 SQL 中的参数名一致,或通过 @Param 注解进行明确绑定。


🔍 查询操作(Select)

在 MyBatis 中,查询是使用最频繁、最关键的操作之一。无论是查询单条记录还是查询列表,MyBatis 提供了丰富的方式来处理返回单个对象和列表的场景,同时也支持自动映射(result mapping)的机制,以便把数据库查询结果转换为 Java 对象。

但自动映射不是"万能"的,字段名不一致时容易出现问题,本节将详解如何解决这些常见坑点 👇


一、查询单个对象与列表的差异

  • 单个对象查询

    通常使用方法返回类型为对象(如 User)的查询接口。当查询结果只返回一行数据时,这样能直接将返回值自动映射到对应的 Java 对象。例如:

    复制代码
    @Select("SELECT id, name, age FROM users WHERE id = #{id}")
    User getUserById(int id);

    如果查询结果为空或多条记录,则可能会导致异常,此时需要保证查询条件唯一。

  • 列表查询

    当查询的结果可能包含多行数据时,接口返回值一般为 List<User>。MyBatis 会遍历每一行数据并调用映射规则将每行数据封装成一个 Java 对象,最终返回一个列表。例如:

    复制代码
    @Select("SELECT id, name, age FROM users")
    List<User> getAllUsers();

二、自动映射原理与字段映射问题

MyBatis 能自动将数据库查询的结果映射到 Java 对象中,但前提是 SQL 返回的列名和 Java 对象的属性名需要保持一致。实际应用中常遇到如下问题及相应解决方法:

1. 起别名(Alias)

如果数据库表中的列名与 Java 对象的属性名不一致,可以在 SQL 中使用别名。例如:

复制代码
@Select("SELECT id, name AS userName, age FROM users WHERE id = #{id}")
User getUserById(int id);

这样 MyBatis 就能将查询结果中的 userName 列映射到 Java 对象中的 userName 属性上。

2. 结果映射(Result Map)

对于更复杂的情况,可以在 XML 中通过 <resultMap> 标签来定义映射关系。这样不仅灵活,而且可在一个地方统一管理映射规则。例如:

复制代码
<resultMap id="userResultMap" type="com.example.User">
    <id column="id" property="id"/>
    <result column="name" property="userName"/>
    <result column="age" property="age"/>
</resultMap>

<select id="getUserById" resultMap="userResultMap" parameterType="int">
    SELECT id, name, age FROM users WHERE id = #{id}
</select>

此方式适用于字段较多、数据结构较复杂或者需要复用映射规则的场景。

3. 开启驼峰命名(Camel Case Mapping)

MyBatis 还支持通过全局配置来自动将下划线命名转换为驼峰命名,这样数据库列 user_name 会自动映射到 Java 属性 userName。可以在配置文件中启用该功能:

复制代码
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!-- 也可以设置日志输出,见下节 -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

或者在 application.yml 中(使用 MyBatis-Spring Boot Starter 时)这样配置:

复制代码
mybatis:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这种方式使得数据库列名与 Java 属性之间的差异无需在每个 SQL 中显式别名,从而减少了维护成本。


三、配置日志打印 SQL、参数及执行结果

为了帮助调试,MyBatis 允许开发者配置日志输出,打印 SQL 语句、参数绑定以及执行结果。主要方法有:

1. 配置 MyBatis 日志实现

MyBatis 支持多种日志实现(例如:STDOUT_LOGGING、LOG4J、SLF4J 等)。可以在 MyBatis 配置文件中设置日志实现:

复制代码
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

此配置会将所有 SQL 执行、参数绑定以及执行结果输出到控制台。

2. 配置日志框架

如果项目中使用 Log4j2 或 SLF4J 等日志框架,则需要在日志配置文件中启用 MyBatis 相关的日志记录器。例如,在 Log4j2 的配置文件中:

复制代码
<Logger name="org.apache.ibatis" level="DEBUG" additivity="false">
    <AppenderRef ref="Console"/>
</Logger>

这样便能确保 MyBatis 执行过程中的详细日志输出到控制台,便于调试和性能分析。

3. 配置 application.yml 打印日志

复制代码
logging:
  level:
    com.yourpackage.mapper: debug
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4. 运行时调试效果

调试过程中,会看到类似下面的日志输出:

复制代码
DEBUG - ==>  Preparing: SELECT id, name, age FROM users WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
DEBUG - <==      Total: 1

这显示了待执行的 SQL 语句、参数绑定情况以及执行结果的行数,极大地帮助开发者定位问题。


示例代码总结

注解方式示例(带驼峰配置)

复制代码
@Mapper
public interface UserMapper {
    @Select("SELECT id, name AS userName, age FROM users WHERE id = #{id}")
    User getUserById(int id);

    @Select("SELECT id, name AS userName, age FROM users")
    List<User> getAllUsers();
}

XML 配置示例(使用 resultMap)

复制代码
<resultMap id="userResultMap" type="com.example.User">
    <id column="id" property="id"/>
    <result column="name" property="userName"/>
    <result column="age" property="age"/>
</resultMap>

<select id="getUserById" resultMap="userResultMap" parameterType="int">
    SELECT id, name, age FROM users WHERE id = #{id}
</select>

<select id="getAllUsers" resultMap="userResultMap">
    SELECT id, name, age FROM users
</select>

全局配置开启驼峰命名与日志输出(例如,在 mybatis-config.xml 中)

复制代码
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

案例说明(JDBC和Mybaits操作数据库案例对比)

假设有一个简单的 User 表,结构如下(MySQL 语法示例):

复制代码
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  password VARCHAR(255) NOT NULL,
  email VARCHAR(100),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

对应的 Java 实体类 User 如下:

复制代码
public class User {
    private Integer id;
    private String username;
    private String password;
    private String email;
    private Timestamp createdAt;

    // Getters and Setters
}

Part 1:基于 JDBC 实现 CRUD

1.1 JDBC 工具类

创建一个简单的 JDBC 工具类,用于获取数据库连接并释放资源。

复制代码
import java.sql.*;

public class JDBCUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";

    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }

    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        try { if(rs != null) rs.close(); } catch (Exception e) { e.printStackTrace(); }
        try { if(stmt != null) stmt.close(); } catch (Exception e) { e.printStackTrace(); }
        try { if(conn != null) conn.close(); } catch (Exception e) { e.printStackTrace(); }
    }
}

1.2 JDBC 实现 CRUD 的 DAO

复制代码
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class UserJDBCDao {

    // 增
    public int insertUser(User user) {
        String sql = "INSERT INTO users(username, password, email) VALUES (?, ?, ?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = JDBCUtil.getConnection();
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            pstmt.setString(1, user.getUsername());
            pstmt.setString(2, user.getPassword());
            pstmt.setString(3, user.getEmail());
            int rows = pstmt.executeUpdate();

            // 获取自增主键
            ResultSet rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                user.setId(rs.getInt(1));
            }
            rs.close();
            return rows;
        } catch (SQLException e) {
            e.printStackTrace();
            return 0;
        } finally {
            JDBCUtil.close(conn, pstmt, null);
        }
    }

    // 删
    public int deleteUserById(int id) {
        String sql = "DELETE FROM users WHERE id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = JDBCUtil.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id);
            return pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            return 0;
        } finally {
            JDBCUtil.close(conn, pstmt, null);
        }
    }

    // 改
    public int updateUserEmail(int id, String newEmail) {
        String sql = "UPDATE users SET email = ? WHERE id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = JDBCUtil.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, newEmail);
            pstmt.setInt(2, id);
            return pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            return 0;
        } finally {
            JDBCUtil.close(conn, pstmt, null);
        }
    }

    // 查 ------ 查询单个
    public User getUserById(int id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtil.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id);
            rs = pstmt.executeQuery();
            if (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("username"));
                user.setPassword(rs.getString("password"));
                user.setEmail(rs.getString("email"));
                user.setCreatedAt(rs.getTimestamp("created_at"));
                return user;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.close(conn, pstmt, rs);
        }
        return null;
    }

    // 查 ------ 查询列表
    public List<User> getAllUsers() {
        String sql = "SELECT * FROM users";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<User> list = new ArrayList<>();
        try {
            conn = JDBCUtil.getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("username"));
                user.setPassword(rs.getString("password"));
                user.setEmail(rs.getString("email"));
                user.setCreatedAt(rs.getTimestamp("created_at"));
                list.add(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.close(conn, pstmt, rs);
        }
        return list;
    }
}

1.3 JDBC 测试代码示例

复制代码
public class TestJDBC {
    public static void main(String[] args) {
        UserJDBCDao dao = new UserJDBCDao();
        
        // 插入数据
        User newUser = new User();
        newUser.setUsername("Bob");
        newUser.setPassword("pass123");
        newUser.setEmail("bob@example.com");
        int insertRows = dao.insertUser(newUser);
        System.out.println("JDBC 插入行数:" + insertRows + ", 生成的ID:" + newUser.getId());
        
        // 更新数据
        int updateRows = dao.updateUserEmail(newUser.getId(), "bob_new@example.com");
        System.out.println("JDBC 更新行数:" + updateRows);
        
        // 查询数据
        User queryUser = dao.getUserById(newUser.getId());
        System.out.println("JDBC 查询到的用户:" + queryUser.getUsername() + ", Email:" + queryUser.getEmail());
        
        // 删除数据
        int deleteRows = dao.deleteUserById(newUser.getId());
        System.out.println("JDBC 删除行数:" + deleteRows);
    }
}

JDBC 实现的优缺点

  • 优点:

    • 灵活度高,对连接、事务、异常处理有完全控制
  • 缺点:

    • 大量重复代码(获取连接、释放资源、异常处理)

    • 代码冗长,可读性和维护性较差


Part 2:基于 MyBatis 重构 CRUD

2.1 MyBatis 配置

配置文件(例如 application.yml 部分):

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.demo.model
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.2 MyBatis Mapper 接口与映射文件

Mapper 接口(UserMapper.java)
复制代码
package com.example.demo.mapper;

import com.example.demo.model.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {

    @Insert("INSERT INTO users(username, password, email) VALUES (#{username}, #{password}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    int insertUser(User user);

    @Delete("DELETE FROM users WHERE id = #{id}")
    int deleteUserById(@Param("id") int id);

    @Update("UPDATE users SET email = #{email} WHERE id = #{id}")
    int updateUserEmail(@Param("id") int id, @Param("email") String email);

    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(@Param("id") int id);

    @Select("SELECT * FROM users")
    List<User> getAllUsers();
}
对应 XML 映射文件(可选,下面以注解方式为主,略)

如果需要使用 XML 映射,可以将 SQL 语句放在 resources/mapper/UserMapper.xml 中,并在接口中不采用注解。

2.3 MyBatis 测试代码示例

使用 Spring Boot 进行单元测试:

复制代码
import com.example.demo.mapper.UserMapper;
import com.example.demo.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 UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testInsertUser() {
        User user = new User();
        user.setUsername("Alice");
        user.setPassword("alicepass");
        user.setEmail("alice@example.com");
        
        int rows = userMapper.insertUser(user);
        System.out.println("MyBatis 插入行数:" + rows);
        System.out.println("生成的主键 ID:" + user.getId());
    }

    @Test
    public void testUpdateUserEmail() {
        int rows = userMapper.updateUserEmail(1, "alice_new@example.com");
        System.out.println("MyBatis 更新行数:" + rows);
    }

    @Test
    public void testGetUserById() {
        User user = userMapper.getUserById(1);
        System.out.println("MyBatis 查询到的用户:" + user.getUsername() + ", Email:" + user.getEmail());
    }

    @Test
    public void testDeleteUser() {
        int rows = userMapper.deleteUserById(1);
        System.out.println("MyBatis 删除行数:" + rows);
    }

    @Test
    public void testGetAllUsers() {
        List<User> users = userMapper.getAllUsers();
        users.forEach(user -> System.out.println("用户:" + user.getUsername() + ", Email:" + user.getEmail()));
    }
}

MyBatis 实现的优缺点

  • 优点:

    • 极大减少重复代码,无需手动管理连接和资源

    • 通过注解或 XML 映射,SQL 与 Java 代码分离,维护性更高

    • 内置功能强大,例如动态 SQL、自动映射、日志输出等

  • 缺点:

    • 初期配置及学习曲线略高

    • 对于非常简单的场景,可能显得"杀鸡用牛刀"


Part 3:实际测试结果对比(IDEA 运行日志截图模拟)

JDBC 版运行日志(示例输出):

复制代码
JDBC 插入行数:1, 生成的ID:10
JDBC 更新行数:1
JDBC 查询到的用户:Bob, Email:bob_new@example.com
JDBC 删除行数:1

说明:运行过程中,各步骤都需要手动捕获异常及关闭连接,在 IDEA 控制台可见大量重复日志信息。


MyBatis 版运行日志(示例输出):

复制代码
==>  Preparing: INSERT INTO users(username, password, email) VALUES (?,?,?)
==>  Parameters: Alice(String), alicepass(String), alice@example.com(String)
<==    Updates: 1
MyBatis 插入行数:1
生成的主键 ID:12

==>  Preparing: UPDATE users SET email = ? WHERE id = ?
==>  Parameters: alice_new@example.com(String), 1(Integer)
<==    Updates: 1
MyBatis 更新行数:1

==>  Preparing: SELECT * FROM users WHERE id = ?
==>  Parameters: 1(Integer)
<==      Total: 1
MyBatis 查询到的用户:Alice, Email:alice_new@example.com

==>  Preparing: DELETE FROM users WHERE id = ?
==>  Parameters: 1(Integer)
<==    Updates: 1
MyBatis 删除行数:1

说明:通过 MyBatis 的日志配置,可直接在控制台看到 SQL 语句、绑定参数及执行结果,大幅提升调试效率与可读性。


总结对比

  • 代码量:

    JDBC 实现需要大量重复代码(连接、语句、资源关闭等),而 MyBatis 通过映射注解和配置文件大大减少了冗余。

  • 易用性:

    使用 MyBatis 后,开发者可以专注于 SQL 和业务逻辑,无需关注细节的资源管理;同时日志配置方便调试。

  • 维护性:

    JDBC 代码中 SQL 嵌入 Java 中导致耦合度高,修改一处需要重新编译;而 MyBatis 通过 XML 或注解实现 SQL 与 Java 的分离,使得后期维护更加高效。

  • 调试:

    MyBatis 内置日志输出(显示具体 SQL、参数、执行结果),使问题排查变得简单直观,而 JDBC 的日志需要自行实现和维护。


结论

通过上面的案例演示,可以看到 MyBatis 在开发体验、代码维护与调试方面均优于传统的 JDBC 实现。在现代项目中,推荐优先考虑基于 MyBatis 或类似框架来进行数据持久化操作,从而提高整体开发效率与代码可维护性。


实际截图说明:

在 IDEA 中运行测试时,你可以截取各个测试方法的控制台日志。

例如:

  • JDBC 版的日志截图显示"JDBC 插入行数:1, 生成的ID:10"等信息。

  • MyBatis 版截图则包含了标准输出的 SQL 语句、参数及执行统计信息(类似上面的日志示例)。

由于本回答无法直接上传图片,你可以自行在 IDEA 中运行上述测试代码并截图保存以作参考。


希望这个完整的小案例能帮助你深刻理解传统 JDBC 与 MyBatis 在 CRUD 操作上的差异,并体会到 MyBatis 在简化代码和提高开发效率方面的优势!


结语 🎉

本文详细讲解了 MyBatis 基础 CRUD 操作的各个方面,从增删改查的基本语法到注解和 XML 映射方式的具体实现,再到参数绑定、自动映射和日志调试的使用技巧。通过这些示例与对比,我们可以发现:

  • 简化开发流程:MyBatis 通过灵活的 SQL 映射机制,大幅减少了传统 JDBC 开发中冗长且重复的代码。

  • 增强代码可维护性:SQL 语句与 Java 代码的分离使得代码结构更清晰,修改和扩展更加方便,解决了 SQL 与业务逻辑耦合的问题。

  • 方便调试与追踪:借助日志配置,开发者能够直观地查看 SQL 语句、参数绑定和执行结果,显著提高了调试效率。

  • 灵活应对复杂场景:无论是简单 CRUD 操作还是复杂的动态 SQL 构造,MyBatis 都能提供有效的支持,满足不同项目的需求。

总体来看,MyBatis 为开发者提供了一个高效、灵活和易维护的数据持久层解决方案,使得构建健壮和可扩展的企业级应用成为可能。希望本篇文章能帮助你更好地理解和掌握 MyBatis 的核心技术,并在实际项目中充分发挥其优势!🚀

继续深入探索 MyBatis 及其它持久化技术,你的开发技能必将更上一层楼!😊

相关推荐
兩尛2 分钟前
Spring面试
java·spring·面试
Java中文社群9 分钟前
服务器被攻击!原因竟然是他?真没想到...
java·后端
Full Stack Developme20 分钟前
java.nio 包详解
java·python·nio
零千叶37 分钟前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝1 小时前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li3714908901 小时前
nginx报400bad request 请求头过大异常处理
java·运维·nginx
摇滚侠1 小时前
Spring Boot 项目, idea 控制台日志设置彩色
java·spring boot·intellij-idea
Aevget2 小时前
「Java EE开发指南」用MyEclipse开发的EJB开发工具(二)
java·ide·java-ee·eclipse·myeclipse
黄昏晓x2 小时前
C++----多态
java·jvm·c++
Brookty2 小时前
【算法】前缀和
java·学习·算法·前缀和·动态规划