MyBatis 从零实战:完整搭建可运行 Demo,注解与 XML 双模式开发详解

一、前言:为什么需要 MyBatis?

如果你写过 JDBC,一定体会过这种痛苦:

java 复制代码
// JDBC 六步曲,每个接口都要写一遍
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, pwd);
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM t_user WHERE id = ?");
pstmt.setLong(1, id);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
    User user = new User();
    user.setId(rs.getLong("id"));
    user.setUsername(rs.getString("username"));
    // ... 十几个字段手动映射
}
rs.close(); pstmt.close(); conn.close();

MyBatis 解决的核心问题:

JDBC 痛点 MyBatis 方案 效果
大量重复代码 Mapper 接口 + 注解/XML 代码量减少 70%
SQL 与 Java 混杂 XML 分离或注解简洁 维护性提升
结果集手动映射 自动映射到对象 不再写 getString
动态 SQL 拼接 <if><where> 标签 条件查询不再头疼
连接管理繁琐 Spring 集成自动管理 专注业务逻辑

MyBatis 定位: 半自动 ORM,SQL 自己写(可控),结果映射自动做(省心)。


二、环境准备

2.1 需要安装

软件 版本 检查命令
JDK 17+ java -version
Maven 3.8+ mvn -v
MySQL 8.0+ mysql --version
IDEA 2023+ -
2.2 创建 Spring Boot 项目
  1. 打开 start.spring.io/

  2. 选择:

    • Project: Maven
    • Language: Java
    • Spring Boot: 3.2.0
    • Dependencies: Spring Web, MySQL Driver, MyBatis Framework
  3. 点击 Generate,下载解压后用 IDEA 打开

2.3 项目结构预览

plain 复制代码
mybatis-demo/
├── pom.xml
└── src/
    └── main/
        ├── java/com/example/demo/
        │   ├── DemoApplication.java
        │   ├── entity/              ← 实体类
        │   │   └── User.java
        │   ├── mapper/              ← Mapper 接口
        │   │   └── UserMapper.java
        │   ├── service/             ← 业务层
        │   │   └── UserService.java
        │   └── controller/          ← 控制器
        │       └── UserController.java
        └── resources/
            ├── application.yml      ← 配置文件
            └── mapper/              ← XML 映射文件
                └── UserMapper.xml

三、创建数据库

3.1 执行建库脚本

sql 复制代码
-- 创建数据库
CREATE DATABASE IF NOT EXISTS mybatis_demo 
    DEFAULT CHARACTER SET utf8mb4 
    COLLATE utf8mb4_unicode_ci;

USE mybatis_demo;

-- 创建用户表
CREATE TABLE t_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    email VARCHAR(100) COMMENT '邮箱',
    age INT COMMENT '年龄',
    phone VARCHAR(20) COMMENT '手机号',
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-正常 1-删除'
) COMMENT '用户表';

-- 插入测试数据
INSERT INTO t_user (username, email, age, phone) VALUES
('张三', 'zhangsan@example.com', 25, '13800138001'),
('李四', 'lisi@example.com', 30, '13800138002'),
('王五', 'wangwu@example.com', 28, '13800138003'),
('赵六', 'zhaoliu@example.com', 35, '13800138004'),
('孙七', 'sunqi@example.com', 22, '13800138005');

-- 验证数据
SELECT * FROM t_user;

四、完整代码

4.1 pom.xml(完整依赖)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0.0</version>
    <name>mybatis-demo</name>
    <description>MyBatis Demo Project</description>
    
    <properties>
        <java.version>17</java.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- MyBatis Spring Boot Starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
        
        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- 参数校验 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.2 application.yml(完整配置)

yaml 复制代码
# 服务器端口
server:
  port: 8080

# 数据源配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    username: root
    password: your_password  # 修改为你的密码
    driver-class-name: com.mysql.cj.jdbc.Driver

