引言

最近想分析一下mybatis源码,但是在这之前呢,先分析一下JDBC操作mysql。

先写个小例子

1.在mysql中建表

1
2
3
4
CREATE TABLE `people` (
`id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.获取连接工具方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public static Connection getConnection() {
Connection conn = null;
try {
//初始化驱动类com.mysql.cj.jdbc.Driver
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8", "root", "123456");
//该类就在 mysql-connector-java-5.0.8-bin.jar中,如果忘记了第一个步骤的导包,就会抛出ClassNotFoundException
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return conn;
}

3.写增删该查操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public static void select() {
String sql = "select * from people order by id";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println("id:" + id + ",name:" + name);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
close(conn);
}
}

public static void update(int id, String name) {
String sql = "update people set name =? where id=?";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
pstmt.setInt(2, id);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
close(conn);
}
}

public static void del(int id) {
String sql = "delete from people where id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
close(conn);
}
}

public static void add(int id, String name) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement("insert into people(id,name) values(?,?)");
pstmt.setInt(1, id);
pstmt.setString(2, name);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
close(conn);
}
}

4.各种关闭:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void close(PreparedStatement pstmt) {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}

}
}

public static void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

public static void close(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

5.测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
System.out.println("------start---------");
select();
System.out.println("------add---------");
add(1, "张三");
select();
System.out.println("------update---------");
update(1, "李四");
select();
System.out.println("------del---------");
del(1);
select();
System.out.println("------end---------");
}

一套下来,看起来没啥难点,但是其内部是如何操作的呢?

DriverManager初始化分析

首先,去掉初始化驱动类com.mysql.cj.jdbc.Driver这一步骤,也能够运行成功,原因就在DriverManager中:

1
2
3
4
5
6
7
8
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

加载初始化驱动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

这里有两处重要的地方,一个是ServiceLoader.load(Driver.class),另一处是
Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
着重看一下ServiceLoader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}

紧接着迭代LazyIterator的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
  • 这里的PREFIX为:”META-INF/services/“
  • service.getName()为:java.sql.Driver

读取jar包下面的META-INF/services/java.sql.Driver文件,
mysql-connector-java-8.0.17中的内容为:com.mysql.cj.jdbc.Driver
最后通过反射加载com.mysql.cj.jdbc.Driver类。
之后就可以使用了。

DriverManager获取Connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//  Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* 当callerCl为空时,我们应该检查应用程序的类加载器(它正在间接调用这个类),
* 这样就可以从这里加载rt.jar外部的JDBC驱动程序类。
*/
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 + "\")");

// 遍历试图建立连接的已加载的registeredDrivers。
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;

for(java.sql.DriverInfo aDriver : registeredDrivers) {
// 如果调用者没有加载驱动程序的权限,那么跳过它。
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
//调用驱动程序的connect方法
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
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");
}

在这里就交给驱动程序获取连接了:
如com.mysql.cj.jdbc.Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Connection connect(String url, Properties info) throws SQLException {
try {
try {
if (!ConnectionUrl.acceptsUrl(url)) {
return null;
} else {
ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
switch(conStr.getType()) {
case SINGLE_CONNECTION:
return ConnectionImpl.getInstance(conStr.getMainHost());
case LOADBALANCE_CONNECTION:
return LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl)conStr);
case FAILOVER_CONNECTION:
return FailoverConnectionProxy.createProxyInstance(conStr);
case REPLICATION_CONNECTION:
return ReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl)conStr);
default:
return null;
}
}
} catch (UnsupportedConnectionStringException var5) {
return null;
} catch (CJException var6) {
throw (UnableToConnectException)ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);
}
} catch (CJException var7) {
throw SQLExceptionsMapping.translateException(var7);
}
}

这边就复杂了,不深入了驱动程序源码了,大概知道java最终将连接交给驱动程序实现。

PreparedStatement

Connection.prepareStatement(sql)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 创建一个<code>PreparedStatement</code>对象,用于向数据库发送参数化SQL语句。
* <P>
* 带或不带IN参数的SQL语句可以预编译并存储在<code>PreparedStatement</code>对象中。
* 然后可以使用此对象有效地多次执行此语句。
*
* 此方法针对处理从预编译中获益的参数化SQL语句进行了优化。
* 如果驱动程序支持预编译,方法<code>prepareStatement</code>将把语句发送到数据库进行预编译。
* 有些驱动程序可能不支持预编译。
* 在这种情况下,在执行<code>PreparedStatement</code>对象之前,语句可能不会被发送到数据库。
* 这对用户没有直接影响;但是,它确实会影响哪些方法抛出特定的<code>SQLException</code>对象。
* <P>
* 使用返回的<code>PreparedStatement</code>对象创建的结果集默认类型为<code>TYPE_FORWARD_ONLY</code>,
* 并发级别为<code>CONCUR_READ_ONLY</code>。
* 可以通过调用{@link #getHoldability}来确定创建的结果集的可持有性。
*
* @param sql 可能包含一个或多个'?在参数占位符中
* @return 一个新的默认<code>PreparedStatement</code>对象,其中包含预编译的SQL语句
* @exception SQLException 如果发生数据库访问错误,或者在关闭连接上调用此方法
*/
PreparedStatement prepareStatement(String sql)
throws SQLException;

tencent.jpg