2021.11.9 晴
JDBC介绍
我们之前学习MYSQL时,为了使用MYSQL服务,我们通常要使用客户端与MYSQL服务进行连接,然后才能输入SQL语句进行数据库的各种操作。客户端有命令行与图形界面2种。
但是在更多的环境下,由我们人直接操作数据是很低效的,比如双11这种业务场景下,一秒中往往要在库中生成上千万甚至几亿条数据,靠人来手工操作是不现实的,只能依赖于程序进行这种高并发的操作。
程序语言有多种,比如Java、Python、C/C++等,程序语言如果想执行SQL语句,也必须要先与数据库进行连接,数据库也有多种,比如MySQL、Oracle、SQL Server等。
不同的程序语言去连接不同的数据库,如果没有一个统一的标准或者规范,肯定是相当混乱的。Java语言对此的解决方案就是JDBC。
JDBC定义了一套规范标准,它对应的是各种接口与抽象类(通常对应java.sql包下面的各种类与接口),具体实现交给各数据库厂商去完成, MySQL的有自己的实现类并打成jar包发布,供程序开发人员使用;Oracle也有自己的实现jar包。
我们开发人员在使用的时候,要根据连接数据库的不同,去对应的官网上下载对应数据库版本与程序语言的数据库驱动(Java语言对应的是一个jar包)。(比如我们使用MySQL 5.1,就要去MySQL官网下载Java语言对应的jar包)
导包:
创建java项目
创建lib文件夹
把mysql-connector-java-5.1.38-bin.jar复制到lib中
右键 -> Build Path -> Add to Build Path
使用步骤:
package study;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 0 导包 , 创建lib文件夹,把jar包放进去,build path
*
* 1 加载驱动
* Class.forName("com.mysql.jdbc.Driver");
* 2 链接数据库
* DriverManager.getConnection("url","username","password);
* 3 创建语句传输对象,用于执行SQL
* createStatement()
* executeQuery();
* executeUpdate();
* 4 执行SQL
* 5 获取结果
* next()
* getxxx()
*
* 6 关闭资源
* close()
* @author 落华见樱
*
*/
public class Part01_base {
public static void main(String[] args) throws Exception {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
Connection conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/day03", "root", "root");
// 3 创建语句传输对象
String sql = "select * from student";
Statement stmt = conn.createStatement();
// 4 执行并获取结果
ResultSet rs = stmt.executeQuery(sql);
// next() : 判断下面是否还有数据,如果有就指向下一个数据
// 遍历获取数据
while (rs.next()) {
// 获取第几列的数据
// System.out.print( rs.getInt(1) );
// 根据列名获取数据
System.out.print(rs.getInt("sid")+" ");
System.out.print(rs.getString("sname")+" ");
System.out.println();
}
// 关闭资源
rs.close();
stmt.close();
conn.close();
}
}
package study;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Part02_base_02 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/day03", "root", "root");
// 3 创建语句传输对象
String sql = "select * from student";
stmt = conn.createStatement();
// 4 执行并获取结果
rs = stmt.executeQuery(sql);
// next() : 判断下面是否还有数据,如果有就指向下一个数据
// 遍历获取数据
while (rs.next()) {
// 获取第几列的数据
// System.out.print( rs.getInt(1) );
// 根据列名获取数据
System.out.print(rs.getInt("sid") + " ");
System.out.print(rs.getString("sname") + " ");
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
package study;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 增删改操作
* @author 落华见樱
*
*/
public class Part03_DML {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/day03", "root", "root");
// 3 SQL
// String sql = "insert into teacher (tid,tname)values(11,'张老师')";
// String sql = "update teacher set tid = 6 where tname = '张老师'";
String sql = "delete from teacher where tid = 6";
stmt = conn.createStatement();
// 执行SQL , 查询用executeQuery , 添加,删除,更新 都用 executeUpdate
// 返回值 是影响的 条数
int count = stmt.executeUpdate(sql);
System.out.println("操作成功,影响了 : "+count+" 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
PreparedStatement
添加或者更新的时候,尽量使用 PreparedStatement ,而不是使用Statement
Statement 和 PreparedStatement 的区别
Statement用于执行静态SQL语句,在执行的时候,必须指定一个事先准备好的SQL语句,并且相对不安全,会有SQL注入的风险
PreparedStatement是预编译的SQL语句对象,sql语句被预编译并保存在对象中, 被封装的sql语句中可以使用动态包含的参数 ? ,
在执行的时候,可以为?传递参数
使用PreparedStatement对象执行sql的时候,sql被数据库进行预编译和预解析,然后被放到缓冲区,
每当执行同一个PreparedStatement对象时,他就会被解析一次,但不会被再次编译 可以重复使用,可以减少编译次数,提高数据库性能
并且能够避免SQL注入,相对安全(把’ 单引号 使用 \ 转义,避免SQL注入 )
package study;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 预解析语句对象,编译解析一次,可以重复使用,提高数据库性能
*
* 并且相对安全,防止SQL注入
*
* @author 落华见樱
*
*/
public class Part04_PreparedStatement {
public static void main(String[] args) {
// addTeacher(7, "耿老师");
// addTeacher(8, "董老师");
addTeacher_01(6, "张老师");
}
public static void addTeacher_01(int tid, String tname) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/day03", "root", "root");
// 3 SQL
// PreparedStatement : 预解析语句对象
String sql = "insert into teacher (tid,tname)values(?,?)";
stmt = conn.prepareStatement(sql);
// 设置?的值
// 设置第一个问号的值 为 tid
stmt.setInt(1, tid);
stmt.setString(2, tname);
// 执行SQL , 查询用executeQuery , 添加,删除,更新 都用 executeUpdate
// 返回值 是影响的 条数
int count = stmt.executeUpdate();
System.out.println("操作成功,影响了 : " + count + " 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void addTeacher(int tid, String tname) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/day03", "root", "root");
// 3 SQL
String sql = "insert into teacher (tid,tname)values(" + tid + ",'"
+ tname + "')";
stmt = conn.createStatement();
// 执行SQL , 查询用executeQuery , 添加,删除,更新 都用 executeUpdate
// 返回值 是影响的 条数
int count = stmt.executeUpdate(sql);
System.out.println("操作成功,影响了 : " + count + " 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
代码优化:
主要针对2个方面进行改进封装。
以下的代码我们每个方法中都会重复一次,这是没必要的,因为不同的方法其实只是具体执行的SQL语句的内容不同,对于获取连接与释放资源,对应的逻辑是相同的。我们完全可以把这一段逻辑抽取出来,形成独立的类与方法,再在实际应用时调用对应的类和方法就可以了。
package study;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class Part05_TestDBUtil {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
// 3 创建语句传输对象
String sql = "select * from student";
stmt = conn.createStatement();
// 4 执行并获取结果
rs = stmt.executeQuery(sql);
// next() : 判断下面是否还有数据,如果有就指向下一个数据
// 遍历获取数据
while (rs.next()) {
// 获取第几列的数据
// System.out.print( rs.getInt(1) );
// 根据列名获取数据
System.out.print(rs.getInt("sid") + " ");
System.out.print(rs.getString("sname") + " ");
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(rs);
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
}
package study;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
/**
* Statement 有SQL注入风险
* @author 落华见樱
*
*/
public class Part06_TestSQLInto {
public static void main(String[] args) {
// update t_user set password= '1234' where username = 'admin'
// updatePassword("admin", "1234");
// update t_user set password= 'qwe' where username = '' or 'a'='a'
// 之所以出现SQL注入 是因为 值中出现了 单引号 把已有的单引号关闭了 使or 变成了或的关键字
// updatePassword("' or 'a'='a", "qwe");
// update t_user set password= 'qwe' where username = '\' or \'a\'=\'a'
// 可以通过转移符 解决问题
// updatePassword("\\' or \\'a\\'=\\'a", "qwe");
// update t_user set password= ? where username = ?
// 使用PreparedStatement 会自动把数据中的 ' 单引号 转义,同时就能解决SQL注入问题了
updatePassword_01("' or 'a'='a", "qwe");
}
public static void updatePassword_01(String username, String password) {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = DBUtil.getConnection();
String sql = "update t_user set password= ? where username = ?";
stmt = conn.prepareStatement(sql);
stmt.setString(1, password);
stmt.setString(2, username);
int count = stmt.executeUpdate();
System.out.println(sql);
System.out.println("修改成功,修改了 " + count + " 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
/**
* 根据用户名修改密码
*
* @param username
*/
public static void updatePassword(String username, String password) {
Connection conn = null;
Statement stmt = null;
try {
conn = DBUtil.getConnection();
String sql = "update t_user set password= '" + password
+ "' where username = '" + username + "'";
stmt = conn.createStatement();
int count = stmt.executeUpdate(sql);
System.out.println(sql);
System.out.println("修改成功,修改了 " + count + " 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
}
package study;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
public static Connection getConnection() throws SQLException,
ClassNotFoundException {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day04",
"root", "root");
}
// AutoCloseable 是 Connection,Statement,ResultSet的父类
public static void close(AutoCloseable obj){
if (obj != null) {
try {
obj.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//public static void close(Connection conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
//public static void close(Statement conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
//public static void close(ResultSet conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
//public static void close(PreparedStatement conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
//public static void closeConnection(Connection conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
//public static void closeStatement(Statement conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
//public static void closeResultSet(ResultSet conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
//public static void closePreparedStatement(PreparedStatement conn){
//if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//}
//}
}
进一步优化:
jdbc.config 文件内容如下:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day04
username=root
password=root
package study02;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
public class ConfigUtil {
private static Map<String, String> data = new HashMap<String, String>();
static {
try (BufferedReader br = new BufferedReader(new FileReader(
"./src/jdbc.config"));) {
String temp = null;
while ((temp = br.readLine()) != null) {
int index = temp.indexOf("=");
String key = temp.substring(0, index);
String value = temp.substring(index + 1);
data.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String get(String key) {
return data.get(key);
}
}
执行多条SQL语句:
package study03;
import java.sql.Connection;
import java.sql.Statement;
import study02.DBUtil;
/**
* 执行多条语句
* @author 落华见樱
*
*/
public class Part01_Batch {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = DBUtil.getConnection();
stmt = conn.createStatement();
stmt.addBatch("insert into test_jdbc (id,name) values(25,'stmt多条测试1')");
stmt.addBatch("insert into test_jdbc (id,name) values(22,'stmt多条测试2')");
stmt.addBatch("insert into test_jdbc (id,name) values(27,'stmt多条测试3')");
stmt.addBatch("insert into test_jdbc (id,name) values(26,'stmt多条测试4')");
stmt.executeBatch();
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
}
package study03;
import java.sql.Connection;
import java.sql.PreparedStatement;
import study02.DBUtil;
public class Part02_Batch_02 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement prst = null;
try {
conn = DBUtil.getConnection();
String sql = "insert into test_jdbc (id,name) values(?,?)";
prst = conn.prepareStatement(sql);
prst.setInt(1, 31);
prst.setString(2, "prst多条测试1");
prst.addBatch();
prst.setInt(1, 32);
prst.setString(2, "prst多条测试2");
prst.addBatch();
prst.setInt(1, 33);
prst.setString(2, "prst多条测试3");
prst.addBatch();
prst.executeBatch();
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(prst);
DBUtil.close(conn);
}
}
}
事务机制管理
Transaction事务机制管理
默认情况下,是执行一条SQL语句就保存一次,那么比如我需要 有三条数据同时成功同时失败,这个时候就需要开启事务机制了
如果开启事务机制,执行中发生问题,会回滚到没有操作之前,相当于什么也没有发生过
package study03;
import java.sql.Connection;
import java.sql.Statement;
import study02.DBUtil;
public class Part03_NoTransaction {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = DBUtil.getConnection();
stmt = conn.createStatement();
stmt.addBatch("insert into test_jdbc (id,name) values(25,'stmt多条测试1')");
// 下面这个语句出错
// 因为默认一条SQL 提交一次,所以 其他三条SQL可以添加成功
stmt.addBatch("insert into test_jdb123c (id,name) vaxxxasdes(22,'stmt多条测试2')");
stmt.addBatch("insert into test_jdbc (id,name) values(27,'stmt多条测试3')");
stmt.addBatch("insert into test_jdbc (id,name) values(26,'stmt多条测试4')");
stmt.executeBatch();
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
}
package study03;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import study02.DBUtil;
public class Part04_Transaction {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = DBUtil.getConnection();
// 关闭 自动提交
conn.setAutoCommit(false);
stmt = conn.createStatement();
stmt.addBatch("insert into test_jdbc (id,name) values(31,'stmt多条测试1')");
// 下面这个语句出错
// 因为默认一条SQL 提交一次,所以 其他三条SQL可以添加成功
stmt.addBatch("insert into test_jdbc (id,name) values(32,'stmt多条测试3')");
stmt.addBatch("insert into test_jdbc (id,name) values(33,'stmt多条测试4')");
stmt.executeBatch();
System.out.println("执行成功");
// 到这里说明没错,提交数据
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 到这里说明出错了,事务回滚
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 再打开自动提交
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
}
Properties优化硬代码
如上,我们项目开发完成测试通过后,通常是要打包发布给别人使用的,如果我们把一些配置信息写死到代码中(这种行为叫硬编码,hardcode),别人就无法修改了
比如别人使用的MySQL服务器对应的IP是10.0.0.1,端口号是9300,用户名是mysqluser、密码是123456。
那么我们只能改代码再重新打包发布,如果有多个用户,他们对应的配置信息都不同,那么我们要针对不同的用户打包发布多次。
以上显然是没必要的,因为我们开发的是程序,只要业务需求/逻辑不变,我们就无需多次打包发布。对此我们的解决方案是,尽量避免硬编码, 将数据与程序分离解耦,将配置信息存储到配置文件中,程序运行时读取配置文件,不同用户只需按自己的实际情况修改配置文件即可。
package study04;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DBUtil {
public static Connection getConnection() throws SQLException,
ClassNotFoundException, IOException {
Properties properties = PropertiesUtil.getProperties();
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 1 加载驱动
Class.forName(driver);
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
return DriverManager.getConnection(url, username, password);
}
// AutoCloseable 是 Connection,Statement,ResultSet的父类
public static void close(AutoCloseable obj) {
if (obj != null) {
try {
obj.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// public static void close(Connection conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void close(Statement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void close(ResultSet conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void close(PreparedStatement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closeConnection(Connection conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closeStatement(Statement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closeResultSet(ResultSet conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closePreparedStatement(PreparedStatement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
}
package study04;
import java.io.IOException;
import java.util.Properties;
public class PropertiesUtil {
private static Properties properties;
static{
properties = new Properties();
try {
properties.load(TestProperties.class.getClassLoader().getResourceAsStream(
"jdbc.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static Properties getProperties(){
return properties;
}
}
package study04;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class Part01 {
public static Connection getConnection() throws SQLException,
ClassNotFoundException, IOException {
Properties properties = new Properties();
properties.load(TestProperties.class.getClassLoader().getResourceAsStream(
"jdbc.properties"));
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 1 加载驱动
Class.forName(driver);
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
return DriverManager.getConnection(url, username, password);
}
// AutoCloseable 是 Connection,Statement,ResultSet的父类
public static void close(AutoCloseable obj) {
if (obj != null) {
try {
obj.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// public static void close(Connection conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void close(Statement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void close(ResultSet conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void close(PreparedStatement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closeConnection(Connection conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closeStatement(Statement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closeResultSet(ResultSet conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
// public static void closePreparedStatement(PreparedStatement conn){
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
}
jdbc.properties 文件内容如下
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day04
username=root
password=root
连接池
4.1 介绍
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
这项技术能明显提高对数据库操作的性能
4.2 应用场景
上面我们抽取了样板代码,将配置信息与代码分离解耦。JDBC编程的效率从一定程度上提高了,如果我们只是为了学习或者开发一些小的简单的项目,到这个地步其实就足够了。如果我们要想更上一层楼,支持如下场景:
场景一:电商网站如淘宝、京东每年都会有双11这种活动,同一时刻,会有上亿甚至上十亿的用户访问数据库(因为要生成订单等数据),如果我们还使用上面的思路,显然是捉襟见肘。
场景二:某服务器上除了运行MYSQL服务,还有其他一些服务比如WEB服务。我们知道,数据库连接的创建维持不只消耗我们客户端(个人PC)的系统资源(CPU、内存、IO设备),更消耗服务器的系统资源,而假如我们在周末时并不会去访问数据库,这时候服务器上依然还维持着一条空闲的连接,假设占用了2M内存,现在服务器上内存已经都被分配出去了,WEB服务却要求新申请1M内存,很显然,由于内存不足,WEB服务就无法正常运行了。
以上2个场景,我们上面的代码,是无法支持的。
连接池的引入,则主要解决了以上2类问题:
- 能给多用户分配连接或者给一个用户分配多个连接;
- 在适当的时候回收空闲连接以节省系统资源。
JDBC连接池有一个对应的接口javax.sql.DataSource。它也是一个标准一个规范,目前实现了这套规范的连接池产品主要有:
DBCP(MyBatis通常使用这个连接池)、C3P0(Hibernate通常使用这个连接池)、JNDI(Tomcat的默认连接池)。
我们使用DBCP来讲解。
DBCP内部提供了一个“池子”,程序启动的时候,先创建一些连接对象放到这个池子中,备用,当调用连接池的getConnection()方法时,就从池子取出连接对象分配给多个用户/线程。使用完毕调用close()方法时,DBCP重写了close方法,它并不真正关闭连接,而是返还到池子中,由DBCP自动管理池子中的连接对象,在适当的时候真正关闭这些连接。
优点 :
资源复用 : 数据库连接得到重用,避免了频繁创建释放链接引起的大量性能开销
在减少系统消耗的基础上,也增进了系统运行环境的平稳性
更快的系统响应速度 : 数据库连接池在初始化过程中,往往就已经创建了若干个数据库连接对象放到池中备用
这时,连接的初始化工作已完成,对于业务请求处理而言,直接利用现有的可用连接,避免了数据库连接初始化和释放过程的时间,从而缩减了系统整体的响应时间
统一的连接管理,避免数据库连接遗漏 : 在较为完备数据库连接池中,可以根据预先的连接占用超时设定,强制回收占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露情况