JDBC&连接池&DBUtils

向右看齐 2022-08-28 10:38 339阅读 0赞

目录

  • 第一章 JDBC概述
    • 1.1 为什么需要JDBC
    • 1.2 JDBC概述
    • 1.3 JDBC使用步骤
      • 1.3.1 准备工作
      • 1.3.2 注册驱动
      • 1.3.3 获取Connection连接对象
      • 1.3.4 执行sql并处理结果
      • 1.3.5 释放资源
      • 代码测试
      • 总结
  • 第二章 JDBC的增删改查练习
    • 2.1 执行添加的SQL语句
    • 2.2 执行删除的SQL语句
    • 2.3 执行修改的SQL语句
    • 2.4 执行查询单行数据的SQL语句
    • 2.5 执行查询多行数据的SQL语句
  • 第三章 使用PreparedStatement处理CRUD
    • 2.1 Statement存在的问题
      • 2.1.1 每次执行一个SQL语句都需要先编译
      • 2.1.2 sql语句拼接
      • 2.1.3 sql注入
    • 2.2 PreparedStatement解决问题
      • 2.2.1 预编译
      • 2.2.2 避免sql拼接
      • 2.2.3 防止SQL注入
    • 2.3 获取自增长键值
      • 2.3.1 获取自增长键值的应用场景
      • 2.3.2 获取自增长键值的步骤
    • 2.4 批处理
      • 2.4.1 批处理优势和应用场景
      • 2.4.2 批处理的具体操作步骤
    • 2.5 事务
      • 2.5.1 事务操作的步骤
      • 2.5.2 事务相关API
      • 2.5.3 使用JDBC的事务完成转账案例
        • 2.5.3.1 准备数据
        • 2.5.3.2 代码实现
  • 第三章 数据库连接池
    • 3.1 什么是数据库连池
    • 3.2 为什么要使用连接池
    • 3.3 连接池的优势
    • 3.5 连接池的原理
    • 3.6 连接池的实现
      • 3.6.1 DataSource接口
      • 3.6.2 常见的数据库连接池
      • 3.6.3 Druid连接池的使用
      • 3.6.4 Druid连接池的配置参数列表
  • 第四章 封装JDBCTools
    • 连接池使用的总结
  • 第五章 Apache的DBUtils
    • 5.1 DBUtils的概述
    • 5.2 DBUtils执行增删改的SQL语句
      • 5.2.1 API介绍
      • 5.2.2 代码实现
    • 5.2.3 DBUtils执行批处理
      • 5.2.3.1 API介绍
      • 5.2.3.2 代码实现
    • 5.2.4 使用QueryRunner类实现查询
      • 5.2.3 API介绍

      • 5.2.4 代码实现
  • 经典错误
    • 1、jar包版本不兼容

第一章 JDBC概述

1.1 为什么需要JDBC

没有JDBC

在这里插入图片描述
有了JDBC后
在这里插入图片描述

1.2 JDBC概述

JDBC:Java Database Connectivity,它是代表一组独立于任何数据库管理系统(DBMS)的API,声明在java.sql与javax.sql包中,是SUN(现在Oracle)提供的一组接口规范。由各个数据库厂商来提供实现类,这些实现类的集合构成了数据库驱动jar。
在这里插入图片描述

即JDBC技术包含两个部分:
(1)java.sql包和javax.sql包中的API

因为为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。

(2)各个数据库厂商提供的jar

因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。

1.3 JDBC使用步骤

1.3.1 准备工作

  1. create database jdbc_test;
  2. use jdbc_test;
  3. create table user(
  4. id int primary key auto_increment,
  5. username varchar(20),
  6. password varchar(20),
  7. nickname varchar(20)
  8. );
  9. INSERT INTO `USER` VALUES(null,'zs','123456','老张');
  10. INSERT INTO `USER` VALUES(null,'ls','123456','老李');
  11. INSERT INTO `USER` VALUES(null,'wangwu','123','东方不败');

1.3.2 注册驱动

(1)将DBMS数据库管理软件的驱动jar拷贝到项目的libs目录中

例如:mysql-connector-java-5.1.36-bin.jar

(2)把驱动jar添加到项目的build path中
(3)将驱动类加载到内存中

Class.forName(“com.mysql.jdbc.Driver”);

1.3.3 获取Connection连接对象

Connection conn = DriverManager.getConnection(url,username,password);
mysql的url:jdbc:mysql://localhost:3306/数据库名?参数名=参数值
jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)

  1. String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
  2. String user = "root";
  3. String password = "123456";
  4. //获得连接
  5. Connection connection = DriverManager.getConnection(url, user, password);

1.3.4 执行sql并处理结果

(1)编写sql

  1. String sql = "select *from user";

