一個案例帶你入門SSM框架開發

SSM框架快速入門javascript

關於SSM框架環境搭建,請點擊這裏前往個人博客:SSM框架整合之環境搭建 因爲本項目採用了maven,關於IDEA搭建maven項目過程請點擊這裏前往個人博客:maven起步前端

項目源碼請 點擊進入 個人GitHub。java

<!--more-->mysql

關於項目

項目環境

項目框架:後端:spring+mybatis+springmvc; 前端:bootstrap+Font Awesome圖標集
測試環境:IDEA + tomcat8 + mysql5.7 + jdk8 + maven
數據庫名稱:ssm

項目功能

1. 實現用戶登陸功能
2. 實現客戶信息的增刪改查功能
3. 實現分頁查詢功能

項目結構

備註 如上是一個標準的maven項目,咱們來解釋一下各個目錄:jquery

img: 放了一些README.md文檔所須要得圖片,沒有實際意義。git

controller: web層,存放springmvc的相關控制器類。github

mapper: 存放接口和映射文件。由於本項目採用了mybatis的接口開發,因此須要將接口和映射文件放在同一目錄下,而且名稱相同。web

pojo: 存放Java的實體類。ajax

service: 業務層,用於存放一些業務層代碼。算法

不要奇怪爲何沒有出現Dao層,由於本項目相對簡單,並無多複雜的邏輯,因此也就必要再寫一個Dao層進行擴展。

resources: 是maven項目存放配置文件的根目錄,在本例中包含兩個子文件夾:resourcespring。前者是存放一些如logback.properties的配置文件;後者是存放spring的配置文件(spring和springmvc)。

my.sql: 存放了關於項目數據庫建立和表建立的SQL語句。

fonts: 存放一些字體的配置文件。爲了頁面的美感,咱們採用了Awesome圖標集。

lib: 包含了項目中用到的一些前端類庫。

page: 包含全部前端頁面。

整合思路

繼上一篇博文:Spring MVC起步其實咱們已經瞭解瞭如何整合Spring和Spring MVC框架。那麼,接下來咱們就須要瞭解如何在此基礎上整合Mybatis框架。 首先須知Mybatis框架是一個持久層框架,而Spring MVC是WEB層框架,Spring框架則充當着業務層的角色。那麼將三者聯繫起來正好組成了web--service--dao的三層架構體系。那麼整合思路就以下所示了:

  1. 整合dao(即mapper),實現Mybatis和Spring的整合
  2. 整合service,由Spring管理service接口,在service中能夠調用dao(mapper)
  3. 整合web(controller),實現Spring MVC和Spring的整合,在controller中調用service

需求實現

1. 實現用戶登陸功能

1.1 建立表結構

SQL語句請查看GitHub中resources目錄下的.sql文件 除了建立表,咱們一樣要建立pojo對象,並提供屬性setter和getter方法。(注意儘可能保持pojo屬性參數和表字段名稱對應相等)

1.2 編寫Controller層

@RequestMapping(value = "/login")
public String login(@RequestParam String username,@RequestParam String password, Model model) {
    User user = userService.login(username);
    if (user != null) {
        if (user.getPassword().equals(password)) {
            //登陸成功
            return "page/page";
        } else {
            model.addAttribute("message", "登陸失敗");
            return "page/loginInfo";
        }
    } else {
        model.addAttribute("message", "你輸入的用戶名或密碼有誤");
        return "page/loginInfo";
    }
}

@RequestMapping標註login()方法有兩個做用(前提是必須在XML中開啓註解掃描<context:component-scan/>):1.表示該方法是請求處理方法;2.指明瞭該方法的請求路徑。@RequestMapping能夠標記類或方法,分別表示了不一樣層級的請求路徑。例如當前的login()方法的請求路徑應爲:localhost:8080/xxx/login.do 對於請求體中包含多個參數的狀況,咱們儘可能用@RequestParam標記參數,以避免出現未知錯誤(但這不是必須的)。 用戶登陸,咱們首先獲取到用戶登陸的用戶名username和密碼password,而後根據用戶名查詢並返回,根據此用戶名查詢到的密碼與登陸的密碼進行equals,若是相等就登陸成功。(固然咱們要判斷根據username查詢後的返回值是否爲null,不作判斷會產生空指針問題,若是一個空值和另外一個值相比顯然會報錯)。 若是登陸成功,將返回到page/page.jsp頁面(這是根據咱們在springmvc.xml下配置的視圖解析器InternalResourceViewResolver決定的);若是登陸失敗將返回到page/loginInfo.jsp頁面。

