SSM整合 最容易看懂的

SSM框架整合配置

首先我们打开IED eclipse,创建一个动态web项目,建立好相应的目录结构(重点!)

项目结构图


我说一下每个目录都有什么用吧(第一次画表格,我发现markdown的表格语法很不友好呀~)
这个目录结构同时也遵循maven的目录规范~

文件名作用
src根目录,没什么好说的,下面有main和test。
main主要目录,可以放java代码和一些资源文件。
java存放我们的java代码,这个文件夹要使用Build Path -> Use as Source Folder,这样看包结构会方便很多,新建的包就相当于在这里新建文件夹咯。
resources存放资源文件,譬如各种的spring,mybatis,log配置文件。
mapper存放dao中每个方法对应的sql,在这里配置,无需写daoImpl。
spring这里当然是存放spring相关的配置文件,有dao service web三层。
sql其实这个可以没有,但是为了项目完整性还是加上吧。
webapp这个貌似是最熟悉的目录了,用来存放我们前端的静态资源,如jsp js css。
resources这里的资源是指项目的静态资源,如js css images等。
WEB-INF很重要的一个目录,外部浏览器无法访问,只有羡慕内部才能访问,可以把jsp放在这里,另外就是web.xml了。你可能有疑问了,为什么上面java中的resources里面的配置文件不妨在这里,那么是不是会被外部窃取到?你想太多了,部署时候基本上只有webapp里的会直接输出到根目录,其他都会放入WEB-INF里面,项目内部依然可以使用classpath:XXX来访问,好像IDE里可以设置部署输出目录,这里扯远了~
test这里是测试分支。
java测试java代码,应遵循包名相同的原则,这个文件夹同样要使用Build Path -> Use as Source Folder,这样看包结构会方便很多。
resources没什么好说的,好像也很少用到,但这个是maven的规范。

我先新建好几个必要的,并为大家讲解一下每个包的作用,顺便理清一下后台的思路~

包结构图

包名名称作用
dao数据访问层(接口)与数据打交道,可以是数据库操作,也可以是文件读写操作,甚至是redis缓存操作,总之与数据操作有关的都放在这里,也有人叫做dal或者数据持久层都差不多意思。为什么没有daoImpl,因为我们用的是mybatis,所以可以直接在配置文件中实现接口的每个方法。
entity实体类一般与数据库的表相对应,封装dao层取出来的数据为一个对象,也就是我们常说的pojo,一般只在dao层与service层之间传输。
dto数据传输层刚学框架的人可能不明白这个有什么用,其实就是用于service层与web层之间传输,为什么不直接用entity(pojo)?其实在实际开发中发现,很多时间一个entity并不能满足我们的业务需求,可能呈现给用户的信息十分之多,这时候就有了dto,也相当于vo,记住一定不要把这个混杂在entity里面,答应我好吗?
service业务逻辑(接口)写我们的业务逻辑,也有人叫bll,在设计业务接口时候应该站在“使用者”的角度。额,不要问我为什么这里没显示!IDE调皮我也拿它没办法~
serviceImpl业务逻辑(实现)实现我们业务接口,一般事务控制是写在这里,没什么好说的。
web控制器springmvc就是在这里发挥作用的,一般人叫做controller控制器,相当于struts中的action。

还有最后一步基础工作,导入我们相应的jar包,我使用的是maven来管理我们的jar,所以只需要在poom.xml中加入相应的依赖就好了,如果不使用maven的可以自己去官网下载相应的jar,放到项目WEB-INF/lib目录下。关于maven的学习大家可以看慕课网的视频教程,这里就不展开了。我把项目用到的jar都写在下面,版本都不是最新的,大家有经验的话可以自己调整版本号。另外,所有jar都会与项目一起打包放到我的github上,喜欢的给个star吧~

