JDBC英文名为:Java Data Base Connectivity(Java数据库连接),是JAVA和数据库之间独立于数据库的链接标准的JAVA API。是Java与数据库的连接的桥梁或者插件,用Java代码就能操作数据库的增删改查、存储过程、事务等。
在JDK中对应的包为 java.sql
。里面就定义了大量的接口,不同类型的数据库,都可以通过实现此接口,编写适用于自己数据库的实现类。而不同的数据库厂商实现的这套标准,我们称为数据库驱动
。
使用 JDBC 连接数据库
这里使用 mysql 安装。
测试一下看是否成功:
一些链接错误问题解决
无法找到 driver
是因为没有导入 mysql-connector-java 包,去 https://static.runoob.com/download/mysql-connector-java-8.0.16.jar 下一个然后拖进 idea 项目目录后, 对它右键,最下面有个 ADD 导入即可。
server time zone value
Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value '�й���ʱ��' is
是时区设置不对,在 URL 后面加入这段字符串就行:
?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
DriverManager
位于 java.sql.DriverManager
, 是用来管理数据库驱动的。
可以通过调用getConnection()来进行数据库的链接:
public static void main(String[] args) throws SQLException {
try (Connection connection = DriverManager.getConnection("localhost","root", "he77495161");
Statement statement = connection.createStatement()) {
ResultSet set = statement.executeQuery("select * from table");
while(set.next()) {
System.out.println(set.getType());
}
}catch (SQLException e) {
e.printStackTrace();
}
}
方法原型:
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass())); //内部有实现
}
添加日志打印
执行的数据库操作日志会在控制台实时打印。
static {
DriverManager.setLogWriter(new PrintWriter(System.out)); //这里直接设定为控制台输出
}
Connection
Connection 是数据库的连接对象, 可以通过连接对象来创建一个Statement用于执行SQL语句。
Statement
之前使用了executeQuery()
方法来执行select
语句,此方法返回给我们一个ResultSet对象,查询得到的数据,就存放在ResultSet中。
Statement除了执行这样的DQL语句外,我们还可以使用executeUpdate()
方法来执行一个DML或是DDL语句,它会返回一个int类型,表示执行后受影响的行数,可以通过它来判断DML语句是否执行成功。
也可以通过excute()
来执行任意的SQL语句,它会返回一个boolean
来表示执行结果是一个ResultSet还是一个int,我们可以通过使用getResultSet()
或是getUpdateCount()
来获取。
上述都是执行单个语句,通过 addBatch
和 executeBatch
方法可以实现一次请求处理多条SQL语句,需要预先调用 statement.addBatch()
来添加一条一条语句,最后调用 executeBatch()
来批量执行。
执行 DML(操作) 操作
插入一行数据
比如在 Student(Sno, Sname, Sage) 表插入一行 (21164291, “李希”, 20):
int i = statement.executeUpdate("insert into Student VALUES (21164291, \"李希\", 20)");
System.out.println("生效了 " + i + " 行。");
删除一行数据
int i = statement.executeUpdate("delete from Student WHERE Sno = 211164295");
System.out.println("生效了 " + i + " 行。");
修改数据
nt i = statement.executeUpdate("update Student set Sname = '卢本伟' where Sno = 211164296");
System.out.println("生效了 " + i + " 行。");
执行 DQL(查询) 操作
通过 statement.execQuery()
可以返回一个 ReasultSet
对象,其内部组织结构类似于:
而我们可以通过调用 .next()
方法来让当前指针指向下一行。
访问当前行的某一列可以用 .getXXX()
方法获得对应的数据类型数据,也可以填对应的列名来获取。
ResultSet res = statement.executeQuery("SELECT * FROM Student");
while(res.next()) {
System.out.println(res.getInt(1) + " " + res.getString(2) + " " + res.getInt(3));
}
将查询结果读取到对象
手动调用构造方法
public static ArrayList<Student> StudentFactory() {
ArrayList<Student> resArr = new ArrayList<>();
try(Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();) {
ResultSet res = statement.executeQuery("Select * FROM Student");
while(res.next()) {
Student student = new Student(res.getInt(1), res.getString(2), res.getInt(3));
resArr.add(student);
}
}catch (SQLException e) {
e.printStackTrace();
}
if(resArr.size() >= 1)
return resArr;
return null;}
使用反射特性
使用反射的好处是,无论什么类型都可以通过我们的方法来进行实体类型映射。
public static <T> T convert(ResultSet set, Class<T> clazz) {
try {
Constructor<T> constructor = clazz.getConstructor(clazz.getConstructors()[0].getParameterTypes());
// 默认获取第一个构造方法
Class<?>[] param = constructor.getParameterTypes(); // 获取参数列表
Object[] object = new Object[param.length]; // 存放参数
for (int i = 0; i < param.length; i++) {
object[i] = set.getObject(i + 1);
if(object[i].getClass() != param[i])
throw new SQLException("错误的类型转换:" + object[i].getClass() + "->" +param[i]);
}
return constructor.newInstance(object);
} catch (ReflectiveOperationException | SQLException e) {
e.printStackTrace();
return null; }
}
后面会学到 Mybatis 持久化数据库,它也是基于反射特征,深度封装JDBC,直接从数据库中读取一个对象。
应用:模拟用户登录
一种方案是获取到用户输入的账号密码后执行SQLQuery去数据库中查询:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("输入账号:");
String username = sc.nextLine();
System.out.print("输入密码:");
String passwd = sc.nextLine();
try (Connection connection = DriverManager.getConnection(url, user, password);
Statement statemente = connection.createStatement();) {
ResultSet res = statemente.executeQuery("SELECT USERNAME FROM Account WHERE USERNAME = '" + username + "' AND PASSWORD = '" + passwd + "';");
boolean flag = true;
while(res.next()) {
System.out.println(res.getString(1) + " 登录成功! ");
flag = false;
}
if(flag) System.out.println("登录失败! 请检查你的用户名和密码。");
} catch (SQLException e) {
e.printStackTrace();
}
}
但这种方式很容易被利用SQL注入漏洞,通过密码输入 1111' or 1=1; --
就可以实现随意登录。
JAVA 对此提供了一种更安全的 SQL 查询函数 PreparedStatement
。
PreparedStatement
PreparedStatement
是增加了安全性的 Statement
,通过在声明时预编译要用的 SQL 命令并在后续加入参数来避免SQL注入漏洞。
try (Connection connection = DriverManager.getConnection(url, user, password);
PreparedStatement statemente = connection.prepareStatement("SELECT * FROM Account WHERE username = ? AND password = ?;");) {
// 用 ? 来占位
statemente.setString(1, username);
statemente.setString(2, passwd);
ResultSet res = statemente.executeQuery();
boolean flag = true;
while(res.next()) {
System.out.println(res.getString(1) + " 登录成功! ");
flag = false;
}
if(flag) System.out.println("登录失败! 请检查你的用户名和密码。");
} catch (SQLException e) {
e.printStackTrace();
}
通过将输入的参数用 ''
括起来,并且将里面的特殊字符自动执行转义,从而避免SQL注入。