日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ssm知识点总结

發布時間:2025/3/21 编程问答 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ssm知识点总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

項目名稱:教育網—在線調查系統

項目總體流程圖:

設計調查:調查-->包裹--->問題(增刪改查)

1.調整包裹順序

2.移動復制包裹

3.深度刪除

創建調查流程分析:

主要生成survey_id、survey_name、completed(是否完成)、logoPath(涉及到圖片上傳)

?

?springMVC文件上傳:

文件上傳對表單的要求 ①form標簽的enctype屬性:multipart/form-data ②form標簽的method屬性:post ③生成文件上傳框:input type="file"


文件的保存
①調用multiPartFile.transfer()方法
②文件的路徑不能使用絕對的物理路徑
<img src="E:\good.jpg"/>
這樣的路徑瀏覽器無法顯示圖片
③有效的路徑形式


<img src="surveyLogos/logo.gif"/>


這個路徑有效是因為它是一個虛擬路徑。
④虛擬路徑VS真實物理路徑
[1]真實物理路徑:Web應用中的文件和目錄在硬盤上保存的真實路徑(注意:這里指的是部署目錄)。


D:\WorkSpaceShenZhen170228\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\Survey_1_UI\surveyLogos\logo.gif


瀏覽器不能直接訪問這個路徑,所以需要由服務器將它轉換為瀏覽器可以訪問的虛擬路徑
Web應用在不同的操作系統下、在不同的服務器上部署時真實物理路徑是有可能變化的。

[2]虛擬路徑:服務器虛擬出來供瀏覽器訪問的路徑,以主機地址為基準的


http://localhost:8080/Survey_1_UI/surveyLogos/logo.gif


不管Web應用部署在什么操作系統的什么服務器上,虛擬路徑都是相同的。

⑤在handler方法中保存文件時如何將文件保存到img標簽可以訪問的路徑下


[1]保存文件的目標路徑一定在部署目錄下
[2]部署目錄會隨著部署的服務器、操作系統不同而發生變化
[3]所以要通過不變的虛擬路徑動態生成有可能變化的真實物理路徑


String 真實物理路徑 = servletContext.getRealPath(虛擬路徑);


⑥壓縮圖片


[1]直接復制一個工具方法resizeImages()
[2]兩個需要手動導入的API
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import com.sun.image.codec.jpeg.JPEGCodec;


[3]傳入的參數
inputStream:上傳文件的輸入流
realPath:/surveyLogos目錄的真實路徑,后面沒有斜杠,而且不帶具體文件名
[4]返回值:可以直接用于設置Survey對象的logoPath屬性

文件數據的驗證

①驗證的內容

[1]文件的大小 [2]文件的類型 ②實現方式 [1]檢測用戶是否上傳了文件 [2]獲取相關數據:文件大小、文件內容類型 [3]如果檢測到大小或類型不符合要求,則拋出對應的異常 分頁顯示我未完成的調查 ①分頁支持:MyBatis插件PageHelper ②要查詢的數據:Survey對象 [1]限制條件1:當前用戶 [2]限制條件2:未完成 ③SurveyMapper.selectAllSurvey(userId,completed);

考慮到將來也會查詢所有已完成的調查,所以userId和completed都需要傳入

?

更新操作的特殊要求: [1]用戶沒有上傳文件時保持舊的logo_path字段值不變 [2]用戶如果上傳了文件那么就將logo_path字段值修改為新值 [3]用戶如果上傳了不符合要求的圖片要回到更新調查的表單頁面并顯示錯誤消息 [4]回到更新調查的表單頁面顯示錯誤消息時要保證表單上模型數據回顯正常 [5]更新完成后回到分頁頁面,且回到的是之前所在的頁碼
[6]文件上傳驗證失敗后,再正常更新還是能夠回到之前所在的分頁頁面

包裹的CRUD

包裹的序號默認采用包裹的id

?原理:通過mybatis的xml映射文件獲取自增主鍵獲取包裹的序號,如果采用插入后查詢id最大值賦值給Order會因為線程問題出錯。

[1]錯誤的做法
  • 保存bag對象
  • 查詢guest_bag表中bag_id的最大值
  • 使用這個最大值設置bag_order
[2]為什么是錯誤的?在并發的情況下,假設有T1和T2兩個線程
  • T1:保存bag對象(bag_id的最大值是6)
  • T2:保存bag對象(bag_id的最大值是7)
  • T1:查詢最大值,得到的結果:7
  • T1:設置bag_order為7就錯了
  • T2……
[3]正確的做法
  • T1:保存bag對象,立即獲取剛剛自增產生的bag_id——6
  • T2:保存bag對象,立即獲取剛剛自增產生的bag_id——7
  • T1:使用已經獲取到的自增主鍵值設置bag_order為6
  • T2:使用已經獲取到的自增主鍵值設置bag_order為7

③獲取自增主鍵值的方式以及相關UPDATE語句