poom.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
     <modelversion> 4.0 . 0 </modelversion>
     <groupid>com.soecode.ssm</groupid>
     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>
             junit</artifactid>
             <version> 4.11 </version>
         </dependency>
 
         <!-- 1 .日志 -->
         <!-- 实现slf4j接口并整合 -->
         <dependency>
             <groupid>ch.qos.logback</groupid>
             logback-classic</artifactid>
             <version> 1.1 . 1 </version>
         </dependency>
 
         <!-- 2 .数据库 -->
         <dependency>
             <groupid>mysql</groupid>
             mysql-connector-java</artifactid>
             <version> 5.1 . 37 </version>
             <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupid>c3p0</groupid>
             c3p0</artifactid>
             <version> 0.9 . 1.2 </version>
         </dependency>
 
         <!-- DAO: MyBatis -->
         <dependency>
             <groupid>org.mybatis</groupid>
             mybatis</artifactid>
             <version> 3.3 . 0 </version>
         </dependency>
         <dependency>
             <groupid>org.mybatis</groupid>
             mybatis-spring</artifactid>
             <version> 1.2 . 3 </version>
         </dependency>
 
         <!-- 3 .Servlet web -->
         <dependency>
             <groupid>taglibs</groupid>
             standard</artifactid>
             <version> 1.1 . 2 </version>
         </dependency>
         <dependency>
             <groupid>jstl</groupid>
             jstl</artifactid>
             <version> 1.2 </version>
         </dependency>
         <dependency>
             <groupid>com.fasterxml.jackson.core</groupid>
             jackson-databind</artifactid>
             <version> 2.5 . 4 </version>
         </dependency>
         <dependency>
             <groupid>javax.servlet</groupid>
             javax.servlet-api</artifactid>
             <version> 3.1 . 0 </version>
         </dependency>
 
         <!-- 4 .Spring -->
         <!-- 1 )Spring核心 -->
         <dependency>
             <groupid>org.springframework</groupid>
             spring-core</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
         <dependency>
             <groupid>org.springframework</groupid>
             spring-beans</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
         <dependency>
             <groupid>org.springframework</groupid>
             spring-context</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
         <!-- 2 )Spring DAO层 -->
         <dependency>
             <groupid>org.springframework</groupid>
             spring-jdbc</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
         <dependency>
             <groupid>org.springframework</groupid>
             spring-tx</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
         <!-- 3 )Spring web -->
         <dependency>
             <groupid>org.springframework</groupid>
             spring-web</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
         <dependency>
             <groupid>org.springframework</groupid>
             spring-webmvc</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
         <!-- 4 )Spring test -->
         <dependency>
             <groupid>org.springframework</groupid>
             spring-test</artifactid>
             <version> 4.1 . 7 .RELEASE</version>
         </dependency>
 
         <!-- redis客户端:Jedis -->
         <dependency>
             <groupid>redis.clients</groupid>
             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>
</code>

下面真的要开始进行编码工作了,坚持到这里辛苦大家了~

第一步:我们先在spring文件夹里新建spring-dao.xml文件,因为spring的配置太多,我们这里分三层,分别是dao service web。

读入数据库连接相关参数(可选) 配置数据连接池
配置连接属性,可以不读配置项文件直接在这里写死 配置c3p0,只配了几个常用的 配置SqlSessionFactory对象(mybatis) 扫描dao层接口,动态实现dao接口,也就是说不需要daoImpl,sql和参数都写在xml文件上

spring-dao.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<code class = "language-xml hljs " ><!--?xml version= "1.0" encoding= "UTF-8" ?-->
     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-placeholder location= "classpath:jdbc.properties" >
 
     <!-- 2 .数据库连接池 -->
     <bean class = "com.mchange.v2.c3p0.ComboPooledDataSource" id= "dataSource" >
         <!-- 配置连接池属性 -->
         <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" >
         <!-- 关闭连接后不自动commit -->
         <property name= "autoCommitOnClose" value= "false" >
         <!-- 获取连接超时时间 -->
         <property name= "checkoutTimeout" value= "10000" >
         <!-- 当获取连接失败重试次数 -->
         <property name= "acquireRetryAttempts" value= "2" >
     </property></property></property></property></property></property></property></property></property></bean>
 
     <!-- 3 .配置SqlSessionFactory对象 -->
     <bean class = "org.mybatis.spring.SqlSessionFactoryBean" id= "sqlSessionFactory" >
         <!-- 注入数据库连接池 -->
         <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" >
     </property></property></property></property></bean>
 
     <!-- 4 .配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
     <bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" >
         <!-- 注入sqlSessionFactory -->
         <property name= "sqlSessionFactoryBeanName" value= "sqlSessionFactory" >
         <!-- 给出需要扫描Dao接口包 -->
         <property name= "basePackage" value= "com.soecode.lyf.dao" >
     </property></property></bean>
