8.2 在JAVA开发中使用MySql--JDBC



1.概述

我们学习了数据库,数据库实现了数据的持久化,但我们最终要在程序里处理数据啊,那java代码中怎么去访问数据库读写数据呢?

这就要用到sun公司设定的一套数据库标准了,这套标准就是JDBC(Java Database Connectivity)。但它只是规范,不做具体实现。于是数据库厂商又根据JDBC标准,实现自家的驱动Driver。如:mysql驱动com.mysql.cj.jdbc.Driver,Oracle的驱动oracle.jdbc.OracleDriver。有了这套解决方案,java就可以访问数据库中的数据了。

所以,JDBC是java database connectivity的简称,是专门用来完成 java程序 和 数据库 的连接的技术.

public interface Connection extends Wrapper, AutoCloseable {}

public interface Statement extends Wrapper, AutoCloseable {}

public interface PreparedStatement extends Statement {}

public interface CallableStatement extends PreparedStatement {}

public interface ResultSet extends Wrapper, AutoCloseable {}

Java中提倡面向接口开发,而最经典的接口设计莫过于JDBC数据库接口。

Connection链接、Statement语句、PreparedStatement预处理语句、CallableStatement存储过程、ResultSet结果集。

调用方式有三种:Statement语句、PreparedStatement预处理语句、CallableStatement存储过程,推荐使用第二种PreparedStatement,防止SQL注入,其也是预编译性能高。

在这里插入图片描述

2.JDBC使用前的准备工作

  1. 导入jar包(使用JDBC提供了丰富的工具类)

  2. 提供连接数据库的参数(用户名root 密码root 端口号3306)

  3. 在java程序中,发起SQL语句操作数据库

  4. 如果数据库有查到的结果,返回给java程序

2.1创建项目并导入jar包

  • 创建project: File - New - Project - 选择java - next - next - 输入工程名称 - Finish
  • 导入jar包:找到文件mysql-connector-java-5.1.32.jar 复制,粘贴到Project里
  • 在IDEA里,选中jar包,右键编译(add as library…),ok
  • 检查是否编译成功:看到IDEA里的jar包可以被点开了,就代表编译成功了

在这里插入图片描述

导入jar包后,我们便可以开始测试JDBC的连接了。

2.2 编写测试连接代码

我们创建一个TestConnection的类,用来测试JDBC连接;

JDBC是java连接数据库的一个标准,本质上就是一堆的工具类。要在JAVA代码中,使用JDBC连接,需要以下几步:

  1. 注册驱动
  2. 获取数据库连接
  3. 获取传输器
  4. 执行SQL,并返回结果集
  5. 处理数据库返回的结果
  6. 释放资源

2.2.1 注册驱动

首先,我们注册驱动,需要获取jar包的字节码对象,使用反射的技术原理,有三种方式可以获取注册驱动:

  1. Class.forName(“类的全路径”):使用全路径名
  2. 类名.class:直接使用类名
  3. 对象.getClass():直接使用对象名

因为我们是导入的jar包,并没有创建对象,并且其导入的目录在工程目录下,并不在我们创建类的同级目录下, 所以我们使用第一种放发,全路径名的方式获取其字节码对象。

Class.forName("com.mysql.cj.jdbc.Driver");

2.2.2 获取数据库连接

jdbc连接mysql数据库的协议需要使用DriverManager类的getConnection方法,其要求传入一个url链接,数据库名称与数据库的密码三个参数,其中url为数据库的协议地址,我们直接使用字符串定义其整个URL,然后将字符串作为参数传入,这样可以实现简单的解耦关系,之后如果使用的数据库有变化,直接更改url即可,并且对于代码的整齐程序也很多帮助;

下方是其方法的源代码:

在这里插入图片描述

这是我们最后完成的代码:

 String url="jdbc:mysql://localhost:3306/study2022" ;
 Connection c = DriverManager.getConnection(url,"root","root");