1.3 編寫Mapper.xml

<select id="login" parameterType="String" resultType="User">
    select * from user where username = #{username}
</select>

咱們使用了Mybatis的接口代理開發模式(保證接口和配置文件在同一目錄下且名稱相同),直接在Mapper.xml中編寫原生sql語句,便可進行與數據庫間的交互。 id指明是哪一個方法調用這個sql;parameterType指定了接口傳遞的參數類型(咱們根據用戶名查詢因此是String類型);resultType指定該查詢語句返回值的數據類型(由於咱們已經在配置文件啓用了別名配置typeAliases,因此這裏直接指定pojo對象類名便可;若沒有啓動別名配置,就必須寫類的全限定名)。使用#{}會將傳遞的參數值會自動添加"";注意#{}${}區分,後者則是直接拼接傳遞進來的字符串,而不會添加任何符號,且前者能避免sql注入。

2. 實現客戶信息的添加

所謂添加客戶信息,就是將JSP中提交的表單數據持久化到數據庫中。

2.1 建立表結構

建表SQL請看github項目中的resources目錄下的.sql文件 一樣咱們還要建立對應的pojo,並提供getter和setter方法。(儘可能保持pojo中的元素屬性名稱和表中字段名稱相同)。

2.2 編寫Controller層

@RequestMapping(value = "/save")
public String save(Customer customer, Model model) {
    customerService.save(customer);
    model.addAttribute("message", "保存客戶信息系成功");
    return "page/info";
}

當點擊了提交按鈕,表單中的全部數據都應該被持久化到數據庫中,而要知道表單中的參數有不少,咱們直接在請求映射方法的參數列表中寫參數顯然是不可取的,那麼咱們若是寫一個pojo對象,Spring就會根據這個pojo對象中的屬性和JSP表單中的參數進行對應,若是徹底對應那麼請求方法會正常執行,一但有某個參數不對應,那麼就會報錯。(注意咱們表單中並不須要指定id主鍵值,由於設計表時已經指定了該id主鍵爲自增加,即便不指定值,id依然會自增,你指定了卻可能會產生錯誤,由於到保證每次的id值都是遞增的)。當數據持久化成功,就使用Spring的Model對象在域對象中設置一個名爲message的值。最後再返回到視圖層。

2.3 編寫Mapper.xml

<insert id="save" parameterType="Customer">
        insert into
        customer(
          c_name,
          c_telephone,
          c_address,
          c_remark)
        values(
          #{c_name},
          #{c_telephone},
          #{c_address},
          #{c_remark}
        );
</insert>

如上這仍然是普通的SQL語句,注意parameterType如上咱們設置爲Customer其實表明的是cn.tycoding.pojo.Customer這個對象,由於咱們已經在beans.xml中啓用了mybatis的別名配置。SQL插入語句中不須要指定id這個字段,緣由是咱們已經配置了id爲自增主鍵

3. 實現客戶信息的刪除功能

3.1 編寫Controller層

@RequestMapping(value="/delete")
public String delete(@RequestParam int c_id,Model model){
    if(customerService.delete(c_id) > 0){
        model.addAttribute("message","刪除客戶信息成功");
        return "page/info";
    }else{
        model.addAttribute("message","刪除客戶信息失敗");
        return "page/info";
    }
}

刪除功能只須要根據點擊刪除按鈕時獲取到的id值,在SQL的delete語句中where這個id值,便可以實現根據id刪除客戶信息。

3.2 編寫Mapper.xml

<delete id="delete" parameterType="int">
    delete from customer where c_id = #{c_id}
</delete>

如此,仍是一個再普通不過的SQL語句,既能夠實現根據id刪除的功能。

4. 更新客戶信息

更新客戶信息須要咱們實現兩個功能:1.再點擊編輯按鈕時(咱們在按鈕設置了onclick="return edit(${xx.id};"),如此咱們用js監聽這個事件,點擊了按鈕,js獲取到id,請求後臺根據這個id值查詢數據庫中的數據。那麼咱們先看一下js部分吧:

function edit(id){
  $.ajax({
    url: 'xxx/findById.do',
    type: 'POST',
    dataType: 'json',
    contentType: 'application/json;charset=UTF-8',
    data: JSON.stringify({id: id}),
    success: function(result){
      $("#username").val(result.username);
      $("#password").val(result.password);
      //繼續講查詢到的字段依次進行賦值...
    }::
  });
}

