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

1 JDBC引言

1.1 JDBC简介

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

 学习过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的步骤。

总结:开发步骤

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
    • 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<Object>() {
     @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<Object>() {
     @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<Person> personList = jdbcTemplate.query(sql, new ResultSetExtractor<List<Person>>() {
        @Override
        //extractData会在查询SQL的结果返回后,被自动调用1次。ResultSet表示查询结果集
        public List<Person> extractData(ResultSet rs) throws SQLException, DataAccessException {
    ​
            List<Person> 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 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<Person> personList = jdbcTemplate.query(sql, new RowMapper<Person>() {
​
    @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<Person> 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. 注意:如果拼接的是字符串值,那么需要在"+之前添加'+"后添加'

//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<Admin> adminList = jdbcTemplate.query(sql, new RowMapper<Admin>() {
    @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<T> rowMapper);
    query(String sql, RowMapper<T> rowMapper, @Nullable Object... 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,处理结果集
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<Admin>() {
    @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 添加日志输出

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

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

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

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

  1. 添加依赖jar包

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

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="info" monitorInterval="5">
        <Appenders>
            <Console name="console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36} %L %m%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="trace">
                <AppenderRef ref="console"/>
            </Root>
        </Loggers>
    </Configuration>

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中默认不能使用扫描器

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值