本文發(fā)表于2016年6月,寫于作者學(xué)生時(shí)期。文中使用到的技術(shù)和框架可能不是當(dāng)下最佳實(shí)踐,甚至很不“優(yōu)雅”。但對于剛接觸JavaEE和Spring的同學(xué)來說,還是能有很多收獲的,大牛輕拍= =
我們看招聘信息的時(shí)候,經(jīng)常會(huì)看到這一點(diǎn),需要具備SSH框架的技能;而且在大部分教學(xué)課堂中,也會(huì)把SSH作為最核心的教學(xué)內(nèi)容。
但是,我們在實(shí)際應(yīng)用中發(fā)現(xiàn),SpringMVC可以完全替代Struts,配合注解的方式,編程非常快捷,而且通過restful風(fēng)格定義url,讓地址看起來非常優(yōu)雅。
另外,MyBatis也可以替換Hibernate,正因?yàn)镸yBatis的半自動(dòng)特點(diǎn),我們程序猿可以完全掌控SQL,這會(huì)讓有數(shù)據(jù)庫經(jīng)驗(yàn)的程序猿能開發(fā)出高效率的SQL語句,而且XML配置管理起來也非常方便。
好了,如果你也認(rèn)同我的看法,那么下面我們一起來做整合吧!
在寫代碼之前我們先了解一下這三個(gè)框架分別是干什么的?
相信大以前也看過不少這些概念,我這就用大白話來講,如果之前有了解過可以跳過這一大段,直接看代碼!
SpringMVC:它用于web層,相當(dāng)于controller(等價(jià)于傳統(tǒng)的servlet和struts的action),用來處理用戶請求。舉個(gè)例子,用戶在地址欄輸入http://網(wǎng)站域名/login,那么springmvc就會(huì)攔截到這個(gè)請求,并且調(diào)用controller層中相應(yīng)的方法,(中間可能包含驗(yàn)證用戶名和密碼的業(yè)務(wù)邏輯,以及查詢數(shù)據(jù)庫操作,但這些都不是springmvc的職責(zé)),最終把結(jié)果返回給用戶,并且返回相應(yīng)的頁面(當(dāng)然也可以只返回json/xml等格式數(shù)據(jù))。springmvc就是做前面和后面過程的活,與用戶打交道!!
Spring:太強(qiáng)大了,以至于我無法用一個(gè)詞或一句話來概括它。但與我們平時(shí)開發(fā)接觸最多的估計(jì)就是IOC容器,它可以裝載bean(也就是我們java中的類,當(dāng)然也包括service dao里面的),有了這個(gè)機(jī)制,我們就不用在每次使用這個(gè)類的時(shí)候?yàn)樗跏蓟?,很少看到關(guān)鍵字new。另外spring的aop,事務(wù)管理等等都是我們經(jīng)常用到的。
MyBatis:如果你問我它跟鼎鼎大名的Hibernate有什么區(qū)別?我只想說,他更符合我的需求。第一,它能自由控制sql,這會(huì)讓有數(shù)據(jù)庫經(jīng)驗(yàn)的人(當(dāng)然不是說我啦捂臉)編寫的代碼能搞提升數(shù)據(jù)庫訪問的效率。第二,它可以使用xml的方式來組織管理我們的sql,因?yàn)橐话愠绦虺鲥e(cuò)很多情況下是sql出錯(cuò),別人接手代碼后能快速找到出錯(cuò)地方,甚至可以優(yōu)化原來寫的sql。
SSM框架整合配置
好了,前面bb那么多,下面我們真正開始敲代碼了~
首先我們打開IED,我這里用的是eclipse(你們應(yīng)該也是用的這個(gè),對嗎?),創(chuàng)建一個(gè)動(dòng)態(tài)web項(xiàng)目,建立好相應(yīng)的目錄結(jié)構(gòu)(重點(diǎn)!)
(打了馬賽克是因?yàn)檫@里還用不到,你們不要那么污好不好?)
我說一下每個(gè)目錄都有什么用吧(第一次畫表格,我發(fā)現(xiàn)markdown的表格語法很不友好呀~)
這個(gè)目錄結(jié)構(gòu)同時(shí)也遵循maven的目錄規(guī)范~
文件名 作用
src 根目錄,沒什么好說的,下面有main和test。
- main 主要目錄,可以放java代碼和一些資源文件。
- - java 存放我們的java代碼,這個(gè)文件夾要使用Build Path -> Use as Source Folder,這樣看包結(jié)構(gòu)會(huì)方便很多,新建的包就相當(dāng)于在這里新建文件夾咯。
- - resources 存放資源文件,譬如各種的spring,mybatis,log配置文件。
- - - mapper 存放dao中每個(gè)方法對應(yīng)的sql,在這里配置,無需寫daoImpl。
- - - spring 這里當(dāng)然是存放spring相關(guān)的配置文件,有dao service web三層。
- - - sql 其實(shí)這個(gè)可以沒有,但是為了項(xiàng)目完整性還是加上吧。
- - - webapp 這個(gè)貌似是最熟悉的目錄了,用來存放我們前端的靜態(tài)資源,如jsp js css。
- - - - resources 這里的資源是指項(xiàng)目的靜態(tài)資源,如js css images等。
- - - - WEB-INF 很重要的一個(gè)目錄,外部瀏覽器無法訪問,只有羨慕內(nèi)部才能訪問,可以把jsp放在這里,另外就是web.xml了。你可能有疑問了,為什么上面java中的resources里面的配置文件不妨在這里,那么是不是會(huì)被外部竊取到?你想太多了,部署時(shí)候基本上只有webapp里的會(huì)直接輸出到根目錄,其他都會(huì)放入WEB-INF里面,項(xiàng)目內(nèi)部依然可以使用classpath:XXX來訪問,好像IDE里可以設(shè)置部署輸出目錄,這里扯遠(yuǎn)了~
- test 這里是測試分支。
- - java 測試java代碼,應(yīng)遵循包名相同的原則,這個(gè)文件夾同樣要使用Build Path -> Use as Source Folder,這樣看包結(jié)構(gòu)會(huì)方便很多。
- - resources 沒什么好說的,好像也很少用到,但這個(gè)是maven的規(guī)范。
我先新建好幾個(gè)必要的包,并為大家講解一下每個(gè)包的作用,順便理清一下后臺的思路~
包名 名稱 作用
dao 數(shù)據(jù)訪問層(接口) 與數(shù)據(jù)打交道,可以是數(shù)據(jù)庫操作,也可以是文件讀寫操作,甚至是redis緩存操作,總之與數(shù)據(jù)操作有關(guān)的都放在這里,也有人叫做dal或者數(shù)據(jù)持久層都差不多意思。為什么沒有daoImpl,因?yàn)槲覀冇玫氖莔ybatis,所以可以直接在配置文件中實(shí)現(xiàn)接口的每個(gè)方法。
entity 實(shí)體類 一般與數(shù)據(jù)庫的表相對應(yīng),封裝dao層取出來的數(shù)據(jù)為一個(gè)對象,也就是我們常說的pojo,一般只在dao層與service層之間傳輸。
dto 數(shù)據(jù)傳輸層 剛學(xué)框架的人可能不明白這個(gè)有什么用,其實(shí)就是用于service層與web層之間傳輸,為什么不直接用entity(pojo)?其實(shí)在實(shí)際開發(fā)中發(fā)現(xiàn),很多時(shí)間一個(gè)entity并不能滿足我們的業(yè)務(wù)需求,可能呈現(xiàn)給用戶的信息十分之多,這時(shí)候就有了dto,也相當(dāng)于vo,記住一定不要把這個(gè)混雜在entity里面,答應(yīng)我好嗎?
service 業(yè)務(wù)邏輯(接口) 寫我們的業(yè)務(wù)邏輯,也有人叫bll,在設(shè)計(jì)業(yè)務(wù)接口時(shí)候應(yīng)該站在“使用者”的角度。額,不要問我為什么這里沒顯示!IDE調(diào)皮我也拿它沒辦法~
serviceImpl 業(yè)務(wù)邏輯(實(shí)現(xiàn)) 實(shí)現(xiàn)我們業(yè)務(wù)接口,一般事務(wù)控制是寫在這里,沒什么好說的。
web 控制器 springmvc就是在這里發(fā)揮作用的,一般人叫做controller控制器,相當(dāng)于struts中的action。
還有最后一步基礎(chǔ)工作,導(dǎo)入我們相應(yīng)的jar包,我使用的是maven來管理我們的jar,所以只需要在pom.xml中加入相應(yīng)的依賴就好了,如果不使用maven的可以自己去官網(wǎng)下載相應(yīng)的jar,放到項(xiàng)目WEB-INF/lib目錄下。關(guān)于maven的學(xué)習(xí)大家可以看慕課網(wǎng)的視頻教程,這里就不展開了。我把項(xiàng)目用到的jar都寫在下面,版本都不是最新的,大家有經(jīng)驗(yàn)的話可以自己調(diào)整版本號。另外,所有jar都會(huì)與項(xiàng)目一起打包放到我的github上,喜歡的給個(gè)star吧~
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.soecode.ssm</groupId>
<artifactId>ssm</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>ssm Maven Webapp</name>
<url>http://github.com/liyifeng1994/ssm</url>
<dependencies>
<!-- 單元測試 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<!-- 1.日志 -->
<!-- 實(shí)現(xiàn)slf4j接口并整合 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>
<!-- 2.數(shù)據(jù)庫 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- DAO: MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 3.Servlet web -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 4.Spring -->
<!-- 1)Spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- 2)Spring DAO層 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- 3)Spring web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- 4)Spring test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- redis客戶端:Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.0.8</version>
</dependency>
<!-- Map工具類 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2</version>
</dependency>
</dependencies>
<build>
<finalName>ssm</finalName>
</build>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
下面真的要開始進(jìn)行編碼工作了,堅(jiān)持到這里辛苦大家了~
第一步:我們先在spring文件夾里新建spring-dao.xml文件,因?yàn)閟pring的配置太多,我們這里分三層,分別是dao service web。
讀入數(shù)據(jù)庫連接相關(guān)參數(shù)(可選)
配置數(shù)據(jù)連接池
配置連接屬性,可以不讀配置項(xiàng)文件直接在這里寫死
配置c3p0,只配了幾個(gè)常用的
配置SqlSessionFactory對象(mybatis)
掃描dao層接口,動(dòng)態(tài)實(shí)現(xiàn)dao接口,也就是說不需要daoImpl,sql和參數(shù)都寫在xml文件上
spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置整合mybatis過程 -->
<!-- 1.配置數(shù)據(jù)庫相關(guān)參數(shù)properties的屬性:${url} -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 2.數(shù)據(jù)庫連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 配置連接池屬性 -->
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- c3p0連接池的私有屬性 -->
<property name="maxPoolSize" value="30" />
<property name="minPoolSize" value="10" />
<!-- 關(guān)閉連接后不自動(dòng)commit -->
<property name="autoCommitOnClose" value="false" />
<!-- 獲取連接超時(shí)時(shí)間 -->
<property name="checkoutTimeout" value="10000" />
<!-- 當(dāng)獲取連接失敗重試次數(shù) -->
<property name="acquireRetryAttempts" value="2" />
</bean>
<!-- 3.配置SqlSessionFactory對象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入數(shù)據(jù)庫連接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- 掃描entity包 使用別名 -->
<property name="typeAliasesPackage" value="com.soecode.lyf.entity" />
<!-- 掃描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>
<!-- 4.配置掃描Dao接口包,動(dòng)態(tài)實(shí)現(xiàn)Dao接口,注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<!-- 給出需要掃描Dao接口包 -->
<property name="basePackage" value="com.soecode.lyf.dao" />
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
因?yàn)閿?shù)據(jù)庫配置相關(guān)參數(shù)是讀取配置文件,所以在resources文件夾里新建一個(gè)jdbc.properties文件,存放我們4個(gè)最常見的數(shù)據(jù)庫連接屬性,這是我本地的,大家記得修改呀~還有喜歡傳到github上“大頭蝦們”記得刪掉密碼,不然別人就很容易得到你服務(wù)器的數(shù)據(jù)庫配置信息,然后干一些羞羞的事情,你懂的!!
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3307/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=
1
2
3
4
友情提示:配置文件中的jdbc.username,如果寫成username,可能會(huì)與系統(tǒng)環(huán)境中的username變量沖突,所以到時(shí)候真正連接數(shù)據(jù)庫的時(shí)候,用戶名就被替換成系統(tǒng)中的用戶名(有得可能是administrator),那肯定是連接不成功的,這里有個(gè)小坑,我被坑了一晚上!!
因?yàn)檫@里用到了mybatis,所以需要配置mybatis核心文件,在recources文件夾里新建mybatis-config.xml文件。
使用自增主鍵
使用列別名
開啟駝峰命名轉(zhuǎn)換 create_time -> createTime
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
? PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
? "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置全局屬性 -->
<settings>
<!-- 使用jdbc的getGeneratedKeys獲取數(shù)據(jù)庫自增主鍵值 -->
<setting name="useGeneratedKeys" value="true" />
<!-- 使用列別名替換列名 默認(rèn):true -->
<setting name="useColumnLabel" value="true" />
<!-- 開啟駝峰命名轉(zhuǎn)換:Table{create_time} -> Entity{createTime} -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
第二步:剛弄好dao層,接下來到service層了。在spring文件夾里新建spring-service.xml文件。
掃描service包所有注解 @Service
配置事務(wù)管理器,把事務(wù)管理交由spring來完成
配置基于注解的聲明式事務(wù),可以直接在方法上@Transaction
spring-service.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 掃描service包下所有使用注解的類型 -->
<context:component-scan base-package="com.soecode.lyf.service" />
<!-- 配置事務(wù)管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入數(shù)據(jù)庫連接池 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置基于注解的聲明式事務(wù) -->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
第三步:配置web層,在spring文件夾里新建spring-web.xml文件。
開啟SpringMVC注解模式,可以使用@RequestMapping,@PathVariable,@ResponseBody等
對靜態(tài)資源處理,如js,css,jpg等
配置jsp 顯示ViewResolver,例如在controller中某個(gè)方法返回一個(gè)string類型的"login",實(shí)際上會(huì)返回"/WEB-INF/login.jsp"
掃描web層 @Controller
spring-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- 配置SpringMVC -->
<!-- 1.開啟SpringMVC注解模式 -->
<!-- 簡化配置:
(1)自動(dòng)注冊DefaultAnootationHandlerMapping,AnotationMethodHandlerAdapter
(2)提供一些列:數(shù)據(jù)綁定,數(shù)字和日期的format @NumberFormat, @DateTimeFormat, xml,json默認(rèn)讀寫支持
-->
<mvc:annotation-driven />
<!-- 2.靜態(tài)資源默認(rèn)servlet配置
(1)加入對靜態(tài)資源的處理:js,gif,png
(2)允許使用"/"做整體映射
-->
<mvc:default-servlet-handler/>
<!-- 3.配置jsp 顯示ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 4.掃描web相關(guān)的bean -->
<context:component-scan base-package="com.soecode.lyf.web" />
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
第四步:最后就是修改web.xml文件了,它在webapp的WEB-INF下。
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
? ? ? ? ? ? ? ? ? ? ? http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1" metadata-complete="true">
<!-- 如果是用mvn命令生成的xml,需要修改servlet版本為3.1 -->
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>seckill-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置springMVC需要加載的配置文件
spring-dao.xml,spring-service.xml,spring-web.xml
Mybatis - > spring -> springmvc
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>seckill-dispatcher</servlet-name>
<!-- 默認(rèn)匹配所有的請求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
我們在項(xiàng)目中經(jīng)常會(huì)使用到日志,所以這里還有配置日志xml,在resources文件夾里新建logback.xml文件,所給出的日志輸出格式也是最基本的控制臺s呼出,大家有興趣查看logback官方文檔。
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
到目前為止,我們一共寫了7個(gè)配置文件,我們一起來看下最終的配置文件結(jié)構(gòu)圖。
SSM框架應(yīng)用實(shí)例(圖書管理系統(tǒng))
一開始想就這樣結(jié)束教程,但是發(fā)現(xiàn)其實(shí)很多人都還不會(huì)把這個(gè)SSM框架用起來,特別是mybatis部分。那我現(xiàn)在就以最常見的“圖書管理系統(tǒng)”中【查詢圖書】和【預(yù)約圖書】業(yè)務(wù)來做一個(gè)demo吧!
首先新建數(shù)據(jù)庫名為ssm,再創(chuàng)建兩張表:圖書表book和預(yù)約圖書表appointment,并且為book表初始化一些數(shù)據(jù),sql如下。
schema.sql
-- 創(chuàng)建圖書表
CREATE TABLE `book` (
? `book_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '圖書ID',
? `name` varchar(100) NOT NULL COMMENT '圖書名稱',
? `number` int(11) NOT NULL COMMENT '館藏?cái)?shù)量',
? PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='圖書表'
-- 初始化圖書數(shù)據(jù)
INSERT INTO `book` (`book_id`, `name`, `number`)
VALUES
(1000, 'Java程序設(shè)計(jì)', 10),
(1001, '數(shù)據(jù)結(jié)構(gòu)', 10),
(1002, '設(shè)計(jì)模式', 10),
(1003, '編譯原理', 10)
-- 創(chuàng)建預(yù)約圖書表
CREATE TABLE `appointment` (
? `book_id` bigint(20) NOT NULL COMMENT '圖書ID',
? `student_id` bigint(20) NOT NULL COMMENT '學(xué)號',
? `appoint_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '預(yù)約時(shí)間' ,
? PRIMARY KEY (`book_id`, `student_id`),
? INDEX `idx_appoint_time` (`appoint_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='預(yù)約圖書表'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在entity包中添加兩個(gè)對應(yīng)的實(shí)體,圖書實(shí)體Book.java和預(yù)約圖書實(shí)體Appointment.java。
Book.java
package com.soecode.lyf.entity;
public class Book {
private long bookId;// 圖書ID
private String name;// 圖書名稱
private int number;// 館藏?cái)?shù)量
// 省略構(gòu)造方法,getter和setter方法,toString方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Appointment.java
package com.soecode.lyf.entity;
import java.util.Date;
/**
* 預(yù)約圖書實(shí)體
*/
public class Appointment {
private long bookId;// 圖書ID
private long studentId;// 學(xué)號
private Date appointTime;// 預(yù)約時(shí)間
// 多對一的復(fù)合屬性
private Book book;// 圖書實(shí)體
// 省略構(gòu)造方法,getter和setter方法,toString方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在dao包新建接口BookDao.java和Appointment.java
BookDao.java
package com.soecode.lyf.dao;
import java.util.List;
import com.soecode.lyf.entity.Book;
public interface BookDao {
/**
* 通過ID查詢單本圖書
*
* @param id
* @return
*/
Book queryById(long id);
/**
* 查詢所有圖書
*
* @param offset 查詢起始位置
* @param limit 查詢條數(shù)
* @return
*/
List<Book> queryAll(@Param("offset") int offset, @Param("limit") int limit);
/**
* 減少館藏?cái)?shù)量
*
* @param bookId
* @return 如果影響行數(shù)等于>1,表示更新的記錄行數(shù)
*/
int reduceNumber(long bookId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
AppointmentDao.java
package com.soecode.lyf.dao;
import org.apache.ibatis.annotations.Param;
import com.soecode.lyf.entity.Appointment;
public interface AppointmentDao {
/**
* 插入預(yù)約圖書記錄
*
* @param bookId
* @param studentId
* @return 插入的行數(shù)
*/
int insertAppointment(@Param("bookId") long bookId, @Param("studentId") long studentId);
/**
* 通過主鍵查詢預(yù)約圖書記錄,并且攜帶圖書實(shí)體
*
* @param bookId
* @param studentId
* @return
*/
Appointment queryByKeyWithBook(@Param("bookId") long bookId, @Param("studentId") long studentId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
提示:這里為什么要給方法的參數(shù)添加@Param注解呢?是因?yàn)樵摲椒ㄓ袃蓚€(gè)或以上的參數(shù),一定要加,不然mybatis識別不了。上面的BookDao接口的queryById方法和reduceNumber方法只有一個(gè)參數(shù)book_id,所以可以不用加 @Param注解,當(dāng)然加了也無所謂~
注意,這里不需要實(shí)現(xiàn)dao接口不用編寫daoImpl, mybatis會(huì)給我們動(dòng)態(tài)實(shí)現(xiàn),但是我們需要編寫相應(yīng)的mapper。
在mapper目錄里新建兩個(gè)文件BookDao.xml和AppointmentDao.xml,分別對應(yīng)上面兩個(gè)dao接口,代碼如下。
BookDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
? ? PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
? ? "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.soecode.lyf.dao.BookDao">
<!-- 目的:為dao接口方法提供sql語句配置 -->
<select id="queryById" resultType="Book" parameterType="long">
<!-- 具體的sql -->
SELECT
book_id,
name,
number
FROM
book
WHERE
book_id = #{bookId}
</select>
<select id="queryAll" resultType="Book">
SELECT
book_id,
name,
number
FROM
book
ORDER BY
book_id
LIMIT #{offset}, #{limit}
</select>
<update id="reduceNumber">
UPDATE book
SET number = number - 1
WHERE
book_id = #{bookId}
AND number > 0
</update>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
AppointmentDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
? ? PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
? ? "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.soecode.lyf.dao.AppointmentDao">
<insert id="insertAppointment">
<!-- ignore 主鍵沖突,報(bào)錯(cuò) -->
INSERT ignore INTO appointment (book_id, student_id)
VALUES (#{bookId}, #{studentId})
</insert>
<select id="queryByKeyWithBook" resultType="Appointment">
<!-- 如何告訴MyBatis把結(jié)果映射到Appointment同時(shí)映射book屬性 -->
<!-- 可以自由控制SQL -->
SELECT
a.book_id,
a.student_id,
a.appoint_time,
b.book_id "book.book_id",
b.`name` "book.name",
b.number "book.number"
FROM
appointment a
INNER JOIN book b ON a.book_id = b.book_id
WHERE
a.book_id = #{bookId}
AND a.student_id = #{studentId}
</select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
mapper總結(jié):namespace是該xml對應(yīng)的接口全名,select和update中的id對應(yīng)方法名,resultType是返回值類型,parameterType是參數(shù)類型(這個(gè)其實(shí)可選),最后#{...}中填寫的是方法的參數(shù),看懂了是不是很簡單!!我也這么覺得~ 還有一個(gè)小技巧要交給大家,就是在返回Appointment對象包含了一個(gè)屬性名為book的Book對象,那么可以使用"book.屬性名"的方式來取值,看上面queryByKeyWithBook方法的sql。
dao層寫完了,接下來test對應(yīng)的package寫我們測試方法吧。
因?yàn)槲覀冎髸?huì)寫很多測試方法,在測試前需要讓程序讀入spring-dao和mybatis等配置文件,所以我這里就抽離出來一個(gè)BaseTest類,只要是測試方法就繼承它,這樣那些繁瑣的重復(fù)的代碼就不用寫那么多了~
BaseTest.java
package com.soecode.lyf;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* 配置spring和junit整合,junit啟動(dòng)時(shí)加載springIOC容器 spring-test,junit
*/
@RunWith(SpringJUnit4ClassRunner.class)
// 告訴junit spring配置文件
@ContextConfiguration({ "classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml" })
public class BaseTest {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
因?yàn)閟pring-service在service層的測試中會(huì)時(shí)候到,這里也一起引入算了!
新建BookDaoTest.java和AppointmentDaoTest.java兩個(gè)dao測試文件。
BookDaoTest.java
package com.soecode.lyf.dao;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.soecode.lyf.BaseTest;
import com.soecode.lyf.entity.Book;
public class BookDaoTest extends BaseTest {
@Autowired
private BookDao bookDao;
@Test
public void testQueryById() throws Exception {
long bookId = 1000;
Book book = bookDao.queryById(bookId);
System.out.println(book);
}
@Test
public void testQueryAll() throws Exception {
List<Book> books = bookDao.queryAll(0, 4);
for (Book book : books) {
System.out.println(book);
}
}
@Test
public void testReduceNumber() throws Exception {
long bookId = 1000;
int update = bookDao.reduceNumber(bookId);
System.out.println("update=" + update);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
BookDaoTest測試結(jié)果
testQueryById
testQueryAll
testReduceNumber
AppointmentDaoTest.java
package com.soecode.lyf.dao;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.soecode.lyf.BaseTest;
import com.soecode.lyf.entity.Appointment;
public class AppointmentDaoTest extends BaseTest {
@Autowired
private AppointmentDao appointmentDao;
@Test
public void testInsertAppointment() throws Exception {
long bookId = 1000;
long studentId = 12345678910L;
int insert = appointmentDao.insertAppointment(bookId, studentId);
System.out.println("insert=" + insert);
}
@Test
public void testQueryByKeyWithBook() throws Exception {
long bookId = 1000;
long studentId = 12345678910L;
Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
System.out.println(appointment);
System.out.println(appointment.getBook());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
AppointmentDaoTest測試結(jié)果
testInsertAppointment
testQueryByKeyWithBook
嗯,到這里一切到很順利那么我們繼續(xù)service層的編碼吧可能下面開始信息里比較大,大家要做好心理準(zhǔn)備~
首先,在寫我們的控制器之前,我們先定義幾個(gè)預(yù)約圖書操作返回碼的數(shù)據(jù)字典,也就是我們要返回給客戶端的信息。我們這類使用枚舉類,沒聽過的小伙伴要好好惡補(bǔ)一下了(我也是最近才學(xué)到的= =)
預(yù)約業(yè)務(wù)操作返回碼說明
返回碼 說明
1 預(yù)約成功
0 庫存不足
-1 重復(fù)預(yù)約
-2 系統(tǒng)異常
新建一個(gè)包叫enums,在里面新建一個(gè)枚舉類AppointStateEnum.java,用來定義預(yù)約業(yè)務(wù)的數(shù)據(jù)字典,沒聽懂沒關(guān)系,我們直接看代碼吧~是不是感覺有模有樣了!
AppointStateEnum.java
package com.soecode.lyf.enums;
/**
* 使用枚舉表述常量數(shù)據(jù)字典
*/
public enum AppointStateEnum {
SUCCESS(1, "預(yù)約成功"), NO_NUMBER(0, "庫存不足"), REPEAT_APPOINT(-1, "重復(fù)預(yù)約"), INNER_ERROR(-2, "系統(tǒng)異常");
private int state;
private String stateInfo;
private AppointStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
public static AppointStateEnum stateOf(int index) {
for (AppointStateEnum state : values()) {
if (state.getState() == index) {
return state;
}
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
接下來,在dto包下新建AppointExecution.java用來存儲(chǔ)我們執(zhí)行預(yù)約操作的返回結(jié)果。
AppointExecution.java
package com.soecode.lyf.dto;
import com.soecode.lyf.entity.Appointment;
import com.soecode.lyf.enums.AppointStateEnum;
/**
* 封裝預(yù)約執(zhí)行后結(jié)果
*/
public class AppointExecution {
// 圖書ID
private long bookId;
// 秒殺預(yù)約結(jié)果狀態(tài)
private int state;
// 狀態(tài)標(biāo)識
private String stateInfo;
// 預(yù)約成功對象
private Appointment appointment;
public AppointExecution() {
}
// 預(yù)約失敗的構(gòu)造器
public AppointExecution(long bookId, AppointStateEnum stateEnum) {
this.bookId = bookId;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
// 預(yù)約成功的構(gòu)造器
public AppointExecution(long bookId, AppointStateEnum stateEnum, Appointment appointment) {
this.bookId = bookId;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.appointment = appointment;
}
// 省略getter和setter方法,toString方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
接著,在exception包下新建三個(gè)文件
NoNumberException.java
RepeatAppointException.java
AppointException.java
預(yù)約業(yè)務(wù)異常類(都需要繼承RuntimeException),分別是無庫存異常、重復(fù)預(yù)約異常、預(yù)約未知錯(cuò)誤異常,用于業(yè)務(wù)層非成功情況下的返回(即成功返回結(jié)果,失敗拋出異常)。
NoNumberException.java
package com.soecode.lyf.exception;
/**
* 庫存不足異常
*/
public class NoNumberException extends RuntimeException {
public NoNumberException(String message) {
super(message);
}
public NoNumberException(String message, Throwable cause) {
super(message, cause);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RepeatAppointException.java
package com.soecode.lyf.exception;
/**
* 重復(fù)預(yù)約異常
*/
public class RepeatAppointException extends RuntimeException {
public RepeatAppointException(String message) {
super(message);
}
public RepeatAppointException(String message, Throwable cause) {
super(message, cause);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AppointException.java
package com.soecode.lyf.exception;
/**
* 預(yù)約業(yè)務(wù)異常
*/
public class AppointException extends RuntimeException {
public AppointException(String message) {
super(message);
}
public AppointException(String message, Throwable cause) {
super(message, cause);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
咱們終于可以編寫業(yè)務(wù)代碼了,在service包下新建BookService.java圖書業(yè)務(wù)接口。
BookService.java
package com.soecode.lyf.service;
import java.util.List;
import com.soecode.lyf.dto.AppointExecution;
import com.soecode.lyf.entity.Book;
/**
* 業(yè)務(wù)接口:站在"使用者"角度設(shè)計(jì)接口 三個(gè)方面:方法定義粒度,參數(shù),返回類型(return 類型/異常)
*/
public interface BookService {
/**
* 查詢一本圖書
*
* @param bookId
* @return
*/
Book getById(long bookId);
/**
* 查詢所有圖書
*
* @return
*/
List<Book> getList();
/**
* 預(yù)約圖書
*
* @param bookId
* @param studentId
* @return
*/
AppointExecution appoint(long bookId, long studentId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
在service.impl包下新建BookServiceImpl.java使用BookService接口,并實(shí)現(xiàn)里面的方法。
BookServiceImpl
package com.soecode.lyf.service.impl;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.soecode.lyf.dao.AppointmentDao;
import com.soecode.lyf.dao.BookDao;
import com.soecode.lyf.dto.AppointExecution;
import com.soecode.lyf.entity.Appointment;
import com.soecode.lyf.entity.Book;
import com.soecode.lyf.enums.AppointStateEnum;
import com.soecode.lyf.exception.AppointException;
import com.soecode.lyf.exception.NoNumberException;
import com.soecode.lyf.exception.RepeatAppointException;
import com.soecode.lyf.service.BookService;
@Service
public class BookServiceImpl implements BookService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 注入Service依賴
@Autowired
private BookDao bookDao;
@Autowired
private AppointmentDao appointmentDao;
@Override
public Book getById(long bookId) {
return bookDao.queryById(bookId);
}
@Override
public List<Book> getList() {
return bookDao.queryAll(0, 1000);
}
@Override
@Transactional
/**
* 使用注解控制事務(wù)方法的優(yōu)點(diǎn): 1.開發(fā)團(tuán)隊(duì)達(dá)成一致約定,明確標(biāo)注事務(wù)方法的編程風(fēng)格
* 2.保證事務(wù)方法的執(zhí)行時(shí)間盡可能短,不要穿插其他網(wǎng)絡(luò)操作,RPC/HTTP請求或者剝離到事務(wù)方法外部
* 3.不是所有的方法都需要事務(wù),如只有一條修改操作,只讀操作不需要事務(wù)控制
*/
public AppointExecution appoint(long bookId, long studentId) {
try {
// 減庫存
int update = bookDao.reduceNumber(bookId);
if (update <= 0) {// 庫存不足
//return new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);//錯(cuò)誤寫法
throw new NoNumberException("no number");
} else {
// 執(zhí)行預(yù)約操作
int insert = appointmentDao.insertAppointment(bookId, studentId);
if (insert <= 0) {// 重復(fù)預(yù)約
//return new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);//錯(cuò)誤寫法
throw new RepeatAppointException("repeat appoint");
} else {// 預(yù)約成功
Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
return new AppointExecution(bookId, AppointStateEnum.SUCCESS, appointment);
}
}
// 要先于catch Exception異常前先catch住再拋出,不然自定義的異常也會(huì)被轉(zhuǎn)換為AppointException,導(dǎo)致控制層無法具體識別是哪個(gè)異常
} catch (NoNumberException e1) {
throw e1;
} catch (RepeatAppointException e2) {
throw e2;
} catch (Exception e) {
logger.error(e.getMessage(), e);
// 所有編譯期異常轉(zhuǎn)換為運(yùn)行期異常
//return new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);//錯(cuò)誤寫法
throw new AppointException("appoint inner error:" + e.getMessage());
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
下面我們來測試一下我們的業(yè)務(wù)代碼吧~因?yàn)椴樵儓D書的業(yè)務(wù)不復(fù)雜,所以這里只演示我們最重要的預(yù)約圖書業(yè)務(wù)??!
BookServiceImplTest.java
package com.soecode.lyf.service.impl;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.soecode.lyf.BaseTest;
import com.soecode.lyf.dto.AppointExecution;
import com.soecode.lyf.service.BookService;
public class BookServiceImplTest extends BaseTest {
@Autowired
private BookService bookService;
@Test
public void testAppoint() throws Exception {
long bookId = 1001;
long studentId = 12345678910L;
AppointExecution execution = bookService.appoint(bookId, studentId);
System.out.println(execution);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
BookServiceImplTest測試結(jié)果
testAppoint
首次執(zhí)行是“預(yù)約成功”,如果再次執(zhí)行的話,應(yīng)該會(huì)出現(xiàn)“重復(fù)預(yù)約”,哈哈,我們所有的后臺代碼都通過單元測試?yán)瞺~是不是很開心~
咱們還需要在dto包里新建一個(gè)封裝json返回結(jié)果的類Result.java,設(shè)計(jì)成泛型。
Result.java
package com.soecode.lyf.dto;
/**
* 封裝json對象,所有返回結(jié)果都使用它
*/
public class Result<T> {
private boolean success;// 是否成功標(biāo)志
private T data;// 成功時(shí)返回的數(shù)據(jù)
private String error;// 錯(cuò)誤信息
public Result() {
}
// 成功時(shí)的構(gòu)造器
public Result(boolean success, T data) {
this.success = success;
this.data = data;
}
// 錯(cuò)誤時(shí)的構(gòu)造器
public Result(boolean success, String error) {
this.success = success;
this.error = error;
}
// 省略getter和setter方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
最后,我們寫web層,也就是controller,我們在web包下新建BookController.java文件。
BookController.java
package com.soecode.lyf.web;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.soecode.lyf.dto.AppointExecution;
import com.soecode.lyf.dto.Result;
import com.soecode.lyf.entity.Book;
import com.soecode.lyf.enums.AppointStateEnum;
import com.soecode.lyf.exception.NoNumberException;
import com.soecode.lyf.exception.RepeatAppointException;
import com.soecode.lyf.service.BookService;
@Controller
@RequestMapping("/book") // url:/模塊/資源/{id}/細(xì)分 /seckill/list
public class BookController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private BookService bookService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
private String list(Model model) {
List<Book> list = bookService.getList();
model.addAttribute("list", list);
// list.jsp + model = ModelAndView
return "list";// WEB-INF/jsp/"list".jsp
}
@RequestMapping(value = "/{bookId}/detail", method = RequestMethod.GET)
private String detail(@PathVariable("bookId") Long bookId, Model model) {
if (bookId == null) {
return "redirect:/book/list";
}
Book book = bookService.getById(bookId);
if (book == null) {
return "forward:/book/list";
}
model.addAttribute("book", book);
return "detail";
}
//ajax json
@RequestMapping(value = "/{bookId}/appoint", method = RequestMethod.POST, produces = {
"application/json; charset=utf-8" })
@ResponseBody
private Result<AppointExecution> appoint(@PathVariable("bookId") Long bookId, @RequestParam("studentId") Long studentId) {
if (studentId == null || studentId.equals("")) {
return new Result<>(false, "學(xué)號不能為空");
}
//AppointExecution execution = bookService.appoint(bookId, studentId);//錯(cuò)誤寫法,不能統(tǒng)一返回,要處理異常(失?。┣闆r
AppointExecution execution = null;
try {
execution = bookService.appoint(bookId, studentId);
} catch (NoNumberException e1) {
execution = new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);
} catch (RepeatAppointException e2) {
execution = new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);
} catch (Exception e) {
execution = new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);
}
return new Result<AppointExecution>(true, execution);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
因?yàn)槲冶容^懶,所以我們就不測試controller了,好討厭寫前端,嗚嗚嗚~
到此,我們的SSM框架整合配置,與應(yīng)用實(shí)例部分已經(jīng)結(jié)束了,我把所有源碼和jar包一起打包放在了我的GitHub上,需要的可以去下載,喜歡就給個(gè)star吧,這篇東西寫了兩個(gè)晚上也不容易啊。
完整代碼下載地址:https://github.com/liyifeng1994/ssm
2017-02-28更新(感謝網(wǎng)友EchoXml發(fā)現(xiàn)):
修改預(yù)約業(yè)務(wù)代碼,失敗時(shí)拋異常,成功時(shí)才返回結(jié)果,控制層根據(jù)捕獲的異常返回相應(yīng)信息給客戶端,而不是業(yè)務(wù)層直接返回錯(cuò)誤結(jié)果。上面的代碼已經(jīng)作了修改,而且錯(cuò)誤示范也注釋保留著,之前誤人子弟了,還好有位網(wǎng)友前幾天提出質(zhì)疑,我也及時(shí)做了修改。
2017-03-30更新(感謝網(wǎng)友ergeerge1建議):
修改BookController幾處錯(cuò)誤
1.detail方法不是返回json的,故不用加@ResponseBody注解
2.appoint方法應(yīng)該加上@ResponseBody注解
3.另外studentId參數(shù)注解應(yīng)該是@RequestParam
4.至于controller測試,測試appoint方法可不必寫jsp,用curl就行,比如
curl -H “Accept: application/json; charset=utf-8” -d “studentId=1234567890” localhost:8080/book/1003/appoint
————————————————
版權(quán)聲明:本文為CSDN博主「李奕鋒」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq598535550/java/article/details/51703190