# MyBatis 配置
mybatis:
  # XML 映射文件位置
  mapper-locations: classpath:mapper/*.xml
  # 实体类别名包
  type-aliases-package: com.example.demo.entity
  configuration:
    # 自动驼峰转换:create_time → createTime
    map-underscore-to-camel-case: true
    # 打印 SQL 到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 缓存开启
    cache-enabled: true

# 日志级别
logging:
  level:
    com.example.demo.mapper: debug  # 打印 Mapper SQL

4.3 实体类 User.java

java 复制代码
package com.example.demo.entity;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户实体类
 * 
 * 对应数据库表 t_user
 * 字段名与表字段一一对应,驼峰命名自动转换
 */
@Data
public class User {
    /** 用户ID */
    private Long id;
    /** 用户名 */
    private String username;
    /** 邮箱 */
    private String email;
    /** 年龄 */
    private Integer age;
    /** 手机号 */
    private String phone;
    /** 创建时间 */
    private LocalDateTime createTime;
    /** 更新时间 */
    private LocalDateTime updateTime;
    /** 逻辑删除:0-正常 1-删除 */
    private Integer deleted;
}

4.4 统一响应结果 Result.java

java 复制代码
package com.example.demo.common;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * 统一响应结果
 */
@Data
public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    private String timestamp;

    private Result() {
        this.timestamp = LocalDateTime.now().toString();
    }

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("成功");
        result.setData(data);
        return result;
    }

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> error(Integer code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
}

4.5 分页结果 PageResult.java

java 复制代码
package com.example.demo.common;

import lombok.Data;

import java.util.List;

/**
 * 分页结果
 */
@Data
public class PageResult<T> {
    private Long total;
    private List<T> list;
    private Integer pageNum;
    private Integer pageSize;
    private Integer totalPages;

    public static <T> PageResult<T> of(Long total, List<T> list, Integer pageNum, Integer pageSize) {
        PageResult<T> result = new PageResult<>();
        result.setTotal(total);
        result.setList(list);
        result.setPageNum(pageNum);
        result.setPageSize(pageSize);
        result.setTotalPages((int) Math.ceil((double) total / pageSize));
        return result;
    }
}

4.6 业务异常 BusinessException.java

java 复制代码
package com.example.demo.exception;

import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {
    private final Integer code;

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

4.7 全局异常处理器 GlobalExceptionHandler.java

java 复制代码
package com.example.demo.handler;

import com.example.demo.common.Result;
import com.example.demo.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import jakarta.servlet.http.HttpServletRequest;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
//        log.warn("业务异常 [{}] - {}", request.getRequestURI(), e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e, HttpServletRequest request) {
//        log.error("系统异常 [{}]", request.getRequestURI(), e);
        return Result.error(500, "系统繁忙,请稍后重试");
    }
}

4.8 Mapper 接口 UserMapper.java

java 复制代码
package com.example.demo.mapper;

import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 用户 Mapper
 * 
 * @Mapper 标记为 MyBatis Mapper,Spring 自动扫描并生成代理对象
 */
@Mapper
public interface UserMapper {
    
    /**
     * 根据 ID 查询
     */
    User selectById(Long id);
    
    /**
     * 查询所有
     */
    List<User> selectAll();
    
    /**
     * 条件查询
     * @param username 用户名(模糊查询)
     * @param minAge 最小年龄
     * @param maxAge 最大年龄
     */
    List<User> selectByCondition(@Param("username") String username,
                                  @Param("minAge") Integer minAge,
                                  @Param("maxAge") Integer maxAge);
    
    /**
     * 分页查询
     * @param offset 偏移量
     * @param pageSize 每页条数
     */
    List<User> selectByPage(@Param("offset") Integer offset, 
                             @Param("pageSize") Integer pageSize);
    
    /**
     * 统计总数
     */
    long count();
    
    /**
     * 插入
     */
    int insert(User user);
    
    /**
     * 更新(动态 SQL)
     */
    int update(User user);
    
    /**
     * 逻辑删除
     */
    int deleteById(Long id);
}

4.9 resources/mapper/UserMapper.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.demo.mapper.UserMapper">
    
    <!-- 
        结果映射
        将数据库字段名映射到实体类属性名
        开启 map-underscore-to-camel-case 后,大部分可自动映射
        这里显式定义是为了演示
    -->
    <resultMap id="BaseResultMap" type="com.example.demo.entity.User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="email" property="email"/>
        <result column="age" property="age"/>
        <result column="phone" property="phone"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
        <result column="deleted" property="deleted"/>
    </resultMap>
    
    <!-- 基础列,避免重复书写 -->
    <sql id="Base_Column_List">
        id, username, email, age, phone, create_time, update_time, deleted
    </sql>
    
    <!-- 根据 ID 查询 -->
    <select id="selectById" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM t_user
        WHERE id = #{id} AND deleted = 0
    </select>
    
    <!-- 查询所有 -->
    <select id="selectAll" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM t_user
        WHERE deleted = 0
        ORDER BY create_time DESC
    </select>
    
    <!-- 
        条件查询(动态 SQL)
        使用 <where> 标签自动处理 WHERE 和 AND
        如果所有条件都不满足,不会生成 WHERE 子句
    -->
    <select id="selectByCondition" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM t_user
        <where>
            deleted = 0
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="minAge != null">
                AND age >= #{minAge}
            </if>
            <if test="maxAge != null">
                AND age <= #{maxAge}
            </if>
        </where>
        ORDER BY create_time DESC
    </select>
    
    <!-- 分页查询 -->
    <select id="selectByPage" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM t_user
        WHERE deleted = 0
        ORDER BY create_time DESC
        LIMIT #{offset}, #{pageSize}
    </select>
    
    <!-- 统计总数 -->
    <select id="count" resultType="long">
        SELECT COUNT(*) FROM t_user WHERE deleted = 0
    </select>
    
    <!-- 
        插入
        useGeneratedKeys: 使用数据库自增主键
        keyProperty: 将生成的主键赋值给实体类的 id 属性
    -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_user (username, email, age, phone)
        VALUES (#{username}, #{email}, #{age}, #{phone})
    </insert>
    
    <!-- 
        更新(动态 SQL)
        <set> 标签自动处理 SET 和逗号
        只更新传入的非 null 字段
    -->
    <update id="update">
        UPDATE t_user
        <set>
            <if test="username != null">username = #{username},</if>
            <if test="email != null">email = #{email},</if>
            <if test="age != null">age = #{age},</if>
            <if test="phone != null">phone = #{phone},</if>
        </set>
        WHERE id = #{id} AND deleted = 0
    </update>
    
    <!-- 逻辑删除 -->
    <update id="deleteById">
        UPDATE t_user SET deleted = 1 WHERE id = #{id}
    </update>
</mapper>

4.10 Service 层 UserService.java

java 复制代码
package com.example.demo.service;

import com.example.demo.common.PageResult;
import com.example.demo.common.Result;
import com.example.demo.entity.User;
import com.example.demo.exception.BusinessException;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 根据 ID 查询
     */
    public User getById(Long id) {
        if (id == null || id <= 0) {
            throw new BusinessException(400, "用户ID必须大于0");
        }
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(404, "用户不存在");
        }
        return user;
    }
    
    /**
     * 查询所有
     */
    public List<User> findAll() {
        return userMapper.selectAll();
    }
    
    /**
     * 条件查询
     */
    public List<User> findByCondition(String username, Integer minAge, Integer maxAge) {
        return userMapper.selectByCondition(username, minAge, maxAge);
    }
    
    /**
     * 分页查询
     */
    public PageResult<User> findByPage(Integer pageNum, Integer pageSize) {
        if (pageNum == null || pageNum < 1) pageNum = 1;
        if (pageSize == null || pageSize < 1) pageSize = 10;
        
        int offset = (pageNum - 1) * pageSize;
        List<User> list = userMapper.selectByPage(offset, pageSize);
        long total = userMapper.count();
        
        return PageResult.of(total, list, pageNum, pageSize);
    }
    
    /**
     * 新增(事务)
     */
    @Transactional
    public User create(User user) {
        if (user == null) {
            throw new BusinessException(400, "用户信息不能为空");
        }
        if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
            throw new BusinessException(400, "用户名不能为空");
        }
        
        userMapper.insert(user);
        return user;
    }
    
    /**
     * 更新(事务)
     */
    @Transactional
    public User update(User user) {
        if (user == null || user.getId() == null) {
            throw new BusinessException(400, "用户ID不能为空");
        }
        
        int rows = userMapper.update(user);
        if (rows == 0) {
            throw new BusinessException(404, "用户不存在或已被删除");
        }
        return user;
    }
    
    /**
     * 删除(事务)
     */
    @Transactional
    public void delete(Long id) {
        if (id == null || id <= 0) {
            throw new BusinessException(400, "用户ID必须大于0");
        }
        
        int rows = userMapper.deleteById(id);
        if (rows == 0) {
            throw new BusinessException(404, "用户不存在");
        }
    }
}

4.11 Controller 层 UserController.java

java 复制代码
package com.example.demo.controller;

import com.example.demo.common.PageResult;
import com.example.demo.common.Result;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    /**
     * 根据 ID 查询
     * GET /api/users/1
     */
    @GetMapping("/{id}")
    public Result<User> getById(@PathVariable Long id) {
        return Result.success(userService.getById(id));
    }
    
    /**
     * 查询所有
     * GET /api/users
     */
    @GetMapping
    public Result<List<User>> findAll() {
        return Result.success(userService.findAll());
    }
    
    /**
     * 条件查询
     * GET /api/users/search?username=张&minAge=20&maxAge=30
     */
    @GetMapping("/search")
    public Result<List<User>> search(
            @RequestParam(required = false) String username,
            @RequestParam(required = false) Integer minAge,
            @RequestParam(required = false) Integer maxAge) {
        return Result.success(userService.findByCondition(username, minAge, maxAge));
    }
    
    /**
     * 分页查询
     * GET /api/users/page?pageNum=1&pageSize=2
     */
    @GetMapping("/page")
    public Result<PageResult<User>> page(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        return Result.success(userService.findByPage(pageNum, pageSize));
    }
    
    /**
     * 新增
     * POST /api/users
     */
    @PostMapping
    public Result<User> create(@RequestBody User user) {
        return Result.success(userService.create(user));
    }
    
    /**
     * 更新
     * PUT /api/users
     */
    @PutMapping
    public Result<User> update(@RequestBody User user) {
        return Result.success(userService.update(user));
    }
    
    /**
     * 删除
     * DELETE /api/users/1
     */
    @DeleteMapping("/{id}")
    public Result<Void> delete(@PathVariable Long id) {
        userService.delete(id);
        return Result.success();
    }
}

4.12 启动类 DemoApplication.java

java 复制代码
package com.example.demo;

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

@SpringBootApplication
@MapperScan("com.example.demo.mapper")  // 添加这行
public class DemoApplication {
    public static void main(String[] args) {
       SpringApplication.run(DemoApplication.class, args);
       System.out.println("=== MyBatis Demo 启动成功 ===");
       System.out.println("访问地址: http://localhost:8080");
    }
}

五、运行与测试

5.1 启动项目

在 IDEA 中右键 DemoApplication.javaRun,或命令行:

bash 复制代码
mvn spring-boot:run

看到控制台输出 SQL 即表示成功:

plain 复制代码
==>  Preparing: SELECT id, username, email, age, phone, create_time, update_time, deleted FROM t_user WHERE deleted = 0 ORDER BY create_time DESC
==> Parameters: 
<==    Columns: id, username, email, age, phone, create_time, update_time, deleted
<==        Row: 1, 张三, zhangsan@example.com, 25, 13800138001, 2026-06-15 10:00:00, 2026-06-15 10:00:00, 0

5.2 接口测试

bash 复制代码
# 1. 查询所有用户
curl http://localhost:8080/api/users

