我是通过易百教程和JDBC获取连接对象源码分析进行JDBC的粗略学习了,主要是为了梳理知识脉络。现在总结如下。
JDBC是用来建立Java程序与数据库的连接的,Java规定了一系列的API,然后让各个类型的数据库公司去实现这些API,Java只负责通过API与数据库通信。数据库公司实现好的API打包成一个jar包,我们称之为数据库的驱动包。通过将该包导入到Java的库中,就可以使用对应的数据库了。数据库驱动有4种类型,上面说的方法就是第4种类型,也是最常用的类型。
0. 前言
由于是与数据库进行IO操作,因此后续步骤都应该在try/catch/finally块中操作,并对应捕获相应的SQLException。
try{
//建立通信&使用数据库
}catch(){
//捕获各种Exception
}finally{
//完成通信的一系列关闭操作
}
1. JDBC驱动的注册
导入一个数据库驱动包后, 我们要在使用改数据库的时候进行注册。具体的方法就是利用反射来加载该类:
Class.forName("com.mysql.jdbc.Driver");
Connection conn = null;
conn = DriverManager.getConnection("jdbc:mysql://hostname:port/db_name","db_username", "db_password");
conn.close();
其具体的原理如下,是利用了Driver类中的静态代码块进行驱动的注册的。当加载这个类是就会自动运行静态代码块。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());// 注册驱动
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
2. 获取Connection接口实例
String URL = "jdbc:oracle:thin:@amrood:1521:EMP";
// String URL = "jdbc:oracle:thin:@192.0.0.10:1521:EMP";
String USER = "username";
String PASS = "password"
Connection conn = DriverManager.getConnection(URL, USER, PASS);
如上所示,在注册好驱动后,我们利用DriverManager.getConnection(URL, USER, PASS);
这个静态方法获得一个Connection类,接着就是通过该类进行一系列的数据库通信操作。
URL的书写方式类似HTML语言,为“通信协议://主机地址/数据库名?参数1&参数2”.
注:Connection类要在使用完成后用.close()
随时关闭,保证良好的代码习惯。
3. 获取Statement接口实例
通过对Connection实例conn操作,我们就可以获得对应的Statement实例。 具体的Statement实例有三种,
3.1. Statement对象
它通过无参方法con.creatStatement()
返回得到。接着通过调用Statement的实例方法进行数据库的操作。具体的实例方法有三个:boolean(是否有返回集ResultSet) execute (String SQL)
,int(返回受影响的条目数) executeUpdate (String SQL)
,ResultSet executeQuery(String SQL)
。这三个方法都接受一个String类参数sql,这个参数就是数据库命令语句的字符串形式。例子如下:
...
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql = "UPDATE Employees set age=30 WHERE id=103";
// Let us check if it returns a true Result Set or not.
Boolean ret = stmt.execute(sql);
System.out.println("Return value is : " + ret.toString() );
// Let us update age of the record with ID = 103;
int rows = stmt.executeUpdate(sql);
System.out.println("Rows impacted : " + rows );
// Let us select all the records and display them.
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);
...
3.2. PreparedStatement对象
PreparedStatement
接口扩展了Statement
接口,它添加了比Statement
对象更好一些优点的功能。
此语句可以动态地提供/接受参数。
它通过有参方法con.prepareStatement(String sql)
返回得到。即该对象需要事先准备好一个String的sql语句,该语句形式如下:String SQL = "Update Employees SET age = ? WHERE id = ?";
JDBC中的所有参数都由 ? 符号作为占位符,这被称为参数标记。 在执行SQL语句之前,必须为每个参数(占位符)提供值。
setXXX()
方法将值绑定到参数,其中XXX
表示要绑定到输入参数的值的Java数据类型。如stmt.setInt(1,10)
表示age=10, 如果忘记提供绑定值,则将会抛出一个SQLException。
每个参数标记是它其顺序位置引用。第一个标记表示位置1,下一个位置2等等。 该方法与Java数组索引不同(它不从0开始)。
所有Statement
对象与数据库交互的方法(a)execute()
,(b)executeQuery()
和(c)executeUpdate()
也可以用于PreparedStatement对象。 但是,这些方法被修改为可以使用输入参数的SQL语句。
注:使用该对象可以防止SQL注入攻击,并且对于反复使用的SQL语句,该类能有更高的效率进行执行。
3.3. CallableStatement对象
该对象主要是用于调用数据库中的储存过程的。由于储存过程含有IN,OUT,INOUT
对象,因此在用问号占位后,对于IN位置的变量,仍用setXXX()
来设置,但是对于OUT位置的问号则要用一个额外的CallableStatement
对象方法registerOutParameter()
。 registerOutParameter()
方法将JDBC数据类型绑定到存储过程并返回预期数据类型。用法如下:stmt.registerOutParameter(java.sql.Types.VARCHAR)
···
System.out.println("Creating statement...");
String sql = "{call getEmpName (?, ?)}";//SQL转义语法
stmt = conn.prepareCall(sql);
//Bind IN parameter first, then bind OUT parameter
int empID = 102;
stmt.setInt(1, empID); // This would set ID as 102
// Because second parameter is OUT so register it
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
//Use execute method to run stored procedure.
System.out.println("Executing stored procedure..." );
stmt.execute();
//Retrieve employee name with getXXX method
String empName = stmt.getString(2);//通过getXXX方式获得OUT参数。
System.out.println("Emp Name with ID:" +
empID + " is " + empName);
···
4. ResultSet类的操作
java.sql.ResultSet
接口表示数据库查询的结果集。
4.1. ResultSet的属性
在创建Statement
,PrepareStatement
,CallableStatement
时使用的对应方法时,我们可以通过给这些方法传入参数来设置ResultSet的属性是仅仅能向后遍历,还是前后都可以遍历。以及是否会在数据库更新后自动更新结果集。
4.2. ResultSet的使用
由于是返回的一个集合的迭代器,因此我们有以下操作:
- 移动迭代器(光标)的操作
- 读取当前光标所指的结果行的数据的操作
- 更新当前行的数据的操作(仅仅是结果集,不会改变数据库内数据)
- 更新当前行对应的数据库行的操作(会改变数据库内容)
5. 事务操作
由于数据库读写是IO操作,因此为了保证原子性,我们需要采用一系列的事务操作。如提交,回滚,设置保存点。
5.1. 提交
con.commit()
用于将自上一次提交后的所有改动操作真正的提交执行。
5.2. 回滚
con.rollback()
用于将自上一次提交后的所有改动操作全部取消。时常将回滚操作放在catch块内进行。
5.3. 保存点
通过设置保存点位置,可以将操作回滚至保存点处,而不用全部回滚。如下:
try{
//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
//set a Savepoint
Savepoint savepoint1 = conn.setSavepoint("Savepoint1");
String SQL = "INSERT INTO Employees " +
"VALUES (106, 24, 'Curry', 'Stephen')";
stmt.executeUpdate(SQL);
//Submit a malformed SQL statement that breaks
String SQL = "INSERTED IN Employees " +
"VALUES (107, 32, 'Kobe', 'Bryant')";
stmt.executeUpdate(SQL);
// If there is no error, commit the changes.
conn.commit();
}catch(SQLException se){
// If there is any error.
conn.rollback(savepoint1);
}
6. 批处理
对于statement类的执行,我们可以通过将各种statement类加入一个batch(批处理集合)中,然后执行这个batch来达到统一执行多条语句的效果。(注意要将自动提交false)
主要是使用stmt.addBatch()
和stmt.excuteBatch()
来添加执行批处理。例子如下:
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(?, ?, ?, ?)";
// Create PrepareStatement object
PreparedStatemen pstmt = conn.prepareStatement(SQL);
//Set auto-commit to false
conn.setAutoCommit(false);
// Set the variables
pstmt.setInt( 1, 400 );
pstmt.setString( 2, "JDBC" );
pstmt.setString( 3, "Li" );
pstmt.setInt( 4, 33 );
// Add it to the batch
pstmt.addBatch();
// Set the variables
pstmt.setInt( 1, 401 );
pstmt.setString( 2, "CSharp" );
pstmt.setString( 3, "Liang" );
pstmt.setInt( 4, 31 );
// Add it to the batch
pstmt.addBatch();
//add more batches
.
.
.
.
//Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();