以上實際是一個ajax請求json格式數據:1.type指定請求類型;2.dataType指定了服務器返回數據格式類型;3.contentType指定發送給服務器的數據格式,默認是application/x-www-form-urlencoded會使此時的data參數爲JSON對象,而設置爲application/json後此時的data參數就是json字符串了,一樣使用stringify()也是將data參數轉換成json字符串。

4.1 編寫Controller層

@ResponseBody
@RequestMapping(value="/findById")
public Customer findById(@RequestBody Customer customer){
    Customer customer_info = customerService.findById(customer.getC_id());
    if(customer_info != null){
        return customer_info;
    }else{
        return null;
    }
}

@RequestBody讀取http請求內容,並將數據綁定在映射方法的參數上;@ResponseBody將映射須要返回的參數轉換成指定的數據格式,而因爲咱們在ajax請求中指定dataTypejson,那麼@ReqponseBody將會返回json格式數據。 當ajax請求了這個映射方法,Controller獲取到指定的id去調用Service層根據這個id查詢數據庫select * from customer where id = #{id}。而後將數據拼裝成JSON格式,並返回給頁面。最後ajax會走success方法,咱們從返回的數據success:function(result)中經過result.xx的方式取出來並經過jquery的val()方式賦值到指定的位置,那麼就實現了數據回顯。 實現修改功能,首先要知道本來的數據(數據回顯),而後將修改後的數據在此存入到數據庫中(update customer set xx=#{xx} where id = #{id}。那麼咱們看一下,更新數據庫的Controller:

@RequestMapping(value="/update")
public String update(Customer customer,Model model){
    int rows = customerService.update(customer);
    if(rows > 0){
        model.addAttribute("message","更新客戶信息成功");
        return "page/info";
    }else{
        model.addAttribute("message","更新客戶信息失敗");
        return "page/info";
    }
}

由於更新數據其實就是簡單的提交表單,表單提交後訪問這個映射方法,而後查詢數據庫,若是正常更新了數據,sql影響行數應該大於0(rows>0),那麼就更新成功,經過SpringMVC的Model方法向request域對象中存入成功信息,在返回的頁面中,經過${message}EL表達式的方式取出提示信息。 最後咱們看一下更新的SQL如何寫:

4.2 編寫Mapper.xml

<!-- 更新客戶信息的方法 -->
<update id="update" parameterType="Customer">
    update customer set
        c_id = #{c_id},
        c_name = #{c_name},
        c_telephone = #{c_telephone},
        c_address = #{c_address},
        c_remark = #{c_remark}
    where c_id = #{c_id}
</update>

5. 分頁查詢

解釋以前咱們先看一下分頁查詢的頁面:

5.1 封裝PageBean

public class PageBean<T> implements Serializable {
    //當前頁
    private int pageCode;

    //總頁數=總記錄數/每頁顯示的記錄數
    private int totalPage;

    //總記錄數
    private int totalCount;

    //每頁顯示的記錄數
    private int pageSize;

    //每頁顯示的數據
    private List<T> beanList;
}

由於咱們要實現分頁查詢,因此沒法避免一些參數,這裏直接將其封裝爲一個JavaBean就是爲了方便調用,而配置自定義泛型<T>就是爲了供多個對象的調用,若是你在對Customer類進行分頁查詢,那麼在調用時只須要new pageBean<Customer>()便可將查詢的數據綁定爲Customer類的數據;對其餘類進行分頁亦是這樣。

pageCode: 表示當前(你點擊)的是第幾頁。

totalPage: 表示總頁數(總頁數=總記錄數/每頁顯示的記錄數)。經過select count(*) from 表查詢到總記錄數,每頁顯示的記錄是用戶指定的;那麼總記錄數/每頁顯示幾條記錄就獲得了一共有幾頁(前端頁面展現)。

totalCount: 表示總記錄數,由SQL:select count(*) from 表查詢到該表咋數據庫中一共多少條記錄數。

pageSize: 表示每頁顯示的記錄數,這個是用戶決定的,好比咱們想讓每頁顯示5條數據,那麼這個pageSize就應該是5,即每頁會顯示5條記錄。

beanList: 表示當前顯示的數據。經上面的一系列查詢和封裝,咱們最終須要將數據返回給頁面,而這些須要返回給頁面的數據最終會被封裝到這個beanList中,在jsp中使用<forEach>標籤遍歷beanList獲得封裝的數據並顯示到頁面上。

