mybatis原理分析(一)---JDBC

1.概述

使用过mybatis的都清楚底层封装了jdbc的操作,将繁琐的jdbc的操作给屏蔽了。所以分析mybatis的原理之前,先来看看jdbc是如何工作的,statement有哪些特点。

2.jdbc相关概念

JDBC是由SUN公司提出的一些列规范,只定义了接口规范,具体实现由各个数据库厂商去实现,它是一种典型的桥接模式。

jdbc是一种规范,所谓规范,就是自己定义了标准接口,做了如下抽象:用Connection代表和数据库的连接,用Statement执行SQL,用ResultSet表示SQL返回的结果。

上面说的Connection、Statement、ResultSet都应该是接口,具体实现由各个数据库提供商提供。有了规范,可以通过统一的接口,访问多种类型的数据库,可随便切换数据库。

3.jbdc的使用

如下的测试代码就是对jdbc的使用。

public class JdbcTest {
  public static final String URL = "jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=UTF-8";
  public static final String USERNAME = "root";
  public static final String PASSWORD = "341341";
  public Connection connection;

  @Before
  public void init() throws SQLException {
    connection = DriverManager.getConnection(URL, USERNAME, PASSWORD); // 1.获取连接
  }

  @After
  public void over() throws SQLException {
    connection.close();
  }

  @Test
  public void jdbcTest() throws SQLException {
    String sql = "select * from users where `name` = ?";
    PreparedStatement statement = connection.prepareStatement(sql);//2. 预编译sql
    statement.setString(1, "森林");//3. 给占位符?设置参数
    statement.execute();//4. 执行sql
    ResultSet resultSet = statement.getResultSet();
    while (resultSet.next()) {
      System.out.println(resultSet.getString(1));
    }
    System.out.println("===============================");
    statement.setString(1, "依依");//重复使用这个statement 设置参数即可执行
    statement.execute();
    resultSet = statement.getResultSet();
    while (resultSet.next()) {
      System.out.println(resultSet.getString(1));
    }
    // 释放资源
    resultSet.close();
    statement.close();

  }
}
3.1 获取连接

接口的由各个厂商来提供,实现类的类名无法得到统一,去创建Connection对象的时候,代码就会写死某个实现类。例如

Connection con=MySqlConnectionImpl("127.0.0.1",3306,"mybatis",userName,pwd);

为了解决这个问题,抽象出了驱动的概念Driver。Driver是通过反射的机制来动态的创建连接。而不同的Driver又交给DriverManager来管理。这样可以在URL中加上前缀,来识别使用哪个数据库的驱动来创建连接。例如

public static final String URL = "jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=UTF-8";

这里的前缀中加了mysql。所以当执行

connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

DriverManager会根据前缀获得mysql的Driver驱动来创建数据库连接。

3.2 预编译sql

有了数据库连接后,通过如下的代码可以预编译sql得到一个sql声明。

PreparedStatement statement = connection.prepareStatement(sql);

可以一次编译多次执行。sql语句一样的时候,只需要设置参数就可以执行。

3.3 设置参数

第一个参数是索引,表示第几个’?'占位符,从1开始 并不是从0开始。

有多个占位符,则需要执行多次这条语句。

statement.setString(1, "森林");
3.4 执行

执行sql,返回结果是个boolean类型。执行的结果集放在了statement当中

statement.execute();

也可以使用executeQuery(),或者executeUpdate()。

两者的区别在于executeQuery只能执行查询操作。

executeUpdate执行增删改操作。

3.5 获取结果集

执行完execute操作后,会将执行结果保存在statement中。通过getResultSet可以获得执行的结果。

ResultSet resultSet = statement.getResultSet();

4. statement特点

4.1 普通statment

最基本的功能是执行静态的sql语句。

传输相关的功能,可以执行批处理和设置数据库返回的行数。

  • 批处理

    这里使用PreparedStatement无关紧要,因为这个接口继承了Statment接口。

    以下代码是执行了100条插入,通过addBatch方法添加批处理参数。

    设置完100条的参数后,executeBatch批处理执行这100条sql语句。记录时间,为了对比批处理的效率,设置了对照试验,不使用批处理,而是一条一条的执行,记录时间。

    @Test
      public void prepareBatchTest() throws SQLException {
        String sql = "INSERT INTO `users` (`name`,age) VALUES (?,18);";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        long l = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
          preparedStatement.setString(1, UUID.randomUUID().toString());
          preparedStatement.addBatch(); // 添加批处理参数
        }
        preparedStatement.executeBatch(); // 批处理  一次发射
        System.out.print("批处理:");
        System.out.println(System.currentTimeMillis() - l);
        preparedStatement.close();
      }
    
     @Test
      public void Test() throws SQLException {
        String sql = "INSERT INTO `users` (`name`,age) VALUES (?,18);";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        long l = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
          preparedStatement.setString(1, UUID.randomUUID().toString());
                preparedStatement.execute(); //单条执行
        }
        System.out.print("一条条执行:");
        System.out.println(System.currentTimeMillis() - l);
        preparedStatement.close();
      }
    

    结果可以看出批处理的效率提高了一倍。
    在这里插入图片描述

  • 设置数据库返回行数setFetchSize

    setFetchSize最主要是为了减少网络交互次数设计的。访问ResultSet时,如果它每次只从服务器上取一行数据,则会产生大量的开销。setFetchSize的意思是当调用rs.next时,ResultSet会一次性从服务器上取得多少行数据回来,这样在下次rs.next时,它可以直接从内存中获取出数据而不需要网络交互,提高了效率。 这个设置可能会被某些JDBC驱动忽略的,而且设置过大也会造成内存的上升。