</context:property-placeholder></beans></code>

因为数据库配置相关参数是读取配置文件,所以在resources文件夹里新建一个jdbc.properties文件,存放我们4个最常见的数据库连接属性,这是我本地的,大家记得修改呀~还有喜欢传到github上“大头虾们”记得删掉密码,不然别人就很容易得到你服务器的数据库配置信息,然后干一些羞羞的事情,你懂的!!

jdbc.properties

?
1
2
3
4
<code class = "language-properties hljs avrasm" >jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql: //localhost:3307/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=</code>

友情提示:配置文件中的jdbc.username,如果写成username,可能会与系统环境中的username变量冲突,所以到时候真正连接数据库的时候,用户名就被替换成系统中的用户名(有得可能是administrator),那肯定是连接不成功的,这里有个小坑,我被坑了一晚上!!

因为这里用到了mybatis,所以需要配置mybatis核心文件,在recources文件夹里新建mybatis-config.xml文件。

使用自增主键 使用列别名 开启驼峰命名转换 create_time -> createTime

mybatis-config.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code class = "language-xml hljs " ><!--?xml version= "1.0" encoding= "UTF-8" ?-->
 
<configuration>
     <!-- 配置全局属性 -->
     <settings>
         <!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
         <setting name= "useGeneratedKeys" value= "true" >
 
         <!-- 使用列别名替换列名 默认: true -->
         <setting name= "useColumnLabel" value= "true" >
 
         <!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
         <setting name= "mapUnderscoreToCamelCase" value= "true" >
     </setting></setting></setting></settings>
</configuration></code>

第二步:刚弄好dao层,接下来到service层了。在spring文件夹里新建spring-service.xml文件。

扫描service包所有注解 @Service 配置事务管理器,把事务管理交由spring来完成 配置基于注解的声明式事务,可以直接在方法上@Transaction

spring-service.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<code class = "language-xml hljs " ><!--?xml version= "1.0" encoding= "UTF-8" ?-->
     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" >
 
     <!-- 配置事务管理器 -->
     <bean class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" id= "transactionManager" >
         <!-- 注入数据库连接池 -->
         <property name= "dataSource" ref= "dataSource" >
     </property></bean>
 
     <!-- 配置基于注解的声明式事务 -->
     <tx:annotation-driven transaction-manager= "transactionManager" >
</tx:annotation-driven></context:component-scan></beans></code>

第三步:配置web层,在spring文件夹里新建spring-web.xml文件。

开启SpringMVC注解模式,可以使用@RequestMapping,@PathVariable,@ResponseBody等 对静态资源处理,如js,css,jpg等 配置 jsp 显示ViewResolver,例如在controller中某个方法返回一个string类型的”login”,实际上会返回”/WEB-INF/login.jsp” 扫描web层 @Controller

spring-web.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<code class = "language-xml hljs " ><!--?xml version= "1.0" encoding= "UTF-8" ?-->
     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 -->
      <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" >
      </property></property></property></bean>
 
      <!-- 4 .扫描web相关的bean -->
      <context:component-scan base- package = "com.soecode.lyf.web" >
</context:component-scan></mvc: default -servlet-handler></mvc:annotation-driven></beans></code>

第四步:最后就是修改web.xml文件了,它在webappWEB-INF下。

web.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<code class = "language-xml hljs " ><web-app metadata-complete= "true" version= "3.1" 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">
     <!-- 如果是用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>
</code>