(2)创建Statement对象

  1. Statement statement = connection.createStatement();

(3)使用Statement对象执行sql

增删改:调用executeUpate方法
查询:调用executeQuery方法

  1. ResultSet resultSet = statement.executeQuery(sql);

(4)处理结果

增删改:返回的是整数值,表示受到影响的数据条数
查询:返回ResultSet结果

  • boolean next():判断是否还有下一行
  • getString(字段名或序号),getInt(字段名或序号),getObject(字段名或序号)

    while (resultSet.next()) {

    1. //获取每一列的数据
    2. System.out.println(resultSet.getObject(1));
    3. System.out.println(resultSet.getObject(2));
    4. System.out.println(resultSet.getObject(3));
    5. System.out.println(resultSet.getObject(4));

    }

1.3.5 释放资源

原则是后创建的资源先关闭,我们会依次关闭ResultSet、Statement、Connection对象

  1. //关闭资源
  2. if(resultSet != null){
  3. resultSet.close();
  4. }
  5. if(statement != null){
  6. statement .close();
  7. }
  8. if(connection != null){
  9. connection.close();
  10. }

代码测试

  1. import org.junit.Test;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.ResultSet;
  5. import java.sql.Statement;
  6. public class TestJDBC {
  7. @Test
  8. public void testFindAll() throws Exception {
  9. //目标:使用JDBC执行查询所用用户的SQL语句
  10. //1.注册驱动
  11. Class.forName("com.mysql.jdbc.Driver");
  12. //2.获取连接
  13. String url = "jdbc:mysql://localhost:3306/jdbc_test?characterEncoding=utf-8";
  14. String user = "root";
  15. String password = "123456";
  16. Connection conn = DriverManager.getConnection(url, user, password);
  17. //3.创建statement对象
  18. Statement statement = conn.createStatement();
  19. //4.使用statement对象执行SQL语句
  20. String sql = "select * from user";
  21. //执行查询数据的SQL语句,获取查询到的结果集
  22. ResultSet rst = statement.executeQuery(sql);
  23. //5.遍历rst,从中获取查询到的数据
  24. while (rst.next()){
  25. //每次调用next()就是将游标移动到下一行
  26. //获取当前行的每列数据,根据列名获取
  27. int id = (int) rst.getObject("id");
  28. String username = (String) rst.getObject("username");
  29. String pwd = (String) rst.getObject("password");
  30. String nickname = (String) rst.getObject("nickname");
  31. System.out.println(id + ":" + username + ":" + pwd + ":" + nickname);
  32. }
  33. //6.关闭资源,后创建的先关闭
  34. rst.close();
  35. statement.close();
  36. conn.close();
  37. }
  38. }

总结

  1. JDBC的使用步骤:
  2. 1. 注册驱动
  3. Class.forName("驱动的全限定名"); //mysql驱动的全限定名是com.mysql.jdbc.Driver
  4. 如果你使用的是mysql8.0那么请你使用mysql8的驱动jar包,并且驱动的全限定名是com.mysql.cj.jdbc.Driver
  5. 2. 获得连接(建立客户端与mysql服务器的连接)
  6. Connection conn = DriverManager.getConnection("数据库服务器路径","用户名","密码");
  7. 3. 创建执行Sql语句的Statement对象
  8. Statement statement = conn.createStatement();
  9. 4. 使用statement执行SQL语句
  10. 4.1 执行增删改的SQL语句: int num = statement.executeUpdate(sql); 返回值表示受到影响的函数
  11. 4.2 执行查询的SQL语句: ResultSet rst = statement.executeQuery(sql);返回值是查询到的结果集
  12. 5. 如果第四步执行的SQL语句是查询,那么我们就要将查询到的结果集中数据遍历出来(难点)
  13. 6. 关闭资源: 后创建的资源先关闭
  14. rst.close();
  15. statement.close();
  16. conn.close();

第二章 JDBC的增删改查练习

2.1 执行添加的SQL语句

  1. @Test
  2. public void testInsert() throws Exception {
  3. //增加 insert into user values(null,'tq','77777','田七');
  4. //1.注册驱动
  5. Class.forName("com.mysql.jdbc.Driver");
  6. //2.获得连接
  7. String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
  8. String user = "root";
  9. String password = "123456";
  10. Connection connection = DriverManager.getConnection(url, user, password);
  11. //3.创建执行sql语句对象
  12. Statement statement = connection.createStatement();
  13. //4.执行sql语句
  14. String sql = "insert into user values(null,'tq','77777','田七')";
  15. int rows = statement.executeUpdate(sql);
  16. System.out.println("几行收影响=" + rows);
  17. //5.释放资源
  18. if (statement != null) {
  19. statement.close();
  20. }
  21. if (connection != null) {
  22. connection.close();
  23. }
  24. }

2.2 执行删除的SQL语句

  1. @Test
  2. //删除id为4的用户
  3. public void testDelete() throws Exception {
  4. //1.注册驱动
  5. Class.forName("com.mysql.jdbc.Driver");
  6. //2.获得连接
  7. String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
  8. String user = "root";
  9. String password = "123456";
  10. Connection connection = DriverManager.getConnection(url, user, password);
  11. //3.创建执行sql语句对象
  12. Statement statement = connection.createStatement();
  13. //4.执行sql语句
  14. String sql = "delete from user where id = 4";
  15. statement.executeUpdate(sql);
  16. //5.释放资源
  17. if (statement != null) {
  18. statement.close();
  19. }
  20. if (connection != null) {
  21. connection.close();
  22. }
  23. }

2.3 执行修改的SQL语句

  1. @Test
  2. //更新 把id为3的用户的密码改成88888888
  3. public void testUpdate() throws Exception {
  4. //1.注册驱动
  5. Class.forName("com.mysql.jdbc.Driver");
  6. //2.获得连接
  7. String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
  8. String user = "root";
  9. String password = "123456";
  10. Connection connection = DriverManager.getConnection(url, user, password);
  11. //3.创建执行sql语句对象
  12. Statement statement = connection.createStatement();
  13. //4.执行sql语句
  14. String sql = "update user set password = '88888888' where id = 3";
  15. statement.executeUpdate(sql);
  16. //5.释放资源
  17. if (statement != null) {
  18. statement.close();
  19. }
  20. if (connection != null) {
  21. connection.close();
  22. }
  23. }

2.4 执行查询单行数据的SQL语句

要求: 将查询到的结果封装到User对象中
User类

  1. public class User {
  2. private int id;
  3. private String username;
  4. private String password;
  5. private String nickname;
  6. //提供get/set方法 Alt+Insert
  7. public User() {
  8. }
  9. public User(int id, String username, String password, String nickname) {
  10. this.id = id;
  11. this.username = username;
  12. this.password = password;
  13. this.nickname = nickname;
  14. }
  15. public int getId() {
  16. return id;
  17. }
  18. public void setId(int id) {
  19. this.id = id;
  20. }
  21. public String getUsername() {
  22. return username;
  23. }
  24. public void setUsername(String username) {
  25. this.username = username;
  26. }
  27. public String getPassword() {
  28. return password;
  29. }
  30. public void setPassword(String password) {
  31. this.password = password;
  32. }
  33. public String getNickname() {
  34. return nickname;
  35. }
  36. public void setNickname(String nickname) {
  37. this.nickname = nickname;
  38. }
  39. @Override
  40. public String toString() {
  41. return "User{" +
  42. "id=" + id +
  43. ", username='" + username + '\'' +
  44. ", password='" + password + '\'' +
  45. ", nickname='" + nickname + '\'' +
  46. '}';
  47. }
  48. }

JDBC 代码

  1. @Test
  2. public void fun01() throws Exception {
  3. //查询id为1的用户
  4. //1.注册驱动
  5. Class.forName("com.mysql.jdbc.Driver");
  6. //2.获得连接
  7. String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
  8. String username = "root";
  9. String password = "123456";
  10. Connection connection = DriverManager.getConnection(url, username, password);
  11. //3.创建执行sql语句对象
  12. Statement statement = connection.createStatement();
  13. //4.执行sql语句
  14. String sql = "select * from user where id = 1";
  15. ResultSet resultSet = statement.executeQuery(sql);
  16. User user = null;
  17. while (resultSet.next()) {
  18. //每遍历一次,就是一条数据.就是一个User对象(有数据才有user)
  19. user = new User(resultSet.getInt("id"),
  20. resultSet.getString("username"),
  21. resultSet.getString("password"),
  22. resultSet.getString("nickname"));
  23. }
  24. //获得用户名
  25. System.out.println("用户名="+user.getUsername());
  26. //5.释放资源
  27. if (resultSet != null) {
  28. resultSet.close();
  29. }
  30. if (statement != null) {
  31. statement.close();
  32. }
  33. if (connection != null) {
  34. connection.close();
  35. }
  36. }

2.5 执行查询多行数据的SQL语句

