SSM框架实现图书管理

SSM框架应用实例《图书管理系统》

2017年07月07日 09:56:21

阅读数:6979

针对上一篇文章详细介绍了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

<codeclass="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=1000DEFAULT 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.entity;

publicclass Book {

    privatelong bookId;// 图书ID

    privateString name;// 图书名称

    privateint 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.entity;

importjava.util.Date;

/**

 * 预约图书实体

 */

publicclass Appointment {

    privatelong bookId;// 图书ID

    privatelong studentId;// 学号

    privateDate appointTime;// 预约时间

    // 多对一的复合属性

    privateBook 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.dao;

importjava.util.List;

importcom.soecode.lyf.entity.Book;

publicinterface BookDao {

    /**

     * 通过ID查询单本图书

     *

     * @param id

     * @return

     */

    Book queryById(longid);

    /**

     * 查询所有图书

     *

     * @param offset 查询起始位置

     * @param limit 查询条数

     * @return

     */

    List<book> queryAll(@Param("offset")intoffset, @Param("limit")intlimit);

    /**

     * 减少馆藏数量

     *

     * @param bookId

     * @return 如果影响行数等于>1,表示更新的记录行数

     */

    intreduceNumber(longbookId);

}

</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

<codeclass="language-java hljs ">packagecom.soecode.lyf.dao;

importorg.apache.ibatis.annotations.Param;

importcom.soecode.lyf.entity.Appointment;

publicinterface AppointmentDao {

    /**

     * 插入预约图书记录

     *

     * @param bookId

     * @param studentId

     * @return 插入的行数

     */

    intinsertAppointment(@Param("bookId")longbookId, @Param("studentId")longstudentId);

    /**

     * 通过主键查询预约图书记录,并且携带图书实体

     *

     * @param bookId

     * @param studentId

     * @return

     */

    Appointment queryByKeyWithBook(@Param("bookId")longbookId, @Param("studentId")longstudentId);

}</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

<codeclass="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

<codeclass="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

<codeclass="language-java hljs ">packagecom.soecode.lyf;

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)

// 告诉junit spring配置文件

@ContextConfiguration({"classpath:spring/spring-dao.xml","classpath:spring/spring-service.xml"})

publicclass 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.dao;

importjava.util.List;

importorg.junit.Test;

importorg.springframework.beans.factory.annotation.Autowired;

importcom.soecode.lyf.BaseTest;

importcom.soecode.lyf.entity.Book;

publicclass BookDaoTest extendsBaseTest {

    @Autowired

    privateBookDao bookDao;

    @Test

    publicvoid testQueryById() throwsException {

        longbookId = 1000;

        Book book = bookDao.queryById(bookId);

        System.out.println(book);

    }

    @Test

    publicvoid testQueryAll() throwsException {

        List<book> books = bookDao.queryAll(0,4);

        for(Book book : books) {

            System.out.println(book);

        }

    }

    @Test

    publicvoid testReduceNumber() throwsException {

        longbookId = 1000;

        intupdate = 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.dao;

importorg.junit.Test;

importorg.springframework.beans.factory.annotation.Autowired;

importcom.soecode.lyf.BaseTest;

importcom.soecode.lyf.entity.Appointment;

publicclass AppointmentDaoTest extendsBaseTest {

    @Autowired

    privateAppointmentDao appointmentDao;

    @Test

    publicvoid testInsertAppointment() throwsException {

        longbookId = 1000;

        longstudentId = 12345678910L;

        intinsert = appointmentDao.insertAppointment(bookId, studentId);

        System.out.println("insert="+ insert);

    }

    @Test

    publicvoid testQueryByKeyWithBook() throwsException {

        longbookId = 1000;

        longstudentId = 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.enums;

/**

 * 使用枚举表述常量数据字典

 */

publicenum AppointStateEnum {

    SUCCESS(1,"预约成功"), NO_NUMBER(0,"库存不足"), REPEAT_APPOINT(-1,"重复预约"), INNER_ERROR(-2,"系统异常");

    privateint state;

    privateString stateInfo;

    privateAppointStateEnum(intstate, String stateInfo) {

        this.state = state;

        this.stateInfo = stateInfo;

    }

    publicint getState() {

        returnstate;

    }

    publicString getStateInfo() {

        returnstateInfo;

    }

    publicstatic AppointStateEnum stateOf(intindex) {

        for(AppointStateEnum state : values()) {

            if(state.getState() == index) {

                returnstate;

            }

        }

        returnnull;

    }

}</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

<codeclass="language-java hljs ">packagecom.soecode.lyf.dto;

importcom.soecode.lyf.entity.Appointment;

importcom.soecode.lyf.enums.AppointStateEnum;

/**

 * 封装预约执行后结果

 */

publicclass AppointExecution {

    // 图书ID

    privatelong bookId;

    // 秒杀预约结果状态

    privateint state;

    // 状态标识

    privateString stateInfo;

    // 预约成功对象

    privateAppointment appointment;

    publicAppointExecution() {

    }

    // 预约失败的构造器

    publicAppointExecution(longbookId, AppointStateEnum stateEnum) {

        this.bookId = bookId;

        this.state = stateEnum.getState();

        this.stateInfo = stateEnum.getStateInfo();

    }

    // 预约成功的构造器

    publicAppointExecution(longbookId, 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.service;

importjava.util.List;

importcom.soecode.lyf.dto.AppointExecution;

importcom.soecode.lyf.entity.Book;

/**

 * 业务接口:站在"使用者"角度设计接口 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)

 */

publicinterface BookService {

    /**

     * 查询一本图书

     *

     * @param bookId

     * @return

     */

    Book getById(longbookId);

    /**

     * 查询所有图书

     *

     * @return

     */

    List<book> getList();

    /**

     * 预约图书

     *

     * @param bookId

     * @param studentId

     * @return

     */

    AppointExecution appoint(longbookId, longstudentId);

}</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

<codeclass="language-java hljs ">packagecom.soecode.lyf.service.impl;

importjava.util.List;

importorg.slf4j.Logger;

importorg.slf4j.LoggerFactory;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Service;

importorg.springframework.transaction.annotation.Transactional;

importcom.soecode.lyf.dao.AppointmentDao;

importcom.soecode.lyf.dao.BookDao;

importcom.soecode.lyf.dto.AppointExecution;

importcom.soecode.lyf.entity.Appointment;

importcom.soecode.lyf.entity.Book;

importcom.soecode.lyf.enums.AppointStateEnum;

importcom.soecode.lyf.service.BookService;

@Service

publicclass BookServiceImpl implementsBookService {

    privateLogger logger = LoggerFactory.getLogger(this.getClass());

    // 注入Service依赖

    @Autowired

    privateBookDao bookDao;

    @Autowired

    privateAppointmentDao appointmentDao;

    @Override

    publicBook getById(longbookId) {

        returnbookDao.queryById(bookId);

    }

    @Override

    publicList<book> getList() {

        returnbookDao.queryAll(0,1000);

    }

    @Override

    @Transactional

    /**

     * 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格

     * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部

     * 3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制

     */

    publicAppointExecution appoint(longbookId, longstudentId) {

        try{

            // 减库存

            intupdate = bookDao.reduceNumber(bookId);

            if(update <= 0) {// 库存不足

                returnnew AppointExecution(bookId, AppointStateEnum.NO_NUMBER);

            }else{

                // 执行预约操作

                intinsert = appointmentDao.insertAppointment(bookId, studentId);

                if(insert <= 0) {// 重复预约

                    returnnew AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);

                }else{// 预约成功

                    Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);

                    returnnew AppointExecution(bookId, AppointStateEnum.SUCCESS, appointment);

                }

            }

        }catch(Exception e) {

            logger.error(e.getMessage(), e);

            // 所有编译期异常转换为运行期异常

            returnnew 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.service.impl;

importstatic org.junit.Assert.fail;

importorg.junit.Test;

importorg.springframework.beans.factory.annotation.Autowired;

importcom.soecode.lyf.BaseTest;

importcom.soecode.lyf.dto.AppointExecution;

importcom.soecode.lyf.service.BookService;

publicclass BookServiceImplTest extendsBaseTest {

    @Autowired

    privateBookService bookService;

    @Test

    publicvoid testAppoint() throwsException {

        longbookId = 1001;

        longstudentId = 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.dto;

/**

 * 封装json对象,所有返回结果都使用它

 */

publicclass Result<t> {

    privateboolean success;// 是否成功标志

    privateT data;// 成功时返回的数据

    privateString error;// 错误信息

    publicResult() {

    }

    // 成功时的构造器

    publicResult(booleansuccess, T data) {

        this.success = success;

        this.data = data;

    }

    // 错误时的构造器

    publicResult(booleansuccess, 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

<codeclass="language-java hljs ">packagecom.soecode.lyf.web;

importjava.util.List;

importorg.apache.ibatis.annotations.Param;

importorg.slf4j.Logger;

importorg.slf4j.LoggerFactory;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.ui.Model;

importorg.springframework.web.bind.annotation.PathVariable;

importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.RequestMethod;

importorg.springframework.web.bind.annotation.ResponseBody;

importcom.soecode.lyf.dto.AppointExecution;

importcom.soecode.lyf.dto.Result;

importcom.soecode.lyf.entity.Book;

importcom.soecode.lyf.service.BookService;

@Controller

@RequestMapping("/book")// url:/模块/资源/{id}/细分 /seckill/list

publicclass BookController {

    privateLogger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired

    privateBookService bookService;

    @RequestMapping(value = "/list", method = RequestMethod.GET)

    privateString 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

    privateString 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"  })

    privateResult appoint(@PathVariable("bookId") Long bookId, @Param("studentId") Long studentId) {

        if(studentId == null|| studentId.equals("")) {

            returnnew Result<>(false,"学号不能为空");

        }

        AppointExecution execution = bookService.appoint(bookId, studentId);

        returnnew Result(true, execution);

    }

}

</appointexecution></appointexecution></book></code>

猜你喜欢

转载自blog.csdn.net/qq_42279014/article/details/81809507