我们在项目中经常会使用到日志,所以这里还有配置日志xml,在resources文件夹里新建logback.xml文件,所给出的日志输出格式也是最基本的控制台s呼出,大家有兴趣查看logback官方文档

logback.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<code class = "language-xml hljs " ><!--?xml version= "1.0" encoding= "UTF-8" ?-->
<configuration debug= "true" >
     
         <!-- 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></root>
</configuration></code>

到目前为止,我们一共写了7个配置文件,我们一起来看下最终的配置文件结构图

配置文件结构图


一开始想就这样结束教程,但是发现其实很多人都还不会把这个SSM框架用起来,特别是mybatis部分。那我现在就以最常见的“图书管理系统”中【查询图书】和【预约图书】业务来做一个demo吧!

首先新建数据库名为ssm,再创建两张表:图书表book和预约图书表appointment,并且为book表初始化一些数据,sql如下。

schema.sql

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<code class = "language-sql hljs " >-- 创建图书表
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 '馆藏数量' ,
   PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT= 1000 DEFAULT CHARSET=utf8 COMMENT= '图书表'
 
-- 初始化图书数据
INSERT INTO `book` (`book_id`, `name`, `number`)
VALUES
     ( 1000 , 'Java程序设计' , 10 ),
     ( 1001 , '数据结构' , 10 ),
     ( 1002 , '设计模式' , 10 ),
     ( 1003 , '编译原理' , 10 )
 