要求: 将查询到的多行数据封装到List

  1. @Test
  2. //查询所有用户
  3. public void fun02() throws Exception {
  4. //1.注册驱动
  5. //DriverManager.registerDriver(new Driver());
  6. //类全限定名(带包名), 加载Driver类, 静态代码块就会执行, 驱动就注册了
  7. Class.forName("com.mysql.jdbc.Driver");
  8. //2.获得连接(连接数据库)
  9. //连接数据库路径
  10. String url="jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";
  11. String username = "root";
  12. String password= "yy8266603";
  13. Connection connection = DriverManager.getConnection(url, username, password);
  14. //3.创建执行sql语句的对象
  15. Statement statement = connection.createStatement();
  16. //4.执行sql语句, 处理结果
  17. String sql = "select * from user";
  18. ResultSet resultSet = statement.executeQuery(sql);
  19. List<User> list = new ArrayList<User>();
  20. while (resultSet.next()){
  21. //每遍历一次就是一条数据, 就封装成一个User对象. 把封装的每一个User添加到list集合里面
  22. User user = new User(resultSet.getInt("id"),
  23. resultSet.getString("username"),
  24. resultSet.getString("password"),
  25. resultSet.getString("nickname")
  26. );
  27. list.add(user);
  28. }
  29. //获得第二个用户的用户名
  30. System.out.println(list);
  31. //5.释放资源(先创建的后关闭)
  32. if(resultSet != null){
  33. resultSet.close();
  34. }
  35. if(statement != null){
  36. statement.close();
  37. }
  38. if(connection != null){
  39. connection.close();
  40. }
  41. }

第三章 使用PreparedStatement处理CRUD

2.1 Statement存在的问题

2.1.1 每次执行一个SQL语句都需要先编译

  1. String sql1 = "insert into user values(null,'tq','77777','田七')";
  2. String sql2 = "insert into user values(null,'zl','666666','赵六')";
  3. String sql3 = "insert into user values(null,'zs','333333','张三')";
  4. //如果使用Statement执行上述SQL语句需要编译三次

2.1.2 sql语句拼接

  1. String username = "ww";
  2. String password= "555555";
  3. String nickname = "王五";
  4. String sql = "insert into user values(null,"+username+","+password+","+nickname+")";

2.1.3 sql注入

  1. String username = "hahahahha' or '1'='1"
  2. String sql = "SELECT * FROM user where username='" + username + "'";
  3. //结果会把所有数据都查询出来
  4. Statement st = conn.createStatement();
  5. ResultSet rs = st.executeQuery(sql);

2.2 PreparedStatement解决问题

2.2.1 预编译

PreparedStatement会先对参数化的SQL语句进行预编译,执行SQL语句的时候不会再进行编译

  1. String sql = "insert into user values(null,?,?,?)";
  2. //预编译
  3. PreparedStatement pstm = connection.prepareStatement(sql);
  4. //后续设置参数、执行添加多少条数据都不会再重新编译

2.2.2 避免sql拼接

  1. String username = "ww";
  2. String password= "555555";
  3. String nickname = "王五";
  4. String sql = "insert into user values(null,?,?,?)";
  5. //预编译
  6. PreparedStatement pstm = connection.prepareStatement(sql);
  7. //设置参数
  8. pstm.setObject(1, username);
  9. pstm.setObject(2, password);
  10. pstm.setObject(3, nickname);
  11. int count = pstm.executeUpdate();//此处不能传sql
  12. System.out.println(count);

2.2.3 防止SQL注入

PreparedStatement在进行预编译的时候,就已经确定好了SQL语句的格式,不会再因为SQL语句的拼接改变SQL语句的格式

  1. String username = "hahahahha' or '1'='1"
  2. String sql = "SELECT * FROM user where username=?";
  3. //即使输入'张三' or '1'= '1'也没问题
  4. PreparedStatement pst = conn.prepareStatement(sql);
  5. //中间加入设置?的值
  6. pst.setObject(1, username);
  7. ResultSet rs = pst.executeQuery();

2.3 获取自增长键值

2.3.1 获取自增长键值的应用场景

主要使用在一些复杂的业务中,在添加完主表的一条数据之后,要获取到这条数据的主键值,然后将该值添加进从表的外键字段

2.3.2 获取自增长键值的步骤

  1. 在预编译的时候,指定要返回自增长的key

    1. PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
  2. 在执行完添加数据的SQL语句之后,通过PreparedStatement的对象调用getGeneratedKeys()方法来获取自增长键值,遍历结果集

    1. ResultSet rs = pst.getGeneratedKeys();
  3. 遍历获取自增长的键值

    1. if(rs.next()){
    2. Object key = rs.getObject(1);
    3. System.out.println("自增的key值did =" + key);
    4. }

