春招总结之JDBC一遍过

本文详细介绍了JDBC的使用,包括连接数据库的多种方式(如反射加载driver)、Statement与PreparedStatement的对比及使用、批量处理、事务管理和DAO实现。重点讲解了PreparedStatement的预编译特性,强调了其在防止SQL注入和提高效率方面的重要性。此外,还探讨了数据库连接池的必要性和Druid连接池的使用。
摘要由CSDN通过智能技术生成

春招总结之JDBC一遍过

一、JDBC是什么?

 JDBC全称是:Java Database Connectivity,是一种独立于特定数据库系统、通用的SQL数据存储和操作的公共接口,定义了用来访问数据库的标准Java类库(Java.sql/javax.sql)使用这些类库可以以一种标准的方法获取数据库的资源。
 说人话就是用一种规范的方法,通过一个统一的标准来实现对数据库的使用而不用去考虑特定数据库的实现细节。
 目前Java中使用的数据库存储技术有以下三类:

  • JDBC直接访问数据库
  • JDO(Java data Object)技术
  • 第三方O/R工具:mybatis等

JDBC技术是所有访问数据库技术的基石,其他工具只是更好的封装了jdbc使得操作更为简便
在这里插入图片描述

1.1JDBC的体系结构

 JDBC的API分为两类:

  • 面向应用的API:程序员使用(连接数据库、执行sql语句、获得结果)
  • 面向数据库的API:开发商开发数据库驱动程序使用

JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。
不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。
                                 ————面向接口编程

JDBC是一系列接口,一套给数据库开发商让他们实现这些接口,每一个实现就是一个驱动,一套给程序开发人员,使用接口来连接数据库进行CURD操作

二、JDBC怎么用?

 作为程序开发人员用的是提供给开发人员的API来进行开发

2.1拷贝驱动程序到项目目录

  1. 将驱动程序拷贝到lib目录下(以idea为例)
    拷贝到项目目录
  2. 将jar包引入到项目路径下
    在这里插入图片描述在这里插入图片描述

2.2连接数据库

2.2.1实例化driver连接数据库
/**
     * @Author Warrior
     * @Date 2022/2/6 12:49
     * @Description 第一种连接方法实例化driver
     * @Param []
     * @Return void
     * @Since version-1.0
     */
    @Test
    public void getConnection1() throws SQLException {
        //实例化驱动
        Driver driver = new com.mysql.jdbc.Driver();
        //获取url
        String URL = "jdbc:mysql://localhost:3306/book";
        //实例化properties
        Properties properties = new Properties();
        //这里的porperties用的手写的方式在这里插入代码片
        properties.setProperty("user", "root");
        properties.setProperty("password", "LWZroot");
        java.sql.Connection connect = driver.connect(URL, properties);
        System.out.println(connect);
    }

第一种连接的方式实例化要连接的数据库的具体驱动,连接方式不便于解耦,一个是更换数据库要换成不同的驱动,一个是连接其他用户名的数据库也需要修改代码,所以不推荐使用这种连接方式

2.2.2通过反射获取driver连接数据库
    @Test
    public void getConnection2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
        //通过反射实例化驱动
        String className = "com.mysql.jdbc.Driver";
        Class clazz = Class.forName(className);
        //在这里更能凸显出面向接口编程的含义
        Driver driver = (Driver) clazz.newInstance();
        //获取url
        String URL = "jdbc:mysql://localhost:3306/book";
        //实例化properties
        Properties properties = new Properties();
        properties.setProperty("user", "root");
        properties.setProperty("password", "LWZroot");
        java.sql.Connection connect = driver.connect(URL, properties);
        System.out.println(connect);

    }

 连接方式2与第一种连接方式的区别在于driver的实例化的部分,第二种采用的是反射的方式进行实例化,不得不说在我仔细的研究过后发现是真的秒啊!之前没有注意过这两种方式有什么不同,后来经过仔细的研究过后确实有很大的差别。

2.2.2.1两种对象的构建方式的比较

 通过连接方式1和连接方式2我们不难发现,连接方式2的driver实例化是通过反射newInstance来完成的,简简单单的一个反射很好的体现了面向接口编程的思想,下面先对两种方式进行比较以下,进而突出反射的妙处。

  • new对象:通过new具体的类名,来创建一个对象,强类型。相对高效。能调用任何public构造。
  • 反射加载对象:
        //通过反射实例化驱动,这里加载的是mysql的驱动,想换可以直接在这里
        //换驱动
        String className = "com.mysql.jdbc.Driver";
        //获得所要获得的类
        Class clazz = Class.forName(className);
        //在这里更能凸显出面向接口编程的含义
        //因为任何数据库厂商的驱动都会继承driver这个接口,所以我们可以直接
        //实例化(mysql)的驱动,或者是任意想加载的驱动
        Driver driver = (Driver) clazz.newInstance();

 采用反射的方式实例化对象,就达到了解耦的目的,想要更改驱动直接在类名处进行修改即可,最终实现的是任何一个实现driver接口的类都可以被实例化出来。为了更好的理解这个面向接口的编程思想,下面在做一点解释:
 我们有一个person的接口是这个样子:(类比于driver这个接口)

