0%

JDBC--基于MySQL

JDBC ,Java DataBase Connectivity ,是一种用于执行SQL语句的Java API;

所有数据库都支持的一套链接方式;

高级的持久化框架也是基于JDBC来实现的,比如Mybatis;

网络图片

这里我用的是mysql的数据库,使用的是Java 8 的环境;

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>

粗略的总结写JDBC的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取Connection
Connection connection = DriverManager.getConnection(dbUrl, name, pwd);

// 获取Statement
Statement statement = connection.createStatement();
PrepareStatement statement = connection.prepareStatement(sql);

// execute SQL
statement.execute

// 获取结果集
ResultSet rs = statement.getResultSet();

连接数据库

驱动方式

连接驱动数据库:这里采用直接通过类加载的方式加载驱动文件,不推荐使用DriverManager.registerDriver()的方式驱动;

缺点:使用DriverManager.registerDriver()驱动的方式会驱动文件会在内存中注册两次,重复注册;

原因:使用DriverManager.registerDriver()注册的时候,需要new 一个 Driver的实例当参数;

而在Driver加载的时候就会执行其中的静态代码块注册;所有使用DriverManager.registerDriver() 内存中有两个Driver的实例;

1
2
3
4
5
6
7
8
9
10
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
...
java.sql.DriverManager.registerDriver(new Driver());
...
}
}

驱动文件

新驱动文件:com.mysql.cj.jdbc.Driver

旧驱动文件:com.mysql.jdbc.Driver :这个已经被废弃了,推荐使用新的;效果是一样的,只是有废弃的提示log;

1
2
3
4
5
6
7
8
9
10
11
12
private static Connection getConnection() throws ClassNotFoundException, SQLException {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 这里不推荐使用 DriverManager.registerDriver(new Driver()); 加载驱动
// 如果是用registerDriver 内存中会有注册两次

String dbUrl = "jdbc:mysql://localhost:3306/conan";
String name = "root";
String pwd = "12345678";

return DriverManager.getConnection(dbUrl, name, pwd);
}

数据库连接Connection

Connection 是一个数据库的链接的接口;提供获取Statement(其中有 session 最终真正执行Sql )的方法;

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

// 获取connection

// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {

for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
Connection con = aDriver.driver.connect(url, info);
return (con);

}
}
}

//完成数据驱动注册

public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
...
/* Register the driver if it has not already been added to our list */
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
...

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// com.mysql.cj.jdbc.NonRegisteringDriver#connect
// Connection con = aDriver.driver.connect(url, info);

public java.sql.Connection connect(String url, Properties info) throws SQLException {

ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
switch (conStr.getType()) {
case SINGLE_CONNECTION:
return com.mysql.cj.jdbc.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;
}

}

com.mysql.cj.jdbc.NonRegisteringDriver#connect 方法根据Url 中的scheme 规则 返回对应的Connection对象;

连接协议:

SINGLE_CONNECTION:单一数据库连接;

jdbc:mysql://localhost:3306/conan 我这里就是一个主机而已;

LOADBALANCE_CONNECTION: 负载均衡连接;可以是一个集群,里面有很多主机地址;

jdbc:mysql://localhost:3306,[host]:[port],…/[database]

FAILOVER_CONNECTION :Failover协议,不知道怎么翻译比较好,如果主机失败,就连接到从机上;

jdbc:mysql:loadbalance:[host]:[port],…/[database]

REPLICATION_CONNECTION :Replication的思想是将数据在集群的多个节点同步、备份,以提高集群数据的可用性;

jdbc:mysql:replication:[host]:[port],…/[database]

上面的connect()方法,根据URL的scheme类型得到不同的Connection,只有SINGLE_CONNECTION类型,是直接创建了一个ConnectionImpl的实例,其他的都是通过动态代理的方式生成Connection; 这就是上一篇为什么总结动态代理的一个原因;

LOADBALANCE_CONNECTION,FAILOVER_CONNECTION,REPLICATION_CONNECTION:

