1. 程式人生 > 實用技巧 >Zk學習筆記——修改節點

Zk學習筆記——修改節點

前言

如果看過前幾篇文章,對 SpringMyBatis 有了一定了解,一定想上手試試。這篇文章從 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。
  1. 載入資料庫配置
  2. 配置資料庫連線池
  3. 配置 SqlSessionFactory 物件(MyBatis)
  4. 配置掃描 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檔案。

  1. 使用自增主鍵
  2. 使用列別名
  3. 開啟駝峰命名轉換 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 檔案。

  1. 掃描 service 包所有註解 @Service
  2. 配置事務管理器,把事務管理交由 spring 來完成
  3. 基於註解的 宣告式事務,可以直接在方法上 @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 檔案。

  1. 開啟SpringMVC註解模式,可以使用@RequestMapping,@PathVariable,@ResponseBody等
  2. 對靜態資源處理,如js,css,jpg等
  3. 配置jsp 顯示ViewResolver,及渲染後的 JSP
  4. 掃描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 包下新建實體 BookAppointment

  • 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.javaAppointment.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.xmlAppointmentDao.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 對應的介面全名,selectupdate 中的 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.javaAppointmentDaoTest.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】。

覺得文章內容不錯,記得點贊或在看都行,這是對我最大的鼓勵!