-- 创建预约图书表
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 '预约时间' ,
   PRIMARY KEY (`book_id`, `student_id`),
   INDEX `idx_appoint_time` (`appoint_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT= '预约图书表' </code>

entity包中添加两个对应的实体,图书实体Book.java和预约图书实体Appointment.java

Book.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<code class = "language-java hljs " > package com.soecode.lyf.entity;
 
public class Book {
 
     private long bookId; // 图书ID
 
     private String name; // 图书名称
 
     private int number; // 馆藏数量
 
     // 省略构造方法,getter和setter方法,toString方法
 
}</code>

Appointment.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<code class = "language-java hljs " > package com.soecode.lyf.entity;
 
import java.util.Date;
 
/**
  * 预约图书实体
  */
public class Appointment {
 
     private long bookId; // 图书ID
 
     private long studentId; // 学号
 
     private Date appointTime; // 预约时间
 
     // 多对一的复合属性
     private Book book; // 图书实体
 
     // 省略构造方法,getter和setter方法,toString方法
 
}</code>

dao包新建接口BookDao.javaAppointment.java

BookDao.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<code class = "language-java hljs " > 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 查询条数
      * @return
      */
     List<book> queryAll( @Param ( "offset" ) int offset, @Param ( "limit" ) int limit);
 
     /**
      * 减少馆藏数量
      *
      * @param bookId
      * @return 如果影响行数等于>1,表示更新的记录行数
      */
     int reduceNumber( long bookId);
}
</book></code>

AppointmentDao.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<code class = "language-java hljs " > package com.soecode.lyf.dao;
 
import org.apache.ibatis.annotations.Param;
 
import com.soecode.lyf.entity.Appointment;
 
public interface AppointmentDao {
 
     /**
      * 插入预约图书记录
      *
      * @param bookId
      * @param studentId
      * @return 插入的行数
      */
     int insertAppointment( @Param ( "bookId" ) long bookId, @Param ( "studentId" ) long studentId);
 
     /**
      * 通过主键查询预约图书记录,并且携带图书实体
      *
      * @param bookId
      * @param studentId
      * @return
      */
     Appointment queryByKeyWithBook( @Param ( "bookId" ) long bookId, @Param ( "studentId" ) long studentId);
 
}</code>

提示:这里为什么要给方法的参数添加@Param注解呢?是因为该方法有两个或以上的参数,一定要加,不然mybatis识别不了。上面的BookDao接口的queryById方法和reduceNumber方法只有一个参数book_id,所以可以不用加@Param注解,当然加了也无所谓~


注意,这里不需要实现dao接口不用编写daoImpl, mybatis会给我们动态实现,但是我们需要编写相应的mapper。
mapper目录里新建两个文件BookDao.xmlAppointmentDao.xml,分别对应上面两个dao接口,代码如下。

BookDao.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<code class = "language-xml hljs " ><!--?xml version= "1.0" encoding= "UTF-8" ?-->
 
<mapper namespace= "com.soecode.lyf.dao.BookDao" >
     <!-- 目的:为dao接口方法提供sql语句配置 -->
     <select id= "queryById" parametertype= "long" resulttype= "Book" >
         <!-- 具体的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></code>

AppointmentDao.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<code class = "language-xml hljs " ><!--?xml version= "1.0" encoding= "UTF-8" ?-->
 
<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把结果映射到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></code>

mapper总结namespace是该xml对应的接口全名,selectupdate中的id对应方法名,resultType是返回值类型,parameterType是参数类型(这个其实可选),最后#{...}中填写的是方法的参数,看懂了是不是很简单!!我也这么觉得~ 还有一个小技巧要交给大家,就是在返回Appointment对象包含了一个属性名为book的Book对象,那么可以使用"book.属性名"的方式来取值,看上面queryByKeyWithBook方法的sql。


dao层写完了,接下来test对应的package写我们测试方法吧。
因为我们之后会写很多测试方法,在测试前需要让程序读入spring-dao和mybatis等配置文件,所以我这里就抽离出来一个BaseTest类,只要是测试方法就继承它,这样那些繁琐的重复的代码就不用写那么多了~

BaseTest.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<code class = "language-java hljs " > 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 {
 
}
</code>

因为spring-serviceservice层的测试中会时候到,这里也一起引入算了!

新建BookDaoTest.javaAppointmentDaoTest.java两个dao测试文件。

BookDaoTest.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<code class = "language-java hljs " > 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);
     }
 
}</book></code>
BookDaoTest测试结果

testQueryById
testQueryById

testQueryAll
testQueryAll

testReduceNumber
testReduceNumber

AppointmentDaoTest.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<code class = "language-java hljs " > 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());
     }
 
}</code>
AppointmentDaoTest测试结果

testInsertAppointment
testInsertAppointment

testQueryByKeyWithBook
testQueryByKeyWithBook


嗯,到这里一切到很顺利~那么我们继续service层的编码吧~可能下面开始信息里比较大,大家要做好心理准备~

首先,在写我们的业务之前,我们先定义几个预约图书操作返回码的数据字典,我们这类使用枚举类,没听过的小伙伴要好好恶补一下了(我也是最近才学到的= =)

预约业务操作返回码说明

返回码说明
1预约成功
0库存不足
-1重复预约
-2系统异常

新建一个包叫enums,在里面新建一个枚举类AppointStateEnum.java,用来定义预约业务的数据字典,没听懂没关系,我们直接看代码吧~是不是感觉有模有样了!

AppointStateEnum.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<code class = "language-java hljs " > package com.soecode.lyf.enums;
 
/**
  * 使用枚举表述常量数据字典
  */
public enum AppointStateEnum {
 
     SUCCESS( 1 , "预约成功" ), NO_NUMBER( 0 , "库存不足" ), REPEAT_APPOINT(- 1 , "重复预约" ), INNER_ERROR(- 2 , "系统异常" );
 
     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 ;
     }
 
}</code>

接下来,在dto包下新建AppointExecution.java用来存储我们执行预约操作的返回结果。

AppointExecution.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<code class = "language-java hljs " > package com.soecode.lyf.dto;
 
import com.soecode.lyf.entity.Appointment;
import com.soecode.lyf.enums.AppointStateEnum;
 
/**
  * 封装预约执行后结果
  */
public class AppointExecution {
 
     // 图书ID
     private long bookId;
 
     // 秒杀预约结果状态
     private int state;
 
     // 状态标识
     private String stateInfo;
 
     // 预约成功对象
     private Appointment appointment;
 
     public AppointExecution() {
     }
 
     // 预约失败的构造器
     public AppointExecution( long bookId, AppointStateEnum stateEnum) {
         this .bookId = bookId;
         this .state = stateEnum.getState();
         this .stateInfo = stateEnum.getStateInfo();
     }
 
     // 预约成功的构造器
     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方法
 
}
</code>

咱们终于可以编写业务代码了,在service包下新建BookService.java图书业务接口。

BookService.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<code class = "language-java hljs " > package com.soecode.lyf.service;
 
import java.util.List;
 
import com.soecode.lyf.dto.AppointExecution;
import com.soecode.lyf.entity.Book;
 
/**
  * 业务接口:站在"使用者"角度设计接口 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)
  */
public interface BookService {
 
     /**
      * 查询一本图书
      *
      * @param bookId
      * @return
      */
     Book getById( long bookId);
 
     /**
      * 查询所有图书
      *
      * @return
      */
     List<book> getList();
 
     /**
      * 预约图书
      *
      * @param bookId
      * @param studentId
      * @return
      */
     AppointExecution appoint( long bookId, long studentId);
 
}</book></code>

service.impl包下新建BookServiceImpl.java使用BookService接口,并实现里面的方法。

BookServiceImpl

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<code class = "language-java hljs " > 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.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
     /**
      * 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格
      * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部
      * 3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制
      */
     public AppointExecution appoint( long bookId, long studentId) {
         try {
             // 减库存
             int update = bookDao.reduceNumber(bookId);
             if (update <= 0 ) { // 库存不足
                 return new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);
             } else {
                 // 执行预约操作
                 int insert = appointmentDao.insertAppointment(bookId, studentId);
                 if (insert <= 0 ) { // 重复预约
                     return new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);
                 } else { // 预约成功
                     Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
                     return new AppointExecution(bookId, AppointStateEnum.SUCCESS, appointment);
                 }
             }
         } catch (Exception e) {
             logger.error(e.getMessage(), e);
             // 所有编译期异常转换为运行期异常
             return new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);
         }
     }
 
}
</book></code>

下面我们来测试一下我们的业务代码吧~因为查询图书的业务不复杂,所以这里只演示我们最重要的预约图书业务!!

BookServiceImplTest.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<code class = "language-java hljs " > 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);
     }
 
}
</code>
BookServiceImplTest测试结果

testAppoint
testAppoint

首次执行是“预约成功”,如果再次执行的话,应该会出现“重复预约”,哈哈,我们所有的后台代码都通过单元测试啦~~是不是很开心~


咱们还需要在dto包里新建一个封装json返回结果的类Result.java,设计成泛型。

Result.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<code class = "language-java hljs " > package com.soecode.lyf.dto;
 
/**
  * 封装json对象,所有返回结果都使用它
  */
public class Result<t> {
 
     private boolean success; // 是否成功标志
 
     private T data; // 成功时返回的数据
 
     private String error; // 错误信息
 
     public Result() {
     }
 
     // 成功时的构造器
     public Result( boolean success, T data) {
         this .success = success;
         this .data = data;
     }
 
     // 错误时的构造器
     public Result( boolean success, String error) {
         this .success = success;
         this .error = error;
     }
 
     // 省略getter和setter方法
}
</t></code>

最后,我们写web层,也就是controller,我们在web包下新建BookController.java文件。

BookController.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<code class = "language-java hljs " > 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.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
     }
 
     // ajax json
     @RequestMapping (value = "/{bookId}/detail" , method = RequestMethod.GET)
     @ResponseBody
     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" ;
     }
 
     @RequestMapping (value = "/{bookId}/appoint" , method = RequestMethod.POST, produces = {
             "application/json; charset=utf-8" })
     private Result appoint( @PathVariable ( "bookId" ) Long bookId, @Param ( "studentId" ) Long studentId) {
         if (studentId == null || studentId.equals( "" )) {
             return new Result<>( false , "学号不能为空" );
         }
         AppointExecution execution = bookService.appoint(bookId, studentId);
         return new Result( true , execution);
     }
 
}
</appointexecution></appointexecution></book></code>



完毕!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jerry.qin

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值