java之JDBC

不足之处请多多指教,感谢大家

JDBC 定义

JDBC是Java Database Connectivity的简称,它定义了一套访问数据库的规范和接口。但它自身不参与数据库访问的实现。因此对于目前存在的数据库(譬如Mysql、Oracle)来说,要么数据库制造商本身提供这些规范与接口的实现,要么社区提供这些实现。

Java 程序只依赖于 JDBC API,通过 DriverManager 来获取驱动,并且针对不同的数据库可以使用不同的驱动

图示:
在这里插入图片描述

JDBC核心五大对象

粗略的画个图
在这里插入图片描述
之前不太明白Class.forName(“com.mysql.jdbc.Driver”)怎么注册驱动。我们可以先看一下Driver类的源码

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

通过源码可以看出,把Driver类加载到内存里面是,静态代码块就立马执行注册驱动方法了。

JDBC封装类:

JDBC连接数据库的操作还是比较繁琐的,将这些操作封装起来,更利于我们的开发
思路:
1.将数据库驱动,连接数据库的url,数据库的账号,密码这些可能会做出修改的参数,从代码中抽离出来,写到配置文件中。有利于后期的维护
2.注册驱动只需要注册一次就可以了,配置文件的读取也只需要读取一次。所以这些操作放到静态代码块中更合理
3.将获取连接对象,获取PreparedStatement对象关闭资源这些常用的操作,分别封装起来,以后就只需要调用对应的方法就可以了
4.executeUpdate,executeQuery这两个方法执行的返回结果肯定不是我们想要的,我们需要把这些结果作为属性传入我们准备的实体类中。所以需要把它封装,经过处理返回我们需要的返回值

代码演示:

package com.feisi.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * jdbc的工具类
 */