其最终都是通过代理的方式创建Connection;

1
2
ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);

以LOADBALANCE_CONNECTION类型为例:

LoadBalancedConnectionProxy.createProxyInstance 实现了InvocationHandler接口;

1
2
3
4
5
6
7
                            
LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl) conStr);

public static LoadBalancedConnection createProxyInstance(LoadbalanceConnectionUrl connectionUrl) throws SQLException {
LoadBalancedConnectionProxy connProxy = new LoadBalancedConnectionProxy(connectionUrl);
return (LoadBalancedConnection) java.lang.reflect.Proxy.newProxyInstance(LoadBalancedConnection.class.getClassLoader(), INTERFACES_TO_PROXY, connProxy);
}

代理类真正代理的被代理对象是com.mysql.cj.conf.url.LoadbalanceConnectionUrl;

1
2
3
4
case LOADBALANCE_CONNECTION:
connectionString = (ConnectionUrl) Util.getInstance("com.mysql.cj.conf.url.LoadbalanceConnectionUrl",
new Class<?>[] { ConnectionUrlParser.class, Properties.class }, new Object[] { connStrParser, info }, null);
break;

Statement

获取到Connection之后说明已经连接上数据库了,接下来我们可以通过connection获取statement接口的实例,来执行sql和获取ResultSet;

Statement 是一个接口;

PreparedStatement 也是一个接口,而且继承了Statement;

CallableStatement 也是一个接口,而且继承了PreparedStatement;

Statement

SINGLE_CONNECTION协议下,获取到的Connection真正的实现是ConnectionImpl;

可以通过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
createStatement()  获取默认配置的Statement的实现StatementImpl

createStatement(int resultSetType, int resultSetConcurrency)

/**
* resultSetType ResultSet.TYPE_FORWARD_ONLY 默认的只能向前滚动;
* TYPE_SCROLL_INSENSITIVE 双向滚动,但不及时更新,在查询过程中,如果其他线程更新数据也可以查询不到;
* TYPE_SCROLL_SENSITIVE 双向滚动,及时更新,在查询过程中,如果其他线程更新数据也可以查询到;
*
* resultSetConcurrency ResultSet.CONCUR_READ_ONLY 并发性:ResultSet 只能Read
* CONCUR_UPDATABLE 并发性:ResultSet 支持更新
*
* resultSetHoldability 只能是 java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT 否则报错,查看代码,只有坚持是不是该类型,也没设置到ResultSet上;
*/
createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)

三个创建statement的方法最终都是创建了一个StatementImpl实例,并把并设置其ResultSet的两个属性resultSetType,resultSetConcurrency;

但是并没有看到设置resultSetHoldability的属性;

Statement 接口方法可以分为以下几类

设置类方法

设置类的方法,没什么好多解释的,直接看方法名称就知道什么意思;

1
2
3
4
5
6
7
8
setFetchSize(int rows)
setFetchDirection(ResultSet.FETCH_FORWARD);
setMaxRows(int max)
setLargeMaxRows(long max)
setQueryTimeout(int seconds)
setCursorName(String name)
close()

excute类方法

Statement接口不能设置sql参数,其中execute方法是用来执行sql的方法,execute ,executeUpdate 一系列的方法最终都会和autoGeneratedKeys这个参参数有关,最简单的是使用默认的autoGeneratedKeys;

当Sql语句是Insert,autoGeneratedKeys 的RETURN_GENERATED_KEYS、NO_GENERATED_KEYS ,关系到自增属性字段的查询问题,只有配置了RETURN_GENERATED_KEYS属性的时候,才可以使用getGeneratedKeys() 方法获取ResultSet。然后获取自增属性字段的值;

PreparedStatement, CallableStatement 的实例对象不能调用下面一系列的excute方法;

