這次進化完成后,JDBC的進化就需要設置1個savepoint了,然后提交1下,提交到我們的腦袋硬盤中。
上1次說到了利用Statement對象來發送sql語句到數據庫。但是這樣的話,會暴露出兩個問題。
那末問題來了!!!
問題1:
在履行executeUpdate(String sql)和executeQuery(String sql)方法時,我們需要拼寫sql字符串,就像下面這樣:
String sql = "insert into customers values(21, 'yushen1', 'yushem@123.com', '1998⑶⑵', null)";
String sql = "SELECT FlowId flowId, type, IDCard idCard, ExamCard examCard, StudentName studentName, location, Grade grade FROM examstudent WHERE ExamCard = '" + examCard + "'";
感遭到點甚么了沒有???麻煩!!是否是,假設說我要修改20個字段,我是否是得拼好長1個字符串?這就是問題1。
問題2:
看下面的代碼:
String user = "' OR 1 = 1--'";
SELECT *
FROM user_table
WHERE user = '"+user+"';
大家知道產生了甚么?我把整張表的信息都獲得了,包括密碼(你的銀行賬戶密碼被我知道了),太可怕了! 這就是SQL注入問題,也是我要說的第2個問題。
告知你個好消息,有1個1勞永逸的辦法可以同時解決這兩個問題,同時對批量處理效力會大大的提升,這個辦法就是PreparedStatement接口。簡直是好處多多啊。來我們來學習這個辦法。還是老模樣,從具體到通用。
Solution:
PreparedStatement接口:它是Statement接口的子接口。
看看API中對它的描寫:
“An object that represents a precompiled SQL statement.”
“A SQL statement is precompiled and stored in a PreparedStatement object. This object can then be used to efficiently execute this statement multiple times. “
先看第1句:代表1個預編譯的SQL命令對象
再看第2句:1個SQL命令是預編譯的和存儲在1個PreparedStatement對象中。這個對象隨后能高效的履行這個命令屢次。
觸及到1個詞:預編譯,我在下面結合代碼來講
先來1個具體的例子:
@Test
public void testPrepareSelect(){
String sql = "SELECT * FROM users WHERE id = ?;";
// get connection
Connection conn = null;
// get PreparedStatement's object
PreparedStatement ps = null;
// execute the sql
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
// set the ?
ps.setInt(1, 1);
// execute the sql
rs = ps.executeQuery();
// get the rs
if(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String address = rs.getString("address");
String phone = rs.getString("phone");
User user = new User(id, name, address, phone);
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs, ps, conn);
}
}
是否是發現個問號,然后給這個問號設置值?問:這個問號是甚么東東啊?答:這個問號相當于1個占位符,我就把這個位置占住了。要不為何叫預編譯。
再來1個通用的:
/**
* PreparedStatement: through the reflect and generic and PreparedStatement
* @param sql
* @param clazz
* @param args
* @return
*/
public <T> T getPrepareSelect(String sql,Class<T> clazz, Object ... args){
T t = null;
// get the connection
Connection conn = null;
// get the PreparedStatement's object
PreparedStatement ps = null;
// execute the ps
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
// set values for ps
for(int i = 0; i < args.length; i++){
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
// get the ResultSetMetaData's object
ResultSetMetaData rsmd = rs.getMetaData();
// get the columnNum
int columnNum = rsmd.getColumnCount();
// read the data of rs, and packaging an object
if(rs.next()){
t = clazz.newInstance();
for(int i = 1; i <= columnNum; i++){
// get the columnName and columnValue of the special row special column
String columnName = rsmd.getColumnLabel(i);
Object columnValue = rs.getObject(columnName);
// through the generic put the columnValue to the Class' field
Field userField = clazz.getDeclaredField(columnName);
userField.setAccessible(true);
userField.set(t, columnValue);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs, ps, conn);
}
return t;
}
我靠,你這寫的甚么東西啊? 我就問你你還記得反射和泛型嗎?
反射:
我們在編譯時不能肯定創建甚么對象,只能在運行的時候創建,而運行時怎樣創建呢?昨天還是前天的博客,我提到過1個思惟分散:描寫數據的數據叫元數據,描寫注解的注解叫元注解,描寫類的類叫??? 汗,我也不知道該叫甚么了,但是的確存在1個類來描寫1個已編譯(已加載?我不肯定,回頭看看)的類。而通過這個類我們可以取得它描寫類的任何信息,包括創建對象和給屬性設置值。我后面會總結1篇反射。現在該知道了吧?
由于要寫成通用的,
1.我們不能肯定返回值是甚么類型的對象,我們使用了泛型。
2.對象的屬性個數,甚么類型你一樣不知道,我們使用反射,和多態參數來解決這個問題。
這里出現了個這東西:ResultSetMetaData 它是用來描寫ResultSet的,我們知道ResultSet存的是1張數據表,而ResultSetMetaData就是用來描寫這張表的,包括他有幾列,每列是甚么。
現在讀我這個程序是否是感覺好多了? 也不過如此么!!!
從具體到1般,我們上面寫的僅僅是查詢1條記錄的。
來,再來1個查詢多條記錄的通用的方法:
/**
* PreparedStatement : getAll
* @param sql
* @param clazz
* @param args
* @return
*/
public <T> List<T> getAll(String sql, Class<T> clazz, Object ... args){
List<T> list = new ArrayList<T>();
// get connection
Connection conn = null;
// get PreparedStatement
PreparedStatement ps = null;
// execute the sql
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
// set the ps
for(int i = 0; i < args.length; i++){
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
// get the columnNum
ResultSetMetaData rsmd = rs.getMetaData();
int columnNum = rsmd.getColumnCount();
// read the rs and write to an object
while(rs.next()){
T t = clazz.newInstance();
for(int i = 1; i <= columnNum; i++){
// read
String columnName = rsmd.getColumnLabel(i);
Object columnVal = rs.getObject(columnName);
// write
// through the field(reflect)
//Field field = clazz.getDeclaredField(columnName);
//field.setAccessible(true);
//field.set(t, columnVal);
// through the method(reflect)
PropertyUtils.setProperty(t, columnName, columnVal);
}
list.add(t);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs, ps, conn);
}
return list;
}
來了,相比于上1個來講,這個其實很簡單了,只不過是增加了1個List<T>
集合來寄存對象,獲得連接和關閉的話,你看看我的進化1就明白了。還有就是使用了Apache提供的工具類,但是我個人還是希望你能自己寫寫,以后實際項目使用的時候再用工具類會好1點。
這里來講預編譯:
預編譯指令唆使了在程序正式編譯前就由編譯器進行的操作,我認為這個編譯固然不是Java中的編譯了,而是將它放到了數據庫中,在數據庫中進行預編譯,你仔細想一想是否是這樣?這個該說說內存了,數據庫在內存中會有庫池,->數據緩沖區,->日志緩沖區,1條簡單的Select語句發送到數據庫要經歷1個硬解析的進程,硬解析->檢查->履行計劃,然后到庫池,再到數據庫緩沖池。對普通的Statement語句發送的Sql語句,它每次都要履行這個進程。而PreparedStatement則不同,它被編譯過的語句會被緩存下來,下次調用有相同的預編譯語句就不會重新進行編譯(即上面那個進程),將參數傳入就會履行。
通過上面1段話:有產生了1個新的東西:
批量處理:
3個方法:addBatch()1個裝載的進程,executeBatch()履行的進程,clearBatch()清空緩沖的數據。
具體的履行進程和上面的類似,我會在效力的比較中給出具體的例子。
這些說完了,我們來測試測試他和Statement的效力,不然你們還以為我騙你們,說PreparedStatement效力高。
我同時向數據庫中插入100000條記錄為例
statement:
@Test
public void testStatement() {// 260111
long start = System.currentTimeMillis();
Connection conn = null;
Statement statm = null;
try {
conn = JDBCUtils.getConnection();
statm = conn.createStatement();
for (int i = 0; i < 100000; i++) {
String sql = "insert into emp1 values(" + i + ", 'emp" + i
+ "')";
statm.executeUpdate(sql);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(statm, conn);
}
long end = System.currentTimeMillis();
System.out.println("the time is :" + (end - start));
}
履行時間:
260111ms
PreparedStatement:
@Test
public void testPreparedStatment() {// 141991
long start = System.currentTimeMillis();
// get connection
Connection conn = null;
// get PreparedStatement's object
PreparedStatement ps = null;
try {
conn = JDBCUtils.getConnection();
String sql = "insert into emp1 values(?, ?)";
ps = conn.prepareStatement(sql);
for (int i = 0; i < 100000; i++) {
ps.setInt(1, i + 1);
ps.setString(2, "emp" + i);
ps.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(null, ps, conn);
}
long end = System.currentTimeMillis();
System.out.println("the time is :" + (end - start));
}
141991ms 快了接近1倍
再看看批量處理:
Statement:
@Test
public void testStatement2() {// 271924
long start = System.currentTimeMillis();
Connection conn = null;
Statement statm = null;
try {
conn = JDBCUtils.getConnection();
statm = conn.createStatement();
for (int i = 0; i < 100000; i++) {
String sql = "insert into emp1 values(" + i + ", 'emp" + i
+ "')";
statm.addBatch(sql);
if ((i % 250) == 0) {
statm.executeBatch();
statm.clearBatch();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(statm, conn);
}
long end = System.currentTimeMillis();
System.out.println("the time is :" + (end - start));
}
271924ms 好慢
PreparedStatement:
接下來就是見證奇跡的時刻!!!
@Test
public void testPreparedStatement2() {// 3230
long start = System.currentTimeMillis();
// get connection
Connection conn = null;
// get PreparedStatement's object
PreparedStatement ps = null;
try {
conn = JDBCUtils.getConnection();
String sql = "insert into emp1 values(?, ?)";
ps = conn.prepareStatement(sql);
for (int i = 0; i < 100000; i++) {
ps.setInt(1, i + 1);
ps.setString(2, "emp" + i);
ps.addBatch();
if ((i % 250) == 0) {
ps.executeBatch();
ps.clearBatch();
}
}
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(null, ps, conn);
}
long end = System.currentTimeMillis();
System.out.println("the time is :" + (end - start));
}
3230ms 甚么???這么快!!!
我就甚么也不多說了,繼續吧!!
我們接下來講事務,我還是分開寫吧,這個太長了,不好瀏覽。沒耐心的觀眾已坐不住了!!
補充1點:大數據處理,這里只提供代碼示例,可以參考著去研究研究
大數據的讀取:
@Test
public void get(){
String sql = "select * from customers where id = ?";
Connection conn = null;
PreparedStatement ps = null;
InputStream is = null;
ResultSet rs = null;
OutputStream os = null;
try {
// get connection
conn = JDBCUtils.getConnection();
// get PreparedStatement's object
ps = conn.prepareStatement(sql);
ps.setInt(1, 22);
// execute the ps
rs = ps.executeQuery();
while(rs.next()){
int id = rs.getInt(1);
String name = rs.getString(2);
String email = rs.getString(3);
Date birth = rs.getDate(4);
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
Blob blob = rs.getBlob(5);
is = blob.getBinaryStream();
os = new FileOutputStream("1.jpg");
byte[] b = new byte[1024];
int len = 0;
while((len = is.read(b)) != -1){
os.write(b, 0, len);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
JDBCUtils.close(rs, ps, conn);
}
}
大數據的寫入:
public int insertBlob() {
String sql = "insert into customers values(?, ?, ?, ?, ?)";
// get connection
Connection conn = null;
// get PreparedStatement's object
PreparedStatement ps = null;
// execute ps
int rows = 0;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, 22);
ps.setString(2, "lisi");
ps.setString(3, "lisi@abc.com");
ps.setDate(4,new Date(new java.util.Date().getTime()));
ps.setBlob(5, new FileInputStream("089.jpg"));
rows = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(ps, conn);
}
return rows;
}