因为数据库在我们本地,所以我们使用localhost代表我们的服务器地址,后面跟着我们创建数据库时设置的端口号,然后是数据库的名称;

2.2.3 获取传输器

JDBC的传输器不止有一种,我们先使用createStatement传输器,使用我们第二步创建的数据库连接对象来实现创建传输器,同时创建传输器的对象:

Statement s = c.createStatement();

2.2.4 执行SQL,并返回结果集

这里我们依旧使用解耦的思想编写代码:

首先编写SQL代码;

然后将SQL放入传输器对象的executeQuery方法中,执行SQL;

传输器对象中共包含三种方法,分别是:

  1. executeQuery():用来执行查询的语句
  2. executeLargeUpdate():用来执行增删改
  3. ResultSet结果集:用来保存查询后的结果

因为我们这里测试能否正确连接数据库并查询其中的数据,所以我们使用executeQuery方法:

String sql="select * from customers" ;
ResultSet r = s.executeQuery(sql);

2.2.5 处理数据库返回的结果

我们这里要对数据库返回的结果进行判断,所以要判断其返回结果中有没有值,有值,则一直循环获取,直接最后,所以这里使用while循环进行判断;

while循环中,我们使用.next()方法来确定获取的对象中有没有值;.next()方法会查询下一条记录,有记录(有值)返回true并把记录内容存入到对应的对象中,也就是obj.next()的obj中。如果没有返回false。

        while(r.next()){//next()判断resultset中有没有数据,有返回true
            //getXxx()获取resultset中的数据
            int a = r.getInt(1);//获取第1列的 整数值
            String b = r.getString(2);//获取第2列的 字符串值
            String c1 = r.getString(3);//获取第3列的 字符串值
            System.out.println(a+" "+b+" "+c1);
        }

while循环中,我们使用其列各自对应的数据类型方法接收对应的值,将其保存到对应的数据类型的变量中。

2.2.6 释放资源

最后,我们查询完成后,需要手动释放资源,包括结果集,传输器,连接器三个已经被占用的资源。

        r.close();//释放结果集
        s.close();//释放传输器
        c.close();//释放连接器

使用其各自的对象,调用close()方法释放资源即可;

2.3 完整代码展示

下面是我们测试的完整代码:

输入:

package cn.study;

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

public class TestConnection {
    public static void main(String[] args) throws Exception {
        //1,注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");//全路径
        //2,获取数据库的连接(用户名/密码)
        //jdbc连接mysql数据库的协议//本机:端口号/数据库的名字
        String url="jdbc:mysql://localhost:3306/study2022" ;
        Connection c = DriverManager.getConnection(url,"root","root");
        //3,获取传输器
        Statement s = c.createStatement();
        //4,执行SQL,并返回结果集
        String sql="select * from customers" ;//查询dept表的所有数据

        //executeQuery()用来执行查询的语句,executeLargeUpdate()用来执行增删改,ResultSet结果集,用来保存查询后的结果
        ResultSet r = s.executeQuery(sql);
        //5,处理数据库返回的结果
        while(r.next()){//next()判断resultset中有没有数据,有返回true
            //getXxx()获取resultset中的数据
            int a = r.getInt(1);//获取第1列的 整数值
            String b = r.getString(2);//获取第2列的 字符串值
            String c1 = r.getString(3);//获取第3列的 字符串值
            System.out.println(a+" "+b+" "+c1);
        }
        //6,释放资源
        r.close();//释放结果集
        s.close();//释放传输器
        c.close();//释放连接器
    }
}

输出:
在这里插入图片描述

3.模拟用户登录

3.1 准备测试数据

首先,我们在数据库中创建用户表,并插入一条用户数据:

CREATE TABLE tb_user(
   id int PRIMARY KEY auto_increment,
   name varchar(20) default NULL,
   password varchar(20) default NULL
)
insert into tb_user values(null,'jack','321')

3.2 测试数据库连接,验证数据是否正确存在