5.2 jsp頁面

5.3 編寫Controller層

/**
 * 客戶信息列表(分頁查詢功能)
 */
@RequestMapping(value="/findByPage")
public String findByPage(@RequestParam(value="pageCode",defaultValue = "1",required = false)int pageCode,
                       @RequestParam(value="pageSize",defaultValue = "2",required = false)int pageSize,
                       HttpServletRequest request,
                       Model model){
  // 封裝分頁數據
  String c_name = request.getParameter("c_name");
  String c_telephone = request.getParameter("c_telephone");
  Map<String,Object> conMap = new HashMap<String,Object>();
  conMap.put("c_name",c_name);
  conMap.put("c_telephone",c_telephone);

  // 回顯數據
  model.addAttribute("page",customerService.findByPage(pageCode,pageSize,conMap));
  return "page/list";
}

對比上面兩張圖,發現,用戶能夠指定的就是pageCode當前頁和pageSize每頁顯示的記錄數。因此在點擊按鈕(好比點擊頁碼3),就會提交表單並訪問Controller的findByPage()方法。 那麼Controller就須要接受這兩個參數:pageCodeandpageSize,而且咱們設置:defaultValue默認值;required是否必須指定(若是沒有寫false,在每次請求這個方法時就必須指定這個參數的具體值,否則就會報錯)。 方法體中咱們還經過request域獲取c_namec_telephone(由於要實現條件查詢:輸入信息,查詢數據)。 最後咱們將這些查詢條件封裝到Map集合中,而後調用Service層,將pageCodepageSize以及封裝的查詢條件信息conMap一同傳入Service層。

5.4 編寫Service層

首先咱們看一下由Controller調用的Service層接口: 因爲咱們在Controller調用Service的findByPage()方法時並無給定返回值什麼類型,因此這裏咱們要手動將其修改成PageBean<Customer>。下面看一下實現類如何編寫:

public PageBean<Customer> findByPage(int pageCode, int pageSize, Map<String, Object> conMap) {
        HashMap<String,Object> map = new HashMap<String,Object>();
        PageBean<Customer> pageBean = new PageBean<Customer>();

        //封裝當前頁
        pageBean.setPageCode(pageCode);
        pageBean.setPageSize(pageSize);

        // 封裝總記錄數(從數據庫中查詢)
        int totalCount = customerMapper.selectCount();
        System.out.println("查詢到的總記錄數:"+totalCount);
        pageBean.setTotalCount(totalCount);

        //封裝總頁數
        double tc = totalCount;
        Double num = Math.ceil(tc / pageSize);
        pageBean.setTotalPage(num.intValue());

        // 設置limit分頁查詢的起始位置和終止位置
        map.put("start",(pageCode - 1) * pageSize);
        map.put("size",pageBean.getPageSize());

        //封裝每頁顯示的數據
        List<Customer> list = customerMapper.findByPage(map);
        pageBean.setBeanList(list);

        // 分頁查詢功能也要封裝顯示起始頁和終止頁
        conMap.put("start",(pageCode - 1) * pageSize);
        conMap.put("size",pageBean.getPageSize());

        // 封裝
        List<Customer> listCondition = customerMapper.findCondition(conMap);
        pageBean.setBeanList(listCondition);
        return pageBean;
}

做爲業務層,固然負責梳理業務信息,首先咱們須要將Controller傳入進來的pageCodepageSize封裝進PageBean的相關屬性中。而後查詢總記錄數(經過select count(*) from 表查詢獲得),根據總記錄數pageCount和前臺傳入的pageSize每頁顯示的記錄數計算獲得總頁數,一樣封裝到PageBean中,最後咱們要設置分頁的起始位置start和數量size,調用Mapper查詢數據庫中的數據,將數據封裝到beanList中便可。可是要注意咱們其實寫了兩個分頁查詢的方法:findByPage()findCondition()由於二者都須要分頁展現到頁面上。最後解釋兩點:

  1. 計算總頁數:總頁數 = 總記錄數 / 每頁顯示的記錄條數。這裏用到的ceil()方法:返回大於或登陸參數的最小double值,並等於數學整數。如double a = 5;double b = 3;ceil(a/b) = 2.0。最後用Double類的intValue()方法返回此Double值做爲int類型的值。
  2. mysql爲分頁查詢提供了limit方法,limit a,b就是讀取第a條到第b條的全部記錄。 設置start爲*(當前頁-1)此時每頁顯示的記錄數*。 設置size咱們在pageBean中封裝的每頁顯示幾條記錄數。 例如:咱們目前頁面每頁顯示2條數據,點擊下一頁,則顯示的數據就是第3 - 5條。

