前端视角 Java Web 入门手册 5.7:真实世界 Web 开发——数据库操作

JDBC 规范

在 Java 早期的版本中,开发人员需要使用不同的数据库厂商提供的不同 API 来访问不同的数据库,这样导致代码的可移植性降低,也增加了开发和维护的成本。JDBC(Java Database Connectivity)制定了用于连接和操作关系型数据库的标准API,允许开发者使用相同代码,通过不同数据库驱动适配多种数据库

JDBC 包含几个核心接口

  • DriverManager:负责管理 JDBC 驱动程序的加载和连接到数据库的操作
  • Connection:表示与数据库的连接,提供了创建 Statement 和管理事务的方法
  • Statement :表示执行 SQL 语句的对象,可以用来执行查询和更新操作
    • Statement:每次执行时,数据库会重新编译SQL语句
    • PreparedStatement:预编译的SQL语句,支持参数化查询,性能和安全性更高
    • CallableStatement:用于调用存储过程的对象
  • ResultSet:表示 SQL 查询结果的对象,可以用来遍历结果集的数据

这些 API 组成了 JDBC 的核心功能,数据库厂商实现 JDBC 规范后提供 JDBC driver 程序,可以通过它们使用同样的 API 来与各种关系型数据库进行交互

JDBC demo

写一个使用 JDBC API 查询数据库表的简单示例

数据库初始化

sql 复制代码
-- 创建测试用户
CREATE USER 'tester'@'localhost' IDENTIFIED BY 'hello1234';
GRANT ALL PRIVILEGES ON demo.* TO 'tester'@'localhost';
FLUSH PRIVILEGES;

-- 创建数据库
CREATE DATABASE demo;

-- 初始化表结构
create table demo.user
(
    id          bigint auto_increment primary key,
    name        varchar(100) charset utf8mb3        not null comment '用户名称',
    mail        varchar(100) charset utf8mb3        not null comment '邮箱',
    create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
    update_time timestamp                           null on update CURRENT_TIMESTAMP comment '更新时间'
)
comment '用户表';

-- 插入测试数据
insert into demo.user (name, mail)
values  ('Lisa Zhang', '[email protected]'),
        ('Byron Sun', '[email protected]'),
        ('Alex Song', '[email protected]');

安装 mysql 的 JDBC driver

xml 复制代码
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <version>8.0.33</version>
</dependency>

编写测试代码

java 复制代码
String url = "jdbc:mysql://localhost:3306/demo";
String user = "tester";
String password = "hello1234"; // 实际开发不会使用明文密码

// JDK6 之前需要手工指定驱动类
// Class.forName("com.mysql.cj.jdbc.Driver");

try (Connection conn = DriverManager.getConnection(url, user, password)) {
    Statement stmt = conn.createStatement();
    String sql = "SELECT * FROM user";
    ResultSet rs = stmt.executeQuery(sql);
    while (rs.next()) {
        int id = rs.getInt("id");
        String name = rs.getNString("name");
        System.out.println("id: " + id + ", name: " + name);
    }
} catch (SQLException ex) {
    ex.printStackTrace();
}

JDBC 4.0 规范要求 JDBC 驱动程序必须提供一个名为 META-INF/services/java.sql.Driver 的文件,其中包含了驱动程序的完整类名。当程序调用 DriverManager.getConnection() 方法时,JDBC 将自动扫描所有的 JDBC 驱动程序,并查找 META-INF/services/java.sql.Driver 文件中的驱动程序类名,如果找到了与传入的 URL 匹配的驱动程序,就会自动加载该驱动程序并建立连接。

ORM

Java 是一门面向对象编程语言,但开发者使用 JDBC API 在持久层数据库访问时重返了面向过程的编程模式。ORM(Object-Relational Mapping,对象关系映射)用于实现面向对象编程语言的类型和关系型数据库类型之间的相互转换

持久层是指将数据存储到持久化存储介质(如数据库、文件系统等)中的一层,也称为数据访问层(Data Access Layer,简称 DAL)。它主要负责将应用程序中的数据持久化到存储介质中,并支持数据的访问、查询、更新和删除等操作

使用 ORM 可以把关系型数据库映射为面向对象的模型,应用程序以面向对象的方式来持久化对象,ORM 框架将这些面向对象的操作转换成底层 SQL 操作

关系型数据库 面向对象
行(record) 对象
列(field) 对象属性

MyBatis 是一个流行的持久层框架,可以将 SQL 语句和 Java 代码进行解耦,通过使用 XML 或注解来配置 SQL 语句,使开发人员可以更加灵活地控制数据库操作和查询

因为需要开发者写 SQL MyBatis 被称为半自动 ORM 框架,虽然带来了一定的成本和可移植性问题,但相对于 Hibernate 等全自动的 ORM 框架,由于 MyBatis 可以自由编写 SQL 语句,因此可以更加精细地控制 SQL 语句,以优化查询性能

MyBatis demo

MyBatis 的核心工作流程如下:

  1. 配置文件加载 :加载 MyBatis 的全局配置文件 mybatis-config.xml,配置数据库连接、映射文件等。
  2. 创建 SqlSessionFactory:通过配置文件创建 SqlSessionFactory,它是 MyBatis 的入口点,负责创建 SqlSession。
  3. 获取 SqlSession:通过 SqlSessionFactory 获取 SqlSession,用于执行数据库操作。
  4. 执行 SQL 映射:根据映射文件(或注解)中的配置,执行相应的 SQL 语句,并将结果映射成 Java 对象。
  5. 关闭 SqlSession:完成数据库操作后,关闭 SqlSession,释放资源。

添加 Mybatis 依赖

xml 复制代码
<dependencies>
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.14</version>
    </dependency>
    
    <!-- MySQL JDBC 驱动 -->
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <version>8.0.33</version>
    </dependency>
</dependencies>

配置 MyBatis

src/main/resources 目录下创建 MyBatis 的全局配置文件 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>
    <!-- 数据库环境配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/your_database"/>
                <property name="username" value="your_username"/>
                <property name="password" value="your_password"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 映射文件的路径 -->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

创建映射文件(Mapper)

src/main/resources/mappers 目录下创建一个映射文件 UserMapper.xml,用于定义 SQL 语句和结果映射

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="com.example.model.User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="email" column="email"/>
    </resultMap>

    <!-- 查询所有用户 -->
    <select id="selectAllUsers" resultMap="UserResultMap">
        SELECT id, name, email FROM users
    </select>

    <!-- 根据 ID 查询用户 -->
    <select id="selectUserById" parameterType="int" resultMap="UserResultMap">
        SELECT id, name, email FROM users WHERE id = #{id}
    </select>

    <!-- 插入用户 -->
    <insert id="insertUser" parameterType="com.example.model.User">
        INSERT INTO users (name, email) VALUES (#{name}, #{email})
    </insert>

    <!-- 更新用户 -->
    <update id="updateUser" parameterType="com.example.model.User">
        UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
    </update>

    <!-- 删除用户 -->
    <delete id="deleteUser" parameterType="int">
        DELETE FROM users WHERE id = #{id}
    </delete>

</mapper>

id 和 Java 接口的方法名对应

编写 Java 接口

src/main/java/com/example/mapper 目录下创建一个与 Mapper 文件对应的 Java 接口 UserMapper.java

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

import java.util.List;
import com.example.model.User;

public interface UserMapper {
    List<User> selectAllUsers();
    User selectUserById(int id);
    void insertUser(User user);
    void updateUser(User user);
    void deleteUser(int id);
}

定义 Java 模型

src/main/java/com/example/model 目录下创建一个与数据库表 users 对应的 Java 类 User.java

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

public class User {
    private int id;
    private String name;
    private String email;

    // 构造方法
    public User() {}

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getters 和 Setters
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    } 

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    } 

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    } 

    @Override
    public String toString() {
        return "User { " +
               "id=" + id +
               ", name='" + name + ''' +
               ", email='" + email + ''' +
               " }";
    }
}

使用 SqlSession 进行数据库操作

编写一个测试类 MyBatisDemo.java,使用 MyBatis 进行数据库操作

java 复制代码
package com.example;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;

import com.example.mapper.UserMapper;
import com.example.model.User;

public class MyBatisDemo {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            // 加载 MyBatis 配置文件
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        // 打开 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 获取 Mapper 接口
            UserMapper mapper = session.getMapper(UserMapper.class);
            
