MySQL-数据库驱动和JDBC
JDBC
在学习JDBC之前,我们需要了解一个概念——驱动。
什么是驱动呢?
驱动是驱动程序的缩写,是指直接工作在各种硬件设备上的软件,其“驱动”这个名称也十分形象的指明了它的功能。正是通过驱动程序,各种硬件设备才能正常运行,达到既定的工作效果。例如声卡驱动、显卡驱动等等,所以数据库,也当然会有驱动。
我们的程序也会通过数据库驱动,和数据库“打交道”。
而SUN公司为了简化开发人员的(对数据库的统一)操作,提供了一个(Java操作数据库)规范,俗称JDBC,这些规范的实现由具体的厂商去做,对于开发人员来说,我们只需要掌握JDBC接口的操作即可!
Java中,要想操作数据库,首先需要导入Java默认就有的java.sql包和javax.sql包之外,还需要导入一个数据库驱动包mysql-connector-java-8.0.26,下载地址:https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.26
测试JDBC程序
首先还是通过一些例子来学习JDBC,首先创建一个测试数据库:
-- JDBC
CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci
USE jdbcStudy
DROP TABLE `users`
CREATE TABLE `users`(
`id` INT NOT NULL,
`name` VARCHAR(40),
`password` VARCHAR(40),
`email` VARCHAR(60),
`brith` DATE
);
INSERT INTO `users`(`id`,`name`,`password`,`email`,`brith`)
VALUES
(1,'张三','123456','127*****236@qq.com','1999-06-20'),
(2,'李四','123456','128*****351@qq.com','1997-08-30'),
(3,'王五','123456','126*****234@qq.com','1996-07-12');
然后导入我们刚刚下载的数据库驱动:新建一个project后,创建一个lib文件夹,然后驱动文件移动至该文件夹——

