原文: https://blog.csdn.net/qq598535550/article/details/51703190
我們看招聘信息的時候涝婉,經(jīng)常會看到這一點嗓化,需要具備SSH框架的技能;而且在大部分教學課堂中瓜喇,也會把SSH作為最核心的教學內(nèi)容。
但是牲尺,我們在實際應(yīng)用中發(fā)現(xiàn),SpringMVC可以完全替代Struts幌蚊,配合注解的方式谤碳,編程非常快捷溢豆,而且通過restful風格定義url蜒简,讓地址看起來非常優(yōu)雅。
另外漩仙,MyBatis也可以替換Hibernate搓茬,正因為MyBatis的半自動特點,我們程序猿可以完全掌控SQL队他,這會讓有數(shù)據(jù)庫經(jīng)驗的程序猿能開發(fā)出高效率的SQL語句卷仑,而且XML配置管理起來也非常方便。
好了麸折,如果你也認同我的看法锡凝,那么下面我們一起來做整合吧!
在寫代碼之前我們先了解一下這三個框架分別是干什么的磕谅?
相信大以前也看過不少這些概念私爷,我這就用大白話來講,如果之前有了解過可以跳過這一大段膊夹,直接看代碼衬浑!
- SpringMVC:它用于web層,相當于controller(等價于傳統(tǒng)的servlet和struts的action)放刨,用來處理用戶請求工秩。舉個例子,用戶在地址欄輸入http://網(wǎng)站域名/login进统,那么springmvc就會攔截到這個請求助币,并且調(diào)用controller層中相應(yīng)的方法,(中間可能包含驗證用戶名和密碼的業(yè)務(wù)邏輯螟碎,以及查詢數(shù)據(jù)庫操作眉菱,但這些都不是springmvc的職責),最終把結(jié)果返回給用戶掉分,并且返回相應(yīng)的頁面(當然也可以只返回json/xml等格式數(shù)據(jù))俭缓。springmvc就是做前面和后面過程的活,與用戶打交道K止华坦!
- Spring:太強大了,以至于我無法用一個詞或一句話來概括它不从。但與我們平時開發(fā)接觸最多的估計就是IOC容器惜姐,它可以裝載bean(也就是我們java中的類,當然也包括service dao里面的)椿息,有了這個機制歹袁,我們就不用在每次使用這個類的時候為它初始化,很少看到關(guān)鍵字new寝优。另外spring的aop宇攻,事務(wù)管理等等都是我們經(jīng)常用到的。
- MyBatis:如果你問我它跟鼎鼎大名的Hibernate有什么區(qū)別倡勇?我只想說逞刷,他更符合我的需求。第一妻熊,它能自由控制sql夸浅,這會讓有數(shù)據(jù)庫經(jī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)(重點!)
(打了馬賽克是因為這里還用不到律秃,你們不要那么污好不好爬橡?)
我說一下每個目錄都有什么用吧(第一次畫表格,我發(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)會方便很多粘室,新建的包就相當于在這里新建文件夾咯。 |
- - resources | 存放資源文件卜范,譬如各種的spring衔统,mybatis,log配置文件海雪。 |
- - - mapper | 存放dao中每個方法對應(yīng)的sql锦爵,在這里配置,無需寫daoImpl奥裸。 |
- - - spring | 這里當然是存放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è)置部署輸出目錄明垢,這里扯遠了~ |
- 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ù)傳輸層 | 剛學框架的人可能不明白這個有什么用劫灶,其實就是用于service層與web層之間傳輸,為什么不直接用entity(pojo)掖桦?其實在實際開發(fā)中發(fā)現(xiàn)本昏,很多時間一個entity并不能滿足我們的業(yè)務(wù)需求,可能呈現(xiàn)給用戶的信息十分之多枪汪,這時候就有了dto涌穆,也相當于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控制器仁期,相當于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的學習大家可以看慕課網(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>
下面真的要開始進行編碼工作了墨微,堅持到這里辛苦大家了~
第一步:我們先在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" />
<!-- 當獲取連接失敗重試次數(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>
因為數(shù)據(jù)庫配置相關(guān)參數(shù)是讀取配置文件氓奈,所以在resources文件夾里新建一個jdbc.properties文件翘魄,存放我們4個最常見的數(shù)據(jù)庫連接屬性,這是我本地的舀奶,大家記得修改呀~還有喜歡傳到github上“大頭蝦們”記得刪掉密碼暑竟,不然別人就很容易得到你服務(wù)器的數(shù)據(jù)庫配置信息,然后干一些羞羞的事情育勺,你懂的5纭!
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3307/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=
友情提示:配置文件中的jdbc.username涧至,如果寫成username腹躁,可能會與系統(tǒng)環(huán)境中的username變量沖突,所以到時候真正連接數(shù)據(jù)庫的時候化借,用戶名就被替換成系統(tǒng)中的用戶名(有得可能是administrator)潜慎,那肯定是連接不成功的捡多,這里有個小坑蓖康,我被坑了一晚上!垒手!
因為這里用到了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" />
<!-- 使用列別名替換列名 默認:true -->
<setting name="useColumnLabel" value="true" />
<!-- 開啟駝峰命名轉(zhuǎn)換:Table{create_time} -> Entity{createTime} -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>
第二步:剛弄好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>
第三步:配置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默認讀寫支持
-->
<mvc:annotation-driven />
<!-- 2.靜態(tài)資源默認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>
第四步:最后就是修改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>
<!-- 默認匹配所有的請求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
我們在項目中經(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>
到目前為止,我們一共寫了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 '學號',
`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ù)約圖書表'
在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方法
}
Appointment.java
package com.soecode.lyf.entity;
import java.util.Date;
/**
* 預(yù)約圖書實體
*/
public class Appointment {
private long bookId;// 圖書ID
private long studentId;// 學號
private Date appointTime;// 預(yù)約時間
// 多對一的復(fù)合屬性
private Book book;// 圖書實體
// 省略構(gòu)造方法,getter和setter方法颊咬,toString方法
}
在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);
}
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);
}
提示:這里為什么要給方法的參數(shù)添加@Param注解呢喳篇?是因為該方法有兩個或以上的參數(shù)敞临,一定要加,不然mybatis識別不了麸澜。上面的BookDao接口的queryById方法和reduceNumber方法只有一個參數(shù)book_id,所以可以不用加 @Param注解痰憎,當然加了也無所謂~
注意票髓,這里不需要實現(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>
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>
mapper總結(jié):namespace是該xml對應(yīng)的接口全名,select和update中的id對應(yīng)方法名踪区,resultType是返回值類型昆烁,parameterType是參數(shù)類型(這個其實可選),最后#{...}中填寫的是方法的參數(shù)缎岗,看懂了是不是很簡單>材帷!我也這么覺得~ 還有一個小技巧要交給大家传泊,就是在返回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 {
}
因為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);
}
}
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());
}
}
AppointmentDaoTest測試結(jié)果
testInsertAppointment
testQueryByKeyWithBook
嗯芯杀,到這里一切到很順利那么我們繼續(xù)service層的編碼吧可能下面開始信息里比較大端考,大家要做好心理準備~
首先,在寫我們的控制器之前揭厚,我們先定義幾個預(yù)約圖書操作返回碼的數(shù)據(jù)字典却特,也就是我們要返回給客戶端的信息。我們這類使用枚舉類筛圆,沒聽過的小伙伴要好好惡補一下了(我也是最近才學到的= =)
預(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;
}
}
接下來提岔,在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)標識
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方法
}
接著碱蒙,在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);
}
}
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);
}
}
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);
}
}
咱們終于可以編寫業(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);
}
在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)點: 1.開發(fā)團隊達成一致約定走孽,明確標注事務(wù)方法的編程風格
* 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)換為運行期異常
//return new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);//錯誤寫法
throw new AppointException("appoint inner error:" + e.getMessage());
}
}
}
下面我們來測試一下我們的業(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);
}
}
BookServiceImplTest測試結(jié)果
testAppoint
首次執(zhí)行是“預(yù)約成功”符匾,如果再次執(zhí)行的話,應(yīng)該會出現(xiàn)“重復(fù)預(yù)約”瘩例,哈哈啊胶,我們所有的后臺代碼都通過單元測試啦~是不是很開心
咱們還需要在dto包里新建一個封裝json返回結(jié)果的類Result.java,設(shè)計成泛型垛贤。
Result.java
package com.soecode.lyf.dto;
/**
* 封裝json對象焰坪,所有返回結(jié)果都使用它
*/
public class Result<T> {
private boolean success;// 是否成功標志
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方法
}
最后,我們寫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}/細分 /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, "學號不能為空");
}
//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);
}
}
因為我比較懶禀酱,所以我們就不測試controller了,好討厭寫前端炬守,嗚嗚嗚~
到此,我們的SSM框架整合配置比勉,與應(yīng)用實例部分已經(jīng)結(jié)束了劳较,我把所有源碼和jar包一起打包放在了我的GitHub上驹止,需要的可以去下載,喜歡就給個star吧观蜗,這篇東西寫了兩個晚上也不容易啊臊恋。
完整代碼下載地址:https://github.com/liyifeng1994/ssm
作者:李奕鋒
原文:https://blog.csdn.net/qq598535550/article/details/51703190