useGeneratedKeys="true"?keyProperty="bagId"
update guest_bag set bag_order=#{bagId} where bag_id=#{bagId}

創建問題的流程分析

?

難點:將選項轉化為json進行處理。

對選項進行特殊處理的四個方法

DataprocessUtils.processOptionToJson(Question question);

判斷題型,簡答題不處理 將option字符串根據“\r\n”拆分為數組 借助于工具將數組轉換為JSON字符串 DataprocessUtils.processOptionFromJson(Question question); 判斷題型,簡答題不處理 借助于工具將JSON格式的option字符串還原為List 將List組合成以“\r\n”分開的字符串 Question.getOptionList(); 借助于工具將JSON格式的option字符串還原為List DataprocessUtils.convertJSONToList(String json);

將重復操作提取出來

答案回顯:type1,2,3

?

包裹和問題數據的來源

答案數據存儲的數據結構:

Session

  allBagMap

    根據bagId→paramMap

      根據表單標簽的name屬性值→values數組

        根據values數組進行標簽的回顯

checkbox radio text

?

四個按鈕相關 ①這是四個提交按鈕,而且他們提交的是同一個表單,同一個Handler方法來處理 ②四個按鈕如何區分

<input type="submit"?name="submit_prev"?value="返回上一個包裹"/>

<input type="submit" name="submit_next" value="進入下一個包裹"/>

<input type="submit" name="submit_quit" value="放棄"/>

<input type="submit" name="submit_done" value="完成"/> 點擊任何一個提交按鈕都會將這個提交按鈕的name、value提交給服務器 在Handler方法中檢查請求參數Map中是否存在對應的name值就能夠區分了 boolean contains = parameterMap.containsKey("submit_prev"); if(contains){

//說明用戶點擊的是"返回上一個包裹"

}

③四個按鈕的顯示條件

[1]返回上一個:當前包裹索引>0 [2]進入下一個:當前包裹索引<size-1

size-1實際上就是最后一個包裹的索引

[3]放棄:無條件 [4]完成:當前包裹索引 == size-1

1、使用異常映射機制統一管理項目中錯誤消息

why?

常規的是當不符合業務情況時產生異常信息返回,但容易因為個人書寫代碼的行為習慣導致,編程混亂,

會增加交流的成本,降低開發效率。所以需要采用異常映射機制統一管理錯誤消息。

if(錯誤條件){

  

map.put("message","對不起,這個用戶名已經被占用了,請重新注冊!");

  return "頁面";

}

how?

異常映射機制統一管理項目錯誤信息:

  拿注冊用戶名字存在為例:

?

//已存在則拋出異常
if
(adminCount > 0) {throw new AdminNameExistsException(GlobalMessage.ADMIN_NAME_EXISTS);} AdminNameExistsException是自定義的Exception
public class AdminNameExistsException extends RuntimeException {private static final long serialVersionUID = 1L;public AdminNameExistsException(String message) {super(message);}}

異常映射機制在spring.xml中進行異常映射:映射到相應頁面

<!--簡單異常映射解析器,對于用戶 名存在throw的異常進行映射跳轉到指定視圖 --><bean id="SimpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="exceptionMappings"><!-- key屬性是異常類型 --><!-- 標簽體配置目標視圖 --><props><prop key="com.lamsey.survey.e.UserNameAlreadyExistException">guest/user_regist</prop><prop key="com.lamsey.survey.e.UserLoginFailedException">guest/user_login</prop><prop key="com.lamsey.survey.e.UserAccessForbiddenException">guest/user_login</prop><prop key="com.lamsey.survey.e.FileTypeInvalidForSaveException">guest/survey_addUI</prop><prop key="com.lamsey.survey.e.FileTooLargeForSaveException">guest/survey_addUI</prop><prop key="com.lamsey.survey.e.FileTypeInvalidForEditException">guest/survey_editUi</prop><prop key="com.lamsey.survey.e.FileTooLargeForEditException">guest/survey_editUi</prop><prop key="com.lamsey.survey.e.RemoveSurveyException">error</prop><prop key="com.lamsey.survey.e.RemoveBagException">error</prop><prop key="com.lamsey.survey.e.SurveyWithoutAnyBagException">error</prop><prop key="com.lamsey.survey.e.SurveyHasEmptyBagException">error</prop><prop key="com.lamsey.survey.e.BagOrderDuplicateException">guest/bag_AdjustUI</prop><prop key="com.lamsey.survey.e.AdminLoginFailedException">manager/admin_login</prop><prop key="com.lamsey.survey.e.HasNoAuthorityException">error</prop><prop key="com.lamsey.survey.e.AdminAccessForbiddenException">error</prop></props> </property></bean>

頁面對異常進行捕獲顯示:

<c:if test="${requestScope.exception != null }"><%-- request.setAttribute("exception",exception) --%><%-- request.getAttribute("exception") --%><%-- exception.getMessage() --%><div class="form-group"> ${requestScope.exception.message}</div> </c:if>