            // 查询所有用户
            List<User> users = mapper.selectAllUsers();
            System.out.println("所有用户:");
            for (User user : users) {
                System.out.println(user);
            }
            
            // 插入新用户
            User newUser = new User("李四", "[email protected]");
            mapper.insertUser(newUser);
            session.commit(); // 提交事务
            System.out.println("插入新用户成功,ID:" + newUser.getId());
            
            // 查询插入的用户
            User insertedUser = mapper.selectUserById(newUser.getId());
            System.out.println("插入的用户: " + insertedUser);
            
            // 更新用户信息
            insertedUser.setEmail("[email protected]");
            mapper.updateUser(insertedUser);
            session.commit();
            System.out.println("更新用户成功。");
            
            // 查询更新后的用户
            User updatedUser = mapper.selectUserById(insertedUser.getId());
            System.out.println("更新后的用户: " + updatedUser);
            
            // 删除用户
            mapper.deleteUser(updatedUser.getId());
            session.commit();
            System.out.println("删除用户成功。");
        }
    }
}

Mybatis 常用标签

resultMap

resultType 可以把数据库表自动一一对应映射到 Java 对象,使用 resultMap 可以定义复杂的映射关系

xml 复制代码
<resultMap id="resultMap的标识符" type="Java对象的类型">
    <id column="主键列名" property="Java对象属性名"/>
    <result column="列名1" property="Java对象属性名1"/>
  	<result column="列名2" property="Java对象属性名2"/>
  
    <association property="Java对象属性名" javaType="关联的Java对象类型">
        <!-- ... -->
    </association>
  
    <collection property="Java对象属性名" ofType="关联的Java对象类型">
        <!-- ... -->
    </collection>
    <!-- ... -->
</resultMap>
  • resultMap 元素中可以包含多个 id 和 result 元素,分别用于映射表的主键列和普通列。如果表的主键列只有一个可以使用 id 元素映射,否则需要使用 composite-id 元素映射复合主键
  • association 元素用于定义一对一关联关系,表示当前 Java 对象属性中包含了一个关联的 Java 对象
  • collection 元素用于定义一对多关联关系,表示当前 Java 对象属性中包含了多个关联的 Java 对象
xml 复制代码
<resultMap id="userResultMap" type="User">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="age" property="age"/>
    <association property="department" javaType="Department">
        <id column="dept_id" property="id"/>
        <result column="dept_name" property="name"/>
    </association>
</resultMap>

<select id="getUser" parameterType="long" resultMap="userResultMap">
    SELECT u.id, u.name, u.age, d.id as dept_id, d.name as dept_name
    FROM user u
    LEFT JOIN department d ON u.dept_id = d.id
    WHERE u.id = #{id}
</select>

sql & include

用于定义重复使用的 SQL 片段

xml 复制代码
<sql id="selectColumns">
    SELECT id, name, age
</sql>

通过 元素引用 selectColumns SQL片段,避免了重复编写 SELECT 语句的列名

xml 复制代码
<select id="getUser" parameterType="long" resultMap="userResultMap">
    <include refid="selectColumns"/>
    FROM user
    WHERE id = #{id}
</select>

Spring Data JPA

Spring Data JPA 是 Spring 生态中基于 JPA(Java Persistence API) 规范的数据访问框架,旨在简化数据库操作,通过声明式接口和自动化 SQL 生成,开发者无需手动编写 CRUD 模板代码,提供标准化的 Repository 接口,支持跨数据库的增删改查、分页、排序等通用操作,当然也毫无意外的提升了学习成本

初始化项目

plain 复制代码
spring-data-jpa-demo
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.example
│   │   │       ├── SpringDataJpaDemoApplication.java
│   │   │       ├── model
│   │   │       │   └── User.java
│   │   │       └── repository
│   │   │           └── UserRepository.java
│   │   └── resources
│   │       └── application.properties
├── pom.xml
└── README.md

添加依赖

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- H2 内存数据库 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

配置数据库连接

src/main/resources/application.properties 文件中配置数据库连接信息和 JPA 相关属性

properties 复制代码
# H2 数据库配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# H2 控制台可选配置
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

创建实体类

