snake_skin

JDBC笔记

使用准备

  1. 使用jdbc前需要有mysql驱动,将其放入lib目录中(驱动不需要与mysql版本一致)

    image-20210818134806955

  2. 右键该驱动,点击最下面的 As the libery 即可

练习需要的配置文件

//以下是mysql.properties配置文件的内容
user=root
password=123456
url=jdbc:mysql://localhost:3306/xsp_db02
driver=com.mysql.jdbc.Driver

快速入门

//注册驱动  com.mysql.jdbc是Driver类所在路径
Driver driver = new com.mysql.jdbc.Driver();

//连接数据库
/*
           jdbc:mysql:// 规定表示协议,通过jdbc方式连接mysql
           localhost     是主机地址,这个位置用来填写ip,表示连接的ip
           3306           mysql监听的端口号
           xsp_db02       连接到 mysql dbms 的xsp_db02数据库
           mysql连接的本质就是socket连接
       */
String url = "jdbc:mysql://localhost:3306/xsp_db02";

/*
           将用户名和密码放入properties对象
           user是默认的主键,不能修改,root是登录用户名
           password是默认的主键,不能修改,123456是登录密码
       */
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");

//进行网络连接,将url和登录信息传入
Connection connect = driver.connect(url, properties);

//要执行的sql语句
String sql = "insert into stu values(1,'mary')";
//用于执行sql语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
//dml语句执行后会返回生效的行数
int rows = statement.executeUpdate(sql);
//如果生效的行数大于0,则说明执行成功
System.out.println(rows > 0 ? "ok" : "no");
//关闭资源
statement.close();
connect.close();

连接数据库的方式

如何选择

  • 看完快速入门的详解即可理解以下方式

  • 方式五是最简单的方式,其次是方式四

  • 方式六通过配置文件对连接需要的数据进行保存读取,使得连接数据库更灵活

  • 前三种方式将连接复杂化,不必选择

方式一

//方式一的详解在快速入门中
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/xsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
Connection connect = driver.connect(url, properties);
System.out.println(connect);
connect.close();

方式二

//使用反射加载Driver类,动态加载,更加灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
//向下转型
Driver driver = (Driver) aClass.newInstance();
//以下跟方式一 一样
String url = "jdbc:mysql://localhost:3306/xsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
//进行网络连接,将url和登录信息传入
Connection connect = driver.connect(url, properties);
System.out.println(connect);
connect.close();

方式三

//使用DriverManager替代driver进行统一管理

////使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/xsp_db02";
//注册Driver驱动
DriverManager.registerDriver(driver);
//进行网络连接,将url和登录信息传入
Connection connection = DriverManager.getConnection(url, "root", "123456");
System.out.println(connection);
connection.close();

方式四

//在方式三的基础上减少了DriverManager.registerDriver(driver);
//原因:Driver类的静态代码块有注册驱动的代码,当类加载时自动执行,因此不需要手动注册
//静态代码块中的语句:DriverManager.registerDriver(new Driver());
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
System.out.println(connection);
connection.close();
//和以上程序没区别,只是将url、用户名、密码存入字符串中再使用,因此代码量看起来更多

Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");

String url = "jdbc:mysql://localhost:3306/xsp_db02";
String user = "root";
String passward = "123456";
Connection connection = DriverManager.getConnection(url,user,passward);

System.out.println(connection);
connection.close();

方式五

//在jkd1.5之后使用了jdbc4,不再需要主动调用class.forName()注册驱动,会自动调用Driver去注册
//因此实际只需要一个语句即可进行数据库连接
//但由于主动调用会比较明显,如果多个数据库,如oracle,mysql等,方便区分,因此韩顺平老师建议我们将注册驱动语句写出来:Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");

System.out.println(connection);
connection.close();

方式六

//通过配置文件保存url、用户名、密码

//读取配置文件
Properties properties = new Properties();
properties.load(new FileReader("src\\mysql.properties"));
//获取配置文件相关值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//注册驱动
Class.forName(driver);
//获取网络连接
Connection connection = DriverManager.getConnection(url, user, password);

System.out.println(connection);
connection.close();

//以下是mysql.properties配置文件的内容
user=root
password=123456
url=jdbc:mysql://localhost:3306/xsp_db02
driver=com.mysql.jdbc.Driver

ResultSet结果集

  • 表示数据库结果集的数据表,通过执行select语句生成

  • ResultSet对象保持一个光标指向当前数据行

  • 初始光标位于第一行之前

  • ResultSet有next方法,该方法将光标移动到下一行,当下一行没有内容时,返回false

  • 因此可以用while循环配合next方法循环遍历ResultSet结果集

  • ResultSet结果集实际是将数据保存到数组中的