5.5 編寫Mapper.xml

  • 注意:這裏使用了mybatis的mapper接口實現方式,再強調幾個注意事項:
    1. mapper.xml文件名稱必須和接口名稱相同
    2. Mapper 接口方法名和 UserMapper.xml 中定義的每一個 sql 的 id 同名。
    3. Mapper 接口方法的輸入參數類型和 UserMapper.xml 中定義的 sql 的parameterType 類型相同。
    4. Mapper 接口的返回類型和 UserMapper.xml 中定義的 sql 的 resultType 類型相同
<!-- 查詢總的記錄數 -->
    <select id="selectCount" resultType="int">
        select count(*) from customer;
    </select>

    <!-- 分頁查詢 -->
    <select id="findByPage" parameterType="Map" resultMap="BaseResultMap">
        select * from customer
        <if test="start != null and size != null">
            limit #{start},#{size}
        </if>
    </select>

    <!-- 多條件查詢 -->
    <select id="findCondition" parameterType="Map" resultMap="BaseResultMap">
        <!-- where 1=1 能夠保證where後的語句永遠是正確的
            由於在where後的動態SQL可能會執行也可能不會不會執行,若是沒有執行,那麼where後將跟一個空值,那麼顯然這樣是會報錯的
        -->
        select * from customer where 1 = 1
        <if test="c_name != null and c_name != ''">
            and c_name like concat('%', #{c_name}, '%')
        </if>
        <if test="c_telephone != null and c_telephone != ''">
            and c_telephone like concat('%', #{c_telephone}, '%')
        </if>
        <!-- 咱們經過在Service中的計算決定了咱們每次請求的數據應該從那一頁開始,那一頁結束 -->
        <if test="start != null and size != null">
            limit #{start},#{size}
        </if>
    </select>

注意幾點:

  1. findByPage方法是用來分頁顯示數據的,咱們傳進來的數據是Map集合,定義了parameType="Map";resultMap實現了將查詢到的複雜的數據映射到一個結果集中
  2. findCondition方法是用來分頁顯示條件查詢到的數據的,注意where 1 = 1主要是用來避免如下動態sql中的條件都不知足的狀況時,where後就沒數據了,這樣顯然報錯,加上1=1就避免了這種狀況

5.6 分頁邏輯

首先咱們看一下頁碼是如何展現出來的:

百度分頁算法(每頁顯示10個頁碼):
	當點擊頁碼7以後的頁碼,最前端的頁碼依次減小
		[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]
		點擊[7]
		[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
算法:
	若 總頁數 <= 10		則begin=1			  end=總頁數
	若 總頁數 > 10		則begin=當前頁-5	  	end=當前頁+4
		頭溢出: 若begin < 1		 則begin=1	   end=10
		尾溢出: 若end > 總記錄數   則brgin=end-9	 end=總頁數	
		
此項目設置每頁顯示5個頁碼:
	若 總頁數 <= 5		則begin=1			  end=總頁數
	若 總頁數 >  5		則begin=當前頁-1	  	end=當前頁+3
		頭溢出: 若begin < 1		  則begin=1	   end=5
		尾溢出: 若end > 總記錄數   則brgin=end-4	 end=總頁數

* (end表示頁面的最後一個頁碼,begin表示頁面的第一個頁碼)

以前有人會問道這個頭溢出尾溢出是什麼意思?其實仔細看看,若是咱們安裝上面設計的算法邏輯:頭溢出就是指當頁數不少一直點擊上一頁,爲避免出現第0頁而設置的;那麼尾溢出就以下圖所示狀況了:


綜上

到此爲止,咱們基本講完了SSM框整合的過程,你是否看明白了呢?其實整合SSM框架並不難,按照這個思路,咱們學習完SSM框架整合,就能夠着手練習一些小項目了。詳細過程,你們能夠從個人項目源碼中分析。

項目運行截圖

<br/>

交流

若是你們有興趣,歡迎你們加入個人Java交流羣:671017003 ,一塊兒交流學習Java技術。博主目前一直在自學JAVA中,技術有限,若是能夠,會盡力給你們提供一些幫助,或是一些學習方法,固然羣裏的大佬都會積極給新手答疑的。因此,別猶豫,快來加入咱們吧!

<br/>

聯繫

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

相關文章
相關標籤/搜索