public interface person {
    public void getName();
}

有两个类分别是father和son者两个类都实现了person这个接口(这两个类相当于是并列关系,类比我们的mysql驱动、sqlServer数据库驱动)
person类的两个实现
两个类是这样写的:

public class father implements person{
    static {
        System.out.println("this is father");
    }

    @Override
    public void getName(){
        System.out.println("my name is father");
    }
}
public class son implements person{
    static {
        System.out.println("this is son");
    }

    @Override
    public void getName(){
        System.out.println("my name is son");
    }
}

我们的测试是这样写的:当我们实例father这个对象的时候

    @Test
    public void test() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String className = "com.warrior.father";
        Class clazz = Class.forName(className);
        person father = (person) clazz.newInstance();

        father.getName();
    }

运行结果是这样的:
在这里插入图片描述
当我们测试实例化son这个类的时候,我们是这样写的:

@Test
    public void test() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    	//看到没,只修改了要用的类名
        String className = "com.warrior.son";
        Class clazz = Class.forName(className);
        person father = (person) clazz.newInstance();

        father.getName();
    }

运行结果是这样的:
在这里插入图片描述
 所以我觉得现在就应该可以理解为什么要用反射的方式来实例化driver可以体现面向接口的编程,因为driver这个接口不变,我们变得是那些实现这个接口的子类,可能又有人问了,再用new的方式实例化不也是改变类名来创建一个对象吗?
 确实是,好像都改了名字,但是不知道你有没有注意到,new的方式来创建对象的时候对应的类名是属于不可变部分,也就是代码的一部分。而反射类名是字符串,也就意味着这个类名我们可以从配置文件中读取进而实例化驱动,如果new的话只能通过修改代码来改变,这样的话代码的耦合就会变高,所以相对来讲更推荐用反射的方式来实例化一个driver

2.2.3通过driverManager连接

 结合前两种方式,将实例化的driver注册到driverManager统一管理,直接获取连接

@Test
    public void getConnection3() throws ClassNotFoundException, SQLException {
        //使用driverManager注册使用
        String className = "com.mysql.jdbc.Driver";
        String user = "root";
        String password = "LWZroot";
        String URL = "jdbc:mysql://localhost:3306/book";

        Class.forName(className);

        Connection connection =  DriverManager.getConnection(URL, user, password);
        System.out.println(connection);

    }
}

在这里没有注册这一步,但是通过看com.mysql.jdbc.Driver的源码可以看到:
在这里插入图片描述

2.2.4从配置文件中读取
@Test
    public void getConnection4() throws IOException, ClassNotFoundException, SQLException {
        InputStream stream = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties pro = new Properties();
        pro.load(stream);

        String driverClass = pro.getProperty("driverclass");
        
        Class.forName(driverClass);

        Connection connection = DriverManager.getConnection(pro.getProperty("url"), pro);
        System.out.println(connection);
    }

通过反射加载驱动自动在manager中注册,后面就可以用driverManager进行连接的获取

2.3怎么操作数据库?

在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

  • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
  • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
  • CallableStatement:用于执行 SQL 存储过程
    在这里插入图片描述
2.3.1使用statement操作数据库

在这里插入图片描述

  1. statement接口中执行sql语句的方法有以下两个
int excuteUpdate(String sql)//执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql)//执行查询操作SELECT
  1. 使用statement存在以下两个问题
    - 存在拼接字符串的问题
    - 存在sql注入的问题(要用prepatedStatement替代)
  2. 使用statement操作的示例
