Web 开发必学:Java 数据库操作从 JDBC 到 MyBatis 的进阶之路

在 Java Web 开发的技术栈中,数据库操作是衔接业务逻辑与数据存储的核心环节。从早期直接使用 JDBC 原生 API 操作数据库,到如今主流的 MyBatis 半自动化 ORM 框架,技术的演进始终围绕简化开发、提升效率、降低耦合三大目标。本文将从实际开发场景出发,带你梳理 Java 连接数据库的技术脉络,从 JDBC 的底层原理到 MyBatis 的实战应用,掌握 Web 开发中数据层的核心技术。

一、底层基石:JDBC 的核心原理与使用

JDBC(Java Database Connectivity)是 Java 官方提供的标准数据库访问接口,它定义了一套统一的 API,让 Java 程序能够与 MySQL、Oracle、SQL Server 等关系型数据库进行交互。无论后续框架如何封装,其底层最终都会回归到 JDBC 的调用。

1. JDBC 的核心组件

要理解 JDBC 的工作流程,首先需掌握其 4 个核心组件的作用:

组件名称 核心作用
DriverManager 加载数据库驱动类,根据连接信息创建Connection对象,是连接的入口
Connection 代表 Java 程序与数据库的一次会话连接,用于创建执行 SQL 的对象、管理事务
PreparedStatement 预编译的 SQL 执行对象,替代Statement避免 SQL 注入,支持参数化查询
ResultSet 存储 SQL 查询的结果集,提供方法遍历和获取数据

2. JDBC 操作数据库的完整流程

以 MySQL 数据库为例,我们通过查询用户信息 的案例,完整演示 JDBC 的使用步骤(注:MySQL 8.0 + 无需手动加载驱动,DriverManager会自动加载com.mysql.cj.jdbc.Driver)。

(1)创建user表
sql 复制代码
create table user
(
    id       int unsigned primary key auto_increment comment 'ID,主键',
    username varchar(20) comment '用户名',
    password varchar(32) comment '密码',
    name     varchar(10) comment '姓名',
    age      tinyint unsigned comment '年龄'
) comment '用户表';

insert into user(id, username, password, name, age)
values (1, 'daqiao', '123456', '大乔', 22),
       (2, 'xiaoqiao', '123456', '小乔', 18),
       (3, 'diaochan', '123456', '貂蝉', 24),
       (4, 'lvbu', '123456', '吕布', 28),
       (5, 'zhaoyun', '12345678', '赵云', 27);
(2)准备工作:引入数据库驱动

在 Maven 项目中添加 MySQL 驱动依赖,这是连接数据库的前提:

xml 复制代码
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.36</version>
    <scope>runtime</scope>
</dependency>

在resources中配置application.yml文件

yml 复制代码
spring:
  # 数据库信息配置
  datasource:
    url: jdbc:mysql://localhost:3306/db205
    username: root
    password: 1234
    driver-class-name: com.mysql.cj.jdbc.Driver
(3)编写 JDBC 核心代码
示例1

目标:修改用户id=1的密码为1234567890

java 复制代码
    @Test
    public void testUpdate() throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");//目的是运行这个字节码对象,里面会运行 DriverManager.registerDriver(new Driver());注册驱动
        //注意:注册驱动可以省略,因为导入的mysql驱动包里面已经设置自动运行这个字节码,不需要我们手动调用

        //2.根据驱动管理器获取连接对象
        //  语法:DriverManager.getConnection(url,username,password)
        //  url 是连接关系型数据库的连接地址,格式 jdbc:mysql://主机地址:端口号/数据库名
        //String url = "jdbc:mysql://localhost:3306/db205";
        String url = "jdbc:mysql:///db205";//本机localhost:3306可省略

        //  username 连接数据库用户名
        //  password 连接数据库用户的密码
        Connection conn = DriverManager.getConnection(url, "root", "1234");

        //3.获取执行sql语句命令对象
        Statement statement = conn.createStatement();

        //4.通过命令对象执行sql语句
        int rows = statement.executeUpdate("update user set password ='123456789' where id = 1");
        System.out.println("影响的行数为:" + rows);

        //5.释放资源
        statement.close();
        conn.close();
    }
示例2(引申)

目标:进行登录方法编写