//连接数据库
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
//得到Statement
Statement statement = connection.createStatement();
//要执行的select语句
String sql = "select id, name , sex, borndate from actor";
/* 以下是select在数据库中显示的数据
+----+-----------+-----+---------------------+
| id | name | sex | borndate |
+----+-----------+-----+---------------------+-------+
| 4 | 刘德华 | 男 | 1970-12-12 00:00:00 |
| 5 | jack | 男 | 1990-11-11 00:00:00 |
+----+-----------+-----+---------------------+-------+
*/
//执行给定的SQL语句,该语句返回ResultSet对象
//执行select语句需要使用statement.executeQuery
ResultSet resultSet = statement.executeQuery(sql);
//使用while取出数据
while (resultSet.next()) { // 让光标向后移动,如果没有更多行,则返回false
int id = resultSet.getInt(1); //获取该行的第1列,返回的是int
//int id1 = resultSet.getInt("id"); 也可以通过列名来获取值, 推荐
String name = resultSet.getString(2);//获取该行的第2列,返回的是String
String sex = resultSet.getString(3);//获取第3列,返回的是String
Date date = resultSet.getDate(4);//获取第4列,返回的是Date
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);//将得到的结果拼接
}

//关闭连接
resultSet.close();
statement.close();
connection.close();
//自己写的 stu表的内容在下面的图
//连接数据库
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
//得到statement
Statement statement = connection.createStatement();
//执行select语句得到resultSet
ResultSet resultSet = statement.executeQuery("select * from stu");
//遍历取值
while (resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
System.out.println(id + "\t" + name);
}
//关流
statement.close();
connection.close();

image-20210818170053762

Statement

  • Statement的使用参照上面的ResultSet程序即可

  • Statement对象用于执行静态sql语句,并返回其生成的结果

  • 在数据库连接后,需要执行命令/sql语句,可以通过以下方式

    • Statement 存在sql注入问题

    • PreparedStatement 预处理

    • CallableStatement 存储过程

  • Statement对象执行sql语句存在sql注入风险,因此目前已不用Statement

  • sql注入是利用某些系统没有对用户输入的数据进行充分的检查,某些用户输入时输入非法的sql语句段或命令,恶意攻击数据库

  • 防范sql注入,使用PrepareStatement替代Statement

PreparedStatement(预处理)

  • 基本使用

    • PreparedStatement执行的sql语句中的参数用问号(?)表示

    • PreparedStatement对象有setXXX()方法来设置 ?的参数

    • set方法有两个参数,参数1:第几个问号,参数2:要设置的值

      • 一般用preparedStatement.setString(参数1,参数2);来设置参数

    • 执行select语句,调用executeQuery();返回ResultSet对象

    • 执行dml操作(update,delete,insert),调用executeUpdate();调用后返回生效的行数,如果生效行数>0,说明执行成功

  • 好处

    • 不在使用 +拼接sql语句,减少语法的错误

    • 有效解决sql注入问题

    • 大大减少编译次数,提高效率

  • 执行select语句使用 executeQuery();

    • Scanner scanner = new Scanner(System.in);
      //让用户输入
      System.out.print("请输入id: "); //next(): 当接收到空格或者'就是表示结束
      String id = scanner.nextLine(); //如果希望看到SQL注入,这里需要用nextLine,nextLine只有按回车才表示结束,即使接收到空格或者'
      System.out.print("请输入名字: ");
      String name = scanner.nextLine();

      //1. 注册驱动
      Class.forName("com.mysql.jdbc.Driver");//建议写上
      //2. 得到连接
      Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");

      //3. 得到PreparedStatement
      //3.1 sql语句 问号?相当于占位符
      String sql = "select id , name from stu where id = ? and name = ?";
      //3.2 通过connection获取prepareStatement对象 在获取prepareStatement对象对象时,就需要将sql语句放入
      PreparedStatement preparedStatement = connection.prepareStatement(sql);//在这里就需要将sql语句放入
      //3.3 给?赋值
      preparedStatement.setString(1, id);
      preparedStatement.setString(2, name);

      //4. 执行 select 语句使用 executeQuery(),会返回ResultSet对象
      // 如果执行的是 dml(update, insert ,delete) executeUpdate()
      // 这一步不能在括号内写入sql语句
      ResultSet resultSet = preparedStatement.executeQuery();//执行sql语句后会返回查询结果
      if (resultSet.next()) { //如果能查询到(即有返回数据),则说明该数据存在
      System.out.println("登录成功");
      } else {
      System.out.println("登录失败");
      }
      //关闭连接
      resultSet.close();
      preparedStatement.close();
      connection.close();
  • 执行dml语句 使用executeUpdate();

    • Scanner scanner = new Scanner(System.in);
      //让用户输入
      System.out.print("请输入名字: ");
      String name = scanner.nextLine();
      System.out.print("请输入id: ");
      String id = scanner.nextLine();

      //连接数据库
      Class.forName("com.mysql.jdbc.Driver");
      Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");

      //设置update语句
      String sql = "update stu set name = ? where id = ?";//输入修改后的名字和目标id即可完成修改
      PreparedStatement preparedStatement = connection.prepareStatement(sql);//在这里就需要将sql语句放入
      preparedStatement.setString(1, name);
      preparedStatement.setString(2, id);
      //执行update语句
      int rows = preparedStatement.executeUpdate();//执行update语句后会返回生效行数
      System.out.println(rows>0 ? "执行成功" : "执行失败");//生效行数大于0说明执行成功
      //关闭连接
      preparedStatement.close();
      connection.close();