public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入用户名:");
        String username = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();

        String sql = "select username,password from t_user where username= '"+username+"' and password='"+password+"'";
        user user = get(sql, user.class);

        if (user != null){
            System.out.println("登陆成功");
            System.out.println(user);
        }else {
            System.out.println("登录失败");
        }
    }


    /**
     * @Author Warrior
     * @Date 2022/2/6 20:38
     * @Description 数据库查询操作的封装
     * @Param [sql, clazz]
     * @Return T
     * @Since version-1.0
     */
    public static <T> T get(String sql,Class<T> clazz){
        T T = null;
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            connection = JdbcUtil.getConnection();
            //获取statement
            statement = connection.createStatement();
            //获取结果集
            resultSet = statement.executeQuery(sql);

            //获取结果集的元数据
            ResultSetMetaData metaData = resultSet.getMetaData();

            //获取结果集的列数
            int columnCount = metaData.getColumnCount();

            //如果有数据则对结果进行封装
            /*
             *一行就是一个对象.next()操作相当于是行操作
             * 因为在这里通过用户名密码查询,所以就返回一个对象
             * 也就是对一个对象进行封装
             */
            if (resultSet.next()){
                //通过反射实例化一个要封装的对象
                T t = clazz.newInstance();
                //对列进行操作
                for (int i = 0; i<columnCount;i++){
                    //获取列的别名
                    String columnLabel = metaData.getColumnLabel(i + 1);
                    //根据列名称获得表中的数据
                    Object columnVal = resultSet.getObject(columnLabel);

                    //将数据表中得到的数据封装到对象
                      //通过反射获取这个对象对应名字的属性
                    Field field = clazz.getDeclaredField(columnLabel);
                    /*
                     * 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
                     * 值为 false 则指示反射的对象应该实施 Java 语言访问检查
                     * 由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                     */
                    field.setAccessible(true);
                    //将这个属性设置为新值
                    field.set(t,columnVal);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //关闭资源
            if (resultSet != null){
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

该例子通过模仿用户的登录操作来进行举例,实现的几个步骤如下:

  1. 获取数据库的连接
  2. 通过连接获取到statement
  3. 通过statement获取数据集resultSet(封装了数据库返回的所有信息)
  4. 通过resultSet获取到元数据(将我们要用的信息封装到了元数据ResultSetMetaData中)
  5. 通过元数据获取结果中包含的列数
  6. 元数据集中每一行就是一个对象通过.next()方法获取每一行的数据
  7. 在改行中通过反射实例化一个要封装的对象
  8. 遍历每一列,并通过列的名字来获得要封装的目标对象的对应属性名字
  9. 并将获得的数据设置为该对象中所对应属性的新值
  10. 返回这个对象
  11. 将连接、数据集、元数据关闭释放

以上就完成了建立连接->执行sql->对结果进行封装的操作
注意:在使用statement进行数据库操作时用到的sql语句为字符串拼接,实际开发过程中应该很少使用,prepatedStatement实现了statement这个接口,对功能进一步进行了完善

2.3.2使用PreparedStatement操作数据库

PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值,说人话就是这个对象对sql语句进行了预编译,sql语句不是写死的而是将参数提取出来

  1. preparedStatement方法的官方文档注释
/**
     * Creates a <code>PreparedStatement</code> object for sending
     * parameterized SQL statements to the database.
     * <P>
     * A SQL statement with or without IN parameters can be
     * pre-compiled and stored in a <code>PreparedStatement</code> object. This
     * object can then be used to efficiently execute this statement
     * multiple times.
     *
     * <P><B>Note:</B> This method is optimized for handling
     * parametric SQL statements that benefit from precompilation. If
     * the driver supports precompilation,
     * the method <code>prepareStatement</code> will send
     * the statement to the database for precompilation. Some drivers
     * may not support precompilation. In this case, the statement may
     * not be sent to the database until the <code>PreparedStatement</code>
     * object is executed.  This has no direct effect on users; however, it does
     * affect which methods throw certain <code>SQLException</code> objects.
     * <P>
     * Result sets created using the returned <code>PreparedStatement</code>
     * object will by default be type <code>TYPE_FORWARD_ONLY</code>
     * and have a concurrency level of <code>CONCUR_READ_ONLY</code>.
     * The holdability of the created result sets can be determined by
     * calling {@link #getHoldability}.
     *
     * @param sql an SQL statement that may contain one or more '?' IN
     * parameter placeholders
     * @return a new default <code>PreparedStatement</code> object containing the
     * pre-compiled SQL statement
     * @exception SQLException if a database access error occurs
     * or this method is called on a closed connection
     */
    PreparedStatement prepareStatement(String sql)
        throws SQLException;

翻译一下注释内容:

创建一个PreparedStatement对象,用于将参数化的 SQL 语句发送到数据库。
带或不带 IN 参数的 SQL 语句可以预编译并存储在PreparedStatement对象中。然后可以使用该对象多次有效地执行该语句。
注意:此方法针对处理受益于预编译的参数化 SQL 语句进行了优化。如果驱动程序支持预编译, prepareStatement方法会将语句发送到数据库进行预编译。某些驱动程序可能不支持预编译。在这种情况下,在执行PreparedStatement对象之前,该语句可能不会发送到数据库。这对用户没有直接影响;但是,它确实会影响哪些方法抛出某些SQLException对象。
使用返回的PreparedStatement对象创建的结果集默认为TYPE_FORWARD_ONLY ,并发级别为CONCUR_READ_ONLY 。可以通过调用getHoldability来确定创建的结果集的可保存性。

更新示例:

 public static void update(String sql, Object... args) throws SQLException {
        Connection connection = null;
        try {
            connection = JdbcUtil.getConnection();
            //获取PreparedStatement对象实现sql预编译
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                //把对象里的?替换成参数,参数的索引从1开始
                preparedStatement.setObject(i+1, args[i]);
            }
            //执行ps对象得到结果集
            preparedStatement.execute();
            //关闭资源
            preparedStatement.close();
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }finally {
            connection.close();

        }
    }

查询操作示例代码

 public static <T> T getInstance(Class<T> t, String sql, Object... args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            connection = JdbcUtil.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i+1, args[i]);
            }
            //执行返回结果的sql语句
            resultSet = preparedStatement.executeQuery();
            //获取数据集的元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            //获取结果中列数
            int columnCount = metaData.getColumnCount();

            if (resultSet.next()){
                //获取目标对象
                T instance = t.newInstance();
                //循环遍历
                for (int i = 0;i< columnCount;i++){
                    //获取列的别名
                    String columnLabel = metaData.getColumnLabel(i+1);
                    //获取该列的数据
                    Object result = resultSet.getObject(columnLabel);
                    //反射获取对象域
                    Field fieldLabel = t.getDeclaredField(columnLabel);
                    fieldLabel.setAccessible(true);
                    fieldLabel.set(instance, result);
                }
                return instance;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

与statement相比就是多了预编译sql得到preparedStatement对象,将该对象中sql语句中的?进行替换,获取数据集和数据集的元数据,并反射造对象对对应域的值进行set最后返回对象。

2.3批量处理数据

2.3.1批量执行sql语句

 批量执行sql语句的几种方式方法

  • addBatch(String ):添加需要批处理的sql语句或是参数
  • executeBatch():执行批处理语句
  • clearBatch():清空缓存的数据

 通常我们会遇到两种批量执行SQL语句的情况

  • 多条sql语句的批量处理
  • 一个sql语句的批量传参

有关“事务”的一二三: 事务的含义就是在实现某个功能时,需要批量执行一些语句,最经典的例子就是银行转账问题,减去的钱和加上的钱必须同时成果才能说明一个事件成功完成。任何一件事没有成功执行都要进行另外的处理,例如回滚操作。当向数据库进行批量提交事件时,需要将事务的自动提交改完false,因为在默认的设置中,执行一次提交一次事务,所以为了避免“成功”执行一半发生,所以要在所有的事件成功执行以后再提交事务。

2.4数据库事务管理

2.4.1JDBC事务管理
  • 数据一旦提交,就不可回滚
  • jdbc让多个sql语句作为一个事务
    - 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
    - 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
    - 在出现异常时,调用 rollback(); 方法回滚事务
2.4.2数据库的并发问题
  • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
  • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
  • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
2.4.3四种隔离级别

在这里插入图片描述

  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED

  • Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

2.5DAO实现类

  • DAO:data Access Object:访问数据信息的类和接口,包括了CURD操作,但是没有任何业务相关的信息
  • 有利于代码的维护和升级

2.6数据库连接池

2.6.1连接池的必要性

 传统的数据库连接方式使用driverManager连接数据库,数据库连接是一种宝贵的资源,连接和释放都很耗系统资源和时间,所有解决思路就是创建一定数量的连接放到数据库的连接池当中,当有需要获取连接的请求的时候,就从连接池中获取,用后放回到连接池当中从而减少资源的浪费。
数据库连接池
 数据库连接池的优点

  1. 资源重用
  2. 更快的系统反应速度
  3. 新的资源分配手段
  4. 统一的连接管理,避免数据库连接泄露
2.6.2数据库连接池有哪些
  • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
  • C3P0 是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用
  • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  • BoneCP 是一个开源组织提供的数据库连接池,速度快
  • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 特别注意:
    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
2.6.3druid(德鲁伊连接池)

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

  1. 连接示例:
 @Test
    public void getConnectionByDruid() throws Exception {
        InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("druid.properties");
        Properties pro = new Properties();
        pro.load(inputStream);

        DataSource dataSource = DruidDataSourceFactory.createDataSource(pro);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);


    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值