Skip to content

阶段二:数据库与持久层(2周) ​

数据库是后端开发的核心基础。本阶段将学习MySQL数据库、JDBC以及主流ORM框架MyBatis。


2.1 数据库基础(第1-4天) ​

2.1.1 MySQL安装与配置 ​

下载安装 ​

  1. 下载MySQL:MySQL官网
  2. 选择Windows版本(MSI安装器推荐)
  3. 安装时设置root密码
  4. 配置环境变量:Path 添加 C:\Program Files\MySQL\MySQL Server 8.0\bin

常用GUI工具 ​

  • Navicat(付费,功能强大)
  • DBeaver(免费开源,推荐)
  • MySQL Workbench(官方免费)
  • DataGrip(JetBrains出品,付费)

验证安装 ​

bash
mysql -u root -p

2.1.2 MySQL基础操作 ​

数据库操作 ​

sql
-- 查看所有数据库
SHOW DATABASES;

-- 创建数据库
CREATE DATABASE test_db;

-- 使用数据库
USE test_db;

-- 删除数据库
DROP DATABASE test_db;

-- 查看当前数据库
SELECT DATABASE();

数据类型 ​

数值类型:

类型大小范围说明
TINYINT1字节-128~127小整数
SMALLINT2字节-32768~32767短整数
INT4字节-2^31~2^31-1整数(最常用)
BIGINT8字节-2^63~2^63-1长整数
FLOAT4字节单精度浮点数
DOUBLE8字节双精度浮点数(最常用)
DECIMAL(M,D)可变精确精确数值(金额用)

字符串类型:

类型大小说明
CHAR(n)n字符定长字符串
VARCHAR(n)n字符变长字符串(最常用)
TEXT64KB长文本
LONGTEXT4GB超长文本

日期时间类型:

类型格式说明
DATEYYYY-MM-DD日期
TIMEHH:MM:SS时间
DATETIMEYYYY-MM-DD HH:MM:SS日期时间(最常用)
TIMESTAMPYYYY-MM-DD HH:MM:SS时间戳(自动更新)
YEARYYYY年份

2.1.3 表操作 ​

创建表 ​

sql
-- 创建用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
    password VARCHAR(100) NOT NULL COMMENT '密码',
    email VARCHAR(100) COMMENT '邮箱',
    age INT COMMENT '年龄',
    status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 创建订单表
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    order_no VARCHAR(50) NOT NULL UNIQUE COMMENT '订单号',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    amount DECIMAL(10,2) NOT NULL COMMENT '订单金额',
    status TINYINT DEFAULT 0 COMMENT '状态:0-待支付,1-已支付,2-已取消',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

修改表 ​

sql
-- 查看表结构
DESC users;
SHOW CREATE TABLE users;

-- 添加字段
ALTER TABLE users ADD COLUMN phone VARCHAR(20) COMMENT '手机号';

-- 修改字段
ALTER TABLE users MODIFY COLUMN email VARCHAR(150) COMMENT '邮箱';

-- 修改字段名
ALTER TABLE users CHANGE COLUMN phone mobile VARCHAR(20) COMMENT '手机号';

-- 删除字段
ALTER TABLE users DROP COLUMN mobile;

-- 修改表名
ALTER TABLE users RENAME TO user_info;

-- 删除表
DROP TABLE users;

2.1.4 CRUD操作 ​

插入数据(INSERT) ​

sql
-- 插入单条
INSERT INTO users (username, password, email, age) 
VALUES ('zhangsan', '123456', 'zhangsan@example.com', 25);

-- 插入多条
INSERT INTO users (username, password, email, age) VALUES
('lisi', '123456', 'lisi@example.com', 28),
('wangwu', '123456', 'wangwu@example.com', 30),
('zhaoliu', '123456', 'zhaoliu@example.com', 22);

查询数据(SELECT) ​

sql
-- 查询所有
SELECT * FROM users;

-- 查询指定字段
SELECT id, username, email FROM users;

