在JDBC應用中,如果你已經是稍有水平開發者,你就應該始終以PreparedStatement代替Statement.也就是說,在任何時候都不要使用Statement.
基于以下的原因:
一.代碼的可讀性和可維護性.
雖然用PreparedStatement來代替Statement會使代碼多出幾行,但這樣的代碼無論從可讀性還是可維護性上來說.都比直接用Statement的代碼高很多檔次:
stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();
不用我多說,對于第一種方法.別說其他人去讀你的代碼,就是你自己過一段時間再去讀,都會覺得傷心.
二.PreparedStatement盡最大可能提高性能.
每一種數據庫都會盡最大努力對預編譯語句提供最大的性能優化.因為預編譯語句有可能被重復調用.所以語句在被DB的編譯器編譯后的執行代碼被緩存下來,那么下次調用時只要是相同的預編譯語句就不需要編譯,只要將參數直接傳入編譯過的語句執行代碼中(相當于一個涵數)就會得到執行.這并不是說只有一個 Connection中多次執行的預編譯語句被緩存,而是對于整個DB中,只要預編譯的語句語法和緩存中匹配.那么在任何時候就可以不需要再次編譯而可以直接執行.而statement的語句中,即使是相同一操作,而由于每次操作的數據不同所以使整個語句相匹配的機會極小,幾乎不太可能匹配.比如:
insert into tb_name (col1,col2) values ('11','22');
insert into tb_name (col1,col2) values ('11','23');
即使是相同操作但因為數據內容不一樣,所以整個個語句本身不能匹配,沒有緩存語句的意義.事實是沒有數據庫會對普通語句編譯后的執行代碼緩存.這樣每執行一次都要對傳入的語句編譯一次.
當然并不是所以預編譯語句都一定會被緩存,數據庫本身會用一種策略,比如使用頻度等因素來決定什么時候不再緩存已有的預編譯結果.以保存有更多的空間存儲新的預編譯語句.
三.最重要的一點是極大地提高了安全性.
即使到目前為止,仍有一些人連基本的惡義SQL語法都不知道.
String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";
如果我們把[' or '1' = '1]作為varpasswd傳入進來.用戶名隨意,看看會成為什么?
select * from tb_name = '隨意' and passwd = '' or '1' = '1';
因為'1'='1'肯定成立,所以可以任何通過驗證.更有甚者:
把[';drop table tb_name;]作為varpasswd傳入進來,則:
select * from tb_name = '隨意' and passwd = '';drop table tb_name;有些數據庫是不會讓你成功的,但也有很多數據庫就可以使這些語句得到執行.
而如果你使用預編譯語句.你傳入的任何內容就不會和原來的語句發生任何匹配的關系.(前提是數據庫本身支持預編譯,但上前可能沒有什么服務端數據庫不支持編譯了,只有少數的桌面數據庫,就是直接文件訪問的那些)只要全使用預編譯語句,你就用不著對傳入的數據做任何過慮.而如果使用普通的statement, 有可能要對drop,;等做費盡心機的判斷和過慮.
上面的幾個原因,還不足讓你在任何時候都使用PreparedStatement嗎?
有的新人可能此時對于用法還不太理解下面給個小例子
Code Fragment 1:
String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′";
stmt.executeUpdate(updateString);
Code Fragment 2:
PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
updateSales.setInt(1, 75);
updateSales.setString(2, "Colombian");
updateSales.executeUpdate();
set中的1對應第一個? 2對應第二個? 同時注意你set 的類型 是int還是string 哈哈很簡單吧
原文出處:http://blog.csdn.net/spcusa/archive/2009/05/09/4164076.aspx
//ValueObject類
public class AdEntity {
private String id;
private String name;
private String broker;
private String date;
private String body;
//get/set

public String toString(){
return "【編號為:"+id+",廣告名稱為:"+name+",代理商為:"+broker+",發布日期為:"+date+",內容為:"+body+"】";
}
}
//調用任務類
public class AdTask implements Callable<AdEntity> {
@Override
public AdEntity call() throws Exception {
// 模擬遠程調用花費的一些時間
Thread.sleep(5*1000);
AdEntity vo=new AdEntity();
vo.setId(String.valueOf(new Random().nextInt(1000)));
vo.setName("Ad@內衣廣告");
vo.setBroker("CHANNEL");
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
String dateStr=sdf.format(date);
vo.setDate(dateStr);
vo.setBody("遠端內容");
return vo;
}
}
//主函數
public class TestQueryMemg {
/**
* @param args
* @throws ExecutionException
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService exec=Executors.newCachedThreadPool();
//創建Future來完成網絡調用任務
Callable<AdEntity> returnAd=new AdTask();
Future<AdEntity> future=exec.submit(returnAd);
//開始執行本地化查詢信息
AdEntity localAd=new AdEntity();
localAd.setId(String.valueOf(new Random().nextInt(1000)));
localAd.setName("Ad@食品廣告");
localAd.setBroker("蒙牛");
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
String dateStr=sdf.format(new Date());
localAd.setDate(dateStr);
localAd.setBody("內容本地");
System.out.println("當前本地化查詢內容為:"+localAd.toString());
System.out.println("稍等片刻以獲取遠端信息
");
while(!future.isDone()){
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
AdEntity entityRemote=(AdEntity)future.get();
System.out.println("合并查詢內容為:\n"+localAd.toString()+"\n"+entityRemote.toString());
}
}
個人賬戶類:
public class PrivateAccount implements Callable {
Integer total;
public Object call() throws Exception {
Thread.sleep(5*1000);
total=new Integer(new Random().nextInt(10000));
System.out.println("您個人賬戶上還有"+total+" 存款可以支配");
return total;
}
}
主函數測試:
public class SumTest {
/**
* @param args
* @throws ExecutionException
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable privateAccount=new PrivateAccount();
FutureTask task=new FutureTask(privateAccount);
//創建新線程獲取個人賬戶信息
Thread thread=new Thread(task);
thread.start();
int total=new Random().nextInt(1000);
System.out.println("主線程在這工作
");
System.out.println("您目前操作金額為: "+total+" .");
System.out.println("請等待計算個人賬戶的金額
");
while(!task.isDone()){//判斷是否已經獲取返回值
try {
Thread.sleep(3*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Integer privateSingle=(Integer)task.get();
int post=privateSingle.intValue();
System.out.println("您當前賬戶共有金額為:"+(total+post)+" ¥");
}
}
Memcached,人所皆知的remote distribute cache(不知道的可以javaeye一下下,或者google一下下,或者baidu一下下,但是鑒于baidu的排名商業味道太濃(從最近得某某事件可以看出),所以還是建議javaeye一下下),使用起來也非常的簡單,它被用在了很多網站上面,幾乎很少有大型的網站不會使用memcached。
曾經我也看過很多剖析memcached內部機制的文章,有一點收獲,但是看過之后又忘記了,而且沒有什么深刻的概念,但是最近我遇到一個問題,這個問題迫使我重新來認識memcache,下面我闡述一下我遇到的問題
問題:我有幾千萬的數據,這些數據會經常被用到,目前來看,它必須要放到memcached中,以保證訪問速度,但是我的memcached中數據經常會有丟失,而業務需求是memcached中的數據是不能丟失的。我的數據丟失的時候,memcached server的內存才使用到60%,也就是還有40%內存被嚴重的浪費掉了。但不是所有的應用都是這樣,其他應用內存浪費的就比較少。為什么內存才使用到60%的時候LRU就執行了呢(之所以確定是LRU執行是因為我發現我的數據丟失的總是前面放進去的,而且這個過程中,這些數據都沒有被訪問,比如第一次訪問的時候,只能訪問第1000w條,而第300w條或者之前的數據都已經丟失了,從日志里看,第300w條肯定是放進去了)。
帶著這些疑問,我開始重新審視memcached這個產品,首先從它的內存模型開始:我們知道c++里分配內存有兩種方式,預先分配和動態分配,顯然,預先分配內存會使程序比較快,但是它的缺點是不能有效利用內存,而動態分配可以有效利用內存,但是會使程序運行效率下降,memcached的內存分配就是基于以上原理,顯然為了獲得更快的速度,有時候我們不得不以空間換時間。
也就是說memcached會預先分配內存,對了,memcached分配內存方式稱之為allocator,首先,這里有3個概念:
1 slab
2 page
3 chunk
解釋一下,一般來說一個memcahced進程會預先將自己劃分為若干個slab,每個slab下又有若干個page,每個page下又有多個chunk,如果我們把這3個咚咚看作是object得話,這是兩個一對多得關系。再一般來說,slab得數量是有限得,幾個,十幾個,或者幾十個,這個跟進程配置得內存有關。而每個slab下得page默認情況是1m,也就是說如果一個slab占用100m得內存得話,那么默認情況下這個slab所擁有得page得個數就是100,而chunk就是我們得數據存放得最終地方。
舉一個例子,我啟動一個memcached進程,占用內存100m,再打開telnet,telnet localhost 11211,連接上memcache之后,輸入stats slabs,回車,出現如下數據:
- STAT 1:chunk_size 80
- STAT 1:chunks_per_page 13107
- STAT 1:total_pages 1
- STAT 1:total_chunks 13107
- STAT 1:used_chunks 13107
- STAT 1:free_chunks 0
- STAT 1:free_chunks_end 13107
- STAT 2:chunk_size 100
- STAT 2:chunks_per_page 10485
- STAT 2:total_pages 1
- STAT 2:total_chunks 10485
- STAT 2:used_chunks 10485
- STAT 2:free_chunks 0
- STAT 2:free_chunks_end 10485
- STAT 3:chunk_size 128
- STAT 3:chunks_per_page 8192
- STAT 3:total_pages 1
- STAT 3:total_chunks 8192
- STAT 3:used_chunks 8192
- STAT 3:free_chunks 0
- STAT 3:free_chunks_end 8192
STAT 1:chunk_size 80
STAT 1:chunks_per_page 13107
STAT 1:total_pages 1
STAT 1:total_chunks 13107
STAT 1:used_chunks 13107
STAT 1:free_chunks 0
STAT 1:free_chunks_end 13107
STAT 2:chunk_size 100
STAT 2:chunks_per_page 10485
STAT 2:total_pages 1
STAT 2:total_chunks 10485
STAT 2:used_chunks 10485
STAT 2:free_chunks 0
STAT 2:free_chunks_end 10485
STAT 3:chunk_size 128
STAT 3:chunks_per_page 8192
STAT 3:total_pages 1
STAT 3:total_chunks 8192
STAT 3:used_chunks 8192
STAT 3:free_chunks 0
STAT 3:free_chunks_end 8192
以上就是前3個slab得詳細信息
chunk_size表示數據存放塊得大小,chunks_per_page表示一個內存頁page中擁有得chunk得數量,total_pages表示每個slab下page得個數。total_chunks表示這個slab下chunk得總數(=total_pages * chunks_per_page),used_chunks表示該slab下已經使用得chunk得數量,free_chunks表示該slab下還可以使用得chunks數量。
從上面得示例slab 1一共有1m得內存空間,而且現在已經被用完了,slab2也有1m得內存空間,也被用完了,slab3得情況依然如此。 而且從這3個slab中chunk得size可以看出來,第一個chunk為80b,第二個是100b,第3個是128b,基本上后一個是前一個得1.25倍,但是這個增長情況我們是可以控制得,我們可以通過在啟動時得進程參數 –f來修改這個值,比如說 –f 1.1表示這個增長因子為1.1,那么第一個slab中得chunk為80b得話,第二個slab中得chunk應該是80*1.1左右。
解釋了這么多也該可以看出來我遇到得問題得原因了,如果還看不出來,那我再補充關鍵的一句:memcached中新的value過來存放的地址是該value的大小決定的,value總是會被選擇存放到chunk與其最接近的一個slab中,比如上面的例子,如果我的value是80b,那么我這所有的value總是會被存放到1號slab中,而1號slab中的free_chunks已經是0了,怎么辦呢,如果你在啟動memcached的時候沒有追加-M(禁止LRU,這種情況下內存不夠時會out of memory),那么memcached會把這個slab中最近最少被使用的chunk中的數據清掉,然后放上最新的數據。這就解釋了為什么我的內存還有40%的時候LRU就執行了,因為我的其他slab中的chunk_size都遠大于我的value,所以我的value根本不會放到那幾個slab中,而只會放到和我的value最接近的chunk所在的slab中(而這些slab早就滿了,郁悶了)。這就導致了我的數據被不停的覆蓋,后者覆蓋前者。
問題找到了,解決方案還是沒有找到,因為我的數據必須要求命中率時100%,我只能通過調整slab的增長因子和page的大小來盡量來使命中率接近100%,但是并不能100%保證命中率是100%(這話怎么讀起來這么別扭呢,自我檢討一下自己的語文水平),如果您說,這種方案不行啊,因為我的memcached server不能停啊,不要緊還有另外一個方法,就是memcached-tool,執行move命令,如:move 3 1,代表把3號slab中的一個內存頁移動到1號slab中,有人問了,這有什么用呢,比如說我的20號slab的利用率非常低,但是page卻又很多,比如200,那么就是200m,而2好slab經常發生LRU,明顯page不夠,我就可以move 20 2,把20號slab的一個內存頁移動到2號slab上,這樣就能更加有效的利用內存了(有人說了,一次只移動一個page,多麻煩啊?ahuaxuan說,還是寫個腳本,循環一下吧)。
有人說不行啊,我的memcache中的數據不能丟失啊,ok,試試新浪的memcachedb吧,雖然我沒有用過,但是建議大家可以試試,它也使利用memcache協議和berkeleyDB做的(寫到這里,我不得不佩服danga了,我覺得它最大的貢獻不是memcache server本身,而是memcache協議),據說它被用在新浪的不少應用上,包括新浪的博客。
補充,stats slab命令可以查看memcached中slab的情況,而stats命令可以查看你的memcached的一些健康情況,比如說命中率之類的,示例如下:
- STAT pid 2232
- STAT uptime 1348
- STAT time 1218120955
- STAT version 1.2.1
- STAT pointer_size 32
- STAT curr_items 0
- STAT total_items 0
- STAT bytes 0
- STAT curr_connections 1
- STAT total_connections 3
- STAT connection_structures 2
- STAT cmd_get 0
- STAT cmd_set 0
- STAT get_hits 0
- STAT get_misses 0
- STAT bytes_read 26
- STAT bytes_written 16655
- STAT limit_maxbytes 104857600
STAT pid 2232
STAT uptime 1348
STAT time 1218120955
STAT version 1.2.1
STAT pointer_size 32
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 1
STAT total_connections 3
STAT connection_structures 2
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT bytes_read 26
STAT bytes_written 16655
STAT limit_maxbytes 104857600
從上面的數據可以看到這個memcached進程的命中率很好,get_misses低達0個,怎么回事啊,因為這個進程使我剛啟動的,我只用telnet連了一下,所以curr_connections為1,而total_items為0,因為我沒有放數據進去,get_hits為0,因為我沒有調用get方法,最后的結果就是misses當然為0,哇哦,換句話說命中率就是100%,又yy了。
該到總結的時候了,從這篇文章里我們可以得到以下幾個結論:
結論一,memcached得LRU不是全局的,而是針對slab的,可以說是區域性的。
結論二,要提高memcached的命中率,預估我們的value大小并且適當的調整內存頁大小和增長因子是必須的。
結論三,帶著問題找答案理解的要比隨便看看的效果好得多。