JDBC

JDBC存在的问题:
1. 数据库连接创建、释放频繁造成系统资源浪费,影响性能
2. 3 5 7中均存在硬编码,修改sql语句还要修改代码,不易维护

 1 import java.sql.*;
 2 
 3 public class JDBC {
 4     public static void main(String[] args) throws Exception {
 5         Connection conn = null;
 6         PreparedStatement stmt = null;
 7         ResultSet rs = null;
 8 
 9         try {
10             // 1.加载数据库驱动
11             Class.forName("com.mysql.jdbc.Driver");
12             // 2.通过驱动管理类获取数据库连接
13             conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "abc");
14             // 3.定义sql语句,?表示占位符
15             String sql = "select * from user where username=?";
16             // 4.获取预处理stmt
17             stmt = conn.prepareStatement(sql);
18             // 5.设置参数(即把sql中的第一个问号设为sakura)
19             stmt.setString(1, "sakura");
20             // 6.向数据库发出sql执行查询得到结果集rs
21             rs = stmt.executeQuery();
22             // 7.遍历查询结果集
23             while (rs.next()) {
24                 System.out.println(rs.getString("id") + " "
25                         + rs.getString("username"));
26             }
27         } catch (Exception e) {
28             e.printStackTrace();
29 
30         } finally {
31             // 8.释放资源
32             if (rs != null) {
33                 try {
34                     rs.close();
35                 } catch (SQLException e) {
36                     e.printStackTrace();
37                 }
38             }
39 
40             if (stmt != null) {
41                 try {
42                     stmt.close();
43                 } catch (SQLException e) {
44                     e.printStackTrace();
45                 }
46             }
47 
48             if (conn != null) {
49                 try {
50                     conn.close();
51                 } catch (SQLException e) {
52                     e.printStackTrace();
53                 }
54             }
55         }
56     }
57 }

PreparedStatement防止sql注入原理:

SQL注入:简单来说就是用户在前端web页面输入恶意的sql语句用来欺骗后端服务器去执行恶意的sql代码,从而导致数据库数据泄露或者遭受攻击

传入参数为sakura,则执行的sql为select * from user where username='sakura',但是如果传入的参数变为sakura' or 1='1,则执行的sql语句就变成了select * from user where username='sakura' or 1='1'。所以仅仅通过对sql预编译简单地把字符串参数两边加上引号的方式仍然很容易被sql注入攻击

mysql-jdbc连接源码中PreparedStatement类的实现:

/**
 * Set a parameter to a Java String value. The driver converts this to a SQL
 * VARCHAR or LONGVARCHAR value (depending on the arguments size relative to
 * the driver's limits on VARCHARs) when it sends it to the database.
 * 
 * @param parameterIndex
 *            the first parameter is 1...
 * @param x
 *            the parameter value
 * 
 * @exception SQLException
 *                if a database access error occurs
 */
public void setString(int parameterIndex, String x) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
        // if the passed string is null, then set this column to null
        if (x == null) {
            setNull(parameterIndex, Types.CHAR);
        } else {
            checkClosed();
            int stringLength = x.length();
            if (this.connection.isNoBackslashEscapesSet()) {
                // Scan for any nasty chars
                boolean needsHexEscape = isEscapeNeededForString(x, stringLength);
                if (!needsHexEscape) {
                    byte[] parameterAsBytes = null;
                    StringBuilder quotedString = new StringBuilder(x.length() + 2);
                    quotedString.append('\'');
                    quotedString.append(x);
                    quotedString.append('\'');
                    if (!this.isLoadDataQuery) {
                        parameterAsBytes = StringUtils.getBytes(quotedString.toString(), this.charConverter, this.charEncoding,
                                this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), getExceptionInterceptor());
                    } else {
                        // Send with platform character encoding
                        parameterAsBytes = StringUtils.getBytes(quotedString.toString());
                    }
                    setInternal(parameterIndex, parameterAsBytes);
                } else {
                    byte[] parameterAsBytes = null;
                    if (!this.isLoadDataQuery) {
                        parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(),
                                this.connection.parserKnowsUnicode(), getExceptionInterceptor());
                    } else {
                        // Send with platform character encoding
                        parameterAsBytes = StringUtils.getBytes(x);
                    }
                    setBytes(parameterIndex, parameterAsBytes);
                }
                return;
            }
            String parameterAsString = x;
            boolean needsQuoted = true;
            if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
                needsQuoted = false; // saves an allocation later
                StringBuilder buf = new StringBuilder((int) (x.length() * 1.1));
                buf.append('\'');
                //
                // Note: buf.append(char) is _faster_ than appending in blocks, because the block append requires a System.arraycopy().... go figure...
                //
                for (int i = 0; i < stringLength; ++i) {
                    char c = x.charAt(i);
                    switch (c) {
                        case 0: /* Must be escaped for 'mysql' */
                            buf.append('\\');
                            buf.append('0');
                            break;
                        case '\n': /* Must be escaped for logs */
                            buf.append('\\');
                            buf.append('n');
                            break;
                        case '\r':
                            buf.append('\\');
                            buf.append('r');
                            break;
                        case '\\':
                            buf.append('\\');
                            buf.append('\\');
                            break;
                        case '\'':
                            buf.append('\\');
                            buf.append('\'');
                            break;
                        case '"': /* Better safe than sorry */
                            if (this.usingAnsiMode) {
                                buf.append('\\');
                            }
                            buf.append('"');
                            break;
                        case '\032': /* This gives problems on Win32 */
                            buf.append('\\');
                            buf.append('Z');
                            break;
                        case '\u00a5':
                        case '\u20a9':
                            // escape characters interpreted as backslash by mysql
                            if (this.charsetEncoder != null) {
                                CharBuffer cbuf = CharBuffer.allocate(1);
                                ByteBuffer bbuf = ByteBuffer.allocate(1);
                                cbuf.put(c);
                                cbuf.position(0);
                                this.charsetEncoder.encode(cbuf, bbuf, true);
                                if (bbuf.get(0) == '\\') {
                                    buf.append('\\');
                                }
                            }
                            buf.append(c);
                            break;
                        default:
                            buf.append(c);
                    }
                }
                buf.append('\'');
                parameterAsString = buf.toString();
            }
            byte[] parameterAsBytes = null;
            if (!this.isLoadDataQuery) {
                if (needsQuoted) {
                    parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding,
                            this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), getExceptionInterceptor());
                } else {
                    parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(),
                            this.connection.parserKnowsUnicode(), getExceptionInterceptor());
                }
            } else {
                // Send with platform character encoding
                parameterAsBytes = StringUtils.getBytes(parameterAsString);
            }
            setInternal(parameterIndex, parameterAsBytes);
            this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.VARCHAR;
        }
    }
}

源码中通过一个for循环对字符串参数每一位上的char字符进行遍历,并通过switch()...case条件语句进行判断,当出现换行符、引号、斜杠等特殊字符时,会对这些特殊字符进行转义。即用PreparedStatement进行传参时,若传入参数为sakura' or 1='1,经过处理后实际执行的sql语句是select * from user where username= 'sakura\' or 1 = \'1',显然这样查询出来的结果为空

posted @ 2018-04-16 23:08  sakura1027  阅读(172)  评论(0编辑  收藏  举报