# 专业名词

说到 Java 访问数据库的方法,就会涉及到非常多的新词,比如:

  • JDBC
  • HikariCP
  • HSQLDB
  • DAO
  • Hibernate
  • JPA
  • MyBatis

作为一个刚学 Java 不到一周的萌新,我看到这堆词的时候直接懵掉了,不知道从哪里开始学起,甚至自闭了几个小时。

所以,我准备先简单讲一下这几个词的概念。

  • JDBC(Java DataBase Connection):Java 访问数据库的 API,只提供接口,底层驱动由 MySQL 等提供。绝大部分 Java 访问数据库的包都是基于 JDBC 进行二次开发
  • HikariCP:一个数据库连接池(Connection Pool),用于和数据库建立高效、可复用的连接
  • HSQLDB:一个 Java 编写的嵌入式数据库,类似于 C/C++ 的 SQLite,可用于测试或小型应用
  • DAO(Data Access Object):一种编程模式/思想,对于每一个存储在数据库的类(如 User)建立一个类,专门负责访问数据库、对数据库进行 CURD
  • ORM(Object-Relational Mapping):将对象和关系进行映射的过程。下面三个 Hibernate JPA MyBatis 其实都是在做 ORM
  • JPA(Java Persistence API):一个 ORM 标准 API,只提供接口,类似于 JDBC。
  • Hibernate:一个 ORM 框架,能够自动将查询语句映射到 SQL,并将查询结果映射为 Java 对象(Python 的 Django 也提供了类似的 ORM 功能)
  • MyBatis:一个 ORM 框架,可以将查询结果映射为 Java 对象,但还是需要手写 SQL

# JDBC

JDBC:万物之源。

JDBC 的全称是 Java 数据库连接 (Java DataBase Connectivity),它是 Java 为关系数据库定义了一套标准的访问接口。

JDBC 是接口,这个接口意味着,虽然不同数据库的访问方法不一样,但是不同应用程序(包括不同框架)访问 JDBC 接口、数据库厂商为 JDBC 实现自己的接口,就可以连接应用程序和数据库了。

JDBC

目前几乎所有 Java 程序都是使用 JDBC 访问数据库,包括 JetBrains 的所有 IDE 在链接数据库前,也需要下载对应数据库的 JDBC 驱动。

更详细的使用的教程可见JDBC编程 - 廖雪峰的官方网站 (opens new window)

# HikariCP

HikariCP 是一个数据库连接池,负责自动管理数据库连接,提高性能。这个没什么好讲的,在高并发下,连接池是标配,Spring Boot 也优先选择 HikariCP 作为连接池。

# HSQLDB

List<User> users = userOrm.query(gender="M", grade=3);


int userId = userOrm.create(user);


int updateCount = userOrm.update(user);


int deleteCount = userOrm.delete(user);

HSQLDB 其实和 SQLite 是类似的,都是一个小型的、嵌入式的、可以运行于内存或文件的数据库,不需要单独安装,只需要编译的时候将包导入即可。

HSQLDB 和 SQLite 的区别是,HSQLDB 是 Java 写的,而 SQLite 是 C 写的,所以 Java 项目导入 HSQLDB 很方便,而 C++ 项目导入 SQLite 会很方便。

HSQLDB 和 SQLite 都常用于开发、测试环境、中小型系统中。

需要注意的是,MySQL 默认使用可重复读,而 HSQLDB 2 不支持可重复读,默认是读提交 (reference (opens new window))。所以 HSQLDB 会出现 A 更新但未提交后、B 事务申请读但是卡死的情况。

# DAO

数据访问对象 (Data Access Object, DAO) 只是一种设计模式,不需要引入新的工具包。

如果不使用 DAO 的话,我们会把访问数据库的逻辑写到业务层里,如果业务逻辑变复杂,就很难管理。

因此,我们可以把数据访问层控制层进行分离。

如果我们使用面向对象编写数据访问层,就有了数据访问对象 (Data Access Object, DAO) 了。

public class UserDao {
    User getById(long id) {
        // access database ...
    };

    List<User> getUsers(int page) {
        // access database ...
    };

    User createUser(User user) {
        // access database ...
    };