-- 条件查询
SELECT * FROM users WHERE age > 25;
SELECT * FROM users WHERE age BETWEEN 20 AND 30;
SELECT * FROM users WHERE age IN (25, 28, 30);
SELECT * FROM users WHERE username LIKE 'zhang%';
SELECT * FROM users WHERE email IS NULL;
SELECT * FROM users WHERE email IS NOT NULL;

-- 排序
SELECT * FROM users ORDER BY age ASC;
SELECT * FROM users ORDER BY age DESC;
SELECT * FROM users ORDER BY age DESC, id ASC;

-- 分页
SELECT * FROM users LIMIT 10;           -- 前10条
SELECT * FROM users LIMIT 0, 10;        -- 从0开始取10条
SELECT * FROM users LIMIT 10 OFFSET 20; -- 跳过20条取10条

-- 聚合函数
SELECT COUNT(*) FROM users;                    -- 总数
SELECT COUNT(DISTINCT age) FROM users;         -- 去重计数
SELECT SUM(amount) FROM orders;                -- 求和
SELECT AVG(age) FROM users;                    -- 平均
SELECT MAX(age) FROM users;                    -- 最大
SELECT MIN(age) FROM users;                    -- 最小

-- 分组查询
SELECT age, COUNT(*) as count FROM users GROUP BY age;
SELECT age, COUNT(*) as count FROM users GROUP BY age HAVING count > 1;

-- 别名
SELECT username AS name, email AS mail FROM users;
SELECT u.username, o.order_no FROM users u, orders o WHERE u.id = o.user_id;

更新数据(UPDATE) ​

sql
-- 更新单条
UPDATE users SET age = 26 WHERE id = 1;

-- 更新多条
UPDATE users SET age = age + 1 WHERE age < 30;

-- 更新所有(慎用)
UPDATE users SET status = 1;

删除数据(DELETE) ​

sql
-- 删除单条
DELETE FROM users WHERE id = 1;

-- 删除多条
DELETE FROM users WHERE age < 18;

-- 清空表(保留表结构)
TRUNCATE TABLE users;

2.1.5 高级查询 ​

多表连接 ​

sql
-- 内连接(只返回匹配的记录)
SELECT u.username, o.order_no, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- 左连接(返回左表所有记录)
SELECT u.username, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

-- 右连接(返回右表所有记录)
SELECT u.username, o.order_no, o.amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

-- 自连接
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

子查询 ​

sql
-- WHERE子查询
SELECT * FROM users 
WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);

-- FROM子查询
SELECT * FROM (
    SELECT user_id, SUM(amount) as total 
    FROM orders 
    GROUP BY user_id
) t WHERE t.total > 1000;

-- EXISTS子查询
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

联合查询 ​

sql
-- UNION(去重)
SELECT username FROM users
UNION
SELECT order_no FROM orders;

-- UNION ALL(不去重)
SELECT username FROM users
UNION ALL
SELECT order_no FROM orders;

2.1.6 索引 ​

索引类型 ​

  • 主键索引(PRIMARY KEY):唯一标识,不能为空
  • 唯一索引(UNIQUE):值唯一,可以为空
  • 普通索引(INDEX):最基本的索引
  • 全文索引(FULLTEXT):用于全文检索

创建索引 ​

sql
-- 创建表时指定索引
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50) UNIQUE,
    email VARCHAR(100),
    INDEX idx_email (email)
);

-- 添加索引
ALTER TABLE users ADD INDEX idx_age (age);
CREATE INDEX idx_created_at ON users (created_at);

-- 添加唯一索引
ALTER TABLE users ADD UNIQUE idx_username (username);

-- 删除索引
DROP INDEX idx_age ON users;

索引使用原则 ​

  1. 频繁作为查询条件的字段建索引
  2. 数据量大的表建索引
  3. 区分度高的字段建索引
  4. 避免过多索引(一般不超过5个)
  5. 更新频繁的字段谨慎建索引

2.1.7 事务 ​