JDBC相关API及方法

  • DriverManager驱动管理类

    • getConnection(url,user,pwd)获取到连接

  • Connection接口

    • createStatement()创建Statement对象

    • preparedStatement(sql)创建预处理PreparedStatement对象

  • Statement接口

    • executeUpdate(sql)执行dml语句,返回影响的行数

    • executeQuery(sql)执行select查询,返回ResultSet对象

    • execute(sql)执行任意的sql,返回boolean值

  • PreparedStatement接口

    • executeUpdate()执行dml语句,返回影响的行数

    • executeQuery()执行select查询,返回ResultSet对象

    • execute()执行任意的sql,返回boolean值

    • setXxx(占位符索引,占位符的值)设置?的值,解决sql注入

    • setObject(占位符索引,占位符的值)设置?的值,解决sql注入

  • ResultSet结果集

    • next()向下移动一行,如果没有下一行返回false

    • previous()向上移动一行,如果没有上一行,返回false

    • getXxx(列的索引/列名)返回对应 索引/列名 的值,接收类型是Xxx ,这里的索引是指第几列

    • getObject(列的索引/列名)返回对应 索引/列名 的值,接收类型是Object

自设JDBCUtils

设计类

设计JDBCUtils工具类,通过方法完成连接数据库和关闭流的操作

public class JDBCUtils {
//定义相关的属性(4个), 因为只需要一份,因此static
private static String user; //用户名
private static String password; //密码
private static String url; //url
private static String driver; //驱动名

//在static代码块初始化
static {
try {
//读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关的属性值
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
//在实际开发中,我们可以这样处理
//1. 将编译异常转成 运行异常
//2. 调用者可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//连接数据库, 返回Connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
//将编译异常转成 运行异常
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
//判断是否为null
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
//将编译异常转成运行异常抛出
throw new RuntimeException(e);
}
}
}

使用

这是要操作的表数据

image-20210818194322744

使用在select

//得到连接 这里先写成null是为了扩大作用域,否则在try-catch中无法找到
Connection connection = null;
//sql语句
String sql = "select * from actor where id = ?";
//这里先写成null是为了扩大作用域,否则在try-catch中无法找到
PreparedStatement preparedStatement = null;
ResultSet resultset = null;
//开始操作
try {
//使用工具类的方法连接数据库
connection = JDBCUtils.getConnection();
//获取prepareStatement并传入sql语句
preparedStatement = connection.prepareStatement(sql);
//给?号赋值
preparedStatement.setInt(1, 5);
//执行, 得到结果集
resultset = preparedStatement.executeQuery();
//遍历该结果集
while (resultset.next()) {
int id = resultset.getInt("id");//获取id列的值
String name = resultset.getString("name");//获取name列的值
String sex = resultset.getString("sex");//获取sex列的值
Date borndate = resultset.getDate("borndate");//获取borndate列的值
String phone = resultset.getString("phone");//获取phone列的值
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//使用工具类关闭资源
JDBCUtils.close(resultset, preparedStatement, connection);
}

使用在dml

//null为了扩大作用域
Connection connection = null;
//sql
String sql = "update actor set name = ? where id = ?";
//null为了扩大作用域
PreparedStatement preparedStatement = null;
//开始操作
try {
//使用工具类的方法连接数据库
connection = JDBCUtils.getConnection();
//获取prepareStatement并传入sql语句
preparedStatement = connection.prepareStatement(sql);
//给占位符赋值
preparedStatement.setString(1, "周星驰");
preparedStatement.setInt(2, 4);
//执行
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//使用工具类关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}

事务

介绍

  • jdbc中当一个Connection对象创建时,默认情况下自动提交事务,每次执行sql成功数据库就会自动提交,不能回滚

  • jdbc为了让多个sql语句作为整体运行,需要使用事务

  • 调用 Connection的setAutoCommit(false);可以取消自动提交事务

  • 需要回滚时调用 Connection的rollback(); 回滚事务

  • 所有sql语句成功执行后 调用 Connection的commit(); 提交事务

  • 可以在两个执行的sql语句之间制造一个异常,模拟没有事务出现的问题

使用

需要的表

image-20210818195101133

转账练习(事务处理)

//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 两句sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
//3. 创建PreparedStatement 对象
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
//将 connection 设置为不自动提交
connection.setAutoCommit(false); //即开启了事务

//获取preparedStatement并提交第一条sql
preparedStatement = connection.prepareStatement(sql);
//执行第1条sql
preparedStatement.executeUpdate();

//制造算术异常 出现异常后,后面的语句不再执行,只执行catch和finally的
int i = 1 / 0;

//再次获取preparedStatement并提交第二条sql
preparedStatement = connection.prepareStatement(sql2);
//执行第2条sql
preparedStatement.executeUpdate();

//提交事务
connection.commit();
} catch (Exception e) {
//这里我们可以进行回滚,即撤销执行的SQL
//默认回滚到事务开始的状态.
System.out.println("执行发生了异常,撤销执行的sql");
try {
//回滚 回滚语句有异常需要捕捉
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}

转账练习(不使用事务)

//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
//3. 创建PreparedStatement 对象
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1条sql
//在抛出异常后 后面的语句都不执行,因此只执行了第1条sql,导致id1扣钱了,id2没有加钱
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第3条sql

} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}

