zl程序教程

您现在的位置是:首页 >  其他

当前栏目

JDBC面试题

2023-09-11 14:14:33 时间

数据库驱动

1.数据库驱动的概念

数据库厂商提供的用来操作数据库的jar包,就叫做数据库的驱动。

JDBC

1.1.JDBC的概念: (掌握)
        1.1.1.数据库驱动: 数据库厂商提供的用来操作数据库的jar包

        1.1.2.JDBC: 由于不同的数据库厂商提供的数据库驱动各不相同,导致开发人员的学习成本十分的高, 于是sun公司提供了一套用来统一访问数据库的标准, 其实就是一大堆的接口, SUN公司要求各个数据库厂商设计的jar包都要实现这套接口, 因此开发人员只需要学会这套接口, 所有的数据库驱动就都会使用了.

        1.1.3.JDBC目前已经被加入到了javase的技术范畴,这意味着一个基本的java工程就具有JDBC相关的jar包(java.sql/javax.sql).
        
        1.1.4.注意: jdbc只是一套接口,具体操作数据库的代码都在接口的实现类中,也就是数据库驱动中,开发时还是要导入具体的数据库驱动.

    1.2.六个步骤实现JDBC程序 (!!掌握)
        //1.注册数据库驱动
        //2.获取数据库连接
        //3.获取传输器对象
        //4.利用传输器传输SQL到数据库执行,获取结果集对象
        //5.遍历结果集,获取结果数据
        //6.关闭资源

    1.3.JDBC的增删改查(略) (!!掌握)
    
    1.4.PreparedStatement (!!掌握)
        Sql注入攻击: 由于后台的sql语句是拼接而来的, 其中的参数是用户提交的, 如果用户在参数中添加了一些sql关键字或者是特殊字符, 就可能改变sql语句的原意, 从而执行意外的操作
        
        PreparedStatement优点:
            (1)可以防止sql注入攻击, 采用预编译机制, 先将sql语句的主干发送数据库, 数据库编译后就确定了sql语句的语意, 如果后面参数中再包含sql关键字或者是特殊字符, 也只会当作普通的字符来处理!!!
            (2)通过方法来设置参数, 省去了拼接sql语句的麻烦.
            (3)能够尽最大可能来提高效率. PreparedStatement发送的sql语句编译后就被数据库缓存下来了, 再次执行时, 如果和缓存中的匹配就会使用缓存中的语句, 不再编译, 直接执行. statement发送的sql语句是先拼接好再发送给数据库, 由于参数不同整条sql语句也就不同, 所以每次都需要编译.

1.JDBC的概念

JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。

由于不同的数据库厂商提供的数据库驱动各不相同,在使用不同数据库时需要学习对应数据库驱动的api,对于开发人员来说学习成本十分的高。

于是sun提供了JDBC的规范,本质上一大堆的接口,要求不同的数据库厂商提供的驱动都实现这套接口,这样以来开发人员只需要学会JDBC这套接口,所有的数据库驱动作为这套接口的

实现,就都会使用了。

2.JDBC包

JDBC主要是由 java.sql 和javax.sql包组成的,并且这两个包已经被集成到J2SE的规范中了,这意味着,只要一个普通的java程序就可以使用JDBC。

要注意的是,在开发数据库程序时,除了如上的两个包,还需要手动的导入具体的数据库驱动。

如图-1所示:

3.六个步骤实现JDBC

Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
    //1.注册数据库驱动
    DriverManager.registerDriver(new Driver());
    //2.获取数据库的连接
    conn=DriverManager.getConnection("jdbc:mysql:///day15?user=root&password=root");
    //3.获取传输器对象
    stat = conn.createStatement();
    //4.传输sql语句到数据库中执行,获取结果集对象
    rs = stat.executeQuery("select * from user");
    //5.遍历结果集获取需要的数据
    while(rs.next()){
        String name = rs.getString("name");
        Date date = rs.getDate("birthday");
        System.out.println(name+":"+date.toLocaleString());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        //6.关闭资源
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally{
                rs = null;
            }
        }
        if(stat != null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally{
                stat = null;
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally{
                conn = null;
            }
        }
    }

4.CRUD操作 —— 删

(1)在src下创建config.properties配置文件,并添加如下内容:

driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///mydb5
user=root
password=root

 (2)在JDBCUtils中添加如下代码:

