前言
jdbc是java内置的一套数据库操作接口规范,对该规范实现的有mysql, oracle, sqlite等,是一种典型的桥接模式设计思想,将接口规范与具体实现进行分离解耦。
业务层仅依赖规范中定义的一系列接口,通过动态注入具体实现,比如引入sqlite实现包(通过Class.forName(“org.sqlite.JDBC”)),如果哪天数据库变成mysql数据库了,我们仅需要修改动态注入的这一行代码即可完全替换,从而保证对业务代码的少侵入,甚至是透明的,降低代码的耦合度,这也是软件设计原则中面向接口编程,面向抽象编程的思想。并且sl4j日志规范跟jdbc是一样的设计思想(其中logback只是对sl4j的其中一种实现而已)。
接口规范
常用的接口如下:import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.DriverManager;
import javax.sql.DataSource;
使用示例
第1步:加载驱动 |
原理介绍
org.sqlite.JDBC代码如下:public class JDBC implements Driver
{
public static final String PREFIX = "jdbc:sqlite:";
static {
try {
// 外部通过Class.forName加载并初始化该类,执行此静态语句块,从而实现sqlite的动态注入
DriverManager.registerDriver(new JDBC());
}
catch (SQLException e) {
e.printStackTrace();
}
}
// 判断是否是合法的sqlite url
public static boolean isValidURL(String url) {
return url != null && url.toLowerCase().startsWith(PREFIX);
}
// 创建Connection
public static Connection createConnection(String url, Properties prop) throws SQLException {
if (!isValidURL(url))
throw new SQLException("invalid database address: " + url);
url = url.trim();
return new Conn(url, extractAddress(url), prop);
}
.........
............
}
java.sql.DriverManager代码如下:public class DriverManager {
// List of registered JDBC drivers 存放注册的驱动列表
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
// 注册驱动
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
//获取连接
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized (DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
//判断是否有权限创建连接
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
//调具体实现层创建连接
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
.........
............
}
Statement 与 PreparedStatement区别
相同点:
- 都是接口
- PreparedStatement继承自Statement
不同点:
PreparedStatement含拼接参数功能,并使用引号包含的(例如:’参数值’),这样可以避免sql注入攻击,但是Statement没有参数拼接的功能,sql语句及条件需要我们在外部拼接好了之后,然后传入直接执行sql,如果开发人员不注意,就很容易出现sql注入攻击。所以通常情况下,必须使用PreparedStatement来操作数据库。String sql1 = "select * from t_table where fid=";
String sql2 = "select * from t_table where fid=?";
String param = "1 or 1==1"; //非法的参数
#方式一
Statement state = conn.createStatement();
ResultSet rs = state.executeQuery(sql1 + param);
最终执行的sql: select * from t_table where fid=1 or 1==1
#方式二
PreparedStatement ps = conn.prepareStatement(sql2);
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
ResultSet rs = ps.executeQuery();
最终执行的sql: select * from t_table where fid='1 or 1==1'
日常开发中推荐使用方式二(特别是参数是由外部传入的),因为方式一很容易出现sql注入攻击。
遇到的坑
在遍历结果集ResultSet之前,一定不能关闭了Statement。因为如果关闭了Statement对象,那么其返回的结果集ResultSet也会被清空,到时候遍历就获取不到数据了。所以,正确的顺序是先遍历结果集拿到数据后,再关闭ResultSet和Statement对象及连接Connection。