批处理

介绍

  • 需要大量 插入/更新 记录,可以采用java的批量更新机制,该机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独处理更有效率

  • jdbc批处理方法,在 Statement/PreparedStatement对象下

    • addBatch(); 添加需要批量处理的sql语句/参数,放入批处理包中(实际是一个数组)

    • executeBatch();执行批量处理

    • clearBatch();清空批量处理包

  • 如果使用批处理功能,需要在jdbc连接数据库时,必须在url后加上 ?rewriteBatchedStatements=true

  • 批处理一般和PreparedStatement搭配,既能减少编译次数,还能减少运行次数,提高效率

演示

批处理的使用

image-20210818203038328

//使用批量方式添加数据
//使用前记得在配置文件的url中加上?rewriteBatchedStatements=true

Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//5000执行
//每次循环都会重新设定sql语句的?参数内容
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
//将sql语句加入到批处理包中
preparedStatement.addBatch();
//当有1000条记录时,在批量执行
if((i + 1) % 1000 == 0) {//满1000条sql
//批量执行
preparedStatement.executeBatch();
//清空
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();//结束时间
System.out.println("批量方式 耗时=" + (end - start));//批量方式 耗时=108
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);

//总耗时71毫秒

传统提交sql

//传统方法,添加5000条数据到admin2

Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//5000执行
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统的方式 耗时=" + (end - start));//传统的方式 耗时=10702
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);

//耗时3328毫秒 = 3秒多

addBatch();源码

//  以下是批处理addBatch();方法的源码
//1. 第一就创建 ArrayList,数组名为elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的sql语句
//3. 当elementData满后,就按照1.5扩容
//4. 当添加到指定的值后,就executeBatch
//5. 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高
public void addBatch() throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
}
}
  1. 在preparedStatement.addBatch();下断点

  2. 进入preparedStatement image-20210818203622140

  3. 找到batchedArgs image-20210818203745680

  4. 进入elementDate,里面的数组存放的就是sql语句 image-20210818203921648

数据库连接池

注意

  • 从这里开始需要大量导入外部的jar包,在使用前请记得导入!!!

  • 还需要根据不同的数据库和表等情况,导入配置文件并对配置文件进行修改!!!

传统连接弊端

  • 传统jdbc数据库连接使用过DriverManager来获取,每次向数据库建立连接都需要将Connection加载到内存中,再验证ip地址、用户名、密码(共需0.05s~1s)。需要数据库连接的时候就向数据库要求一个连接,频繁的进行数据库连接会占用很多系统资源,过多时会造成服务器崩溃

  • 每次数据连接,使用完都需要断开,如果程序出现异常没有关闭,会导致数据库内存泄漏,最终导致重启数据库

  • 传统的获取连接方式,不能控制创建的连接数量,如果连接过多可能导致内存泄漏,导致mysql崩溃

  • 解决传统连接的弊端,可以采用数据库连接池技术

数据库连接池

  • 原理:

    • 预先在缓冲池中放入一定数量的连接,需要建立数据库连接时,从缓冲池取出一个,使用完再放回缓冲池

    • 数据库连接池负责分配、管理、释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立新的连接

    • 当应用程序向连接池请求的连接数超过最大的连接数量时,这些请求会加入到等待队列中

  • jdbc的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,通常由第三方提供实现

  • 分类:

    • C3P0数据库连接池,速度相对较慢,稳定性较好(hibernate,spring中使用到)

    • Druid(德鲁伊)数据库连接池,由阿里提供,集DBCP、C3P0、Proxool优点

    • Proxool数据库连接池,能监控连接池状态,稳定性比C3P0略差

    • BoneCP数据库连接池,速度较快

  • 在数据库连接池技术中close不是真的断掉连接,只是将连接放回缓冲池