private static Properties prop = new Properties();
    
    static{
        //读取配置文件
        try {
            //String path = "bin/config.properties";
            //通过类加载器获取路径
            ClassLoader loader = JDBCUtils.class.getClassLoader();
            String path = loader.getResource("config.properties").getPath();
            prop.load(new FileInputStream(path));
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }
    
    public static Connection getConnection(){
        Connection conn;
        try {
            String driverClass = prop.getProperty("driverClass");
            String jdbcUrl = prop.getProperty("jdbcUrl");
            String user = prop.getProperty("user");
            String password = prop.getProperty("password");
            
            Class.forName(driverClass);
            conn = DriverManager.getConnection(jdbcUrl,user,password);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

 (3)删除代码变为:

Connection conn = null;
        Statement stat = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            stat = conn.createStatement();
            int rows = stat.executeUpdate("delete from account where name='张飞'");
            System.out.println("影响了"+rows+"行");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }finally{
            //释放资源
            JDBCUtils.close(conn, stat, rs);
        }

5.SQL注入攻击

(1)Sql注入攻击演示

 在登录界面我们输入,如图-6,图-7所示:

 

发现无需输入密码,直接登录,发现无需密码登录了进去。

这就是发生了SQL注入问题。

(2)Sql注入攻击的原理

由于jdbc程序在执行的过程中sql语句在拼装时使用了由页面传入参数,如果用户,恶意传入一些sql中的特殊关键字,会导致sql语句意义发生变化,这种攻击方式就叫做sql注入。

如何防止SQL注入攻击呢?

这时候就需要用到PreparedStatement对象。

6.PreparedStatement

PreparedStatement是Statement的孩子,不同的是,PreparedStatement使用预编译机制,在创建PreparedStatement对象时就需要将sql语句传入,传入的过程中参数要用?替代,这个过程回导致传入的sql被进行预编译,然后再调用PreparedStatement的setXXX将参数设置上去,由于sql语句已经经过了预编译,再传入特殊值也不会起作用了。

PreparedStatement使用了预编译机制,sql语句在执行的过程中效率比Statement要高。

PreparedStatement使用了 “?”通配符省去了字符串的拼接,使代码更加优雅。

代码演示:

/**
     * 增加用户到数据库
     * @param user
     */
    public void addUser(User user) {
        Connection conn = JDBCUtils.getConn();
        PreparedStatement ps= null;
        try {
            ps = conn.prepareStatement("insert into users values (null,?,?,?,?,?,null)");
            ps.setString(1, user.getUsername());
            ps.setString(2, user.getPassword());
            ps.setString(3, user.getNickname());
            ps.setString(4, user.getEmail());
            ps.setString(5, user.getRole());
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }finally{
            JDBCUtils.close(conn, ps, null);
        }
    }

经测试可以防止sql注入共计问题。

批处理

    2.1.批处理
        如果有大量的sql语句要到数据库执行, 如果一条一条发送, 有多少条就要发送多少次, 效率低下.
        可以将这些sql语句打成一个批, 一次性发送给数据库, 数据库收到后打开批, 依次执行, 减少了发送sql语句次数, 从而提高程序执行的效率.

    2.2.Statement方式来实现批处理
        优点:
            可以包含结构不同的sql语句
        缺点:
            不能防止sql注入攻击
            没有预编译机制, 效率低下
            如果发送的sql语句主干部分相同, 主干部分每次都需要写.
        
    2.3.PreparedStatement方式实现批处理
        优点:
            可以防止sql注入攻击
            采用预编译机制, 效率高
            如果发送的sql语句主干部分相同, 主干部分只需要写一次, 每次发送的只是参数部分.
        缺点:
            包含的sql语句的主干部分必须相同

1.批处理业务场景

当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。

2.Statement 方式实现批处理

Statement.addBatch(sql)

执行批处理SQL语句

executeBatch()方法:执行批处理命令

clearBatch()方法:清除批处理命令

代码演示:

Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
    conn = JdbcUtil.getConnection();
    String sql1 = "insert into person(name,password,email,birthday)     values('kkk','123','abc@sina.com','1978-08-08')";
    String sql2 = "update user set password='123456' where id=3";
    st = conn.createStatement();
    st.addBatch(sql1);  //把SQL语句加入到批命令中
    st.addBatch(sql2);  //把SQL语句加入到批命令中
    st.executeBatch();
} finally{
    JdbcUtil.free(conn, st, rs);
}

采用Statement.addBatch(sql)方式实现批处理:

优点:可以向数据库发送多条不同的SQL语句。

缺点

SQL语句没有预编译。

当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句。

例如:

insert into user(name,password) values('aa','111');
insert into user(name,password) values('bb','222');
insert into user(name,password) values('cc','333');
insert into user(name,password) values('dd','444');

实现批处理的第二种方式:

PreparedStatement.addBatch()

代码演示:

conn = JdbcUtil.getConnection();
String sql = "insert into person(name,password,email,birthday)
values(?,?,?,?)";
ps = conn.prepareStatement(sql);
for(int i=0;i<50000;i++){
    ps.setString(1, "aaa" + i);
    ps.setString(2, "123" + i);
    ps.setString(3, "aaa" + i + "@sina.com");
    ps.setDate(4,new Date(1980, 10, 10));
    ps.addBatch(); 
    if(i%1000==0){
    ps.executeBatch();
    ps.clearBatch();
}
}
ps.executeBatch();

采用PreparedStatement.addBatch()实现批处理:

优点:发送的是预编译后的SQL语句,执行效率高。

缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。

连接池

3.1.自定义连接池 (!!重要)
        对一个方法进行改造:

        3.1.1.继承 -- 不能对已有的对象上的方法进行改造
            定义一个类继承被改造者所属的类, 在子类中覆盖父类中的方法来实现方法的改造

        3.1.2.装饰设计模式
            如果需要对已有对象上的方法进行改造, 可以定义类将被装饰者传入, 基于已有的功能, 添加更强的功能

            那么这个自定义的类就是装饰类. 装饰类通常会提供构造函数将被装饰者传入, 并保存在类的内部, 基于已有对象上的功能, 添加更强的功能

            实现步骤:
                a) 定义装饰类, 装饰类要求和被装饰者所属的类实现相同的接口或者是继承相同的类
                b) 在装饰类中, 通过构造方法将被装饰者传入, 保存在类的内部
                c) 对于不想改造的方法, 直接调用已有对象上的方法, 对于想要改造的方法进行改造
            
    3.2.开源数据库连接池 (!!掌握)
        3.2.1.C3P0连接池应用
            (1).导入开发包
            (2).创建数据库连接池
                ComboPooledDataSource cpds = new ComboPooledDataSource();
            (3).配置基本的连接信息
                a) 通过setters方法来设置参数
                    cpds.setDriverClass("com.mysql.jdbc.Driver");
                    cpds.setJdbcUrl("jdbc:mysql:///mydb5");
                    cpds.setUser("root");
                    cpds.setPassword("root");

                b) 通过c3p0-config.xml文件来配置基本连接信息
                    提供c3p0-config.xml文件, 放置在程序source目录下, 在文件中添加如下配置:
                    <?xml version="1.0" encoding="UTF-8"?>
                    <c3p0-config>
                        <default-config>
                            <property name="driverClass">com.mysql.jdbc.Driver</property>
                            <property name="jdbcUrl">jdbc:mysql:///mydb1</property>
                            <property name="user">root</property>
                            <property name="password">root</property>
                        </default-config>
                    </c3p0-config>
                c) 通过c3p0.properties文件来配置基本连接信息
                    c3p0.driverClass=com.mysql.jdbc.Driver
                    c3p0.jdbcUrl=jdbc:mysql:///mydb1
                    c3p0.user=root
                    c3p0.password=root