    User updateUser(User user) {
        // access database ...
    };

    void deleteUser(User user) {
        // access database ...
    };
}

# ORM

ORM(Object-Relational Mapping),对象关系映射,就是将 Java 原生对象和关系数据库的数据建立关系的一种思想。

ORM

纯 JDBC 是没有 ORM 的,所以需要手动取出 resultSet 中的每个字段,然后处理,非常麻烦。

下面是使用 JDBC 查询数据库,JDBC 没有 ORM,需要手动将对象字段映射为 SQL 查询条件、将 SQL 查询结果映射为对象。

try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
        ps.setObject(1, "M");
        ps.setObject(2, 3);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                long id = rs.getLong("id");
                long grade = rs.getLong("grade");
                String name = rs.getString("name");
                String gender = rs.getString("gender");
            }
        }
    }
}

如果有 ORM(运用现有的 ORM 框架,或者手写 ORM)自动把 resultSet 映射到 Java 原生类,在开发上都会变得简单很多。

// 理想的 ORM
List<User> users = userOrm.query(gender="M", grade=3);

Java 的常见 ORM 框架有:

  • JPA(Java Persistence API):一个 ORM 标准 API,只提供接口,类似于 JDBC。
  • Hibernate:一个 ORM 框架,能够自动将查询语句映射到 SQL,并将查询结果映射为 Java 对象
  • MyBatis:一个 ORM 框架,可以将查询结果映射为 Java 对象,但还是需要手写 SQL

下面会分别介绍。

# JPA

JPA(Java Persistence API Java 持久层 API) 是 JavaEE 的 ORM 标准,大家可以用这个标准提供的接口以 ORM 的形式访问数据库。

值得注意的是,JPA 只是提供了接口,底层的实现不是 JPA 做的,可以使用 EclipseLink 或 Hibernate(没错,你不仅可以使用 Hibernate API 调用 Hibernate,也可以使用 JPA 调用)作为实现。

简单的操作用 JPA 写会非常简单,比如按 id 查询用户:

public class User {
    public User getUserById(long id) {
        User user = this.entityManager.find(User.class, id);
        // ...
    }
}

但是复杂的操作看起来就很麻烦了,SELECT * FROM user WHERE email = ? 用 JPA 写:

public User fetchUserByEmail(String email) {
    var cb = em.getCriteriaBuilder();
    CriteriaQuery<User> q = cb.createQuery(User.class);
    Root<User> r = q.from(User.class);
    q.where(cb.equal(r.get("email"), cb.parameter(String.class, "e")));
    TypedQuery<User> query = em.createQuery(q);
    query.setParameter("e", email);
    List<User> list = query.getResultList();
    return list.isEmpty() ? null : list.get(0);
}

还不如写 SQL 呢。所以,一般来说不会选择这个 API 作为 ORM 的工具。

# Hibernate

相较于 JPA,Hibernate API 就友好很多了。

上面的 SELECT * FROM user WHERE email = ? 用 Hibernate 可以这么写:

public User fetchUserByEmail(String email) {
    User example = new User();
    example.setEmail(email);
    List<User> list = hibernateTemplate.findByExample(example);
    return list.isEmpty() ? null : list.get(0);
}

相较 JPA 就简单很多了。

顺便一提,如果使用的是 Django 的 ORM,SELECT * FROM user WHERE email = ? 可以这么写:

def fetchUserByEmail(email : str) -> User:
    q = User.objects.filter(email=email)
    return q[0] if len(q) else None

可以看到,Hibernate 和 JPA 可以将查询条件自动转换为 SQL,同时自动将查询结果返回为 Java 对象,而 Hibernate 的语法较 JPA 简单了很多。

但是,Hibernate 为了兼容多种数据库,它使用 HQL 或 JPQL 查询,经过一道转换,变成特定数据库的 SQL,理论上这样可以做到无缝切换数据库,但这一层自动转换除了少许的性能开销外,给 SQL 级别的优化带来了麻烦。

此外,如果有大量表连接操作,不直接用 SQL 写的话,语法上也会非常麻烦。

所以,产生了另一种框架,需要手写 SQL,但是能够将结果自动转换为 Java 对象,MyBatis 就是这么一个 ORM 框架。