C3P0数据库连接池

  • 首先需要有c3p0的jar包,放入lib目录,右键点击add as library

  • 方式一

    • //方式1:在程序中指定user, url , password等

      //1. 创建一个c3p0数据源对象
      ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
      //2. 通过配置文件mysql.properties 获取相关连接的信息
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\\mysql.properties"));
      //读取相关的属性值
      String user = properties.getProperty("user");
      String password = properties.getProperty("password");
      String url = properties.getProperty("url");
      String driver = properties.getProperty("driver");

      //给数据源 comboPooledDataSource 设置相关的参数
      //注意:连接管理是由 comboPooledDataSource 来管理
      comboPooledDataSource.setDriverClass(driver);//driver = com.mysql.jdbc.Driver
      comboPooledDataSource.setJdbcUrl(url);
      comboPooledDataSource.setUser(user);
      comboPooledDataSource.setPassword(password);

      //设置初始化连接数
      comboPooledDataSource.setInitialPoolSize(10);
      //最大连接数 如果超过最大连接数,就会进入等待队列
      comboPooledDataSource.setMaxPoolSize(50);
      //测试连接池的效率, 测试对mysql 5000次操作
      long start = System.currentTimeMillis();
      for (int i = 0; i < 5000; i++) {
      //进行连接
      //从DataSource接口实现的方法comboPooledDataSource.getConnection()
      Connection connection = comboPooledDataSource.getConnection();
      //关闭连接
      connection.close();
      }
      long end = System.currentTimeMillis();
      //c3p0 5000连接mysql 耗时=391
      System.out.println("c3p0 5000连接mysql 耗时=" + (end - start));
  • 方式二

    • //第二种方式 使用配置文件模板来完成
      //1. 将c3p0提供的 c3p0_config.xml 拷贝到 src目录下(文件名必须一样,不能改变)
      //2. 该文件指定了连接数据库和连接池的相关参数

      //使用c3p0,mysql_xsp是在配置文件中自定义的名字,用来找到配置文件
      ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mysql_xsp");
      //获取数据库连接
      Connection connection = comboPooledDataSource.getConnection();
      //关闭数据库连接
      connection.close();
    • //测试5000次连接mysql
      ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mysql_xsp");
      long start = System.currentTimeMillis();
      System.out.println("开始执行....");
      for (int i = 0; i < 500000; i++) {
      Connection connection = comboPooledDataSource.getConnection();
      connection.close();
      }
      long end = System.currentTimeMillis();
      System.out.println("c3p0的第二种方式(500000) 耗时=" + (end - start));//1917
  • 方式二需要的配置文件c3p0_config.xml,内容根据实际情况进行修改,配置文件放于src目录下

    • <!-- 后面有对配置文件的解读 -->
      <c3p0-config>
      <named-config name="mysql_xsp">
      <!-- 驱动类 -->
      <property name="driverClass">com.mysql.jdbc.Driver</property>
      <!-- url-->
      <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/xsp_db02</property>
      <!-- 用户名 -->
      <property name="user">root</property>
      <!-- 密码 -->
      <property name="password">123456</property>
      <!-- 每次增长的连接数--><!-- 当超过了当前的连接数,小于最大连接数,就会进行自定的5增长 -->
      <property name="acquireIncrement">5</property>
      <!-- 初始的连接数 -->
      <property name="initialPoolSize">10</property>
      <!-- 最小连接数 --><!-- 如果进行一定时间的等待后,连接数太少,就会将连接减少到自定的5 -->
      <property name="minPoolSize">5</property>
      <!-- 最大连接数 --><!-- 自设最多只有50个连接,如果超过50则进入等待队列 -->
      <property name="maxPoolSize">50</property>
      <!-- 可连接的最多的命令对象数 -->
      <property name="maxStatements">5</property>
      <!-- 每个连接对象可连接的最多的命令对象数 -->
      <property name="maxStatementsPerConnection">2</property>
      </named-config>
      </c3p0-config>
    • <named-config name="mysql_xsp">这里的mysql_xsp是自定义的,在方式二中进行c3p0连接时需要此名字来找到配置文件ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mysql_xsp");

    • <property name="driverClass">com.mysql.jdbc.Driver</property>中间填写的是注册驱动Driver类的地址,如果是oracel数据库,则替换成<property name="driverClass">com.oracel.jdbc.Driver</property>

    • <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/xsp_db02</property>中间是url地址,注意xsp_db02是要连接的数据库名字,如果是其它数据库就改成其它的

    • <property name="user">root</property> root是要登录的数据库用户名

    • <property name="password">123456</property> 123456是要登录的数据库密码

    • 后面的较简单的已在配置文件中给出注释

Druid数据库连接池

