本文测试程序使用MySQL数据库,不同数据库的驱动和URL格式有所差异。
一、使用JDBC连接数据库的步骤
- 加载数据库驱动
//动态加载mysql驱动
Class.forName("com.mysql.cj.jdbc.Driver");
- 配置URL
String url = "jdbc:mysql://localhost:3306/study?user=root&password=@@@zxm&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false";
- 获取数据库连接
//获取连接
Connection connection = DriverManager.getConnection(url);
- 获取Statement,Statement对应一条SQL语句
connection.createStatement();//获取一般Statement
connection.prepareStatement(sql);//获取预编译Statement
二、使用一般方法查询数据
public void testSelect() {
try {
String sql = "select * from tb_test t where t.content like '%6%'";
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery(sql);
while(result.next()) {
//通过result迭代可以获取每一行数据
System.out.println(result.getInt(1) + ", " + result.getString(2));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
三、使用预编译SQL语句查询数据
当同一句SQL需要反复执行很多次的时候,我们可以使用预编译的SQL来提升执行效率,即PreparedStatement。
- 缓存PreparedStatement并重复使用的用法
//预编译的SQL语句对象
private PreparedStatement statement1;
//对SQL语句进行预编译,如果statement1不为null,则无需再次编译
private void loadStatment() throws SQLException{
if (null != statement1) return;
String sql = "select * from tb_test t where t.content like ?";
statement1 = connection.prepareStatement(sql);
}
//使用预编译的SQL语句查询数据
public void testPreparedSelect(String parameter) {
try {
loadStatment();
//对预编译的SQL语句中需要的参数赋值
statement1.setString(1, "%" + parameter + "%");
statement1.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
}
- PreparedStatement用完即销毁的用法,这种用法每次调用查询函数时,都会重新创建一个PreparedStatement,与直接Statement没有区别,不能获得效率上的提升,但是赋值相比Statement更加方便了,也比缓存PreparedStatement的用法节省内存开销,并且因为SQL语句是预编译的,可以有效防止SQL注入式攻击
public void testPreparedSelect2(String parameter) {
try {
String sql = "select * from tb_test t where t.content like ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, "%" + parameter + "%");
statement.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
}
四、几种用法的效率简单对比
- 我们使用三种方式对同一个SQL语句查询100000次,记录各自花费时间
package com.baymax.study;
import java.util.Date;
import com.baymax.study.tester.JDBCTester;
public class Main
{
public static void main( String[] args )
{
//在执行查询时,进程所占用的内存会持续增大,三个函数均执行时,后面的函数会因为内存不足异常缓慢,暂时不清楚为什么会出现这种情况,感觉是内存泄漏
//分别执行,各组所花费的时间为29.639s,13.312s,29.685s
//可以看出使用Statement和不重复使用PreparedStatement效率大同小异,而重复使用PreparedStatement预编译SQL语句,效率提升显著
JDBCTester jdbcTester = new JDBCTester();
test1(jdbcTester);
test2(jdbcTester);
test3(jdbcTester);
}
private static void test1(JDBCTester jdbcTester) {
long start = new Date().getTime();
for (int i = 0; i < 100000; i++) {
jdbcTester.testSelect();
}
long end = new Date().getTime();
System.out.println((end-start)/1000.0);
}
private static void test2(JDBCTester jdbcTester) {
long start = new Date().getTime();
for (int i = 0; i < 100000; i++) {
jdbcTester.testPreparedSelect("6");
}
long end = new Date().getTime();
System.out.println((end-start)/1000.0);
}
private static void test3(JDBCTester jdbcTester) {
long start = new Date().getTime();
for (int i = 0; i < 100000; i++) {
jdbcTester.testPreparedSelect2("6");
}
long end = new Date().getTime();
System.out.println((end-start)/1000.0);
}
}
- JDBCTester完整代码
package com.baymax.study.tester;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCTester {
private Connection connection;
public JDBCTester() {
loadConnection();
}
private void loadConnection(){
try {
String url = "jdbc:mysql://localhost:3306/study?user=root&password=@@@zxm&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false";
Class.forName("com.mysql.cj.jdbc.Driver");//动态加载mysql驱动
connection = DriverManager.getConnection(url);//获取连接
} catch (Exception e) {
e.printStackTrace();
}
}
//使用Statement,不对SQL进行预编译
public void testSelect() {
try {
String sql = "select * from tb_test t where t.content like '%6%'";
Statement statement = connection.createStatement();
statement.executeQuery(sql);
} catch (SQLException e) {
e.printStackTrace();
}
}
//将PreparedStatement对象缓存,避免重复初始化,这样使用在执行频率相当高时,可以获得极大的效率提升
private PreparedStatement statement1;
private void loadStatment() throws SQLException{
if (null != statement1) return;
String sql = "select * from tb_test t where t.content like ?";
statement1 = connection.prepareStatement(sql);
}
public void testPreparedSelect(String parameter) {
try {
loadStatment();
statement1.setString(1, "%" + parameter + "%");
statement1.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*这样使用PreparedStatement类似于使用Statement,每次调用函数都会进行初始化,所以并不会获得效率上的提升。
优点:
1、可以让你更方便地对参数进行赋值;
2、每次用完就将PreparedStatement销毁,也可以节约内存,对于执行频率不高的SQL语句,是一种不错的选择;
3、使用预编译SQL可以有效防止SQL注入式攻击;*/
public void testPreparedSelect2(String parameter) {
try {
String sql = "select * from tb_test t where t.content like ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, "%" + parameter + "%");
statement.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
五、批量操作
在进行增删改操作时,可以使用PreparedStatement进行批量操作来提升效率。
private PreparedStatement statement2;
private void loadStatment2() throws SQLException {
if (null != statement2) return;
String sql = "insert into tb_test (id, content) values (?, ?)";
statement2 = connection.prepareStatement(sql);
}
public int[] testBatchInsert(List<Map<String, Object>> parameters) {
try {
loadStatment2();
for (Map<String, Object> parameter : parameters) {
//循环添加批量任务
statement2.setInt(1, (Integer) parameter.get("id"));
statement2.setString(2, (String) parameter.get("content"));
statement2.addBatch();
}
//批量执行,results为各条sql执行后影响行数的集合
int[] results = statement2.executeBatch();
connection.commit();
return results;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
六、调用存储过程
- 调用无参存储过程
public void testCallProcedureWithoutParameter() throws SQLException {
CallableStatement statement = connection.prepareCall("{call pro_test()}");
statement.execute();
}
- 调用有输入参数存储过程
public void testCallProcedureWithInParameter(int id) throws SQLException {
CallableStatement statement = connection.prepareCall("{call pro_test(?)}");
statement.setInt(1, id);//给入参赋值
statement.execute();
}
- 调用有入参出参存储过程
public String testCallProcedureWithInOutParameter(int id) throws SQLException {
CallableStatement statement = connection.prepareCall("{call pro_test(?,?)}");
statement.setInt(1, id);//给入参赋值
statement.registerOutParameter(2, Types.CHAR);//注册输出参数类型
statement.execute();
return statement.getString(2);//根据索引获取输出参数值
}