1JDBC(了解即可)

JDBC(Java Database Connectivity)是 Java 语言操作关系型数据库的标准 API,属于 Java 核心库,定义了一套独立于数据库厂商的统一接口,实现 Java 程序与数据库的无缝连接。

它通过驱动管理器加载不同数据库(如 MySQL、Oracle)的驱动类,支持建立连接、创建执行 SQL 的 Statement/PreparedStatement 对象、处理 ResultSet 结果集、释放资源等核心操作。开发者无需关注数据库底层实现,仅需调用 JDBC 接口即可完成增删改查。其核心组件包括DriverManagerConnectionStatementResultSet,是 Java 后端开发中数据库交互的基础,也是 MyBatis 等持久层框架的底层支撑。

本质

是sun 公司官方定义的一套操作所有关系型数据库的规范,就是接口

各个数据库厂商取实现这套接口,提供数据库驱动jar包

我们可以使用这套接口编程,真正执行的代码时驱动jar包中的实现类

下面是一段JDBC的代码。繁琐,不方便打包,资源浪费,系统性能降低,因此一般不推荐些JDBC

package com.example.demo.dao;

import com.example.demo.model.User;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class UserJdbcDao {

    // 替换为你的实际数据库配置
    private static final String URL = "jdbc:mysql://localhost:3306/spring_exercise?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC";
    private static final String USERNAME = "root"; // 默认为root,若有修改请调整
    private static final String PASSWORD = "123456";

    // 全表查询User数据的方法
    public List<User> queryAllUsers() {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        List<User> userList = new ArrayList<>();

        try {
            // 加载MySQL驱动(MySQL 8.0+ 可省略)
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 获取数据库连接
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);

            // 执行查询SQL
            String sql = "SELECT id, name, man, phone, address FROM user"; // 确保表名和字段名与数据库一致
            stmt = conn.createStatement();
            rs = stmt.executeQuery(sql);

            // 封装结果集
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setMan(rs.getBoolean("man"));
                user.setPhone(rs.getString("phone"));
                user.setAddress(rs.getString("address"));
                userList.add(user);
            }

        } catch (ClassNotFoundException e) {
            System.err.println("驱动加载失败:" + e.getMessage());
        } catch (SQLException e) {
            System.err.println("数据库操作异常:" + e.getMessage());
        } finally {
            // 关闭资源
            try {
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                System.err.println("关闭资源失败:" + e.getMessage());
            }
        }

        return userList;
    }

2MyBatis注解

2.1lombok

Lombok 是一款 Java 注解式工具库,核心目标是消除 Java 开发中大量重复的模板代码(比如实体类的 getter/setter、构造方法、toString 方法等)。对新手来说,它就像给 Java 类 “贴标签”—— 你只需要添加注解,编译器会在编译阶段自动帮你生成这些繁琐代码,不用手动一行行写

引入依赖

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

1. 常用注解

注解 核心作用
@Data(最常用) 组合注解:包含 @Getter+@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor
@Getter/@Setter 为字段生成 getter/setter 方法(可指定仅给部分字段生成)
@NoArgsConstructor 生成无参构造方法
@AllArgsConstructor 生成全参构造方法
@Builder 生成建造者模式代码,支持链式创建对象
@Slf4j 自动生成 SLF4J 日志对象(无需手动写 Logger log = LoggerFactory.getLogger(...)

下面是使用lombok的注解和不用的代码量的差别

public class User {
    private Long id;
    private String name;
    private Integer age;

    // 无参构造
    public User() {}

    // 全参构造
    public User(Long id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    // getter 方法
    public Long getId() {
        return id;
    }

    // setter 方法
    public void setId(Long id) {
        this.id = id;
    }

    // toString 方法
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

使用lombok

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data // 自动生成 getter/setter/toString/equals/hashCode 等
@NoArgsConstructor // 无参构造
@AllArgsConstructor // 全参构造
public class User {
    private Long id;
    private String name;
    private Integer age;
}

可以看到代码量是大幅度减少了

一、MyBatis 核心定义

MyBatis 是一款半自动的 ORM(对象关系映射)框架,也是 Java 生态中最主流的持久层框架之一。

  • “半自动”:区别于 JPA(全自动,几乎无 SQL),MyBatis 需要开发者手写 SQL,但框架会帮你完成「SQL 执行」和「结果集与 Java 对象的映射」,无需像原生 JDBC 那样手动处理ResultSetPreparedStatement等底层细节;
  • 核心定位:专注于解决 “SQL 与 Java 对象映射” 的问题,既保留了 SQL 的灵活性(开发者完全掌控 SQL),又简化了 JDBC 的繁琐代码。

二、MyBatis 核心优势

  1. 完全掌控 SQL:开发者可手写 SQL,便于优化(比如加索引、调整联查逻辑),适配复杂业务场景;
  2. 简化 JDBC 操作:自动管理数据库连接、参数绑定、结果映射,省去原生 JDBC 的 try-catch、手动赋值等冗余代码;
  3. 灵活的映射规则:支持 XML / 注解两种方式写 SQL,支持复杂结果映射(一对一、一对多联查);
  4. 低学习成本:只要懂 SQL 和 Java 基础,就能快速上手,比 JPA 更容易理解;
  5. 适配性强:兼容主流数据库(MySQL、Oracle、SQL Server 等),支持动态 SQL(根据条件拼接 SQL)。

2.2入门配置操作和全表查询

使用Mybatis查询用户所有数据

1.创建数据表,实体类,(表中字段名最好与java实体类中的字段名相一致)

2引入Mybatis的依赖,3编写SQL语句

MySQL Driver和MyBatis Framework 是核心的

如果前面没加可以在pom.xml中增加

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</artifactId>
        </dependency>
//mybitis
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>4.0.1</version>
        </dependency>
//mysql
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>4.0.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

IDE配置数据库链接

主机默认一般是localhost,用户一般是root,但是这里我用的不是本地的mysql 可能会有点区别

这里的URL 中jdbc:mysql是协议  localhost 是主机名  3306是端口,后面还可以配置是哪个数据库

向我这里所示

在application.properties中添加设置


spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.111.130:3306/springexercise
spring.datasource.username=root
spring.datasource.password=123456abc

创建数据表

添加数据

INSERT INTO `User` (name, man, phone, address)
VALUES
('张三', 1, '13800138000', '北京市海淀区中关村大街1号'),
('李四', 0, '13900139000', '上海市浦东新区张江高科技园区'),
('王五', 1, '13700137000', '广州市天河区珠江新城'),
('赵六', 0, '13600136000', '深圳市南山区科技园'),
('孙七', 1, '13500135000', '杭州市西湖区西溪湿地'),
('周八', 0, '13400134000', '南京市玄武区紫金山'),
('吴九', 1, '13300133000', '成都市高新区天府软件园'),
('郑十', 0, '13200132000', '武汉市洪山区光谷广场'),
('陈一', 1, '13100131000', '西安市雁塔区高新区'),
('杨二', 0, '13000130000', '重庆市渝中区解放碑'),
('刘三', 1, '15800158000', '苏州市工业园区金鸡湖'),
('黄四', 0, '15900159000', '宁波市鄞州区东部新城'),
('张五', 1, '15700157000', '青岛市市南区五四广场'),
('李六', 0, '15600156000', '大连市中山区星海广场'),
('王七', 1, '15500155000', '济南市历下区泉城路'),
('赵八', 0, '15400154000', '郑州市金水区CBD'),
('孙九', 1, '15300153000', '长沙市岳麓区岳麓山'),
('周十', 0, '15200152000', '合肥市蜀山区高新区'),
('吴一', 1, '15100151000', '南昌市红谷滩新区'),
('郑二', 0, '15000150000', '太原市小店区高新区'),
('陈三', 1, '18800188000', '昆明市五华区翠湖'),
('杨四', 0, '18900189000', '贵阳市观山湖区'),
('刘五', 1, '18700187000', '南宁市青秀区东盟商务区'),
('黄六', 0, '18600186000', '海口市龙华区国贸'),
('张七', 1, '18500185000', '兰州市城关区张掖路'),
('李八', 0, '18400184000', '西宁市城西区海湖新区'),
('王九', 1, '18300183000', '银川市金凤区阅海商务区'),
('赵十', 0, '18200182000', '呼和浩特市赛罕区如意开发区'),
('孙一', 1, '18100181000', '乌鲁木齐市新市区高新区'),
('周二', 0, '18000180000', '拉萨市城关区布达拉宫广场'),
('吴三', 1, '17800178000', '厦门市思明区鼓浪屿'),
('郑四', 0, '17900179000', '福州市鼓楼区三坊七巷'),
('陈五', 1, '17700177000', '南昌市东湖区滕王阁'),
('杨六', 0, '17600176000', '合肥市庐阳区逍遥津'),
('刘七', 1, '17500175000', '济南市市中区趵突泉'),
('黄八', 0, '17400174000', '郑州市二七区二七广场'),
('张九', 1, '17300173000', '长沙市芙蓉区五一广场'),
('李十', 0, '17200172000', '武汉市江汉区江汉路'),
('王一', 1, '17100171000', '成都市锦江区春熙路'),
('赵二', 0, '17000170000', '重庆市江北区观音桥'),
('孙三', 1, '16600166000', '西安市碑林区钟楼'),
('周四', 0, '16500165000', '南京市秦淮区夫子庙'),
('吴五', 1, '16400164000', '杭州市拱墅区武林广场'),
('郑六', 0, '16300163000', '广州市越秀区北京路'),
('陈七', 1, '16200162000', '深圳市福田区市民中心'),
('杨八', 0, '16100161000', '苏州市姑苏区平江路'),
('刘九', 1, '16000160000', '宁波市海曙区天一广场'),
('黄十', 0, '19900199000', '青岛市市北区台东步行街'),
('张一', 1, '19800198000', '大连市沙河口区星海广场'),
('李二', 0, '19700197000', '济南市槐荫区西客站');

构造实体类

package com.example.demo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    private Integer id;
    private String name;
    private Boolean man;
    private String phone;
    private String address;
    public User(String name,Boolean man,String phone,String address){
        this.name=name;
        this.man=man;
        this.phone=phone;
        this.address=address;
    }

}

userMapper

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import com.example.demo.model.User;
@Mapper
public interface userMapper {
    @Select("select * from User")
    List<User> list();
}

一般情况下@Select注解中的编译器的时候是不会识别错误的,但是在程序运行的时候会出现错误,我们可以选中SQL语句执行

来判断SQL语句写的是否正确

全表查询

package com.example.demo;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import com.example.demo.model.User;
import com.example.demo.Mapper.userMapper;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@RequestMapping("data")
@RestController
public class MyController {
    @Autowired
    userMapper user1;
    @GetMapping("")
    public ResponseEntity<?>f1(){

        List<User>userList=user1.list();

        return ResponseEntity.ok(userList);
    }
}

你会发现Mybatis对比JDBC 把一些硬编码放在了application.properties中,方便打包前随时换代码,同时,也不用很繁琐的去获取每一个字段,直接封装到一个list,同时使用了数据库连接池技术统一管理这些连接

2.3数据库连接池

数据库连接池是管理数据库连接的「复用容器」,解决原生 JDBC 频繁创建 / 销毁连接的性能损耗问题。它在程序启动时提前创建指定数量的数据库连接并缓存,程序需操作数据库时,直接从池中获取连接,用完后归还(而非销毁),实现连接复用。

核心优势:① 降低连接创建开销,提升系统性能;② 控制最大连接数,防止数据库过载;③ 统一管理连接,支持超时、空闲回收等策略。

如果没有数据库连接池,用户操作数据库要先创建一个连接对象,然后使用sql语句获取数据,然后关闭这个连接,每一次都是这样,这样资源就很浪费,无法做到复用

如果有数据库连接池,程序在启动时在容器当中初始化一定数量的数据库连接对象,客户端执行SQL语句从连接池中获取连接,获取数据后,会把这个连接对象归还给连接池,这样可以做到连接的复用,而不是每一次都创建一次心得连接

同时如果用户一直用连接对象,没有去访问操作数据库对象,这样连接池会去监测这个连接对象,一旦超过了数据库设置的时间,就会归还连接池

数据库连接池的标准接口DataSource  获取连接

Connection getConnection()throws SQLException

主流实现:HikariCP(Spring 默认,性能最优)、Druid(阿里开源,功能丰富)、C3P0。使用时只需配置连接池参数(URL、用户名、密码、最大 / 最小连接数等)

调用数据库之后会有个HikariCP的日志

使用druid依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>

2.4数据表的创建

CREATE TABLE employee (eid INT AUTO_INCREMENT PRIMARY KEY, pic VARCHAR(255),man BOOLEAN, pos_id INT NOT NULL , stime DATE,last_opt TIME, CONSTRAINT pos FOREIGN KEY (pos_id) REFERENCES pos(posId) ON DELETE CASCADE)CHARSET utf8mb4; 

插入数据

INSERT INTO employee (ename, pic, man, pos_id, stime, create_time, last_opt)
VALUES
('张三', '', FLOOR(RAND()*2), 1, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('李四', '', FLOOR(RAND()*2), 2, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('王五', '', FLOOR(RAND()*2), 3, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('赵六', '', FLOOR(RAND()*2), 4, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('钱七', '', FLOOR(RAND()*2), 5, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('孙八', '', FLOOR(RAND()*2), 6, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('周九', '', FLOOR(RAND()*2), 7, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('吴十', '', FLOOR(RAND()*2), 8, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('郑十一', '', FLOOR(RAND()*2), 9, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('冯十二', '', FLOOR(RAND()*2),10, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('陈十三', '', FLOOR(RAND()*2), 1, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('褚十四', '', FLOOR(RAND()*2), 2, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('卫十五', '', FLOOR(RAND()*2), 3, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('蒋十六', '', FLOOR(RAND()*2), 4, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('沈十七', '', FLOOR(RAND()*2), 5, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('韩十八', '', FLOOR(RAND()*2), 6, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('杨十九', '', FLOOR(RAND()*2), 7, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('朱二十', '', FLOOR(RAND()*2), 8, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('秦二十一', '', FLOOR(RAND()*2), 9, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('尤二十二', '', FLOOR(RAND()*2),10, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('许二十三', '', FLOOR(RAND()*2), 1, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('何二十四', '', FLOOR(RAND()*2), 2, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('吕二十五', '', FLOOR(RAND()*2), 3, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('施二十六', '', FLOOR(RAND()*2), 4, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('张二十七', '', FLOOR(RAND()*2), 5, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('李二十八', '', FLOOR(RAND()*2), 6, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('王二十九', '', FLOOR(RAND()*2), 7, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('赵三十', '', FLOOR(RAND()*2), 8, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('钱三十一', '', FLOOR(RAND()*2), 9, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('孙三十二', '', FLOOR(RAND()*2),10, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('周三十三', '', FLOOR(RAND()*2), 1, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('吴三十四', '', FLOOR(RAND()*2), 2, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('郑三十五', '', FLOOR(RAND()*2), 3, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('冯三十六', '', FLOOR(RAND()*2), 4, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('陈三十七', '', FLOOR(RAND()*2), 5, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('褚三十八', '', FLOOR(RAND()*2), 6, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('卫三十九', '', FLOOR(RAND()*2), 7, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('蒋四十', '', FLOOR(RAND()*2), 8, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('沈四十一', '', FLOOR(RAND()*2), 9, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('韩四十二', '', FLOOR(RAND()*2),10, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('杨四十三', '', FLOOR(RAND()*2), 1, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('朱四十四', '', FLOOR(RAND()*2), 2, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('秦四十五', '', FLOOR(RAND()*2), 3, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('尤四十六', '', FLOOR(RAND()*2), 4, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('许四十七', '', FLOOR(RAND()*2), 5, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('何四十八', '', FLOOR(RAND()*2), 6, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('吕四十九', '', FLOOR(RAND()*2), 7, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('施五十', '', FLOOR(RAND()*2), 8, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('林五十一', '', FLOOR(RAND()*2), 9, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW()),
('张五十二', '', FLOOR(RAND()*2),10, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*1095) DAY), NOW(), NOW());

java的实体类

package com.example.demo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class department {
    private Integer eid;
    private String pic;
    private String ename;
    private Boolean man;
    private Integer pos_id;
    //日期使用localdate
    private LocalDate stime;
    private LocalDate create_time;
    //时间localDateTime
    private LocalDateTime last_opt;
}

创建一个Mapper接口然后加上Mapper注解

package com.example.demo.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface deptMapper {

}

@Mapper是 MyBatis 提供的注解,标注在接口类上,作用是:

  1. 告诉 MyBatis 这是一个数据访问层(DAO)接口;
  2. 让 MyBatis 自动为该接口生成实现类(代理对象),无需手动编写实现类;
  3. 配合 SQL 注解(如@Select/@Insert)或 XML 映射文件,实现数据库操作。

2.5根据id删除数据

在employee表中根据id删除数据

delete from employee where id=50;

package com.example.demo.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface deptMapper {
    @Delete("delete from employee where eid=50")
    public void delete();
}

然而你会发现这个eid已经写死了,如果下次删eid是12 或者48 、49这种其他数字时就得要重新写了,因此我们要设置一个动态的

public interface deptMapper {
    @Delete("delete from employee where eid=#{eid}")
    public void delete(Integer eid);
}

在mybatis中 用#{变量}来做变量的一个占位符

在test下面进行测试:

package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.demo.model.department;
import com.example.demo.Mapper.deptMapper;
@SpringBootTest//单元测是
class Demo4ApplicationTests {
    @Autowired
    deptMapper maperDept;
    @Test
    public void test1() {
        Integer val = 50;
        maperDept.delete(val);
    }
}

也要注意,对数据进行删除操作是有返回值的,返回值是删除记录的数量,对于insert和update 也是影响操作数据的值

因此我们需要把原来的void 变成int

@Mapper
public interface deptMapper {
    @Delete("delete from employee where eid=#{eid}")
    public int delete(Integer eid);
}

2.5.2删除底层和SQL注入预编译SQL

我们可以在application.properties中,打开mybatis的日志,并指定输出到控制台

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

现在将eid为49 进行删除

    @Test
    public void test1() {
        Integer val = 49;
        maperDept.delete(val);
    }
JDBC Connection [HikariProxyConnection@1775247809 wrapping com.mysql.cj.jdbc.ConnectionImpl@69ab2d6a] will not be managed by Spring
==>  Preparing: delete from employee where eid=?
==> Parameters: 49(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@15371de2]

看到准备执行的SQL语句 delete from employee where eid=? (预编译SQL)

这个问号是个占位符,最终运行SQL语句的时候会在parameters来替换掉这个?

下面的update是更新的记录数为1 ,删除一条语句

对比普通 SQL(直接拼接参数),预编译的核心优势:

  • 防 SQL 注入:参数和 SQL 结构分离,数据库会自动处理参数的转义,避免恶意参数篡改 SQL 逻辑。
  • 提升性能:重复执行相同结构的 SQL 时,省去编译步骤,效率更高。

Java项目编写SQL语句, 然后连接Mysql 服务器,SQL语句发给Mysql服务器时,数据库并不是立即就执行的,需要经历     SQL语法解析检查->优化SQL->编译SQL->执行SQL

前面三个步骤会被缓存起来,下一次在进行执行SQL语句时会检查是否有编译好的SQL语句,如果有 直接执行SQL语句

SQL注入:

-- 假设后端拼接逻辑:"SELECT * FROM user WHERE username = '" + 用户名 + "' AND password = '" + 密码 + "'" SELECT * FROM user WHERE username = 'zhangsan' AND password = '123456';

这是非常危险的

(用户名输入密码:' OR 1=1 )

SELECT * FROM user WHERE username = '' " OR 1=1  ' AND password = '任意字符';

这样无论账号或者密码对不对,肯定都是有数据返回的,因为这样改变了查询的逻辑

#和$核心区别

特性 #{} (预编译占位符) ${} (字符串拼接)
底层实现 基于 JDBC PreparedStatement 直接字符串拼接
SQL 注入风险 安全(防注入) 危险(易注入)存在SQL注入问题
参数处理 自动加引号、转义特殊字符 直接拼接,无任何处理
适用场景 传入参数值(如条件、字段值) 传入 SQL 关键字 / 标识符(表名、列名)

2.6新增数据

新增数据,数据可能有很多种,为了方便,也为了简化代码,我们可以传一个对象进来,

    @Test
    public void test02Insert(){
        department d=new department();
        d.setEname("张明1");
        d.setStime(LocalDate.of(2023,11,23));
        d.setMan(true);
        d.setPic("");
        d.setPos_id(5);
        int ans=maperDept.insert(d);
        System.out.println("update:"+ans);
    }
package com.example.demo.Mapper;

import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Insert;

import java.util.List;
import com.example.demo.model.department;
@Mapper
public interface deptMapper {
    @Delete("delete from employee where eid=#{eid}")
    public int delete(Integer eid);
//插入
    @Insert("insert into employee(ename,man,pos_id,stime,pic) values(#{ename},#{man},#{pos_id},#{stime},#{pic})")
    public int insert(department d);
}

新增之后的主键返回

在某些需求中,在数据添加成功后,需要获取插入数据数据的主键,如:添加套餐数据时,还需要维护套餐菜品关系表数据

实现方法:Options注解

核心属性

1. useGeneratedKeys(最常用)

  • 作用:是否开启 “自动获取数据库生成的主键”(适配 MySQL 自增 ID、Oracle 序列等)。
  • 取值true/false(默认false)。

2. keyProperty(配合useGeneratedKeys使用)

  • 作用:指定数据库生成的主键要赋值到实体类的哪个属性上。
  • 取值:实体类的属性名(如idemployeeId)。
  • 注意:必须和useGeneratedKeys = true搭配,否则无效。

3. keyColumn(可选补充)

  • 作用:指定数据库表中主键字段的名称(当实体属性名和数据库字段名不一致时必填)。
  • 取值:数据库表的主键字段名(如emp_id)。
  • 示例:实体属性是id,数据库字段是emp_id
    @Options(keyProperty="eid",useGeneratedKeys=true)
    @Insert("insert into employee(ename,man,pos_id,stime,pic) values(#{ename},#{man},#{pos_id},#{stime},#{pic})")
    public int insert(department d);
    @Test
    public void test02Insert(){
        department d=new department();
        d.setEname("张明1");
        d.setStime(LocalDate.of(2023,11,23));
        d.setMan(true);
        d.setPic("");
        d.setPos_id(5);
        int ans=maperDept.insert(d);
        System.out.println("update:"+ans);
        System.out.println(d);
    }

2.7更新

在更新操作中,一般不会更新的字段是主键Id,因此一般是通过Id来对数据表的记录进行更新

    @Update("update employee set ename=#{ename}, man=#{man},pos_id=#{pos_id},pic=#{pic},last_opt=CURRENT_TIMESTAMP where eid=#{eid}")
    public int update(department d);
    @Test
    public void test03Update(){
        department d=new department();
        d.setEname("张一命");
        d.setMan(true);
        d.setPos_id(1);
        d.setPic("/load");
        d.setEid(12);
        int res=maperDept.update(d);
    }

2.8查询id单条查询

    @Select("select * from employee where eid=#{eid}")
    public department getId(int eid);
 @Test
    public void test04Select(){
        department d=maperDept.getId(15);
        System.out.println(d);
    }

注意点:

数据封装如果实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装,

如果实体类属性名和数据库表查询返回字段名不一致,不能自动封装

现在我单独创建个类,(可以看到没几个对的上)

import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept2 {
    private Integer e_id;
    private String p_ic;
    private String e_name;
    private Boolean man;
    private Integer pos_id;
    //日期使用localdate
    private LocalDate start_time;
    private LocalDate create_time;
    //时间localDateTime
    private LocalDateTime last_opt;
}
    @Select("select * from employee where eid=#{eid}")
    public Dept2 getId(int eid);
    @Test
    public void test04Select(){
        Dept2 d=maperDept.getId(15);
        System.out.println(d);
    }

终端信息

Dept2(e_id=null, p_ic=null, e_name=null, man=true, pos_id=3, start_time=null, create_time=2026-01-26, last_opt=2026-01-26T21:55:19)

解决方案

给查询的变量起一个别名

    @Select("select eid e_id,ename e_name,pic,man,stime start_time,pos_id,create_time,last_opt from employee where eid=#{eid}")
    public Dept2 getId(int eid);

第二种方法使用Results注解和Result注解

    @Select("select * from employee where eid=#{eid}")
    @Results({
            @Result(column="eid",property="e_id"),
            @Result(column="ename",property="e_name")
    })
    public Dept2 getId2(int eid);

@Results(复数)和@Result(单数)是配套使用的注解:

  • @Results:用于定义一组结果映射规则(对应 XML 的<resultMap>标签);
  • @Result:用于定义单个字段的映射规则(对应 XML 的<result>/<id>标签);
  • 核心价值:解决「数据库字段名」和「实体类属性名不一致」、「关联查询(一对一 / 一对多)」(之后)等场景的结果映射问题。

解决数据库字段名和实体属性名不一致的问题用到的属性

Result

参数名 作用 示例
id 是否是主键字段(true = 主键,false = 普通字段,默认 false) id = true
column 数据库表中的字段名 column = "emp_id"
property 实体类中的属性名 property = "eid"

Results

参数名 作用 示例
id 给当前结果映射规则命名,方便复用(对应 XML 的resultMap="xxx" id = "employeeMap"
value 包含多个@Result注解的数组,定义具体映射规则 value = {@Result(...), @Result(...)}

如果是驼峰命名法和下划线的关系可以开启mybatis中的驼峰和下划线命名转换

mybatis.configuration.map-underscore-to-camel-case=true

2.9过滤查询

对信息进行查询的要求

1:姓名采用模糊查询

2性别使用精准查询

3对查询时间对最后修改时间进行倒序排序

 @Select("select * from employee where ename like '%${ename}%' AND man=#{man} AND stime between #{start} AND #{end} order by last_opt DESC")
    public List<department> filter(String ename, Boolean man, LocalDate start, LocalDate end);

你会发现这里用到了$ ,因为模糊查询要在SQL语句中套个引号,这样mybatis就无法识别到

如果还是想用#传参额话可以用CONCAT函数

 @Select("select * from employee where ename like CONCAT('%',#{ename},'%') AND man=#{man} AND stime between #{start} AND #{end} order by last_opt DESC")
    @Results({
            @Result(column="create_time",property="create_time"),
            @Result(column="last_opt",property="last_opt")}

    )
    public List<department> filter(String ename, Boolean man, LocalDate start, LocalDate end);
    @Test
    public void test05Select(){
        LocalDate start=LocalDate.of(2018,11,2);
        LocalDate end=LocalDate.of(2026,4,12);
        List<department>ans=maperDept.filter("张",true,start,end);
        ans.stream().forEach(res->{
            System.out.println(res);
        });

    }

3Mybatis(XML)

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

3.1创建配置文件

XML映射文件规范

1XML映射文件的名称与Maple接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下同包同名(同时java文件放在java文件夹下 ,XML文件需要放在resource文件下中,要保证包的名称相同)

2 XML映射文件的namespace属性为member接口全限定名一致

3 XML映射文件中SQL语句的ID与min per接口中的方法名一致并且保持返回类型一致

一般时一个接口一个配置文件

表结构

创建实体类和mapper

实体类

package com.example.demo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class employee {
    private int id;
    private String pic;
    private String name;
    private boolean man;
    private int pos_id;
    private LocalDate start_time;
    private LocalDateTime create_time;
    private LocalDateTime last_opt;
}
package com.example.demo.mapper;
import com.example.demo.model.employee;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface employeeMapper {
    public List<employee>list();

}

再resource创建包要注意

没有package包选项

我们需要手动创建一级一级的文件夹

我这里的目录结构

创建目录不要用.分割   java的包对于目录来说是一级一级的

创建与mapper包下相同的Mapper

配置SQL语句的Mapper

<?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>
    
</mapper>

3.2写XML

配置SQL语句的根标签<Mapper></Mapper>

<?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.employeeMapper">
    <select id="list">select * from employee</select>
</mapper>

mapper标签的namespace 对应的是包名, select 标签对应查询语句, id是Mapper接口中对应的方法 resultType对应的是单条数据的返回值类型 如果是查询返回对象

推荐的插件下载

安装完之后会发现这里多了两只小鸟(点击这两只鸟可以相互跳转)

测试

package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.demo.model.employee;
import com.example.demo.mapper.employeeMapper;
import java.time.LocalDateTime;
import java.util.List;
import java.time.LocalDate;
@SpringBootTest//单元测是
class Demo4ApplicationTests {
    @Autowired
    employeeMapper employee;
    @Test
    public void All(){
        List<employee>employeeList=employee.list();
        employeeList.stream().forEach(res->{
            System.out.println(res);
        });
    }
}

连接成功!

但也存在问题 ,因为实体类的属性和表结构的属性没对上

3.3实体类与表中字段冲突解决

我们需要继续在XML中进行配置

resultMap是 MyBatis 的核心映射功能,用于自定义字段与实体类属性的映射关系,支持任意命名格式的映射,还能处理联表查询、一对一 / 一对多关联等复杂场景。

适用场景

  • 实体类属性与数据库字段命名无规律(无法用驼峰转换)
  • 复杂查询(联表、关联映射)
  • 希望统一管理映射关系,提升可维护性
<?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.employeeMapper">
    <resultMap type="com.example.demo.model.employee" id="res">
        <id column="eid" property="id"></id>
        <result column="ename" property="name"></result>
        <result column="stime" property="start_time"></result>
        <result column="create_time" property="create_time"></result>
        <result column="last_opt" property="last_opt"></result>
    </resultMap>
    <select id="list" resultMap="res">select * from employee</select>
</mapper>

resultMap的

type属性:指定要映射的目标实体类(Model/POJO)的全类名(包名 + 类名)。

作用:告诉 MyBatis,查询结果要封装到哪个实体类的对象中。

 id属性:当前<resultMap>在本 XML 文件内的唯一标识(自定义名称)。

  • 作用:供后续的 SQL 标签(如<select>)通过resultMap属性引用这个映射关系(后面的<select>就用了resultMap="res")。
  • 约束:只需保证「当前 XML 文件内唯一」,符合标识符命名规范即可

4. <resultMap> 内部子标签

<resultMap> 内部可以嵌套 <id><result> 两个标签,用于定义具体的字段 - 属性映射关系,两者属性完全一致,仅用途不同。

  • MyBatis 会对<id>映射的主键字段做特殊优化(比如缓存主键值、提升关联查询性能),普通非主键字段用<result>即可。
  • 核心属性(两个属性都是必填,且大小写敏感):
    1. column数据库表中的字段名(或 SQL 查询结果中的列名)。column="eid" 对应员工表中的主键字段eid
    2. property实体类(type指定的类)中的属性名property="id" 对应employee.java实体类中的id属性。
    3. 映射关系:把数据库字段eid的值,赋值给实体类的id属性(解决eidid命名不一致的问题)。

3.4增删改记录

删除操作

@Mapper
public interface employeeMapper {
    public List<employee>list();
    public int delete(int id);
}
<?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.employeeMapper">
    <resultMap type="com.example.demo.model.employee" id="res">
        <id column="eid" property="id"></id>
        <result column="ename" property="name"></result>
        <result column="stime" property="start_time"></result>
        <result column="create_time" property="create_time"></result>
        <result column="last_opt" property="last_opt"></result>
    </resultMap>
    <select id="list" resultMap="res">select * from employee</select>
    <!--下面是删除-->
    <delete id="delete"> delete from employee where eid =#{id}</delete>
</mapper>
    @Test
    public void delete(){
        int ans=employee.delete(51);
        System.out.println(ans);
    }

增加记录

在之前增加记录中我们可能会获取最新的id,于是我们导入了Options 注解声明了useGeneratedKeys=true 和keyColumn和keyProperty

我们也可以直接写到XML中

package com.example.demo.mapper;
import com.example.demo.model.employee;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.time.LocalDate;
@Mapper
public interface employeeMapper {
    public List<employee>list();
    public int delete(int id);
    
    public int insert(employee e);
}
 <insert id="insert"
            useGeneratedKeys="true"
            keyColumn="eid"
            keyProperty="id">insert into employee (ename,pic,man,pos_id,stime)
                             values(#{name},#{pic},#{man},#{pos_id},#{start_time})</insert>
    @Test
    public void insert(){
        employee e=new employee();
        e.setName("张明");
        e.setPic("");
        e.setPos_id(3);
        e.setMan(true);
        e.setStart_time(LocalDate.of(2024,12,23));
        employee1.insert(e);
        System.out.println(e);
    }

因为我剩下两个column在数据库在数据中设置了默认约束

更新数据

    <update id="update"> update employee set ename=#{name},pic=#{pic},
                                                man=#{man},pos_id=#{pos_id},
                                                last_opt=CURRENT_TIMESTAMP where eid=#{id}</update>

完整Mapper

package com.example.demo.mapper;
import com.example.demo.model.employee;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.time.LocalDate;
@Mapper
public interface employeeMapper {
    public List<employee>list();
    public int delete(int id);
    public int insert(employee e);
    public int update(employee e);
}

完整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.employeeMapper">
    <resultMap type="com.example.demo.model.employee" id="res">
        <id column="eid" property="id"></id>
        <result column="ename" property="name"></result>
        <result column="stime" property="start_time"></result>
        <result column="create_time" property="create_time"></result>
        <result column="last_opt" property="last_opt"></result>
    </resultMap>
    <select id="list" resultMap="res">select * from employee</select>
    <!--下面是删除-->
    <delete id="delete"> delete from employee where eid =#{id}</delete>
    <!--下面是插入-->
    <insert id="insert"
            useGeneratedKeys="true"
            keyColumn="eid"
            keyProperty="id">insert into employee (ename,pic,man,pos_id,stime)
                             values(#{name},#{pic},#{man},#{pos_id},#{start_time})</insert>
    
<update id="update"> update employee set ename=#{name},pic=#{pic},
                                                man=#{man},pos_id=#{pos_id},
                                                last_opt=CURRENT_TIMESTAMP where eid=#{id}</update>
</mapper>
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.demo.model.employee;
import com.example.demo.mapper.employeeMapper;
import java.time.LocalDateTime;
import java.util.List;
import java.time.LocalDate;
@SpringBootTest//单元测是
class Demo4ApplicationTests {
    @Autowired
    employeeMapper employee1;
    @Test
    public void All(){
        List<employee>employeeList=employee1.list();
        employeeList.stream().forEach(res->{
            System.out.println(res);
        });
    }
    @Test
    public void delete(){
        int ans=employee1.delete(51);
        System.out.println(ans);
    }
    @Test
    public void insert(){
        employee e=new employee();
        e.setName("张明");
        e.setPic("");
        e.setPos_id(3);
        e.setMan(true);
        e.setStart_time(LocalDate.of(2024,12,23));
        employee1.insert(e);
        System.out.println(e);
    }
    @Test
    public void update(){
        employee e=new employee(53,"","小强",true,2,LocalDate.of(2023,1,2),null,null);
        int ans=employee1.update(e);
        System.out.println(ans);

    }
}

过滤查询

 <resultMap id="filterName" type="com.example.demo.model.employee">
        <id column="eid" property="id"></id>
        <result column="ename" property="name"></result>
        <result column="stime" property="start_time"></result>
        <result column="last_opt" property="last_opt"></result>
        <result column="create_time" property="create_time"></result>
    </resultMap>
    <select id="filter" resultMap="filterName"> 
select * from employee where ename like CONCAT("%",#{name},"%")
    </select>
    public List<employee>filter(String name);
    @Test
    public void filterName(){
        List<employee>em=employee1.filter("张");
        em.stream().forEach(res->{
            System.out.println(res);
        });
    }

4关联模型

4.1一对一关联

一对一关联(One-to-One)是实体之间最基础的关联关系之一,指两个实体之间是「一一对应」的关系:其中一个实体的一条记录,只能对应另一个实体的一条记录,反之亦然。

可以用通俗的比喻理解:

  • 一个用户(User)对应一个用户详情(UserDetail)(用户基本信息存在 User,详细信息如地址、手机号存在 UserDetail,避免单表字段过多)
  • 一个订单(Order)对应一个物流单(OrderLogistics)(一个订单只有一条专属物流记录)
  • 一个学生(Student)对应一个学生证(StudentCard)(一个学生只有一个学生证,一个学生证只属于一个学生)

数据库中没有直接的「一对一」约束关键字,通常通过「外键唯一约束」或「共享主键」实现,其中「外键唯一约束」更规范、更常用。

我这里用学生信息和学生成绩一对一关联

下面是学生和学生成绩表的一对一关联

添加数据后 进行一对一关联查询

SELECT * FROM stu  LEFT JOIN score ON stu.sid=score.name_id;

这次查询中我们可以去掉score_id和name_id这两个,因为这两个是用来关联的,

SELECT  t.sid,t.sname,t.man,c.Chinese,c.Math,c.English
	FROM stu t LEFT JOIN score c 
	ON t.sid=c.name_id;

实体类:

student(很明显可以看到我的实体类与数据库的字段名不同)

package com.example.demo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private boolean man;
    private score score1;
}

score

package com.example.demo.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Score {
        private Integer score_id;
        private Short Chinese;
        private Short Math;
        private Short English;
}

定义Mapper

package com.example.demo.mapper;
import java.util.List;
import com.example.demo.model.Student;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface stuMapper {
    public List<Student>list();
}

写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.stuMapper">
    <select id="list" resultType="com.example.demo.model.student">
        select * from stu left join score on stu.sid=score.name_id
    </select>
</mapper>

在resultMap处理 字段和实体类属性冲突

    <resultMap id="show" type="com.example.demo.model.student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
    </resultMap>

<association> 标签,核心结论先明确:<association> 是 MyBatis 专门用于处理「一对一关联映射」的核心标签,它的作用是将关联表(从表)的查询结果,映射到主表实体类中对应的「单个关联对象属性」上

<association> 标签的常用属性不多,核心属性仅 5 个,

属性名 核心作用 是否必填 适用场景
property 对应主表实体类中的 所有一对一场景
javaType 对应关联对象的「全类名」 否(可省略) 手动指定关联实体类型,MyBatis 可自动推断
resultMap 引用已定义好的「从表 resultMap」,用于映射从表字段到关联对象 嵌套结果(联合查询)场景
select 指定「分步查询的从表 Mapper 方法全路径」(比如 com.example.mapper.UserDetailMapper.getDetailByUserId 嵌套查询(分步查询)场景
column 传递给 select 方法的「参数字段」(比如主表的 id,对应从表的 user_id 嵌套查询(分步查询)场景

用association标签进行一对一(内部和原来ResultMap一样的)

    <resultMap id="show" type="com.example.demo.model.Student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
        <association property="score1" javaType="com.example.demo.model.Score">
            <id column="score_id" property="score_id"></id>
            <result column="Chinese" property="Chinese"></result>
            <result column="Math" property="Math"></result>
            <result column="English" property="English"></result>
        </association>
    </resultMap>

完整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.stuMapper">
    <resultMap id="show" type="com.example.demo.model.Student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
        <association property="score1" javaType="com.example.demo.model.Score">
            <id column="score_id" property="score_id"></id>
            <result column="Chinese" property="Chinese"></result>
            <result column="Math" property="Math"></result>
            <result column="English" property="English"></result>
        </association>
    </resultMap>
    <select id="list" resultType="com.example.demo.model.Student" resultMap="show">
        select * from stu left join score on stu.sid=score.name_id
    </select>
</mapper>

4.2一对一双向关联

4.1的一对一关联中我们只能通过用户表来获取对应的三个成绩,正向关联,但是无法通过成绩表获取用户,

现在我们需要成绩和学生双向关联,根据学生可以知道成绩,也可以根据成绩知道学生

package com.example.demo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor

public class Student {
    private Integer id;
    private String name;
    private boolean man;
    private Score score;
}
package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Score {
        private Integer scoreId;
        private Short Chinese;
        private Short Math;
        private Short English;
        private Student student;
        private Integer studentId;
}

观看这两个模型 大概可以思考到一个问题,就是双向关联后toString循环打印会导致栈溢出问题

解决方案:@toString注解的of属性或者exclude属性

  • of 是「选我要的」,适合少数字段需要展示的场景,简化输出结果。
  • exclude 是「弃我不要的」,适合少数字段无需展示的场景,尤其核心用于解决双向关联的循环调用问题。

使用时注意

互斥性ofexclude 不能同时在一个 @ToString 注解中使用,两者是二选一的关系,同时使用会导致编译报错。

@Data 配合@Data 已包含默认的 @ToString(无参数,包含所有字段),若需要使用 ofexclude,只需单独添加 @ToString(of/exclude = ...) 即可覆盖 @Data 的默认配置。

多字段顺序ofexclude 中多个字段的书写顺序,不影响 toString() 中字段的输出顺序(toString() 中字段顺序与实体类中字段定义顺序一致)

package com.example.demo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(of={"id","name","man"})
public class Student {
    private Integer id;
    private String name;
    private boolean man;
    private Score score;
}
package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude="student")
public class Score {
        private Integer scoreId;
        private Short Chinese;
        private Short Math;
        private Short English;
        private Student student;
        private Integer studentId;
}

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.stuMapper">
    <resultMap id="show" type="com.example.demo.model.Student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
        <association property="score" javaType="com.example.demo.model.Score">
            <id column="score_id" property="scoreId"></id>
            <result column="Chinese" property="Chinese"></result>
            <result column="Math" property="Math"></result>
            <result column="English" property="English"></result>
            <result column="name_id" property="studentId"></result>
        </association>
    </resultMap>
    <select id="list" resultType="com.example.demo.model.Student" resultMap="show">
        select * from stu left join score on stu.sid=score.name_id
    </select>
</mapper>

成绩连接学生

<?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.scoreMapper">
    <resultMap id="showScore" type="com.example.demo.model.Score">
        <id column="score_id" property="scoreId"></id>
        <result column="Chinese" property="Chinese"></result>
        <result column="Math" property="Math"></result>
        <result column="English" property="English"></result>
        <association property="student" javaType="com.example.demo.model.Student">
            <id column="sid" property="id"></id>
            <result column="sname" property="name"></result>
            <result column="man" property="man"></result>
        </association>
    </resultMap>
    <select id="list" resultType="com.example.demo.model.Score" resultMap="showScore">
        select * from score left join stu on score.name_id=stu.sid
    </select>

</mapper>

4.3一对多关联

在数据库和业务系统设计中,一对多关联模型是最常见的关系类型之一,指的是一个主表(父实体)的一条记录,可以对应子表(子实体)的多条记录,但子表的一条记录只能对应主表的一条记录。

  • 父表(主实体):作为 “一” 的一方,包含唯一标识(如 user_id)。
  • 子表(子实体):作为 “多” 的一方,通过外键(如 user_id)关联到父表的主键。
  • 关联规则:父表的一条记录可以被子表的多条记录引用,但子表的一条记录只能引用父表的一条记录。
  1. 用户与订单
    • 一个用户(父)可以创建多个订单(子)。
    • 每个订单(子)只能属于一个用户(父)。
  2. 课程与学生
    • 一门课程(父)可以有多个学生(子)选修。
    • 每个学生(子)在该课程中只有一条选课记录。

下面的例子用学生和班级来展示一对多关联

建表

CREATE TABLE class(id INT AUTO_INCREMENT PRIMARY KEY, enter_year INT, number TINYINT(5));
CREATE TABLE student(sid INT AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(20), man TINYINT(1), class_id INT(5), CONSTRAINT stuToClass FOREIGN KEY(class_id) REFERENCES class(`id`) ON DELETE CASCADE ON UPDATE CASCADE); 

随便添加50条数据后

连表查询

SELECT * FROM student s LEFT JOIN class c ON s.class_id=c.`id`;

实体类

package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class Stu {
    private Integer id;
    private String sname;
    private Boolean man;
}
package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@AllArgsConstructor
@Data
@NoArgsConstructor
public class Class1 {
    private Integer id;
    private Integer enter_year;
    private Short number;
    private List<Stu>student;
}

声明接口

package com.example.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.model.Class1;
import java.util.List;

@Mapper
public interface class1Mapper {
    public List<Class1> list();
}

在一对多中 不是用association 而是用<collection></collection>

collection

  • 作用:将查询结果集中,属于同一个主对象的多个子对象数据,封装成集合(ListSet 等),赋值给主对象的对应属性。
  • 常用场景:一对多关联映射(多对多也可通过中间表转化为两次一对多实现)。
  • 核心属性(与 <association>(一对一)类似,重点差异在集合相关配置):

心属性(与 <association>(一对一)类似,重点差异在集合相关配置):

属性 说明
property 主对象中集合属性的名称(必填,比如 Order 中的 List<OrderItem> orderItems
ofType 集合中元素的全限定类名(必填,区别于 <association>javaType,比如 com.example.entity.OrderItem
column 主表与子表关联的字段(主表的字段,用于传递参数给子查询)
select 子查询的全限定 ID(用于分步查询,即先查主表,再根据关联字段查子表)
resultMap 子对象的结果映射集 ID(用于嵌套结果映射,即一次 SQL 查询出所有数据,再通过 MyBatis 封装)
javaType 集合的类型(可选,MyBatis 可自动推断,比如 ArrayList
<?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.class1Mapper">
    <resultMap id="showStudent" type="com.example.demo.model.Class1">
        <id column="id" property="id"></id>
        <result column="enter_year" property="enter_year"></result>
        <result column="number" property="number"></result>

<!----这里是ofType不是javaType---->

        <collection property="student" ofType="com.example.demo.model.Stu">
            <id column="sid" property="id"></id>
            <result column="name" property="sname"></result>
            <result column="man" property="man"></result>
        </collection>
    </resultMap>
    <select id="list"
            resultMap="showStudent"
    >
        select * from spring_exercise.class c
            left JOIN spring_exercise.student s
            on s.class_id=c.id;
    </select>
</mapper>

测试

    @Test
    public void test(){
        cls1Mapper.list().stream().forEach(res->{
            System.out.println(res);
            System.out.println(res.getStudent().size());
        });
    }

4.4多对一

班级对应学生是一对多,反过来,学生对应班级就是多对一

实体类

package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@Data
@NoArgsConstructor
public class Stu {
    private Integer id;
    private String sname;
    private Boolean man;
    private Class1 class1;
}

因为对应一条数据 XML中我们进行关联时用的时<association></assiciation>

学生类

package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@AllArgsConstructor
@Data
@NoArgsConstructor
@ToString(exclude="class1")
public class Stu {
    private Integer id;
    private String sname;
    private Boolean man;
    private Class1 class1;
}
<?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.studentMapper">
    <resultMap id="showStudent" type="com.example.demo.model.Stu">
        <id property="id" column="sid"></id>
        <result property="sname" column="name"></result>
        <result property="man" column="man"></result>
<!---对应结果一条数据用association 同时用Javatype--->
        <association property="class1" javaType="com.example.demo.model.Class1">
            <id property="id" column="id"></id>
            <result property="enter_year" column="enter_year"></result>
            <result property="number" column="number"></result>
        </association>
    </resultMap>
    <select id="list" resultMap="showStudent">
        select * from spring_exercise.student s left join spring_exercise.class c
        on s.class_id=c.id
    </select>
</mapper>

测试

 @Autowired
    class1Mapper cls1Mapper;
    @Autowired
    studentMapper stuMapper;
    @Test
    public void test(){
        cls1Mapper.list().stream().forEach(res->{
            System.out.println(res);
            System.out.println(res.getStudent().size());
        });
    }
    @Test
    public void test2(){
        stuMapper.list().stream().forEach(res->{
            System.out.println(res);
            System.out.println(res.getClass1());
        });

    }

5动态SQL

随着用户的输入或外部条件的变化而变化的SQL语句并,称为动态SQL

没有动态SQL的语句

这个Update 标签进行更新的时候,我们已经写死了,这就意味着我们在更新时,必须把字段写全,同时还会覆盖原来所有数据

但是在有些情况下,我们不希望写死,而是有选择的去使用,同时有一些数据我们并不是想写

5.1if标签

之前的employee表

package com.example.demo.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class employee {
    private int id;
    private String pic;
    private String name;
    private boolean man;
    private int pos_id;
    private LocalDate start_time;
    private LocalDateTime create_time;
    private LocalDateTime last_opt;
}

现在我需要更新数据

但时我不想全部更新,我只想更新我输入的,这里就可以用if标签来进行筛选

package com.example.demo.mapper;
import com.example.demo.model.employee;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.time.LocalDate;
@Mapper
public interface employeeMapper {
    public List<employee>list();
    public int delete(int id);
    public int insert(employee e);
    public int update(employee e);//注意这个就可以
    public List<employee>filter(String name);
}

XML中根据update部分

为什么pos_id时0,因为integer默认时0

 <update id="update">
            update employee
        <set>
            <if test="name!=null">
                ename=#{name},
            </if>
            <if test="pic!=null">
                  pic=#{pic},
            </if>
            <if test="pos_id!=0">
                pos_id=#{pos_id},
            </if>
            <if test="start_time!=null">
                stime=#{start_time},
            </if>
            last_opt=CURRENT_TIMESTAMP()
        </set>
        where eid=#{id}

    </update>

set标签<update> 语句使用的动态 SQL 标签,核心作用是优雅地处理更新操作中的字段拼接,自动解决多余逗号和 SET 关键字的问题,避免手动拼接 SQL 出现语法错误。

与set标签差不多的还有一个where标签

  • 自动添加 WHERE 关键字:只有当 <where> 标签内部包含至少一个有效条件(即动态判断为 true 的 SQL 片段)时,MyBatis 才会在最终生成的 SQL 中添加 WHERE 关键字。
  • 自动剔除多余的开头 AND/OR:如果内部的条件片段以 ANDOR 开头,<where> 标签会自动剔除这些多余的逻辑运算符,避免出现 WHERE AND xxxWHERE OR xxx 这样的无效 SQL。

注意:<where> 标签只处理开头的 AND/OR,不处理结尾的 AND/OR,所以编写条件时建议将 AND/OR 放在条件片段的开头,而非结尾。

例子2:

    public List<employee>filter(employee e);
<resultMap id="res1" type="com.example.demo.model.employee">
        <result column="ename" property="name"></result>
        <result column="eid" property="id"></result>
        <result column="stime" property="start_time"></result>
        <result column="create_time" property="create_time"></result>
        <result column="last_opt" property="last_opt"></result>
    </resultMap>
    <select id="filter" resultMap="res1">
        select * from employee
        <where>
            <if test="pic!=null">
                pic=#{pic}
            </if>
            <if test="name!=null">
               and ename like concat("%",#{name},"%")
            </if>
            <if test="pos_id!=0">
               and pos_id=#{pos_id}
            </if>
            <if test="start_time!=null">
              and  stime=#{start_time}
            </if>
        </where>
    </select>
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.demo.model.employee;
import com.example.demo.mapper.employeeMapper;
import java.time.LocalDateTime;
import java.util.List;
import java.time.LocalDate;
import com.example.demo.mapper.class1Mapper;
import com.example.demo.mapper.studentMapper;
@SpringBootTest//单元测是
class Demo4ApplicationTests {
    @Autowired
    employeeMapper empMapper;
    @Test
    public void test(){
        employee e=new employee();
        e.setName("张");
        empMapper.filter(e).stream().forEach(res->{
           System.out.println(res);
        });
    }
}

5.2forEach

<forEach> 标签,它是 MyBatis 动态 SQL 中专门用于遍历集合 / 数组类型参数的标签,核心用途是实现批量操作(比如批量查询、批量插入、批量删除 / 更新),避免手动循环拼接 SQL 带来的繁琐和语法错误。

属性名 是否必选 核心作用
collection 指定要遍历的集合 / 数组参数名(新手最易踩坑,后续详细说明取值规则)
item 遍历过程中,给当前元素起的别名,标签内部用 #{item别名} 获取当前元素的值
index 遍历的索引 / 下标:集合 / 数组返回数字下标(从 0 开始),Map 返回 key 值
open 遍历结果拼接的前缀字符串(比如 (
close 遍历结果拼接的后缀字符串(比如 )
separator 遍历元素之间的分隔符(比如 ,,用于分隔多个元素)

下面是个批量删除的例子

    int deleteIds(List<Integer> arr);
    <delete id="deleteIds">
        delete from employee where eid in
        <foreach item="id" collection="arr" separator="," open="(" close=")">
            #{id}
        </foreach>

    </delete>
    @Test
    public void test2(){
        List<Integer> a=new ArrayList<>();
        a.add(5);
        a.add(8);
        a.add(9);
        a.add(12);
        empMapper.deleteIds(a);
    }

5.3sql标签和include标签

<sql><include> 标签,这两个标签是搭配使用的复用型标签,核心作用是提取 SQL 语句中的重复片段(比如字段列表、查询条件)进行统一管理,减少 XML 配置的冗余代码,同时提升后续的维护效率(比如修改字段时只需改一处,无需多处修改)。

1. <sql> 标签:定义可复用的 SQL 片段

  • 用于抽取重复出现的 SQL 内容,生成一个可被引用的 “公共片段”。
  • 必须指定 id 属性(唯一标识该片段),供 <include> 标签引用。
  • 可抽取的内容:查询字段列表、表名、WHERE 条件、ORDER BY 子句等。

2. <include> 标签:引用已定义的 SQL 片段

  • 用于在需要的 SQL 语句中,插入 <sql> 标签定义的公共片段
  • 必须指定 refid 属性,值为 <sql> 标签的 id(实现精准引用)。
  • 支持跨命名空间引用(格式:命名空间全路径.sql片段id)。

下面是两个全表查询的命令,会发现有很多重写的逻辑,如果resultMapd

 <resultMap type="com.example.demo.model.employee" id="res">
        <id column="eid" property="id"></id>
        <result column="ename" property="name"></result>
        <result column="stime" property="start_time"></result>
        <result column="create_time" property="create_time"></result>
        <result column="last_opt" property="last_opt"></result>
    </resultMap>
    <select id="list" resultMap="res">select * from employee</select>
    <!--下面是删除-->
  
    <resultMap id="res1" type="com.example.demo.model.employee">
        <result column="ename" property="name"></result>
        <result column="eid" property="id"></result>
        <result column="stime" property="start_time"></result>
        <result column="create_time" property="create_time"></result>
        <result column="last_opt" property="last_opt"></result>
    </resultMap>
    <select id="filter" resultMap="res1">
        select * from employee
        <where>
            <if test="pic!=null">
                pic=#{pic}
            </if>
            <if test="name!=null">
               and ename like concat("%",#{name},"%")
            </if>
            <if test="pos_id!=0">
               and pos_id=#{pos_id}
            </if>
            <if test="start_time!=null">
              and  stime=#{start_time}
            </if>
        </where>
    </select>

第一种简化就是ResultMap 变成一个

第二种(我这里用了别名的方式) 另外几个字段因为下划线问题必须用resultMap

<sql id="select1">
        select eid id,pic ,ename name,man from employee
    </sql>
    <select id="list" resultType="com.example.demo.model.employee">
        <include refid="select1"></include>
    </select>
    <select id="filter" resultType="com.example.demo.model.employee">
        <include refid="select1"></include>
        <where>
            <if test="pic!=null">
                pic=#{pic}
            </if>
            <if test="name!=null">
               and ename like concat("%",#{name},"%")
            </if>
            <if test="pos_id!=0">
               and pos_id=#{pos_id}
            </if>
            <if test="start_time!=null">
              and  stime=#{start_time}
            </if>
        </where>
    </select>

Logo

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

更多推荐