public class JDBCUtil {
    static Properties props = new Properties();
    static{
        InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("dbconfig.properties");
        try {
            props.load(in);
            Class.forName(props.getProperty("driverClassName"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    //得到连接的方法
    public static Connection getConnection() throws SQLException {
        return  DriverManager.getConnection(props.getProperty("url"),props.getProperty("username"),props.getProperty("password"));
    }

    //关闭资源的方法
    public static void close(ResultSet rs, PreparedStatement pstmt,Connection conn ){
        try {
            if(rs != null) rs.close();
            if(pstmt != null) pstmt.close();
            if(conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 执行增删改的sql
     */
    public static int executeUpdate(String sql,Object...params){
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = JDBCUtil.getConnection();
            pstmt = createPreparedStatement(conn,sql,params);
            return pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.close(null,pstmt,conn);
        }
        return 0;
    }

    private static  PreparedStatement  createPreparedStatement(Connection conn,String sql,Object...params) throws SQLException {
        conn = JDBCUtil.getConnection();
        PreparedStatement pstmt = conn.prepareStatement(sql);
        //给?赋值 params本质数组
        if( params != null  && params.length > 0 ){
            for (int i = 0; i < params.length; i++) {
                pstmt.setObject(i+1,params[i]);
            }
        }
        return pstmt;
    }
    /**
     * 执行查询的sql
     *  可变参数约束:
     *    1. 位于所有参数后面
     *    2. 一个方法只能有一个可变参数
     *  所有可变参数都可以使用数组替换
     */
    /**
     * 优化版本
     */
    public static <T> List<T> executeQuery(String sql,Class<T> clazz, Object... params){
        //使用jdbc 查询tb_user表
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtil.getConnection();
            //创建PreparedStatement
            pstmt = createPreparedStatement(conn,sql,params);

            //执行
            rs = pstmt.executeQuery();
            System.out.println(rs.toString());
            //解决ResultSet  反射
            List<T> list = new ArrayList<>();
            while(rs.next()){
                //rs.getXxx()
                //newInstance() 调用无参构造方法
                T t = clazz.newInstance();
                //给t的属性赋值
                //获取T的属性
                Field[] fields = clazz.getDeclaredFields();

                //获取数据库中的元数据
                ResultSetMetaData metaData = rs.getMetaData();
                //获取字段查到的字段个数
                int columnCount = metaData.getColumnCount();
                //遍历结果集中所有的元数据
                for (int i = 1; i <= columnCount; i++) {
                    //获取字段名
                    String columnLabel = metaData.getColumnLabel(i);
                    //遍历属性集合对象
                    for (Field field : fields) {
                        //将属性对象设置为暴力访问
                        field.setAccessible(true);
                        //获取对象的属性名
                        String name = field.getName();
                        //查找属性名字和字段名相同的属性,忽略大小写
                        if (name.equalsIgnoreCase(columnLabel)){
                            //获取数据库中字段对应的数据
                            Object object = rs.getObject(columnLabel);
                            //赋值给对象的属性
                            field.set(t,object);
                        }

                    }
                }
                //将对象添加到集合中
                list.add(t);
            }
            return list;
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtil.close(rs,pstmt,conn);
        }
        return null;
    }
}

执行SQL语句的过程
(图片来源于网络)
在这里插入图片描述

JDBC深入研究

1.关于PreparedStatement和Statement的区别
Statement执行过程:发客户端送sql语句到数据库服务器,数据库服务器会先分析语法,在编译SQL语句,最后再执行SQL语句。
在这里插入图片描述
通过上面的图片,可以看出。当执行的语句大体相同,只是后面参数不同时。重复的对SQL语句进行编译和语法分析会浪费很多时间。
并且当sql语句是:SELECT * FROM tab_user WHERE username=‘a’ or ‘1’=‘1’ and password=‘a’ or ‘1’=‘1’。这种就会存在很大的问题–SQL攻击。
为了解决上面的问题,Java提供了Statement的子类PreparedStatement类

PreparedStatement:可以进行预编译
1.MySQL执行预编译分为三步

  • 1.执行预编译语句
  • 2.设置变量
  • 3.执行语句
    通过预编译,可以提供一个sql语句模板,这样就不用每次都对语句进行分析,编译了,可以节省很多时间

2.我们一般用的PreparedStatement并没有用到预编译功能的(原因后面分析),只是用到了防止sql注入攻击的功能。
PreparedStatement里面会对敏感词进行转义,所有就不会出现SQL攻击的问题。这个操作是在PreparedStatement类中实现的,和服务器无关。

注意:
1.JDBC MySQL驱动5.0.5以后的版本默认PreparedStatement是关闭预编译功能的,,需要我们手动开启。而之前的JDBC MySQL驱动版本默认是开启预编译功能的。并且MySQL服务器4.1版本之前是不支持预编译的。
2.开启预编译的方式:设置MySQL连接URL参数:useServerPrepStmts=true

PreparedStatement 的预编译
首先我们知道它默认是关闭预编译的,如果不开启的话,将不会进行预编译。
代码:

public class ceshi {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    	//加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //&useServerPrepStmts=true&cachePrepStmts=true 开启预编译和缓存
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/ceshi?useUnicode=true&characterEncoding=utf8&useSSL=false","root","root");
         String sql = "select * from subjecttype where subjectID = ?";
         //预编译
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //给参数赋值
        preparedStatement.setObject(1,1);
        //发送SQL语句,获取结果集
        ResultSet resultSet1 = preparedStatement.executeQuery();
        preparedStatement.setObject(1,2);
        //再发送一条SQL语句
        ResultSet resultSet2 = preparedStatement.executeQuery();
        if (resultSet1!=null) resultSet1.close();
        if (resultSet2!=null) resultSet2.close();
        if (preparedStatement!=null) preparedStatement.close();
        if (connection!=null) connection.close();
    }
}

查看日志文件可知:平时使用PreparedStatement 只是进行了防治SQL攻击,并没有进行预编译
在这里插入图片描述
PreparedStatement 预编译后的函数不会缓存
这也就意味这,当使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象。
如果希望缓存编译后函数的key,那么就要设置URL参数cachePrepStmts=true:
代码:

public class ceshi {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        //&useServerPrepStmts=true&cachePrepStmts=true 开启预编译和缓存
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/ceshi?useUnicode=true&characterEncoding=utf8&useSSL=false&useServerPrepStmts=true&cachePrepStmts=true","root","root");
        //对象一
        String sql = "select * from subjecttype where subjectID = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,1);
        ResultSet resultSet1 = preparedStatement.executeQuery();
        if (resultSet1!=null) resultSet1.close();
        if (preparedStatement!=null) preparedStatement.close();
        //对象二
        PreparedStatement preparedStatement2 = connection.prepareStatement(sql);
        preparedStatement2.setObject(1,2);
        ResultSet resultSet2 = preparedStatement2.executeQuery();
        if (resultSet2!=null) resultSet2.close();
        if (preparedStatement!=null) preparedStatement.close();

        if (connection!=null) connection.close();
    }
}

查看日志文件:

在这里插入图片描述

注意:
1.每次使用PreparedStatement对象后都要关闭该PreparedStatement对象流,否则预编译后的函数key是不会缓存的。
2.不同connection之间,预编译的结果是独立的,是无法共享的,一个connection无法得到另外一个connection的预编译缓存结果

2.PreparedStatementd的性能真的比Statement效率高很多吗?
直接上代码测试
完成向数据库中插入一万条记录的功能(INSERT into subjecttype VALUES(1,1))

public class ceshi {
    public static void main(String[] args) throws Exception{
//        PreparedStatementTest2();
        StatementTest();
    }
    public static void StatementTest() throws Exception{
        Class.forName("com.mysql.jdbc.Driver");
        //&useServerPrepStmts=true&cachePrepStmts=true 开启预编译和缓存     //数据库批处理&rewriteBatchedStatements=true
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/ceshi?useUnicode=true&characterEncoding=utf8&useSSL=false&useServerPrepStmts=true&rewriteBatchedStatements=true","root","root");
        String sql = "INSERT into subjecttype VALUES(1,";
        Statement preparedStatement = connection.createStatement();
        long l = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
           String sql2 = sql+i+")";
           preparedStatement.addBatch(sql2);
        }
        preparedStatement.executeBatch();
        long l1 = System.currentTimeMillis();
        long l2 = l1 - l;
        System.out.println("预编译需要花费的时间"+l2);
        if (preparedStatement!=null) preparedStatement.close();

        if (connection!=null) connection.close();
    }



    public static void PreparedStatementTest2() throws Exception{
        Class.forName("com.mysql.jdbc.Driver");
        //&useServerPrepStmts=true&cachePrepStmts=true 开启预编译和缓存     //数据库批处理&rewriteBatchedStatements=true
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/ceshi?useUnicode=true&characterEncoding=utf8&useSSL=false&useServerPrepStmts=true&rewriteBatchedStatements=true&cachePrepStmts=true","root","root");
        String sql = "INSERT into subjecttype VALUES(1,?)";
        //获取PreparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //记录插入数据前的时间
        long l = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            //给SQL设置不同的参数
            preparedStatement.setObject(1,i);
            //批处理
            preparedStatement.addBatch();
        }
        //提交批处理操作
        preparedStatement.executeBatch();
        //记录执行后的时间
        long l1 = System.currentTimeMillis();
        //相减得出花费的时间
        long l2 = l1 - l;
        System.out.println("预编译需要花费的时间"+l2);
        //关闭资源
        if (preparedStatement!=null) preparedStatement.close();
        if (connection!=null) connection.close();
    }

}

当不开启预编译功能时,做5次测试,单位毫秒

第一次:39957	第二次:40020	第三次:39251	第四次:36505	第五次:39411

这个结果是开启了预编译(需要使用批处理方法来提交sql语句,并且需要在URL后面设置参数&rewriteBatchedStatements=true,因为数据库默认不开启批处理操作)
平均值:39313

第一次:259	第二次:266	第三次:242	第四次:268	第五次:260

经过测试:PreparedStatementd的性能真的比Statement效率高很多。

1.Statement的批处理:
优点:可以向数据库发送多条不同的SQL语句。
缺点:SQL语句没有预编译。
2.PreparedStatement的批处理:
优点:PreparedStatment批量操作时与数据库的通信次数远少于Statment。
缺点:只能应用在SQL语句相同,但参数不同的批处理中

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值