Zk學習筆記——修改節點
前言
如果看過前幾篇文章,對
Spring
和MyBatis
有了一定了解,一定想上手試試。這篇文章從 0 到 1,手把手整合SSM
(Spring、Spring MVC、MyBatis)。
本篇是程式碼篇,在 PC 端瀏覽更佳,原始碼在文末
搭建整合 SSM 之 HelloWorld
開發環境
- idea
- MySql5.x
- jdk8
- maven
對應的技術入門在 公眾號 歷史文章 都可以找到
目錄
目錄包括 main、resources、mapper、webapp,不一一介紹,不熟悉檢視前面文章。
環境搭建
新建專案
這裡使用的是 IDEA 編輯器,新建一個 Maven 工程,選擇 web 專案。
匯入依賴
使用 Maven 管理專案 jar ,只需要在 pom.xml
加如相關依賴即可。
<?xmlversion="1.0"encoding="UTF-8"?>
<projectxmlns="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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>javapub.rodert.github</groupId>
<artifactId>ssm_helloword_web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<url></url>
<!--原始碼:https://github.com/Rodert/JavaPub-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.6.RELEASE</spring.version>
</properties>
<dependencies>
<!--單元測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--1.日誌-->
<!--實現slf4j介面並整合-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>
<!--2.資料庫-->
<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.Servletweb-->
<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>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--2)SpringDAO層-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!--3)Springweb-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--4)Springtest-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</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>
<!--註解-->
<!--https://mvnrepository.com/artifact/org.projectlombok/lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>ssm_helloword_web</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
<!--原始碼:https://github.com/Rodert/JavaPub-->
編碼
配置檔案
spring-dao.xml
- 先在spring資料夾裡新建spring-dao.xml檔案,我們這裡分三層,分別是dao service web。
- 載入資料庫配置
- 配置資料庫連線池
- 配置
SqlSessionFactory
物件(MyBatis) - 配置掃描 dao 層介面,動態代理實現 Dao 實現類,執行 sql 寫在 xml
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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.配置資料庫相關引數properties的屬性:${url}-->
<context:property-placeholderlocation="classpath:jdbc.properties"/>
<!--2.資料庫連線池-->
<beanid="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置連線池屬性-->
<propertyname="driverClass"value="${jdbc.driver}"/>
<propertyname="jdbcUrl"value="${jdbc.url}"/>
<propertyname="user"value="${jdbc.username}"/>
<propertyname="password"value="${jdbc.password}"/>
<!--c3p0連線池的私有屬性-->
<propertyname="maxPoolSize"value="30"/>
<propertyname="minPoolSize"value="10"/>
<!--關閉連線後不自動commit-->
<propertyname="autoCommitOnClose"value="false"/>
<!--獲取連線超時時間-->
<propertyname="checkoutTimeout"value="10000"/>
<!--當獲取連線失敗重試次數-->
<propertyname="acquireRetryAttempts"value="2"/>
</bean>
<!--3.配置SqlSessionFactory物件-->
<beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入資料庫連線池-->
<propertyname="dataSource"ref="dataSource"/>
<!--配置MyBaties全域性配置檔案:mybatis-config.xml-->
<propertyname="configLocation"value="classpath:Mybatis-config.xml"/>
<!--掃描entity包使用別名-->
<propertyname="typeAliasesPackage"value="javapub.rodert.github.entity"/>
<!--掃描sql配置檔案:mapper需要的xml檔案-->
<propertyname="mapperLocations"value="classpath:mapper/*.xml"/>
</bean>
<!--4.配置掃描Dao介面包,動態實現Dao介面,注入到spring容器中-->
<beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sqlSessionFactory-->
<propertyname="sqlSessionFactoryBeanName"value="sqlSessionFactory"/>
<!--給出需要掃描Dao介面包-->
<propertyname="basePackage"value="javapub.rodert.github.dao"/>
</bean>
</beans>
jdbc.properties
資料庫配置,在 resources
資料夾裡新建一個 jdbc.properties
檔案,注意自己的密碼。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm1?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=
mybatis-config.xml
MyBatis
核心檔案,在recources資料夾裡新建mybatis-config.xml檔案。
- 使用自增主鍵
- 使用列別名
- 開啟駝峰命名轉換 create_time -> createTime
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEconfiguration
PUBLIC"-//mybatis.org//DTDConfig3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--配置全域性屬性-->
<settings>
<!--使用jdbc的getGeneratedKeys獲取資料庫自增主鍵值-->
<settingname="useGeneratedKeys"value="true"/>
<!--使用列別名替換列名預設:true-->
<settingname="useColumnLabel"value="true"/>
<!--開啟駝峰命名轉換:Table{create_time}->Entity{createTime}-->
<settingname="mapUnderscoreToCamelCase"value="true"/>
</settings>
</configuration>
spring-service.xml
在 spring 資料夾裡新建 spring-service.xml
檔案。
- 掃描 service 包所有註解 @Service
- 配置事務管理器,把事務管理交由 spring 來完成
- 基於註解的 宣告式事務,可以直接在方法上
@Transaction
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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-scanbase-package="javapub.rodert.github.service"/>
<!--配置事務管理器-->
<beanid="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入資料庫連線池-->
<propertyname="dataSource"ref="dataSource"/>
</bean>
<!--配置基於註解的宣告式事務-->
<tx:annotation-driventransaction-manager="transactionManager"/>
</beans>
spring-web.xml
web 層,在 spring 資料夾裡新建 spring-web.xml
檔案。
- 開啟SpringMVC註解模式,可以使用@RequestMapping,@PathVariable,@ResponseBody等
- 對靜態資源處理,如js,css,jpg等
- 配置jsp 顯示ViewResolver,及渲染後的 JSP
- 掃描web層 @Controller
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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)提供一些列:資料繫結,數字和日期的format@NumberFormat,@DateTimeFormat,xml,json預設讀寫支援
-->
<mvc:annotation-driven/>
<!--2.靜態資源預設servlet配置
(1)加入對靜態資源的處理:js,gif,png
(2)允許使用"/"做整體對映
-->
<mvc:default-servlet-handler/>
<!--3.配置jsp顯示ViewResolver-->
<beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver">
<propertyname="viewClass"value="org.springframework.web.servlet.view.JstlView"/>
<!--<propertyname="contentType"value="text/html"/>-->
<propertyname="prefix"value="/WEB-INF/jsp/"/>
<propertyname="suffix"value=".jsp"/>
</bean>
<!--4.掃描web相關的bean-->
<context:component-scanbase-package="javapub.rodert.github.web"/>
</beans>
web.xml
修改 web.xml
檔案了,它在 webapp 的 WEB-INF 下。也可以在這裡配置過濾器、監聽器等。
<web-appxmlns="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>dispatcherServlet</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>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--預設匹配所有的請求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
日誌
配置一些簡單的日誌,使用 logback
,在 resources
資料夾裡新建logback.xml
檔案。
<?xmlversion="1.0"encoding="UTF-8"?>
<configurationdebug="true">
<appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">
<!--encodersarebydefaultassignedthetypech.qos.logback.classic.encoder.PatternLayoutEncoder-->
<encoder>
<pattern>%d{HH:mm:ss.SSS}[%thread]%-5level%logger{36}-%msg%n</pattern>
</encoder>
</appender>
<rootlevel="debug">
<appender-refref="STDOUT"/>
</root>
</configuration>
配置說明
以上配置是整合 SSM
的基礎配置,目錄結構如圖所示:
SSM例項-圖書管理系統
sql
以上部分整個 SSM 框架就已經搭建好了,下面是一個 Demo ,供參考。
新建倆張表,圖書表 book
和 預約圖書表 appointment
,並初始化資料。
/*
NavicatMySQLDataTransfer
SourceServer:localhost
SourceServerVersion:50716
SourceHost:localhost:3306
SourceDatabase:ssm1
TargetServerType:MYSQL
TargetServerVersion:50716
FileEncoding:65001
Date:2020-07-1216:50:43
*/
SETFOREIGN_KEY_CHECKS=0;
------------------------------
--Tablestructureforappointment
------------------------------
DROPTABLEIFEXISTS`appointment`;
CREATETABLE`appointment`(
`book_id`bigint(20)NOTNULLCOMMENT'圖書ID',
`student_id`bigint(20)NOTNULLCOMMENT'學號',
`appoint_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'預約時間',
PRIMARYKEY(`book_id`,`student_id`),
KEY`idx_appoint_time`(`appoint_time`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='預約圖書表';
------------------------------
--Recordsofappointment
------------------------------
------------------------------
--Tablestructureforbook
------------------------------
DROPTABLEIFEXISTS`book`;
CREATETABLE`book`(
`book_id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'圖書ID',
`name`varchar(100)NOTNULLCOMMENT'圖書名稱',
`number`int(11)NOTNULLCOMMENT'館藏數量',
PRIMARYKEY(`book_id`)
)ENGINE=InnoDBAUTO_INCREMENT=1004DEFAULTCHARSET=utf8COMMENT='圖書表';
------------------------------
--Recordsofbook
------------------------------
INSERTINTO`book`VALUES('2000','Java程式設計','20');
INSERTINTO`book`VALUES('2001','資料結構','7');
INSERTINTO`book`VALUES('2002','設計模式','20');
INSERTINTO`book`VALUES('2003','編譯原理','20');
實體
在 entity
包下新建實體 Book
和 Appointment
- Book.java
packagejavapub.rodert.github.entity;
importlombok.Getter;
importlombok.Setter;
importlombok.ToString;
/**
*@authorwangshiyurodert
*@date2020/7/620:58
*@description
*
*@Data註解,簡化程式碼,自動新增getsettoSting方法
*@Getter
*@Setter
*@ToString
*/
@Getter
@Setter
@ToString
publicclassBook{
privatelongbookId;//圖書ID
privateStringname;//圖書名稱
privateintnumber;//館藏數量
//省略構造方法,getter和setter方法,toString方法
}
- Appointment.java
packagejavapub.rodert.github.entity;
/**
*@authorwangshiyurodert
*@date2020/7/620:58
*@description
*/
importlombok.Data;
importlombok.Getter;
importlombok.Setter;
importlombok.ToString;
importjava.util.Date;
/**
*預約圖書實體
*@Data註解,簡化程式碼,自動新增getsettoSting方法
*/
@Data
publicclassAppointment{
privatelongbookId;//圖書ID
privatelongstudentId;//學號
privateDateappointTime;//預約時間
//多對一的複合屬性
privateBookbook;//圖書實體
//省略構造方法,getter和setter方法,toString方法
}
dao介面
在dao
包新建介面 BookDao.java
和 Appointment.java
- BookDao.java
packagejavapub.rodert.github.dao;
/**
*@authorwangshiyurodert
*@date2020/7/621:01
*@description
*/
importjavapub.rodert.github.entity.Book;
importorg.apache.ibatis.annotations.Param;
importjava.util.List;
publicinterfaceBookDao{
/**
*通過ID查詢單本圖書
*
*@paramid
*@return
*/
BookqueryById(longid);
/**
*查詢所有圖書
*
*@paramoffset查詢起始位置
*@paramlimit查詢條數
*@return
*/
List<Book>queryAll(@Param("offset")intoffset,@Param("limit")intlimit);
/**
*減少館藏數量
*
*@parambookId
*@return如果影響行數等於>1,表示更新的記錄行數
*/
intreduceNumber(longbookId);
}
- AppointmentDao.java
packagejavapub.rodert.github.dao;
/**
*@authorwangshiyurodert
*@date2020/7/621:01
*@description
*/
importjavapub.rodert.github.entity.Appointment;
importorg.apache.ibatis.annotations.Param;
publicinterfaceAppointmentDao{
/**
*插入預約圖書記錄
*
*@parambookId
*@paramstudentId
*@return插入的行數
*/
intinsertAppointment(@Param("bookId")longbookId,@Param("studentId")longstudentId);
/**
*通過主鍵查詢預約圖書記錄,並且攜帶圖書實體
*
*@parambookId
*@paramstudentId
*@return
*/
AppointmentqueryByKeyWithBook(@Param("bookId")longbookId,@Param("studentId")longstudentId);
}
提示:這裡為什麼要給方法的引數新增 @Param
註解呢?是因為該方法有兩個或以上的引數,一定要加,不然 mybatis
識別不了。上面的 BookDao
介面的 queryById
方法和 reduceNumber
方法只有一個引數 book_id
,所以可以不用加 @Param
註解。
dao介面xml
這裡不需要寫 dao介面
的實現類,mybatis會幫我們動態實現,上面我們已經在 spring-dao.xml
配置了動態掃描。現在需要編寫相應的 mapper
。 在 mapper
目錄裡新建兩個檔案 BookDao.xml
和 AppointmentDao.xml
,分別對應上面兩個dao介面。
- BookDao.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEmapper
PUBLIC"-//mybatis.org//DTDMapper3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="javapub.rodert.github.dao.BookDao">
<!--目的:為dao介面方法提供sql語句配置-->
<selectid="queryById"resultType="Book"parameterType="long">
<!--具體的sql-->
SELECT
book_id,
name,
number
FROM
book
WHERE
book_id=#{bookId}
</select>
<selectid="queryAll"resultType="Book">
SELECT
book_id,
name,
number
FROM
book
ORDERBY
book_id
LIMIT#{offset},#{limit}
</select>
<updateid="reduceNumber">
UPDATEbook
SETnumber=number-1
WHERE
book_id=#{bookId}
ANDnumber>0
</update>
</mapper>
- AppointmentDao.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEmapper
PUBLIC"-//mybatis.org//DTDMapper3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="javapub.rodert.github.dao.AppointmentDao">
<insertid="insertAppointment">
<!--ignore主鍵衝突,報錯-->
INSERTignoreINTOappointment(book_id,student_id)
VALUES(#{bookId},#{studentId})
</insert>
<selectid="queryByKeyWithBook"resultType="Appointment">
<!--如何告訴MyBatis把結果對映到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
appointmenta
INNERJOINbookbONa.book_id=b.book_id
WHERE
a.book_id=#{bookId}
ANDa.student_id=#{studentId}
</select>
</mapper>
mapper 說明:namespace
是 xml 對應的介面全名,select
和 update
中的 id
對應方法名(唯一),resultType
是返回值型別,parameterType
是引數型別(這個其實可選),#{...}
中填寫的是方法的引數
dao介面測試
現在的寫法是從資料庫層向前(web)寫,現在測試一下 dao
介面,編寫測試類。
因為每次測試都要載入配置檔案,所有抽離一個類(BaseTest
),每次測試方法都繼承它。
- BaseTest.java
packagejavapub.rodert.github;
/**
*@authorwangshiyurodert
*@date2020/7/621:07
*@description
*/
importorg.junit.runner.RunWith;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
*配置spring和junit整合,junit啟動時載入springIOC容器spring-test,junit
*/
@RunWith(SpringJUnit4ClassRunner.class)
//告訴junitspring配置檔案
@ContextConfiguration({"classpath:spring/spring-dao.xml","classpath:spring/spring-service.xml"})
publicclassBaseTest{
}
新建 BookDaoTest.java
和 AppointmentDaoTest.java
兩個 dao 測試檔案。
- BookDao.java
packagejavapub.rodert.github.dao;
/**
*@authorwangshiyurodert
*@date2020/7/621:08
*@description
*/
importjavapub.rodert.github.BaseTest;
importjavapub.rodert.github.entity.Book;
importorg.junit.Test;
importorg.springframework.beans.factory.annotation.Autowired;
importjava.util.List;
publicclassBookDaoTestextendsBaseTest{
@Autowired
privateBookDaobookDao;
@Test
publicvoidtestQueryById()throwsException{
longbookId=1000;
Bookbook=bookDao.queryById(bookId);
System.out.println(book);
}
@Test
publicvoidtestQueryAll()throwsException{
List<Book>books=bookDao.queryAll(0,4);
for(Bookbook:books){
System.out.println(book);
}
}
@Test
publicvoidtestReduceNumber()throwsException{
longbookId=1000;
intupdate=bookDao.reduceNumber(bookId);
System.out.println("update="+update);
}
}
- AppointmentDaoTest.java
packagejavapub.rodert.github.dao;
/**
*@authorwangshiyurodert
*@date2020/7/621:18
*@description
*/
importjavapub.rodert.github.BaseTest;
importjavapub.rodert.github.entity.Appointment;
importorg.junit.Test;
importorg.springframework.beans.factory.annotation.Autowired;
publicclassAppointmentDaoTestextendsBaseTest{
@Autowired
privateAppointmentDaoappointmentDao;
@Test
publicvoidtestInsertAppointment()throwsException{
longbookId=1000;
longstudentId=12345678910L;
intinsert=appointmentDao.insertAppointment(bookId,studentId);
System.out.println("insert="+insert);
}
@Test
publicvoidtestQueryByKeyWithBook()throwsException{
longbookId=1000;
longstudentId=12345678910L;
Appointmentappointment=appointmentDao.queryByKeyWithBook(bookId,studentId);
System.out.println(appointment);
System.out.println(appointment.getBook());
}
}
- BookDaoTest.java -- > testQueryById()
測試方法都驗證過,沒有問題,不一一測試了
業務層-結果集封裝
到這裡,我們的 dao
層,及資料庫介面操作都沒有問題,下面開始業務層編寫。
如果你有實戰專案經驗,那一定會發現,對於後端介面,我們都會定義一個統一的返回格式,及定義一個返回標準方便前端解析,如下:
{
"code":200,
"message":"成功",
"result":{},
"isSuccess":true
}
開始寫我們的程式碼,新建列舉類,用來定義預約業務的資料字典。如果不太明白,先看程式碼,後面在 JavaPub 微信公眾號文章索引中查詢對應文章。
新建一個包叫 enums
,在裡面新建一個列舉類 AppointStateEnum.java
。
- AppointStateEnum.java
packagejavapub.rodert.github.enums;
/**
*@authorwangshiyurodert
*@date2020/7/621:20
*@description
*/
/**
*使用列舉表述常量資料字典
*/
publicenumAppointStateEnum{
SUCCESS(1,"預約成功"),NO_NUMBER(0,"庫存不足"),REPEAT_APPOINT(-1,"重複預約"),INNER_ERROR(-2,"系統異常");
privateintstate;
privateStringstateInfo;
privateAppointStateEnum(intstate,StringstateInfo){
this.state=state;
this.stateInfo=stateInfo;
}
publicintgetState(){
returnstate;
}
publicStringgetStateInfo(){
returnstateInfo;
}
publicstaticAppointStateEnumstateOf(intindex){
for(AppointStateEnumstate:values()){
if(state.getState()==index){
returnstate;
}
}
returnnull;
}
}
在 dto
包下新建 AppointExecution.java
用來儲存我們執行預約操作的返回結果。
- AppointExecution.java
packagejavapub.rodert.github.dto;
/**
*@authorwangshiyurodert
*@date2020/7/716:26
*@description
*/
importjavapub.rodert.github.entity.Appointment;
importjavapub.rodert.github.enums.AppointStateEnum;
importlombok.Data;
/**
*封裝預約執行後結果
*/
@Data
publicclassAppointExecution{
//圖書ID
privatelongbookId;
//秒殺預約結果狀態
privateintstate;
//狀態標識
privateStringstateInfo;
//預約成功物件
privateAppointmentappointment;
publicAppointExecution(){
}
//預約失敗的構造器
publicAppointExecution(longbookId,AppointStateEnumstateEnum){
this.bookId=bookId;
this.state=stateEnum.getState();
this.stateInfo=stateEnum.getStateInfo();
}
//預約成功的構造器
publicAppointExecution(longbookId,AppointStateEnumstateEnum,Appointmentappointment){
this.bookId=bookId;
this.state=stateEnum.getState();
this.stateInfo=stateEnum.getStateInfo();
this.appointment=appointment;
}
//省略getter和setter方法,toString方法
}
在 exception
包下新建三個檔案
NoNumberException.java
RepeatAppointException.java
AppointException.java
預約業務異常類(都需要繼承 RuntimeException
---執行時異常類),分別是無庫存異常、重複預約異常、預約未知錯誤異常,用於業務層非成功情況下的返回(即成功返回結果,失敗丟擲異常)。為事務做準備。
- AppointException.java
packagejavapub.rodert.github.exception;
/**
*@authorwangshiyurodert
*@date2020/7/716:31
*@description
*/
/**
*預約業務異常
*/
publicclassAppointExceptionextendsRuntimeException{
publicAppointException(Stringmessage){
super(message);
}
publicAppointException(Stringmessage,Throwablecause){
super(message,cause);
}
}
- NoNumberException.java
/**
*@authorwangshiyurodert
*@date2020/7/716:30
*@description
*/
packagejavapub.rodert.github.exception;
/**
*庫存不足異常
*/
publicclassNoNumberExceptionextendsRuntimeException{
publicNoNumberException(Stringmessage){
super(message);
}
publicNoNumberException(Stringmessage,Throwablecause){
super(message,cause);
}
}
- RepeatAppointException.java
/**
*@authorwangshiyurodert
*@date2020/7/716:31
*@description
*/
packagejavapub.rodert.github.exception;
/**
*重複預約異常
*/
publicclassRepeatAppointExceptionextendsRuntimeException{
publicRepeatAppointException(Stringmessage){
super(message);
}
publicRepeatAppointException(Stringmessage,Throwablecause){
super(message,cause);
}
}
Service 業務介面程式碼
在service包下新建BookService.java圖書業務介面。
- BookService.java
/**
*@authorwangshiyurodert
*@date2020/7/716:32
*@description
*/
packagejavapub.rodert.github.service;
importjavapub.rodert.github.dto.AppointExecution;
importjavapub.rodert.github.entity.Book;
importjava.util.List;
/**
*業務介面:站在"使用者"角度設計介面三個方面:方法定義粒度,引數,返回型別(return型別/異常)
*/
publicinterfaceBookService{
/**
*查詢一本圖書
*
*@parambookId
*@return
*/
BookgetById(longbookId);
/**
*查詢所有圖書
*
*@return
*/
List<Book>getList();
/**
*預約圖書
*
*@parambookId
*@paramstudentId
*@return
*/
AppointExecutionappoint(longbookId,longstudentId);
}
在 service.impl
包下新建 BookServiceImpl.java
使用 BookService
介面,並實現裡面的方法。
- BookServiceImpl.java
/**
*@authorwangshiyurodert
*@date2020/7/716:39
*@description
*/
packagejavapub.rodert.github.service.impl;
importjavapub.rodert.github.dao.AppointmentDao;
importjavapub.rodert.github.dao.BookDao;
importjavapub.rodert.github.dto.AppointExecution;
importjavapub.rodert.github.entity.Appointment;
importjavapub.rodert.github.entity.Book;
importjavapub.rodert.github.enums.AppointStateEnum;
importjavapub.rodert.github.exception.AppointException;
importjavapub.rodert.github.exception.NoNumberException;
importjavapub.rodert.github.exception.RepeatAppointException;
importjavapub.rodert.github.service.BookService;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importorg.springframework.transaction.annotation.Transactional;
importjava.util.List;
@Service
publicclassBookServiceImplimplementsBookService{
privateLoggerlogger=LoggerFactory.getLogger(this.getClass());
//注入Service依賴
@Autowired
privateBookDaobookDao;
@Autowired
privateAppointmentDaoappointmentDao;
@Override
publicBookgetById(longbookId){
returnbookDao.queryById(bookId);
}
@Override
publicList<Book>getList(){
returnbookDao.queryAll(0,1000);
}
@Override
@Transactional
/**
*使用註解控制事務方法的優點:
*1.開發團隊達成一致約定,明確標註事務方法的程式設計風格
*2.保證事務方法的執行時間儘可能短,不要穿插其他網路操作,RPC/HTTP請求或者剝離到事務方法外部
*3.不是所有的方法都需要事務,如只有一條修改操作,只讀操作不需要事務控制
*/
publicAppointExecutionappoint(longbookId,longstudentId){
try{
//減庫存
intupdate=bookDao.reduceNumber(bookId);
if(update<=0){//庫存不足
//returnnewAppointExecution(bookId,AppointStateEnum.NO_NUMBER);//錯誤寫法
thrownewNoNumberException("nonumber");//丟擲異常,保證觸發事務執行
}else{
//執行預約操作
intinsert=appointmentDao.insertAppointment(bookId,studentId);
if(insert<=0){//重複預約
//returnnewAppointExecution(bookId,AppointStateEnum.REPEAT_APPOINT);//錯誤寫法
thrownewRepeatAppointException("repeatappoint");
}else{//預約成功
Appointmentappointment=appointmentDao.queryByKeyWithBook(bookId,studentId);
returnnewAppointExecution(bookId,AppointStateEnum.SUCCESS,appointment);
}
}
//要先於catchException異常前先catch住再丟擲,不然自定義的異常也會被轉換為AppointException,導致控制層無法具體識別是哪個異常
}catch(NoNumberException|RepeatAppointExceptione1){
throwe1;
}catch(Exceptione){
logger.error(e.getMessage(),e);
//所有編譯期異常轉換為執行期異常
//returnnewAppointExecution(bookId,AppointStateEnum.INNER_ERROR);//錯誤寫法
thrownewAppointException("appointinnererror:"+e.getMessage());
}
}
}
實現類使用了我們上邊定義的異常方法 RepeatAppointException
,用於業務層非成功情況下的返回(即成功返回結果,失敗丟擲異常)。觸發事務。
測試一下業務層程式碼,這裡演示預約圖書業務。
- BookServiceImplTest.java
/**
*@authorwangshiyurodert
*@date2020/7/716:40
*@description
*/
packagejavapub.rodert.github.service.impl;
importjavapub.rodert.github.BaseTest;
importjavapub.rodert.github.dto.AppointExecution;
importjavapub.rodert.github.service.BookService;
importorg.junit.Test;
importorg.springframework.beans.factory.annotation.Autowired;
publicclassBookServiceImplTestextendsBaseTest{
@Autowired
privateBookServicebookService;
@Test
publicvoidtestAppoint()throwsException{
longbookId=1001;
longstudentId=12345678910L;
AppointExecutionexecution=bookService.appoint(bookId,studentId);
System.out.println(execution);
}
}
測試結果:
首次執行是“預約成功”,如果再次執行的話,應該會出現“重複預約”,至此,我們所有的後臺程式碼都通過單元測試啦~~ 是不是很開心~
咱們還需要在dto包裡新建一個封裝json返回結果的類Result.java,設計成泛型。
- Result.java
packagejavapub.rodert.github.dto;
/**
*@authorwangshiyurodert
*@date2020/7/721:00
*@description
*/
importlombok.Data;
/**
*封裝json物件,所有返回結果都使用它
*/
@Data
publicclassResult<T>{
privatebooleansuccess;//是否成功標誌
privateTdata;//成功時返回的資料
privateStringerror;//錯誤資訊
publicResult(){
}
//成功時的構造器
publicResult(booleansuccess,Tdata){
this.success=success;
this.data=data;
}
//錯誤時的構造器
publicResult(booleansuccess,Stringerror){
this.success=success;
this.error=error;
}
//省略getter和setter方法使用註解代替
}
web層
web 層,也就是 controller 層,我們在web包下新建BookController.java檔案。
- BookController.java
packagejavapub.rodert.github.web;
/**
*@authorwangshiyurodert
*@date2020/7/721:05
*@description
*/
importjavapub.rodert.github.dto.AppointExecution;
importjavapub.rodert.github.dto.Result;
importjavapub.rodert.github.entity.Book;
importjavapub.rodert.github.enums.AppointStateEnum;
importjavapub.rodert.github.exception.NoNumberException;
importjavapub.rodert.github.exception.RepeatAppointException;
importjavapub.rodert.github.service.BookService;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.*;
importorg.springframework.web.servlet.ModelAndView;
importjava.util.List;
@RestController
@RequestMapping("/book")//url:/模組/資源/{id}/細分/seckill/list
publicclassBookController{
privateLoggerlogger=LoggerFactory.getLogger(this.getClass());
@Autowired
privateBookServicebookService;
@RequestMapping(value="/test")
publicModelAndViewtest(){
ModelAndViewmodelAndView=newModelAndView();
modelAndView.setViewName("book/test");
modelAndView.addObject("key","welcomejavaPub");
returnmodelAndView;
}
@RequestMapping(value="/list",method=RequestMethod.GET)
privateStringlist(Modelmodel){
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)
privateStringdetail(@PathVariable("bookId")LongbookId,Modelmodel){
if(bookId==null){
return"redirect:/book/list";
}
Bookbook=bookService.getById(bookId);
if(book==null){
return"forward:/book/list";
}
model.addAttribute("book",book);
return"detail";
}
//ajaxjson
//method=RequestMethod.POST,
@RequestMapping(value="/{bookId}/appoint",produces={
"application/json;charset=utf-8"})
@ResponseBody
privateResult<AppointExecution>appoint(@PathVariable("bookId")LongbookId,@RequestParam("studentId")LongstudentId){
if(studentId==null||studentId.equals("")){
returnnewResult<>(false,"學號不能為空");
}
//AppointExecutionexecution=bookService.appoint(bookId,studentId);//錯誤寫法,不能統一返回,要處理異常(失敗)情況
AppointExecutionexecution=null;
try{
execution=bookService.appoint(bookId,studentId);
}catch(NoNumberExceptione1){
execution=newAppointExecution(bookId,AppointStateEnum.NO_NUMBER);
}catch(RepeatAppointExceptione2){
execution=newAppointExecution(bookId,AppointStateEnum.REPEAT_APPOINT);
}catch(Exceptione){
execution=newAppointExecution(bookId,AppointStateEnum.INNER_ERROR);
}
returnnewResult<AppointExecution>(true,execution);
}
}
目前大多專案都是前後端分離,我們作為服務端,一般和前端通過介面資料互動(json),像介面方法 appoint
,應該新增 @ResponseBody
註解。 測試 controller --> appoint
方法可以通過 curl
,如:
curl -H “Accept: application/json; charset=utf-8” -d “studentId=1234567890” localhost:8080/book/1003/appoint
執行專案
現在整個專案全部完成,配置tomcat,通過左上角引入 tomcat ,選擇我們的專案 ssm。
啟動成功後:
這裡對前端程式碼只寫較少部分,具體可參考 BookController
--> book/test
介面,有需要幫助請留言。
- BookController.java --> book/test
@RequestMapping(value="/test")
publicModelAndViewtest(){
ModelAndViewmodelAndView=newModelAndView();
modelAndView.setViewName("book/test");
modelAndView.addObject("key","welcomejavaPub");
returnmodelAndView;
}
- test.jsp
<%@pagelanguage="java"contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPEHTML>
<html>
<head>
<%@pageisELIgnored="false"%>
<metacharset="UTF-8">
<metaname="viewport"content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<title>newsdetail</title>
</head>
<bodyclass="fn-pd24">
<h1>大家好${key}</h1>
<ahref="https://mp.weixin.qq.com/s/kfyRAPnRDp8LLktjgd658Q">JavaPub知識清單</a>
</body>
</html>
通過 ModelAndView
將我們需要渲染的資料儲存傳輸到對應檢視,由 Sping MVC 定義好的檢視解析器對該物件解析,最後將結果資料顯示到指定頁面。
完整程式碼地址:https://github.com/Rodert/JavaPub/code/ssm_helloworld_web/
最後:知識點總結
文章底部都有對應原創PDF,持續更新中, 教程純手打,致力於最實用教程,微信搜:
JavaPub
,無套路領取免費原創 PDF 、學習路線圖,後臺回覆【666】。
- 51頁MyBatis
- 19頁Maven
- 14頁zookeeper
- 10頁Git
- 18頁spring
- 8頁布隆過濾器
- 50頁排序演演算法
- redis系列文章
- dubbo
- nginx
- ffmpeg
- Jenkins
- http
- ...
覺得文章內容不錯,記得點贊或在看都行,這是對我最大的鼓勵!