4.2 PreparedStatement

预处理sql语句,可以一次编译多次执行。只需要设置参数就可以执行。第一个jdbc使用的例子中已经体现了这一点。

还有一个更重要的优点,就是防止sql注入

  • sql注入

    什么是sql注入呢,例如

    String sql = "select * from users where `name` ='" + name + "'";
    

    需要传入一个name 来补全sql。但是有人恶意在name中拼接了一个字符串 or 1=1;这样就会导致信息的泄漏。
    在这里插入图片描述

  • 实验

    做对比实验,分别使用Statment和PreparedStatment去执行相同的sql语句,看看有什么差别。代码如下

    public int selectByName(String name) throws SQLException {
      String sql = "select * from users where `name` ='" + name + "'";
      System.out.println(sql);
      Statement statement = connection.prepareStatement(sql);
      statement.executeQuery(sql);
      ResultSet resultSet = statement.getResultSet();
      int count = 0;
      while (resultSet.next()) {
        count++;
      }
      statement.close();
      return count;
    }
    
    public int selectByName2(String name) throws SQLException {
      String sql = "SELECT * FROM users WHERE `name`=?";
      PreparedStatement statement = connection.prepareStatement(sql);
      statement.setString(1, name);
      System.out.println(statement);
      statement.executeQuery();
      ResultSet resultSet = statement.getResultSet();
      int count = 0;
      while (resultSet.next()) {
        count++;
      }
      statement.close();
      return count;
    }
    
    @Test//防止sql注入 测试
    public void injectTest() throws SQLException {
      System.out.println(selectByName("森林"));
      System.out.println(selectByName("森林' or '1'='1"));
      System.out.println(selectByName2("森林' or '1'='1"));
    }
    

    两个几乎相同的方法,只是使用的Statement不同。selectByName使用的是Statment,selectByName2使用的是PreparedStatment。得到的结果如下。
    在这里插入图片描述

    正常操作查询name为森林得到结果有3个。但是通过sql注入之后,将数据库中所有的数据都查了出来,这就造成了安全隐患。而最后一行的结果可以看出PreparedStatment可以防止sql注入的发生。

    那么PreparedStatment是如何做到防止sql注入的呢?

    答:在设置参数的时候,setString方法中会给引号加上转译字符,转移操作在数据库端执行,这样就起到了防止sql注入的功能。

4.3 CallableStatement

这是一个和存储过程相关的statement

  • 存储过程

    存储过程 (Stored Procedure) 是在大型数据库系统中 , 一组为了完成特定功能的 SQL 语句集 , 存储在数据库中 , 经过第一次编译后再次调用不需要再次编译 , 用户通过指定存储过程的名字并给出参数 (如果该存储过程带有参数) 来执行它 , 存储过程是数据库中的一个重要对象 ; 存储过程中可以包含 逻辑控制语句数据操纵语句 , 它可以接受参数 , 输出参数 , 返回单个或多个结果集以及返回值 ;

  • mysql中的存储过程

    在这里插入图片描述

    第一行存储过程的名字后面跟着入参和出参。

    sql语句中结果传入给出参 使用INTO

  • jdbc使用存储过程

    可以设置入参、出参、读取出参。

    @Test // 存储过程
    public void processTest() throws SQLException {
      CallableStatement statement = connection.prepareCall("call select_all(?,?)");
      // 设置入参
      statement.setString(1, "森林");
      // 设置出参
      statement.registerOutParameter(2, Types.INTEGER);
      statement.execute();
      ResultSet resultSet = statement.getResultSet();
      int count = 0;
      while (resultSet.next()) {
        count++;
      }
      System.out.println(count);
      // 读取出参
      int totalCount = statement.getInt(2);
      System.out.println("total:" + totalCount);
    
      statement.close();
    }
    

    执行结果如下
    在这里插入图片描述

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值