当前位置:Gxlcms > 数据库问题 > JDBC详解

JDBC详解

时间:2021-07-01 10:21:17 帮助过:12人阅读

数据库创建 */ CREATE DATABASE `daodemodb` /*!40100 DEFAULT CHARACTER SET utf8 */; /*创建测试表*/ CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) DEFAULT NULL, `age` int(11) DEFAULT NULL, `money` decimal(10,2) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*数据准备*/ insert into t_user(name,age,money) values (张三,24,666.66), (李四,25,888.88), (王二,26,999.99), (小明,27,555.55), (小赵,28,333.33) View Code

按照上面的操作步骤,需要引入驱动,这里使用pom.xml引入mysql的jdbc驱动。

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.12</version>
    </dependency>

然后就是在代码中依次注册驱动、创建连接、执行操作、返回结果、释放资源步骤,下面代码演示的就是这个过程,从t_user表中查询数据并打印到日志中。

技术分享图片
package com.demo.jdbc;

import java.sql.*;
public class StatementDemo {

    public static void main(String[] args) throws SQLException {
        // TODO Auto-generated method stub
        Connection conn=null;
        Statement stmt=null;
        ResultSet rs=null;
        try{
            //注册驱动
            DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
            //通过注册的驱动获得连接对象Connection
            conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/daodemodb?useUnicode=true&characterEncoding=UTF-8"
                    + "&serverTimezone=UTC&useSSL=false","root","123456");
            //通过Statement对象执行操作 返回结果ResultSet
            stmt=conn.createStatement();
            //返回结果 
            rs=stmt.executeQuery("select * from t_user");
            while(rs.next()){
                System.out.println("姓名:"+rs.getString("name")+"  年龄:"+rs.getInt("age") +"  余额:"+rs.getDouble("money"));
                }
            }
        catch(SQLException e){
            System.out.println(e.getMessage());
            e.printStackTrace();
            }
        finally{
            //释放资源
            if(conn!=null){ conn.close(); }
            if(stmt!=null){ stmt.close(); }   
            if(rs!=null){ rs.close(); }
            }
    }

}
View Code

输出结果:

姓名:张三  年龄:24  余额:666.66
姓名:李四  年龄:25  余额:888.88
姓名:王二  年龄:26  余额:999.99
姓名:小明  年龄:27  余额:555.55
姓名:小赵  年龄:28  余额:333.33

4.使用PreparedStatement返回自增主键

实际上有三种 Statement 对象,它们都作为在给定连接上执行 SQL 语句的包容器:Statement、PreparedStatement(它从 Statement 继承而来)和 CallableStatement(它从 PreparedStatement 继承而来)。它们都专用于发送特定类型的 SQL 语句: Statement 对象用于执行不带参数的简单 SQL 语句;PreparedStatement 对象用于执行带或不带 IN 参数的预编译 SQL 语句;CallableStatement 对象用于执行对数据库已存在的存储过程的调用。Statement 接口提供了执行语句和获取结果的基本方法。PreparedStatement 接口添加了处理 IN 参数的方法;而 CallableStatement 添加了处理 OUT 参数的方法。
这里向t_user表中插入一条数据,并返回自增主键id的值。在准备sql时使用?来做参数的占位符,在实例化PreparedStatement 对象之后对sql进行传参,这样也能防止注入式攻击。

技术分享图片
package com.demo.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class PreparedStatementDemo {

    public static void main(String[] args) throws SQLException {
        // TODO Auto-generated method stub
        Connection conn=null;
        PreparedStatement prestmt=null;
        ResultSet rs=null;
        try{
            //注册驱动
            DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
            //通过注册的驱动获得连接对象Connection
            conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/daodemodb?useUnicode=true&characterEncoding=UTF-8"
                    + "&serverTimezone=UTC&useSSL=false","root","123456");
            //PreparedStatement对象 
            String sql="insert into t_user (name,age,money) values(?,?,?)";
            prestmt=conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
            prestmt.setString(1, "小李");
            prestmt.setInt(2, 25);
            prestmt.setDouble(3,222.22);
            //返回结果
            int result=prestmt.executeUpdate();
            if(result>0)
            {
                System.out.println("新增成功");
                rs=prestmt.getGeneratedKeys();
                while(rs.next())
                {
                    System.out.println("生成的主键ID为:"+rs.getInt(1));
                }
            } 
            }
        catch(SQLException e){
            System.out.println(e.getMessage());
            e.printStackTrace();
            }
        finally{
            //释放资源
            if(conn!=null){ conn.close(); }
            if(prestmt!=null){ prestmt.close(); }   
            if(rs!=null){ rs.close(); }
            }
    }

}
View Code