com.example.model 包下创建一个简单的实体类 User,对应数据库中的 users

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

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@Entity
@Table(name = "users") // 可选,指定表名
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增策略
    private Long id;

    @Column(nullable = false, length = 50)
    private String name;

    @Column(nullable = false, length = 100, unique = true)
    private String email;
}

创建 Repository 接口

com.example.repository 包下创建 UserRepository 接口,继承自 JpaRepository,提供基本的 CRUD 方法

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

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.model.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // 基于方法名称的查询,Spring Data JPA 会自动解析并生成相应的查询语句
    User findByEmail(String email);
}

JpaRepository 提供了常用的数据库操作方法,如 save, findAll, findById, deleteById 等,Spring Data JPA 会自动为这个接口生成实现类,并将其作为 Spring Bean 注入到需要的地方

编写主应用类并执行 CRUD 操作

为了简化示例,我们在主应用类中使用 CommandLineRunner 接口,在应用启动时执行一些 CRUD 操作,并将结果输出到控制台

CommandLineRunner 是一个功能型接口,用于在应用启动时执行代码

java 复制代码
package com.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.example.model.User;
import com.example.repository.UserRepository;

@SpringBootApplication
public class SpringDataJpaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringDataJpaDemoApplication.class, args);
    }

    @Bean
    public CommandLineRunner demo(UserRepository userRepository) {
        return (args) -> {
            // 创建并保存用户
            User user1 = new User();
            user1.setName("张三");
            user1.setEmail("[email protected]");
            userRepository.save(user1);

            User user2 = new User();
            user2.setName("李四");
            user2.setEmail("[email protected]");
            userRepository.save(user2);

            // 查询所有用户
            System.out.println("所有用户:");
            for (User user : userRepository.findAll()) {
                System.out.println(user);
            }

            // 根据 ID 查询用户
            User user = userRepository.findById(user1.getId()).orElse(null);
            System.out.println("根据 ID 查询用户:");
            System.out.println(user);

            // 根据 Email 查询用户
            User userByEmail = userRepository.findByEmail("[email protected]");
            System.out.println("根据 Email 查询用户:");
            System.out.println(userByEmail);

            // 更新用户
            if (user != null) {
                user.setEmail("[email protected]");
                userRepository.save(user);
                System.out.println("更新后的用户:");
                System.out.println(userRepository.findById(user.getId()).orElse(null));
            }

            // 删除用户
            userRepository.delete(user2);
            System.out.println("删除后的所有用户:");
            for (User u : userRepository.findAll()) {
                System.out.println(u);
            }
        };
    }
}

Spring Data JPA VS MyBatis

总体而言如果项目主要是标准的 CRUD 操作,且需要快速开发和高效维护,Spring Data JPA 是更好的选择;如果项目对 SQL 语句有高控制要求,涉及复杂查询和性能优化,选择 MyBatis 更为合适。

Spring Data JPA

  • 快速开发标准 CRUD 应用:通过自动化的 Repository 接口,极大降低模板代码量。
  • 复杂对象关系管理:内置支持多表关联和实体关系映射,简化对象关系操作。

MyBatis

  • 需要精细控制 SQL 语句:手动编写和优化 SQL,适用于对性能和查询逻辑有严格要求的应用。
  • 复杂查询和高性能需求:对于需要复杂 JOIN、多表查询或高性能批量操作的场景,MyBatis 提供更高的灵活性和优化能力。
相关推荐
雷渊4 分钟前
深入分析ZooKeeper的选举机制
后端
芦屋花绘6 分钟前
Java的JUC详细全解
java·开发语言·jvm·spring boot·kafka
Asthenia041212 分钟前
Java函数式编程详解:更优雅的表达
后端
珠峰下的沙砾14 分钟前
如何将IDP映射属性添加,到accountToken中 方便项目获取登录人信息
java
lfwh20 分钟前
Java 实现单链表翻转(附详细注释)
java·开发语言·python
热爱学习的路人甲30 分钟前
GoKV
后端
陈哥聊测试31 分钟前
开发认为测试不及时,测试吐槽工作量太大?
后端·测试·devops
写bug写bug31 分钟前
为什么 LIMIT 0, 10 快,而 LIMIT 1000000, 10 慢?
数据库·后端·mysql
飞鱼荷兰猪1 小时前
LLM大语言模型简述
后端·aigc