1.连接池的概述

用户每次请求都需要向数据库获得链接,而数据库创建链接通常需要消耗相对较大的资源,创建时间也较长。

假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。

如图-9所示:

频繁的开关连接相当的耗费资源,所以我们可以设置一个连接池,在程序启动时就初始化一批连接,在程序中共享,需要连接时从池中获取,用完再还回池中,通过池共享连接,减少开关连接的次数,提高程序的效率。

如图-10所示:

2.c3p0 连接池使用

(1)导入jar包

(2)写配置文件,放置到类加载目录下

<?xml version="1.0"?>
<c3p0-config>
<default-config>
         <property name="driverClass">com.mysql.jdbc.Driver</property>
         <property name="jdbcUrl">jdbc:mysql:///mydb1</property>
         <property name="user">root</property>
         <property name="password">root</property>
</default-config>
</c3p0-config>

(3)程序中获取连接池对象

ComboPooledDataSource pool = new ComboPooledDataSource();

JDBC的事务管理

1.什么是事务管理

默认情况下,我们创建的数据库连接,是工作在自动提交的模式下的。这意味着只要我们执行完一条查询语句,就会自动进行提交。

因此我们的每条查询,实际上都是一个事务,如果我们执行的是DML或者DDL,每条语句完成的时候,数据库就已经完成修改了。

有的时候我们希望由一组SQL查询组成一个事务,如果它们都执行OK我们再进行提交,如果中途出现异常了,我们可以进行回滚。

JDBC接口提供了一个setAutoCommit(boolean flag)方法,我们可以用它来关闭连接自动提交的特性。

我们应该在需要手动提交时才关闭这个特性,不然的话事务不会自动提交,每次都得手动提交。

数据库通过表锁来管理事务,这个操作非常消耗资源。因此我们应当完成操作后尽快的提交事务。

2.如何回滚事务

通过Connection对象的rollback方法可以回滚事务。它会回滚这次事务中的所有修改操作,并释放当前连接所持有的数据库锁。

3.JDBC的保存点(Savepoint)是什么,如何使用?

有时候事务包含了一组语句,而我们希望回滚到这个事务的某个特定的点。JDBC的保存点可以用来生成事务的一个检查点,使得事务可以回滚到这个检查点。

一旦事务提交或者回滚了,它生成的任何保存点都会自动释放并失效。回滚事务到某个特定的保存点后,这个保存点后所有其它的保存点会自动释放并且失效。