编写测试代码:
// 测试
public class JDBCFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1. 加载驱动(注意,MySQL8.0之前的版本的字符串为"com.mysql.jdbc.Driver")
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 用户信息和url(问号之后的参数分别是:支持中文,设置字符编码为utf8,设置安全链接)
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
String name = "root";
String password = "12345";
// 3. 连接成功,数据库对象 connection代表数据库
Connection connection = DriverManager.getConnection(url, name, password);
// 4. 执行SQL的对象 Statement是执行SQL的对象
Statement statement = connection.createStatement();
// 5. 执行SQL的对象去执行SQL,可能存在结果,查看返回结果
String sql = "SELECT * FROM `users`";
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("birth=" + resultSet.getObject("birth"));
}
// 6. 释放连接
resultSet.close();
statement.close();
connection.close();
}
}
如果上述代码报错,可以试着将useSSL修改为false,或者再url后面添加时区字段serverTimezone=UTC,如果还是报错,建议这边再检查一下代码或者是问万能的百度~
总结一下步骤:
1、加载驱动
2、连接数据库DriverManager
3、获得执行SQL对象Statement
4、获得返回的结果集
5、释放连接
扩展:为什么在第一个步骤中,Class不需要返回对象呢?首先我们观察一下整体的代码,并不需要返回这个Class对象,其次呢,在原本的写法中,是有一个DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());的写法,意思是注册驱动,但是我们查看Driver类的源代码可以发现,它执行了一个静态代码类,而该类里面已经执行了一次注册驱动,所以再写这串代码就显得有些多余了。
关于Connection:
Connection connection = DriverManager.getConnection(url, name, password);
// connection代表数据库,我们可以在这里进行一些数据库级别的操作,例如设置数据库自动提交,以及事务的提交和事务回滚等
connection.setAutoCommit(true);
connection.commit();
connection.rollback();
关于url:
// MySQL -- 端口号默认为3306
String url = "jdbc:mysql://localhost:3306/jdbcstudy?" +
"useUnicode=true&characterEncoding=utf8&useSSL=true";
//协议://主机地址:端口号/数据库名?参数1&参数2&参数3...
//Oralce -- 端口号默认为1521
// jdbc:oralce:thin:@localhost:1521:sid
关于Statement:
JDBC中的Statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查的语句即可~
statement.executeQuery(); // 执行查询操作,返回一个结果集
statement.execute(); // 执行任何SQL
statement.executeUpdate(); // 执行更新、插入、删除操作,返回一个受影响的行数
注意:最后一定要释放连接,否则会非常消耗资源,切记切记~
详解(使用创建工具类的方式将上述代码进行解耦)
properties文件:
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true
name = root
password = 12345
工具类:
public class JDBCUtils {
private static String driver;
private static String url;
private static String username;
private static String password;
static {
try {
InputStream resource = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(resource);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("name");
password = properties.getProperty("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 conn, Statement st, ResultSet rs) {
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
测试类:
public class TestInsert {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
// 获取连接
try {
connection = JDBCUtils.getConnection(); // 获取数据库的连接
statement = connection.createStatement(); // 获取SQL对象
String sql = "INSERT INTO `users`(`id`,`name`,`password`,`email`,`birth`)" +
"VALUES" +
"(4,'吴六','123456','127*****369@qq.com','1998-12-01')";
// 如果我们需要将SQL操作替换为删除、更改,只需要修改sql变量即可~
// String sql = "DELETE FROM `users` WHERE id = 4";
// String sql = "UPDATE `users` SET `name` = '张山' WHERE id = 1";
int i = statement.executeUpdate(sql);
// 要注意,查询操作需要用到的是excuteQuery()
if (i > 0) {
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.release(connection, statement, null);
}
}
}
SQL注入问题
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
public class SQLInjection {
public static void main(String[] args) throws SQLException {
login("李四", "123456");
}
public static void login(String name, String password) throws SQLException {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
// SELECT * FROM `users`WHERE `name` = '李四' AND `password` = '123456'
String sql = "SELECT * FROM `users`WHERE `name` = '" +
name + "' AND `password` = '"+ password + "'";
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()) {
System.out.println(resultSet.getString("name"));
System.out.println(resultSet.getString("password"));
}
}
JDBCUtils.release(connection, statement, resultSet);
}
例如上述代码,我们将“李四”替换成”‘or’ 1=1“这串字符,居然将表内的全部用户信息都输出了,这是咋回事呢?
仔细观察,我们就会发现这串字符并不是凭空而来,将这串字符代入SQL语句如下:
SELECT * FROM `users`WHERE `name` = ''or' 1=1' AND `password` = '123456'
由于在SQL中,存在一种拼接情况,将or拼接之后,原本的name="李四"就变成了name=空或者1=1,后者返回true,就可以通过查询,同理,我们也可以在password后也添加这串字符,发现连密码都不需要,用户数据全部都被输出了!这个致命的漏洞,会导致SQL的数据泄露!
但Java是强类型语言,存在着严格的类型转换规则,于是我们会将Statement对象替换为Prepared对象!
Prepared对象
相比于Statement对象,Prepared对象继承了Statement对象并且对其做了一些扩展,可以防止SQL注入,并且效率更好!
使用PreparedStatemnet对象进行插入操作:
public class PreparedInsert {
public static void main(String[] args) throws SQLException {
Connection connection = JDBCUtils.getConnection();
// 使用?占位符替代参数
String url = "INSERT INTO `users`(`id`,`name`,`password`,`email`,`birth`) " +
"VALUES" + "(?, ?, ?, ?, ?)";
PreparedStatement ps = connection.prepareStatement(url); // 预编译SQL, 先写SQL,然后不执行
// 手动赋值set(下标, 参数)
Date date = new Date();
ps.setInt(1, 4);
ps.setString(2, "吴六");
ps.setString(3, "123456");
ps.setString(4, "127*****369@qq.com");
// 注意:在setDate()方法中,需要的参数类型为java.sql.Date,而不是java.util.Date
ps.setDate(5, new java.sql.Date(new Date().getTime()));
ps.executeUpdate();
JDBCUtils.release(connection, ps, null);
}
}
删除:
String url = "DELETE FROM `users` WHERE id = ?"
PreparedStatement ps = connection.prepareStatement(url);
ps.setInt(1, 4);
ps.executeUpdate();
更新:
String url = "UPDATE `users` SET `name` = '?' WHERE id = ?"
PreparedStatement ps = connection.prepareStatement(url);
ps.setInt(1, "张山");
ps.setInt(2, 1);
ps.executeUpdate();
查询:
String url = "SELECT * FROM `users` WHERE `id` = ?";
PreparedStatement ps = connection.prepareStatement(url); // 预编译SQL, 先写SQL,然后不执行
ps.setInt(1, 1);
ResultSet resultSet = ps.executeQuery();
while(resultSet.next()) {
System.out.println(resultSet.getString("name"));
}
按照以上的逻辑,我们就可以对之前的登录业务进行修改,防止SQL注入:
public class SQLInjectionRewrite {
public static void main(String[] args) throws SQLException {
login("张山");
}
public static void login(String name) throws SQLException {
Connection connection = JDBCUtils.getConnection();
// 使用?占位符替代参数
String url = "SELECT * FROM `users` WHERE `name` = ?";
PreparedStatement ps = connection.prepareStatement(url); // 预编译SQL, 先写SQL,然后不执行
ps.setString(1, name);
ResultSet resultSet = ps.executeQuery();
while(resultSet.next()) {
System.out.println(resultSet.getString("name"));
System.out.println(resultSet.getString("password"));
}
JDBCUtils.release(connection, ps, null);
}
}
PreparedStatement防止SQL注入的本质是:将传递进来的参数,都当做字符,假设参数里面包括了转义字符,会被直接转义,” ‘or’ 1=1 “这串字符中的 ‘ 会被直接转义。‘## JDBC操作事务
public class TestTransaction01 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
// 关闭事务的自动提交(开启事务)
conn.setAutoCommit(false);
String sql01 = "update account set money = money - 100 where name = 'A'";
st = conn.prepareStatement(sql01);
st.executeUpdate();
// int x = 1/0; // 模拟事务失败
String sql02 = "update account set money = money + 100 where name = 'B'";
st = conn.prepareStatement(sql02);
st.executeUpdate();
conn.commit();
System.out.println("成功!");
} catch (SQLException e) {
// 如果事务失败,则默认回滚
e.printStackTrace();
} finally {
JDBCUtils.release(conn, st, rs);
}
}
}
JDBC操作事务
public class TestTransaction01 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
// 关闭事务的自动提交(开启事务)
conn.setAutoCommit(false);
String sql01 = "update account set money = money - 100 where name = 'A'";
st = conn.prepareStatement(sql01);
st.executeUpdate();
// int x = 1/0; // 模拟事务失败
String sql02 = "update account set money = money + 100 where name = 'B'";
st = conn.prepareStatement(sql02);
st.executeUpdate();
conn.commit();
System.out.println("成功!");
} catch (SQLException e) {
// 如果事务失败,则默认回滚
e.printStackTrace();
} finally {
JDBCUtils.release(conn, st, rs);
}
}
}
JDBC操作事务,大致可以分为如下几个步骤:
1、开启事务 conn = JDBCUtils.getConnection();
2、一组事务执行完毕,提交事务
3、可以在catch语句中显示定义回滚语句,不过事务执行失败的情况下,会默认回滚
数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
简单来说,就是数据库有:连接—>执行完毕—>释放,这一系列过程,但是连接–>释放这个过程非常浪费资源,于是就引人了池化技术来解决这类问题。
池化技术 (Pool) 是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。
换句话说,就是准备一些预先的资源,等数据库过来连接的时候连接预先准备好的。
我们还需要知道以下几个概念:最小连接数、最大连接数,等待超时…
编写连接池,实现一个接口DataSource,现如今在市面上对该技术的应用较为出名的有DBCP、C3P0以及Druid(阿里巴巴),使用了这些数据库连接池之后,我们在项目开发中就不需要编写数据库连接的代码了~’
更多推荐
所有评论(0)