?

jsp四大域對象經常用來保存數據信息。

?

pageContext ?????????? 可以保存數據在同一個jsp頁面中使用

request?????????????????????? 可以保存數據在同一個request對象中使用。經常用于在轉發的時候傳遞數據

session?????????????????????? 可以保存在一個會話中使用

application(ServletContext)???? 就是ServletContext對象

?jsp 九大內置對象分別是:

request 對象?? ??? 請求對象,可以獲取請求信息

response 對象??????? 響應對象。可以設置響應信息

pageContext 對象 當前頁面上下文對象。可以在當前上下文保存屬性信息

session 對象??????????? 會話對象。可以獲取會話信息。

exception 對象 ???? 異常對象只有在jsp頁面的page 指令中設置 isErrorPage="true" 的時候才會存在

application 對象???? ServletContext對象實例,可以獲取整個工程的一些信息。

config 對象????????????? ServletConfig對象實例,可以獲取Servlet的配置信息

out 對象?????????????????? 輸出流。

page 對象??????????????? 表示當前Servlet對象實例(無用,用它不如使用this對象)。

九大內置對象,都是我們可以在【代碼腳本】中或【表達式腳本】中直接使用的對象。

2、通過序列化和反序列化技術實現對象的深度復制

為什么用深度復制?

若我們系統中存在大量的對象是通過拷貝生成的,如果我們每一個類都寫一個clone()方法,并將還需要進行深拷貝,新建大量的對象,這個工程是非常大的,

這里我們可以利用序列化來實現對象的拷貝。

復制包裹,

執行深度復制
Bag targetBag = (Bag)DataprocessUtils.deeplyCopy(sourceBag);

?如何利用序列化來完成對象的拷貝呢?

在內存中通過字節流的拷貝是比較容易實現的。把母對象寫入到一個字節流中,再從字節流中將其讀出來,

這樣就可以創建一個新的對象了,并且該新對象與母對象之間并不存在引用共享的問題,真正實現對象的深拷貝。

?

1.首先將數據進行序列化

deeplyCopy(Serializable source)

2.將序列化的數據 /*** 通過序列化和反序列的方式對對象進行深度復制*///深克隆: 具有相同的值,但是兩個全新的對象實例,相互之間不會受影響// 被復制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。// 那些引用其他對象的變量將指向被復制過的新對象,而不再是原有的那些被引用的對象。public static Object deeplyCopy(Serializable source){if(source == null) {return null;}//1.聲明一個變量用來保存復制得到的目標對象Object targetObject = null;//2.聲明四個變量用來保存四個流ObjectInputStream ois =null;ObjectOutputStream oos = null;ByteArrayInputStream bais = null;ByteArrayOutputStream baos = null;//3.try...catch...finally結構try{//4.創建字節數組輸出流baos = new ByteArrayOutputStream();//5.根據字節數組輸出流創建對象輸出流oos = new ObjectOutputStream(baos);//6.執行對象的序列化操作(本質:將對象序列化后得到的數據寫入字節數組) oos.writeObject(source);//7.獲取保存了序列化數據的字節數組byte[] byteArray = baos.toByteArray();//8.創建字節數組輸入流bais = new ByteArrayInputStream(byteArray);//9.根據字節數組輸入流創建對象輸入流ois = new ObjectInputStream(bais);//10.執行反序列化操作targetObject = ois.readObject();}catch(Exception e){e.printStackTrace();} finally{//11.釋放資源if(oos != null){try{oos.close();}catch(Exception e){e.printStackTrace();}}if(ois != null){try{ois.close();}catch(Exception e){e.printStackTrace();}}} return targetObject;}

?

?

3、使用pageHelper對商品結果進行分頁瀏覽功能

why?

普通的sql語句分頁:

limit x,y;

#x:起始數據行,y:要查詢的數據行

SELECT last_name,salary FROM employees ORDER BY salary DESC #分頁 (寫在order by的后面) #limit 0,10; LIMIT 20,10;#21-30段數據:第3頁 #公式:limit (pageNo - 1) * pageSize , pageSize;

使用普通分頁太麻煩了,利用mybatis的pageHelper插件會更容易操作。

how?

