• Mybatis JDBC->Mybatis


      1 什么是JDBC

      Java程序都是通过JDBC(Java Data Base Connectivity)连接数据库的,通过SQL对数据库编程。JDBC是由SUN公司(SUN公司已被Oracle公司收购)提出的一系列规范,只定义了接口规范,具体的实现是由各个数据库厂商去完成的。因为每个数据库都有其特殊性,这些是Java规范没有办法确定的,所以JDBC就是一种典型的桥接模式。

      

     

      2 常用接口

      2.1 Driver接口

      要连接数据库,必须先加载特定厂商的数据库驱动程序,不同的数据库有不同的加载方法。共有2种方式。

     

      2.1.1 Class.forName("com.mysql.jdbc.Driver");

      推荐这种方式,不会对具体的驱动类产生依赖。

      例如:

    1 // 加载Oracle驱动
    2 Class.forName("oracle.jdbc.driver.OracleDriver");

     

      2.1.2 DriverManager.registerDriver("com.mysql.jdbc.Driver");

      会造成DriverManager中产生2个一样的驱动,并会对具体的驱动类产生依赖。

     

      2.2 Connection接口

      与特定数据库连接后,Connection执行SQL语句并返回结果。DriverManager.getConnection(url, user, password)方法建立在JDBC URL中定义的数据库Connection连接上。例如:

    1 // 连接MySQL数据库
    2 Connection conn = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");
    3 
    4 // 连接Oracle数据库
    5 Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@host:port:database", "user", "password");

      

      常用方法:

      createStatement():创建向数据库发送sql的statement对象。

      prepareStatement(sql) :创建向数据库发送预编译sql的PreparedSatement对象。

      prepareCall(sql):创建执行存储过程的callableStatement对象。

      setAutoCommit(boolean autoCommit):设置事务是否自动提交。

      commit() :在此连接上提交事务。

      rollback() :在此连接上回滚事务。

      

      2.3 Statement接口

      用于执行静态SQL语句并返回它所生成结果的对象。

     

      3个Statement类:

      Statement:由createStatement方法创建,用于发送简单的SQL语句(不带参数)。

      PreparedStatement :继承Statement类,由prepareStatement方法创建,用于发送含有1个或多个参数的SQL语句。与父类Statement相比,有以下几个优点:

      —可以防止SQL注入。

      DBUtils辅助类

     1 package com.test.jdbc;
     2 
     3 import java.sql.Connection;
     4 import java.sql.DriverManager;
     5 import java.sql.ResultSet;
     6 import java.sql.SQLException;
     7 import java.sql.Statement;
     8 /**
     9  * @author Administrator
    10  * 模板类DBUtils
    11  */
    12 public final class DBUtils {
    13     // 参数定义
    14     private static String url = "jdbc:mysql://localhost:3306/mytest"; // 数据库地址
    15     private static String username = "root"; // 数据库用户名
    16     private static String password = "root"; // 数据库密码
    17 
    18     private DBUtils() {
    19 
    20     }
    21     // 加载驱动
    22     static {
    23         try {
    24             Class.forName("com.mysql.jdbc.Driver");
    25         } catch (ClassNotFoundException e) {
    26             System.out.println("驱动加载出错!");
    27         }
    28     }
    29 
    30     // 获得连接
    31     public static Connection getConnection() throws SQLException {
    32         return DriverManager.getConnection(url, username, password);
    33     }
    34 
    35     // 释放连接
    36     public static void free(ResultSet rs, Statement st, Connection conn) {
    37         try {
    38             if (rs != null) {
    39                 rs.close(); // 关闭结果集
    40             }
    41         } catch (SQLException e) {
    42             e.printStackTrace();
    43         } finally {
    44             try {
    45                 if (st != null) {
    46                     st.close(); // 关闭Statement
    47                 }
    48             } catch (SQLException e) {
    49                 e.printStackTrace();
    50             } finally {
    51                 try {
    52                     if (conn != null) {
    53                         conn.close(); // 关闭连接
    54                     }
    55                 } catch (SQLException e) {
    56                     e.printStackTrace();
    57                 }
    58 
    59             }
    60 
    61         }
    62 
    63     }
    64 
    65 }

     

      在写SQL注入演示类之前看下数据库结构(简单的users表):

      

      SQL注入演示类

     1 package com.test.jdbc;
     2 
     3 import java.sql.Connection;
     4 import java.sql.ResultSet;
     5 import java.sql.SQLException;
     6 import java.sql.Statement;
     7 
     8 public class SqlInner {
     9     public static void main(String[] args) {
    10         //Read("zhangsan"); // 如果普通查询可以
    11         Read("' or 1 or'");
    12     }
    13     public static void Read(String name) {
    14         Statement st = null;
    15         ResultSet rs = null;
    16         Connection conn = null;
    17         try {
    18             conn = DBUtils.getConnection();
    19             st = conn.createStatement();
    20             String sql = "select * from users where lastname = '" + name + "'"; // 主要注入发生地
    21             System.out.println("sql: " + sql); // 打印SQL语句
    22             rs = st.executeQuery(sql); 
    23             System.out.println("age	lastname	firstname	id");
    24             while (rs.next()) {
    25                 System.out.println(rs.getInt(1) + "	" + rs.getString(2)
    26                         + "		" + rs.getString(3) + "		" + rs.getString(4));
    27             }
    28         } catch (SQLException e) {
    29             e.printStackTrace();
    30         } finally {
    31             DBUtils.free(rs, st, conn);
    32         }
    33     }
    34 }

      如果用普通的键zhangsan来查询,结果如下:

      

      使用下面的' or 1 or',结果如下:

      

      把所有结果都打印出来了。打印生成后的SQL语句如下:

      

      这是一种SQL注入,用了2个单引号,分别把前后的两个''给封闭了,变成两个空,然后通过or 1即or true符合所有条件。

      使用PreparedStatement

     1 package com.test.jdbc;
     2 
     3 import java.sql.Connection;
     4 import java.sql.PreparedStatement;
     5 import java.sql.ResultSet;
     6 import java.sql.SQLException;
     7 
     8 public class SqlInner {
     9     public static void main(String[] args) {
    10         Read("' or 1 or'");
    11     }
    12     public static void Read(String name) {
    13         PreparedStatement st = null;
    14         ResultSet rs = null;
    15         Connection conn = null;
    16         try {
    17             conn = DBUtils.getConnection();
    18             String sql = "select * from users where lastname = ?"; // 这里用问号        
    19             st = conn.prepareStatement(sql);
    20             st.setString(1,name); // 这里将问号赋值
    21             rs = st.executeQuery(); 
    22             System.out.println("age	lastname	firstname	id");
    23             while (rs.next()) {
    24                 System.out.println(rs.getInt(1) + "	" + rs.getString(2)
    25                         + "		" + rs.getString(3) + "		" + rs.getString(4));
    26             }
    27         } catch (SQLException e) {
    28             e.printStackTrace();
    29         } finally {
    30             DBUtils.free(rs, st, conn);
    31         }
    32     }
    33 }

      PreparedStatement用?代替变量,然后通过setString赋值。由于PreparedStatement内置了字符过滤,就相当于where name = ' or 1 or ',没有对应记录,所以结果为空。这体现了PreparedStatement的防注入功能。

     

      —在特定的驱动数据库下相对效率要高(不绝对)。

      —因为已经预加载了,所以不需要频繁编译。

      CallableStatement:继承PreparedStatement类,由prepareCall方法创建,用于调用存储过程。

     

      Statement常用方法:

      execute(String sql):返回值为true时,执行的是查询语句,可以通过getResultSet方法获取结果;返回值为false时,执行的是更新语句或DDL语句。

      executeQuery(String sql):执行select语句,返回ResultSet结果集。

      executeUpdate(String sql):执行insert/update/delete语句,返回影响的行数。

      addBatch(String sql) :把多条SQL语句放到一个批处理中。

      executeBatch():向数据库发送一批SQL语句并执行,返回每条SQL语句执行后的影响行数的int数组。

     

      2.4 ResultSet接口

      结果集

     

      检索不同类型字段的常用方法:

      getString(int index)、getString(String columnName):获得在数据库里是varchar、char等类型的数据对象。

      getFloat(int index)、getFloat(String columnName):获得在数据库里是Float类型的数据对象。

      getDate(int index)、getDate(String columnName):获得在数据库里是Date类型的数据对象。

      getBoolean(int index)、getBoolean(String columnName):获得在数据库里是Boolean类型的数据对象。

      getObject(int index)、getObject(String columnName):获取在数据库里任意类型的数据对象。

      

      对结果集进行滚动的方法:

      next():移动到下一行。

      Previous():移动到前一行。

      absolute(int row):移动到指定行。

      beforeFirst():移动resultSet的最前面。

      afterLast() :移动到resultSet的最后面。

     

      3 使用JDBC的步骤

      加载JDBC驱动 → 获取数据库连接Connection对象 → 创建执行SQL语句的PreparedStatement对象 → 处理执行结果ResultSet对象 → 释放资源。

      注意,释放资源顺序:ResultSet → PreparedStatement → Connection。

     

      3.1 加载JDBC驱动(只加载1次)

      Class.forName("com.MySQL.jdbc.Driver");

     

      3.2 获取数据库连接Connection对象

    1 Connection conn = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");

      URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:

      

      其他参数如:useUnicode=true&characterEncoding=utf8

      

      3.3 创建执行SQL语句的PreparedStatement对象

    1 //Statement  
    2 String id = "5";
    3 String sql = "delete from table where id=" +  id;
    4 Statement st = conn.createStatement();  
    5 st.executeQuery(sql);  
    6 //存在sql注入的危险
    7 //如果用户传入的id为“5 or 1=1”,那么将删除表中的所有记录

     

    1 //PreparedStatement 有效的防止sql注入(SQL语句在程序运行前已经进行了预编译,当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 or '1=1'也数据库会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令)
    2 String sql = “insert into user (name,pwd) values(?,?)”;  
    3 PreparedStatement ps = conn.preparedStatement(sql);  
    4 ps.setString(1, “col_value”);  //占位符顺序从1开始
    5 ps.setString(2, “123456”); //也可以使用setObject
    6 ps.executeQuery();

      

      3.4 处理执行结果ResultSet对象

    1 ResultSet rs = ps.executeQuery();  
    2     While(rs.next()){  
    3         rs.getString(“col_name”);  
    4         rs.getInt(1);  
    5         //
    6 }  

      

      3.5 释放资源

     1 // 数据库连接(Connection)非常耗资源,尽量晚创建,尽量早的释放
     2 // 都要加try catch finally以防前面关闭出错,后面的就不执行了
     3 try {
     4     if (rs != null) {
     5         rs.close();
     6     }
     7 } catch (SQLException e) {
     8     e.printStackTrace();
     9 } finally {
    10     try {
    11         if (st != null) {
    12             st.close();
    13         }
    14     } catch (SQLException e) {
    15         e.printStackTrace();
    16     } finally {
    17         try {
    18             if (conn != null) {
    19                 conn.close();
    20             }
    21         } catch (SQLException e) {
    22             e.printStackTrace();
    23         }
    24     }
    25 }

      

      4 什么是MyBatis

      官网 http://www.mybatis.org/mybatis-3/zh/index.html

      MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。

     

      5 JDBC演变到MyBatis过程

      5.1 连接获取和释放

      频繁地开启和关闭数据库连接造成资源浪费,影响系统性能。

      解决方案:

      通过数据库连接池反复利用已经建立的连接去访问数据库,减少连接的开启和关闭的时间。即使用数据库连接池动态管理数据库连接。

      但数据库连接池多种多样,有可能采用DBCP连接池,也有可能采用容器本身的JNDI连接池。

      从DataSource里面获取数据库连接,具体实现由用户来配置。

     

      5.2 SQL语句统一存取

      使用JDBC操作数据库时,SQL语句基本都散落在各个Java类中,这样有3个不足之处:

      —可读性很差,不利于维护以及做性能调优。

      —改动Java代码需要重新编译、打包部署。

      —不利于取出SQL语句在数据库客户端执行(因为之前编写好的SQL语句通过+号进行拼凑,所以取出后需要删掉中间的Java代码)。

      解决方案:

      SQL语句统一存放到XML中。

      

      5.3 重复SQL语句

      如何复用SQL语句?

      解决方案:

      将重复的SQL片段独立成一个SQL块,然后各个SQL语句引用重复的SQL块。

      5.4 传入参数映射和动态SQL

      根据前台传入参数的不同,如何动态生成对应的SQL语句呢?

      解决方案:

      使用if、choose、when、otherwise元素组装SQL语句,#{变量名}传递SQL所需要的参数,${变量名}传递SQL语句本身。

     

      5.5 结果映射和结果缓存

      如何封装结果处理?

      解决方案:

      为了将结果匹配到对应的数据结构,SQL处理器需要明确两点:第一,需要返回什么类型的对象?第二,结果如何映射到相应的数据结构?

      如何存储SQL执行结果即缓存来提升性能?

      缓存数据都是key-value的格式,SQL语句和传入参数两部分合起来可以作为数据缓存的key值。

      

      参考资料

      《深入浅出MyBatis技术原理与实战》

      JDBC教程

      JDBC详解

      JDBC Statement接口实现的execute方法

      JDBC PrepareStatement对象执行批量处理实例

      JDBC之PreparedStatement

      终结篇:MyBatis原理深入解析(一)

  • 相关阅读:
    计算广告学学习1
    scala学习手记20
    scala学习手记19
    scala学习手记18
    scala学习手记17
    SAM4E单片机之旅——12、USART
    SAM4E单片机之旅——11、UART之PDC收发
    SAM4E单片机之旅——10、UART与MCK之PLL
    SAM4E单片机之旅——9、UART与MCK之MAINCK
    SAM4E单片机之旅——8、UART初步
  • 原文地址:https://www.cnblogs.com/WJQ2017/p/7619850.html
Copyright © 2020-2023  润新知