针对上一篇文章详细介绍了SSM框架的搭建,这篇文章使用SSM给大家举一个栗子;希望对大家有所帮助。
一开始想就这样结束教程,但是发现其实很多人都还不会把这个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
|
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=
'预约图书表'
|
在entity
包中添加两个对应的实体,图书实体Book.java
和预约图书实体Appointment.java
。
Book.java
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
Book {
private
long
bookId;
// 图书ID
private
String name;
// 图书名称
private
int
number;
// 馆藏数量
// 省略构造方法,getter和setter方法,toString方法
}
|
Appointment.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
java.util.Date;
/**
* 预约图书实体
*/
public
class
Appointment {
private
long
bookId;
// 图书ID
private
long
studentId;
// 学号
private
Date appointTime;
// 预约时间
// 多对一的复合属性
private
Book book;
// 图书实体
// 省略构造方法,getter和setter方法,toString方法
}
|
在dao
包新建接口BookDao.java
和Appointment.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
|
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
@Param
(
"offset"
)
int
offset,
@Param
(
"limit"
)
int
limit);
/**
* 减少馆藏数量
*
* @param bookId
* @return 如果影响行数等于>1,表示更新的记录行数
*/
int
reduceNumber(
long
bookId);
}
|
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
|
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);
}
|
提示:这里为什么要给方法的参数添加@Param
注解呢?是因为该方法有两个或以上的参数,一定要加,不然mybatis识别不了。上面的BookDao
接口的queryById
方法和reduceNumber
方法只有一个参数book_id
,所以可以不用加 @Param
注解,当然加了也无所谓~
注意,这里不需要实现dao接口不用编写daoImpl, mybatis会给我们动态实现,但是我们需要编写相应的mapper。
在mapper
目录里新建两个文件BookDao.xml
和AppointmentDao.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
|
SELECT
book_id,
name,
number
FROM
book
WHERE
book_id = #{bookId}
SELECT
book_id,
name,
number
FROM
book
ORDER BY
book_id
LIMIT #{offset}, #{limit}
UPDATE book
SET number = number -
1
WHERE
book_id = #{bookId}
AND number >
0
|
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
|
INSERT ignore INTO appointment (book_id, student_id)
VALUES (#{bookId}, #{studentId})
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}
|
mapper总结:namespace
是该xml对应的接口全名,select
和update
中的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
|
import
org.junit.runner.RunWith;
import
org.springframework.test.context.ContextConfiguration;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* 配置spring和junit整合,junit启动时加载springIOC容器 spring-test,junit
*/
@RunWith
(SpringJUnit4ClassRunner.
class
)
// 告诉junit spring配置文件
@ContextConfiguration
({
"classpath:spring/spring-dao.xml"
,
"classpath:spring/spring-service.xml"
})
public
class
BaseTest {
}
|
因为spring-service
在service
层的测试中会时候到,这里也一起引入算了!
新建BookDaoTest.java
和AppointmentDaoTest.java
两个dao测试文件。
BookDaoTest.java
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
|
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
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);
}
}
|
testQueryById
testQueryAll
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
|
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());
}
}
|
testInsertAppointment
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
|
/**
* 使用枚举表述常量数据字典
*/
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) {
&
|