public PageInfo<Survey> getSurveyPage(Integer userId, boolean completed, Integer pageNum) {//設置每頁顯示數量int pageSize = 5;PageHelper.startPage(pageNum, pageSize);//執行分頁查詢List<Survey> list = surveyMapper.selectAllSurvey(userId, completed);//用PageInfo對結果進行包裝int navigatePages = 6;PageInfo<Survey> page = new PageInfo<>(list, navigatePages);return page;}

?4.JFreeChart將選擇題的答案數據導出為餅圖

why?

JFreeChart是JAVA平臺上的一個開放的圖表繪制類庫。它完全使用JAVA語言編寫,是為applications, applets, servlets 以及JSP等使用所設計。

JFreeChart可生成餅圖(pie charts)、柱狀圖(bar charts)、散點圖(scatter plots)、時序圖(time series)、甘特圖(Gantt charts)等等

多種圖表,并且可以產生PNG和JPEG格式的輸出,還可以與PDF和EXCEL關聯。

因為要對每道題統計數據,所以采用餅狀圖進行顯示每道選擇題的結果。簡答題

how?

?

@RequestMapping(value="manager/statistics/showAnswerChart/{questionId}",method=RequestMethod.GET)public void showAnswerChart(@PathVariable(value="questionId") Integer questionId,HttpServletResponse response) throws IOException{//1.調用Service方法生成JFreeChart對象JFreeChart chart = statisticsService.getChart(questionId);//2.將JFreeChart對象生成的圖表圖片返回給瀏覽器//通過response對象獲取一個能夠給瀏覽器返回數據的輸出流ServletOutputStream outputStream = response.getOutputStream();//借助ChartUtilities工具類的方法將圖表數據寫入到上面獲取的輸出流ChartUtilities.writeChartAsJPEG(outputStream, chart, 1200, 600);//③當前Handler方法通過上面的輸出流已經能夠給瀏覽器明確的響應數據,所以不再前往任何一個視圖//所以沒有任何返回值}

JFreeChart對象的創建

public JFreeChart getChart(Integer questionId) {//獲取題目數據Question question = questionMapper.selectByPrimaryKey(questionId);int count = answerMapper.selectQuestionEngagedCount(questionId);//獲取圖例區數據List<String> optionList = question.getOptionList();//獲取標簽區數據Map<String, Object> map = new HashMap<>();for(int index= 0;index < optionList.size();index++){//(1)option作為標簽名String option = optionList.get(index);//(2)index結合questionId查詢optionEngagedCountString optionValue = "%," + index + ",%";int optionCount = answerMapper.SelectOptionEngagedCount(questionId,optionValue);map.put(option, optionCount);}String title = question.getQuestionName()+count+"次參與";Object chart = DataprocessUtils.generateChart(title, map);return (JFreeChart) chart;}     //通過response對象獲取一個能夠給瀏覽器返回數據的輸出流ServletOutputStream outputStream = response.getOutputStream();//借助ChartUtilities工具類的方法將圖表數據寫入到上面獲取的輸出流ChartUtilities.writeChartAsJPEG(outputStream, chart, 1200, 600);
統計答案中的數據,
SELECT COUNT(*) FROM guest_answer WHERE question_id = 19 AND CONCAT(",", answer_content, ",") LIKE '%,1,%' 1)分析采用字符串,所以采用like來進行匹配 SELECT COUNT(*) FROM guest_answer WHERE question_id = 19 AND answer_content ?LIKE '%1%' 但這樣查詢存在一個問題,如存在10或者21也會統計進去,會導致特殊問題。 2)更改為?LIKE '%,1,%'會使前后沒有逗號的匹配不到,造成遺漏 所以在查詢選項前先加上兩個逗號 SELECT COUNT(*) FROM guest_answer WHERE question_id = 19 AND CONCAT(",", answer_content, ",") LIKE '%,1,%' answer_context

總結:首先創建JFreeChart對象(title,各個選項的count存進map里面),然后借助ChartUtilities工具類的方法將圖表數據寫入文件到指定目的地

?創建response的outPutStream進行輸出到瀏覽器

5.使用POI匯總數據,并將整個調查參與的結果導出為Excel表格

why?

為了將所有調查問卷的數據進行收集

how?

①POI技術本身 [1]數據→Excel [2]Excel→數據

②項目中將數據導出為Excel的數據來源

③羅列所需要的數據 [1]從URL地址中匹配surveyId [2]根據surveyId深度加載Survey對象 [3]根據Survey對象中的包裹、問題數據創建List<Question> [4]根據surveyId查詢所有答案數據:List<Answer> [5]根據surveyId查詢surveyEngagedCount

④生成Excel文件所需要的數據的要求

?

?

⑤符合要求的數據結構

?

  /*** 導出excel表* @throws IOException */@RequestMapping(value="manager/survey/exportExcel/{surveyId}",method=RequestMethod.GET)public void exportExcel(@PathVariable(value="surveyId") Integer surveyId,HttpServletResponse response) throws IOException{//1.生成excel對象HSSFWorkbook workbook = statisticsService.getWorkBook(surveyId);//2.將Excel文件以下載形式返回給瀏覽器//i.設置響應數據的內容類型response.setContentType("application/vnd.ms-excel");//ii.生成文件名String filename = System.nanoTime()+".xls";//iii.在響應消息頭中設置文件名response.setHeader("Content-Disposition", "attachment;filename="+filename);//iv.獲取一個能夠給瀏覽器返回二進制數據的輸出流ServletOutputStream outputStream = response.getOutputStream();//v.將workbook對象寫入這個輸出流 workbook.write(outputStream);} //1.生成excel對象 public HSSFWorkbook getWorkBook(Integer surveyId) throws FileNotFoundException {//1.獲取數據//2.建表HSSFWorkbook workbook = new HSSFWorkbook();//獲取表名//獲取題目數據,構建excel表名Survey survey = surveyMapper.getSurveyDeeply(surveyId);String surveyName = survey.getSurveyName();int count = answerMapper.getSurveyEngagedCount(surveyId);String sheetName = surveyName+"共有"+count+"調查";HSSFSheet sheet = workbook.createSheet(sheetName);//iv.如果surveyEngagedCount被參與的次數為零,則停止函數執行if(count == 0) {return workbook;} //創建首行,包括行標題//1.遍歷所有題目填進第一行LinkedHashSet<Bag> bagSet = survey.getBagSet();List<Question> questionList = new ArrayList<>();for(Bag bag:bagSet){LinkedHashSet<Question> questionSet = bag.getQuestionSet();//把set轉化為List方便索引一一取出 questionList.addAll(questionSet);}//填寫首行HSSFRow firstRow = sheet.createRow(0);for(int i=0;i<questionList.size();i++){Question question = questionList.get(i);String questionName = question.getQuestionName();HSSFCell cell = firstRow.createCell(i);cell.setCellValue(questionName); }//填充所有行答案數據//查出所有批次的answerContext//answerContext必須要與questionId一一對應//uuid questionId answerContext//[4]根據surveyId查詢所有答案數據:List<Answer>List<Answer> answerList = answerMapper.selectAnswerListBySurveyId(surveyId);//2.轉換數據格式Map<String, Map<Integer, String>> bigMap = getBigMap(answerList); //填充答案行//按照questionList中一一查出的id對smallMap進行取值,從而一一對應//v.從bigMap中獲取values部分Collection<Map<Integer,String>> values = bigMap.values();//vi.將values轉換為List集合List<Map<Integer,String>> smallMapList = new ArrayList(values);//遍歷smallMapList//Map<uuid, Map<questionId, answerContext>> bigMap//uuid-->對應一行的questionId,所以uuid的數目為行(即smallMapList.size()),以questionId遍歷question單元格for(int i=0;i<smallMapList.size();i++){//獲取第一個Map<Integer, String> smallMap = smallMapList.get(i);//viii.這里注意:i控制行索引int rowIndex = i + 1;//ix.根據rowIndex創建行HSSFRow row = sheet.createRow(rowIndex);//x.創建具體單元格for(int j=0;j<questionList.size();j++){HSSFCell cell = row.createCell(j);//xi.以j為索引從questionList中獲取Question對象Question question = questionList.get(j);//xii.從Question對象中獲取questionIdInteger questionId = question.getQuestionId();//xiii.以questionId為鍵從smallMap中獲取對應的答案內容String context = smallMap.get(questionId); //xiv.用content設置當前單元格內容 cell.setCellValue(context);}}return workbook;}

把所有答案內容進行處理:

//根據answerList將數據轉換為適合生成Excel表的形式//一個uuid對應一套的questionId,所以smallMap中的questionId只要相同就要賦值給一樣的smallMap元素//不停創建map,得到不同的地址,相同的uuid的smallMap指向同一個地址private Map<String, Map<Integer, String>> getBigMap(List<Answer> answerList) {//1.創建空的bigMapMap<String,Map<Integer,String>> bigMap = new HashMap<>();//2.遍歷answerList,在遍歷過程中解析Answer對象的數據存入bigMap for(int i=0;i<answerList.size();i++){Answer answer = answerList.get(i);String uuid = answer.getUuid();Integer questionId = answer.getQuestionId();String context = answer.getAnswerContext();//3.先嘗試從bigMap中獲取smallMap,因為answer中有很多重復的uuid//避免重復創建Map<Integer, String> smallMap = bigMap.get(uuid);if(smallMap==null){//4.smallMap如果為null,說明這是此前沒有創建過對應的smallMapsmallMap = new HashMap<>();//5.將創建好的smallMap存入bigMap,下次再通過同樣的uuid獲取就不會是null了 bigMap.put(uuid, smallMap);}//6.將數據存入smallMap smallMap.put(questionId, context);}return bigMap;}

關鍵點:

創建bigMap-->smallMap得到

String context = smallMap.get(questionId); 按照questionList中一一查出的id對smallMap進行取值,從而一一對應

總結:創建 HSSFWorkbook 建表

1).對每一行進行填充,第一行填充題目: 構建questionList,list有索引,后面進行答案填充時可以利用索引找到對應的答案 for(Bag bag:bagSet){LinkedHashSet<Question> questionSet = bag.getQuestionSet();//把set轉化為List方便索引一一取出questionList.addAll(questionSet);}//填寫首行HSSFRow firstRow = sheet.createRow(0);for(int i=0;i<questionList.size();i++){Question question = questionList.get(i); String questionName = question.getQuestionName(); HSSFCell cell = firstRow.createCell(i); cell.setCellValue(questionName); }

2).填充答案

for(int i=0;i<smallMapList.size();i++){//獲取第一個Map<Integer, String> smallMap = smallMapList.get(i);//viii.這里注意:i控制行索引int rowIndex = i + 1;//ix.根據rowIndex創建行HSSFRow row = sheet.createRow(rowIndex);//x.創建具體單元格for(int j=0;j<questionList.size();j++){HSSFCell cell = row.createCell(j); //xi.以j為索引從questionList中獲取Question對象 Question question = questionList.get(j); //xii.從Question對象中獲取questionId Integer questionId = question.getQuestionId(); //xiii.以questionId為鍵從smallMap中獲取對應的答案內容 String context = smallMap.get(questionId); //xiv.用content設置當前單元格內容 cell.setCellValue(context); } }

6、使用Spring提供的緩存抽象機制整合EHCache為項目提供二級緩存

why?

為了減輕數據庫的負擔,每次加載調查問卷時可以進行緩存。

適合作為緩存的條件:

1.經常查詢

2.可以容忍偶爾的并發問題

3.不會被其他應用修改

Survey項目中適合存入二級緩存的數據

EngageService.PageInfo<Survey> getSurveyPage(Integer userId, boolean completed, Integer pageNum); EngageService.Survey getSurveyDeeply(Integer surveyId); how? 工作原理:偽代碼 try{//1.查詢緩存value = getCache(key);//2.如果緩存不存在if(value==null){//3.查詢數據庫value=dao.select();//4.設置緩存      setCache(key,value);} //5.返回查詢值return value; } catch(Exception e){} 使用步驟 ①創建鍵生成器類,實現org.springframework.cache.interceptor.KeyGenerator接口 需要在Spring配置文件中配置對應的bean (也可以采用默認的生成方案) ②引入EHCache環境 [1]加入jar包 [2]引入EHCache自身的配置文件,同時創建一個具名的緩存區域 ③在Spring配置文件中配置緩存抽象對EHCache的整合 配置EhCacheManagerFactoryBean? 配置EhCacheCacheManager 切面及切面表達式配置(帥選出需要緩存的方法) <!-- spring 整合ehcache --><!-- 自定義key生成器 --><bean id="userKeyGenerator" class="com.lamsey.survey.Ehcache.UserKeyGenerator"/><!-- 配置 EhCacheManagerFactoryBean工廠--><bean id="ehCacheManagerFactoryBean" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" ><property name="configLocation" value="classpath:ehcache.xml"></property></bean><!-- 配置EhCacheCacheManager --><bean id="ehCacheCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" ><property name="cacheManager" ref="ehCacheManagerFactoryBean"></property></bean><!--切面及切面表達式配置 --><aop:config><!-- 利用切面表達式找到切面切入點,進行切面編程 --><aop:pointcut expression="execution(* *..ResService.getResByServletPath(String)) or execution(* *..AnswerService.getSurveyPage(Integer, boolean, Integer)) or execution(* *..AnswerService.getSurveyDeeply(Integer)) or execution(* *..SurveyService.completedSurvey(Integer))" id="cachePointCut" /><!-- 承上啟下,得到切入點,同時連接處理的方法。對切入點進行處理(cache) --><!-- 緩存切面優先級高于數據庫事務切面優先級 --><aop:advisor advice-ref="cacheAdvice" pointcut-ref="cachePointCut" order="1"/></aop:config> <!-- 對切入點進行處理,這里表現為緩存 --><!-- 這里的自定義key【className.method.param1..paramn】 --><cache:advice id="cacheAdvice" cache-manager="ehCacheCacheManager" key-generator="userKeyGenerator"><!-- 在cache屬性中指定緩存區域的名稱 --><!-- 指定要使用緩存的具體方法,要求必須是緩存切入點覆蓋范圍內的方法 --><cache:caching cache="surveyCache"> <cache:cacheable method=" getResByServletPath" /><cache:cacheable method="getSurveyDeeply"/></cache:caching><!-- 使用另外一個有可能被清空數據的緩存區域 --><cache:caching cache="surveyCacheEvicable"> <cache:cacheable method="getSurveyPage" /><!-- 執行updateSurveyCompleted方法時清空當前緩存區域 --><!-- 因為調查有可能更新,當更新后就需要進行重新獲取參與調查 ,所以清空該緩存--><cache:cache-evict method="completedSurvey" all-entries="true" /></cache:caching> </cache:advice> 為了減少不必要的事務操作讓緩存切面的優先級高于事務切面的優先級。 7.使用石英調度創建定時任務在每月固定時間自動創建日志表,以實現日志數據分流 why? 因為需要利用數據庫記錄一些日志信息

因為是記錄到數據庫,考慮到殺雞不用牛刀。所以采用aop進行日志記錄

manager_log表
  • log_id
  • log_operator
  • log_operate_time
  • method_name
  • method_type
  • input_data
  • output_data
  • exception_type
  • exception_message

利用切面來記錄日志:

環繞通知,記錄用戶操作信息等(利用ThreadLocal產生request)

環繞通知:一個完整的try...catch...finally結構 在切面的通知方法中獲取HttpSession對象 ①基本思路

?

[1]創建一個負責綁定、移除、獲取request對象的線程本地化類 [2]在一個專門的攔截器中執行綁定和移除操作 [3]在切面類的通知方法中獲取前面綁定的request對象 ③注意事項:在RequestBinder類中應該以靜態方式調用方法,保證local對象是單例的 /*** 日志記錄儀* @author Administrator**/ @Component @Aspect public class LogRecord {@AutowiredLogService logService;@Around("execution(* *..*Service.update*(..)) || execution(* *..*Service.remove*(..))||execution(* *..*Service.regist(..))||execution(* *..*Service.save*(..)) && !execution(* com.lamsey.survey.component.service.m.LogServiceImpl.*(..))" )public Object recordLog(ProceedingJoinPoint joinPoint){String logOperator=null;String logOperateTime=null,methodName=null,methodType=null,inputData=null,outputData=null,exceptionType=null,exceptionMessage=null;Object returnValue =null;//獲取調用目標方法時的實參數組//調用目標方法try {//獲取目標方法簽名Signature signature = joinPoint.getSignature();//簽名中獲取方法類型屬于的類,接口methodType = signature.getDeclaringTypeName();//獲取方法的名字methodName = signature.getName();//輸入的參數Object[] args = joinPoint.getArgs();if(args.length>0 && args!=null){List<Object> list = Arrays.asList(args);inputData = list.toString();} else{inputData="沒有輸入的參數";}// returnValue = joinPoint.proceed(args);// } catch (Throwable e) {//將捕獲到的目標方法異常繼續向上拋出 e.printStackTrace();//異常的類型及信息Throwable cause = e.getCause();if(cause!=null){//獲取異常原因的類型exceptionType = cause.getClass().getName();cause = cause.getCause();} exceptionMessage = e.getMessage(); } finally{//時間logOperateTime = new SimpleDateFormat("yyyy年MM月dd日hh:mm:ss").format(new Date());//outputValueif(returnValue!=null){outputData = returnValue.toString();} else{outputData ="無有效的輸出數據";}}//收集當前登錄的用戶信息//創建TreadLocal,從該變量中當前線程上獲取request對象:獲取sessionHttpServletRequest request = SysContent.getRequest(); HttpSession session = request.getSession();Admin admin=(Admin) session.getAttribute(GlobalNames.LOGIN_ADMIN);User user = (User) session.getAttribute(GlobalNames.LOGIN_USER);String adminPart = (admin==null)?"admin沒有登陸":admin.getAdminName();String userPart = (user==null)?"user沒有登陸":user.getUserName();//logOperatorlogOperator = adminPart + "/" + userPart;//將產生的信息存進日志數據庫logService.saveLog(new Log(null, logOperator, logOperateTime, methodName, methodType,inputData, outputData, exceptionType, exceptionMessage));//將目標方法返回的數據繼續返回給上層調用的方法return returnValue; } }

?

?

.在IOC容器中配置切面類 ①配置切面類對應的bean ②配置日志切面的切入點表達式 (execution(* *..*Service.update*(..)) or execution(* *..*Service.remove*(..)) or execution(* *..*Service.regist(..)) or execution(* *..*Service.save*(..))) and !bean(logServiceImpl) ③整體配置方式 <!-- 配置日志切面 --> <bean?id="logRecorder"?class="com.atguigu.survey.log.aspect.LogRecorder"/> <!-- 配置日志切面切入點表達式 --> <aop:config> <aop:pointcut?expression="(execution(* *..*Service.update*(..)) or execution(* *..*Service.remove*(..)) or execution(* *..*Service.regist(..)) or execution(* *..*Service.save*(..))) and !bean(logServiceImpl)"?id="logPointCut"/> <!-- 配置切面的通知方法 --> <aop:aspect?id="logAspect"?ref="logRecorder"> <aop:around?method="recordLog"?pointcut-ref="logPointCut"/> </aop:aspect> </aop:config> ④無限死循環的問題 保存日志的方法本身也要記錄日志,從而導致無限死循環 userService.regist(...) logService.saveLog(...) logService.saveLog(...) logService.saveLog(...) logService.saveLog(...) …… 石英時鐘創建表: 建表注意事項: 1)提前建好未來三個月(包括下個月)的表,當月建立來不及。 2)因為日志表增長太快,所以采用水平分庫,不斷進行保存。同時創立一個單獨的數據庫進行日志保存。 3)初始部署時前先創建數據表 配置Spring監聽器在IOC容器啟動時執行建表操作,創建當月的日志表。 Spring監聽器在IOC容器啟動時除了建當月的表還要建后三個月的表 定時任務 ①在固定時間執行固定操作。 ②石英調度(Quartz)是實現定時任務的其中一種方式。 ③石英調度和Spring整合思路

?1.)工作bean配置

工作bean:創建Quartz任務類:繼承org.springframework.scheduling.quartz.QuartzJobBean

?2.)配置石英任務觸發器(克龍表達式)