java 复制代码
    public static void main(String[] args) throws Exception {
        //1.定义Scanner
        Scanner sc = new Scanner(System.in);

        //2.提示用户输入账号和密码
        System.out.println("请输入用户名:");
        String user = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();

        //3.执行查询sql语句返回结果集ResultSet
        String url = "jdbc:mysql:///db205";
        Connection conn = DriverManager.getConnection(url, "root", "1234");
        Statement statement = conn.createStatement();
        String s = String.format("select * from user where username = '%s' and password = '%s'", user, password);
        ResultSet resultSet = statement.executeQuery(s);
       
        //4.解析ResultSet读取数据
        if (resultSet.next()){//注意:查询登录用户最多返回1条数据,所以next()一次即可,如果是多条循环循环next()
            //4.1 有数据,显示登录成功
            //获取名字
            String name = resultSet.getString("name");
            //获取年龄
            int age = resultSet.getInt("age");

            System.out.printf("登录成功,恭喜姓名为:%s年龄为:%d登录本系统%n",name,age);
        }else {
            //4.2 没有数据,登录失败
            System.err.println("登录失败,账号或密码错误!");
        }

        //5.释放资源
        resultSet.close();
        statement.close();
        conn.close();
    }
(4)解决sql注入问题

第三步执行查询sql语句返回结果集ResultSet

java 复制代码
String s = String.format("select * from user where username = '%s' and password = '%s'", user, password);

这种拼接sql语句有sql注入攻击的风险

例如

在用户名上输入 ' or 1=1 --

这样传到mysql的语法就变成了

sql 复制代码
select * from user where username = '' or 1=1 --' and password = '%s'

username会判断出不等于'',但是or又会判断另一个条件1=1,又返回了true 接着--注释掉了后面的内容所以就返回登录成功了

因此采用预编译sql执行sql语句, 可以防止sql注入攻击并性能更高

将第三步修改为:

java 复制代码
//3.执行查询sql语句返回结果集ResultSet
String url = "jdbc:mysql:///db205";
Connection conn = DriverManager.getConnection(url, "root", "1234");
PreparedStatement pstat = conn.prepareStatement("select * from user where username = ? and password = ?");
//这种?占位符可以防止sql注入攻击的风险,  如攻击账号=' or 1=1 --
//设置?占位符的值
pstat.setString(1,user);//设置第1个?的值,从1开始
pstat.setString(2,password);//设置第2个?的值

ResultSet resultSet = pstat.executeQuery();

3. JDBC 的痛点分析

JDBC 作为底层 API,虽然原理清晰,但在实际开发中存在诸多不便,这也是框架诞生的原因:

  • 代码冗余:每次操作都要重复写 "获取连接→创建执行对象→关闭资源" 的模板代码;
  • 硬编码问题:SQL 语句、数据库连接信息直接写在 Java 代码中,修改需重新编译;
  • 资源管理低效:频繁创建和关闭连接会消耗大量数据库资源,导致性能瓶颈;
  • 结果映射繁琐 :需手动将ResultSet中的数据映射到 Java 实体类(POJO)。

为了解决这些问题,首先出现了数据库连接池优化资源管理,而 MyBatis 则从根本上解决了 SQL 与代码耦合、结果映射的问题。

二、性能优化:数据库连接池的应用

在生产环境中,直接使用DriverManager创建连接是性能杀手 ------ 数据库连接的创建和销毁需要消耗大量系统资源。数据库连接池的核心思想是预先创建一组数据库连接并复用,从而大幅提升操作效率。

目前主流的连接池有 HikariCP(Spring Boot 默认)、Druid、C3P0和阿里的druid等,这里以druid为例演示使用方式。

引入 druid 依赖

xml 复制代码
 <!--druid依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.19</version>
        </dependency>

连接池的使用解决了 JDBC 的资源管理问题,但 SQL 与代码耦合、结果映射的问题依然存在,这就引出了 MyBatis 框架。

三、主流选择:MyBatis 的核心实战

MyBatis 是一款半自动化 ORM(对象关系映射)框架,它既保留了 JDBC 对 SQL 的灵活控制,又解决了其代码冗余、耦合的问题。相比 Hibernate 等全自动化 ORM 框架,MyBatis 更轻量、更贴合实际开发中的复杂 SQL 场景。

1. MyBatis 的核心优势

  • SQL 解耦:将 SQL 写在 XML 或注解中,与 Java 代码分离,便于维护和优化;
  • 自动映射 :无需手动遍历ResultSet,自动将查询结果映射到 POJO 对象;
  • 动态 SQL :支持ifwhereforeach等标签,灵活构建动态 SQL 语句;
  • 缓存机制:内置一级缓存(会话级)和二级缓存(全局级),提升查询效率;
  • 低学习成本:相比全自动化 ORM 框架,MyBatis 更易上手,保留了 SQL 的可控性。

2. MyBatis 实战:实现用户数据 CRUD

我们以博客系统的user表为例,完整演示 MyBatis 的使用流程,包括配置文件、Mapper 接口、SQL 映射文件等核心环节。