常规使用

  • 首先需要在lib目录加入Druid的jar包,右键点击add as library

  • 然后将配置文件 druid.properties , 放在src目录,内容根据实际情况进行修改,配置文件在下面的代码块

  • 使用:

    • //连接Druid数据连接池和获取连接的操作

      Properties properties = new Properties();
      properties.load(new FileInputStream("src\\druid.properties"));
      //创建一个指定参数的数据库连接池, Druid连接池
      DataSource dataSource =
      DruidDataSourceFactory.createDataSource(properties);
      //获取数据库连接
      Connection connection = dataSource.getConnection();
      //关闭数据库连接
      connection.close();
    • //测试Druid的速度
      //1. 加入 Druid jar包
      //2. 加入 配置文件 druid.properties , 将该文件拷贝项目的src目录
      //3. 创建Properties对象, 读取配置文件
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\\druid.properties"));

      //4. 创建一个指定参数的数据库连接池, Druid连接池
      DataSource dataSource =
      DruidDataSourceFactory.createDataSource(properties);
      //连接断开500000次统计时间
      long start = System.currentTimeMillis();
      for (int i = 0; i < 500000; i++) {
      //获取数据库连接
      Connection connection = dataSource.getConnection();
      //System.out.println("连接成功!");
      connection.close();
      }
      long end = System.currentTimeMillis();
      //druid连接池 操作500000 耗时=539
      System.out.println("druid连接池 操作500000 耗时=" + (end - start));
  • 需要的配置文件 druid.properties

    • #key=value  Driver类的地址
      driverClassName=com.mysql.jdbc.Driver
      #url=jdbc:mysql://localhost:3306/girls
      url=jdbc:mysql://localhost:3306/xsp_db02?rewriteBatchedStatements=true
      username=root
      password=123456
      #initial connection Size 初始连接数
      initialSize=10
      #min idle connecton size 最小连接数
      minIdle=5
      #max active connection size 最大连接数
      maxActive=20
      #max wait time (5000 mil seconds 是5秒)
      #最大等待时间(如果超过等待时间,则取消尝试连接)
      maxWait=5000

设计成工具类

  • 功能

    • 类加载时创建和读取properties

    • getConnection方法获取数据库连接

    • close方法关闭连接

public class JDBCUtilsByDruid {
//定义DataSource,用来接收DruidDataSourceFactory.createDataSource(properties);
private static DataSource ds;
//在静态代码块完成 ds初始化 创建和读取properties
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}

//编写getConnection方法 获取连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}

//关闭连接 在数据库连接池技术中close不是真的断掉连接,只是将连接放回缓冲池
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

工具类使用

注意:这里使用到的表是actor演员表,有属性:id、name、sex、borndate、phone,请按顺序使用(如果表在不同数据库,记得修改配置文件)

image-20210819213346499

public void testSelect() {
System.out.println("使用druid方式完成");
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
//3. 创建PreparedStatement 对象
try {
//使用工具类获取数据库连接,工具类中的静态代码块已经完成了对配置文件的读取
connection = JDBCUtilsByDruid.getConnection();

//运行类型 com.alibaba.druid.pool.DruidPooledConnection
//System.out.println(connection.getClass());

//将sql语句放入preparedStatement中
preparedStatement = connection.prepareStatement(sql);
//给sql语句中的?号赋值
preparedStatement.setInt(1, 1);//第一个问号,赋值为1
//执行, 得到结果集
set = preparedStatement.executeQuery();

//遍历该结果集
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");//getName()
String sex = set.getString("sex");//getSex()
Date borndate = set.getDate("borndate");
String phone = set.getString("phone");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//工具类关闭资源
JDBCUtilsByDruid.close(set, preparedStatement, connection);
}
}

apache-DbUtils

为什么有DBUtils

  • 关闭connection连接后,resultSet结果集就无法使用

  • resultSet不利于数据的管理

DbUtils介绍

  • commons-dbutils是apache组织提供的开源jdbc工具类库,是对jdbc的封装,使用dbutils能极大简化jdbc编码的工作量

  • DbUtils类

    • QueryRunnber类:封装了sql的执行,线程安全,能实现 增删改查、批处理

    • ResultSetHandler接口:用于处理ResultSet,将数据按要求转换成另外的形式保存

      • 方法:

      • image-20210819213301113

dbutils+druid使用

  • 需要先创建对应表的类

  • 注意:这里使用到的表是actor演员表,有属性:id、name、sex、borndate、phone,请按顺序使用(如果表在不同数据库,记得修改配置文件)

  • 生成javaBean的时候是通过无参构造器构建对象,set方法也需要有(测试发现,如果没有给对应的set,存入的就会是null),如果没有有参构造器,没有影响

    image-20210819213346499