<property name="cronExpression" value="0 0 0 15 * ? *"></property>

?3.)配置任務調度工廠Bean

<!-- 注冊監聽器 ,保證一啟動就創建三張表--><bean id="createTableListener" class="com.lamsey.survey.log.listener.CreateTableListener"></bean>

?

<!--========= Quartz石英時鐘====== --><!-- 工作的bean --><bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailBean"><!--CreateTable的bean由 JobDetailBean創建,不是ioc容器創建,所以logServiceImpl需要注意 --><property name="jobClass" value="com.lamsey.survey.log.quartz.CreateTable" ></property><property name="jobDataMap"><map><!-- 特殊配置:裝配logService --><entry key="logService" value-ref="logServiceImpl"></entry></map></property></bean> <!-- 配置石英任務觸發器 --> <bean id="cronTriggerFactoryBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"><property name="jobDetail" ref="jobDetailBean"></property><property name="cronExpression" value="0 0 0 15 * ? *"></property></bean><!-- 設置日程表 --><!-- 配置任務調度工廠Bean --><bean id="startQuertz" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"><property name="triggers"><list><ref bean="cronTriggerFactoryBean"/></list></property></bean>

?7.使用路由器數據源實現數據庫操作在主數據庫和日志數據庫之間的切換

多數據源使用路由器數據源管理然后再裝配