事务特性(ACID) ​

  • 原子性(Atomicity):事务是最小执行单位,要么全部成功,要么全部失败
  • 一致性(Consistency):事务执行前后,数据库状态保持一致
  • 隔离性(Isolation):并发事务之间互不干扰
  • 持久性(Durability):事务提交后,数据永久保存

事务操作/ ​

sql
-- 开启事务
START TRANSACTION;
-- 或
BEGIN;

-- 提交事务
COMMIT;

-- 回滚事务
ROLLBACK;

-- 示例
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
UPDATE users SET balance = balance + 100 WHERE id = 2;
COMMIT;

隔离级别 ​

隔离级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED不会可能可能
REPEATABLE READ(默认)不会不会可能
SERIALIZABLE不会不会不会
sql
-- 查看隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

2.1.8 视图 ​

sql
-- 创建视图
CREATE VIEW user_orders AS
SELECT u.username, o.order_no, o.amount, o.created_at
FROM users u
JOIN orders o ON u.id = o.user_id;

-- 使用视图
SELECT * FROM user_orders WHERE amount > 1000;

-- 删除视图
DROP VIEW user_orders;

2.1.9 存储过程 ​

sql
-- 创建存储过程
DELIMITER //
CREATE PROCEDURE GetUserOrders(IN userId BIGINT)
BEGIN
    SELECT * FROM orders WHERE user_id = userId;
END //
DELIMITER ;

-- 调用存储过程
CALL GetUserOrders(1);

-- 删除存储过程
DROP PROCEDURE GetUserOrders;

2.2 JDBC(第5-6天) ​

2.2.1 JDBC概述 ​

JDBC(Java Database Connectivity)是Java连接数据库的标准API。

2.2.2 JDBC核心步骤 ​

java
import java.sql.*;

public class JdbcDemo {
    public static void main(String[] args) {
        // 数据库连接信息
        String url = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8";
        String user = "root";
        String password = "123456";
        
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            // 1. 加载驱动(JDBC 4.0+可省略)
            Class.forName("com.mysql.cj.jdbc.Driver");
            
            // 2. 获取连接
            conn = DriverManager.getConnection(url, user, password);
            
            // 3. 创建SQL
            String sql = "SELECT * FROM users WHERE age > ?";
            
            // 4. 创建PreparedStatement
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, 25);
            
            // 5. 执行查询
            rs = pstmt.executeQuery();
            
