Java Web效能優化之一:減少DAO層的呼叫次數

小燈光環發表於2015-11-14



前言



很簡單的一個問題,一個業務方法,需要先查詢一次得到結果(select),然後再根據查詢的結果進行一次更新(update),通常情況下我們會在DAO層定義兩個介面,一個介面實現查詢,一個介面實現更新,在service層呼叫2次。當然還有一種解決方案,就是僅在DAO層定義一個介面,通過update set select 這種語法去做,這樣呼叫一次就可以完成更新,那麼實際情況我們選用哪一種高效呢?根據我的經驗應該是第二種,但有人說盡量不要寫子查詢,那我就具體寫個test case測試一下效率問題。



Test



package zhsz_service;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.Log4jConfigurer;

import cn.zhsz.service.dao.evaluation.impl.EvaluationDaoImpl;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:/spring/spring_servlet.xml",
		"classpath*:/spring/spring_config.xml" })
@WebAppConfiguration
public class MainTest {

	private static Log logger = LogFactory.getLog(MainTest.class);// 日誌
	
	@Resource(name="evaluationDao")
	private EvaluationDaoImpl evaluationDao;

	@Test
	public void testOne() throws Exception {
		long beforeCurrentTimeMillis = System.currentTimeMillis();
		float score =this.evaluationDao.findScoreByOid(10);
		boolean result = this.evaluationDao.updateOptionScoreByQid("123",5, score);
		long afterCurrentTimeMillis = System.currentTimeMillis();
		logger.error("testOne用時"+ (afterCurrentTimeMillis - beforeCurrentTimeMillis) + "毫秒!");
	}

	@Test
	public void testTwo() throws Exception {
		long beforeCurrentTimeMillis = System.currentTimeMillis();
		boolean result = this.evaluationDao.updateOptionScoreByQid("123",5, 9);
		long afterCurrentTimeMillis = System.currentTimeMillis();
		logger.error("testTwo用時"+ (afterCurrentTimeMillis - beforeCurrentTimeMillis) + "毫秒!");
	}

	@Before
	public void setUp() throws Exception {
		Log4jConfigurer.initLogging("classpath:log4j.properties");
	}

	@After
	public void tearDown() throws Exception {

	}

}



可以看到,testOne呼叫了2次DAO層,分別是下面這兩條sql:

	<select id="findScoreByOid" parameterType="int" resultType="float">
		select score from t_evaluation_options where id = #{oid}
	</select>

	<update id="updateOptionScoreByQid2" parameterType="Map">
		update t_self_evaluation set score = #{score}
   		   where stu_number = #{stuNum} and question_id = #{qid}
	</update>

而testTwo只呼叫了1次DAO層,但sql是帶子查詢的update set select語法:

	<update id="updateOptionScoreByQid" parameterType="Map">
		update t_self_evaluation set score = 
  		 (select score from t_evaluation_options where id = #{oid}) 
   		   where stu_number = #{stuNum} and question_id = #{qid}
	</update>

現在分別執行這兩個方法8次後得到8組測試介面,最後觀察一下log4j輸出在檔案中的除錯資訊:

2015-11-14 12:25:41  [ main:2529 ] - [ ERROR ]  testOne用時578毫秒!
2015-11-14 12:25:51  [ main:1483 ] - [ ERROR ]  testTwo用時156毫秒!


2015-11-14 12:26:00  [ main:1469 ] - [ ERROR ]  testOne用時172毫秒!
2015-11-14 12:26:07  [ main:1452 ] - [ ERROR ]  testTwo用時172毫秒!


2015-11-14 12:26:17  [ main:1530 ] - [ ERROR ]  testOne用時235毫秒!
2015-11-14 12:26:25  [ main:1484 ] - [ ERROR ]  testTwo用時156毫秒!


2015-11-14 12:26:33  [ main:1531 ] - [ ERROR ]  testOne用時173毫秒!
2015-11-14 12:26:41  [ main:1454 ] - [ ERROR ]  testTwo用時171毫秒!


2015-11-14 12:26:49  [ main:1547 ] - [ ERROR ]  testOne用時204毫秒!
2015-11-14 12:26:57  [ main:1498 ] - [ ERROR ]  testTwo用時156毫秒!


2015-11-14 12:27:18  [ main:1468 ] - [ ERROR ]  testOne用時187毫秒!
2015-11-14 12:27:57  [ main:1469 ] - [ ERROR ]  testTwo用時156毫秒!


2015-11-14 12:28:52  [ main:1498 ] - [ ERROR ]  testOne用時202毫秒!
2015-11-14 12:28:59  [ main:1473 ] - [ ERROR ]  testTwo用時156毫秒!


2015-11-14 12:29:24  [ main:1531 ] - [ ERROR ]  testOne用時189毫秒!
2015-11-14 12:29:31  [ main:1469 ] - [ ERROR ]  testTwo用時156毫秒!

可以看到除了第二組用時一樣,其它7組測試資料均是testOne的用時大於testTwo,也就是說即使testTwo的SQL中用了子查詢,依舊比呼叫2次DAO層的效率要高一些。

下面再看一下最後一組測試的完整debug日誌:



首先看一下圖片中的紅色標記,由於TestOne呼叫了兩次DAO,那必然執行了2次SQL,也就是說會有2個PreparedStatement物件,這裡我們用的druid連線池,也就是說會向池裡put兩次,最後再remove兩次,再看一下綠色的標記,可以發現TestOne和TestTwo的效率差距也正是由於兩次put中間的差值,而資料庫的效率差距已經小到無法區分。



總結



所以當我們在寫程式的時候如果遇到複雜的業務方法,可以考慮儘量直接在資料庫層通過複雜sql、function或procedure去處理而避免多次呼叫DAO層的方法,這樣在效率方面應該會有一些提升,如果有不對的地方歡迎批評指正,The End。


相關文章