Statement 执行execute的时候就已经可以获取结果集;
1
2
3
4
5
execute(String sql)
boolean execute(String sql, int autoGeneratedKeys)
executeUpdate(String sql)
executeLargeUpdate(String sql, int autoGeneratedKeys)
executeLargeUpdate(String sql, int columnIndexes[])
批处理方法(前提是connection 关闭自动提交功能)
1
2
3
4
addBatch( String sql )
clearBatch()
int[] executeBatch()
long[] executeLargeBatch()
获取类
1
2
3
boolean getMoreResults()
ResultSet getResultSet()
ResultSet getGeneratedKeys()
示例代码
自增属性
1
2
3
4
5
6
 String creatSql = "insert into users( name, password, email, birthday) values ( \"222\",\"222\",\"2222\",\"222222\")"; //其中有一个id 是自增属性
Statement statement = connection.createStatement();
int i = statement.executeUpdate(creatSql, Statement.NO_GENERATED_KEYS);
ResultSet resultSet = statement.getResultSet();
resultSet.next();
int anInt = resultSet.getInt(1);
批处理
1
2
3
4
5
connection.setAutoCommit(false);
for(..){
statement.addBatch(sql); //如果是PreparedStatement 也是类似
}
statement.executeBatch(); //statement. executeLargeBatch();

ResultSet

ResultSet 作为查询放回的结果集,有必要也介绍一下其中的方法;

ResultSet 可以分为如下几类方法

查询字段valvue类:此类方法很多,这里不一一列举
1
2
3
4
5
int getInt(int columnIndex) 
int getInt(String columnLabel)

String getString(int columnIndex)
String getString(String columnLabel)
操作游标类:
1
2
3
4
5
6
boolean next() 
boolean previous()
boolean absolute( int row )
boolean relative( int rows )
void afterLast()
void moveToInsertRow()
修改系列
1
2
3
4
5
6
void insertRow()
boolean rowInserted() //增加了返回而已
void updateRow()
boolean rowUpdated()
void deleteRow()
boolean rowDeleted()

修改系列的方法相对特别,查询的时候还可以操作数据库;前面说过的resultSetConcurrency属性 ResultSet.CONCUR_READ_ONLY 并发性:ResultSet 只能Read ,CONCUR_UPDATABLE 并发性:ResultSet 支持更新;如果需要修改就必须设置为CONCUR_UPDATABLE;

如果需要插入行,得先执行moveToInsertRow();示例代码如下

1
2
3
4
5
6
7
8
String sql = "select u.name ,u.password,u.birthday,u.id from users as u";
PreparedStatement statement = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
statement.setFetchSize(1);
statement.setFetchDirection(ResultSet.FETCH_FORWARD);
resultSet.moveToInsertRow();
resultSet.updateInt("id",14);
resultSet.updateString("name","xxxxx");
resultSet.insertRow();

PreparedStatement

PreparedStatement 区别于 Statement ,PreparedStatement可以在执行之前修改sql语句中的占位符?参数;有一系列的setXxx()方法;

设置好sql中的数据之后,执行execute()方法执行sql;

CallableStatement

CallableStatement 是用来执行存储过程的Statement;

有一系列的方法用于设置输入参数,还有一系列方法用于获取输出参数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14

CREATE PROCEDURE add_pro(a int,b int,out sum int)
BEGIN
set sum = a + b;
END;

====================================================================================
CallableStatement cstmt = conn.prepareCall("{call add_pro(?,?,?)}")

cstmt.setInt(1,4);
cstmt.setInt(2,5);
cstmt.registerOutParameter(3,Types.INTEGER);
cstmt.execute();
System.out.println("执行结果是:"+cstmt.getInt(3));

事务管理

JDBC的事务操作是基于Connection接口如下几个方法展开的;

采用这些API设计的一套编程式事务管理方式;其中connection自动提交应该设置为 false ;connection.setAutoCommit(false);

1
2
3
4
5
6
void commit()
void rollback()
void rollback(Savepoint savepoint)
Savepoint setSavepoint()
Savepoint setSavepoint(String name)

GitHub登录不了?信任该网站之后再登录。