(1)引入 MyBatis 依赖
xml 复制代码
<!--springboot父工程-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.0</version>
    <relativePath></relativePath><!--放在这里解决资源访问问题-->
</parent>

<!--导入依赖-->
<dependencies>
    <!--web依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.2.0</version> <!-- 最新稳定版本,根据需要调整 -->
    </dependency>

    <!--测试启动器:里面包含junit5-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <!--mybatis与spring整合包启动器: mybatis创建接口实现类对象(代理对象)会被spring加入spring容器-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>
    <!--druid依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.19</version>
    </dependency>

</dependencies>

在resources中配置application.yml文件

yml 复制代码
spring:
  # 数据库信息配置
  datasource:
    url: jdbc:mysql://localhost:3306/db205
    username: root
    password: 1234
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

# mybatis框架配置
mybatis:
  configuration:
    # 打印控制日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
(2)编写实体类(POJO)

对应数据库的user表,封装数据的载体:

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id; //ID
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Integer age; //年龄
}
(3)注解配置sql语句
java 复制代码
@Mapper //不用 @Repository 而是用mybatis提供的注解@Mapper
//作用: 创建当前接口的实现类代理对象(jdk动态代理)并加入spring容器中
public interface UserMapper {
    /**
     * 查询所有用户列表
     *
     * @return
     */
    @Select("select * from user")
    List<User> findAll();
    //生成代理对象,就会重写这个方法,就会执行反射获取sql语句+使用jdbc执行获取结果映射封装到List<User>集合中

    @Delete("delete from user where id = #{id}")
    Integer deleteById(Integer id);//如果只是映射一个参数,类型为包装类型、基本类型、字符串类型,#{映射的参数名可以任意写},建议正常写

    @Insert("insert into user values (null,#{username},#{password},#{name},#{age})")
    void insert(User user);//注意:映射参数名不要写user,直接写user对象里面的属性名,会自动调用属性名的getUsername()获取值映射进来。前提是方法的参数只有1个

    @Update("update user set username =#{username},password=#{password},name=#{name},age = #{age} where id = #{id}")
    void update(User user);

    @Select("select * from user where username =#{username} and password=#{password}")
    User findByUsernameAndPassword(String username, String password);
    //User findByUsernameAndPassword(@Param("username") String username,@Param("password") String password); //springboot1.0就必须使用@Param设置别名

(4)测试注解配置sql语句是否成功

java 复制代码
import com.tgt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest //加载spring环境,它会去找启动类并运行启动类(spring环境)
public class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

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

    @Test
    public void testDeleteById(){
        Integer i = userMapper.deleteById(5);
        System.out.println("影响的行数为:" + i);
    }

    @Test
    public void testInsert(){

        userMapper.insert(new User(null,"itheima","123456","黑马",20));
        System.out.println("新增用户成功");
    }

    @Test
    public void testUpdate(){

        userMapper.update(new User(7,"itheima2","123456","黑马2",22));
        System.out.println("修改用户成功");
    }

    @Test
    public void testFindByUsernameAndPassword(){

        User user = userMapper.findByUsernameAndPassword("xiaoqiao", "123456");
        System.out.println(user);
    }

    @Test
    public void testFindAll2(){

        List<User> user = userMapper.findAll2();
        user.forEach(System.out::println);
    }
}
(5)编写 Mapper 接口与 SQL 映射文件

MyBatis 采用接口 + XML的方式实现数据操作,接口定义方法,XML 编写 SQL。

① Mapper 接口com/tgt/mapper/UserMapper.java):

java 复制代码
import com.tgt.pojo.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper2 {
     List<User> findAll2();
    //注意:在mybatis框架中要求当前接口中的方法名不可以方法重载
    
    void update(User user);
}

② SQL 映射文件resources/mapper/UserMapper.xml):

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tgt.mapper.UserMapper2">

    <!-- List<User> findAll2(); 映射接口中这个方法
    <select> 代表执行查询select的sql语句命令, 标签体写sql语句
    id="findAll2" 设置映射的方法名,必须唯一,不可以重复,所以不可以方法重载
    resultType="com.itheima.pojo.User" 设置接口方法返回值类型,如果集合只需要设置泛型类型即可

    xml映射规则,如下是默认规则,可以改变(后面再说)
        1。XML映射文件的名称与Mapper接口名称一致,并且将xML映射文件和Mapper接口放置在相同包下(同包同名)。
        2。XML映射文件的namespace属性为Mapper接口全限定名一致。
        3。XML映射文件中sql语句的id与Mapper 接口中的方法名一致,并保持返回类型一致。
    -->
    <select id="findAll2" resultType="com.tgt.pojo.User">
        select * from user
    </select>
    
    <update id="update">
        update user set username =#{username},password=#{password},name=#{name},age = #{age} where id = #{id}
    </update>