            // 6. 处理结果
            while (rs.next()) {
                Long id = rs.getLong("id");
                String username = rs.getString("username");
                String email = rs.getString("email");
                int age = rs.getInt("age");
                System.out.println(id + ", " + username + ", " + email + ", " + age);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭资源(逆序关闭)
            try {
                if (rs != null) rs.close();
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

2.2.3 CRUD操作 ​

java
public class JdbcCrudDemo {
    
    private static final String URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    
    // 插入
    public void insert(String username, String password, String email, int age) {
        String sql = "INSERT INTO users (username, password, email, age) VALUES (?, ?, ?, ?)";
        
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setString(1, username);
            pstmt.setString(2, password);
            pstmt.setString(3, email);
            pstmt.setInt(4, age);
            
            int rows = pstmt.executeUpdate();
            System.out.println("插入成功,影响行数:" + rows);
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    // 更新
    public void update(Long id, String email) {
        String sql = "UPDATE users SET email = ? WHERE id = ?";
        
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setString(1, email);
            pstmt.setLong(2, id);
            
            int rows = pstmt.executeUpdate();
            System.out.println("更新成功,影响行数:" + rows);
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    // 删除
    public void delete(Long id) {
        String sql = "DELETE FROM users WHERE id = ?";
        
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setLong(1, id);
            
            int rows = pstmt.executeUpdate();
            System.out.println("删除成功,影响行数:" + rows);
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    // 查询单个
    public User findById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        User user = null;
        
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setLong(1, id);
            
            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    user = new User();
                    user.setId(rs.getLong("id"));
                    user.setUsername(rs.getString("username"));
                    user.setEmail(rs.getString("email"));
                    user.setAge(rs.getInt("age"));
                }
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        return user;
    }
    
    // 查询列表
    public List<User> findAll() {
        String sql = "SELECT * FROM users";
        List<User> users = new ArrayList<>();
        
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             PreparedStatement pstmt = conn.prepareStatement(sql);
             ResultSet rs = pstmt.executeQuery()) {
            
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getLong("id"));
                user.setUsername(rs.getString("username"));
                user.setEmail(rs.getString("email"));
                user.setAge(rs.getInt("age"));
                users.add(user);
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        return users;
    }
}

2.2.4 事务管理 ​

java
public class JdbcTransactionDemo {
    
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        String url = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC";
        String user = "root";
        String password = "123456";
        
        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            
            // 开启事务
            conn.setAutoCommit(false);
            
            try {
                // 扣款
                String sql1 = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
                try (PreparedStatement pstmt1 = conn.prepareStatement(sql1)) {
                    pstmt1.setBigDecimal(1, amount);
                    pstmt1.setLong(2, fromId);
                    pstmt1.executeUpdate();
                }
                
                // 模拟异常
                // int i = 1 / 0;
                
                // 入账
                String sql2 = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
                try (PreparedStatement pstmt2 = conn.prepareStatement(sql2)) {
                    pstmt2.setBigDecimal(1, amount);
                    pstmt2.setLong(2, toId);
                    pstmt2.executeUpdate();
                }
                
                // 提交事务
                conn.commit();
                System.out.println("转账成功");
                
            } catch (Exception e) {
                // 回滚事务
                conn.rollback();
                System.out.println("转账失败,已回滚");
                e.printStackTrace();
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

2.2.5 连接池 ​

什么是连接池 ​

连接池预先创建并维护多个数据库连接,避免频繁创建和销毁连接的开销。

HikariCP(Spring Boot默认) ​

Maven依赖:

xml
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>

使用示例:

java
public class HikariDemo {
    
    private static HikariDataSource dataSource;
    
    static {
        // 配置连接池
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC");
        config.setUsername("root");
        config.setPassword("123456");
        config.setMaximumPoolSize(10);      // 最大连接数
        config.setMinimumIdle(5);           // 最小空闲连接
        config.setConnectionTimeout(30000); // 连接超时时间
        config.setIdleTimeout(600000);      // 空闲超时时间
        config.setMaxLifetime(1800000);     // 连接最大存活时间
        
        dataSource = new HikariDataSource(config);
    }
    
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    
    public static void main(String[] args) {
        try (Connection conn = getConnection()) {
            System.out.println("获取连接成功:" + conn);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

2.3 MyBatis(第7-12天) ​

2.3.1 MyBatis概述 ​

MyBatis是一个半自动化的持久层框架,将SQL语句与Java代码分离,支持动态SQL。

2.3.2 快速入门 ​

Maven依赖 ​

xml
<dependencies>
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.13</version>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    
    <!-- 日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

核心配置文件 mybatis-config.xml ​

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <!--  properties:引入外部配置文件  -->
    <properties resource="db.properties"/>
    
    <!-- settings:全局配置 -->
    <settings>
        <!-- 开启驼峰命名自动转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启日志 -->
        <setting name="logImpl" value="LOG4J"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    <!-- typeAliases:类型别名 -->
    <typeAliases>
        <package name="com.example.entity"/>
    </typeAliases>
    
    <!-- environments:环境配置 -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- mappers:映射文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
        <!-- 或使用包扫描 -->
        <package name="com.example.mapper"/>
    </mappers>
    
</configuration>

db.properties ​

properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456

实体类 User.java ​

java
public class User {
    private Long id;
    private String username;
    private String password;
    private String email;
    private Integer age;
    private Date createdAt;
    private Date updatedAt;
    
    // getter和setter省略
}

Mapper接口 UserMapper.java ​

java
public interface UserMapper {
    User findById(Long id);
    List<User> findAll();
    int insert(User user);
    int update(User user);
    int deleteById(Long id);
}

Mapper XML 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.mapper.UserMapper">
    
    <!-- 结果映射 -->
    <resultMap id="UserResultMap" type="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="email" column="email"/>
        <result property="age" column="age"/>
        <result property="createdAt" column="created_at"/>
        <result property="updatedAt" column="updated_at"/>
    </resultMap>
    
    <!-- 查询单个 -->
    <select id="findById" resultMap="UserResultMap">
        SELECT * FROM users WHERE id = #{id}
    </select>
    
    <!-- 查询全部 -->
    <select id="findAll" resultMap="UserResultMap">
        SELECT * FROM users
    </select>
    
    <!-- 插入 -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users (username, password, email, age)
        VALUES (#{username}, #{password}, #{email}, #{age})
    </insert>
    
    <!-- 更新 -->
    <update id="update">
        UPDATE users
        SET username = #{username},
            email = #{email},
            age = #{age}
        WHERE id = #{id}
    </update>
    
    <!-- 删除 -->
    <delete id="deleteById">
        DELETE FROM users WHERE id = #{id}
    </delete>
    
</mapper>

测试类 ​

java
public class MybatisTest {
    
    private SqlSessionFactory sqlSessionFactory;
    
    @Before
    public void setup() throws IOException {
        // 加载配置
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }
    
    @Test
    public void testFindById() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.findById(1L);
            System.out.println(user);
        }
    }
    
    @Test
    public void testInsert() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            
            User user = new User();
            user.setUsername("test");
            user.setPassword("123456");
            user.setEmail("test@example.com");
            user.setAge(25);
            
            int rows = mapper.insert(user);
            session.commit();
            
            System.out.println("插入成功,ID:" + user.getId());
        }
    }
}

2.3.3 动态SQL ​

xml
<!-- 条件查询 -->
<select id="findByCondition" resultMap="UserResultMap">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="minAge != null and maxAge != null">
            AND age BETWEEN #{minAge} AND #{maxAge}
        </if>
    </where>
    ORDER BY id DESC
</select>

<!-- 批量更新 -->
<update id="batchUpdate">
    UPDATE users
    <trim prefix="SET" suffixOverrides=",">
        <trim prefix="username = CASE" suffix="END,">
            <foreach collection="list" item="user">
                WHEN id = #{user.id} THEN #{user.username}
            </foreach>
        </trim>
        <trim prefix="email = CASE" suffix="END,">
            <foreach collection="list" item="user">
                WHEN id = #{user.id} THEN #{user.email}
            </foreach>
        </trim>
    </trim>
    WHERE id IN
    <foreach collection="list" item="user" open="(" separator="," close=")">
        #{user.id}
    </foreach>
</update>

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

<!-- choose选择 -->
<select id="findByChoose" resultMap="UserResultMap">
    SELECT * FROM users
    <where>
        <choose>
            <when test="id != null">
                AND id = #{id}
            </when>
            <when test="username != null">
                AND username = #{username}
            </when>
            <otherwise>
                AND status = 1
            </otherwise>
        </choose>
    </where>
</select>

<!-- set标签 -->
<update id="updateSelective">
    UPDATE users
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="email != null">email = #{email},</if>
        <if test="age != null">age = #{age},</if>
    </set>
    WHERE id = #{id}
</update>

<!-- foreach批量删除 -->
<delete id="batchDelete">
    DELETE FROM users WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

2.3.4 缓存机制 ​

一级缓存 ​

  • SqlSession级别的缓存
  • 默认开启
  • 同一个SqlSession中,相同的查询会从缓存获取
java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);

// 第一次查询,从数据库获取
User user1 = mapper.findById(1L);

// 第二次查询,从一级缓存获取
User user2 = mapper.findById(1L);

System.out.println(user1 == user2);  // true

二级缓存 ​

  • Mapper级别的缓存
  • 需要手动配置
  • 多个SqlSession共享

mybatis-config.xml开启:

xml
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

Mapper.xml配置:

xml
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
    
    <!-- 或使用ref引用其他mapper的缓存 -->
    <cache-ref namespace="com.example.mapper.RoleMapper"/>
</mapper>

2.3.5 关联查询 ​

一对一 ​

xml
<!-- 用户-身份证 -->
<resultMap id="UserIdCardResultMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 关联身份证 -->
    <association property="idCard" javaType="IdCard">
        <id property="id" column="id_card_id"/>
        <result property="cardNo" column="card_no"/>
    </association>
</resultMap>

<select id="findWithIdCard" resultMap="UserIdCardResultMap">
    SELECT u.*, i.id as id_card_id, i.card_no
    FROM users u
    LEFT JOIN id_cards i ON u.id = i.user_id
    WHERE u.id = #{id}
</select>

一对多 ​

xml
<!-- 用户-订单 -->
<resultMap id="UserOrdersResultMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 关联订单列表 -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
    </collection>
</resultMap>

<select id="findWithOrders" resultMap="UserOrdersResultMap">
    SELECT u.*, o.id as order_id, o.order_no, o.amount
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

2.3.6 MyBatis插件 ​

java
// 自定义插件:打印SQL执行时间
@Intercepts({
    @Signature(type = Executor.class, method = "query", 
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlTimePlugin implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        
        Object result = invocation.proceed();
        
        long time = System.currentTimeMillis() - start;
        System.out.println("SQL执行时间:" + time + "ms");
        
        return result;
    }
}

注册插件:

xml
<plugins>
    <plugin interceptor="com.example.plugin.SqlTimePlugin"/>
</plugins>

2.4 MyBatis-Plus(第13-14天) ​

2.4.1 MyBatis-Plus概述 ​

MyBatis-Plus是MyBatis的增强工具,简化CRUD操作。

2.4.2 快速入门 ​

Maven依赖 ​

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

实体类 ​

java
@Data  // Lombok注解
@TableName("users")  // 表名
public class User {
    @TableId(type = IdType.AUTO)  // 主键自增
    private Long id;
    
    private String username;
    private String password;
    private String email;
    private Integer age;
    
    @TableField(fill = FieldFill.INSERT)  // 自动填充
    private Date createdAt;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updatedAt;
    
    @TableLogic  // 逻辑删除
    private Integer deleted;
}

Mapper接口 ​

java
@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 继承BaseMapper,自动获得CRUD方法
}

基本CRUD ​

java
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 插入
    public void insert() {
        User user = new User();
        user.setUsername("test");
        user.setPassword("123456");
        user.setEmail("test@example.com");
        user.setAge(25);
        
        userMapper.insert(user);
        System.out.println("插入ID:" + user.getId());
    }
    
    // 根据ID查询
    public User findById(Long id) {
        return userMapper.selectById(id);
    }
    
    // 条件查询
    public List<User> findByCondition() {
        return userMapper.selectList(
            new QueryWrapper<User>()
                .eq("age", 25)
                .like("username", "test")
                .orderByDesc("id")
        );
    }
    
    // 分页查询
    public IPage<User> findByPage(int page, int size) {
        return userMapper.selectPage(
            new Page<>(page, size),
            new QueryWrapper<User>().eq("status", 1)
        );
    }
    
    // 更新
    public void update() {
        User user = new User();
        user.setId(1L);
        user.setEmail("new@example.com");
        
        userMapper.updateById(user);
    }
    
    // 删除
    public void delete(Long id) {
        userMapper.deleteById(id);
    }
    
    // 批量删除
    public void batchDelete(List<Long> ids) {
        userMapper.deleteBatchIds(ids);
    }
}

2.4.3 条件构造器 ​

java
// QueryWrapper
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "test")           // 等于
       .ne("status", 0)                  // 不等于
       .gt("age", 18)                    // 大于
       .ge("age", 18)                    // 大于等于
       .lt("age", 60)                    // 小于
       .le("age", 60)                    // 小于等于
       .between("age", 18, 60)           // BETWEEN
       .like("username", "test")         // LIKE '%test%'
       .likeLeft("username", "test")     // LIKE '%test'
       .likeRight("username", "test")    // LIKE 'test%'
       .in("id", Arrays.asList(1, 2, 3)) // IN
       .orderByDesc("id")                // 降序
       .orderByAsc("age")                // 升序
       .groupBy("age")                   // 分组
       .having("COUNT(*) > 1")           // HAVING
       .select("id", "username", "age"); // 指定字段

// LambdaQueryWrapper(推荐,避免字段名写错)
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(User::getAge, 25)
             .like(User::getUsername, "test")
             .orderByDesc(User::getId);

// UpdateWrapper
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", 1L)
             .set("email", "new@example.com")
             .set("age", 26);
userMapper.update(null, updateWrapper);

2.4.4 自动填充 ​

java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createdAt", Date.class, new Date());
        this.strictInsertFill(metaObject, "updatedAt", Date.class, new Date());
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updatedAt", Date.class, new Date());
    }
}

