JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常

1 JDBC引言

1.1 JDBC简介

JDBC(Java DataBase Connectivity) :Java数据库连接技术:具体讲就是通过Java连接广泛的数据库,并对表中数据执行增、删、改、查等操作的技术。

JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常_第1张图片

 学习过SQL后,可以通过DataGripNavicatSQLyog等图形化客户端发送SQL操作数据库。本质上,JDBC的作用和图形化客户端的作用相同,都是发送SQL操作数据库。差别在图形化界面的操作是图形化、傻瓜化的,而JDBC则需要通过编码(先不要思考JDBC代码怎么写,也不要觉得它有多难)完成图形操作时的效果。

总结:JDBC本质上也是一种发送SQL操作数据库的client技术,只不过需要通过Java编码完成。

为什么要学习JDBC? 既然说JDBC和 Navicat的作用相同,而 Navicat图形化操作很简单(我们也很熟练),那么为什么还要学习操作更复杂(需要编码)、更陌生的新的Java客户端技术呢?

Java是可编程的(可定制化),可以将普通用户输入的数据拼接成各种各样的可运行的SQL,从而降低普通用户使用数据库服务的难度。

举个例子:任何系统中都有登录功能,登录时需要从用户表中根据用户名查询然后比对密码,每个人的用户名密码肯定不同,执行的select语句的条件部分就不同,直接让用户通过 Navicat 操作数据库,就得由用户拼接SQL,这对于小白用户要求过高。而JDBC就可以通过Java代码将用户输入的用户名密码拼接到SQL中完成查询,普通小白用户通过JDBC程序操作数据库时,只要会输入用户名密码即可!!!

1.2 JDBC原生编程(了解)

1.2.1 JDBC涉及的API

JDBC要通过Java代码操作数据库,JDBC中定义了操作数据库的各种接口和类型:

  • Driver: 驱动接口,定义了Java如何和数据库获取连接

  • DriverManager: 管理驱动的工具类, 可以管理多种驱动,通过它可以获取和数据库的连接

  • Connection:连接接口,通过它完成Java和数据库之间的交互

  • DataSource::数据源接口,用来管理Java和数据库建立的连接,可以复用之前建立的连接

  • PreparedStatement: 发送SQL的工具接口,该类型对象用于向数据库发送一条SQL

  • ResultSet: 结果集接口, 该类型的对象表示一条查询SQL返回的结果

注意:学习到这里不需要你掌握上述的任何一个类型(后续会逐步学习到每一个类型),只需要加深理解JDBC需要通过编码操作数据库(也就是JDBC的可编程属性)即可。

1.2.2 JDBC原生编程示例(了解)

之前,我们已经反复强调JDBC是一种和 Navicat 相当的客户端程序,那么JDBC操作数据库的步骤和 Navicat 操作数据库步骤就大同小异,下面我们先回顾下 Navicat的操作步骤,然后分析出JDBC的步骤。

JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常_第2张图片

总结:开发步骤

1. 加载驱动

2. 获取链接:配置url、username和password
​
3. 准备SQL以及发送SQL的工具
​
4. 执行SQL
​
5. 处理结果集
​
6. 释放资源

需求:从t_person表中查询数据