</mapper>
(6)测试 MyBatis 操作
java 复制代码
import com.tgt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserMapperTest2 {

    @Autowired
    private UserMapper2 userMapper2;

    @Test
    public void testFindAll2(){

        List<User> user = userMapper.findAll2();
        user.forEach(System.out::println);
    }
    
    @Test
    public void testUpdate(){

        userMapper2.update(new User(7,"itheima2","123456","黑马2",22));
        System.out.println("修改用户成功");
    }

}

3. MyBatis 的动态 SQL(核心亮点)

动态 SQL 是 MyBatis 最实用的特性之一,它允许我们根据业务条件动态拼接 SQL,避免了手动拼接的繁琐和错误。常用的动态 SQL 标签包括:

  • <if>:条件判断,用于拼接 SQL 片段;
  • <where>:自动去除多余的AND/OR,简化条件查询;
  • <foreach>:遍历集合,用于批量操作(如IN查询、批量插入);
  • <set>:用于更新操作,自动去除多余的逗号。

例如,实现批量插入用户的功能:

xml 复制代码
<insert id="batchInsertUser">
    INSERT INTO user (username, email, password, create_time)
    VALUES
    <foreach collection="userList" item="user" separator=",">
        (#{user.username}, #{user.email}, #{user.password}, #{user.createTime})
    </foreach>
</insert>

四、技术对比与选型建议

特性 JDBC MyBatis
代码量 多(模板代码重复) 少(仅需定义接口和 SQL)
SQL 与代码耦合度 高(SQL 写在 Java 代码中) 低(SQL 写在 XML / 注解中)
结果映射 手动映射 自动映射
动态 SQL 手动拼接,易出错 内置标签,灵活高效
学习成本 低(底层原理清晰) 中(需掌握配置和标签)
性能 最高(直接操作数据库) 接近 JDBC(轻微框架开销)

选型建议:

  • 学习 / 小型项目:可使用 JDBC,便于理解数据库操作的底层原理;
  • 中大型实际项目:优先选择 MyBatis,提升开发效率和可维护性;
  • 高并发场景:MyBatis 结合 HikariCP/Druid 连接池,性能可满足大部分业务需求;
  • 快速开发场景:可使用 MyBatis-Plus(MyBatis 增强工具),实现无代码 CRUD。

五、总结与拓展

本文从 JDBC 的底层原理出发,分析了其痛点,进而介绍了数据库连接池的优化作用,最终聚焦于 MyBatis 的核心实战。核心要点可总结为:

  1. JDBC 是基础:所有 Java 数据库操作框架的底层都是 JDBC,理解其原理是掌握后续技术的关键;
  2. 连接池是刚需:生产环境中必须使用连接池优化资源管理,避免频繁创建 / 关闭连接;
  3. MyBatis 是主流:兼顾 SQL 的灵活性和开发效率,是 Java Web 开发中数据层的首选框架。

后续拓展方向:

  • MyBatis-Plus:基于 MyBatis 的增强工具,提供分页插件、逻辑删除、自动生成 CRUD 等功能;
  • Spring 整合 MyBatis:在 Spring/Spring Boot 项目中,通过自动配置简化 MyBatis 的使用;
  • ORM 框架对比:了解 Hibernate/JPA 等全自动化 ORM 框架,根据业务场景选择合适技术;
  • 数据库性能优化:学习索引、SQL 优化、分库分表等高级知识,提升数据层性能。

Java 数据库操作的技术演进,本质是从 "手动操作" 到 "框架封装" 的效率提升。掌握这些技术,能让你在 Web 开发中更高效地处理数据层业务,为构建稳定、高效的应用打下坚实基础。

相关推荐
全栈工程师修炼指南2 小时前
Nginx | HTTPS 加密传输:Nginx 反向代理与上游服务 SSL 双向认证实践
网络·数据库·nginx·https·ssl
不被AI替代的BOT2 小时前
AgentScope深入分析-LLM&MCP
人工智能·后端
德迅云安全-小潘2 小时前
网络空间资产安全发展演进与实践框架
数据库·web安全
极限实验室2 小时前
APM(二):监控 Python 服务
数据库
技术不打烊2 小时前
凌晨2点,MySQL连接爆了!我用这招28秒救回90万订单
后端
_852 小时前
你真的懂context吗?
后端
总是学不会.2 小时前
【JUC编程】多线程学习大纲
java·后端·开发
川川菜鸟2 小时前
谷歌安全告警(Chrome 红页)完整处理指南
数据库·chrome·安全
BingoGo2 小时前
使用 PHP 和 WebSocket 构建实时聊天应用:完整指南
后端·php