2.4.5 逻辑删除 ​

yaml
# application.yml
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted  # 逻辑删除字段
      logic-delete-value: 1        # 删除值
      logic-not-delete-value: 0    # 未删除值

2.4.6 代码生成器 ​

java
public class CodeGenerator {
    
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/test_db", "root", "123456")
            .globalConfig(builder -> {
                builder.author("yourname")
                       .outputDir("D:\\code\\src\\main\\java");
            })
            .packageConfig(builder -> {
                builder.parent("com.example")
                       .moduleName("system")
                       .entity("entity")
                       .mapper("mapper")
                       .service("service")
                       .serviceImpl("service.impl")
                       .xml("mapper.xml")
                       .controller("controller")
                       .pathInfo(Collections.singletonMap(OutputFile.xml, 
                           "D:\\code\\src\\main\\resources\\mapper"));
            })
            .strategyConfig(builder -> {
                builder.addInclude("users", "orders")
                       .addTablePrefix("t_");
            })
            .templateConfig(builder -> {
                builder.disable(TemplateType.ENTITY);
            })
            .execute();
    }
}

2.5 JPA/Hibernate(了解) ​

2.5.1 JPA概述 ​

JPA(Java Persistence API)是Java持久化规范,Hibernate是实现。

2.5.2 基本使用 ​