-- 数据准备
create table t_person(
	person_id int primary key auto_increment,
    person_name varchar(20),
    age tinyint,
    sex varchar(6),
    mobile varchar(20),
    address varchar(200)
);
insert into t_person values(1,'王月华',18,'F','1563868xxxx','郑州'),(2,'刘天赐',18,'M','1563768xxxx','郑州');
-- 需求 查询t_person表所有数据
select * from t_person;
  1. 准备工作(搭建开发环境)

    需要在项目中引入数据库驱动jar包(jar文件:针对class文件的压缩格式包含了多个带包的class文件,类似于普通文件打包的zip、rar)

    • eclipse

      将mysql-connector-java.jar添加到项目环境中
      1. 右键选中项目,新建一个folder名为lib,将jar包复制到lib目录中
      2. 右键选中jar包,build path-->add to build path
    • JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常_第3张图片idea

      将mysql-connector-java.jar添加到项目环境中
      1. 右键选中项目,新建一个Directory名为lib,将jar包复制到lib文件夹中
      2. 右键选中jar包,Add as Library --> OK

  2. 编码(JDBC 6步)

    public class JDBCTest {
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            //1 加载驱动
            /*
                驱动版本8.0.x com.mysql.cj.jdbc.Driver
                驱动版本5.1.x com.mysql.jdbc.Driver
            */
            Class.forName("com.mysql.cj.jdbc.Driver");
            
            //2 获取连接
            String username = "root";//用户名
            String password = "123456";//密码
            /*
                url参数用来确定连接的数据库信息: 数据库机器ip 端口号port 数据库名db_name 连接的参数,比如编解码集、时区...
                url格式:jdbc:mysql://ip:port/db_name?k=v参数 ,只需要了解url的组成,不需要记忆
            */
            String url = "jdbc:mysql://localhost:3306/baizhi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";
            Connection conn = DriverManager.getConnection(url, username, password);//通过DriverManager管理驱动获取连接
            
            //3 准备发送SQL的工具
            String sql = "select * from t_person";
            PreparedStatement pstm = conn.prepareStatement(sql);
            
            //4 发送执行SQL
            ResultSet rs = pstm.executeQuery();
            
            //5 处理结果集(如果有)
            while(rs.next()){
                /*
                    rs.getXxx(列顺序从1开始) 或者 rs.getXxx("列名") 获取指定列的数据,Xxx为数据类型
                    实战中多使用列名,可读性强
                 */
                int personId1 = rs.getInt("person_id");
                String personName1 = rs.getString("person_name");
                int age1 = rs.getInt("age");
                String sex1 = rs.getString("sex");
                String mobile1 = rs.getString("mobile");
                String address1 = rs.getString("address");
                System.out.println("personId="+personId1+",personName="+personName1
                        +",age="+age1+",sex="+sex1+",mobile="+mobile1+",address="+address1);
    ​
                int personId2 = rs.getInt(1);
                String personName2 = rs.getString(2);
                int age2 = rs.getInt(3);
                String sex2 = rs.getString(4);
                String mobile2 = rs.getString(5);
                String address2 = rs.getString(6);
                System.out.println("personId="+personId2+",personName="+personName2
                        +",age="+age2+",sex="+sex2+",mobile="+mobile2+",address="+address2);
            }
            
            //6 释放资源(反序关闭:后出现先关闭)
            rs.close();
            pstm.close();
            conn.close();
        }
    }
     

2 JdbcTemplate编程

JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。JDBC如同是毛坯房,而JdbcTemplate类似于精装房,使用JdbcTemplate会比原生JDBC更简单舒适一些。

2.1 JdbcTemplate编程步骤

1. 创建DataSource (连接池,管理java和数据库之间所有的连接)
​
2. 创建JdbcTemplate (封装了各种操作数据库的简化后的方法)
​
3. 执行SQL (调用jdbcTemplate的方法 update:执行增删改  query:执行查询)

2.2 第1个JdbcTemplate程序

需求:向t_person表中新增一行数据

create table t_person(
    person_id int primary key auto_increment,
    person_name varchar(20),
    age tinyint,
    sex varchar(6),
    mobile varchar(20),
    address varchar(200)
)char set utf8mb4;
​
-- 需求 向t_person表中插入一行数据
insert into t_person values(null,'王月华',88,'其他','138383838','硅谷');
  1. 准备工作(搭建开发环境)

    需要在项目中引入数据库驱动jar包和JdbcTemplate的jar包(jar文件:针对class文件的压缩格式包含了多个带包的class文件,类似于普通文件打包的zip、rar)

  2. 编码

    public class JdbcTemplateTest {
        public static void main(String[] args) {
            //1 创建,并配置DataSource
            String url = "jdbc:mysql://localhost:3306/baizhi?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai";
            String username = "root";
            String password = "123456";
            DataSource dataSource = new DriverManagerDataSource(url,username,password);
    ​
            //2 建立JdbcTemplate
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    ​
            //3 执行sql
            String sql = "insert into t_person values(null,'王月华',18,'F','1563868xxxx','郑州')";
            int update = jdbcTemplate.update(sql);
            System.out.println("本次执行的sql插入"+update+"条数据");
    ​
    ​
        }
    }

