JDBC总结

目录

什么是Jdbc:

Jdbc的执行顺序: ​编辑

加载并注册数据库驱动:

​编辑

Connection对象

通过这种方式获取Connection对象会有什么问题?

使用properties文件解决硬编码问题

将部分代码抽离写出JdbcUtils工具类

Statement对象

使用Statement对象会遇到什么问题:sql注入

sql注入是什么:

sql注入如何解决:

造成SQL注入的原因

PrepareStatement对象

Sql注入演示: 

ResultSet对象

连接池对象

什么是数据库连接池?

为什么要有数据库连接池?

获取数据库缓冲池的代码示例:


声明部分图片来自百战尚学堂

什么是Jdbc:

JDBC定义了在Java语言中连接数据库的标准,当我们查询数据时就需要使用到数据库驱动,而不同的数据库有不同的驱动,java通过jdbc标准屏蔽了这种不同,将不同化为相同,应用程序只要实现了JDBC且驱动实现了JDBC那么就可以用不同的驱动

Jdbc的执行顺序: 

加载并注册数据库驱动:

首先我们需要导入外部数据库驱动,我们这里使用的是mysql的数据库驱动,大家要的话可以拿去:

百度网盘链接:https://pan.baidu.com/s/1wmdtKDnxIOxc1jpIRE6Ndw?pwd=2bkx 
提取码:2bkx

我们可以通过反射机制Class.forName(数据库驱动名称)加载数据库驱动器,在mysql数据库jar包中有一个jdbc包,mysql实现了java的jdbc标准,在该包下有一个Driver类,这个类就是用来加载并注册驱动器的

 我们可以通过他的源码可知:

该类的代码中有一段静态代码块,静态代码块在类加载时加载,只会加载一次,所以当我们通过反射机制加载数据库驱动器时,相当于调用了该类的静态代码块

那么为什么要放到静态代码块中呢,如果你放在非静态的方法里,那么就表示他是和对象有关的
可是我们一般都只是想加载驱动后,得到一个Connection而已,跟对象一点关系都没有,那么又为什么要去实例一个对象先,然后再得到一个Connection这么劳累的事

mysql数据库的url:jdbc:mysql://localhost:数据库端口号/数据库名?useSSL=false

代码示例:

Connection对象

java会将加载好的数据库驱动放在DriverManager类中,我们可以通过DriverManager调用getConnection(String url,String username,String password)静态方法获取Connection对象,而这个方法需要传递三个参数,

第一个就是数据库驱动的url,因为java会将加载好的数据库驱动放在DriverManager类中,那都是数据库驱动java咋知道我们用的是哪个数据库驱动呢,所以我们需要使用数据库驱动的url进行获取相对应的数据库连接

mysql数据库的url:jdbc:mysql://localhost:数据库端口号/数据库名?useSSL=false

第二个是登录数据库的用户名

第三个是登录数据库的密码

代码示例:

通过这种方式获取Connection对象会有什么问题?

 通过这种方式获取Connection对象会有硬编译的问题,硬编码是指将可变变量用一个固定值来代替的方法。用这种方法编译后,如果以后需要更改此变量就非常困难了。

使用properties文件解决硬编码问题

通过properties属性文件可以解决这种问题,这种文件以key=value格式存储内容。Java中可以使用Properties工具类来读取这个文件。项目中会将一些配置信息放到properties文件中,所以properties文件经常作为配置文件来使用。

Properties工具类中常用方法

load(InputStream is)        通过给定的输入流对象读取properties文件并解析

getProperty(String key)        根据key获取对应的value

我们可以将url、username、password这些可变的变量提取放到properties文件中

然后通过Properties对象的getProperty(String key)方法,传递key参数返回对应的value

try(//通过字节输入流读取properties文件
            FileInputStream fis = new FileInputStream("src/jdbc.properties")){
            //实例化Properties对象
            Properties prop = new Properties();
            //解析读取到的properties文件
            prop.load(fis);
            //读取连接数据库的url
            url = prop.getProperty("url");
            //获取用户名
            name = prop.getProperty("username");
            //获取密码
            pwd = prop.getProperty("pwd");
            //获取数据库驱动全名
            String driver = prop.getProperty("driver");
            //通过反射机制加载数据库驱动
            Class.forName(driver);
        }catch (Exception e){
            e.printStackTrace();
        }

然后我们就可以通过DriverManager创建Connection对象了,而此时每当我们需要加载mysql连接时就得这么写岂不是太麻烦了,所以我们可以将这部分代码抽离出来写一个Utils工具类

将部分代码抽离写出JdbcUtils工具类

此时我们可以看到我将这段代码放到了静态代码块中,为什么呢,我们可以参考一下mysql的jdbc包中的Driver类,它通过了静态代码块进行数据库驱动器的加载,而静态代码块在类加载时执行一次,之后就会再执行了

而我们操作properties文件是需要用到IO流的,大家都知道IO流是比较耗时间的,而且操作properties文件只需要执行一次就好了,所以我们直接将操作properties文件的代码放到静态代码块中

