# 专业名词
说到 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 实现自己的接口,就可以连接应用程序和数据库了。
目前几乎所有 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 原生对象和关系数据库的数据建立关系的一种思想。
纯 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 的优势有:
- (相较于 JDBC)自动将 ResultSet 转化为 Java 对象
- (相较于 Hibernate 和 JPA)执行原生 SQL,不会有效率影响
- (相较于 JDBC)自动生成查询函数,语法简洁
# Mybatis 的三种风格
Mybatis 也有三种风格:
- 注解风格,将 SQL 写在接口方法的注解里;
- XML 风格,将 SQL 语句单独放到一个 XML 文件里;
- 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
即可。