spring單元測試集錦
參考資料
普通查詢效能測試
本測試目的在於摸清單純的資料庫查詢的效能以及併發數量。
測試程式碼為java程式碼呼叫儲存過程然後成功返回查詢資料
儲存過程:
CREATE OR REPLACE FUNCTION "sp_common_region_getRegionPath"("id" int4) RETURNS varchar AS $BODY$ declare tmpId integer; declare needGoing boolean; declare loopIndex integer; declare selected_result record; declare json_result varchar; declare tmp_array json[]; declare real_array json[]; declare tmp_json_obj json; declare arr_length integer; begin tmpId:=id; needGoing:=true; loopIndex:=0; tmp_array:=array[]::json[]; real_array:=array[]::json[]; perform * from common_region cr where cr.id=tmpId; <<labelLoop4Lv1>> while needGoing loop if FOUND then needGoing:=true; <<labelLoop4Lv2>> for selected_result in select * from common_region cr where cr.id=tmpId loop select row_to_json(cr) into tmp_json_obj from common_region cr where cr.id=tmpId; -- raise notice 'tmp_json_obj is:%',tmp_json_obj; tmp_array:=array_prepend(tmp_json_obj,tmp_array); tmpId:=selected_result.parent_id; loopIndex:=loopIndex+1; end loop labelLoop4Lv2; else needGoing:=false; raise notice '注意,這裡沒有發現任何記錄。'; if loopIndex=0 then return ''; else exit labelLoop4Lv1; end if ; end if; end loop labelLoop4Lv1; return array_to_json(tmp_array); end; $BODY$ LANGUAGE 'plpgsql' VOLATILE ;
java端呼叫程式碼:
@Service public class RegionService { @Autowired JdbcTemplate jdbcTemplate; public List<Region> getPath(Integer id){ int theParamIndex=0; List<Region> paths=new ArrayList<>(); paths=jdbcTemplate.execute(new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection con) throws SQLException { String sql="{ call \"sp_common_region_getRegionPath\"(?,null)}"; sql="{ call \"sp_common_region_getRegionPath\"(?)}"; CallableStatement st=con.prepareCall(sql); int paraIndex=1; st.setInt(paraIndex,id); return st; } },new CallableStatementCallback<List<Region>>(){ @Override public List<Region> doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { List<Region> res=new ArrayList<>(); cs.execute(); ResultSet rs=(ResultSet)cs.getResultSet(); if (rs.next()){ String json_str=rs.getString(1); if(ValidateUtils.isEmpty(json_str)){ return res; } res=JSONObject.parseArray(json_str,Region.class); } rs.close(); cs.getConnection().setAutoCommit(true); return res; } }); return paths; } }
/** * @author MR white * @version 2.00 */ public class Region { public Long id=0L; public void setId(Long id){ this.id=id; } public Long getId(){ return this.id; } public Long parent_id=0L; public void setParent_id(Long parent_id){ this.parent_id=parent_id; } public Long getParent_id(){ return this.parent_id; } public String name=""; public void setName(String name){ this.name=name; } public String getName(){ return this.name; } public Integer level=0; public void setLevel(Integer level){ this.level=level; } public Integer getLevel(){ return this.level; } public String code=""; public void setCode(String code){ this.code=code; } public String getCode(){ return this.code; } public String pingyin=""; public void setPingyin(String pingyin){ this.pingyin=pingyin; } public String getPingyin(){ return this.pingyin; } public String name_en=""; public void setName_en(String name_en){ this.name_en=name_en; } public String getName_en(){ return this.name_en; } public void setNULL(){ this.id=null; this.parent_id=null; this.name=null; this.level=null; this.code=null; this.pingyin=null; this.name_en=null; } public void resetDefaultVal(){ this.id=0L; this.parent_id=0L; this.name=""; this.level=0; this.code=""; this.pingyin=""; this.name_en=""; } }
java端測試程式碼:
public class RegionTester extends BaseTest {
@Autowired
private RegionService regionService;
@Test
public void singleThreadGetPath(){
List<Region> list=regionService.getPath(5600);
System.out.println(JSONObject.toJSONString(list));
}
@Test
public synchronized void multiThreadGetPath() throws InterruptedException {
runMultiThreadTest(250, new Runnable() {
@Override
public void run() {
Double rnd = (Math.random() * 10000);
int rndId = rnd.intValue() % 5000;
List<Region> list = regionService.getPath(rndId);
System.out.println(JSONObject.toJSONString(list));
}
});
}
}
注意,basetest是經過定製的,為了能夠同時執行多個執行緒而得到結果,加了一點東西,程式碼如下:
package main;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration({"classpath:spring-mvc.xml","classpath:spring-mybatis.xml"})
@ContextConfiguration(locations={
"classpath*:applicationContext.xml",
"classpath*:spring/spring-mvc.xml"}
)
public class BaseTest {
Date beginDate;
Long begin=null;
Long end=null;
Date endDate=null;
@Before
public void init() {
//--初始化spring上下文
System.out.println("初始化spring上下文中......");
//在執行測試之前的業務程式碼
beginDate = new Date();
begin = beginDate.getTime();
System.out.println("任務開始時間:" + beginDate);
}
@After
public void after() {
//在測試完成之後的業務程式碼
endDate = new Date();
end = endDate.getTime();
System.out.println("任務結束時間:" + endDate + "");
System.out.println("任務話費時間:" + (end - begin) + "毫秒");
}
public void runMultiThreadTest(int runThreadCount,Runnable runnable){
{
final int threadSize=runThreadCount;
ExecutorService executor= Executors.newFixedThreadPool(threadSize);
final AtomicInteger lockCount=new AtomicInteger(threadSize);
final AtomicInteger successCount=new AtomicInteger(0);
try {
for (int i = 0; i < threadSize; i++) {
final int theThreadNumber = i;
executor.submit(new Runnable() {
@Override
public void run() {
try {
runnable.run();
successCount.incrementAndGet();
}
catch (Exception ed){
ed.printStackTrace();
}
finally {
lockCount.decrementAndGet();
}
}
});
}
while(true){
synchronized (this){
if(lockCount.intValue()>0){
;
}
else{
break;
}
}
}
System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
}
catch (Exception ed){
ed.printStackTrace();
}
}
}
}
單執行緒的測試就算了。。測不出什麼東西。現在直接執行多執行緒的測試方法,然後可以看到。。
一堆報錯。
可以看到。。成功的執行緒就只有10個。。而我們的併發數量是250,可見250這個併發也是很恐怖的—不過引起這個的原因是druid的資料庫連線池的最大數量設定為10,下面我們設定一下併發為50然後再看看結果:
啟動時候留意一下pgadmin4,檢視一下資料庫連線,可以發現,
除了第一個是pgadmin拿來連線之外,其他的在資料庫連線池初始化的時候就已經事先獲得連線了。
而程式碼執行結果為:
還是會報錯,拿不到連線,不過併發成功數量已經升到66個了,而不是50個----按照邏輯成功的應該是50的,最多。。不過,每個執行緒獲取連線的時候估計會先等幾毫米,假如這時候一個任務查詢成功釋放連線的話,那麼也會獲得連線從而執行程式的。
對了,附加執行完成之後的pgadmin的監控圖:
可見程式結束之後連線都釋放了。
接下來進行多次測試,務求獲得比較準確的效能概況。
併發太高的話無論如何一些請求都是會出錯的,這樣不行。
無論如何,客戶端發起的請求都必須完成,無論是250同時來的請求還是1000個,單鑑於單機的硬體都是有限的,無法無限提升連線數量,所以,下面提出嘗試一種解決方案,就是用阻塞佇列進行處理。
阻塞佇列執行任務效能測試
好了,併發數量太大的話多執行緒的執行都成問題了,現在改為阻塞佇列來試試,下面要改改baseTest的程式碼,新增阻塞佇列的測試方法。
package main;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration({"classpath:spring-mvc.xml","classpath:spring-mybatis.xml"})
@ContextConfiguration(locations={
"classpath*:applicationContext.xml",
"classpath*:spring/spring-mvc.xml"}
)
public class BaseTest {
Date beginDate;
Long begin=null;
Long end=null;
Date endDate=null;
@Before
public void init() {
//--初始化spring上下文
System.out.println("初始化spring上下文中......");
//在執行測試之前的業務程式碼
beginDate = new Date();
begin = beginDate.getTime();
System.out.println("任務開始時間:" + beginDate);
}
@After
public void after() {
//在測試完成之後的業務程式碼
endDate = new Date();
end = endDate.getTime();
System.out.println("任務結束時間:" + endDate + "");
System.out.println("任務話費時間:" + (end - begin) + "毫秒");
}
/***用於測試最大併發***/
public void runMultiThreadTest(int runThreadCount,Runnable runnable){
{
final int threadSize=runThreadCount;
ExecutorService executor= Executors.newFixedThreadPool(threadSize);
final AtomicInteger lockCount=new AtomicInteger(threadSize);
final AtomicInteger successCount=new AtomicInteger(0);
try {
for (int i = 0; i < threadSize; i++) {
final int theThreadNumber = i;
executor.submit(new Runnable() {
@Override
public void run() {
try {
runnable.run();
successCount.incrementAndGet();
}
catch (Exception ed){
ed.printStackTrace();
}
finally {
lockCount.decrementAndGet();
}
}
});
}
while(true){
synchronized (this){
if(lockCount.intValue()>0){
;
}
else{
break;
}
}
}
System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
}
catch (Exception ed){
ed.printStackTrace();
}
}
}
/***用於測試任務阻塞的任務佇列執行下的效能***/
public void runMultiThreadByBlockQueue(int threadCount,TaskProducer producer,TaskConsumer consumer){
final LinkedBlockingQueue<TaskOutLine> queue=new LinkedBlockingQueue<>(threadCount);
final int threadSize=threadCount;
ExecutorService executor= Executors.newFixedThreadPool(threadSize);
final AtomicInteger lockCount=new AtomicInteger(threadSize);
final AtomicInteger successCount=new AtomicInteger(0);
try {
/***執行緒池同時產生任務佇列***/
for (int i = 0; i < threadSize; i++) {
final int theThreadNumber = i;
TaskOutLine tmpOutLine=new TaskOutLine();
tmpOutLine.taskIndex=theThreadNumber;
executor.submit(new Runnable() {
@Override
public void run() {
try {
tmpOutLine.taskData=producer.produce();
queue.put(tmpOutLine);
}
catch (Exception ed){
ed.printStackTrace();
}
finally {
}
}
});
}
/***另起一個執行緒用於消費佇列**/
new Thread(new Runnable() {
@Override
public void run() {
while (lockCount.get()>0){
try{
TaskOutLine currentObj=queue.take();
consumer.consume(currentObj);
successCount.incrementAndGet();
}
catch (Exception ed){
}
finally {
lockCount.decrementAndGet();
}
}
}
}).start();
while(true){
synchronized (this){
if(lockCount.intValue()>0){
;
}
else{
break;
}
}
}
System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
}
catch (Exception ed){
ed.printStackTrace();
}
}
public interface TaskProducer{
public Object produce();
}
public interface TaskConsumer{
public void consume(TaskOutLine taskOutLine);
}
public class TaskOutLine{
public int taskIndex=0;
public Object taskData=new Object();
public int getTaskIndex() {
return taskIndex;
}
public void setTaskIndex(int taskIndex) {
this.taskIndex = taskIndex;
}
public Object getTaskData() {
return taskData;
}
public void setTaskData(Object taskData) {
this.taskData = taskData;
}
}
}
然後在測試檔案新增程式碼:
@Test
public synchronized void multiThreadGetPathByQueue() throws InterruptedException {
runMultiThreadByBlockQueue(250, new TaskProducer() {
@Override
public Object produce() {
Double rnd = (Math.random() * 10000);
Integer rndId = rnd.intValue() % 5000;
return rndId;
}
}, new TaskConsumer() {
@Override
public void consume(TaskOutLine outLine) {
TaskOutLine resOutLine=outLine;
Integer rndId=(Integer)resOutLine.taskData;
List<Region> list = regionService.getPath(rndId);
System.out.println("任務序號:"+outLine.taskIndex);
System.out.println(JSONObject.toJSONString(list));
}
});
}
好了,多次執行看看效能如何:
可以看到,併發再多也是能夠處理完畢的,就是耗費的時間有點多。
要5到6秒。按照第一種效能的測試來看。。50個併發的耗時是500ms到600ms,那麼,一批一批完成250個任務的話,時間應該在2500ms到3000ms,所以阻塞佇列可以處理完所有請求,防止併發過多而崩潰,但並不是效能的最優解。
下面將提出效能更好的方案。
任務分批提交
還記得執行緒池的設定嗎?執行緒池可以設定最大執行緒執行數量的,也就是說,我們可以修改成為每次可以處理的最大併發數,那麼修改一下程式碼:
baseTest
ount, int threadPoolSize, Runnable runnable){
{
final int threadSize= totalThreadCount;
ExecutorService executor= Executors.newFixedThreadPool(threadPoolSize);
final AtomicInteger lockCount=new AtomicInteger(threadSize);
final AtomicInteger successCount=new AtomicInteger(0);
try {
for (int i = 0; i < threadSize; i++) {
final int theThreadNumber = i;
executor.submit(new Runnable() {
@Override
public void run() {
try {
runnable.run();
successCount.incrementAndGet();
}
catch (Exception ed){
ed.printStackTrace();
}
finally {
lockCount.decrementAndGet();
}
}
});
}
while(true){
synchronized (this){
if(lockCount.intValue()>0){
;
}
else{
break;
}
}
}
System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
}
catch (Exception ed){
ed.printStackTrace();
}
}
}
然後,呼叫例子是這樣的:
@Test
public synchronized void multiThreadGetPath() throws InterruptedException {
runMultiThreadTest(250,25, new Runnable() {
@Override
public void run() {
Double rnd = (Math.random() * 10000);
int rndId = rnd.intValue() % 5000;
List<Region> list = regionService.getPath(rndId);
System.out.println(JSONObject.toJSONString(list));
}
});
}
我們分別設定執行緒池的最大數量為25,30,40,觀察一下效率變化。
最大執行緒數量25:
最大執行緒數量30:
對了,在baseTest裡面新增一下說明,例如:
這樣就能得到最大執行緒數量了。
40的如下:
嗯。。。耗費時間=應用伺服器耗時+資料庫伺服器耗時+資料傳輸耗時
即使應用伺服器已經開多了執行緒不過時間看來沒有多大的優化,估計已經到了資料庫伺服器的極限了。