1、什么是JDBC
Java数据库连接(Java Database Connectivity
,简称JDBC
)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了增删改查数据库中数据的方法。
注:我们通常说的JDBC
是面向关系型数据库的。
JDBC操作数据库的过程如下图所示:
由图可见:SUN公司为了简化Java 程序员对数据库的操作,制定了两套接口。
- 一套是JDBC API,供程序员操作数据库使用;
- 另一套是JDBC驱动API,供数据库厂商将自家的数据库插入到驱动管理器中使用。
因此,对于程序员而言,只需掌握JDBC API即可。
2、JDBC依赖包与核心类
2.1、依赖包
注:JDBC依赖包我们既可以手动下载导入,也可以使用Maven自动下载导入(重点掌握)。
2.2、核心类
2.1.1、DriverManager
DriverManager
类:
2.1.2、Connection
Connection
类的对象:
- 是一种特定数据库的连接(会话);
- 主要用于创建Statement(SQL语句执行器)对象和数据库操作。
注:一个Connection
(连接器)对象可以创建多个Statement
(SQL语句执行器)对象。
2.1.3、Statement(重点)
Statement
类:用于向数据库发送静态 SQL 语句,并返回 SQL 语句的执行结果。
Statement
对象的executeQuery
方法用于向数据库发送SQL查询语句,返回查询结果的ResultSet
(结果集)对象;
Statement
对象的executeUpdate
方法用于向数据库发送SQL增加、删除、修改语句,返回数据库受影响的行数。
注:
Statement
对象只有在执行SQL查询语句时,才会返回ResultSet
(结果集)对象。
Statement
对象在执行SQL更新、插入和删除语句时,返回的是受影响的行数。
- 一个
Statement
(SQL语句执行器)最多只能获取一个结果集。
(1)CRUD操作——create
使用executeUpdate(String sql)
方法完成数据添加操作。示例操作:
1 2 3 4 5 6 7 8 9
| Statement statement = connection.createStatement();
String sql = "INSERT INTO 表名[(字段名1,字段名2,……)] VALUES(字段1的值,字段2的值,……)[,(字段1的值,字段2的值,……),……]"; int num = statement.executeUpdate(sql); if(num > 0){ System.out.println("插入成功"); }
|
(2)CRUD操作——delete
使用executeUpdate(String sql)
方法完成数据删除操作。示例操作:
1 2 3 4 5 6 7 8 9
| Statement statement = connection.createStatement();
String sql = "DELETE FROM 表名 WHERE 删除条件"; int num = statement.executeUpdate(sql); if(num > 0){ System.out.println("删除成功"); }
|
(3)CRUD操作——update
使用executeUpdate(String sql)
方法完成数据修改操作。示例操作:
1 2 3 4 5 6 7 8 9
| Statement statement = connection.createStatement();
String sql = "UPDATE 表名 SET 字段名1 = 字段1的新值[, 字段名2 = 字段2的新值,……] where 修改条件"; int num = statement.executeUpdate(sql); if(num > 0){ System.out.println("修改成功"); }
|
(4)CRUD操作——read
使用executeQuery(String sql)
方法完成数据查询操作。示例操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Statement statement = connection.createStatement();
String sql = "SELECT `id`,`name`,`sex` FROM `student` WHERE `name` LIKE '李%'"; 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("sex=" + resultSet.getObject("sex")); System.out.println("----------------------------------------"); }
|
2.1.4、PreparedStatement(重点)
详见:6、PreparedStatement对象详解
。
2.1.5、ResultSet
ResultSet
(结果集)类的对象:用来存放SQL查询语句的查询结果。
这样,我们便可以在应用层使用ResultSet
类的方法对SQL查询语句的查询结果进行处理了。
3、如何使用JDBC操作数据库
3.1、JDBC操作数据库的步骤
- 加载驱动;
- 设置连接信息(URL、用户名和密码);
- 连接数据库,返回Connection(连接器)对象;
- Connection(连接器)对象创建Statement(SQL语句执行器)对象;
- Statement(SQL语句执行器)对象执行SQL语句,获取结果集;(核心步骤)
- 处理结果集;
- 释放连接资源。
附:URL格式
注:MySQL
默认端口号为3306
。
3.2、示例(第一个JDBC程序)
(1)新建一个JDBC测试项目
(2)在数据库中新建一个测试表
测试(student)
表如下图所示:
![student表 student表]()
(3)导入数据库驱动包
- 下载数据库驱动包
mysql-connector-java-8.0.29.jar
,并将它移动到lib
目录下:
注:此时jar包并未导入到项目中,jar包不能展开。
注:此时jar包便导入到项目中,jar包便能自然展开了。如下图所示:
(4)编写测试代码
- 在src目录下新建一个
com.atang.test
包:
- 在
com.atang.test
包中新建一个jdbcDemo
类:
![新建类 新建类]()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.atang.test;
import java.sql.*;
public class jdbcDemo { public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/new_db?useUnicode=true&characterEncoding=utf8&useSSL=true"; String userName = "root"; String password = "123456";
Connection connection = DriverManager.getConnection(url, userName, password);
Statement statement = connection.createStatement(); String sql = "SELECT `id`,`name`,`sex` FROM `student` WHERE `name` LIKE '李%'"; 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("sex=" + resultSet.getObject("sex")); System.out.println("----------------------------------------"); } resultSet.close(); statement.close(); connection.close(); } }
|
执行结果为:
4、简单封装
为了更加方便地使用JDBC操作数据库,避免代码的重复书写,我们可以在上述JDBC示例程序的基础上,对其进行简单封装。项目目录如下图所示:
4.1、新建配置文件
- 在
com.atang
目录下新建一个db.properties
配置文件(用于配置数据库连接信息)。
db.properties
文件:
1 2 3 4
| driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/new_db?useUnicode=true&characterEncoding=utf8&useSSL=true userName = root password = 123456
|
4.2、封装JDBC工具类
- 先在src目录下新建一个
com.atang.jdbc1.utils
包;
- 然后,在utils目录下新建一个
JdbcUtils
类,用于封装JDBC工具。
JdbcUtils.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.atang.jdbc1.utils;
import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties;
public class JdbcUtils {
private static String dirver = null; private static String url = null; private static String userName = null; private static String password = null;
static { try { InputStream resourceAsStream = JdbcUtils.class.getClassLoader().getResourceAsStream("./com/atang/db.properties"); Properties properties = new Properties(); properties.load(resourceAsStream);
dirver = properties.getProperty("driver"); url = properties.getProperty("url"); userName = properties.getProperty("userName"); password = properties.getProperty("password");
Class.forName(dirver); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException 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) throws SQLException { if (rs != null){ rs.close(); } if (st != null){ st.close(); } if (conn != null){ conn.close(); } } }
|
4.3、封裝测试
(1)CRUD操作——create功能测试
- 在
jdbc1
目录下新建一个TestInsert
类,用于测试JDBC新增数据功能。
TestInsert.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
public class TestInsert { public static void main(String[] args) throws SQLException {
Connection conn = null; Statement st = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "INSERT INTO `student`(`name`,`sex`,`birthday`,`address`) VALUES('韩梅梅','女','2000-2-03','湖北武汉')"; int i = st.executeUpdate(sql); if(i > 0){ System.out.println("插入数据成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,st,rs); } } }
|
运行后程序后,数据库数据变为:
(2)CRUD操作——delete功能测试
- 在
jdbc1
目录下新建一个TestDelete
类,用于测试JDBC删除数据功能。
TestDelete.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
public class TestDelete { public static void main(String[] args) throws SQLException {
Connection conn = null; Statement st = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "DELETE FROM `student` WHERE `name` = '韩梅梅'"; int i = st.executeUpdate(sql); if(i > 0){ System.out.println("删除数据成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,st,rs); } } }
|
运行后程序后,数据库数据变为:
(3)CRUD操作——update功能测试
- 在
jdbc1
目录下新建一个TestUpdate
类,用于测试JDBC修改数据功能。
TestUpdate.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
public class TestUpdate { public static void main(String[] args) throws SQLException {
Connection conn = null; Statement st = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "UPDATE `student` SET `birthday` = '1990-05-03' WHERE `name` = '张三'"; int i = st.executeUpdate(sql); if(i > 0){ System.out.println("修改数据成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,st,rs); } } }
|
运行后程序后,数据库数据变为:
(4)CRUD操作——read功能测试
- 在
jdbc1
目录下新建一个TestSelect
类,用于测试JDBC查询数据功能。
TestSelect.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
public class TestSelect { public static void main(String[] args) throws SQLException {
Connection conn = null; Statement st = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "SELECT `id`,`name`,`address` FROM `student` WHERE `address` LIKE '%武汉%'"; rs = st.executeQuery(sql);
while (rs.next()){ System.out.println("id=" + rs.getObject("id")); System.out.println("name=" + rs.getObject("name")); System.out.println("address=" + rs.getObject("address")); System.out.println("----------------------------------------"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,st,rs); } } }
|
运行程序后,查询结果为:
5、SQL注入问题
5.1、什么是SQL注入
SQL注入是指:web应用程序对用户输入数据的合法性没有判断或过滤不严,导致攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
注:
5.2、典型示例
![users表 users表]()
- 在
jdbc1
目录下新建一个SqlInjectProblem
类,用于演示SQL注入问题。
SqlInjectProblem.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
public class SqlInjectProblem { public static void main(String[] args) throws SQLException { loginInfoDetect("张三","1234567"); }
public static void loginInfoDetect(String userName, String password) throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "SELECT * FROM `users` WHERE `name` = '" + userName+ "' AND `password` = '" + password +"'"; rs = st.executeQuery(sql); if (rs.isBeforeFirst()){ System.out.println("用户信息验证成功!"); while (rs.next()){ System.out.println("name=" + rs.getObject("name")); System.out.println("password=" + rs.getObject("password")); System.out.println("----------------------------------------"); } } else { System.out.println("用户信息验证失败!"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,st,rs); } } }
|
此时用户信息验证失败,运行结果为:
若在main函数中将校验登录的用户的信息改为:
1 2 3
| public static void main(String[] args) throws SQLException { loginInfoDetect("张三","123456"); }
|
则用户信息验证成功,运行结果为:
1 2 3
| public static void main(String[] args) throws SQLException { loginInfoDetect(" 'or '1 = 1"," 'or '1 = 1"); }
|
重新运行该程序,运行结果为:
由此我们可以发现:即使我们没有输入正确的用户名和密码,我们仍然可以盗取所有的用户信息。
出现这一现象的主要问题在于:黑客利用了1 = 1
恒成立的特性,通过特殊的字符拼接对SQL语句拼接过程中存在的漏洞进行了攻击,进而盗取到所有的用户信息,这一现象便是典型的“SQL注入问题”。
6、PreparedStatement对象详解
为了防止出现SQL注入问题,JAVA对Statement
类进行了改进,为我们提供了一个PreparedStatement
(预备语句)类。
6.1、Statement
对象 VS PreparedStatement
对象
(1)创建Statement(SQL语句执行器)对象;
(2)将待执行的SQL语句放入字符串中;
(3)直接执行SQL语句。
PreparedStatement
对象执行SQL语句的步骤:
(1)将待执行的SQL语句放入字符串中,但其中不填具体值,而是使用?
(占位符)占位;
(2)预编译SQL(不执行);
(3)依次给?
(占位符)赋值;
(4)执行SQL语句。
注:
- 与
Statement
类相比,PreparedStatement
类更安全,效率更高。
- 实际项目中,我们应当使用
PreparedStatement
对象执行SQL语句。
6.2、使用PreparedStatement
对JDBC工具类进行封装测试
前面,我们介绍了如何使用Statement
对象对JDBC工具类进行封装测试。接下来,我们将介绍如何使用PreparedStatement
对象对JDBC工具类进行封装测试。
(1)CRUD操作——create功能测试
- 在
jdbc1
目录下新建一个TestInsertPre
类,用于测试JDBC新增数据功能。
TestInsertPre.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils; import java.sql.*;
public class TestInsertPre { public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement preSt = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); String sql = "INSERT INTO `student`(`name`,`sex`,`birthday`,`address`) VALUES(?,?,?,?)"; preSt = conn.prepareStatement(sql); preSt.setString(1,"张三强"); preSt.setString(2,"男"); preSt.setDate(3,new java.sql.Date(new java.util.Date().getTime())); preSt.setString(4,"安徽省合肥市"); int i = preSt.executeUpdate();
if(i > 0){ System.out.println("插入数据成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,preSt,rs); } } }
|
运行后程序后,数据库数据变为:
(2)CRUD操作——delete功能测试
- 在
jdbc1
目录下新建一个TestDeletePre
类,用于测试JDBC删除数据功能。
TestDeletePre.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils; import java.sql.*;
public class TestDeletePre { public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement preSt = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); String sql = "DELETE FROM `student` WHERE `name` = ?"; preSt = conn.prepareStatement(sql); preSt.setString(1,"李娜"); int i = preSt.executeUpdate();
if(i > 0){ System.out.println("删除数据成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,preSt,rs); } } }
|
运行后程序后,数据库数据变为:
(3)CRUD操作——update功能测试
- 在
jdbc1
目录下新建一个TestUpdatePre
类,用于测试JDBC修改数据功能。
TestUpdatePre.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils; import java.sql.*;
public class TestUpdatePre { public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement preSt = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); String sql = "UPDATE `student` SET `address` = ? WHERE `name` = ?"; preSt = conn.prepareStatement(sql); preSt.setString(1,"湖北省武汉市东湖新技术开发区"); preSt.setString(2,"张三"); int i = preSt.executeUpdate();
if(i > 0){ System.out.println("修改数据成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,preSt,rs); } } }
|
运行后程序后,数据库数据变为:
(4)CRUD操作——read功能测试
- 在
jdbc1
目录下新建一个TestSelectPre
类,用于测试JDBC查询数据功能。
TestSelectPre.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils; import java.sql.*;
public class TestSelectPre { public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement preSt = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); String sql = "SELECT `id`,`name`,`address` FROM `student` WHERE `address` LIKE ?"; preSt = conn.prepareStatement(sql); preSt.setString(1,"%武汉%"); rs = preSt.executeQuery();
while (rs.next()){ System.out.println("id=" + rs.getObject("id")); System.out.println("name=" + rs.getObject("name")); System.out.println("address=" + rs.getObject("address")); System.out.println("----------------------------------------"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,preSt,rs); } } }
|
运行程序后,查询结果为:
7、SQL注入问题的解决
接下来,我们在5.2、(SQL注入问题)典型示例
的基础上,演示PreparedStatement
对象是如何解决SQL注入问题的。
- 在
jdbc1
目录下新建一个SqlCannotInject
类,用于演示SQL注入问题。
SqlCannotInject.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package com.atang.jdbc1;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.*;
public class SqlCannotInject { public static void main(String[] args) throws SQLException { loginInfoDetect("张三","123456"); }
public static void loginInfoDetect(String userName, String password) throws SQLException { Connection conn = null; PreparedStatement preSt = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); String sql = "SELECT * FROM `users` WHERE `name` = ? AND `password` = ?"; preSt = conn.prepareStatement(sql); preSt.setString(1,userName); preSt.setString(2,password); rs = preSt.executeQuery();
if (rs.isBeforeFirst()){ System.out.println("用户信息验证成功!"); while (rs.next()){ System.out.println("name=" + rs.getObject("name")); System.out.println("password=" + rs.getObject("password")); System.out.println("----------------------------------------"); } } else { System.out.println("用户信息验证失败!"); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JdbcUtils.release(conn,preSt,rs); } } }
|
此时用户信息验证成功,运行结果为:
1 2 3
| public static void main(String[] args) throws SQLException { loginInfoDetect(" 'or '1 = 1"," 'or '1 = 1"); }
|
重新运行该程序,运行结果为:
![SQL注入问题解决测试验证失败 SQL注入问题解决测试验证失败]()
由此我们可以发现:SQL注入问题被成功解决了。
此时,黑客将无法在不知道用户名和密码的情况下,盗取数据库数据了。
8、JDBC处理事务
前面章节已经介绍事务的基本概念、ACID特性和SQL语句处理事务的过程,这里不再赘述。
JDBC处理事务与SQL语句处理事务的步骤基本一致,只是实现方式不同罢了。两者的区别主要包括:
- JDBC禁用事务自动提交功能的同时,事务会自动开启,不用再像SQL语句处理事务一样手动开启。
- JDBC若处理事务失败,则自动回滚。(但为了逻辑清晰,一般不建议省略手动回滚代码!)
- JDBC处理事务结束后会自动恢复事务自动提交功能。
例:已知account表的数据如下图所示,初始状态下:张三有100元、李四有50元。现在张三要转账20元给李四。
![事务前 事务前]()
- 在
com.atang.test
包中新建一个TransactionDemo
类,用来演示JDBC处理事务的过程。
TransactionDemo.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.atang.test;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException;
public class TransactionDemo { public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement preSt = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); conn.setAutoCommit(false);
String sql1 = "UPDATE `account` SET `money` = `money` - 20 WHERE `name` = ?"; preSt = conn.prepareStatement(sql1); preSt.setString(1,"张三"); preSt.executeUpdate();
String sql2 = "UPDATE `account` SET `money` = `money` + 20 WHERE `name` = ?"; preSt = conn.prepareStatement(sql2); preSt.setString(1,"李四"); preSt.executeUpdate(); conn.commit(); System.out.println("事务处理成功!"); } catch (Exception e) { conn.rollback(); System.out.println("事务处理失败!"); } finally { JdbcUtils.release(conn,preSt,rs); } } }
|
运行该程序,运行结果为:
![事务处理成功 事务处理成功]()
此时事务处理成功,数据库数据变为:
![事务处理成功后数据 事务处理成功后数据]()
此时TransactionDemo.java
文件变为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.atang.test;
import com.atang.jdbc1.utils.JdbcUtils;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException;
public class TransactionDemo { public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement preSt = null; ResultSet rs = null;
try { conn = JdbcUtils.getConnection(); conn.setAutoCommit(false);
String sql1 = "UPDATE `account` SET `money` = `money` - 20 WHERE `name` = ?"; preSt = conn.prepareStatement(sql1); preSt.setString(1,"张三"); preSt.executeUpdate();
int x = 1/0;
String sql2 = "UPDATE `account` SET `money` = `money` + 20 WHERE `name` = ?"; preSt = conn.prepareStatement(sql2); preSt.setString(1,"李四"); preSt.executeUpdate(); conn.commit(); System.out.println("事务处理成功!"); } catch (Exception e) { conn.rollback(); System.out.println("事务处理失败!"); } finally { JdbcUtils.release(conn,preSt,rs); } } }
|
运行该程序,运行结果为:
![事务处理失败 事务处理失败]()
此时事务处理失败,数据库数据回滚到事务执行前的状态。
![事务处理失败后数据 事务处理失败后数据]()
9、数据库连接池(池化技术)
9.1、为什么使用数据库连接池
频繁地进行数据库连接和释放是十分浪费系统资源的,这一点在多用户的网页应用程序中体现得尤为突出。数据库连接池正是针对这个问题提出来的。
9.2、什么是数据库连接池
数据库连接池(Database Connection Pooling)是指在程序初始化时就创建一定数量的数据库连接对象并将其保存在一块内存区中,它允许应用程序重复使用同一个现有的数据库连接,而不是重新建立一个;释放空闲时间超过最大空闲时间的数据库连接以避免因为没有释放数据库连接而引起的数据库连接遗漏。
即在程序初始化的时候就创建一定数量的数据库连接,用完可以放回去,下一个再接着用,通过配置连接池的参数来控制连接池中的初始连接数、最小连接、最大连接、最大空闲时间等参数来保证访问数据库的数量在一定可控制的范围类,防止系统崩溃,提升用户体验。
![数据库连接池 数据库连接池]()
这项技术能明显提高对数据库操作的性能。
9.3、连接池的实现(数据源)
编写数据库连接池只需要实现一个DataSource
接口,通常我们把DataSource
的实现,即连接池的实现,按其英文含义称之为数据源。
数据源中都包含了数据库连接池的实现,DataSource
接口由数据库驱动程序供应商实现。实际项目开发中我们不需要编写连接数据库代码,直接从数据源获得数据库的连接即可。
9.4、Java中常用的连接池
目前,Java中开源的数据库连接池主要有:
DBCP
(Database Connection Pool)是一个依赖Jakarta commons-pool
对象池机制的数据库连接池。DBCP
可能是使用最多的开源连接池,Tomcat
的数据源使用的就是DBCP
。
C3P0
是另外一个开源的数据库连接池,在业界也比较有名。使用它的开源项目有Hibernate、Spring
等。
Druid
是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0
、DBCP
等 DB 池的优点,同时加入了日志监控。它在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource
等。
注:无论使用什么数据源,它们的本质都是实现DataSource
接口。
(本讲完,系列博文持续更新中…… )
![阿汤笔迹微信公众平台 阿汤笔迹微信公众平台]()
关注“阿汤笔迹” 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2022/08/31/jdbcInDetail
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。