一:JDBC应用
JDBC主要能分为几个步骤,分别是初始化驱动,建立数据库的连接,创建statement,执行sql语句,关闭连接。
初始化驱动:
通过Class.forName("com.mysql.jdbc.Driver"); 初始化驱动类.Class.forName是加载这个类到JVM中,加载的时候就会执行其中的静态初始化快,完成驱动的初始化相关工作。
向java.sql.DriverManager注册自身,注册驱动首先就是初始化。
然后封装信息到DriverInfo中,最后成为驱动集合的一部分。
可以看到jdbc 的驱动类Driver实际上实现了java.sql.Driver
任何驱动类都需要实现这个接口。在DriverManager类中使用的都是Driver类。也就是说使用驱动不会涉及具体的实现。
不管使用何种数据库,只需要更换参数即可。
从网上找到一个注册驱动的具体过程序列图如下:
建立数据库的连接:
数据库连接的底层是客户端与mysql服务器建立了TCP长连接。
Connection c = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1:3306/hellow?characterEncoding=UTF-8",
"root", "admin");
建立与数据库的连接,需要提供数据库所在ip 127.0.0.1,数据库端口号port 3306,数据库名称db hellow,编码方式 UTF-8
数据库账号 root,数据库密码 admin。(视个人情况而定)
getConnection:
从driverManager可以看出调用getConnection时,会根据初始化加载的驱动Driver来查找合适的驱动程序。
最后通过com.mysql.jdbc.Driver.connect建立连接。
通过com.jdbc.mysql.ConnectionImpl建立连接。
接着跟代码
可以发现this.connectOneTryOnly(xx); this.connectWithRetries(xx);中都用到了coreConnect(xxx) 方法。这就是连接的核心:
上图红圈中分别会做两件事:与MysqlServer建立Socket连接。连接成功后将参数账号,密码,数据库名等发送并进行登录校检。。
最后依旧是以网上一张具体的获取数据库连接的序列图结尾:
创建statement:
Statement s = c.createStatement();
注意是java.sql.Statement 而不是 com.mysql.jdbc.Statement
c是上一步创建的Connection对象
这个比较简单,即通过刚刚建立的Connection类来获取执行sql语句并存储返回结果的对象。
这里要特别提及另外一种PreparedStatement方式。
创建预编译PreparedStatement
String sql = "insert into user values(null,?,?,?)";
PreparedStatement ps = c.prepareStatement(sql);
和Statement一样,PreparedStatement也是用来执行sql语句的,但是与Statement不一样的是,需要先创建sql语句,再根据sql语句来创建PreparedStatement。除此之外,还能够通过设置参数,而不是像Statement那样拼接字符串。
注意:JAVA里面有两个基为1的地方,一个是这里,还有一个是查询语句中的ResultSet也是基1的。
PreparedStatetment与Statement相比,PreparedStatement有三个好处。
第一个是刚刚提及的设置参数,而不用在写sql时就将参数都确定。
第二个是有预编译机制,性能比Statement更快。
第三个是防止SQL注入攻击。
补充:SQL注入就是在url或者页面上查询或插入时,插入SQL语句,最终使服务器执行恶意的SQL命令,比如select * from user where name = ?。 当参数 ? 为 xiaoming,最多返回符合名字为xiaoming的数据,但是如果参数 ? 为 xiaoming or 1=1 时,将会返回所有的数据,当user表数据量达到上百万时,甚至可能让服务器瘫痪。而PreparedStatement则会规定好SQL语句的格式,需要查的数据也设置好了,缺的只是那几个参数,而不会额外执行恶意添加的语句(or 1=1 或者 ; delete * from user )
执行sql语句:
当创建好Statement或者PreparedStatement之后,就可以通过execute()或者executeQuery()来执行了。
Statement与PreparedStatement不同,PreparedStatement是根据SQL预编译后的模板,加上设置的参数,解析成一条完整的语句,而Statement则是直接编译和执行整条完整的语句。最后都根据MySql协议,序列化成字节流,RPC发送给MySql服务端。
1. public java.sql.ResultSet executeQuery() throws SQLException {
2. checkClosed();
3. ConnectionImpl locallyScopedConn = this.connection;
4. CachedResultSetMetaData cachedMetadata = null;
5. synchronized (locallyScopedConn.getMutex()) {
6. if (doStreaming
7. && this.connection.getNetTimeoutForStreamingResults() > 0) {
8. locallyScopedConn.execSQL(this, "SET net_write_timeout="
9. + this.connection.getNetTimeoutForStreamingResults(),
10. -1, null, ResultSet.TYPE_FORWARD_ONLY,
11. ResultSet.CONCUR_READ_ONLY, false, this.currentCatalog,
12. null, false);
13. }
14. //解析封装需要发送的sql语句,序列化成MySQL协议对应的字节流
15. Buffer sendPacket = fillSendPacket();
17. if (locallyScopedConn.getCacheResultSetMetadata()) {
18. cachedMetadata = locallyScopedConn.getCachedMetaData(this.originalSql);
19. }
21. Field[] metadataFromCache = null;
23. // 执行sql语句,并获取MySQL发送过来的结果字节流,根据MySQL协议反序列化成ResultSet
24. this.results = executeInternal(-1, sendPacket,
25. doStreaming, true,
26. metadataFromCache, false);
27.
28. if (oldCatalog != null) {
29. locallyScopedConn.setCatalog(oldCatalog);
30. }
32. }
33. this.lastInsertId = this.results.getUpdateID();
34. return this.results;
35. }
关闭连接:
// 先关闭Statement
if (s != null)
try {
s.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 后关闭Connection
if (c != null)
try {
c.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
也可以使用try-with-resource的方式自动关闭连接。因为Connection和Statement都实现了AutoCloseable接口。
注意这是jdk1.7之后才有的特性