当前位置 博文首页 > 不负时光可怜人:JDBC、数据库连接池以及事务的基本概念与使用

    不负时光可怜人:JDBC、数据库连接池以及事务的基本概念与使用

    作者:[db:作者] 时间:2021-06-27 21:33

    1 关于JDBC

    JDBC 全称为:Java DataBase Connectivity,它是可以执?SQL 语句的Java API。由Sun公司提供接口,数据库厂商负责实现,在使用不同数据库时,只要用数据库厂商提供的数据库驱动程序即可,大大简化学习成本。

    1.1 步骤

    1. 导?MySQL或者Oracle驱动包,添加到路径
    2. 装载数据库驱动程序
    3. 获取到与数据库连接
    4. 获取可以执?SQL 语句的对象
    5. 执?SQL 语句
    6. 关闭连接
    //简单示例
    Class.forName(come.mysql.jdbc.Driver);
    
    String username="root";
    String password="123456";
    String connectUrl="jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncodeing=UTF-8";
    
    Connection conn=DriverManager.getConnection(connectionUrl,username,password);
    System.out.println("连接成功");
    
    conn.close();
    system.out.println("关闭连接")

    1.2 Connection 对象

    客户端与数据库所有的交互都是通过Connection 来完成的。常用方法如下:

    //创建向数据库发送sql 的statement对象。
    createcreateStatement ()
    //创建向数据库发送预编译sql 的PrepareSatement对象。
    prepareStatement (sql ) 
    //创建执?存储过程的callableStatement对象
    prepareCall (sql )
    //设置事务?动提交
    setAutoCommit (boolean autoCommit)
    //提交事务
    commit ()
    //回滚事务
    rollback (
    

    1.3 Statement对象

    Statement对象?于向数据库发送Sql语句,对数据库的增删改查都可以通过此对象发送sql语句完成,方法如下:

    //查询
    executeQuery(String  sql )
    //增删改
    executeUpdate (String  sql )
    //任意sql 语句都可以,但是?标不明确,很少?
    execute(String  sql )
    //把多条的sql 语句放进同?个批处理中
    addBatch (String  sql )
    //向数据库发送?批sql 语句执?
    executeBatch()
        
    
    PreparedStatement对象

    PreparedStatement 对象继承Statement对象,它?Statement对象更强?,使?起来更简单

    1. Statement 对象编译SQL 语句时,如果SQL 语句有变量,就需要使?分隔符来隔开,如果变量?常多,就会使SQL 变得?常复杂。PreparedStatement 可以使?占位符,简化sql的编写
    2. Statement 会频繁编译SQL 。PreparedStatement 可对SQL 进?预编译,提?效率,预编译的SQL 存储在PreparedStatement 对象中
    3. PreparedStatement 防?SQL 注?。【Statement 通过分隔符’++’, 编写永等式,可以不需要密码就进?数据库】
     //模拟查询id为2的信息
             String  id = "2" ;
             Connection connection = UtilsDemo.getConnection ();
             String  sql  = "SELECT * FROM users WHERE id = ?";
             PreparedStatement preparedStatement = 
    connection.preparedStatement(sql );
             //第?个参数表示第?个占位符【也就是?号】,第?个参数表示值是多少
             preparedStatement.setString(1,id);
             ResultSet resultSet = preparedStatement.executeQuery()
                  //模拟查询id为2的信息
             String  id = "2" ;
             Connection connection = UtilsDemo.getConnection ();
             String  sql  = "SELECT * FROM users WHERE id = ?";
             PreparedStatement preparedStatement = 
    connection.preparedStatement(sql );
             //第?个参数表示第?个占位符【也就是?号】,第?个参数表示值是多少
             preparedStatement.setString(1,id);
             ResultSet resultSet = preparedStatement.executeQuery()
    

    1.4 ResultSet对象

    ResultSet 对象代表Sql语句的执?结果,当Statement 对象执?executeQuery()时,会返回?个ResultSet对象
    ResultSet对象维护了?个数据?的游标【简单理解成指针】,调用ResultSet.next()?法,可以让游标指向具体的数据?,进?获取该?的数据。

    1.5 MetaData元数据

    对列字段的描述信息,在结果集中,不但由每一行的数据,还有元数据信息。包括列名、列标题、列类型等。对于增删改??,只有SQL 和参数是不同的,我们为何不把这些相同的代码抽取成?个?法?对于查询??,不同的实体查询出来的结果集是不?样的。我们要使?元数据获取结果集的信息,才能对结果集进?操作。

    • ParameterMetaData – 参数的元数据
    • ResultSetMetaData --结果集的元数据
    • DataBaseMetaData --数据库的元数据

    2 数据库连接池

    数据库的连接的建?和关闭是?常消耗资源的、频繁地打开、关闭连接造成系统性能低下。

    2.1 认识JDBC连接

    每一个JDBC连接,内部实现上是创建了一个TCP/Socket连接。在MySQL中可以查看连接的情况。一个连接上可以执行多次SQL交互,连接要及时关闭,否则会导致连接量过大,到达上限后便不会接收新的连接。

    SHOW STATUS LIKE 'Threads%' /*连接数量*/
    SHOW processlist	/*连接详情*/
    SHOW full processlist
    SHOW VARIABLES LIKE 'max_connections' /*查看最大连接数*/
    

    连接超时:当一个连接到MySQL之后,如果长时间未作任何SQL交互,会被认定超时而断开。其中,wait_timeout即为超时时间的设定

    SHOW GLOBAL VARIABLES LIKES '%timeout%'
    

    2.2 连接效率

    JDBC两种连接方式:

    • 短连接方式:需要时连接,用完了就关闭。
      • 优点:容易管理
      • 缺点:连接费时,运行效率低
    • 长连接方式:准备好一个连接,需要的时候就用,用完了不关闭。
      • 优点:运行效率高
      • 缺点:管理困难,在多线程环境下,并发难以处理。

    2.3 c3p0连接池库

    //一个DataSource指代一个数据源,内部有一个连接池,自动读取c3p0-config.xml的配置
    ComboPooledDataSource pool=new ComboPooledDataSource();
    //一个connection代表一次访问
    Connection conn=pool.getConnection();
    //下面建立statement对象,通过查询生成ResultSet
    
    //用完之后
    conn.close();//连接放回池子
    pool.close();
    
    1. 池子中取得的connection,在取出之前就已经通过用户名密码验证,可以直接使用。
    2. 当conn.close()时,并没有关闭底层的socket连接,仅仅是把这个连接放回池子里。
    3. 连接池一般创建为全局对象(public static),多个线程可以并发使用。

    3 Transaction事务

    一个业务单元:需要一次性地执行多个SQL操作,修改多个有关系的表。

    事务的一般使用模式:

    beginTrasaction();//开始事务
    try{
        sql1;
        sql2;
        sql3;
        commit()//提交,确认所有修改
    }catch(e){
    rollback()	//回滚,撤销所有修改
    }
    
    1. 原子性(Atomicity)
      事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
      回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
    2. 一致性(Consistency)
      数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
    3. 隔离性(Isolation)
      一个事务所做的修改在最终提交以前,对其它事务是不可见的。
    4. 持久性(Durability)
      一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
      可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。

    3.2 事务的使用

    举例:A向B转账,转账这个流程中如果出现问题,事务可以让数据恢复成?样【A账户的钱没变,B账户的钱也没变】。

    /*
        * 我们来模拟A向B账号转账的场景
        *   A和B账户都有1000块,现在我让A账户向B账号转500 块钱
        *
        * */
                //JDBC默认的情况下是关闭事务的,下?我们看看关闭事务去操作转账操作有什么问题
                //A 账户减去500 块
                String  sql  = "UPDATE a SET money=money-500 " ;
                preparedStatement = connection.prepareStatement (sql );
                preparedStatement.executeUpdate ();
                //B 账户多了500 块
                String  sql2 = "UPDATE b SET money=money+500";
                preparedStatement = connection.prepareStatement (sql2);
                preparedStatement.executeUpdate ();
    

    模拟出现问题的情况,显然,上?代码是会抛出异常的,我们再来查询?下数据。A账户少了500 块钱,B账户的钱没有增加。这明显是不合理的。

    	  //A 账户减去500 块
                String  sql  = "UPDATE a SET money=money-500 " ;
                preparedStatement = connection.prepareStatement (sql );
                preparedStatement.executeUpdate ();
          
          //这?模拟出现问题
                int  a = 3 / 0;
                String  sql2 = "UPDATE b SET money=money+500";
                preparedStatement = connection.prepareStatement (sql2);
                preparedStatement.executeUpdate ();
    

    通过事务解决上面的问题

    //开启事务,对数据的操作就不会?即?效。
                connection.setAutoCommit (false);
                
                //A 账户减去500 块
                String  sql  = "UPDATE a SET money=money-500 " ;
                preparedStatement = connection.prepareStatement (sql );
                preparedStatement.executeUpdate ();
                //在转账过程中出现问题
                int  a = 3 / 0;
                //B 账户多500 块
                String  sql2 = "UPDATE b SET money=money+500";
                preparedStatement = connection.prepareStatement (sql2);
                preparedStatement.executeUpdate ();
                
                //如果程序能执?到这?,没有抛出异常,我们就提交数据
                connection.commit ();
          //关闭事务【?动提交】
          connection.setAutoCommit (true);
                
            } catch (SQLException e) {
                try  {
                     //如果出现了异常,就会进到这?来,我们就把事务回滚【将数据变成原来那样】
                     connection.rollback ();
          //关闭事务【?动提交】
                     connection.setAutoCommit (true);
                } catch (SQLException e1) {
                     e1.printStackTrace();
                }
    

    上?的程序也?样抛出了异常,A账户钱没有减少,B账户的钱也没有增加。
    注意:当Connection遇到?个未处理的SQLException时,系统会?正常退出,事务也会?动回滚,但如果程序捕获到了异常,是需要在catch 中显式回滚事务的.

    3.3 事务的隔离级别

    数据库定义了4个隔离级别:

    1. Serializable【可避免脏读,不可重复读,虚读】
    2. Repeatable read 【可避免脏读,不可重复读】
    3. Read committed 【可避免脏读】
    4. Read uncommitted 【级别最低,什么都避免不了】

    分别对应Connection类中的4个常量

    1. TRANSACTION_READ_UNCOMMITTED
    2. TRANSACTION_READ_COMMITTED
    3. TRANSACTION_REPEATABLE_READ
    4. TRANSACTION_SERIALIZABLE

    脏读:一个事务读取到另外一个事务未提交的数据

    不可重复读:?个事务读取到另外?个事务已经提交的数据,也就是说?个事务可以看到其他事务所做的修改

    虚读(幻读):是指在?个事务内读取到了别的事务插?的数据,导致前后读取不?致。注:和不可重复读类似,但虚读(幻读)会读到其他事务的插?的数据,导致前后读取不?致

    MySQL数据库从入门到实战应用