首先,我们创建TestEntry类,并创建method1( )方法,用于测试数据库连接,查询上面创建的表即插入的表数据是否存在:

    private static void method1() throws SQLException {
        //1.调用工具类的方法
        String url="jdbc:mysql://localhost:3306/study2022" ;
        Connection c = DriverManager.getConnection(url,"root","root");
        //2.获取传输器
        Statement s = c.createStatement();
        //3.执行SQL
        ResultSet r = s.executeQuery("select * from tb_user");
        //4.解析结果集
        while(r.next()){//判断r有数据
            //获取r的数据
            int a = r.getInt("id");//获取表里的id字段的值
            String b = r.getString("name");//获取表里的name字段的值
            String c1 = r.getString("password");//获取表里的password字段的值
            System.out.println(a+b+c1);
        }
        //5.释放资源
        r.close();//释放结果集
        s.close();//释放传输器
        c.close();//释放连接器
    }

3.3 测试用户登录

当我们使用jar包调用其工具类方法获取传输器时,需要每次都运用反射获取jar包中的方法,并传入数据库的名称及用户名、密码,这是十分繁琐的,所以我们将这一步封装到一个类中,这样我们今后只要直接调用它就可以了,而不用每次都手动完成,我们在创建JDBCUtils类并在其中创建getConnection用来封装这部分代码:

package cn.study;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JDBCUtils {
    public static Connection getConnection() throws Exception {

        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");//全路径
        //2.获取数据库连接
        String url="jdbc:mysql://localhost:3306/study2022" ;
        Connection c = DriverManager.getConnection(url,"root","root");
        //3.返回给调用者
        return c;

    }
}

为了避免每次调用时都需要new一个新的对象调用,我们这里使用静态关键字描述方法,后续可以直接使用类名调用。

然后,我们在TestEntry类中创建method2( )方法,用于模拟用户登录;

用户登录时,会输入自己的账户名及密码,我们只要确定保存账户及密码的表中,有对应的数据可以与之匹配,那么则表明当前用户已经注册,并且用户名与密码都输入正确,可以登录;

      private static void method2() throws Exception {
        //1,获取传输器
        Connection connection = JDBCUtils.getConnection();
        Statement s = connection.createStatement();
        //2,执行SQL
        System.out.println("请输入用户名:");
        String a = new Scanner(System.in).nextLine();//用户名
        System.out.println("请输入密码:");
        String b = new Scanner(System.in).nextLine();//密码
        //如果动态的拼接字符串时,数据在中间的位置  "+a+"
//        String sql="select * from tb_user where name='jack' and password='321'" ;
        String sql="select * from tb_user where name='"+a+"' and password='"+b+"'" ;
        ResultSet r = s.executeQuery(sql);
        //3,解析结果集
        if(r.next()){//查到数据了吗?查到了就登录成功
            System.out.println("登录成功~");
        }else{
            System.out.println("用户名或者密码输入错误,登录失败~");
        }
        //4,关闭资源
        r.close();
        s.close();
        connection.close();
    }

在上面的代码中,我们还是先注册驱动,获取数据库连接并获取传输器;然后我们提示用户在控制台输入账号和密码并接收其具体的值;

接收到用户的值后,我们需要动态拼接值,将值放入SQL查询中作为WHERE子句传入;正常的WHERE子句中的值需要用单引号将其扩起来,表明这是一个字符串,动态拼接字符串时,也需要这样做,但是单引号中的值需要双引号及俩个加号包裹,这样才可以正确的动态拼接,你可以理解为,使用"+变量名+"替换掉原本该放在WHERE子句位置的具体的值即可。

后续我们不再需要循环遍历获取的结果,我们只要判断是否正确返回了值即可,使用if语句进行判断,如果正确返回值,那么则查询成功,代表可以成功登录,此时返回给客户成功登录的提示;如果没有查询到数据,那么代表数据库中不存在输入的账号及密码,此时返回给客户登录失败的提示即可;

在这里插入图片描述