//Actor类,对应的是Actor表,所有的属性都是该表的 列 
//该类创建的对象用来保存ResultSet结果集的结果
//因此也被叫做Javabean/POJO/Domain对象
public class Actor {
private Integer id;
private String name;
private String sex;
private Date borndate;
private String phone;
public Actor() { //!!!一定要给一个无参构造器!!![反射需要]
}
public Actor(Integer id, String name, String sex, Date borndate, String phone) {
this.id = id;
this.name = name;
this.sex = sex;
this.borndate = borndate;
this.phone = phone;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public Date getBorndate() {
return borndate;
}

public void setBorndate(Date borndate) {
this.borndate = borndate;
}

public String getPhone() {
return phone;
}

public void setPhone(String phone) {
this.phone = phone;
}
//对结果集输出时,直接输出该对象,自动调用toString
@Override
public String toString() {
return "\nActor{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", borndate=" + borndate +
", phone='" + phone + '\'' +
'}';
}
}
  • 不同返回情况的使用

  • 注意

    • 需要提前导入dbutils的jar包,一般名字是commons-dbutils-1.7.jar

    • 对应的表需要的也要创建好

  • 下面的程序体现了不同的情况,但实际只有 String sql = ... 语句后面的 queryRunner.query()方法是最重要

    • 主要是学会根据不同的返回情况 用不同的值接收以及对应需要的参数

  • 前三个Test 是select不同的返回情况

  • 最后一个Test是dml操作的使用

  • 先看第一个Test即可明白大部分,注释较详细

//注意:需要提前导入dbutils的jar包,一般名字是commons-dbutils-1.7.jar
// 对应的表需要的**类**也要创建好

//不同返回情况的使用

//返回结果是多行的情况
@Test
public void testQueryMany() throws SQLException {
//1. 通过druid工具类得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 执行QueryRunner相关的方法,返回ArrayList结果集
String sql = "select id, name from actor where id >= ?";
//返回的是多行,因此需要List接收保存,并且指定泛型是Actor类
//返回的是多行,Handler使用new BeanListHandler<>(Actor.class),这里的Actor.class是反射,表示Actor类
//第三个参数:是sql语句中问号的参数,可以写多个,比如1,4,5等,按顺序有多少问号写多少
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
System.out.println("输出集合的信息");
//list数组中,每一个有效索引对应一个Actor对象
for (Actor actor : list) {
System.out.print(actor);
}
//释放资源
//queryRunner.query将结果返回后,还会将ResultSet和PreparedStatement资源关闭
//因此在它们对应的位置填null即可
JDBCUtilsByDruid.close(null, null, connection);
}

//返回的结果是单行记录(单个对象)
@Test
public void testQuerySingle() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 执行相关的方法,返回单个对象
String sql = "select * from actor where id = ?";
// 因为我们返回的单行记录,即单个对象 , 返回单行使用的Hander 是 BeanHandler
// 只需要使用一个actor对象接收,不需要使用List
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10);
System.out.println(actor);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);

}

//查询结果是单行单列,返回的是object
@Test
public void testScalar() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 执行相关的方法,返回单行单列 , 返回的就是Object
String sql = "select name from actor where id = ?";
//因为返回的是一个Object对象, 返回单行单列使用的handler 就是 ScalarHandler,并且使用Object对象接收
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
System.out.println(obj);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}

//dml (update, insert ,delete)
@Test
public void testDML() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 这里组织sql 完成 update, insert delete
//String sql = "update actor set name = ? where id = ?";
//String sql = "insert into actor values(null, ?, ?, ?, ?)";
String sql = "delete from actor where id = ?";
//执行dml 操作是 queryRunner.update() 返回的值是受影响的行数,因此只需要一个整型接收
//int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966-10-10", "116");
int affectedRow = queryRunner.update(connection, sql, 1000 );
System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}

query()方法的解读

        // 老韩解读
//(1) query方法会执行sql 语句,得到resultset 并将其封装到 ArrayList 集合中
//(2) 然后返回集合
//参数 connection: 连接
// sql : 执行的sql语句
// new BeanListHandler<>(Actor.class): 将 resultset 放入 Actor 对象 然后 封装到 ArrayList
// 底层使用反射机制 去获取Actor类的属性,然后进行封装
// 第四个参数 1 是给 sql 语句中的?赋值,可以有多个值,因为是可变参数Object... params
// 底层得到的resultset ,会在query关闭, 以及关闭PreparedStatment
/**
* 分析 queryRunner.query方法:
* public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
* PreparedStatement stmt = null;//定义PreparedStatement
* ResultSet rs = null;//接收返回的 ResultSet
* Object result = null;//返回ArrayList
*
* try {
* stmt = this.prepareStatement(conn, sql);//创建PreparedStatement
* this.fillStatement(stmt, params);//对sql 进行 ? 赋值
* rs = this.wrap(stmt.executeQuery());//执行sql,返回resultset
* result = rsh.handle(rs);//返回的resultset --> arrayList[result] [使用到反射,对传入class对象处理]
* } catch (SQLException var33) {
* this.rethrow(var33, sql, params);
* } finally {
* try {
* this.close(rs);//关闭resultset
* } finally {
* this.close((Statement)stmt);//关闭preparedstatement对象
* }
* }
*
* return result;
* }
*/

