驱动:驱动程序即添加到操作系统中的一小块代码,其中包含有关硬件设备的信息。有了此信息,计算机就可以与设备进行通信。驱动程序是硬件厂商根据操作系统编写的配置文件,可以说没有驱动程序,计算机中的硬件就无法工作。
种类:声卡驱动、显卡驱动、数据库驱动等。
注意:我们的程序会通过数据库驱动,来和数据库打交道,不同的数据库驱动由不同的厂商去做。
JDBC:Java数据库连接,Java Database Connectivity
定义:是 Java 语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
公司:JDBC 是 Sun 公司提供的。
对于开发人员来说,我们只需要掌握 JDBC 接口的操作。
Navicat
中创建测试数据库 jdbcStudy
。CREATE DATABASE `jdbcStudy` CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `jdbcStudy`;
CREATE TABLE IF NOT EXISTS `users`(
`id` INT PRIMARY KEY,
`name` VARCHAR(40),
`password` VARCHAR(40),
`email` VARCHAR(60),
birthday DATE
);
INSERT INTO `users`(`id`,`name`,`password`,`email`,`birthday`) VALUES
(1,'张三','123456','[email protected]','1980-12-04'),
(2,'李四','123456','[email protected]','1981-12-04'),
(3,'王五','123456','[email protected]','1979-12-04');
src
文件夹,建立子模块,在子项目的 main
中建立 java
以及 resources
文件夹,并标记功能。pom.xml
文件中导入依赖(mysql 数据库驱动包)。<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.16version>
dependency>
JdbcDemo01
类,编写 JDBC 程序。package com.Sun3285.jdbc;
import java.sql.*;
public class JdbcDemo01 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1. 加载驱动,固定写法
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. url 和用户信息,用 ? 连接参数
// useUnicode=true&characterEncoding=uft8&useSSL=true&serverTimezone=Asia/Shanghai 编码、字符集、使用安全的连接、时区
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "1142553864qq";
// 3. 连接数据库,connection 代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
// 4. 得到执行 SQL 的对象,即:statement 可以执行 SQL
Statement statement = connection.createStatement();
// 5. 编写要执行的 SQL
String sql = "SELECT * FROM `users`;";
// 6. 执行 SQL,得到的 resultSet 为结果集,封装了全部的查询出来的结果,链表的形式
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
System.out.println("id = " + resultSet.getObject("id"));
System.out.println("name = " + resultSet.getObject("name"));
System.out.println("password = " + resultSet.getObject("password"));
System.out.println("email = " + resultSet.getObject("email"));
System.out.println("birthday = " + resultSet.getObject("birthday"));
System.out.println("=========================");
}
// 7. 释放连接
resultSet.close();
statement.close();
connection.close();
}
}
步骤总结(具体看代码中注释):
- 加载驱动;
- url 和用户信息;
- 连接数据库;
- 得到执行 SQL 的对象;
- 编写要执行的 SQL;
- 执行 SQL;
- 释放连接。
String url = "协议://主机地址:端口号/数据库名?参数1&参数2&参数3&参数x";
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai";
// 协议:jdbc:mysql
// 主机地址:localhost
// 端口号:mysql 默认为 3306
// 数据库名:jdbcstudy
// 若干参数:useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai 编码、字符集、使用安全的连接、时区
connection
对象代表数据库,可以设置数据库自动提交(setAutoCommit),事务提交(commit)、回滚(rollback)等;statement
对象是执行类,可以执行 SQL;statement
对象可以执行的方法,例如statement.execute(); // 执行任何 SQL
statement.executeQuery(); // 执行查询操作,得到的 resultSet 为结果集,封装了全部的查询出来的结果,链表的形式
statement.executeUpdate(); // 执行更新、插入、删除操作,返回一个受影响的行数(int 类型)
resultSet
为结果集,封装了全部的查询出来的结果
getObject
、getString
、getInt
等方法;next
方法,指针移动到下一个数据(先移动,再判断);通过提取工具类,来改进 JDBC 程序,之后只写 SQL 语句和输出即可。
driver
、url
、username
以及 password
信息放到一个属性 properties
文件中存放;JdbcUtils
完成重复代码的封装;properties
文件存放信息时,没有引号,没有分号,如:driver=com.mysql.cj.jdbc.Driver
;JdbcUtils.java
TestDemo01.java
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username=root
password=1142553864qq
JdbcUtils.java
package com.Sun3285.utils;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static {
try {
// 通过反射获取类加载器,再转变为流
InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(is);
driver = (String) properties.get("driver");
url = (String) properties.get("url");
username = (String) properties.get("username");
password = (String) properties.get("password");
// 加载驱动
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
// 释放连接资源
public static void release(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
TestDemo.java
package com.Sun3285.jdbc;
import com.Sun3285.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestDemo {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
// 插入
String sql1 = "INSERT INTO `users` (`id`, `name`, `password`, `email`, `birthday`) VALUES" +
"(4, '健康', '1375380', '[email protected]', '1999-11-01'), " +
"(5, '快乐', '7894561', '[email protected]', '1998-2-3');";
int column1 = statement.executeUpdate(sql1);
if (column1 > 0) {
System.out.println("插入数据成功!");
}
// 修改
String sql2 = "UPDATE `users` SET `password` = '753159' WHERE `id` = 2 OR `birthday` = '1979-12-4';";
String sql3 = "UPDATE `users` SET `name` = '天天', `email` = '[email protected]' WHERE `id` = 1;";
int column2 = statement.executeUpdate(sql2);
int column3 = statement.executeUpdate(sql3);
if (column2 > 0 || column3 > 0) {
System.out.println("修改数据成功!被修改的数据有 " + (column2 + column3) + " 条。");
}
// 删除
String sql4 = "DELETE FROM `users` WHERE `name` = '李四';";
int column4 = statement.executeUpdate(sql4);
if (column4 > 0) {
System.out.println("删除数据成功!被删除的数据有 " + column4 + " 条。");
} else {
System.out.println("没有删除数据。");
}
// 查询
String sql5 = "SELECT `name` AS '姓名', `password` AS '密码', `birthday` AS '生日' FROM `users`;";
resultSet = statement.executeQuery(sql5);
System.out.println("查询到的信息如下:");
while (resultSet.next()) {
System.out.println("姓名:" + resultSet.getString("姓名"));
System.out.println("密码:" + resultSet.getString("密码"));
System.out.println("生日:" + resultSet.getString("生日"));
System.out.println("================");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(connection, statement, resultSet);
}
}
}
百度百科:
SQL 是操作数据库数据的结构化查询语言,网页的应用数据和后台数据库中的数据进行交互时会采用 SQL 。而 SQL 注入是将 Web 页面的原 URL、表单域或数据包输入的参数,修改拼接成 SQL 语句,传递给 Web 服务器,进而传给数据库服务器以执行数据库命令。
如果 Web 应用程序的开发人员对用户所输入的数据或 cookie 等内容不进行过滤或验证(即存在注入点)就直接传输给数据库,就可能导致拼接的 SQL 被执行,获取对数据库的信息以及提权,发生 SQL 注入攻击。
举例:模拟用户的登录:输入用户名和密码,来进行登录。
注意:
- SQL 中
AND
的优先级高于OR
;- 登录也可以写为:
login("", "' OR '1=1");
。
运行结果
代码
package com.Sun3285.jdbc;
import com.Sun3285.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class SQLInjection {
public static void main(String[] args) {
// login("健康", "1375380");
login("' OR '1=1", "' OR '1=1");
}
// 登录方法
public static void login(String username, String password) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
String sql = "SELECT * FROM `users` WHERE `name` = '" + username + "' AND `password` = '" + password + "';";
resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
resultSet.previous();
while (resultSet.next()) {
System.out.println("登录成功!");
System.out.println("姓名:" + resultSet.getString("name"));
System.out.println("密码:" + resultSet.getString("password"));
System.out.println("邮箱:" + resultSet.getString("email"));
System.out.println("生日:" + resultSet.getString("birthday"));
System.out.println("=========================");
}
} else {
System.out.println("登录失败,用户名或密码错误!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(connection, statement, resultSet);
}
}
}
PreparedStatement
继承了 Statement
可以防止 SQL 注入,并且效率更好。防止 SQL 注入的本质:把传递进来的参数当作字符,假设其中存在转义字符,会直接忽略。例如,单引号 '
会被直接转义,然后被直接忽略。
connection
后,进行编写 SQL、预编译、传递参数、执行;PreparedStatement
的 setDate
方法,第二个参数是 java.sql.Date
;?
占位符代替参数;?
的位置(从 1 开始),第二个参数是要传入的值;executeUpdate
或 executeQuery
方法执行时,不传入 SQL,在预编译时已经传入了 SQL;PreparedStatement
对象防止 SQL 注入 --> 节省资源,使用数据库连接池。以插入数据和查询数据为例:
在原先基础上,将 Statement
对象替换为 PreparedStatement
对象。
运行结果
PreparedDemo.java
package com.Sun3285.jdbc;
import com.Sun3285.utils.JdbcUtils;
import java.sql.*;
public class PreparedDemo {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = JdbcUtils.getConnection();
// 插入
String sql1 = "INSERT INTO `users` (`id`, `name`, `password`, `email`, `birthday`) VALUES (?,?,?,?,?);";
pst = con.prepareStatement(sql1);
pst.setInt(1, 6);
pst.setString(2, "李龙");
pst.setString(3, "147258");
pst.setString(4, "[email protected]");
pst.setDate(5, Date.valueOf("1989-2-3"));
int column1 = pst.executeUpdate();
if (column1 > 0) {
System.out.println("插入数据成功!共插入 " + column1 + " 条数据。");
} else {
System.out.println("没有插入数据!");
}
// 修改
String sql2 = "UPDATE `users` SET `birthday` = ? WHERE `name` = ? OR `id` = ?;";
pst = con.prepareStatement(sql2);
pst.setDate(1, Date.valueOf("2007-9-1"));
pst.setString(2, "天天");
pst.setInt(3, 6);
int column2 = pst.executeUpdate();
if (column2 > 0) {
System.out.println("修改数据成功!共修改 " + column2 + " 条数据。");
} else {
System.out.println("没有修改数据!");
}
// 删除
String sql3 = "DELETE FROM `users` WHERE `email` = ?;";
pst = con.prepareStatement(sql3);
pst.setString(1, "[email protected]");
int column3 = pst.executeUpdate();
if (column3 > 0) {
System.out.println("删除数据成功!共删除 " + column3 + " 条数据。");
} else {
System.out.println("没有删除数据!");
}
// 查询
String sql4 = "SELECT `name` AS '姓名', `birthday` AS '生日' FROM `users` WHERE `id` > ? AND `password` = ?; ";
pst = con.prepareStatement(sql4);
pst.setInt(1, 3);
pst.setString(2, "222222");
rs = pst.executeQuery();
if (rs.next()) {
System.out.println("查询成功!结果为:");
rs.previous();
while (rs.next()) {
System.out.println("姓名:" + rs.getString("姓名"));
System.out.println("生日:" + rs.getString("生日"));
System.out.println("========================");
}
} else {
System.out.println("没有查询到数据!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(con, pst, rs);
}
}
}
package com.Sun3285.jdbc;
import com.Sun3285.utils.JdbcUtils;
import java.sql.*;
public class SQLInjection2 {
public static void main(String[] args) {
// login("健康", "1375380");
login("", "' OR '1=1");
}
// 登录方法
public static void login(String username, String password) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = JdbcUtils.getConnection();
String sql = "SELECT * FROM `users` WHERE `name` = ? AND `password` = ?;";
pst = con.prepareStatement(sql);
pst.setString(1, username);
pst.setString(2, password);
rs = pst.executeQuery();
if (rs.next()) {
rs.previous();
while (rs.next()) {
System.out.println("登录成功!");
System.out.println("姓名:" + rs.getString("name"));
System.out.println("密码:" + rs.getString("password"));
System.out.println("邮箱:" + rs.getString("email"));
System.out.println("生日:" + rs.getString("birthday"));
System.out.println("=========================");
}
} else {
System.out.println("登录失败,用户名或密码错误!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(con, pst, rs);
}
}
}
和 Navicat
一样,IDEA
也可以连接数据库,来管理数据库。
注意:
- 在这一步中,进行参数设置以及连接数据库;
- 在此之前需要加载数据库驱动,即在
pom.xml
配置文件中导入依赖(mysql 数据库驱动包)见 一、3;- 这里的
URL
需要和之前一致,可以不写数据库;- 测试连接,成功后,选择
OK
。
注意:如果由于 MySQL 的版本或驱动不对,而出现错误,可以在这个地方修改(一般不要改)。
之前已经学过用 SQL 语言实现一个事务(如下图),现在用 Java 来实现一个事务。
JDBC 操作事务的步骤:
con.setAutoCommit(false);
;con.commit()
;catch
语句中显示地定义回滚语句:con.rollback();
。注意点:
db.properties
中的各个参数,例如 url
中的数据库是否是要操作的数据库;con.rollback()
可以不写,因为如果事务执行失败,会默认回滚;举例:实现转账业务。
package com.Sun3285.jdbc;
import com.Sun3285.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TransactionDemo01 {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = JdbcUtils.getConnection();
// 关闭数据库自动提交,自动会开启事务
con.setAutoCommit(false);
String sql1 = "update `account` set `money` = `money` - 2000 where `name` = ?;";
pst = con.prepareStatement(sql1);
pst.setString(1, "Sun3285");
pst.executeUpdate();
String sql2 = "update `account` set `money` = `money` + 2000 where `name` = ?;";
pst = con.prepareStatement(sql2);
pst.setString(1, "Sun1478");
pst.executeUpdate();
// 提交事务
con.commit();
System.out.println("转账成功!");
} catch (SQLException e) {
try {
// 出现异常,回滚事务
con.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.release(con, pst, rs);
}
}
}
背景:每次连接数据库和释放连接资源都十分浪费系统资源。
解决方法:编写一个数据库连接池,需要实现 DataSource
接口(如下图)。
比较知名的 DataSource
接口的实现类(开源的数据源实现,会用就好)有:DBCP、C3P0、Druid 等。
注意:
- 当使用了数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了;
- 无论使用什么数据源,本质都是一样的,
DataSource
接口不会变,方法就不会变。
所用到的池化技术:准备一些预先的资源,过来就连接预先准备好的。例如:常用连接数为 10 个,可以设置最小连接数为 10,最大连接数为 15(业务最高承载上限)。若 15 个业务都在进行,其他的业务将会排队等待,还可以设置等待超时为 5 秒,若业务的排队等待时间超过 5 秒,则不会继续等待。
注意:
BasicDataSource
实现了 DataSource
接口,代替了之前连接数据库的步骤;dataSource
时使用了工厂模式,用来创建 BasicDataSource
类的对象,可以理解为将属性文件中的各个参数传给 BasicDataSource
类,然后来创建数据源。步骤:
pom.xml
中导入依赖:commons-dbcp
以及 commons-pool
<dependency>
<groupId>commons-dbcpgroupId>
<artifactId>commons-dbcpartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>commons-poolgroupId>
<artifactId>commons-poolartifactId>
<version>1.6version>
dependency>
driverClassName
的名字不能改变,固定的)#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=1142553864qq
#
initialSize=10
#最大连接数量
maxActive=50
#
maxIdle=20
#
minIdle=5
#
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8;useSSL=true;serverTimezone=Asia/Shanghai
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
注意:
使用数据库连接池 ComboPooledDataSource
实现了 DataSource
接口,代替了之前连接数据库的步骤;
在编写 xml
配置文件时,可以指定多个数据源,还需要注意 &
表示与的意思;
在获取数据源 dataSource
时,不写参数代表获取默认的数据源,有参数代表指定的数据源。
步骤:
pom.xml
中导入依赖:c3p0
以及 mchange-commons-java
<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>0.9.1.2version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>mchange-commons-javaartifactId>
<version>0.2.15version>
dependency>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghaiproperty>
<property name="user">rootproperty>
<property name="password">1142553864qqproperty>
<property name="acquireIncrement">5property>
<property name="initialPoolSize">10property>
<property name="minPoolSize">5property>
<property name="maxPoolSize">20property>
default-config>
<named-config name="MySQL">
<property name="driverClass">com.mysql.cj.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghaiproperty>
<property name="user">rootproperty>
<property name="password">1142553864qqproperty>
<property name="acquireIncrement">5property>
<property name="initialPoolSize">10property>
<property name="minPoolSize">5property>
<property name="maxPoolSize">20property>
named-config>
c3p0-config>
Add Framework Support...
,打开后选择 Maven
项目。程序执行时,会先找到 main 函数(相当于入口),从 main 函数开始执行,当 main 函数执行完毕退出后,整个程序就结束。
代码块分为静态代码块(类加载时调用,一个类的静态代码块只会执行一次)和普通初始化代码块(在每次创建对象时都会调用一次),先执行静态代码块,后执行普通初始化代码块。可以用静态代码块来一次性对静态成员变量进行赋值。
当使用 JDBC 进行查询 executeQuery
操作时,若没有查询到数据,得到的 resultSet
结果集不为 null
,因为此时结果集中还包括了字段名。因此,要判断是否查询到数据,不能用 if (resultSet != null)
,而应该用 if (resultSet.next())
,如果结果为 false
,则说明没有查询到数据。
理解结果集 resultSet
的 next
方法:最初指针被置于第一行之前(即 beforeFirst
),当执行 next
方法时,指针会移动到下一行,此时若有下一行,结果为 true
,再执行 getString
等方法时,得到指针所指的这一行数据;若没有下一行,结果为 false
。与 next
相反的另一个方法为 previous
。
SQL 中 AND
的优先级高于 OR
。
java.sql.Date
和 java.util.Date
是不一样的包,前者是数据库用的,后者是 Java 用的,前者继承了后者。
properties
类型和 xml
类型的配置文件都可以放在资源目录 resources
下。