在这里我们还要注意一个地方,在 2.2 小节我们编写测试连接代码时,连接数据库首先要通过反射原理注册驱动,Class.forName可以指定class类路径进行动态创建对象实例,可JDBC这句话没有返回对象,这是为什么呢?

我们通过查看java.sql.Driver.class的源码就找到真相了,原来它用了静态代码块创建对象。

在这里插入图片描述
但是这里我们并没有进行这一步同样连接成功并获取了数据,那么我们到底需要注册驱动吗?

其实这是因为java提供了SPI机制,用户可以自行配置类,JDBC高版本驱动就都引入了这个支持。如果用户使用了Class.forName方式就自己指定了驱动,如果未写这句话,则Java自动去META-INF/services/java.sql.Driver文件中找启动类。所以即使你不写,java也会在编译时自动帮你补全这段代码;

在这里插入图片描述

3.5 完整代码展示

到这里,我们测试用户登录就成功完成了,下面是测试的完整代码:

输入:

package cn.study;

import javax.sound.midi.Soundbank;
import java.sql.*;
import java.util.Scanner;

public class TestEntry {
    public static void main(String[] args) throws Exception {
        //method1();
        method2();
    }

    private static void method2() throws Exception {
        //1,注册驱动 2,获取连接
        String url="jdbc:mysql://localhost:3306/study2022" ;
        Connection c = DriverManager.getConnection(url,"root","root");
        //3,获取传输器
        Statement s = c.createStatement();
        //4,执行SQL
        System.out.println("请输入用户名:");
        String a = new Scanner(System.in).nextLine();//用户名
        System.out.println("请输入密码:");
        String b = new Scanner(System.in).nextLine();//密码
        //如果动态的拼接字符串时,数据在中间的位置  "+a+"
//        String sql="select * from tb_user where name='jack' and password='321'" ;
        String sql="select * from tb_user where name='"+a+"' and password='"+b+"'" ;
        ResultSet r = s.executeQuery(sql);
        //5,解析结果集
        if(r.next()){//查到数据了吗?查到了就登录成功
            System.out.println("登录成功~");
        }else{
            System.out.println("用户名或者密码输入错误,登录失败~");
        }
        //6,关闭资源
        r.close();
        s.close();
        c.close();
    }

    private static void method1() throws SQLException {
        //调用工具类的方法
        String url="jdbc:mysql://localhost:3306/study2022" ;
        Connection c = DriverManager.getConnection(url,"root","root");
        //获取传输器
        Statement s = c.createStatement();
        //执行SQL
        ResultSet r = s.executeQuery("select * from tb_user");
        //解析结果集
        while(r.next()){//判断r有数据
            //获取r的数据
            int a = r.getInt("id");//获取表里的id字段的值
            String b = r.getString("name");//获取表里的name字段的值
            String c1 = r.getString("password");//获取表里的password字段的值
            System.out.println(a+b+c1);
        }
        //释放资源
        r.close();//释放结果集
        s.close();//释放传输器
        c.close();//释放连接器
    }

}

输出:
在这里插入图片描述

4. SQL注入问题及解决方案

我们创建TesTInjection类,并复制TestEntry类的method2( )方法到TesTInjection类中,并将方法名改为method1(),然后我们运行代码:

在这里插入图片描述

我们在输入用户名时,输入jack'#字符串,然后在提示输入密码时不输入密码,直接按回车,竟然登录成功了。首先数据库中并没有jack'#这个用户,其次,我们也没有正确输入密码,为什么会登录成功呢?