Maven依赖 ​

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

实体类 ​

java
@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 50)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    @Column(length = 100)
    private String email;
    
    private Integer age;
    
    @CreationTimestamp
    private Date createdAt;
    
    @UpdateTimestamp
    private Date updatedAt;
}

Repository接口 ​

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 方法命名查询
    List<User> findByAgeGreaterThan(Integer age);
    List<User> findByUsernameLike(String username);
    User findByEmail(String email);
    
    // JPQL查询
    @Query("SELECT u FROM User u WHERE u.age BETWEEN :min AND :max")
    List<User> findByAgeBetween(@Param("min") Integer min, @Param("max") Integer max);
    
    // 原生SQL
    @Query(value = "SELECT * FROM users WHERE age > ?1", nativeQuery = true)
    List<User> findByAgeNative(Integer age);
}

2.6 练习任务 ​

练习1:学生管理系统 ​

  • 设计学生表、课程表、选课表
  • 实现完整的CRUD
  • 使用MyBatis实现关联查询

练习2:电商订单系统 ​

  • 设计用户表、商品表、订单表、订单详情表
  • 实现下单流程(事务处理)
  • 使用连接池优化性能

练习3:博客系统 ​

  • 设计文章表、分类表、标签表、评论表
  • 实现文章分页查询
  • 实现标签多对多关联

2.7 学习检查清单 ​

  • [ ] 掌握MySQL基本操作
  • [ ] 理解事务ACID特性
  • [ ] 掌握索引使用原则
  • [ ] 熟练使用JDBC
  • [ ] 理解连接池原理
  • [ ] 掌握MyBatis核心配置
  • [ ] 熟练使用动态SQL
  • [ ] 理解MyBatis缓存机制
  • [ ] 掌握MyBatis-Plus基本使用

完成本阶段后,你应该能够熟练使用MyBatis进行数据库操作,为后续Spring Boot开发打下基础。

评论

正在加载评论…