输出结果:
新增成功
生成的主键ID为:6

5.使用CallableStatement调用存储过程

在使用数据库的过程中,可能会调用存储过程,可以使用CallableStatement来调用存储过程。调用存储函数 1.{?= call <procedure-name>[(<arg1>,<arg2>, ...)]} 。调用存储过程 2.{call <procedure-name>[(<arg1>,<arg2>, ...)]}。通过CallableStatement对象的registerOutParameter() 方法注册Out参数。通过CallableStatement对象的setXxx()方法设定IN或In out 参数,若想将参数设为null,可以使用setNUll()。如果所调用的是带返回参数的存储过程还需要通过CallableStatement对象的getXxx()获取输出参数。
在数据库中创建了存储过程p_selectUserByAge,根据用户年龄查找用户,存储过程一个传入参数age,一个传出参数count,参数count存放根据年龄查找的用户个数。

技术分享图片
CREATE  PROCEDURE `p_selectUserByAge`(age int, out count int)
BEGIN
  set count=(select count(1) from t_user t where t.age =age);
  select * from  t_user t where t.age =age;
END
View Code

在下面的代码中先使用Connection的prepareCall方法来实例化CallableStatement,再使用CallableStatement对象的registerOutParameter方法设置传入参数,最后执行存储过程返回结果。

技术分享图片
package com.demo.jdbc;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;

public class CallableStatementDemo {

    public static void main(String[] args) throws SQLException {
        // TODO Auto-generated method stub
        Connection conn=null;
        CallableStatement callstmt=null;
        ResultSet rs=null;
        try{
            //注册驱动
            DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
            //通过注册的驱动获得连接对象Connection
            conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/daodemodb?useUnicode=true&characterEncoding=UTF-8"
                    + "&serverTimezone=UTC&useSSL=false","root","123456");
            //CallableStatement对象 
            callstmt=conn.prepareCall("{call p_selectUserByAge(?,?)}");
            callstmt.setInt(1,25);
            //设置传入参数
            callstmt.registerOutParameter(2, Types.INTEGER);
            rs=callstmt.executeQuery();
            while(rs.next())
            {
                System.out.println("姓名:"+rs.getString("name")+"  年龄:"+rs.getInt("age") +"  出生日期:"+rs.getDouble("money"));
            }
            System.out.println("存储过程返回值:"+callstmt.getInt(2));
            }
        catch(SQLException e){
            System.out.println(e.getMessage());
            e.printStackTrace();
            }
        finally{
            //释放资源
            if(conn!=null){ conn.close(); }
            if(callstmt!=null){ callstmt.close(); }   
            if(rs!=null){ rs.close(); }
            }
    }
    
}
View Code

输出结果:
姓名:李四  年龄:25  出生日期:888.88
姓名:小李  年龄:25  出生日期:222.22
存储过程返回值:2

6.批处理

在实际开发中往往会批量执行sql,Statement和PreparedStatement 都支持批量执行sql语句。但这些sql必须是Insert、update、delete这种执行后返回一个int类数表示影响的行数。 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行静态 SQL 语句并返回它所生成结果的对象。Statement和PreparedStatement 都是通过addBatch()方法添加一条sql语句,通过executeBatch()方法批量执行sql语句,返回一个int类型的数组,表示各sql的返回值,这样就减少了注入驱动、创建连接这些步骤,提升了效率。首先看下Statement批处理的例子:

技术分享图片
package com.demo.jdbc;
import java.sql.*;

public class StatementSQLBatch {

    public static void main(String[] args) throws SQLException {
        // TODO Auto-generated method stub
        Connection conn=null;
        
        ResultSet rs=null;
        Statement stmt=null;
        try{
            //注册驱动
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());
            //通过注册的驱动获得连接对象Connection
            conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/daodemodb?useUnicode=true&characterEncoding=UTF-8"
                + "&serverTimezone=UTC&useSSL=false","root","123456");
            
