标签:模式 img 对象 while private 写法 mybatis原理 man edr
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\tlastname\tfirstname\tid"); 24 while (rs.next()) { 25 System.out.println(rs.getInt(1) + "\t" + rs.getString(2) 26 + "\t\t" + rs.getString(3) + "\t\t" + 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\tlastname\tfirstname\tid"); 23 while (rs.next()) { 24 System.out.println(rs.getInt(1) + "\t" + rs.getString(2) 25 + "\t\t" + rs.getString(3) + "\t\t" + 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语句的Statement → 处理执行结果ResultSet → 释放资源。
注意,释放资源顺序:ResultSet → Statement → 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语句的Statement
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语句呢?
解决方案:
使用if、choose、when、otherwise元素组装SQL语句,#{变量名}传递SQL所需要的参数,${变量名}传递SQL语句本身。
5.4 结果映射和结果缓存
如何封装结果处理?
解决方案:
为了将具体的值复制到对应的数据结构上,SQL处理器需要明确两点:第一,需要返回什么类型的对象?第二,返回的对象的数据结构与执行的结果如何映射?
如何存储SQL执行结果即缓存来提升性能?
解决方案:
缓存数据都是key-value的格式,SQL语句和传入参数两部分合起来可以作为数据缓存的key值。
5.5 重复SQL语句
如何复用SQL语句?
解决方案:
将重复的SQL片段独立成一个SQL块,然后各个SQL语句引用重复的SQL块。
参考资料
《深入浅出Mybatis技术原理与实战》
JDBC PrepareStatement对象执行批量处理实例
标签:模式 img 对象 while private 写法 mybatis原理 man edr
原文地址:http://www.cnblogs.com/WJQ2017/p/7619850.html