注意:不同版本的驱动jar包,在连接代码中有稍微的差别。
SQLHelper代码如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class SQLHelper {
//创建数据库连接、预处理和结果集对象
private static Connection conn = null;
private static PreparedStatement pre = null;
private static ResultSet rs = null;
//加载驱动
static {
String driver = "com.mysql.cj.jdbc.Driver";
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 建立连接
*/
private static void buildConnection() {
//数据库配置信息
String url = "jdbc:mysql://localhost:3306/db_name?serverTimezone=GMT";
String user = "root", password = "123456";
try {
conn = DriverManager.getConnection(url, user, password);
System.out.println("数据库连接成功");
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 执行查询SQL语句,返回查询结果集
*
* @param sql SQL语句
* @return 结果集对象
*/
public static ResultSet executeQuery(String sql) {
buildConnection();
try {
pre = conn.prepareStatement(sql);
rs = pre.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
//使用后需要在调用处手动关闭连接
return rs;
}
/**
* 执行更新语句
*
* @param sql 更新语句
* @return 是否更新成功
*/
public static boolean executeUpdate(String sql) {
buildConnection();
int r = 0;
try {
pre = conn.prepareStatement(sql);
r = pre.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
closeConnection();
return r != 0;
}
/**
* 执行单个语句,返回结果对象
*
* @param singleSql 单个SQL语句
* @return 结果对象
*/
public static Object executeSingleQuery(String singleSql) {
buildConnection();
Object obj = null;
try {
pre = conn.prepareStatement(singleSql);
rs = pre.executeQuery();
if (rs.next()) {
obj = rs.getObject(1);
}
} catch (Exception ex) {
ex.printStackTrace();
}
closeConnection();
return obj;
}
/**
* 关闭连接
*/
public static void closeConnection() {
try {
if (conn != null && !conn.isClosed()) {
conn.close();
}
System.out.println("数据库连接已关闭");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
注意:上述连接代码使用的为mysql-connector-java-8.0.15.jar
版本的驱动jar包。如果使用的是版本6以后的jar包,则不需要更改上述代码任何信息,只需把url
中的db_name
改成自己的数据库名称、user
及password
也做出相应改变即可。但如果使用的为版本5以后版本6以前的jar包,那么上述代码中要更改两处信息。如下:
String driver = "com.mysql.cj.jdbc.Driver";
更改为:
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/db_name?serverTimezone=GMT";
更改为:
String url = "jdbc:mysql://localhost:3306/db_name";
import java.sql.*;
public class SQLHelper {
//创建数据库连接、预处理和结果集对象
private static Connection conn = null;
private static PreparedStatement pre=null;
private static ResultSet rs=null;
//静态代码块,用于加载驱动和建立连接
static {
String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
String url = "jdbc:sqlserver://127.0.0.1:1433;database=db_name";
String user = "geshu", password = "123456";
try {
//加载驱动
Class.forName(driver);
//建立连接
long startTime = System.currentTimeMillis();
conn = DriverManager.getConnection(url, user, password);
long endTime = System.currentTimeMillis();
System.out.println("建立连接耗时:" + (endTime - startTime) + " ms");
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 执行sql语句,返回结果集
*
* @param sql SQL语句
* @return 结果集对象
*/
public static ResultSet executeQuery(String sql) {
try {
pre = conn.prepareStatement(sql);
rs = pre.executeQuery();
} catch (Exception ex) {
ex.printStackTrace();
}
return rs;
}
/**
* 执行更新语句
*
* @param sql 更新语句
* @return 是否更新
*/
public static boolean executeUpdate(String sql) {
int r = 0;
try {
pre = conn.prepareStatement(sql);
r = pre.executeUpdate();
conn.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return r != 0;
}
/**
* 执行select语句,返回唯一值
*
* @param singleSql select语句
* @return 结果对象
*/
public static Object executeSingleQuery(String singleSql) {
Object object = null;
try {
pre = conn.prepareStatement(singleSql);
rs = pre.executeQuery();
if (rs.next()) { //如果结果集不为空集
object = rs.getObject(1);
}
conn.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return object;
}
}
Statement结构图:
Java提供了 Statement、PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, PreparedStatement 一般用来执行带IN参数或不带参数的查询语句,而 CallableStatement则是用于存储过程。
我们应优先使用PreparedStatement命令对象,因而在本文介绍一下PreparedStatement。
PreparedStatement是java.sql包下面的一个接口,继承了Statement,并与之在两方面有所不同。通过调用connection.preparedStatement(sql)方法可以获得PreparedStatment对象。数据库系统会对sql语句进行预编译处理,预处理语句将被预先编译好,这条预编译的sql查询语句能在将来的查询中重用,因此它比Statement对象生成的查询速度更快。
有人主张,在JDBC应用中,如果你已经是稍有水平开发者,你就应该始终以PreparedStatement代替Statement。也就是说,减少使用Statement。
假如数据库中有一张表模式为people(name, age),则向该表中插入数据可用如下代码:
String sql = insert into people values (?,?);
PreparedStatement pre = connection.preparedStatement(sql);
pre.setString(1,"张三");
pre.setInt(2,21);
pre.executeUpdate();
分析:?
为占位符,一个占位符只允许有且仅有一个值(所以这也是PreparedStatement的一点局限性)。设置参数值(占位符值)时要根据参数的类型选择不同的set方法,值得注意的是:占位符的索引位置从1开始而不是0,如果填入0会导致java.sql.SQLException invalid column index异常。
正如上述代码所示,PreparedStatement可以使用参数进行执行sql语句,所以在一些复杂执行语句情况下,使用PreparedStatement对象可以更清晰的阅读与维护代码。
因为预编译语句有可能被重复调用,所以预编译语句在被DB编译器编译后的执行代码会被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。这并不是说只有一个Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配,那么在任何时候就可以不需要再次编译而可以直接执行,所以它的性能更高。而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配。比如:
insert into people values('李四','22');
insert into people values('王五','23');
即使是相同操作,但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义,事实上没有数据库会对普通语句编译后的执行代码缓存。
当然并不是所有预编译语句都一定会被缓存,数据库本身会用一种策略,比如使用频度等因素来决定什么时候不再缓存已有的预编译结果,以保存有更多的空间存储新的预编译语句。
先来了解一下普通Statement中的安全隐患。
(1)免密登录
假如我们有如下SQL代码:
String sql="select * from users where account='"+account+"' and pwd='"+password+"'";
如果我们输入account和pwd如下:
String account = "1' OR '1'='1";
String pwd = "1' OR '1'='1";
最终执行的SQL语句就变成了如下代码:
select * from users
where account = '1' OR '1' = '1' and pwd = '1' OR '1' = '1';
所以会出现where子句条件恒为真值,因此可以达到无账号密码就可以登录成功。
(2)删除数据库
如果我们把account和pwd输入如下:
String account = "';drop table users; -- ";
String pwd = "密码内容被注释";
最终执行的SQL语句就变成了如下代码:
select * from users where account = ''; drop table users; -- 密码内容被注释
这样虽然用户没有登录,却把数据库中的users表给删除了。
★使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有破坏性的指令,也不会被数据库所运行。
注意: