本文發(fā)表于2016年6月入问,寫于作者學(xué)生時期。文中使用到的技術(shù)和框架可能不是當(dāng)下最佳實踐,甚至很不“優(yōu)雅”。但對于剛接觸JavaEE和Spring的同學(xué)來說锚扎,還是能有很多收獲的惯疙,大牛輕拍= =
我們看招聘信息的時候,經(jīng)常會看到這一點(diǎn),需要具備SSH框架的技能骑脱;而且在大部分教學(xué)課堂中岳瞭,也會把SSH作為最核心的教學(xué)內(nèi)容。
但是,我們在實際應(yīng)用中發(fā)現(xiàn),SpringMVC可以完全替代Struts摊沉,配合注解的方式苍柏,編程非彻卓茫快捷楼咳,而且通過restful風(fēng)格定義url,讓地址看起來非常優(yōu)雅烛恤。
另外母怜,MyBatis也可以替換Hibernate缚柏,正因為MyBatis的半自動特點(diǎn)苹熏,我們程序猿可以完全掌控SQL,這會讓有數(shù)據(jù)庫經(jīng)驗的程序猿能開發(fā)出高效率的SQL語句币喧,而且XML配置管理起來也非常方便轨域。
好了,如果你也認(rèn)同我的看法杀餐,那么下面我們一起來做整合吧干发!
在寫代碼之前我們先了解一下這三個框架分別是干什么的?
相信大以前也看過不少這些概念怜浅,我這就用大白話來講铐然,如果之前有了解過可以跳過這一大段,直接看代碼恶座!
SpringMVC:它用于web層搀暑,相當(dāng)于controller(等價于傳統(tǒng)的servlet和struts的action),用來處理用戶請求跨琳。舉個例子自点,用戶在地址欄輸入http://網(wǎng)站域名/login,那么springmvc就會攔截到這個請求脉让,并且調(diào)用controller層中相應(yīng)的方法桂敛,(中間可能包含驗證用戶名和密碼的業(yè)務(wù)邏輯,以及查詢數(shù)據(jù)庫操作溅潜,但這些都不是springmvc的職責(zé))术唬,最終把結(jié)果返回給用戶,并且返回相應(yīng)的頁面(當(dāng)然也可以只返回json/xml等格式數(shù)據(jù))滚澜。springmvc就是做前面和后面過程的活粗仓,與用戶打交道!设捐!
Spring:太強(qiáng)大了借浊,以至于我無法用一個詞或一句話來概括它。但與我們平時開發(fā)接觸最多的估計就是IOC容器萝招,它可以裝載bean(也就是我們java中的類蚂斤,當(dāng)然也包括service dao里面的),有了這個機(jī)制槐沼,我們就不用在每次使用這個類的時候為它初始化曙蒸,很少看到關(guān)鍵字new捌治。另外spring的aop,事務(wù)管理等等都是我們經(jīng)常用到的逸爵。
MyBatis:如果你問我它跟鼎鼎大名的Hibernate有什么區(qū)別具滴?我只想說,他更符合我的需求师倔。第一,它能自由控制sql周蹭,這會讓有數(shù)據(jù)庫經(jīng)驗的人(當(dāng)然不是說我啦捂臉)編寫的代碼能搞提升數(shù)據(jù)庫訪問的效率趋艘。第二,它可以使用xml的方式來組織管理我們的sql凶朗,因為一般程序出錯很多情況下是sql出錯瓷胧,別人接手代碼后能快速找到出錯地方,甚至可以優(yōu)化原來寫的sql棚愤。
SSM框架整合配置
好了搓萧,前面bb那么多,下面我們真正開始敲代碼了~
首先我們打開IED宛畦,我這里用的是eclipse(你們應(yīng)該也是用的這個瘸洛,對嗎?)次和,創(chuàng)建一個動態(tài)web項目反肋,建立好相應(yīng)的目錄結(jié)構(gòu)(重點(diǎn)!)
(打了馬賽克是因為這里還用不到踏施,你們不要那么污好不好石蔗?)
我說一下每個目錄都有什么用吧(第一次畫表格,我發(fā)現(xiàn)markdown的表格語法很不友好呀~)
這個目錄結(jié)構(gòu)同時也遵循maven的目錄規(guī)范~
文件名 作用
src 根目錄畅形,沒什么好說的养距,下面有main和test。
- main 主要目錄日熬,可以放java代碼和一些資源文件棍厌。
- - java 存放我們的java代碼,這個文件夾要使用Build Path -> Use as Source Folder碍遍,這樣看包結(jié)構(gòu)會方便很多定铜,新建的包就相當(dāng)于在這里新建文件夾咯。
- - resources 存放資源文件怕敬,譬如各種的spring揣炕,mybatis,log配置文件东跪。
- - - mapper 存放dao中每個方法對應(yīng)的sql畸陡,在這里配置鹰溜,無需寫daoImpl。
- - - spring 這里當(dāng)然是存放spring相關(guān)的配置文件丁恭,有dao service web三層曹动。
- - - sql 其實這個可以沒有,但是為了項目完整性還是加上吧牲览。
- - - webapp 這個貌似是最熟悉的目錄了墓陈,用來存放我們前端的靜態(tài)資源,如jsp js css第献。
- - - - resources 這里的資源是指項目的靜態(tài)資源贡必,如js css images等。
- - - - WEB-INF 很重要的一個目錄庸毫,外部瀏覽器無法訪問仔拟,只有羨慕內(nèi)部才能訪問,可以把jsp放在這里飒赃,另外就是web.xml了利花。你可能有疑問了,為什么上面java中的resources里面的配置文件不妨在這里载佳,那么是不是會被外部竊取到炒事?你想太多了,部署時候基本上只有webapp里的會直接輸出到根目錄刚盈,其他都會放入WEB-INF里面羡洛,項目內(nèi)部依然可以使用classpath:XXX來訪問,好像IDE里可以設(shè)置部署輸出目錄藕漱,這里扯遠(yuǎn)了~
- test 這里是測試分支欲侮。
- - java 測試java代碼,應(yīng)遵循包名相同的原則肋联,這個文件夾同樣要使用Build Path -> Use as Source Folder威蕉,這樣看包結(jié)構(gòu)會方便很多。
- - resources 沒什么好說的橄仍,好像也很少用到韧涨,但這個是maven的規(guī)范。
我先新建好幾個必要的包侮繁,并為大家講解一下每個包的作用虑粥,順便理清一下后臺的思路~
包名 名稱 作用
dao 數(shù)據(jù)訪問層(接口) 與數(shù)據(jù)打交道,可以是數(shù)據(jù)庫操作宪哩,也可以是文件讀寫操作娩贷,甚至是redis緩存操作,總之與數(shù)據(jù)操作有關(guān)的都放在這里锁孟,也有人叫做dal或者數(shù)據(jù)持久層都差不多意思彬祖。為什么沒有daoImpl茁瘦,因為我們用的是mybatis,所以可以直接在配置文件中實現(xiàn)接口的每個方法储笑。
entity 實體類 一般與數(shù)據(jù)庫的表相對應(yīng)甜熔,封裝dao層取出來的數(shù)據(jù)為一個對象,也就是我們常說的pojo突倍,一般只在dao層與service層之間傳輸腔稀。
dto 數(shù)據(jù)傳輸層 剛學(xué)框架的人可能不明白這個有什么用,其實就是用于service層與web層之間傳輸羽历,為什么不直接用entity(pojo)烧颖?其實在實際開發(fā)中發(fā)現(xiàn),很多時間一個entity并不能滿足我們的業(yè)務(wù)需求窄陡,可能呈現(xiàn)給用戶的信息十分之多,這時候就有了dto拆火,也相當(dāng)于vo跳夭,記住一定不要把這個混雜在entity里面,答應(yīng)我好嗎们镜?
service 業(yè)務(wù)邏輯(接口) 寫我們的業(yè)務(wù)邏輯币叹,也有人叫bll,在設(shè)計業(yè)務(wù)接口時候應(yīng)該站在“使用者”的角度模狭。額颈抚,不要問我為什么這里沒顯示!IDE調(diào)皮我也拿它沒辦法~
serviceImpl 業(yè)務(wù)邏輯(實現(xiàn)) 實現(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浑侥,放到項目WEB-INF/lib目錄下姊舵。關(guān)于maven的學(xué)習(xí)大家可以看慕課網(wǎng)的視頻教程,這里就不展開了寓落。我把項目用到的jar都寫在下面括丁,版本都不是最新的,大家有經(jīng)驗的話可以自己調(diào)整版本號零如。另外躏将,所有jar都會與項目一起打包放到我的github上锄弱,喜歡的給個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.日志 -->
<!-- 實現(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)行編碼工作了,堅持到這里辛苦大家了~
第一步:我們先在spring文件夾里新建spring-dao.xml文件祸憋,因為spring的配置太多会宪,我們這里分三層,分別是dao service web蚯窥。
讀入數(shù)據(jù)庫連接相關(guān)參數(shù)(可選)
配置數(shù)據(jù)連接池
配置連接屬性掸鹅,可以不讀配置項文件直接在這里寫死
配置c3p0,只配了幾個常用的
配置SqlSessionFactory對象(mybatis)
掃描dao層接口拦赠,動態(tài)實現(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)閉連接后不自動commit -->
<property name="autoCommitOnClose" value="false" />
<!-- 獲取連接超時時間 -->
<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接口包荷鼠,動態(tài)實現(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
因為數(shù)據(jù)庫配置相關(guān)參數(shù)是讀取配置文件,所以在resources文件夾里新建一個jdbc.properties文件允乐,存放我們4個最常見的數(shù)據(jù)庫連接屬性矮嫉,這是我本地的,大家記得修改呀~還有喜歡傳到github上“大頭蝦們”記得刪掉密碼牍疏,不然別人就很容易得到你服務(wù)器的數(shù)據(jù)庫配置信息蠢笋,然后干一些羞羞的事情,你懂的A墼伞昨寞!
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厦滤,可能會與系統(tǒng)環(huán)境中的username變量沖突援岩,所以到時候真正連接數(shù)據(jù)庫的時候,用戶名就被替換成系統(tǒng)中的用戶名(有得可能是administrator)馁害,那肯定是連接不成功的窄俏,這里有個小坑,我被坑了一晚上5獠恕凹蜈!
因為這里用到了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中某個方法返回一個string類型的"login"篙贸,實際上會返回"/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)自動注冊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
我們在項目中經(jīng)常會使用到日志,所以這里還有配置日志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個配置文件鳞滨,我們一起來看下最終的配置文件結(jié)構(gòu)圖洞焙。
SSM框架應(yīng)用實例(圖書管理系統(tǒng))
一開始想就這樣結(jié)束教程,但是發(fā)現(xiàn)其實很多人都還不會把這個SSM框架用起來拯啦,特別是mybatis部分澡匪。那我現(xiàn)在就以最常見的“圖書管理系統(tǒng)”中【查詢圖書】和【預(yù)約圖書】業(yè)務(wù)來做一個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 '館藏數(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è)計', 10),
(1001, '數(shù)據(jù)結(jié)構(gòu)', 10),
(1002, '設(shè)計模式', 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ù)約時間' ,
? 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包中添加兩個對應(yīng)的實體甸鸟,圖書實體Book.java和預(yù)約圖書實體Appointment.java。
Book.java
package com.soecode.lyf.entity;
public class Book {
private long bookId;// 圖書ID
private String name;// 圖書名稱
private int number;// 館藏數(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ù)約圖書實體
*/
public class Appointment {
private long bookId;// 圖書ID
private long studentId;// 學(xué)號
private Date appointTime;// 預(yù)約時間
// 多對一的復(fù)合屬性
private Book book;// 圖書實體
// 省略構(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);
/**
* 減少館藏數(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ù)約圖書記錄,并且攜帶圖書實體
*
* @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注解呢扯夭?是因為該方法有兩個或以上的參數(shù)鳍贾,一定要加鞍匾,不然mybatis識別不了。上面的BookDao接口的queryById方法和reduceNumber方法只有一個參數(shù)book_id骑科,所以可以不用加 @Param注解橡淑,當(dāng)然加了也無所謂~
注意,這里不需要實現(xiàn)dao接口不用編寫daoImpl纵散, mybatis會給我們動態(tài)實現(xiàn)梳码,但是我們需要編寫相應(yīng)的mapper。
在mapper目錄里新建兩個文件BookDao.xml和AppointmentDao.xml伍掀,分別對應(yīng)上面兩個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 主鍵沖突蜜笤,報錯 -->
INSERT ignore INTO appointment (book_id, student_id)
VALUES (#{bookId}, #{studentId})
</insert>
<select id="queryByKeyWithBook" resultType="Appointment">
<!-- 如何告訴MyBatis把結(jié)果映射到Appointment同時映射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ù)類型(這個其實可選)沪伙,最后#{...}中填寫的是方法的參數(shù),看懂了是不是很簡單O睾谩围橡!我也這么覺得~ 還有一個小技巧要交給大家,就是在返回Appointment對象包含了一個屬性名為book的Book對象缕贡,那么可以使用"book.屬性名"的方式來取值翁授,看上面queryByKeyWithBook方法的sql。
dao層寫完了晾咪,接下來test對應(yīng)的package寫我們測試方法吧收擦。
因為我們之后會寫很多測試方法,在測試前需要讓程序讀入spring-dao和mybatis等配置文件谍倦,所以我這里就抽離出來一個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啟動時加載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
因為spring-service在service層的測試中會時候到宴猾,這里也一起引入算了!
新建BookDaoTest.java和AppointmentDaoTest.java兩個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)備~
首先送淆,在寫我們的控制器之前税产,我們先定義幾個預(yù)約圖書操作返回碼的數(shù)據(jù)字典,也就是我們要返回給客戶端的信息。我們這類使用枚舉類辟拷,沒聽過的小伙伴要好好惡補(bǔ)一下了(我也是最近才學(xué)到的= =)
預(yù)約業(yè)務(wù)操作返回碼說明
返回碼 說明
1 預(yù)約成功
0 庫存不足
-1 重復(fù)預(yù)約
-2 系統(tǒng)異常
新建一個包叫enums撞羽,在里面新建一個枚舉類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用來存儲我們執(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包下新建三個文件
NoNumberException.java
RepeatAppointException.java
AppointException.java
預(yù)約業(yè)務(wù)異常類(都需要繼承RuntimeException)碌宴,分別是無庫存異常、重復(fù)預(yù)約異常蒙畴、預(yù)約未知錯誤異常贰镣,用于業(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è)計接口 三個方面:方法定義粒度蹬音,參數(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接口,并實現(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)隊達(dá)成一致約定楼入,明確標(biāo)注事務(wù)方法的編程風(fēng)格
* 2.保證事務(wù)方法的執(zhí)行時間盡可能短,不要穿插其他網(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);//錯誤寫法
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);//錯誤寫法
throw new RepeatAppointException("repeat appoint");
} else {// 預(yù)約成功
Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
return new AppointExecution(bookId, AppointStateEnum.SUCCESS, appointment);
}
}
// 要先于catch Exception異常前先catch住再拋出扬舒,不然自定義的異常也會被轉(zhuǎn)換為AppointException,導(dǎo)致控制層無法具體識別是哪個異常
} 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);//錯誤寫法
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è)務(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)該會出現(xiàn)“重復(fù)預(yù)約”,哈哈毫炉,我們所有的后臺代碼都通過單元測試?yán)瞺~是不是很開心~
咱們還需要在dto包里新建一個封裝json返回結(jié)果的類Result.java瓮栗,設(shè)計成泛型。
Result.java
package com.soecode.lyf.dto;
/**
* 封裝json對象,所有返回結(jié)果都使用它
*/
public class Result<T> {
private boolean success;// 是否成功標(biāo)志
private T data;// 成功時返回的數(shù)據(jù)
private String error;// 錯誤信息
public Result() {
}
// 成功時的構(gòu)造器
public Result(boolean success, T data) {
this.success = success;
this.data = data;
}
// 錯誤時的構(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);//錯誤寫法微服,不能統(tǒng)一返回,要處理異常(失斢Ю)情況
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
因為我比較懶以蕴,所以我們就不測試controller了,好討厭寫前端,嗚嗚嗚~
到此辛孵,我們的SSM框架整合配置丛肮,與應(yīng)用實例部分已經(jīng)結(jié)束了,我把所有源碼和jar包一起打包放在了我的GitHub上觉吭,需要的可以去下載腾供,喜歡就給個star吧,這篇東西寫了兩個晚上也不容易啊鲜滩。
完整代碼下載地址:https://github.com/liyifeng1994/ssm
2017-02-28更新(感謝網(wǎng)友EchoXml發(fā)現(xiàn)):
修改預(yù)約業(yè)務(wù)代碼伴鳖,失敗時拋異常,成功時才返回結(jié)果徙硅,控制層根據(jù)捕獲的異常返回相應(yīng)信息給客戶端榜聂,而不是業(yè)務(wù)層直接返回錯誤結(jié)果。上面的代碼已經(jīng)作了修改嗓蘑,而且錯誤示范也注釋保留著须肆,之前誤人子弟了,還好有位網(wǎng)友前幾天提出質(zhì)疑桩皿,我也及時做了修改豌汇。
2017-03-30更新(感謝網(wǎng)友ergeerge1建議):
修改BookController幾處錯誤
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