示例代码

  1. public class TestAutoIncrement {
  2. public static void main(String[] args) throws Exception{
  3. //1、注册驱动
  4. Class.forName("com.mysql.jdbc.Driver");
  5. //2、获取连接
  6. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true" +
  7. "&characterEncoding=utf8", "root", "123456");
  8. //3、执行sql
  9. String sql = "insert into user values(null,?,?,?)";
  10. /* * 这里在创建PreparedStatement对象时,传入第二个参数的作用,就是告知服务器端 * 当执行完sql后,把自增的key值返回来。 */
  11. PreparedStatement pst = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
  12. //设置?的值
  13. pst.setObject(1, "aobama");
  14. pst.setObject(2, "1234678");
  15. pst.setObject(3, "圣枪游侠");
  16. int i = pst.executeUpdate();
  17. System.out.println("添加了"+i+"行数据");
  18. //从pst中获取到服务器端返回的键值
  19. ResultSet rs = pst.getGeneratedKeys();
  20. //执行sql
  21. if(rs.next()){
  22. Object key = rs.getObject(1);
  23. System.out.println("自增的key值id =" + key);
  24. }
  25. //4、关闭
  26. pst.close();
  27. conn.close();
  28. }
  29. }

2.4 批处理

2.4.1 批处理优势和应用场景

批处理相比较单独一条条执行SQL语句来说,其效率高很多。批处理一般会使用在批量添加多条数据和批量修改多条数据

2.4.2 批处理的具体操作步骤

  1. 在url中要加一个参数 rewriteBatchedStatements=true,那么此时url就变成了

    1. jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
  2. 在完成所有参数设置之后,调用PreparedStatement的addBatch()方法,添加到批处理中
  3. 最后执行PreparedStatement的executeBatch()方法执行批处理语句

    public class TestBatch {

    1. public static void main(String[] args) throws Exception{
    2. long start = System.currentTimeMillis();
    3. //例如:在部门表t_department中添加1000条模拟数据
    4. //1、注册驱动
    5. Class.forName("com.mysql.jdbc.Driver");
    6. //2、获取连接
    7. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true", "root", "123456");
    8. //3、执行sql,sql语句不能用value,只能用values
    9. String sql = "insert into user values(null,?,?,?)";
    10. PreparedStatement pst = conn.prepareStatement(sql);
    11. //设置?的值
    12. for (int i = 1; i <=1000; i++) {
    13. pst.setObject(1, "aobama"+i);
    14. pst.setObject(2, "000000"+i);
    15. pst.setObject(3, "圣枪游侠"+i);
    16. //执行这个操作明显慢
    17. //pst.executeUpdate();
    18. pst.addBatch();//添加到批处理一组操作中,攒一块处理
    19. }
    20. pst.executeBatch();
    21. //4、关闭
    22. pst.close();
    23. conn.close();
    24. long end = System.currentTimeMillis();
    25. System.out.println("耗时:" + (end - start));//耗时:821
    26. }

    }

2.5 事务

2.5.1 事务操作的步骤

  1. 执行逻辑单元之前先开启事务
  2. 逻辑单元执行完毕,没有出现异常则提交事务
  3. 逻辑单元执行过程中出现异常,则回滚事务

2.5.2 事务相关API






















Connection中与事务有关的方法 说明
setAutoCommit(boolean autoCommit) 参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务; 类似sql里面的 start transaction;
void commit() 提交事务; 类似sql里面的 commit;
void rollback() 回滚事务; 类似sql里面的 rollback;

2.5.3 使用JDBC的事务完成转账案例

2.5.3.1 准备数据

  1. create table account(
  2. id int primary key auto_increment,
  3. name varchar(20),
  4. money double
  5. );
  6. insert into account values (null,'zs',1000);
  7. insert into account values (null,'ls',1000);
  8. insert into account values (null,'ww',1000);

2.5.3.2 代码实现

  1. import org.junit.Test;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. /** * * @author Leevi * 日期2021-04-29 11:40 * 事务相关的API: * 1. 开启事务 connection.setAutoCommit(false); * 2. 提交事务 connection.commit(); * 3. 回滚事务 connection.rollback(); * 4. 事务结束之后,要将此此接的autoCommit还原成true */
  6. public class TestTransaction {
  7. @Test
  8. public void testTransfer() throws Exception {
  9. //测试转账
  10. //1. 注册驱动
  11. Class.forName("com.mysql.jdbc.Driver");
  12. //2. 获得连接
  13. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8","root","yy8266603");
  14. //3. 预编译sql语句
  15. String sql = "update account set money=money+? where name=?";//改变用户的金额
  16. PreparedStatement preparedStatement = conn.prepareStatement(sql);
  17. //开启事务
  18. conn.setAutoCommit(false);
  19. try {
  20. //3.1 zs扣款500
  21. preparedStatement.setObject(1,-500);
  22. preparedStatement.setObject(2,"zs");
  23. //执行zs扣款的sql语句
  24. preparedStatement.executeUpdate();
  25. //张三付完款程序出现异常
  26. int num = 10/0;
  27. //3.2 ls收款500
  28. preparedStatement.setObject(1,500);
  29. preparedStatement.setObject(2,"ls");
  30. preparedStatement.executeUpdate();
  31. //提交事务
  32. conn.commit();
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. conn.rollback();
  36. }finally {
  37. //还原connection的AutoCommit为true
  38. conn.setAutoCommit(true);
  39. }
  40. //关闭资源
  41. preparedStatement.close();
  42. conn.close();
  43. }
  44. }