然后定义了getConnection方法,该方法的返回值是Connection对象,通过该方法我们可以获取到mysql的连接器

/**
 * Jdbc工具类
 */
public class JdbcUtils {
    private static String url;
    private static String name;
    private static String pwd;
    static{
        try(//通过字节输入流读取properties文件
            FileInputStream fis = new FileInputStream("src/jdbc.properties")){
            //实例化Properties对象
            Properties prop = new Properties();
            //解析读取到的properties文件
            prop.load(fis);
            //读取连接数据库的url
            url = prop.getProperty("url");
            //获取用户名
            name = prop.getProperty("username");
            //获取密码
            pwd = prop.getProperty("pwd");
            //获取数据库驱动全名
            String driver = prop.getProperty("driver");
            //通过反射机制加载数据库驱动
            Class.forName(driver);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
 
    //获取数据库连接对象
    public static Connection getConnection(){
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url,name,pwd);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }
}

而Connection对象时需要关闭的,包括后期的Statement对象,ResultSet对象都是需要关闭的,而且他们经常联合使用所以我们再添加一些关闭这些对象的方法
 

//关闭连接对象
    public static void closeConnection(Connection connection){
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //提交事务
    public static void commit(Connection connection){
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //事务回滚
    public static void rollback(Connection connection){
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //关闭Statement对象
    public static void closeStatement(Statement statement){
        try {
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //关闭ResultSet
    public static void closeResultSet(ResultSet resultSet){
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //DML操作时关闭资源
    public static void closeResource(Statement statement,Connection connection){
        //先关闭Statement对象
        closeStatement(statement);
        //再关闭Connection对象
        closeConnection(connection);
    }

    //DQL操作时关闭资源
    public static void closeResource(ResultSet resultSet,Statement statement,Connection connection){
        //先关闭ResultSet
        closeResultSet(resultSet);
        //再关闭Statement
        closeStatement(statement);
        //最后关闭Connection
        closeConnection(connection);
    }

Statement对象

Statement对象有张图就说过了,它是用来执行sql语句的,我们通过Connection对象下的createStatement()方法创建Statement对象

Statement执行sql语句的代码编写顺序:

1、首先先搭建好try…catch…finally框架,并且在try语句外声明Connection和Statement对象

2、获取Connection连接

3、通过Connection对象的createStatement()方法创建Statement对象

4、编写sql语句

5、Statement对象调用execute(String sql)传递sql语句来执行sql

6、关闭Statement对象、Connection对象

注意:先关闭Statement对象再关闭Connection对象

那执行sql语句首先我们得要有表把,所以我们这里创建一个表叫users,userid为主键列

 代码示例:实现向users表添加数据,这里主要是看代码的编写顺序,sql语句怎么写完全是看你自己,execute()方法返回的是一个布尔值,如果sql语句执行过后有结果集返回true,没有结果集返回false

添加用户:

 

使用Statement对象会遇到什么问题:sql注入

sql注入是什么:

可以用它来从数据库获取敏感信息、利用数据库的特性执行添加用户、导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。

sql注入如何解决:

使用PreparedStatement对象执行sql语句

造成SQL注入的原因

程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的 SQL 脚本,程序在接收后错误的将攻击者的输入作为 SQL 语句的一部分执行,导致原始的查询逻辑被改变,执行了攻击者精心构造的恶意 SQL 语句。

PrepareStatement对象

PreparedStatement对象拥有预编译sql语句的能力,我们可以使用通配符(?)代替参数部位,PreparedStatement对象可将sql语句和参数分离,预编译sql语句过后再绑定参数

PreparedStatement对象执行sql语句代码编写顺序:

1、首先先搭建好try…catch…finally框架,并且在try语句外声明Connection和PreparedStatement对象

2、获取Connection对象

3、编写sql语句参数部位通过通配符(?)占位

4、通过Connection对象的prepareStatement()方法创建PreparedStatement对象

5、sql语句中的参数绑定,需要传递?的位置(?的位置从1开始)

6、preparedStatement对象通过execute()方法执行sql语句

7、关闭PreparedStatement对象、Connection对象

代码示例:

根据userid修改用户信息username、userage

这段代码就遵循了我们刚刚写PreparedStatement对象执行sql语句的代码编写顺序,这里主要要讲的是绑定参数是通过preparedStatement对象调用set参数类型(通配符位置,参数);通配符位置从1开始

那么学到这里大家可能云里雾里,哎呀这跟sql注入有毛关系啊,这样子还不如使用Statement对象呢,不是这样的下面我们就来演示一下sql注入

Sql注入演示: 

我们刚刚说过Statement对象会出现sql注入的问题,那么我们就使用Statement对象来演示一下sql注入,这里先声明一下这个ResultSet是接收查询到的结果集的,executeQuery()方法就是执行DQL语句的,这些在ResultSet对象讲解中会详细的讲

现在我们来看sql语句的编写,此时sql语句是通过字符串拼接的方式将参数写入的,查询users表中用户名username为xxx并且用户年龄userage为xxx的用户信息,顺便将拼接过后的sql语句输出出来

这是users表中所有用户信息

  

 现在我们来些一个测试类,测试类中用户名我们这么写:"oldlu 'or 1=1 -- " ,按理来说此时执行sql语句进行DQL操作是没有结果的因为我们根本就没有这个名字的人

执行过后我们会发现,我们查询除了所有用户的信息,这是一个非常可怕的问题,攻击者通过改变DQL操作的逻辑,查询到所有用户的信息,这相当于就是数据泄露了呀

此时我们可以看看sql语句在拼接过后是什么样的,查询users表中用户名username = 'oldlu'或者 1=1的用户信息,后面的--在mysql中是注释的意思,所以后面相当于注释掉了。or就是或者的意思,一方为true就为true,而1=1永远都为true,所以这个条件将永远成立,那么就可以查询出所有用户的信息

sql注入就是通过更改我们DQL查询的逻辑进行从数据库获取敏感信息

那么PreparedStatement对象为什么就不会出现sql注入的问题呢?刚刚不是说了嘛,PreparedStatement对象先预编译sql语句,然后再将参数绑定,所以不管怎样都不会对我们的查询逻辑进行改变

我们使用PreparedStatement对象模拟一下sql注入看是否会出现问题,在执行sql语句过后输出sql语句

 通过测试类再次进行sql注入

 通过结果我们可以发现参数绑定过后sql语句照样还是和我们编写的DQL查询逻辑一样,这就说明了预编译功能的作用,就是说不管我们输入的参数是什么他就只是一个参数,不是通过Statement字符串拼接那样可以改变DQL查询的逻辑。

ResultSet对象

ResultSet用来存放数据库查询操作获得结果集,通过对ResultSet的操作可以获取查询到的结果集数据。

ResultSet特点

  • ResultSet 对象具有指向其当前数据行的指针。最初,指针被置于第一行之前next 方法将指针移动到下一行;因为该方法在 ResultSet 对象中没有下一行时返回 false,所以可以在 while 循环中使用它来迭代结果集。
  • 默认的 ResultSet 对象仅有一个向前移动的指针。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。
  • ResultSet 接口提供用于获取当前行检索列值的获取方法(getBoolean、getLong 等)。可以使用列的索引位置或列的名称检索值。

ResultSet使用原理

代码示例:

连接池对象

什么是数据库连接池?

为数据库建立一个缓冲区,预先在缓冲区中创建多个连接对象,当我们需要使用时就从缓冲池中拿连接对象,使用完之后返还给缓冲池

为什么要有数据库连接池?

如果我们使用直连的方式每次向数据库建立连接然后获取Connection对象,用完之后关闭,这样子就会浪费资源和时间,为什么我们不在代码运行之前就建立好一个缓冲区,当我们需要使用连接时就通过缓冲区获得连接,用完之后返还不就好了吗

获取数据库缓冲池的代码示例:

Java使用DataSource存放数据库连接池对象,而数据库缓冲池有很多种第三方的,我们这里使用的是阿里的Druid,阿里的数据库缓冲池性能较好,大家如果想要可以通过一下连接获取,获取过后在项目路径下创建一个lib目录用于存放jar包,这个目录名字是你自己定的,然后将jar包拉进去即可,最后右键添加为库才能使用

 百度网盘链接:https://pan.baidu.com/s/1ssYWSdVEo10OZkn_CyO_cQ?pwd=j913 
提取码:j913

通过DruidDataSourceFactory的静态方法createDataSource(Properties pos);传递存放了数据库url,用户名,密码的Properties对象,创建连接池对象

properties文件:

 initialSize代表的是初始化容量,就是说这个缓冲池中初始化时里边放多少个数据库连接对象

maxActive代表最大容量,代表该缓冲池中的数据库连接对象不允许超过这么多个

driverClassName指的是数据库驱动的全名,Druid通过加载这个类获取数据库驱动器

/**
 * 基于Druid连接池获取数据库连接的工具类
 */
public class JdbcDruidUtils {
    //连接池对象
    private static DataSource ds;
    static{
        //加载Druid配置文件
            FileInputStream fis;
            try {
                //获取读取Druid配置文件的输入流
                fis = new FileInputStream("src/druid.properties");
                //创建Properties对象
                Properties pos = new Properties();
                //加载Druid配置文件
                pos.load(fis);
                //获取连接池对象
                ds = DruidDataSourceFactory.createDataSource(pos);
 
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
}

要点:记住PreparedStatement对象执行sql语句的代码编写顺序,Statement对象以后不常用

1、搭建try...catch...finally矿建,try上边声明Connection对象和PreparedStatement对象

2、获取连接Connection

3、编写sql语句

4、通过Connection对象的prepareStatement(String sql)方法创建PreparedStatement对象并且预编译sql语句

5、参数绑定

6、PreparedStatement对象调用execute()方法执行sql语句

7、关闭PreparedStatement对象、Connection对象

注意:一定要先关闭PreparedStatement对象再关闭Connection对象

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值