?

路由器數據源:抽象類AbstractRoutingDataSource

③可以參照的例子 org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter ④細節追問 [1]路由器數據源是如何管理那么多具體數據源的? Map/鍵值對形式 [2]路由器數據源為什么能夠代替具體的數據源裝配給SqlSessionFactoryBean和事務管理器? SqlSessionFactoryBean和事務管理器要的數據源都是javax.sql.DataSource類型的 所有的路由器數據源都必須繼承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

所以要想實現數據庫操作的切換,需要實現抽象路由數據源

①創建自定義路由器數據源類:繼承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource ②實現抽象方法:determineCurrentLookupKey() 從當前線程上獲取key信息 將key信息從線程上移除 將key信息作為返回值返回 ③在Spring配置文件中配置自定義路由器數據源 以鍵值對形式指定所有目標數據源 指定默認數據源——在determineCurrentLookupKey()返回null時使用 ④將自定義路由器數據源裝配給SqlSessionFactoryBean和事務管理器 ⑤在有需要的service方法前執行線程綁定:訪問日志數據庫的操作 Spring監聽器建表 石英任務建表 保存日志信息 分頁查詢日志數據 ※注意:因為每次用完后key信息需要從線程上移除,所以哪怕是同一個線程每一個具體操作前也需要重復設置 ※注意:自動建表時的SQL需要參照主數據庫的manager_log或將manager_log復制到日志數據庫 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import com.lamsey.survey.log.thread.NMRoutingToken; /*** 路由器數據源切換實現* @author Administrator**/ public class NMRoutingDataSource extends AbstractRoutingDataSource{@Overrideprotected Object determineCurrentLookupKey() {//獲取當前線程的令牌NMRoutingToken token = NMRoutingToken.getCurrentToken();if (token != null) {String dataSourceName = token.getDataSourceName();//將key從當前線程上移除 NMRoutingToken.unbindToken();return dataSourceName;}return null;} }

?

<!--2.配置數據源 --><!-- 垂直分庫,log庫另外存儲,所以需要采用路由數據源 --><context:property-placeholder location="classpath:dbconfig.properties"/><bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="user" value="${prop.user}"></property><property name="password" value="${prop.password}"></property><property name="jdbcUrl" value="${prop.jdbcUrl}"></property><property name="driverClass" value="${prop.driverClass}"></property></bean><bean id="logDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="user" value="${log.user}"></property><property name="password" value="${log.password}"></property><property name="jdbcUrl" value="${log.jdbcUrl}"></property><property name="driverClass" value="${log.driverClass}"></property></bean><!-- 實現了抽現類AbstractRoutingDataSource的類 --><bean id="nMRoutingDataSource" class="com.lamsey.survey.log.router.NMRoutingDataSource"><property name="targetDataSources"><map><!-- 當輸入log時,調用 logDataSource數據庫--><entry key="LOG_DATA_SOURCE_KEY" value-ref="logDataSource"></entry></map></property><property name="defaultTargetDataSource" ref="comboPooledDataSource"/> </bean>

?

?

?

?

轉載于:https://www.cnblogs.com/limingxian537423/p/7684718.html

總結

以上是生活随笔為你收集整理的ssm知识点总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。