第三章 数据库连接池

3.1 什么是数据库连池

连接池是connection对象的缓冲区,它里面会存放一些connection,当我们Java程序需要使用connection的时候,如果连接池中有则直接从连接池获取,不需要去新创建connection了。连接池让Java程序能够复用连接、管理连接

3.2 为什么要使用连接池

  • 1.因为每次创建和销毁连接都会带来较大的系统开销
  • 2.每次创建和销毁连接都要消耗大概0.05~1s的时间。
  • 3.可以防止大量用户并发访问数据库服务器。

3.3 连接池的优势

  1. 资源重用

由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。

  1. 更快的系统响应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。

  1. 新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年前也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。

  1. 统一的连接管理,避免数据库连接泄漏

在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。

3.5 连接池的原理

  1. 连接池维护着两个容器空闲池活动池
  2. 空闲池用于存放未使用的连接,活动池用于存放正在使用的连接,活动池中的连接使用完之后要归还回空闲池
  3. 当Java程序需要连接时,先判断空闲池中是否有连接,如果空闲池中有连接则取出一个连接放置到活动池供Java程序使用
  4. Java程序需要连接时,如果空闲池中没有连接了,则先判断活动池的连接数是否已经达到了最大连接数,如果未达到最大连接数,则会新创建一个连接放置到活动池,供Java程序使用
  5. 如果空闲池中没有连接了,活动池中的连接也已经达到了最大连接数,则不能新创建连接了,那么此时会判断是否等待超时,如果没有等待超时则需要等待活动池中的连接归还回空闲池
  6. 如果等待超时了,则可以采取多种处理方式,例如:直接抛出超时异常,或者将活动池中使用最久的连接移除掉归还回空闲池以供Java程序使用
    在这里插入图片描述

3.6 连接池的实现

3.6.1 DataSource接口

JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),所有的Java数据库连接池都需要实现该接口。该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现

3.6.2 常见的数据库连接池

  • DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
  • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
  • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  • HikariCP 俗称光连接池,是目前速度最快的连接池
  • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池

3.6.3 Druid连接池的使用

(1)加入jar包

例如:druid-1.1.10.jar

阿里网盘
(2)代码步骤

第一步:创建druid连接池的配置文件druid.properties文件,放置到类路径下

  1. driverClassName=com.mysql.jdbc.Driver
  2. url=jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8
  3. username=root
  4. password=123456
  5. #初始化连接数
  6. initialSize=5
  7. # 最大活动连接数
  8. maxActive=10
  9. # 最大等待时间
  10. maxWait=1000

第二步:使用工厂模式创建DruidDataSource对象

  1. @Test
  2. public void test01() throws Exception {
  3. //1. 创建一个Properties对象,让其去读取druid.properties文件
  4. Properties properties = new Properties();
  5. //不建议使用
  6. //1.1 将druid.properties配置文件转成字节输入流
  7. // FileInputStream is = new FileInputStream("D:\\idea\\JDBC_TEST\\resources\\druid.properties");
  8. //使用相对路径来将配置文件转成字节输入流,我们可以使用类加载器来读取类路径下文件
  9. //TestDataSource.class.getClassLoader() 表示获取ClassLoader对象
  10. InputStream is = TestDataSource.class.getClassLoader().getResourceAsStream("druid.properties");
  11. //1.2 使用properties对象加载流
  12. properties.load(is);
  13. //2. 使用DruidDataSourceFactory创建Druid连接池对象
  14. DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
  15. }

第三步:使用连接池对象获取连接

  1. Connection connection = dataSource.getConnection();

3.6.4 Druid连接池的配置参数列表






























































































































配置 缺省 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

第四章 封装JDBCTools

配置文件:src/jdbc.properties 或者resources/jdbc.properties

  1. driverClassName=com.mysql.jdbc.Driver
  2. url=jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8
  3. username=root
  4. password=123456
  5. #初始化连接数
  6. initialSize=5
  7. # 最大活动连接数
  8. maxActive=10
  9. # 最大等待时间
  10. maxWait=1000

