前期准备
JDBC : java数据库连接,
使用它时,我们需要在工程中导入.jar包,
例如 : mysql-connector-java-5.1.40-bin.jar
我们可以在工程下创建一个lib目录, lib目录与src目录同级别,
将所有导入的.jar包都可以放入lib包中, 从而进行统一管理
1. JDBC实现步骤
1.注册数据库驱动
2.获取数据库连接
3.创建传输器
4.传输sql语句, 并返回结果
5.遍历返回结果
6.关闭资源
1.1 JDBC连接基础版
import com.mysql.jdbc.Driver;
import java.sql.*;
/**
* JDBC实现步骤
* 1.注册数据库驱动
* 2.获取数据库连接
* 3.创建传输器
* 4.传输sql语句, 并返回结果
* 5.遍历返回结果
* 6.关闭对象,释放资源
*/
public class JDBCDemo1 {
public static void main(String[] args) throws SQLException {
//1.注册数据库驱动(暂时抛出异常)
DriverManager.registerDriver(new Driver());
//2.获取数据库连接,并接收返回值
//连接方式一
//Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school?user=root&password=123456");
//连接方式二
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "123456");
//3.创建传输器,并接收返回值
Statement stat = conn.createStatement();
//4.传输sql查询语句,并接收返回值
ResultSet rs = stat.executeQuery("select * from student");
//5.遍历返回结果
while (rs.next()) {//将游标从列名那一行开始往下一行依次移动,判断下一行是否有数据
System.out.print(rs.getString(1));//通过每一列的下标找到每一行对应位置的元素,数据库中列下标从1开始
System.out.println(rs.getString("student_name"));//通过列名找到每一行对应位置的元素
}
//6.关闭对象,释放资源(后创建对象的先关闭)
rs.close();
rs = null;
stat.close();
stat=null;
conn.close();
conn=null;
}
}
1.2 改进一 : 异常处理
IDEA异常处理快捷键 :
选中所有异常的部分, 然后点击快捷键 ctrl + alt + T
//异常处理后
import com.mysql.jdbc.Driver;
import java.sql.*;
public class JDBCDemo2 {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//1.注册数据库驱动(暂时抛出异常)
DriverManager.registerDriver(new Driver());
//2.获取数据库连接,并接收返回值
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "123456");
//3.创建传输器,并接收返回值
stat = conn.createStatement();
//4.传输sql查询语句,并接收返回值
rs = stat.executeQuery("select * from student");
//5.遍历返回结果
while (rs.next()) {//将游标从列名那一行开始往下一行依次移动,判断下一行是否有数据
System.out.print(rs.getString(1));//通过每一列的下标找到每一行对应位置的元素,数据库中列下标从1开始
System.out.println(rs.getString("student_name"));//通过列名找到每一行对应位置的元素
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//6.关闭对象(后创建对象的先关闭)
//处理异常
if (rs != null) {//为了防止rs为空,进而出现空指针异常
try {
//关闭对象
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {//无论对象是否正常关闭,都要释放资源,以便垃圾回收
//释放资源
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
conn = null;
}
}
}
}
}
1.3 改进二 : 改用反射来加载数据库驱动
-
注册数据库驱动所用的Driver类的源码
//注册数据库驱动所用的Driver类的源码 package com.mysql.jdbc; import java.sql.DriverManager; import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }
-
我们自己写的注册数据库驱动的代码
DriverManager.registerDriver(new Driver());
-
我们发现Driver类的静态代码块中已经有了我们自己所写的代码, 而我们又重新写了一遍, 相当于我们执行了两次注册数据库驱动的代码, 代码重复冗余.
-
我们只需要加载Driver类, 而不创建它的对象, 这样就可以成功注册数据库驱动
-
我们用反射来加载Driver类
Class.forName("com.mysql.jdbc.Driver");//处理异常 ClassNotFoundException
//改进二 : 改用反射来加载数据库驱动
import com.mysql.jdbc.Driver;
import java.sql.*;
public class JDBCDemo3 {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//1.注册数据库驱动
//DriverManager.registerDriver(new Driver());//多次注册数据库驱动,浪费资源
//使用反射来加载类只会注册数据库驱动一次
Class.forName("com.mysql.jdbc.Driver");//处理异常 ClassNotFoundException
//2.获取数据库连接,并接收返回值
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "123456");
//3.创建传输器,并接收返回值
stat = conn.createStatement();
//4.传输sql查询语句,并接收返回值
rs = stat.executeQuery("select * from student");
//5.遍历返回结果
while (rs.next()) {//将游标从列名那一行开始往下一行依次移动,判断下一行是否有数据
System.out.print(rs.getString(1));//通过每一列的下标找到每一行对应位置的元素,数据库中列下标从1开始
System.out.println(rs.getString("student_name"));//通过列名找到每一行对应位置的元素
}
} catch (SQLException | ClassNotFoundException throwables) {
throwables.printStackTrace();
} finally {
//处理异常
if (rs != null) {//为了避免空指针异常
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
conn = null;
}
}
}
}
}
1.4 改进三 : 将JDBC封装成一个工具类
为了避免每次都需要重写连接数据库的代码, 我们可以将重复使用的代码封装成一个工具类
//JDBC工具类
import java.sql.*;
//该类的所有方法设置为静态方法的目的,是为了能直接使用类名调用该类的方法,不需要单独创建对象
public class JDBCUtils {
//私有化构造方法,禁止其他类创建本类的对象
private JDBCUtils() {
}
//1.加载数据库驱动 2.连接数据库
public static Connection getConnection() throws ClassNotFoundException, SQLException {
//由于我们不知道具体使用我们这个方法的类会遇到那些异常,
//因此我们抛出异常,让具体使用该方法的类去自己单独处理异常
Class.forName("com.mysql.jdbc.Driver");//抛出异常 ClassNotFoundException
return DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "123456");//抛出异常 SQLException
}
//6.关闭对象
public static void close(Connection conn, Statement stat, ResultSet rs) {
//处理异常
if (rs != null) {//为了避免空指针异常
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
conn = null;
}
}
}
}
测试JDBC工具类
//测试JDBC工具类
class JdbcTest {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//1.加载数据库驱动 2.连接数据库
conn = JDBCUtils.getConnection();//处理异常
//3.创建传输器
stat = conn.createStatement();
//4.通过sql语句对数据库进行操作
rs = stat.executeQuery("select * from student");
//5.遍历返回结果
while (rs.next()) {
System.out.print(rs.getString(1));//通过每一列的下标找到每一行对应位置的元素,数据库中列下标从1开始
System.out.println(rs.getString("student_name"));//通过列名找到每一行对应位置的元素
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//6.关闭对象,释放资源
JDBCUtils.close(conn, stat, rs);
}
}
}
1.5 改进四 : 利用properties配置文件来避免代码后期的频繁改动
Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "123456");
上面代码中的内容容易经常改动, 为了避免我们写的代码被频繁的修改, 我们将里面的数据放到 XX.properties 配置文件中, 后期需要修改的话, 我们只需要修改配置文件中的内容即可, 对我们的代码没有影响
- 在工程中的src目录中新建文件, 命名为db.properties, 里面的内容如下
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/school user=root password=123456
//改进四 : 利用properties配置文件来避免代码后期的频繁改动
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils2 {
//私有化构造方法,禁止其他类创建本类的对象
private JDBCUtils2() {
}
//创建Properties类对象
//静态属性(对象)--- 全局统一 --- 只需要创建一次对象就可以读取配置文件中的内容
private static Properties p = new Properties();
//静态代码块---保证程序先读取配置文件内容
static {
//静态代码块中不能抛出异常, 只能捕获代码块
try {
/*方法一
JDBCUtils2.class---获取当前类的字节码对象
getClassLoader()---获取类加载器
getResource("src目录下的文件名称")---获取当前工程的src目录
getPath()---把URL类型转换成String类型*/
//p.load(new FileInputStream(new File(JDBCUtils2.class.getClassLoader().getResource("db.properties").getPath())));
//方法二
p.load(JDBCUtils2.class.getClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
//1.加载数据库驱动 2.连接数据库
public static Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName(p.getProperty("driver"));//抛出异常 ClassNotFoundException
return DriverManager.getConnection(p.getProperty("url"), p.getProperty("user"), p.getProperty("password"));//抛出异常 SQLException
}
//6.关闭对象
public static void close(Connection conn, Statement stat, ResultSet rs) {
//处理异常
if (rs != null) {//为了避免空指针异常
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
conn = null;
}
}
}
}
测试类
class JdbcTest2 {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//1.加载数据库驱动 2.连接数据库
conn = JDBCUtils2.getConnection();//处理异常
//3.创建传输器
stat = conn.createStatement();
//4.通过sql语句对数据库进行操作
rs = stat.executeQuery("select * from student");
//5.遍历返回结果
while (rs.next()) {
System.out.print(rs.getString(1));//通过每一列的下标找到每一行对应位置的元素,数据库中列下标从1开始
System.out.println(rs.getString("student_name"));//通过列名找到每一行对应位置的元素
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//6.关闭对象,释放资源
JDBCUtils2.close(conn, stat, rs);
}
}
}
2. SQL注入攻击
Statement
由于sql语句中的参数部分是由用户手动输入的, 并且我们通过用户给定的参数与sql语句的主干部分进行拼接. 用户可以通过输入sql语句中的关键字来改变sql的语义, 从而执行一些别的操作.
2.1 用Java实现在数据库中创建表,并在表中添加数据
//java实现在数据库中创建表,并在表中添加数据
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class InsertDemo {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils2.getConnection();//处理异常
//创建传输器
stat = conn.createStatement();
stat.execute("drop table t");//先删除,再创建
stat.executeUpdate("create table t(name varchar(20),password varchar(20))");
stat.executeUpdate("insert into t values('lili','123456')");
stat.executeUpdate("insert into t values('Jack','123456')");
stat.executeUpdate("insert into t values('Tom','123456')");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils2.close(conn, stat, rs);
}
}
}
2.2 模拟用户登入
通过将用户输入的用户名和密码与数据库中的数据进行匹配, 如果匹配成功, 则登入成功, 否则, 登入失败
判断方法一
通过遍历每一行的数据与用户输入的数据进行比较, 如果匹配成功, 则登入成功, 否则, 登入失败
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
//模拟用户登入
public class LoginDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名 : ");
String name = sc.nextLine();
System.out.println("请输入密码 : ");
String password = sc.nextLine();
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils2.getConnection();//处理异常
//创建传输器
stat = conn.createStatement();
rs = stat.executeQuery("select * from t");
//通过遍历每一行的数据与用户输入的数据进行比较
boolean b = false;
while (rs.next()) {
if (rs.getString("name").equals(name) && rs.getString("password").equals(password)) {
b = true;
}
}
if (b) {
System.out.println("登入成功");
} else {
System.out.println("登录失败");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils2.close(conn, stat, rs);
}
}
}
判断方法二
通过sql语句直接查询满足用户输入的数据的内容, 如果能查到数据, 代表登入成功, 否则, 登入失败
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
//模拟用户登入
public class LoginDemo2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名 : ");
String name = sc.nextLine();
System.out.println("请输入密码 : ");
String password = sc.nextLine();
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils2.getConnection();//处理异常
//创建传输器
stat = conn.createStatement();
//通过sql语句直接查询满足用户输入的数据的内容
//通过字符串拼接来形成完整的sql语句
//注意此处sql语句中的单引号
rs = stat.executeQuery("select * from t where name='" + name + "' and password='" + password + "'");
if(rs.next()){//如果下一行有数据,那么登入成功
System.out.println("登入成功!");
}else{
System.out.println("登入失败");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils2.close(conn, stat, rs);
}
}
}
用户通过输入sql语句中的关键字 # , 进入使得原sql语句忽略 # 后面的代码,
rs = stat.executeQuery("select * from t where name='" + name + "' and password='" + password + "'");
原sql语句的意思从原来的根据用户名与密码来查询数据, 变成了现在的只通过用户名来查询数据, 跳过了输入密码的部分, 这就是sql注入攻击所引起的安全问题
2.3 解决sql注入攻击问题
我们原来使用Statement来创建传输器, 通过传输器来传送sql语句
现在我们换成PreparedStatement来传送sql语句
- PreparedStatement(避免sql注入攻击)
- 带有预编译的传输器
- 先将sql的主干语句部分发送给数据库服务器, 主干语句中的参数位置用 ? 来预留.
- 主干语句发送到数据库服务器之后就会变成机器码, 这个机器码无法修改.
- 以纯文本的形式来接收用户发送的参数数据, 如果参数中包含关键字也会被认为是一段文本内容.
- PreparedStatement是Statement类的子类
import java.sql.*;
import java.util.Scanner;
//模拟用户登入
public class LoginDemo3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名 : ");
String name = sc.nextLine();
System.out.println("请输入密码 : ");
String password = sc.nextLine();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils2.getConnection();//处理异常
//发送sql主干语句,参数部分用?预留位置
ps = conn.prepareStatement("select * from t where name=? and password=?");
//第一个参数代表选择第几个问号(?), 第二个参数是传入的数据(即问号位置所传入的具体的值)
ps.setString(1, name);
ps.setString(2, password);
//执行sql语句
rs = ps.executeQuery();
if (rs.next()) {//如果下一行有数据,那么登入成功
System.out.println("登入成功!");
} else {
System.out.println("登入失败");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//此处ps是PreparedStatement类的对象, 而此处要求传入的对象应该是Statement类的对象,
//此处没有出错, 是因为PreparedStatement是Statement的子类
JDBCUtils2.close(conn, ps, rs);
}
}
}
3. 批处理
将多条sql语句加载好, 达到一定数量后统一发送给数据库,
避免了系统频繁的一条一条的向数据库发送sql语句,
这样一批一批的向数据库发送大大减少了系统与数据库交互的次数,节省了与数据库交互所花费的时间
需求 : 以50条sql语句为一批, 一共向数据库发送510条sql语句, 用批处理实现
3.1 Statement实现批处理
addBatch() : 向当前批处理中添加(加载)一条sql语句
executeBatch() : 执行批处理
clearBatch() : 清空批处理, 释放批处理中的空间
//以50条sql语句为一批, 一共向数据库发送510条sql语句, 用批处理实现
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class StatementBatch {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils2.getConnection();
stat = conn.createStatement();
System.out.println("开始执行批处理");
//以50条sql语句为一批, 一共向数据库发送510条sql语句, 用批处理实现
for (int i = 1; i <= 510; i++) {
stat.addBatch("insert into t values('" + i + "','123456')");//向当前批处理中添加(加载)一条sql语句
if (i % 50 == 0) {//每加载50条sql语句,执行一次批处理
stat.executeBatch();//执行批处理, 即将加载好的一批sql语句一起发送给数据库
stat.clearBatch();//清空批处理, 清空所有加载到批处理中的SQL语句
System.out.println("执行了" + i / 50 + "次批处理");
}
}
System.out.println("批处理执行完毕");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils2.close(conn, stat, rs);
}
}
}
关键代码分析 :
for (int i = 1; i <= 510; i++) {
stat.addBatch("insert into t values('" + i + "','123456')");//向当前批处理中添加(加载)一条sql语句
if (i % 50 == 0) {//每加载50条sql语句,执行一次批处理
stat.executeBatch();//执行批处理, 即将加载好的一批sql语句一起发送给数据库
stat.clearBatch();//清空批处理, 清空所有加载到批处理中的SQL语句
System.out.println("执行了" + i / 50 + "次批处理");
}
}
如果将上面代码中的 stat.clearBatch(); 语句放在 stat.executeBatch(); 前面, 那么将不会往数据库中发送sql语句. 原因是 clearBatch() 方法会清空所有已经加载到批处理中的sql语句, 轮到 executeBatch() 方法执行的时候, 已经没有需要发送的sql语句了.
问题分析 :
上面的代码改进, 上面的代码只能向数据库发送500条sql语句, 无法满足我们的向数据库发送510条sql语句的需求, 因为剩余的10条sql语句不足50条sql语句, 因此没有被执行, 具体的修改方法请看下面 PreparedStatement 实现的批处理(Stetement参照下面的代码修改即可)
3.2 PreparedStatement实现批处理
addBatch() : 向当前批处理中添加(加载)一条sql语句
executeBatch() : 执行批处理
clearBatch() : 清空批处理, 释放批处理中的空间
上面问题的改进 :
//在for循环外部添加下面的操作
//如果剩余的一些sql语句不够50条语句,那么执行下面的代码
ps.executeBatch();//执行之前没有执行的批处理
ps.clearBatch();//清空批出六
System.out.println("批处理执行完毕");
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PreparedStatementBatch {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils2.getConnection();
ps = conn.prepareStatement("insert into t values(?,?)");
System.out.println("开始执行批处理");
for (int i = 1; i <= 510; i++) {
ps.setString(1, "" + i);//为了确保传入数据库的数据为字符串,利用数据库拼接将int类型,转换为字符串
ps.setString(2, "123456");
ps.addBatch();//添加sql语句到批处理中
if (i % 50 == 0) {
ps.executeBatch();//执行批处理
ps.clearBatch();//清空批处理
System.out.println("执行了" + i / 50 + "次批处理");
}
}
//如果剩余的一些sql语句不够50条语句,那么执行下面的代码
ps.executeBatch();//执行之前没有执行的批处理
ps.clearBatch();//清空批出六
System.out.println("批处理执行完毕");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils2.close(conn, ps, rs);
}
}
}
4. 连接池
开源连接池(DBCP、C3P0)
上述模型表示,如果有多个用户访问服务器,服务器需要通过其中的程序与数据库建立连接,使用完连接之后,要销毁连接。若是面对海量的访问,频繁的创建和销毁连接十分占用资源。应该将创建和销毁的过程消除掉。
为了消除创建和销毁连接的过程,可以使用连接池。连接池中会保留一些连接,用户需要使用时,直接取出使用,使用完成后归还到连接池中即可。通过取出和归还代替了创建和销毁。若是面对海量访问,也能降低服务器和数据库的压力。
连接池原理 :
在服务器启动的时候,会主动向数据库服务器索要一批连接连接数量。在连接被取完之后,连接池还可以再次初始化更多连接。用户使用完连接之后,将连接归还到连接池中。若是较长一段时间,无人操作连接,则连接池会销毁其中一半的连接。
4.1 手动实现简易的连接池
创建一个类来实现 DataSource 接口
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
public class TestDataSource implements DataSource {
//使用集合来存储数据库连接
private static List<Connection> list = new LinkedList<>();
//保证类加载时初始化一定数量的数据库连接
static {
//静态代码块中只能捕获异常
try {
Connection conn = JDBCUtils2.getConnection();
// Class.forName("com.mysql.jdbc.Driver");//异常处理
// Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school","root","123456");//异常处理
for (int i = 0; i < 5; i++) {//一次循环存放一个数据库连接
list.add(conn);//将数据库连接放到集合中
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
//取出数据库连接
@Override
public Connection getConnection() throws SQLException {
if (list.isEmpty()) {//判断集合中有没有数据库连接,如果没有,则执行if中的代码
try {
Connection conn = JDBCUtils2.getConnection();
for (int i = 0; i < 5; i++) {
list.add(conn);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Connection conn = list.remove(0);//remove(下标索引):返回值为删除元素的数据
System.out.println("取出连接,连接池中还剩" + list.size() + "个连接");
return conn;
}
//归还连接
public void returnConnection(Connection conn) throws SQLException {
//如果接收的连接已经被关闭,或者接收的值为null,都不能归还连接
if (conn != null && (!conn.isClosed())) {//isClosed()方法是判断接收的连接是否已经被关闭,需要处理异常
list.add(conn);
System.out.println("归还连接,连接池中还剩" + list.size() + "个连接");
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
测试类
class TestData {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
//创建连接池实现类的对象
TestDataSource source = new TestDataSource();
try {
//调用连接的方法来获取连接--取出连接
conn = source.getConnection();
ps = conn.prepareStatement("select * from student where student_id=?");
ps.setInt(1, 2018);
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString(2));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
if (rs != null) {//为了防止rs为空,进而出现空指针异常
try {
//关闭对象
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {//无论对象是否正常关闭,都要释放资源,以便垃圾回收
//释放资源
rs = null;
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
ps = null;
}
}
if (conn != null) {
try {
//归还连接
source.returnConnection(conn);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
conn = null;
}
}
}
}
}
4.2 DBCP
需要在工程下导入两个.jar包, 一般在工程下创建lib文件夹, lib文件夹与src文件夹一个级别, 将.jar包放入lib文件夹下
-
导入包的名称为 :
commons-dbcp-1.4.jar
commons-pool-1.5.6.jar -
在src文件夹下创建 XX.properties 文件,
dbcp.properties文件内容为driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/school username=root password=123456
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class DBCPDemo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//读取dbcp的配置文件
Properties p = new Properties();
//p.load(new FileInputStream(new File(DBCPDemo.class.getClassLoader().getResource("dbcp.properties").getPath())));
p.load(DBCPDemo.class.getClassLoader().getResourceAsStream("dbcp.properties"));//处理异常
//创建工厂对象
BasicDataSourceFactory factory = new BasicDataSourceFactory();
//获取连接池对象
DataSource source = factory.createDataSource(p);//处理异常
//通过连接池对象获取连接
conn = source.getConnection();
ps = conn.prepareStatement("select * from student");
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString(2));
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {//为了防止rs为空,进而出现空指针异常
try {
//关闭对象
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {//无论对象是否正常关闭,都要释放资源,以便垃圾回收
//释放资源
rs = null;
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
ps = null;
}
}
if (conn != null) {
try {
//归还连接(不是原来的关闭连接了)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
conn = null;
}
}
}
}
}
4.3 C3P0
需要在工程中导入一个.jar包
-
导入的包名为 :
c3p0-0.9.1.2.jar -
在src文件夹下创建 c3p0.properties文件, 该文件的文件名称必须是 c3p0, 文件中的内容如下 :
c3p0.driverClass=com.mysql.jdbc.Driver c3p0.jdbcUrl=jdbc:mysql://localhost:3306/school c3p0.user=root c3p0.password=123456
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class C3P0Demo {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
//创建连接池对象
//默认去读取src目录下c3p0.properties文件的内容
ComboPooledDataSource source = new ComboPooledDataSource();
try {
//取出连接
conn = source.getConnection();
ps = conn.prepareStatement("select * from student");
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString(2));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.close(conn, ps, rs);
}
}
}
5. 用C3P0对JDBC工具类做最后的修改
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class C3P0Utils {
//私有化构造方法,禁止其他类创建本类的对象
private C3P0Utils() {
}
//创建c3p0连接池对象,全局统一
private static ComboPooledDataSource source = new ComboPooledDataSource();
//1.加载数据库驱动 2.连接数据库
public static Connection getConnection() throws ClassNotFoundException, SQLException {
return source.getConnection();
}
//6.关闭对象
public static void close(Connection conn, Statement stat, ResultSet rs) {
//处理异常
if (rs != null) {//为了避免空指针异常
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
stat = null;
}
}
if (conn != null) {
try {
//归还连接(不是关闭连接)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
conn = null;
}
}
}
}