查看原文
其他

JDBC初识

树莓蛋黄派 Java规途 2023-07-04
  • JDBC概述

    • 使用JDBC完成添加操作

    • 使用JDBC完成更新删除操作

    • 使用JDBC完成查询操作1

    • 使用JDBC完成查询操作2

    • 使用JDBC完成登录操作

  • JDBC初级操作

    • 使用PreparedStatement来模拟登录

    • 批处理

    • 获取自增id

    • 获取数据库表的元数据

  • JDBC高级操作

    • JDBC中使用事务

    • 数据库连接池

  • JDBC API总结

    • Connection接口

    • DriverManager

    • Statement接口

    • PreparedStatement接口

    • ResultSet接口

  • 往期文章回顾

Java数据库连接技术 JDBC

JDBC概述

  • 什么是JDBC?

  1. JDBC(Java DataBase Connectivity) Java数据库连接
  2. 是一种用于执行SQL语句的Java API,为多种关系数据提供多种访问。
  3. 它是一组由Java语言编写的类和接口组成
  • 有了JDBC,程序员只需要用JDBC API写一个程序便可以访问所有的数据库。(常用框架的底层都是JDBC,都是对JDBC的封装)

  • Sun公司,数据库厂商和程序员之间的关系

    • DriverManager类,作用:「管理了各种不同的JDBC驱动」
    • Connection接口
    • Statement接口和PreparedStatement接口
    • ResultSet接口
    1. Sun公司是规范的制定者,制定了规范JDBC(连接数据库规范)
    2. 数据库厂商微软,甲骨文等分别提供实现JDBC接口的驱动jar包
    3. 程序员学习JDBC规范来应用这些jar包里的类。
  • JDBC访问数据库的步骤:

    • ResultSet
    • Statement
    • Connection
    1. 加载一个Driver驱动
    2. 创建数据库连接(Connection)
    3. 创建SQL命令发送器Statement
    4. 通过Statement发送SQL命令并得到结果
    5. 处理结果(SELECT语句)
    6. 关闭数据库资源

    使用JDBC完成添加操作

    public class TestInsert {
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            //1.加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.和数据库建立连接 connection url:ip+port+dbname
            String url = "jdbc:mysql://localhost:3306/School";
            String username = "root";
            String password="root";
            Connection connection= DriverManager.getConnection(url,username,password);
            //System.out.println(connection);
            //3.通过连接创建SQL命令发送器statement
            Statement statement=connection.createStatement();
            //4.使用发送器向数据库发送SQL命令并得到结果
            String sql = "INSERT  INTO  Student  VALUES('小明',22,8);";
            /**此方法支持insert update delete等操作
             * n表操作的次数*/

            int n=statement.executeUpdate(sql);
            //5.处理结果
            if (n>0){
                System.out.println("添加成功");
            }else {
                System.out.println("添加失败");
            }
            //6.关闭资源
            statement.close();
            connection.close();
        }
    }

    常见的异常错误:

    1. java.lang.ClassNotFoundException: com.mysql.cj.jbdc.Driver没有找到对应的驱动, 要注意检查驱动路径的正确与否以及是否添加了相应的jar包
    2. java.sql.SQLException: No suitable driver found for jdbc:myslq://localhost:3306/School 注意url错误,mysql书写正确与否。
    3. java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) 表示帐号名或者密码错误
    4. java.sql.SQLSyntaxErrorException: Unknown database 'Schoolsa' 表示不存在的数据库,表示数据库名写错了
    5. com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Out of range value for column 'age' at row 1 表示age字段的内容越界了,超过了所规定的字段
    6. java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'Student.PRIMARY' 表示重复的主键,产生了冲突
    7. Class.forName("com.mysql.cj.jdbc.Driver"); 目的:第一次加载类,执行静态代码块(完成驱动的注册)
    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        public Driver() throws SQLException {
        }

        static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    }

    1. 注释掉Class.forName("com.mysql.cj.jdbc.Driver");依旧可以操作jdbc成功 jar-META-INF-Services-java.sql.Driver-com.mysql.cj.jdbc.Driver 高版本的jar包中会自动到指定位置寻找驱动的路径,所以Class.forName("com.mysql.cj.jdbc.Driver")可以省略

    使用JDBC完成更新删除操作

    使用JDBC更新表数据

    public class TestUpdate {
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            //加载驱动
            String driver="com.mysql.cj.jdbc.Driver";
            Class.forName(driver);

            //建立连接(url,username,password)
            String url="jdbc:mysql://localhost:3306/School";
            String user="root";
            String password="root";
            Connection connection=DriverManager.getConnection(url, user, password);
           // System.out.println(connection);

            //创建一个SQL命令发送器
            Statement statement=connection.createStatement();

            //使用SQL命令发送器发送SQL命令
            String sql = "UPDATE Student \n" +
                    "SET  age=16 WHERE id =1;";
            int flag=statement.executeUpdate(sql);
            //判断是否修改成功
            System.out.println(flag);
            //不要忘记关闭连接
            statement.close();
            connection.close();
        }
    }

    使用JDBC完成删除操作

    public class TestDelete {
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            String driver="com.mysql.cj.jdbc.Driver";
            Class.forName(driver);
            String url = "jdbc:mysql://localhost:3306/School";
            String user = "root";
            String password="root";

            //此处使用try语句块,之后不用手动关闭连接和启动器
            try (    Connection connection = DriverManager.getConnection(url, user, password);
                    Statement statement = connection.createStatement()
                    ) {
                String sql = "DELETE  FROM  Student  WHERE id =8;";
                int n = statement.executeUpdate(sql);
                if (n == 1) {
                    System.out.println("操作成功");
                }else {
                    System.out.println("操作失败");
                }
            }
        }
    }

    使用JDBC完成查询操作1

    public class InquireTest {
        public static void main(String[] args) {
            String driver="com.mysql.cj.jdbc.Driver";
            try {
                Class .forName(driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            String url = "jdbc:mysql://localhost:3306/School";
            String user = "root";
            String password="root";
            String sql="SELECT * FROM  Student;";

            try (Connection connection= DriverManager.getConnection(url,user,password);
                Statement statement = connection.createStatement();){
                ResultSet resultSet = statement.executeQuery(sql);
                System.out.println("姓名\t  年龄\t  ID");
                //对结果进行处理
                while (resultSet.next()){
                    //true代表有数据,false代表超出了结果范围,只能操作结果集当前行的数据

                    //1.获取结果集当前行的数据
                    String name=resultSet.getString("name");
                    int age=resultSet.getInt("age");
                    int id=resultSet.getInt("id");

                    //2.输出结果集当前行的数据
                    System.out.println(name + "\t  "+age + "\t  "+id);
                }

            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    • ResultSet里的数据一行一行排列,每行多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能操作当前数据行,我们如果想要取得某一条记录就要使用next()方法,如果想要 获取所有数据就需要使用while循环
    • ResultSet对象自动维护指向当前数据行的游标,每调用一次next()方法,游标向下移动一行。
    • 初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录,循环完毕后指向最后一条记录的后面。

    常用方法

    使用JDBC完成查询操作2

    public class InquireTest2 {
        /**将main方法看成是用户看得见的平台*/
        public static void main(String[] args) throws SQLException {
            List<Student> allID = findAllID();
            System.out.println("姓名\t  年龄\t  ID");
            for (Student student : allID) {
                System.out.println(student.toString());
            }
        }

        /**将该方法看成是后台数据*/
        public static List<Student> findAllID(){
            String driver="com.mysql.cj.jdbc.Driver";
            String url = "jdbc:mysql://localhost:3306/School";
            String username = "root";
            String password="root";
            String sql="SELECT * FROM  Student;";
            List<Student> StudentList=new ArrayList<Student>();
            try(Connection connection = DriverManager.getConnection(url,username,password);
                Statement statement = connection.createStatement();) {
                Class.forName(driver);
               ResultSet resultSet = statement.executeQuery(sql);
               while (resultSet.next()) {
                   //有了集合后首先要获取set中的对象并传入list中
                   //1.先获取各个字段
                   String name=resultSet.getString("name");
                   int age=resultSet.getInt("age");
                   int id=resultSet.getInt("id");
                   //2.将字段封装到一个Student对象中
                   Student st=new Student(name,age,id);
                   StudentList.add(st);
               }


            } catch (ClassNotFoundException e ) {
                e.printStackTrace(System.err);
            }catch (SQLException e){
                e.printStackTrace(System.err);
            }
            return StudentList;
        }
    }
    /**此类Student对应数据库School中的Student表,student类中的成员变量对应表中的字段
     * Student类中的一个对象对应数据库表中的一条记录。*/

    class Student{
        private String name;
        private int age;
        private  int id;

        public Student(String name, int age, int id) {
            this.name = name;
            this.age = age;
            this.id = id;
        }
        public Student(){}

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public int getId() {
            return id;
        }

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

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", id=" + id +
                    '}';
        }
    }

    注意:若在主函数中直接使用ResultSet来输出,则会出现:java.sql.SQLException: Operation not allowed after ResultSet closed 原因是

    1. 要在后台关闭statement和connection,关闭了这两个之后,结果集就会自动关闭
    2. ResultSet\Statement \Connection属于后台JDBC的操作,要解耦合,否则导致后台换了数据库的访问技术。前台也必须改

    主要的解决办法是:使用集合来替代结果集的使用。此处需要注意集合的泛型类型,需要重新创建一个类xxx对应数据库中的xxx表

    总结:

    • 作为一种良好的编程风格,应在不需要Statement和Connection对象时显式关闭他们。
    • 用户不必关闭ResultSet,当他的Statement关闭、重新执行或用于从多结果序列中获取下一个结果时,该ResultSet将会被自动关闭。

    使用JDBC完成登录操作

    案例操作:

    模拟淘宝的登录功能,在前台输入用户名和密码,后台判断信息是否正确,并给出前台反馈信息,前台输出反馈信息。主要步骤:

    1. 创建数据库表t_User
    2. 创建实体类User
    3. 开发前台代码
    4. 开发后台代码
    5. 进行测试 例:
    public class ReceptionTest {
        /**
         * 此处作为前台来操作需要返回相应的信息
         */

        public static void main(String[] args) {
            //1.从键盘输入用户的姓名和密码
            Scanner input = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String userId = input.next();
            System.out.println("请输入密码:");
            String password = input.next();

            //2.调用后台,完成登录并返回结果
            User user = logIn(userId, password);

            //3输出结果
            if (user == null) {
                System.out.println("登录失败,请重新登录");
            } else {
                System.out.println("登录成功,欢迎" + user.getRealname() + "!");
            }

        }


        /**
         * 定义一个后台需要的方法
         *
         * @param userId
         * @param password 返回值类型:
         *                 1.布尔类型 ,只知道登录是否成功
         *                 2. User对象,不仅可以知道是否登录成功,若对象为空,表示登录失败,若对象不为空,表示登录成功。可以返回更多信息
         */

        public static User logIn(String userId, String password) {
            User user=null;
            String driver = "com.mysql.cj.jdbc.Driver";
            String url = "jdbc:mysql://localhost:3306/School";
            String username = "root";
            String pw = "root";
            String sql = "SELECT  * FROM t_User WHERE userid='"+userId+"' AND passwords='"+password+"'";
            try (Connection connection = DriverManager.getConnection(url, username, pw);
                 Statement statement = connection.createStatement();) {
                Class.forName(driver);
                ResultSet resultSet = statement.executeQuery(sql);
                if (resultSet.next()){
                    //由于userid和password之前已将在查询语句中匹配,所以不用再重复匹配。

                    String realname=resultSet.getString("realname");
                    double money= resultSet.getDouble("money");
                    //将获得的信息封装成user对象。
                    user = new User(userId,realname,password,money);

                }

            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }catch (Exception e){
                e.printStackTrace(System.err);
            }

            return user;
        }
    }


    存在SQL注入的风险,原因在于字符串的拼接, 解决方案有两种:

    1. 分步骤进行登录,获得userid之后使用findByID得到一个user对象吗,在通过user对象来查看password是否匹配。
    2. 使用PreparedStatement,这样不存在拼接问题。

    JDBC初级操作

    使用PreparedStatement来模拟登录

    可以防止SQL注入的问题

    public class ReceptionTest2 {
        /**
         * 此处作为前台来操作需要返回相应的信息
         */

        public static void main(String[] args) {
            //1.从键盘输入用户的姓名和密码
            Scanner input = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String userId = input.next();
            System.out.println("请输入密码:");
            String password = input.next();

            //2.调用后台,完成登录并返回结果
            User user = logIn(userId, password);

            //3输出结果
            if (user == null) {
                System.out.println("登录失败,请重新登录");
            } else {
                System.out.println("登录成功,欢迎" + user.getRealname() + "!");
            }

        }


        /**
         * 定义一个后台需要的方法
         *
         * @param userId
         * @param password 返回值类型:
         *                 1.布尔类型 ,只知道登录是否成功
         *                 2. User对象,不仅可以知道是否登录成功,若对象为空,表示登录失败,若对象不为空,表示登录成功。可以返回更多信息
         */

        public static User logIn(String userId, String password) {
            User user=null;
            String driver = "com.mysql.cj.jdbc.Driver";
            String url = "jdbc:mysql://localhost:3306/School";
            String username = "root";
            String pw = "root";
            String sql = "SELECT  * FROM t_User WHERE userid= ? AND passwords= ?";
            //?表示占位符号
            try (Connection connection = DriverManager.getConnection(url, username, pw);
                 PreparedStatement statement = connection.prepareStatement(sql);) {
                Class.forName(driver);
                //为占位符赋值,索引从1开始计算
                statement.setString(1,"userId");
                statement.setString(2"password");
                ResultSet resultSet = statement.executeQuery();
                if (resultSet.next()){
                    //由于userid和password之前已将在查询语句中匹配,所以不用再重复匹配。

                    String realname=resultSet.getString("realname");
                    double money= resultSet.getDouble("money");
                    //将获得的信息封装成user对象。
                    user = new User(userId,realname,password,money);

                }

            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }catch (Exception e){
                e.printStackTrace(System.err);
            }

            return user;
        }
    }


    使用PreparedStatement的好处(相比statement)

    1. 安全性高,可以防止SQL注入
    2. 可读性高,不需要进行繁琐的字符串拼接,尤其是参数多的情况。
    3. 速度更快

    statement语句和PreparedStatement语句对比

    「statement」

    public class TestInsert3ForMoreInformation {   
    public static void main(String[] args) throws ClassNotFoundException, SQLException 
    String url = "jdbc:mysql://localhost:3306/School";  
    String username = "root"
    String password="root";  
    Connection connection= DriverManager.getConnection(url,username,password);       
    Statement statement=connection.createStatement();  
    String sql1 = "INSERT  INTO  Student  VALUES('小明',21,8);";
    String sql2 = "INSERT  INTO  Student  VALUES('小红',22,9);";   
    String sql3 = "INSERT  INTO  Student  VALUES('小华',23,10);";
    String sql4 = "INSERT  INTO  Student  VALUES('小刚',24,11);";  
    /**此方法支持insert update delete等操作         
    * n表操作的次数
    */
         
    //数据库会认为这是4条不同的SQL语句,执行SQL语句所花费的时间是一样的 
    statement.executeUpdate(sql1);
    statement.executeUpdate(sql2);
    statement.executeUpdate(sql3);
    statement.executeUpdate(sql4);
    statement.close(); 
    connection.close();
        }
    }

    「PreparedStatement」

    public class TestInsert4ForPreparedStatement 
    public static void main(String[] args) throws ClassNotFoundException, SQLException {   
    String url = "jdbc:mysql://localhost:3306/School"
    String username = "root";  
    String password="root";
    Connection connection= DriverManager.getConnection(url,username,password);       
    PreparedStatement statement=connection.prepareStatement("INSERT INTO Student VALUES(?,?,?)"); 
    statement.setString(1,"小美" );   
    statement.setInt(2,22); 
    statement.setInt(3,9); 
    //此时数据库会认为执行的是相同的SQL语句,因为sql中的变化量用?来代替了,每条SQL语句执行的时间都是不一样的。
    statement.executeUpdate();  
    statement.close();
    connection.close();  
        }
    }

    注意:

    1. 当客户发送一条SQL语句给DBMS时,DBMS总是需要校验语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行 SQL语句,其中校验语法,编译所花的时间可能比执行SQL花的时候还要多。
    2. 通过statement对象执行SQL语句时,需要将SQL语句发送给DBMS,即使SQL语句之间只有一个字符的差别,也会被认为是 不同的SQL语句,都要进行编译后再执行。
    3. 预编译语句PreparedStatement和Statement不同,在创建PreparedStatement对象时就指定了SQL语句,该语句立即 发送给DBMS进行编译,当该编译语句被执行时,DBMS直接运行编译后的SQL语句,而不需要像其它的SQL语句那样首先将其编译。
    4. 例如当我们执行多条insert语句时,只是每次插入的值不同,如果使用Statement,每次执行插入操作都需要进行编译和语 法校验,比较浪费时间,而使用了PreparedStatement则由于指定了SQL语句,执行一次之后,后续的数据便不需要再次进行语 法校验和编译,比较节省时间,提高效率。

    扩展:客户端发生SQL语句给DBMS时

    1. 客户端向服务器端发送SQL命令
    2. 服务器端连接模块连接并验证
    3. 缓存模块解析SQL为Hash并与缓存中的Hash表对应,如果有结果直接返回结果, 如果没有对应继续向下执行。
    4. 解析器解析SQL为解析树,如出现错误,报SQL解析错误,如果正确,向下传输。
    5. 预处理器对解析树继续处理,处理成新的解析树
    6. 优化器根据开销自动选择最优执行计划,生成执行计划
    7. 执行器执行最优执行计划,访问存储引擎接口
    8. 存储引擎访问物理文件并返回结果
    9. 如果开启缓存,缓存管理器把结果放入到查询缓存中
    10. 返回结果给客户端

    批处理

    使用PreparedStatement使用一个SQL语句添加多条数据时,可以提升效率,但是还是逐个向服务器发送SQL命令(添加 四条语句,需要四个executeUpdate()),此时「批处理」可以将多个SQL语句进行打包处理,一次性提交给服务器,服务器依次 处理完之后,再一次性返回所有命令的处理结果。

    使用批处理(Batch)的关键如下:

    1. 批处理是指将你关联的SQL语句组合成一个批处理,并将他们当成一个调用提交给数据库,当你一次发送多个SQL语句到 数据库时,可以减少通信的资源消耗,从而提高了性能。
    2. 可以使用DatebaseMetaData.supportsBatchUpdates()方法来确定目标数据库是否支持批处理更新。如果JDBC 驱动程序支持此功能,该方法返回值为true。
    3. Statement、PreparedStatement和CallableStatement的addBatch()方法用于添加单个语句到批处理。
    4. executeBatch()方法用于启动执行所有组合在一起的语句。
    5. executeBatch()方法返回一个整数数组,数组中的每个元素代表了各自的更新语句的更新数目
    6. 正如可以添加语句到批处理中,你也可以用clearBatch()方法删除它们,此方法删除所有用addBatch()方法 添加的语句,但是你不能有选择地选择要删除的语句。
    public class BatchTest {  
    public static void main(String[] args) {    
    String urljdbc = "com.mysql.cj.jdbc.Driver";
    String url = "jdbc:mysql://localhost:3306/School";  
    String user = "root";      
    String password="root";   
    try(Connection connection=DriverManager.getConnection(url, user, password);
    PreparedStatement statement = connection.prepareStatement("INSERT INTO Student VALUES(?,?,?)");) 
    {          
    Class.forName(urljdbc); 
    statement.setString(1,"小美" );
    statement.setInt(2,21);  
    statement.setInt(3,8); 
    //表示将该条SQL语句添加到批处理当中去    
    statement.addBatch();  
    statement.setString(1,"小华"); 
    statement.setInt(2,22);
    statement.setInt(3,9);
    statement.addBatch(); 
    statement.setString(1,"小东");   
    statement.setInt(2,23);
    statement.setInt(3,10);
    statement.addBatch();  
    int[] ints = statement.executeBatch();
    System.out.println(Arrays.toString(ints)); 
        } catch (ClassNotFoundException | SQLException e) {    
        e.printStackTrace();  
        }
      }
    }

    获取自增id

    public class TestInsert5ForGetIncrementId {  
    public static void main(String[] args) throws ClassNotFoundException, SQLException {    
    String url = "jdbc:mysql://localhost:3306/School";  
    String username = "root"
    String password="root"
    Connection connection= DriverManager.getConnection(url,username,password);       
    //使用PreparedStatement在传入sql之后需要获取自增id还需要添加statement语句      //如果INSERT的语句中有些东西不能写死,那么可以用default和null来代替该位置        PreparedStatement statement=connection.prepareStatement("INSERT INTO Student VALUES(?,?,default)", Statement.RETURN_GENERATED_KEYS);  
    statement.setString(1,"小美" );  
    statement.setInt(2,22);
    // statement.setInt(3,9);   
    //此时数据库会认为执行的是相同的SQL语句,因为sql中的变化量用?来代替了,每条SQL语句执行的时间都是不一样的。        
    int i = statement.executeUpdate();  
    System.out.println("添加了:"+i+"条语句");   
    ResultSet generatedKeys = statement.getGeneratedKeys();
    //光标移到第一行(本次添加之后,一共一行一列)   
    generatedKeys.next(); 
    //获取第一列的数据    
    int anInt = generatedKeys.getInt(1); 
    System.out.println(anInt);
    statement.close();  
    connection.close(); 
        }
    }

    在Statement和PreparedStatement通过execute或者executeUpdate执行完插入之后,MySQL会为新插入的数据分配一个 自增长id(前提是这个表设置了自增长,在MySQL建表的时候,AUTO_INCREMENT就表示自增长) 但是无论是execute还是executeUpdate都不会返回这个自增长id是多少,需要通过Statement的getGeneratedKeys 方法获取该id,必须要事先指定要返回自增的ID。

    • 在创建PreparedStatement对象时指明要返回自增的ID PreparedStatement statement=connection.prepareStatement("INSERT INTO Student VALUES(?,?,default)", Statement.RETURN_GENERATED_KEYS);
    • 使用Statement时,需要执行execute或者executeUpdate是指明要返回的自增ID,statement.execute(sql, Statement.RETURN_GENERATED_KEYS)

    获取数据库表的元数据

    利用ResultSet的getMetaData方法可以获得ResultSetMeta对象,而ResultSetMetaData存储了ResultSet的MetaData对象 MetaData一般翻译成元数据,实际上就是描述及解释含义的数据,以Result的MetaData对象为例,ReslutSet是以表格的 形式存在,所以MetaData就包括了数据的字段名称,类型以及数目等表格所具备的信息,就是其结构信息(即desc tablename 所显示的内容)

    public class TestInsertForMeta {  
    public static void main(String[] args) {      
    String driver="com.mysql.cj.jdbc.Driver";  
    try {          
    Class .forName(driver);   
    catch (ClassNotFoundException e) {      
    e.printStackTrace();    
    }      
    String url = "jdbc:mysql://localhost:3306/School";  
    String user = "root";   
    String password="root";  
    String sql="SELECT * FROM  Student;";    
    try (Connection connection= DriverManager.getConnection(url,user,password); Statement statement = connection.createStatement();){   
    ResultSet resultSet = statement.executeQuery(sql);   
    //获取结果集的结构(MetaData:结构)  
    ResultSetMetaData metaData = resultSet.getMetaData();
    //得到结果集的列数    
    System.out.println(metaData.getColumnCount());   
    //遍历元数据,获得每个列的名字      
    int i = 1;   
    for (; i <= metaData.getColumnCount();i++) {    
    //获取列的名字    
    String columnName = metaData.getColumnName(i); 
    int columnType = metaData.getColumnType(i); 
    //获取列的类型名字    
    String columnTypeName = metaData.getColumnTypeName(i); 
    //获取列类型对应的Java包装器类型    
    String columnClassName = metaData.getColumnClassName(i);                System.out.println(columnName+"\t"+columnType+"\t"+columnTypeName + "\t"+columnClassName);           
      }   
    catch (SQLException throwables) {   
    throwables.printStackTrace(); 
            }   
          }
      }

    JDBC高级操作

    JDBC中使用事务

    在JDBC中,事务操作缺省是自动提交

    • 一条对数据库的DML(insert,update,delete)代表一项事务操作。
    • 操作成功后,系统将自动调用commit()提交,否则自动调用rollback回滚。

    在JDBC中,事务操作方法都位于接口java.sql.Connection中

    • 可以通过调用setAutoCommit(false)来禁止自动提交
    • 之后就可以把多个数据库操作的表达式作为一个事务,在操作完成之后调用commit()来进行整体提交。
    • 倘若其中一个表达式操作失败,都不会执行commit(),并且将产生响应的异常;此时就可以在异常捕获时调用rollback 进行回滚,回复至数据的初始状态。

    事务开始的边界则不是那么明显了,它会开始于组成当前事务的所有statement中第一个被执行的时候。

    事务结束的边界是commit或者rollback方法的调用。

    JDBC中所有的事务都是由connection发起的。

    「事务处理保证转账安全」

    public class TransactionTest {    
    public static void main(String[] args) 
    /**默认情况下,每条DML(insert update delete)语句都是一个单独的事务,执行时事务自动开始,执行完毕,会自动提交或者回滚   
    * 很多情况下,多个DML语句是一个事务,保证整体数据的完整性。
    */
         
    Connection connection = null;
    Statement statement = null;  
    String driver="com.mysql.cj.jdbc.Driver";      
    String url = "jdbc:mysql://localhost:3306/School";   
    String username = "root";  
    String password = "root"
    String sql1 = "Update t_User SET money=money-1000 WHERE userid='zhangsan'";        
    String sql2 = "Update t_User SET money=money+1000 WHERE userid='lisi'"try{
    Class.forName(driver); 
    connection=DriverManager.getConnection(url, username,password);  
    statement= connection.createStatement();
    //表示事务不会再自动提交    
    connection.setAutoCommit(false);   
    //两个操作都应该统一,要么都成功,要么都回滚    
    statement.executeUpdate(sql1); 
    // int n=10/0;     
    statement.executeUpdate(sql2);  
    //如果一切正常(最后一条语句执行结束之后),都没出问题,再通过connection进行整体提交。            
    connection.commit();     
    catch (Exception e){    
    System.out.println(e.getMessage());    
      try {    
      connection.rollback();
      } catch (SQLException ex) {     
      ex.printStackTrace();          
      }       
      }finally {    
      try {      
    connection.close();    
    catch (SQLException e) {    
    e.printStackTrace();      
    }           
    try {        
    statement.close();   
    catch (SQLException e) {     
    e.printStackTrace();   
          }    
        } 
      }
    }

    数据库连接池

    建立数据库连接池的两种方式:

    1. 传统连接方式:
    • 首先调用Class.forName()方法加载数据库驱动。
    • 然后调用DriverManger.getConnection()方法建立连接
    1. 连接池技术
    • 连接池解决方案是在应用程序启动时就预先建立多个数据库连接对象,然后将连接对象保存到连接池中。
    • 当客户请求来时,从池中取走一个连接对象为客户服务。
    • 当请求完成时,客户程序调用close()方法,将连接对象放回池中。
    • 对于多个连接池中连接数的请求,排队等待。
    • 应用程序还可以根据连接池中的连接的使用率,动态增加或减少池中的连接数。

    传统的数据库连接方式的缺点:

    • 一个连接对象对应一个物理连接
    • 每次操作都打开一个物理连接
    • 使用完都关闭连接,造成系统性能低下

    连接池技术的优点:

    • 客户程序得到的连接对象是连接池中物理连接的一个句柄
    • 调用对象的close()方法,物理连接并没有关闭,数据源的实现只是删除了客户程序中的连接对象和池中连接对象的关系。

    数据库连接的建立及关闭是耗费系统资源的操作,在大型应用中对系统性能影响尤为明显,为了能重复利用数据库连接对象, 缩短请求的响应时间和提高服务器的性能,支持更多的用户,应采用连接池技术。

    「手写连接池」

    public class ConnectionPool {      
    //准备一个list,存储已经创建好的连接。   
    //获取连接就是删除第一个连接,返回连接就是加到最后一个位置。如果采用以数组为基础的ArrayList,删除操作太过低效率。    
    LinkedList<Connection> list = new LinkedList<Connection>(); 
    private  int SIZE=20;  
    /**创建一个构造方法*/     
    public ConnectionPool(){     
    }  
    public ConnectionPool(int SIZE){      
    this.SIZE = SIZE;     
    init();     
    }       
    /**创建一个初始化方法      
    * 创建SIZE个连接放入到list中*/
       
    public void init()  {    
    //加载驱动  
    String driver="com.mysql.cj.jdbc.Driver";
    //数据库连接条件      
    String url = "jdbc:mysql://localhost:3306/School";   
    String user = "root"
    String password="root"
    try {   
    Class.forName(driver); 
    //利用循环来创建物理连接   
    for (int i = 0; i < SIZE; i++) {  
    Connection c= DriverManager.getConnection(url,user,password);  
    list.add(c); 
        }  
      } catch (ClassNotFoundException e) {    
      System.out.println(e.getMessage());  
    }catch (SQLException e) {   
    System.out.println(e.getMessage());  
        } 

    /**创建获取连接的方法      
    * 直接从集合中获取*/
        
    public synchronized Connection getConnection(){  
    /*不能通过集合直接get,因为这样始终只是使用了第一个连接  
    list.get(0);应该使用remove操作*/
       
    //如果连接少请求多      
    /*1.创建新的连接       
    2.等待wait(多线程)     
    * */
             
    while (list.isEmpty()){     
    //如果集合为空,则需要进行wait方法等待,此时需要同步getConnection方法                try {  
    this.wait();    
    catch (InterruptedException e) {   
    e.printStackTrace();      
        }
      }  
      //获取第一个并删除,返回删除的连接   
      Connection remove = list.removeFirst(); 
      return  remove; 
    }     
    /**创建一个返回连接的方法        
    * @param cn */
        
    public  synchronized void closeConnection(Connection cn){     
    list.addLast(cn);    
    //由于getConnection方法进行了等待操作,再返回连接时需要唤醒等待的线程            //通知等待的线程     
    this.notifyAll(); 
    }   
    public static void main(String[] args) throws SQLException, InterruptedException {   
    ConnectionPool connectionPool=new ConnectionPool(30);  
    for (int i = 0; i < 40; i++) {       
    /*         
    //获取一个连接       
    Connection connection = connectionPool.getConnection();  
    //利用连接来执行查询操作      
    connection.createStatement().executeQuery("SELECT * FROM Student");      System.out.println("输出数据");    
    //代表数据库操作的时间花费     
    TimeUnit.SECONDS.sleep(1); 
    connectionPool.closeConnection(connection);   
    此处表示在集合中,获取一个执行一个关闭一个,都是按照排列顺序来的,前一个执行完,后一个执行,无法实现并发。         
    */
              
    new Thread(()->{         
    //获取一个连接    
    Connection connection = connectionPool.getConnection();                //利用连接来执行查询操作  
    try {                  
    connection.createStatement().executeQuery("SELECT * FROM Student");      System.out.println("输出数据");    
    //代表数据库操作的时间花费    
    TimeUnit.SECONDS.sleep(1);  
    connectionPool.closeConnection(connection);    
    catch (SQLException | InterruptedException e) {   
    e.printStackTrace();  
          }
        } 
      ).start();   
          }
        }
    }

    JDBC API总结

    Connection接口

    作用:代表数据库连接。

    Connection connection;

    1. 普通的statement Statement statement;

    2. PreparedStatement继承了Statement,预编译的Statement,与占位符号有关 PreparedStatement preparedStatement;

    3. CallableStatement接口继承了  PreparedStatement,调用存储过程,而不是执行SQL语句 CallableStatement callableStatement;

    DriverManager

    作用:管理一组JDBC驱动程序的基本服务。

    应用程序不需要再显式地使用Class.forName()加载数据库驱动,在调用getConnection()方法时,DriverManager 会试着从初始化加载的哪些驱动程序以及使用与当前applet或者应用程序相同的类加载器显式加载的那些驱动程序中查找合适 的驱动程序。

    Statement接口

    作用:用于将SQL语句发送到数据库中,或者可以理解为执行SQL语句。

    有三种Statement对象:

    • Statement:用于执行不带参数的简单的SQL语句
    • PreparedStatement(继承自Statement):用于执行带参数或者不带参数的预编译的SQL语句。
    • CallableStatement:(继承自PreparedStatement):用于执行数据库存储过程的调用。

    PreparedStatement接口

    关系:public interface PreparedStatement extends Statement

    区别:

    • PreparedStatement安全性高,可以避免SQL注入
    • PreparedStatement简单不繁琐,不用进行字符串拼接
    • PreparedStatement性能高,用在执行多个相同数据库DML操作时

    ResultSet接口

    ResultSet对象是executeQuery()方法的返回值,它被称为结果集,它代表符合SQL语句条件的所有行,并且通过 一套的getXXX方法(这些get方法可以访问当前行中的不同列)提供了对这些行中数据的访问。

    ResultSet里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据叫做当前数据行,我们只能 操作当前的数据行,我们如果想要取得某一条记录,就要使用ResultSet的next()方法,如果我们需要ResultSet 里的所有记录,就应该使用while循环。

    ResultSet对象自动维护指向当前数据行的游标,每调用一次next()方法,游标向下移动一行。

    初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录,循环完毕后最后一条记录

    往期文章回顾

    你不可不知的语言---JAVA

    Java基本程序设计结构——数据类型

    变量+运算=?

    字符串详解

    输入输出与流程

    数组与大数

    类初识

    自定义类与时间类

    方法参数与对象构造

    包、注释及JAR文件

    继承及其子类

    Object类及其方法

    泛型数组列表及包装类

    IO流概述

    XML文档与解析

    Java多线程之JUC



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存