DAO(BasicDAO)

引入BasicDAO

  • dbutils+druid开发极大简化jdbc开发,但还有缺陷

    • sql语句固定,不能通过参数传入,通用性不好,需要改进

    • select操作,如果有返回值,返回类型不能固定,需要使用泛型

    • 未来的表很多,业务需求复杂,需要多个java类

    • 因此有了BasicDAO

    • 自下而上的BasicDAO示意图

image-20210819222140383

 

基本说明

  • DAO:data access object 数据访问对象

  • 这样的通用类,成为BasicDAO,是专门和数据库交互的,完成对数据库(表)的crud

  • 在BasicDao的基础上,实现一张表对应一个DAO,能更好的完成功能

    • 比如Custome表对应CustomerDao类

    • 这样的类也被称为javabean/domain/pojo

使用

  • 使用前需要

    • 类:Actor类,JDBCUtilsByDruid类,将会放在最后

    • jar包:dbutils包,druid包

    • 配置文件:druid(一般下载后压缩包有对应的配置文件)

  • BasicDao类是druid连接池进行crud的通用类,一般继承它的类是 对应表 的 表名+DAO类

  • 真正进行测试使用的是第三个程序

//BasicDAO实际是一个druid连接池进行crud的通用类,需要则继承即可,就可以使用所有方法了
public class BasicDAO<T> { //泛型指定具体类型

//创建QueryRunner对象
private QueryRunner qr = new QueryRunner();

//开发通用的dml方法, 针对任意的表
public int update(String sql, Object... parameters) {
Connection connection = null;
try {
//用druid工具类获得连接
connection = JDBCUtilsByDruid.getConnection();
int update = qr.update(connection, sql, parameters);
return update;
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}

}

//返回多个对象(即查询的结果是多行), 针对任意表
/**
* @param sql sql 语句,可以有 ?
* @param clazz 传入一个类的Class对象 比如 Actor.class
* @param parameters 传入 ? 的具体的值,可以是多个
* @return 根据Actor.class 返回对应的 ArrayList 集合
*/
public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}

//查询单行结果 的通用方法
public T querySingle(String sql, Class<T> clazz, Object... parameters) {

Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);

} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}

//查询单行单列的方法,即返回单值的方法

public Object queryScalar(String sql, Object... parameters) {

Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new ScalarHandler(), parameters);

} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
}
public class ActorDAO extends BasicDAO<Actor> {
//1. 就有 BasicDAO 的方法
//2. 根据业务需求,可以编写特有的方法.
}
public class TestDAO {
//测试ActorDAO 对actor表crud操作
@Test
public void testActorDAO() {
//创建ActorDAO对象,该对象继承了BasicDao,因此具有BasicDao的方法
ActorDAO actorDAO = new ActorDAO();

//1. 查询多行记录
List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
System.out.println("===查询结果===");
for (Actor actor : actors) {
System.out.println(actor);
}

//2. 查询单行记录
Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6);
System.out.println("====查询单行结果====");
System.out.println(actor);

//3. 查询单行单列
Object o = actorDAO.queryScalar("select name from actor where id = ?", 6);
System.out.println("====查询单行单列值===");
System.out.println(o);

//4. dml操作 insert ,update, delete
int update = actorDAO.update("insert into actor values(null, ?, ?, ?, ?)", "张无忌", "男", "2000-11-11", "999");
System.out.println(update > 0 ? "执行成功" : "执行没有影响表");
}
}

数据连接池、DbUtils、DAO总结

  • 数据连接池、DbUtils、DAO 这三者是层层递进的

    • DAO基于DbUtils,DbUtils基于数据连接池

  • 必须从数据连接池开始理解,后面的才能逐步理解

  • 三者都是对crud的操作,只是一步一步进行简化

     

解析示意图

 

 

image-20210819222140383

  • 图片自下而上分层

  • 第一层:domain(javabean)和utils工具类 层

    • 一个javabean对应一张sql表

    • javabean是一个类,其中有表的所有列(参照前面使用过的Actor类)

  • 第二层:DAO层

    • 我们将不同的服务内容(表)分成了不同的DAO

    • BasicDAO是所有DAO类的父类

    • 其它的DAO类继承BasicDAO后就可以使用其所有方法

    • DAO层主要操作数据库

  • 第三层:服务层

    • 包含了业务需求的代码,并调用DAO层类

  • 第四次:界面层

    • 在界面层调用服务层的类

    • 服务层又调用了DAO层

    • DAO层又调用了javabean和utils操作数据库,最后结果一层一层返回到界面层

    •  

posted on 2021-08-23 17:21  snake_skin  阅读(36)  评论(1编辑  收藏  举报