# MyBatis

MyBatis 虽然需要手写 SQL,但是他的语法也相当简洁:

public interface UserMapper {
    @Select("SELECT * FROM user WHERE email = #{email}")
	User getByEmail(@Param("email") String email);
}

写好这个接口以后,MyBatis 甚至可以帮我们创建实现类,我们直接调用就行了:

User user = userMapper.getByEmail(email);

可见,MyBatis 的优势有:

  1. (相较于 JDBC)自动将 ResultSet 转化为 Java 对象
  2. (相较于 Hibernate 和 JPA)执行原生 SQL,不会有效率影响
  3. (相较于 JDBC)自动生成查询函数,语法简洁

# Mybatis 的三种风格

Mybatis 也有三种风格:

  1. 注解风格,将 SQL 写在接口方法的注解里;
  2. XML 风格,将 SQL 语句单独放到一个 XML 文件里;
  3. MyBatis-Plus 插件,类似 Hibernate API,提供通用的 API,能方便地对单表增删查改。

这三种方案使用哪一种,就见仁见智了。如果使用注解风格,在看方法名的时候顺便就能看到实际的 SQL;使用 XML 风格实现了 SQL 层和 Java 代码的分离;MyBatis Plus 则是类似 Hibernate,可能会有效率问题。

<!--  使用 XML 配置的 MyBatis Mapper  -->
<mapper>
  <select id="getByEmail" resultType="User">
    SELECT * FROM user WHERE email = #{email}
  </select>
</mapper>
// 使用注解配置的 MyBatis Mapper
public interface UserMapper {
    @Select("SELECT * FROM user WHERE email = #{email}")
    User getByEmail(@Param("email") String email);
}
User user = userMapper.getByEmail(email);
// 使用 MyBatis Plus 配置的 MyBatis Mapper
public interface UserMapper extends BaseMapper<User> {}

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("email", email);
User user = userMapper.selectOne(queryWrapper);

# MyBatis Generator 自动生成 XML

没错,MyBatis 官方也提供了生成器,能根据数据库表,自动生成实体类 (User) 和映射类 (UserMapper)!

英文官网:http://mybatis.org/generator/index.html
参考:https://juejin.cn/post/6844903982582743048

<!-- pom.xml -->
<plugins>
    <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.4.0</version>
        <configuration>
            <!--mybatis的代码生成器的配置文件-->
            <configurationFile>src/main/resources/mybatis-generator-config.xml</configurationFile>
            <!--允许覆盖生成的文件-->
            <overwrite>true</overwrite>
            <!--将当前pom的依赖项添加到生成器的类路径中-->
            <includeCompileDependencies>true</includeCompileDependencies>
        </configuration>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.25</version>
            </dependency>
        </dependencies>
    </plugin>
</plugins>
<!-- src/main/resources/mybatis-generator-config.xml -->
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <properties resource="application.properties"/>
    <context id="dsql" targetRuntime="MyBatis3DynamicSql">
        <commentGenerator>
            <!-- 忽略注释 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <jdbcConnection
                driverClass="${spring.datasource.driver-class-name}"
                        connectionURL="${spring.datasource.url}"
                        userId="${spring.datasource.username}"
                        password="${spring.datasource.password}">
            <!--高版本的 mysql-connector-java 需要设置 nullCatalogMeansCurrent=true-->
            <property name="nullCatalogMeansCurrent" value="true"/>
        </jdbcConnection>

        <javaModelGenerator targetPackage="com.lyh543.springbootdemo.entity"
                            targetProject="src/main/java"/>

        <javaClientGenerator targetPackage="com.lyh543.springbootdemo.mapper"
                             targetProject="src/main/java"
                             type="ANNOTATEDMAPPER"
        />

        <table tableName="users" domainObjectName="User"/>

    </context>
</generatorConfiguration>

目前 mybatis-generator-config.xml 只支持从 properties 读取数据,因此还要把 application.yaml 改回 application.properties(很多在线工具):

server.port=8080
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springtest?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

然后在 Idea 侧边栏运行 Maven - Plugins - mybatis-generator:generate 即可。