其实这是因为SQL注入导致的攻击问题,本质上就是因为SQL语句中出现了特殊符号(#,注释掉了一些条件),导致了SQL语义改变了。这里出现的#让程序错误的以为,此次SQL语句执行到select * from tb_user where name='"+a+"'的位置就已经结束了,SQL语句后面的部分,被认为是注释的信息(#在sql语句中是注释的意思),所以查询结果也还会返回数据,且被认为是正确的查询结果。

解决这个问题很简单,我们只需要将传输器更换即可,Statement是低级的传输器,不安全,而且低效,更换为PreparedStatement高级传输器,会更加安全。

并且,我们在编写SQL语句时,也可以选择更加成熟的方案,使用?代替参数的位置,?叫占位符;其更加简洁,并且可以有效的避免SQL拼接参数可能带来的问题。

下面我们创建method2()方法,将method1( )方法中的内容复制过来,并修改其传输器及SQL语句:

    private static void method2() throws Exception {
        //1,注册驱动
        Connection connection = JDBCUtils.getConnection();
        //2,执行SQL
        System.out.println("请输入用户名:");
        String a = new Scanner(System.in).nextLine();//用户名
        System.out.println("请输入密码:");
        String b = new Scanner(System.in).nextLine();//密码
        // //SQL骨架:用?代替了参数的位置,?叫占位符;好处:简洁(避免了SQL拼接参数)
        String sql="select * from tb_user where name= ? and password= ?" ;
        //获取传输器
        PreparedStatement s = connection.prepareStatement(sql);
        //ResultSet r = s.executeQuery(sql);
        /*因为更换了新的传输器,SQL将作为参数传入到新的传输器中使用,
            并且使用占位符重新编写了SQL语句,所以需要给占位符传入参数
            我们使用其对象s中的setxxx方法传入参数*/
        s.setString(1,a);//括号中需要俩个参数,第一个是位置,第二个是值
        s.setString(2,b);//?的索引,要给?设置的值
        ResultSet r = s.executeQuery();
        //5,解析结果集
        if(r.next()){//查到数据了吗?查到了就登录成功
            System.out.println("登录成功~");
        }else{
            System.out.println("用户名或者密码输入错误,登录失败~");
        }
        //6,关闭资源
        r.close();
        s.close();
        connection.close();
    }

更换传输器后,我们需要将SQL语句作为参数传入新的传输器对象中进行使用;并且我们使用占用符更新了SQL,所以需要传输器对象 s 的setxxx方法,明确表明占位符接受的参数是哪个。

在这里插入图片描述

此时,便已经解决了SQL注入导致的攻击问题。

5.JDBC常见问题

5.1 驱动版本

不同版本的mysql需要对应不同版本的驱动,版本不对应会导致驱动无法正确运行而报错

Mysql5.0x mysql-connector-java-5.1.32.jar

Mysql8.0x mysql-connector-java-8.0.21.jar

5.2 中文乱码

url增中加参数:characterEncoding=utf8可以防止中文乱码

String url ="jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false";
## 数据库的名字  解决中文乱码  指定时区  关闭权限检验

5.3 PreparedStatement 语句

SQL注入解决方案:

Statement对象换为PreparedStatement对象

sql = "select * from teachers where tname=?";			#参数使用问号
PreparedStatement stat = cn.prepareStatement(sql); 		#对象换掉
stat.setString(1, condition);					#对应参数类型,第几个问号
ResultSet rs = stat.executeQuery();			#去掉sql参数

PS后的结果:

SELECT * FROM teachers WHERE tname='陈强\' or 1=1 or \''

利用转义字符,屏蔽了SQL中的恶意字符。不仅解决了sql注入问题,使系统变的安全,PreparedStatement还有个极大的好处,它是预编译的语句,其主干部分mysql进行预编译后缓存,下次这部分就无需在解析,只把条件拼入,这样执行效率远高于statement每次都要编译sql语句。

5.4 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

错误原因:

  • jar没有导入,没有builder path

  • Class.forName(“com.mysql.jdbc.Driver”); 字符串拼写错误

5.5 Unknown database mydb;

错误原因:

  • 数据库名称拼写错误

5.6 Access denied for user ‘root123’@‘localhost’ (using password: YES)

错误原因:

数据库用户名或者密码错误

5.7 Table ‘py-school-db.mydb’ doesn’t exist

错误原因:

表不存在,也可能表名写错了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值