            stmt=conn.createStatement();
            for(int i=0;i<2;i++)
            {
                String sql="insert into t_user (name,age,money) values(‘StatementTest"+i+"‘,"+25+i+",222.22)";
                stmt.addBatch(sql);
            }
            //批处理
            int [] result=stmt.executeBatch();
            System.out.println("影响的行数分别为:");
            for(int i=0;i<result.length;i++)
            {
                System.out.print(result[i]+"  ");
            }
        }
        catch(SQLException e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        finally
        {
            //释放资源
            if(conn!=null)
            {
                conn.close();
            }
            if(stmt!=null)
            {
                stmt.close();
            }
            if(rs!=null)
            {
                rs.close();
            }
        }
    }
    
}
View Code

由于Statement无法传递参数,必须是完整的sql语句,所以先将sql拼接之后通过addBatch(sql)方法加入到批处理中,然后通过executeBatch方法执行批处理返回影响行数的数组。

输出结果:

影响的行数分别为:
1  1 
PreparedStatement即可以是完整的sql,也可以用带参数的不完整的sql。我们看下使用PreparedStatement进行批处理的例子。

技术分享图片
package com.demo.jdbc;

import java.sql.*;

public class PreparedStatementSQLBatch {
    
    public static void main( String[] args ) throws SQLException
    {
        Connection conn=null;
        
        ResultSet rs=null;
        PreparedStatement prestmt=null;
        try{
            //注册驱动
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());
            //通过注册的驱动获得连接对象Connection
            conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/daodemodb?useUnicode=true&characterEncoding=UTF-8"
                + "&serverTimezone=UTC&useSSL=false","root","123456");
            String sql="insert into t_user (name,age,money) values(?,?,?)";
            prestmt=conn.prepareStatement(sql);
            for(int i=0;i<2;i++)
            {
                prestmt.setString(1, "PreparedStatementTest"+i);
                prestmt.setInt(2, 25+i);
                prestmt.setDouble(3,222.22);
                prestmt.addBatch();
            }
            //批处理
            int [] result=prestmt.executeBatch();
            System.out.println("影响的行数分别为:");
            for(int i=0;i<result.length;i++)
            {
                System.out.print(result[i]+"  ");
            }
        }
        catch(SQLException e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        finally
        {
            //释放资源
            if(conn!=null)
            {
                conn.close();
            }
            if(prestmt!=null)
            {
                prestmt.close();
            }
            if(rs!=null)
            {
                rs.close();
            }
        }
    }
    
}
View Code

这里使用占位符?来初始化sql,然后通过不带参数的addBatch加入批处理中,最后还是通过executeBatch执行批处理操作。
执行结果:
影响的行数分别为:
1  1
上面演示了Statement、PreparedStatement批处理的使用,这里还要说明一下,批量执行sql需要数据库的支持,有些数据库可能不支持。批量处理将多条sql语句提交给数据库一块执行,效率高一些,但如果数据比较多,比如几万条sql,就需要分批次执行,例如200条执行一次,如果为了增加一致性,可以在批量处理的基础上增加上事务。

7.事务处理

关系型数据库一般都支持事务。事务有四大特性:原子性、一致性、隔离性、持久性。
原子性:原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。例如转账,A账户转给B账户,包含两个操作,将A账户的钱减去然后将B账户的加上对应的钱数,不可能A账户减了B账户没加上,也不可能A账户没减就给B账户加上了。要么两个操作都成功要么都失败。
一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。假设账户A和账户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。这个就涉及到隔离级别的问题了。
隔离性:隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
本例中使用t_user表的两个用户来模拟转账操作。目前李四账户有888.88元,张三账户有666.66元。这里用户李四给用户张三转账111.11元,两个账户都有777.77元。

技术分享图片

事务有两个结果,一是成功二是回滚,如果在事务中任何一个操作发生异常就会回滚。

上面代码只是做测试并未做金额是否满足转账要求检查,先使用 conn.setAutoCommit(false);将自动提交设置为手动提交,默认是自动,然后批量执行两个sql语句,在conn.commit();提交事务之前如果没出现错误,执行结果会保存到数据库,一旦出现异常,就会执行conn.rollback();回滚操作。执行上面代码转账成功输出结果如下:

技术分享图片

上面是提交成功的例子,为了演示事务回滚,可以在提交事务conn.rollback();之前制造一个异常int a=1/0;,然后执行,发现数据库的值并不会改变,并抛出了异常。

技术分享图片

JDBC详解

标签:姓名   money   proc   127.0.0.1   编写   多个   标准   call   img   

人气教程排行