3 结果集的处理

需求:查询t_person表中的数据

select * from t_person;

3.1 ResultSet结果集

使用JdbcTemplate对象的query方法执行查询sql时会返回ResultSet结果集对象,通过它我们可以获取到SQL语句的查询结果。要通过ResultSet获取到查询结果需要解决以下几个问题:

  • 如何获取ResultSet?

    String sql = "select * from t_person";
    ​
    // query方法中,传入ResultSetExtractor接口实现,其extractData方法将会得到ResultSet
    jdbcTemplate.query(sql, new ResultSetExtractor() {
     @Override
     //extractData会在查询SQL的结果返回后,被自动调用1次。ResultSet表示查询结果集
     public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
    ​
         return null;
     }
    });
    

  • 如何获取一行中各个字段的值?

    两种方式:

    • rs.getXxx(列编号) ,列编号从1开始

    • rs.getXxx("字段名")

    注意:Xxx为数据类型,比如String、Int、Double...

  • 如何获取ResultSet中的所有行?

    rs内部有一个游标,每调用rs.next()一次,游标从上一行向后移动到当前行,并返回指向的当前行是否有数据,true:有数据,false:没有数据。

    • rs游标初始执行第一行的上方

    • rs不断调用next(),第一次调用游标指向第一行,第二次调用游标指向第二行,直到返回false,即可以遍历所有行。

  • 3.2 处理结果集

    开发步骤回顾

    1. 创建DataSource
    ​
    2. 创建JdbcTemplate
    ​
    3. 执行SQL,处理结果集
    1. while循环处理结果集

      while(rs.next()){ //不断循环,遍历rs中所有行
       rs.getXxx("字段名"); //获取一行中每个字段的值
      }

      示例:

      //3 执行sql
      String sql = "select * from t_person";
      jdbcTemplate.query(sql, new ResultSetExtractor() {
       @Override
       //extractData会在查询SQL的结果返回后,被自动调用1次。ResultSet表示查询结果集
       public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
      ​
           while(rs.next()){//从上一行移动到当前行
               //获取当前行数据
               int id = rs.getInt("person_id");
               String name = rs.getString("person_name");
               int age = rs.getInt("age");
               String sex = rs.getString("sex");
               String mobile = rs.getString("mobile");
               String address = rs.getString("address");
      ​
               System.out.println("id = "+id+",name = "+name+",age = "+age+",sex = "+sex+",mobile = "+mobile+",address = "+address);
           }
      ​
           return null;
       }
      });
      

    2. 封装结果,并返回

      通过rs获取到多行数据后,为了程序后续流程方便使用这个数据,我们会将多行多列的数据保存起来,而不是简单的打印输出。通常,我们会根据表中字段内容,定义一个与之对应的实体类(DO)。比如,与t_person表对应的实体类如下所示:

      public class Person  implements Serializable {
       private Integer personId;
       private String personName;
       private Integer age;
       private String sex;
       private String mobile;
       private String address;
       //省略 构造方法和get set方法
       ...
      }
      • 实体类名和表名相关(表名去掉t_后,单词首字母大写)

      • 一个表对应一个实体类,多个表多个实体类,按照规范实体类所在的包名通常为entitydomainpojo中的一种

      • 实体类中属性和表中字段一一对应,属性私有提供公开的get和set

      • 实体类必须包含无参构造方法

      • 实体类应该实现Serializable接口

      返回结果:

      • 通过rs查询出的每行数据应该封装为一个实体对象

      • 如果有多行,则可以使用List保存多行转换的实体对象

      List personList = jdbcTemplate.query(sql, new ResultSetExtractor>() {
          @Override
          //extractData会在查询SQL的结果返回后,被自动调用1次。ResultSet表示查询结果集
          public List extractData(ResultSet rs) throws SQLException, DataAccessException {
      ​
              List personList = new ArrayList<>();
              while (rs.next()) {//从上一行移动到当前行
                  //获取当前行数据
                  int id = rs.getInt("person_id");
                  String name = rs.getString("person_name");
                  int age = rs.getInt("age");
                  String sex = rs.getString("sex");
                  String mobile = rs.getString("mobile");
                  String address = rs.getString("address");
      ​
                  Person person = new Person(id, name, age, sex, mobile, address);
                  personList.add(person);
              }
      ​
              return personList;
          }
      });
      ​
      personList.forEach((p)->{
          System.out.println(p); //遍历打印输出
      });

    3. 3.3 RowMapper接口

      通过上面的学习我们会发现,不同表的查询结果集处理步骤除了从当前行获取数据的处理不同外,其它步骤完全相同。都是:

      List list = new ArrayList();//2 都需要定义一个List集合
      while(rs.next()){ //1 都需要循环
          ... //处理当前行,一行封装成一个实体对象
              
          list.add(o);//3 将实体对象保存到list中
      }
      return list;//4 将封装了多行结果的list返回

      为了避免频繁编写上述while循环模板代码,JdbcTemplate中还可以使用RowMapper接口代替ResultSetExtractor接口,简化结果集的处理。

       //3 执行sql
      String sql = "select * from t_person";
      List personList = jdbcTemplate.query(sql, new RowMapper() {
      ​
          @Override
          //mapRow用来获取一行的多列值,mapRow会被自动的循环调用
          public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
              //获取当前行数据
              int id = rs.getInt("person_id");
              String name = rs.getString("person_name");
              int age = rs.getInt("age");
              String sex = rs.getString("sex");
              String mobile = rs.getString("mobile");
              String address = rs.getString("address");
      ​
              Person person = new Person(id, name, age, sex, mobile, address);
              return person;
          }
      });
      
      

      3.4 BeanPropertyRowMapper

      JdbcTemplate还提供了内置的BeanPropertyRowMapper,简化查询结果和实体类对象的转换。注意,BPRM的使用需要满足以下要求:

      • 字段名和属性名由1个单词构成时,2者要相同。

        t_person表中有address字段,则Person实体类中有address属性

      • 字段名和属性名由多个单词构成时,字段名多个单词间使用_连接,属性名第一个单词小写其它单词首字母必须大写

        t_person表中有person_id字段,则Person实体类中有personId属性

         
           //1. 创建DataSource
              // url 起到定位数据库的作用,格式: jdbc:mysql://数据库所在机器的ip:port/数据库名?k=v参数&k=v参数
              /*
                  jdbc:mysql:// 固定写法
                  数据库所在的ip,如果是自己的机器,可以直接使用127.0.0.1 或者localhost
                  port  MySQL监听3306
                  数据库名 baizhi
                  k=v 用来配置连接的  useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
               */
              String url ="jdbc:mysql://localhost:3306/baizhi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";
              String username = "root"; //数据库用户名
              String password = "123456"; //数据库密码
              DataSource dataSource = new DriverManagerDataSource(url, username, password);
      ​
              //2. 创建JdbcTemplate
              JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
      ​
              //3. 执行sql
              String sql = "select * from t_person";
              List personList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Person.class));
      ​
              personList.forEach(p->{
                  System.out.println("p = " + p);
              });

      4 数据绑定

      数据绑定:将用户输入的数据,绑定到要执行的SQL语句中。

      JDBC执行的SQL中的数据要根据用户的输入发生变化,比如 登录功能背后的查询sql要根据用户名不同,执行不同的条件。这就需要将用户输入的数据绑定到执行的SQL中。

      绑定数据的方式有2种:

      • 字符串拼接

      • ?占位符绑定

      环境准备:

      create table t_admin(
          admin_id int primary key auto_increment,
          admin_name varchar(20) unique not null,
          password varchar(50) not null
      );
      ​
      -- 添加测试数据
      insert into t_admin values(null,'xiaohei','123456');

      4.1 字符串拼接

      字符串拼接方式,本质上就是通过Java字符串拼接语法构造出正确的可执行的SQL语句。拼接步骤如下:

      1. 在需要数据的地方,使用变量名替换

      2. 在变量名前添加"+,变量后添加+"

      3. 注意:如果拼接的是字符串值,那么需要在"+之前添加'+"后添加'

      JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常_第4张图片

      //1 创建DataSource
      String url = "jdbc:mysql://localhost:3306/baizhi?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai";
      String username = "root";
      String password = "123456";
      DataSource dataSource = new DriverManagerDataSource(url,username,password);
      ​
      //2 创建JdbcTemplate
      JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
      ​
      //3 执行sql,处理结果集
      Scanner sc = new Scanner(System.in);
      String adminName = sc.nextLine();
      String adminPassword = sc.nextLine();
      ​
      /*
                  ① 在需要绑定数据的地方,使用变量名替换
                  ② 在变量名前追加( "+ )  在变量名后追加( +" )
                  ③ 注意:如果拼接的数据是字符串,那么需要在"+前和+"后各添加一个'
               */
      String sql = "select * from t_admin where admin_name = '"+adminName+"' and password = '"+adminPassword+"'";
      System.out.println("sql = " + sql);
      ​
      List adminList = jdbcTemplate.query(sql, new RowMapper() {
          @Override
          public Admin mapRow(ResultSet rs, int rowNum) throws SQLException {
              int id = rs.getInt("admin_id");
              String name = rs.getString("admin_name");
              String password = rs.getString("password");
              return new Admin(id, name, password);
          }
      });
      ​
      if(!adminList.isEmpty()){
          System.out.println("登录成功,admin = "+adminList.get(0));
      }else {
          System.out.println("登录失败!");
      }

      4.2 ?占位符

      ?占位符是JDBC的一种特殊语法,专用于参数绑定。使用步骤:

      1. 在需要使用数据的地方,使用?代替(占位)

        String sql = "select * from t_admin where admin_name = ? and password = ?";
      2. update和query都有Object[]参数的版本和可变长参数的版本,用来接收数据按照顺序为?赋值

        update(String sql, @Nullable Object... args);
        query(String sql, @Nullable Object[] args,RowMapper rowMapper);
        query(String sql, RowMapper rowMapper, @Nullable Object... args);

      JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常_第5张图片

      //1 创建DataSource
      String url = "jdbc:mysql://localhost:3306/baizhi?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai";
      String username = "root";
      String password = "123456";
      DataSource dataSource = new DriverManagerDataSource(url,username,password);
      ​
      //2 创建JdbcTemplate
      JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
      ​
      //3 执行sql,处理结果集
      Scanner sc = new Scanner(System.in);
      String adminName = sc.nextLine();
      String adminPassword = sc.nextLine();
      ​
      /*
                  ① 在需要绑定数据的地方,使用变量名替换
                  ② 在变量名前追加( "+ )  在变量名后追加( +" )
                  ③ 注意:如果拼接的数据是字符串,那么需要在"+前和+"后各添加一个'
               */
      String sql = "select * from t_admin where admin_name = ? and password = ?";
      System.out.println("sql = " + sql);
      ​
      Admin admin = jdbcTemplate.queryForObject(sql,new Object[]{adminName,adminPassword}, new RowMapper() {
          @Override
          public Admin mapRow(ResultSet rs, int rowNum) throws SQLException {
              int id = rs.getInt("admin_id");
              String name = rs.getString("admin_name");
              String password = rs.getString("password");
              return new Admin(id, name, password);
          }
      });
      ​
      if(admin != null){
          System.out.println("登录成功,admin = "+admin);
      }else {
          System.out.println("登录失败!");
      }

      4.3 字符串拼接和?占位符的区别

      方式 特点 使用场景 最佳实践
      字符串拼接 可能会被SQL注入攻击 可以拼接表名、列名、SQL关键字 动态的查询不同的表以及根据用户选择升序或者降序查询数据
      ?占位符 可以防止SQL注入攻击 绑定纯数值数据 通常情况下使用占位符

      4.3.1 SQL注入攻击

      SQL注入是一种非常常见的数据库攻击手段,技术上就是用户使用包含SQL关键字的数据参与了SQL拼接,拼接出一个绕过验证的恶意的SQL。然后这样被拼接出来的SQL语句被数据库执行,产生了开发者预期之外的动作。

      String adminName = "xiaohei'-- ";//真实用户名后添加'-- ,后面的内容会被注释掉
      String adminPassword = "1234567";//密码错误,但也能登录成功
      

      4.3.2 拼接关键字

      为了更灵活的SQL效果,有时候我们需要拼接表名、字段名和关键字。比如动态的查询不同的表、根据不同的字段排序等。字符串拼接的方式可以,而?占位符则不行。

      5 异常

      解决问题的步骤:

      1. 定位问题(哪一行出错)

        ① 添加打印语句

        ② 根据异常信息定位(找到异常信息中关于自己写的代码部分)

      2. 为什么出错

        根据异常信息和所学知识分析问题的原因

      3. 解决异常

        根据问题的原因,解决异常记录异常

      5.1 根据异常日志分析

      分析异常最高效的办法是阅读异常日志,根据异常日志的信息,可以快速定位问题的位置、问题的原因设置是问题的解决方案。举个例子:

      org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Access denied for user 'root'@'172.17.0.1' (using password: YES)
      ​
          at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:82)
          at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:612)
          at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:669)
          at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:700)
          at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:712)
          at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:783)
          at com.baizhi.test.JdbcTemplateTest.testDataBindingPreparedStatement(JdbcTemplateTest.java:217)
          at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
          at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
          at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
          at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
          at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
          at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
          at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
          at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
          at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
          at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
          at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
          at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
          at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
          at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
          at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
          at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
          at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
          at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
      Caused by: java.sql.SQLException: Access denied for user 'root'@'172.17.0.1' (using password: YES)
      1. 先看异常类型:

        java.sql.SQLException: Access denied for user 'root'@'172.17.0.1' (using password: YES),说明是数据库账密不正确

      2. 再看异常位置:

        自上而下看出现数字的行,找到自己写的代码。比如当前案例下:自己的代码 JdbcTemplateTest.java第217行有问题,那么错误就在这里发生的

      3. 解决方案:

        修改为正确的用户名和密码

      说明:阅读异常日志挑错是最有效的,但是对于初学者而言难度过高。刚开始不用急于求成,异常信息也非常有规范,在学习过程中慢慢提升异常阅读能力,最终掌握阅读异常信息的能力。

      5.2 添加日志输出

      笨但有效的办法:手动添加打印语句

      可以在代码的关键位置处添加打印语句,通过语句的输出与否判定异常发生的问题。 打印输出尽量有意义,比如关键代码有返回值,则可以打印返回值。这样通过返回值可以判断程序运行的状态,为调错提供指导。

      JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常_第6张图片

      更为标准的做法:开启日志

      各种框架和类库为了更好的反馈程序执行的过程,都会在代码中预埋日志的输出(类似我们的打印语句,但功能更强大),我们可以开启JdbcTemplate中的日志输出。

      1. 添加依赖jar包

        JDBC从入门到熟练使用——功能类详解、增删改查(CRUD)、sql注入、异常_第7张图片

      2. 项目src目录下(位置固定)添加log4j2.xml日志配置文件

        
        
            
                
                    
                
            
            
                
                    
                
            
        

      5.3 记录JDBC中的异常

      JDBC的异常其实还是非常固定的,就那么几种:驱动加载问题、数据库用户名密码错误、sql语法错误、绑定数据错误...。有1个办法可以快速的掌握JDBC中的异常:站在上帝视角,人为的造错,观察记录异常信息,日后出现时可以快速调错。

      JDBC项目的开发步骤


      1.搭建项目开发环境
      a.打开idea,新建一个项目

      b.导入依赖iar包项目日录下新建一个lib目录,然后将jar包复制其中右键选中lib目录,add as library(添加为库)

      c.复制配置文件jdbc.properties + JDBCUtils工具类Cs
      2.数据库中建表
      3.实体类entity包
      4.dao
      dao包 接口:定义常见的增删改查方法

      dao.impl包实现类:使用jdbcTemplate3步实现
      5.service
      service包 接口:针对一张表的业务功能方法

      service.impl包实现类:业务逻辑判断+dao调用+事务控制

      6.test
      JUnit测试 一个service一个测试类,注意:Junit在idea中默认不能使用扫描器

      你可能感兴趣的:(java,数据库,mysql)