# 2. 根据 ID 查询
curl http://localhost:8080/api/users/1

# 3. 条件查询(模糊匹配用户名,年龄范围)
curl "http://localhost:8080/api/users/search?username=张&minAge=20&maxAge=30"

# 4. 分页查询
curl "http://localhost:8080/api/users/page?pageNum=1&pageSize=2"

# 5. 新增用户
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"周八","email":"zhouba@example.com","age":40,"phone":"13800138006"}'

# 6. 更新用户(只传需要改的字段)
curl -X PUT http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"id":1,"username":"张三(已修改)","age":26}'

# 7. 删除用户(逻辑删除)
curl -X DELETE http://localhost:8080/api/users/1

# 8. 验证删除后查不到
curl http://localhost:8080/api/users/1
# 返回:{"code":404,"message":"用户不存在",...}

六、注解方式对比(快速参考)

上面的 Demo 使用 XML 方式,简单场景也可用注解:

java 复制代码
@Mapper
public interface UserMapperAnnotation {
    
    @Select("SELECT * FROM t_user WHERE id = #{id} AND deleted = 0")
    User selectById(Long id);
    
    @Select("SELECT * FROM t_user WHERE deleted = 0")
    List<User> selectAll();
    
    @Insert("INSERT INTO t_user (username, email, age, phone) VALUES (#{username}, #{email}, #{age}, #{phone})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    @Update("UPDATE t_user SET deleted = 1 WHERE id = #{id}")
    int deleteById(Long id);
}
方式 适用场景 优点 缺点
注解 简单 CRUD 简洁,一眼看到 SQL 复杂 SQL 难维护
XML 复杂查询、动态 SQL 语法高亮,便于管理 需要维护两个文件

七、核心知识点总结

知识点 要点
#{} vs ${} #{} 预编译防注入;${} 直接拼接,用于动态表名/列名
resultMap 自定义字段与属性映射关系
<where> 自动处理 WHERE 和 AND,避免语法错误
<set> 自动处理 SET 和逗号,动态更新
<if> 条件判断,test 属性写 OGNL 表达式
useGeneratedKeys 获取数据库自增主键
@Transactional 声明式事务,方法异常自动回滚

八、常见错误排查

错误 原因 解决
Invalid bound statement Mapper 接口与 XML 命名空间不匹配 检查 namespace
Property 'sqlSessionFactory' not found 数据源配置错误 检查 application.yml
SQL 不打印 日志配置未开启 添加 log-impl 配置
返回 null 字段名与属性名不匹配 开启驼峰转换或写 resultMap

九、面试题自检

问题 答案
MyBatis 与 Hibernate 区别? MyBatis SQL 可控,Hibernate 自动生成
#{}${} 区别? #{} 预编译防注入,${} 直接拼接
动态 SQL 标签有哪些? <if><where><set><foreach><choose>
一对多怎么查? <resultMap> + <collection>
延迟加载怎么配? fetchType="lazy" + 代理对象
相关推荐
砍材农夫1 小时前
python环境|conda安装和使用(1)
开发语言·后端·python·conda
用户298698530141 小时前
Java 实践:查找与提取 Word 文档超链接
java·后端
Rust研习社1 小时前
Rust 错误处理的黄金搭档:一个定义错误,一个传播错误
后端·rust·编程语言
Flittly1 小时前
【AgentScope Java新手村系列】(9)SpringBoot集成
java·spring boot·spring
Moment1 小时前
从多人编辑到 Agent 写文档,Hocuspocus v4 正在改写协同系统 😍😍😍
前端·后端·面试
贺国亚1 小时前
评估-Eval-Hallucination与质量度量
后端·面试
Java内核笔记1 小时前
Spring Security 源码解析(五)表单登录认证全流程:UsernamePasswordAuthenticationFilter 拆解
后端
Dilee2 小时前
Spring AI 对话记忆:MessageChatMemoryAdvisor 最小接入
后端
星环科技2 小时前
数据标准Agent ,让企业数据说同一种语言
java·开发语言·前端