Java 原生提供了
java.sql.*
包用于与数据库交互,然而只是接口而已,你还需要导入对应数据库的驱动库实现,譬如mysql-connector-java.jar
,什么?你说你不知道?叫你教授微信发你。
导入 MySQL 数据库驱动
如果已有 mysql-connector-java.jar
库,可以在 IED 里面导入,在此不再展开,自行搜索。
如果没有,如果你是 Maven 项目,在 pom.xml
里面的 <dependencies>
标签内插入:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>版本</version>
</denpendency>
JDBC API 入门
java.sql.*
有几个很重要的类,分别是:
类 | 作用 |
---|---|
DriverManager | 驱动管理器,负责注册驱动和创建数据库连接 |
Connection | 数据库连接,负责开启数据库查询删除等操作 |
Statement 和 PreparedStatement | 负责操作 SQL 语句 |
ResultSet | 储存数据库SELECT 语句查询的结果 |
SQLException | Statement 和PreparedStatement 所有的方法都会强制抛出此异常,必须得用 try_catch 语句捕获 |
注册 MySQL 驱动类
DriverManager
的常用方法:
// 手动注册驱动类
static void registerDriver(java.sql.Driver) throws SQLException
// 手动卸载驱动类
static void deregisterDriver(java.sql.Driver) throws SQLException
// 根据 URL, 数据库用户名及其密码,开启 Connection 类,此很重要,是我们开启查询删除插入操作的把柄
static Connection getConnection(String url, // URL 填 "jdbc:mysql://localhost:3306/数据库名称"
String username,
String password) throws SQLException
接下来是我们注册驱动类,
旧版:
Class.forName("com.mysql.jdbc.Driver"); // 用类加载器加载驱动类
DriverManager.registerDriver(com.mysql.jdbc.Driver.class); // 用驱动加载器挂载
新版:
Class.forName("com.mysql.jdbc.Driver"); // 自动挂载,只需类加载器加载就行了
然后再用DriverManager.getConnection
方法获取Connection
类,这个类是我们打开增删改查的把柄
总之:
protected static final Connection CONN;
// 全局静态代码块,只加载一次,以免多次加载带来额外的I/O开销
static {
try {
Class.forName("com.mysql.jdbc.Driver"); // 注册 MySQL 驱动
// 在实际开发中不要犯密码写进代码的低级错误,这里只是举例子
CONN = DriverManager.getConnection
("jdbc:mysql://localhost:8080/数据库名字", "用户名", "密码");
} catch (ClasNotFoundException | SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
小提示:不建议将密码写进源代码,一旦源代码泄漏,安全风险很大。正确的做法是将用户名和密码分开写成一个配置文件,然后设计一个专门的类来读取。感兴趣可以了解
c3p0
连接池库,它自带了一个配置文件系统
配置文件
写配置文件jdbc.properties
# 连接数据库 URL
url=jdbc:mysql://localhost:3306/数据库名称
# 数据库驱动类全类名
driver=com.mysql.jdbc.Driver
username=root
password=toor
读取配置文件的类
import java.io.IOException;
import java.util.Properties;
public class PropertiesLoader {
/**
* 配置文件主体
*/
private static final Properties props;
/**
* 默认的配置文件名
*/
private static final String DEFAULT_NAME = "jdbc.properties";
static {
try {
props.load(getClassLoader().getResourceAsStream(DEFAULT_NAME));
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
public static Properties getProperties() { return props; }
}
protected static final Connection CONN;
static {
try {
final Properties cfg = PropertiesLoader.getProperties();
final String driver = cfg.get("driver");
final String url = cfg.get("url");
final String username = cfg.get("username");
final String password = cfg.get("password");
Class.forName(driver);
CONN = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException | SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
小提示:给业务上永远不需要改变的变量加上
final
关键字,有助于编译器优化。给方法加上final
关键字可以让 JVM 虚拟机内联优化。官方用专门的注解类@ForcedInline
来强制给方法内联
写用户类
这里我用了lombok
库的注解来自动生成 getter 和 setter
,毕竟手动编写简直浪费时间,懒惰是程序员第一美德
import java.util.Objects;
import lombok.*;
@Getter
@Setter
public class User {
public static final int ADMIN_ROLE = 0;
public static final int USER_ROLE = 1;
public static final String DEFAULT_NAME = "匿名";
private long id;
private String name;
private String password;
private int role;
public User(long id, String name, String password, int role) {
this.id = id;
this.name = Objects.requireNonNullElse(name, DEFAULT_NAME);
this.password = Objects.requireNonNull(password);
this.role = role;
}
public User(long id, String name, String password) {
this(id, name, password, USER_ROLE);
}
public User(String name, String password) {
this(-1, name, password);
}
}
数据库增删查改操作
Connection
类常用方法
// 创建语句操作
Statement createStatement();
// 根据SQL语句新建句柄
PreparedStatement prepareStatement(String sqlString);
void close();
// 回滚事务
void rollback()
PreparedStatement
类常用方法
// 设置参数
void setByte(int paramIndex, byte x);
void setShort(int paramIndex, short x);
void setInt(int paramIndex, int x);
void setLong(int paramIndex, long x);
void setString(int paramIndex, String x);
// 执行查询操作,返回结果集合
ResultSet executeQuery() throws SQLException;
// 执行删除或插入操作
boolean execute() throws SQLException;
void close() // 关闭,一定要使用这个方法!
void closeOnCompletion();
以下是PreparedStatement
查找的例子
public User findUserById(long id) throws SQLException {
try (var pstmt = CONN.prepareStatement("SELECT * FROM users WHERE id=?")) {
pstmt.setLong(1, id);
final ResultSet r = pstmt.executeQuery();
return r.next() ? new User(id, rs.getString("username"), rs.getString("password")) : null;
}
return null;
}
删除
public void removeUser(long id) throws SQLException {
try (var pstmt = CONN.prepareStatement("DELETE FROM user WHERE id=?")) {
pstmt.setLong(1, id);
pstmt.execute();
}
}
设计 DAO 层
首先,我们要明确 DAO (Database Access Operation) 的设计模式,模块化编程,代码写成一坨只会增加开发 & 维护难度,毕竟论起写💩山代码没人比我更在行。
[接口] UserDatabaseAccessor (定义了一系列方法)
|
+---- [类] MySQLDatabaseAccessor (MySQL 数据库实现)
|
+---- [类] OracleDatabaseAccessor (Oracle 数据库实现)
|
+---- [类] RedisDatabaseAccessor (Radis 数据库实现)
定义 DAO 接口
public interface UserDataBaseAccessor {
public void add(User user); // 添加用户
public void remove(long id); // 根据用户id移除用户
public default void remove(User user) {
remove(user.getId());
}
public List<User> queryAll(); // 返回所有用户的列表
public List<User> queryAll(String name);
public User find(long id); // 通过id查找指定用户
}
写 MySQL 数据库 DAO 实现
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MySQLDatabaseAccessor implements UserDatabaseAccessor {
/**
* 静态加载,就加载一次,重复加载浪费I/O
*/
protected static final Connection CONN;
static {
try {
Class.forName("com.mysql.jdbc.Driver"); // 注册 MySQL 驱动
// 在实际开发中不要犯密码写进代码的低级错误,这里只是举例子
CONN = DriverManager.getConnection
("jdbc:mysql://localhost:8080/数据库名字", "用户名", "密码");
} catch (ClasNotFoundException | SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public List<User> queryAll() throws SQLException {
return newStatement("SELECT * FROM users").getResultList();
}
@Override
public List<User> queryAll(String name) throws SQLException {
return newStatement("SELECT * FROM users").getResultStream()
.filter(user -> user.getName().equals(name))
.toList();
}
@Override
public User find(long id) throws SQLException {
return newStatement("SELECT * FROM users WHERE id=?")
.setParameter(1, id)
.getResultList().get(0);
}
public void remove(long id) throws SQLException {
newStatement("DELETE FROM user WHERE id=?")
.setParameter(1, if)
.execute();
}
@Override
public void add(User user) {
newStatement("INSERT INTO users(username, password, role) VALUES(?, ?, ?)")
.setParameter(1, user.getName())
.setParameter(2, user.getPassword())
.setParameter(3, user.getRole())
.execute();
}
protected Query newStatement(String sqlString) throws SQLException {
return new Query(CONN.prepareStatement(sqlString));
}
// 链式构造工具类
static final class Query {
private final PreparedStatement pstmt;
Query(PreparedStatement pstmt) { this.pstmt = pstmt; }
public void execute() throws SQLException {
pstmt.execute();
}
public List<User> getResultList() throws SQLException {
final ResultSet rs = pstmt.executeQuery();
List<User> result = new ArrayList<>();
while (rs.next()) {
long id. = rs.getLong("id");
String name = rs.getString("username");
String password = rs.getString("password");
int role. = rs.getInt("role");
result.add(new User(id, name, password, role));
}
return result.isEmpty() ? null : result;
}
public Stream<User> getResultStream() throws SQLException {
List<User> l;
return (l = getResultList()) == null ? null : l;
}
public Query setParameter(int parameterIndex, long x) throws SQLException {
pstmt.setInt(parameterIndex, x);
return this;
}
public Query setParameter(int parameterIndex, String x) throws SQLException {
pstmt.setString(parameterIndex, x);
return this;
}
// TODO: ...
}
}
public final class Accessors {
private Accessors() { return new AssertionError(); }
public static UserDatabaseAccessor newMySQLPool() {
return new MySQLUserDatabaseAccessor();
}
public static UserDatabaseAccessor newOraclePool() {
return new OracleUserDatabaseAccessor();
}
}