JDBCTools工具类:

  1. import com.alibaba.druid.pool.DruidDataSourceFactory;
  2. import javax.sql.DataSource;
  3. import java.io.InputStream;
  4. import java.sql.Connection;
  5. import java.sql.SQLException;
  6. import java.util.Properties;
  7. /** * 这个工具类中会提供仨方法: * 1. 获取连接池对象 * 2. 从连接池中获取连接 * 3. 将链接归还到连接池 */
  8. public class JDBCTools {
  9. private static DataSource dataSource;
  10. static {
  11. try {
  12. //1.使用类加载器读取配置文件,转成字节输入流
  13. InputStream is = JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties");
  14. //2.使用Properties对象加载字节输入流
  15. Properties properties = new Properties();
  16. properties.load(is);
  17. //3.使用DruidDataSourceFactory创建连接池对象
  18. dataSource = DruidDataSourceFactory.createDataSource(properties);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. public static DataSource getDataSource(){
  24. return dataSource;
  25. }
  26. /** * 获取连接 */
  27. public static Connection getConnection(){
  28. try {
  29. return dataSource.getConnection();
  30. } catch (SQLException e) {
  31. e.printStackTrace();
  32. throw new RuntimeException(e.getMessage());
  33. }
  34. }
  35. /** * 归还连接 */
  36. public static void releaseConnection(Connection connection) {
  37. try {
  38. connection.close();
  39. } catch (SQLException e) {
  40. e.printStackTrace();
  41. throw new RuntimeException(e.getMessage());
  42. }
  43. }
  44. }

测试

  1. @Test
  2. public void testDelete1() throws Exception {
  3. //测试删除id为2的用户
  4. //1.注册驱动:Druid框架层已经注册驱动了
  5. //2.获取连接
  6. Connection coon = JDBCTools.getConnection();
  7. //3.预编译sql语句
  8. String sql = "delete from user where id=?";
  9. PreparedStatement preparedStatement = coon.prepareStatement(sql);
  10. //4.设置参数
  11. preparedStatement.setObject(1,2);
  12. //5.执行sql语句
  13. preparedStatement.executeUpdate();
  14. //6.关闭资源
  15. preparedStatement.close();
  16. JDBCTools.releaseConnection(coon);
  17. }

连接池使用的总结

  1. 拷贝加入druid的jar包
  2. 拷贝druid的配置文件到类路径,并修改
  3. 拷贝JDBCTools工具类
  4. 在需要连接的地方编写Connection conn = JDBCTools.getConnection();此时拿到的连接就是从连接池拿的

    1. 连接使用完毕之后,调用JDBCTools.releaseConnection(conn);归还连接

第五章 Apache的DBUtils

阿里云盘

5.1 DBUtils的概述

commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

其中QueryRunner类封装了SQL的执行,是线程安全的。

(1)可以实现增、删、改、查、批处理、

(2)考虑了事务处理需要共用Connection。

(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

5.2 DBUtils执行增删改的SQL语句

5.2.1 API介绍

  1. QueryRunner() ,创建QueryRunner对象,用于执行SQL语句
  2. QueryRunner的update(Connection conn, String sql, Object… params)方法,用于执行增删改的SQL语句

5.2.2 代码实现

  1. @Test
  2. public void testInsert() throws SQLException {
  3. //创建queryRunner对象
  4. QueryRunner queryRunner = new QueryRunner();
  5. //String sql = "insert into user values(null,'aaa','111','露西')";
  6. String sql = "insert into user values(?,?,?,?)";
  7. Object[] params = { null, "aaa", "111", "露西"};
  8. queryRunner.update(JDBCTools.getConnection(), sql, params);
  9. }
  10. @Test
  11. //把用户名是aaa的user密码改成222
  12. public void testUpdate() throws SQLException {
  13. //创建queryRunner对象
  14. QueryRunner queryRunner = new QueryRunner();
  15. String sql = "update user set password = ? where username = ?";
  16. Object[] params = { "222", "aaa"};
  17. queryRunner.update(JDBCTools.getConnection(), sql, params);
  18. }
  19. //把用户名是aaa的user给删除
  20. @Test
  21. public void testDelete() throws SQLException {
  22. //创建queryRunner对象
  23. QueryRunner queryRunner = new QueryRunner();
  24. String sql = "delete from user where username = ?";
  25. Object[] params = { "aaa"};
  26. queryRunner.update(JDBCTools.getConnection(), sql, params);
  27. }

5.2.3 DBUtils执行批处理

5.2.3.1 API介绍

  1. public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: 支持批处理INSERT, UPDATE, or DELETE语句
  2. public <T> T insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object[][] params)throws SQLException:只支持INSERT语句

5.2.3.2 代码实现

  1. public class TestBatch {
  2. public static void main(String[] args) throws Exception {
  3. long start = System.currentTimeMillis();
  4. //例如:在部门表t_department中添加20条模拟数据
  5. QueryRunner queryRunner = new QueryRunner();
  6. //创建一个二维数组,用于存储批量参数,二维数组的第一维表示批量操作多少条数据,第二维表示每条数据设置多少个参数
  7. Object[][] params = new Object[20][3];
  8. //3、执行sql,sql语句不能用value,只能用values
  9. String sql = "insert into user values(null,?,?,?)";
  10. //设置?的值
  11. for (int i = 0; i <20; i++) {
  12. Object[] insertParams = new Object[3];
  13. insertParams[0] = "aobama"+i;
  14. insertParams[1] = "000000"+i;
  15. insertParams[2] = "圣枪游侠"+i;
  16. params[i] = insertParams;
  17. }
  18. queryRunner.batch(JDBCTools.getConnection(),sql,params);
  19. long end = System.currentTimeMillis();
  20. System.out.println("耗时:" + (end - start));//耗时:821
  21. }
  22. }

5.2.4 使用QueryRunner类实现查询

5.2.3 API介绍

  1. query(String sql, ResultSetHandler rsh, Object… params) ,执行查询 select
  2. ResultSetHandler结果集处理类
#













































Handler类型 说明
ArrayHandler 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值
ArrayListHandler 将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。
BeanHandler 将结果集中第一条记录封装到一个指定的javaBean中。
BeanListHandler 将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
ColumnListHandler 将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler 将结果集中每一条记录封装到Map<String,Object>,在将这个map集合做为另一个Map的value,另一个Map集合的key是指定的字段的值。
MapHandler 将结果集中第一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值
MapListHandler 将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中。
ScalarHandler 它是用于单个数据。例如select count(*) from 表。

5.2.4 代码实现

  1. //查询id为1的用户信息
  2. @Test
  3. public void selectById() throws SQLException{
  4. //创建queryRunner对象
  5. QueryRunner queryRunner = new QueryRunner();
  6. String sql = "select *from user where id = ?";
  7. Object[] params = { 1};
  8. User user = queryRunner.query(JDBCTools.getConnection(),sql, new BeanHandler<>(User.class), params);
  9. System.out.println(user.toString());
  10. }
  11. //查询所有的用户信息
  12. @Test
  13. public void selectAll() throws SQLException{
  14. //创建queryRunner对象
  15. QueryRunner queryRunner = new QueryRunner();
  16. String sql = "select *from user";
  17. Object[] params = { };
  18. List<User> list = queryRunner.query(JDBCTools.getConnection(),sql, new BeanListHandler<>(User.class), params);
  19. System.out.println(list.toString());
  20. }
  21. //统计用户的个数
  22. @Test
  23. public void getCount() throws SQLException{
  24. //创建queryRunner对象
  25. QueryRunner queryRunner = new QueryRunner();
  26. String sql = "select count(*) from user";
  27. Long n = (Long) queryRunner.query(JDBCTools.getConnection(),sql, new ScalarHandler());
  28. System.out.println(n.intValue());
  29. }

经典错误

1、jar包版本不兼容

  1. Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.
  2. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  3. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  4. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  5. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  6. at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
  7. at com.mysql.jdbc.Util.getInstance(Util.java:383)
  8. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1023)
  9. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:997)
  10. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:983)
  11. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:928)
  12. at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2576)
  13. at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2309)
  14. at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:834)
  15. at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:46)
  16. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  17. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  18. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  19. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  20. at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
  21. at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:419)
  22. at com.mysql.jdbc.NonRegisteringDriver.connect(Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary. Exception in thread "main" java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
  23. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
  24. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
  25. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
  26. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
  27. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:73)
  28. at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:76)
  29. at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:835)
  30. at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:455)
  31. at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240)
  32. at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:199)
  33. at java.sql.DriverManager.getConnection(DriverManager.java:664)
  34. at java.sql.DriverManager.getConnection(DriverManager.java:247)
  35. .java:344)
  36. at java.sql.DriverManager.getConnection(DriverManager.java:664)
  37. at java.sql.DriverManager.getConnection(DriverManager.java:247)

看异常好像是无事务连接异常,无法创建连接。将MySQL驱动改为了最新的8.0版本的MySQL驱动。显示那个驱动类已经过时了,新的驱动类是“com.mysql.cj.jdbc.Driver”,而不是“com.mysql.jdbc.Driver”了,并且还说我没有配置时区,查了一下,原来从JDBC6.0开始驱动类使用了新的,并且url中必须要设置时区,否侧会报错。

  1. 第一步:使用最新的MySQL驱动jar包。
  2. 第二步:把驱动的类名改为:
  3. static String driver="com.mysql.cj.jdbc.Driver";
  4. 第三步:在访问mysqlurl后加入时区设置:
  5. static String url="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC"

发表评论

表情:
评论列表 (有 0 条评论,339人围观)

还没有评论,来说两句吧...

相关阅读