嘚儿驾...

JDBC

1 JDBC 的基本概念

JDBC 概念: Java DataBase Connectivity ,java 数据库连接,java 语言操作数据库

本质:

​     程序员(Coder),写 java 代码,去操作数据库,但是市场上面的关系型数据库很多,比如说(MySQL,Oracle,DB2)每种关系型数据库都有自己的一套规范,这时候,想用一套 java 代码操作所有的关系型数据库,是行不通的。

所以呢,就期望使用统一的一套,java 代码,去操作所有的关系型数据库。

​      官方,就是,Sun 公司的程序员写了一套 java 代码,起名叫 JDBC,定义了操作所有关系数据库的规则(接口)。 定义了一套接口。

​      这个时候,所有的数据库厂商都使用这一套接口,而具体的实现类,都是这些数据库厂商自己实现的。(而这每种关系型数据的实现类,有个名字,叫做 数据库驱动

简述 JDBC 本质:

​     其实是官方(sun 公司)定义了一套操作所有关系型数据库的规则,即接口。各个数据库 厂商去实现这套接口(JDBC),提供数据库驱动 jar 包。我们可以使用这套接口编程,真正执行的代码是驱动 jar 包中的实现类,对应的实现方法。

2 快速入门

了解 java 语言如何操作数据库,流程,步骤

步骤:

  1. 导入驱动 jar 包

  2. 注册驱动 (让程序知道使用的是哪一个驱动包,哪个版本)

  3. 获取数库连接对象 Connection (这个对象就是本地的 java 代码和数据裤之间的桥梁对象)

  4. 定义 sql 语句(通过一些方式将 sql 语句发送给数据库,从而达到 java 代码操作数据库的要求)

  5. 获取执行 sql 语句的对象 Statement

  6. 执行 sql ,接收返回结果

  7. 处理结果

  8. 释放资源

    为什么需要释放jdbc资源?

    提问:
     	Connection、Statement 和 ResulSet?这三个对象是在方法内部定义的,则这三个对象不是在方法执行完毕就消失了么,为什么还要单独去关闭它们呢?
    解答:
     这个连接是与数据库服务器的一个连接,虽然你的方法结束了,但是这个资源依然存在,数据库连接并没有释放。
     
    提问:
     	为什么在 JDBC 对数据库访问结束后,要按先关闭 ResultSet,然后关闭 PreparedStatement ,最后关闭 Connection,直接关闭 Connection 不就行了吗?
    解答:
    1. 感觉上好象是只要把connection给关闭了,系统就能正常运行了。  那在查询或是其它操作中,如果只关闭Connection,不作 ResultSet 和 Statement 的关闭的话,对系统性能是否会有影响呢?或者是其它实方面的不良影响。
       如果你不使用连接池,那么就没有什么问题,一旦 Connection 关闭,数据库物理连接就被释放,所有相关 Java 资源也可以被G C 回收了。    
       但是如果你使用连接池,那么请注意,Connection 关闭并不是物理关闭,只是归还连接池,所以 PreparedStatement 和 ResultSet 都被持有,并且实际占用相关的数据库的游标资源,在这种情况下,只要长期运行,往往就会报“游标超出数据库允许的最大值”的错误,导致程序无法正 常访问数据库
    2. 因为你打开的时候有顺序,
        打开时:Connection -> PreparedStatement -> ResultSet
        关闭时:ResultSet-> PreparedStatement -> Connection
        这个就像 栈,后进先出
        
        
    
    package com.kang.jdbc;
    
    import java.sql.*;
    
    public class jdbcDemo04 {
        public static void main(String[] args) {
            Connection conn = null;
            Statement stmt = null;
    
            try {
                // 0. 添加驱动
                // 1. 注册驱动
                Class.forName("com.mysql.cj.jdbc.Driver");
                // 2. 获取 Connection 对象
                conn = DriverManager.getConnection("jdbc:mysql:///mybatis?serverTimezone=Asia/Shanghai", "root", "root");
                // 3. 定义 sql
                String sql = "delete from users where id = 1";
                // 4. 获取执行 sql 的对象
                stmt = conn.createStatement();
                // 5. 执行 sql
                int count = stmt.executeUpdate(sql);
                // 6. 处理结果
                System.out.println(count);
                if (count != 0) {
                    System.out.println("Success!");
                } else {
                    System.out.println("Fail!");
                }
    
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                // 7. 释放资源
                if (stmt != null)
                    try {
                        stmt.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                if (conn != null)
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
            }
        }
    }
    
    

3 详解各个对象

1、 DriverManager:

​ (驱动管理对象)

功能:

  • 注册驱动 (告诉程序该使用哪一个数据库驱动 jar )

    static void registerDriver(Driver driver) :注册给定的驱动程序 DriverManager

    写代码使用: Class.forName("com.mysql.cj.jdbc.Driver");(加载Driver 类,肯定有代码在类被加载的时候被执行,一定是放在 静态代码块中的。

    Class.forName 方法的作用,就是初始化给定的类。而我们给定的 MySQL 的 Driver 类中,它在静态代码块中通过 JDBC 的 DriverManager 注册了一下驱动。我们也可以直接使用 JDBC 的驱动管理器注册 mysql 驱动,从而代替使用 Class.forName。DriverManager.registerDriver(new Driver)

    通过查看源码发现:在 com.mysql.cj.jdbc.Driver 类中存在静态代码块,其实是 DriverManager 注册驱动,Class.forName("com.mysql.cj.jdbc.Driver"); 这样写比较简单

注意mysql 5 之后的驱动 jar 包可以省略注册驱动步骤,建议写上。

​ 原因: services 里写上了

  • 获取数据库连接

    • 方法:static Connection getConnection(String url, String user, String password)

    • url : 指定连接的路径

      语法:jdbc:mysql://ip地址(域名):端口号/数据库名称

      例子:jdbc:mysql://localhost:3306/mybatis

      细节: 如果连接的是本机的 mysql 服务器,并且 mysql 服务器默认端口是 3306 ,则 url 可以简写为: jdbc: mysql 😕//数据库名称

      jdbc:mysql:///mybatis?serverTimezone=Asia/Shanghai
      
    • user: 用户名

    • password:密码

2 、Connection:

数据库连接对象

功能:

  1. 获取执行 sql 的对象

    • Statement createStatement()
    • PrepareStatement prepareStatement(String sql)
  2. 管理事务:

    • 开启事务:void setAutoCommit(boolean autoCommit) 调用该方法设置参数为 false ,即开启事务
    • 提交事务: commit()
    • 回滚事务: rollback()

3、 Statement:

执行 sql 的对象

  1. boolean execute(String sql) : 可以执行任意 sql (了解一下,用的不多)

  2. int executeUpdate(String sql) : 执行 DML (insert ,update,delete)DDL (create , alert ,drop ) 语句。

    返回值(int):影响的行数, 可以通过这个影响的行数来判断 DML 语句是否执行成功,返回值 >0 的则执行成功,反之,则执行失败。

  3. ResultSet executeQuery(String sql) 执行 DQL(Select语句)

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

4、 ResultSet:

结果集对象:就是来封装查询的结果

  • boolean next() : 游标向下移动一行, 判断当前行是否是最后一行末尾(是否有数据)如果是则返回 false, 如果不是 则返回 true

  • getXxx(参数): 获取数据

    • ( Xxx 代表数据类型) 如 int getInt(),这个方法会返回一个 int 类型的值
    • 参数:
      1. int:代表列的编号 ,从 1 开始 。 如 getString (1) 获取第一列的数据
      2. String :代表列的名称。 如: getInt(id)

    ResultSet 初步使用:

public static void main(String[] args) {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    try {
        Class.forName("com.mysql.cj.jdbc.Driver");
        conn = DriverManager.getConnection("jdbc:mysql:///mybatis?serverTimezone=Asia/Shanghai", "root", "root");
        String sql = "select * from db1";
        stmt = conn.createStatement();
        // 结果集对象也是一个资源,不要忘记关闭
        rs = stmt.executeQuery(sql);

        // 处理结果
        // 1. 让游标向下移动一行,  (本来是指向字段名的,向下一行指向数据)
        rs.next();
        // 2. 获取数据
        int id = rs.getInt(1);
        String name = rs.getString("name");
        //  3. 打印数据
        System.out.println(id + "---" + name);
  
        // 只能打印一行数据 ,可以通过重复 复制处理结果代码,实现显示多条信息,且不知道有多少条数据,会报错, After end and resultSet 有很大的局限性  
        // 还可以改良,看看下面的  resultSet 真正使用方法
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {

        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

    }

}

ResultSet 真正使用:

  1. 游标向下移动一行
  2. 判断是否有数据
  3. 获取数据
public class JdbcDemo07 {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;


        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql:///mybatis?serverTimezone=Asia/Shanghai", "root", "root");
            String sql = "select * from db1";
            stmt = conn.createStatement();
            rs = stmt.executeQuery(sql);


/**
重要的在这里!
*/
            // 处理结果    rs.next() 返回的是 boolean 值,如果没有
            while (rs.next()) {
                // 循环判断游是否是最后一行末尾
                // 获取数据 ,打印数据
                int id = rs.getInt(1);
                String name = rs.getString("name");
                System.out.println(id + "---" + name);
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

练习:

​ 定义一个方法,查询 db1 表中的数据,将其封装为对象,然后装在集合(专门装载对象的容器),返回,打印。

1. 定义` user `类
2. 定义方法 `public List<Emp> findAll(){}`
3. 实现方法   ` select * from user`

create table mybatis.db1
(
    id   int(100) auto_increment
        primary key,
    name varchar(20) null
);

我们来实现一下:

  • 先定义一个 User 类,封装 db1 表数据的 JavaBean
public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    // 方便打印看效果
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 然后写 jdbc
/*
定义一个方法,查询 db1 表的数据将其封装为对象,然后装在集合,返回
 */
public class JdbcDemo08 {


    public List<User> findAll() {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        List<User> list = null;
        try {
            // 1. 注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2. 获取连接对象
            conn = DriverManager.getConnection("jdbc:mysql:///mybatis?serverTimezone=Asia/Shanghai", "root", "root");
            // 3.定义 sql
            String sql = "select * from db1";
            // 4. 获取执行 sql 的对象
            stmt = conn.createStatement();
            // 5. 执行 sql
            rs = stmt.executeQuery(sql);
            // 6. 处理结果:遍历结果集,封装对象,装在集合
            User user = null;  // 复用 引用
            list = new ArrayList<User>();
            while (rs.next()) {
                // 获取数据
                int id = rs.getInt("id");
                String name = rs.getString("name");

                // 创建 user 对象  并赋值    这里实现 引用的复用
                user = new User();
                user.setId(id);
                user.setName(name);


                // 装载集合
                list.add(user);
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }


        return list;
    }

// 测试
    public static void main(String[] args) {
        List<User> list = new JdbcDemo08().findAll();
        System.out.println(list);
    }

}

结果:

​ 问题来了!这样写代码的重复度特别高,每一次都要写,显然,这样写不太聪明,我们可以写一个工具类来简化一下代码。

接下来:

抽取 jdbc 的工具类: 目的 :简化代码!!

  1. 分析:

    • 注册驱动

    • 抽取一个方法获取对象

      • 需求:不想传递参数(麻烦),还得保证工具类的通用性。

      • 解决:配置文件。 前人想好的。。。

        • jdbc.properties

          url=

          user=

          password=

    • 抽取一个方法释放资源

JDBCUtils 工具类

/*
JDBC 工具类
 */
public class JDBCUtils {


    private static String url;
    private static String user;
    private static String password;
    private static String driver;

    /**
     * 获取连接
     * @return 连接对象
     * 在这里为了方便调用都写成,static
     */
    // 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块来完成这个事情
    static {
        // 读取资源文件,获取值


        try {
            // 1. 创建 Properties 集合类
            Properties pro = new Properties();

            // 获取 src 路径下的文件的方式 ----> ClassLoader  类加载器
            ClassLoader classLoader = JDBCUtils.class.getClassLoader();  // 获取 classLoader 对象
            // 传一个文件名,就能获取一个resource 资源  URL :统一资源定位符,可以定位一个文件的绝对路径
            URL res = classLoader.getResource("jdbc.properties");
            String path = res.getPath();
            System.out.println(path);


            // 2. 加载文件
            //pro.load(new FileReader("src/jdbc.properties"));
            pro.load(new FileReader(path));
            // 3. 获取数据,赋值
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            driver = pro.getProperty("driver");

            // 4. 注册驱动
            try {
                Class.forName(driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }


    // executeUpdate 和 executeQuery 两个方法返回的东西不一样,后者还要返回 resultSet 多了一个释放的资源,一共要释放3个资源
    // 所以这里用重载写一下

    /**
     * 释放资源
     *
     * @param stmt
     * @param conn
     */
    public static void close(Statement stmt, Connection conn) {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

还是上面的例子,演示 JDBCUtils

/*
测试 JDBC 工具类
定义一个方法,查询 db1 表的数据将其封装为对象,然后装在集合,返回
 */
public class JdbcDemo09 {


    public List<User> findAll2() {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        List<User> list = null;
        try {
            conn = JDBCUtils.getConnection();
            // 3.定义 sql
            String sql = "select * from db1";
            // 4. 获取执行 sql 的对象
            stmt = conn.createStatement();
            // 5. 执行 sql
            rs = stmt.executeQuery(sql);
            // 6. 处理结果:遍历结果集,封装对象,装在集合
            User user = null;  // 复用 引用
            list = new ArrayList<User>();
            while (rs.next()) {
                // 获取数据
                int id = rs.getInt("id");
                String name = rs.getString("name");

                // 创建 user 对象  并赋值    这里实现 引用的复用
                user = new User();
                user.setId(id);
                user.setName(name);


                // 装载集合
                list.add(user);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {

            JDBCUtils.close(rs,stmt,conn);
        }


        return list;
    }

    // 测试
    public static void main(String[] args) {
        List<User> list = new JdbcDemo09().findAll2();
        System.out.println(list);
    }

}

同样也是可以的!


登录案例:

需求:

​ 1、通过键盘录入用户名和密码

​ 2、判断用户是否登录成功

select * from user1 where username = "" and password ="";

如果这个 sql 有查询结果,登录成功,反之则失败

步骤:

​ 1、创建一个数据库表 user 表

create table user1(
    id int primary key auto_increment,
    username varchar(32),
    password varchar(32)
);

实现:

/*需求:

        1、通过键盘录入用户名和密码

        2、判断用户是否登录成功*/
public class JdbcDemo10 {


    public static void main(String[] args) {
        // 1. 键盘录入,接受用户名和密码
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.nextLine();
        System.out.println("请输入密码:");
        String password = input.nextLine();
        // 2. 调用方法
        boolean flag = new JdbcDemo10().login(username, password);
        if (flag){
            // 登录成功
            System.out.println("登录成功!");
        }else{
            System.out.println("用户名或密码错误!");
        }
    }

    /**
     * 登录方法
     */

    public boolean login(String username, String password) {
        if (username == null || password == null) {
            return false;
        }
        
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        // 连接数据库来判断是否登录成功
        try {
            // 1.获取连接
            conn = JDBCUtils.getConnection();
            // 2. 定义 sql
            String sql = "select * from user1 where username='" + username + "' and password = '" + password + "'";
            // 3. 获取执行 sql 的对象
            stmt = conn.createStatement();
            // 4. 执行 sql
            rs = stmt.executeQuery(sql);
            // 5. 判断
/*            if(rs.next()){   // 如果有下一行,则返回 true
                return true;
            }else{
                return false;
            }  // 这样写代码太垃圾了,本来就是 个布尔值 下面这样写*/

            return rs.next();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.close(rs,stmt,conn);

        }

		// 这里不知道具体情况,先返回个 false ,防止报错
        return false;
    }

}

5、 prepareStatement:

执行 sql 对象 继承 Statement,功能比 Statement 更强大

上面的登录,写的其实是有问题的,会有 sql 注入问题。

居然登录成功了? 这个就是 sql 注入问题了。

  1. sql 注入问题:在拼接 sql 时,有一些 sql 的特殊关键字参与字符串的拼接。会造成安全性问题。

    • 输入用户随便,输入密码:a' or 'a' = 'a
    • sql :select * from user1 where username = 'sdf' and password = a' or 'a' = 'a' 这句 sql 最后结果是 true。 所以会查询出来所有信息。
  2. 解决 sql 注入问题:使用 PreparedStatement 对象来解决

  3. 预编译 SQL: 参数使用 ? 作为占位符

  4. 步骤:

    1. 导入驱动 jar 包

    2. **注册驱动 **

    3. **获取数库连接对象 **

    4. 定义 sql 语句

      注意:sql 的参数所使用 ?作为占位符,如select * from user1 where username = ? and password = ? ;

    5. 获取执行 sql 语句的对象 preparedStatement ( Connection.prepareStatement(String sql))

    6. ? 去赋值

      方法:setXxx( 参数1,参数2)

      ​ 参数1:? 的位置从 1 开始

      ​ 参数2: ? 的值

    7. 执行 sql ,接收返回结果,不需要传递 sql

    8. 处理结果

    9. 释放资源

  5. 注意:后期都会使用 PrepareStatement 来完成增删改查的所有操作

    1、可以防止 sql 注入

    2、效率更高


我们把上面的登录案例改进一下


/*需求:

​			1、通过键盘录入用户名和密码

​			2、判断用户是否登录成功*/
public class JdbcDemo11 {


    public static void main(String[] args) {
        // 1. 键盘录入,接受用户名和密码
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.nextLine();
        System.out.println("请输入密码:");
        String password = input.nextLine();
        // 2. 调用方法
        boolean flag = new JdbcDemo11().login(username, password);
        if (flag){
            // 登录成功
            System.out.println("登录成功!");
        }else{
            System.out.println("用户名或密码错误!");
        }
    }

    /**
     * 登录方法  使用 prepareStatement 实现
     */

    public boolean login(String username, String password) {
        if (username == null || password == null) {
            return false;
        }
        // 连接数据库来判断是否登录成功
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 1.获取连接
            conn = JDBCUtils.getConnection();
            // 2. 定义 sql
            String sql = "select * from user1 where username= ? and password = ? ";
            // 3. 获取执行 sql 的对象
             pstmt = conn.prepareStatement(sql);
             // 给 ? 赋值
            pstmt.setString(1,username);
            pstmt.setString(2,password);
            // 4. 执行 sql 的时候,不需要传参
             rs = pstmt.executeQuery();
            // 5. 判断下一行有没有下一行    sql 查出东西没有,
            return rs.next();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.close(rs,pstmt,conn);

        }


        return false;
    }

}


4 JDBC 控制事务

  1. 事务:一个包含多个步骤的业务操作,如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。

  2. 操作:

    • 开启事务
    • 提交事务
    • 回滚事务
  3. 使用的是Connection 对象来管理事务

    • 开启事务:void setAutoCommit(boolean autoCommit) 调用该方法设置参数为 false ,即开启事务

      在执行 sql 之前开启事务 事务开始的地方

    • 提交事务: commit()

      当所有 sql 执行完之后提交事务

    • 回滚事务: rollback() 回滚到事务开始的地方,一般在财务转账的时候用的较多.防止扣钱成功,却加钱失败的现象.

      在 catch 中回滚事务

事务的管理

要知道 什么时候开事务,操作都执行完了,提交事务,一旦操作异常,回滚事务

事务回滚和提交只会执行一个,提交就是正常执行,回滚就是不正常执行.

实现:

/**
 * 事务操作
 */
public class JdbcDemo12 {


    public static void main(String[] args) {

        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;

        try {
            // 1. 获取连接
            conn = JDBCUtils.getConnection();

            // 开启事务  事务手动提交
            conn.setAutoCommit(false);

            // 2. 定义 sql
            // 2.1  张三 -500
            String sql1 = "update account set balance = balance - ? where id = ?";
            // 2.2  李四 +500
            String sql2 = "update account set balance = balance + ? where id = ?";
            // 3. 获取执行 SQL 的对象
            pstmt1 = conn.prepareStatement(sql1);
            pstmt2 = conn.prepareStatement(sql2);
            // 4. 设置参数
            pstmt1.setDouble(1,500);
            pstmt1.setInt(2,1);


            pstmt2.setDouble(1,500);
            pstmt2.setInt(2,2);

            // 5.执行 sql
            int count = pstmt1.executeUpdate();

            // 手动设置异常
            int i= 3/0 ;
            int count1 = pstmt2.executeUpdate();

            // 提交事务
            conn.commit();
        } catch (Exception e) {
            // 事务回滚
            try {
                if (conn != null)
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }finally{
            JDBCUtils.close(pstmt2,null);
            JDBCUtils.close(pstmt1,conn);
        }
    }


}

如何优化和简化 JDBC 的东西呢 ?

5 数据库连接池

简述


为什么要使用数据库连接池呢?

获取连接的速度快了,我们知道 向系统底层申请连接是很耗时的,优化的用户体验

  1. 数据库连接池概念:其实就是一个容器(集合),存放数据的连接对象的容器。

​ 当系统初始化好之后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

  1. 好处:
  • 节约了系统的资源
  • 用户访问高效
  1. 实现

    1、标准接口:DataSource javax.sql包下的

    • 方法
      • 获取连接对象:getConnection
      • 归还连接:如果连接对象Connection是从连接池中获取的,那么调用 Connection.close() 方法,则不会再关闭连接,而是归还连接
    • 一般我们不去实现它,由数据库厂商来实现
      • C3P0:数据库连接池技术
      • Druid: 数据库连接池技术实现技术 ,由阿里巴巴提供的 ,号称全世界最好的连接池结束之一

C3P0

数据库连接池技术

步骤:

  1. 导入 jar 包 (三个包,c3p0 jar 包,它的依赖包,还有数据库驱动 jar 包) ( c3p0-0.9.5.5.jar 、 mchange-commons-java-0.2.19.jar

  2. 两种使用方式,一种是自己定义,另一种是使用配置文件的方式,我们一般使用配置文件的形式。 如下:

    定义配置文件:

    • 名称:必须是C3P0.properties or C3P0-config.xml
    • 路径:直接将文件放在 src 目录下即可。
  3. 创建核心对象 数据库连接池对象 CombopooledDateSource

  4. 获取连接 : getConnection

简单实现:

public class C3P0Demo02 {
    public static void main(String[] args) throws SQLException {
        // 1. 获取数据库连接池对象   什么都没传,使用默认配置,
       // DataSource ds = new ComboPooledDataSource();
        // 1.1 获取 DataSource ,使用指定的名称配置
        DataSource ds = new ComboPooledDataSource("otherc3p0");
        // 2. 获取连接
        for (int i = 0; i <= 7; i++) {
            Connection conn = ds.getConnection();
            System.out.println(i+":"+conn);

//            if(i == 5){
//                conn.close(); // 归还连接到连接池中
//
//            }
        }
    }
}

C3P0-config.xml

<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!-- 连接池参数 -->
        <!--初始化申请的连接数量-->
        <property name="initialPoolSize">5</property>
        <!--最大的连接数量-->
        <property name="maxPoolSize">10</property>
        <!--超时时间,3 秒之后才会报错 当连接池用完时客户端调用 getConnection() 后等待获取新连接的时间,超时后将抛出    -->
        <property name="checkoutTimeout">3000</property>
    </default-config>

    <named-config name="otherc3p0">
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!-- 连接池参数 -->
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">3000</property>
    </named-config>
</c3p0-config>

Druid

数据库连接池实现技术 由阿里巴巴提供的,号称全世界最好的连接池结束之一

步骤:

  1. 导入 jar 包

  2. 定义配置文件:

    • properties 形式的
    • 可以是任意名称的,可以放在任意的目录下
  3. 加载配置文件 Properties

  4. 获取数据库连接池对象:通过工厂来获取 DruidDataSourceFactory

  5. 获取连接 getConnection()

代码演示:

public class DruidDemo01 {

    public static void main(String[] args) throws Exception {
        // 1. 导入 jar 包
        // 2. 定义配置文件
        // 3. 加载配置文件
        // 属性集对象
        Properties pro = new Properties();
        // 读取配置文件
        InputStream is = DruidDemo01.class.getClassLoader().getResourceAsStream("druid.properties");
        pro.load(is);
        // 4. 获取连接池对象
        DataSource ds = DruidDataSourceFactory.createDataSource(pro);
        // 5. 获取连接
        Connection conn = ds.getConnection();
        System.out.println(conn);

    }


}

那我们实际怎么使用呢?

定义工具类

  • 定义一个类 JDBCUtils
  • 提供静态代码块加载配置文件,初始化连接对象
  • 提供方法
    1. 获取连接的方法:通过数据库连接池获取连接
    2. 释放资源
    3. 获取连接池的方法

上代码:

DruidUtils

/**
 * Druid 连接池的工具类
 */
public class JDBCUtils {
    // 1. 定义一个成员变量  DataSource
    private static DataSource ds;


    static {

        try {
            // 1. 加载配置文件
            Properties pro = new Properties();
            pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            // 2. 获取 DataSource
             ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取连接对象
     */
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    // 释放资源
    public static void close(Statement stmt, Connection conn) {
       /* if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();   // 归还连接
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }*/

       close(null,stmt,conn);
    }

    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();   // 归还连接
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /*
    获取连接池的方法
    * */
    public static DataSource getDataSource(){
        return ds;
    }


}

这是一个工具类,接下类, 实际用一下吧!!

/**
需求
 * 完成添加操作,给 account 表添加一条记录
 * */
/*
演示druid 工具类
*/
public class DruidDemo02 {
    public static void main(String[] args) {

        /**
         * 完成添加操作,给 account 表添加一条记录
         * */
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // 1. 获取连接对象
            conn = JDBCUtils.getConnection();
            // 2. 定义 sql
            String sql = "insert into account values(null,?,?)";
            // 3. 获取 pstmt 对象
            pstmt = conn.prepareStatement(sql);
            // 4. 给 ? 赋值
            pstmt.setString(1, "王伟");
            pstmt.setDouble(2, 3000);
            // 5. 执行 sql
            int count = pstmt.executeUpdate();
            System.out.println(count);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 6. 释放资源
            JDBCUtils.close(pstmt, conn);
        }


    }
}

结果:

添加成功


6 Spring JDBC : JDBC Template

序:

​ 在使用了连接池之后,我们的连接对象复用性更高了!程序整体运行性能也更高了,但是在做具体的 jdbc操作的时候,还是比较麻烦,要定义 sql, 要执行 sql,要设置参数,要处理结果,尤其是 resultSet,要遍历结果集,封装对象,装在集合,非常麻烦! 这些体力活我们不想做!!

为了简化我们的开发步骤!

Spring JDBC

Spring,JavaEE 的灵魂框架!

Spring 框架对 jdbc 的简单封装,提供了一个JdbcTemplate对象简化 JDBC 的开发

posted @   走马!  阅读(96)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示