[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis

Posted 脑机科技

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis相关的知识,希望对你有一定的参考价值。

本文接

《[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(一)》


SSM框架应用实例(图书管理系统)

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

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

schema.sql

-- 创建图书表

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

package com.soecode.lyf.entity;public class Book {

    private long bookId;// 图书ID

    private String name;// 图书名称

    private int number;// 馆藏数量

    // 省略构造方法,getter和setter方法,toString方法}

Appointment.java

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方法}

dao包新建接口BookDao.javaAppointment.java

BookDao.java

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); }

AppointmentDao.java

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); }

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


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

BookDao.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.soecode.lyf.dao.BookDao">    <!-- 目的:为dao接口方法提供sql语句配置 -->    <select id="queryById" resultType="Book" parameterType="long">        <!-- 具体的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>

AppointmentDao.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"    "http://mybatis.org/dtd/mybatis-3-mapper.dtd"
<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>

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


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

BaseTest.java

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

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

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

BookDaoTest.java

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);    } }

BookDaoTest测试结果

testQueryById 

testQueryAll 
[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)

testReduceNumber 
[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)

AppointmentDaoTest.java

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());    } }

AppointmentDaoTest测试结果

testInsertAppointment 
[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)

testQueryByKeyWithBook 
[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)


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

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

预约业务操作返回码说明

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

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

AppointStateEnum.java

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

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

AppointExecution.java

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方法}

接着,在exception包下新建三个文件 
NoNumberException.java 
RepeatAppointException.java 
AppointException.java 
预约业务异常类(都需要继承RuntimeException),分别是无库存异常、重复预约异常、预约未知错误异常,用于业务层非成功情况下的返回(即成功返回结果,失败抛出异常)。

NoNumberException.java

package com.soecode.lyf.exception;/** * 库存不足异常 */public class NoNumberException extends RuntimeException {

    public NoNumberException(String message) {
       super(message);    }    
   public NoNumberException(String message, Throwable cause) {
           super(message, cause);    } }

RepeatAppointException.java

package com.soecode.lyf.exception;/** * 重复预约异常 */public class RepeatAppointException extends RuntimeException {

    public RepeatAppointException(String message) {
       super(message);    }    
   public RepeatAppointException(String message, Throwable cause) {
       super(message, cause);    } }

AppointException.java

package com.soecode.lyf.exception;/** * 预约业务异常 */public class AppointException extends RuntimeException {

    public AppointException(String message) {
       super(message);    }
   public AppointException(String message, Throwable cause) {
       super(message, cause);    } }

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

BookService.java

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); }

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

BookServiceImpl

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.exception.AppointException;
import com.soecode.lyf.exception.NoNumberException;
import com.soecode.lyf.exception.RepeatAppointException;
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);
                   //错误写法                
             throw new NoNumberException("no number");            } else {                // 执行预约操作                int insert = appointmentDao.insertAppointment(bookId, studentId);                
                     if (insert <= 0) {// 重复预约                    //return new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);
                            //错误写法
                   throw new RepeatAppointException("repeat appoint");                } else {// 预约成功                    Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
                         return new AppointExecution(bookId, AppointStateEnum.SUCCESS, appointment);                }            }        
         // 要先于catch Exception异常前先catch住再抛出,不然自定义的异常也会被转换为AppointException,导致控制层无法具体识别是哪个异常        } catch (NoNumberException e1) {
               throw e1;        } catch (RepeatAppointException e2) {            
               throw e2;        } catch (Exception e) {            logger.error(e.getMessage(), e);            
             // 所有编译期异常转换为运行期异常            //return new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);
                //错误写法
           throw new AppointException("appoint inner error:" + e.getMessage());        }    } }

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

BookServiceImplTest.java

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);    } }

BookServiceImplTest测试结果

testAppoint 
[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)

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


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

Result.java

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方法
}

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

BookController.java

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.enums.AppointStateEnum;
import com.soecode.lyf.exception.NoNumberException;
import com.soecode.lyf.exception.RepeatAppointException;
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    }    
           @RequestMapping(value = "/{bookId}/detail", method = RequestMethod.GET)    
           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";    }    
     
     //ajax json    @RequestMapping(value = "/{bookId}/appoint", method = RequestMethod.POST, produces = {"application/json; charset=utf-8" })
   @ResponseBody    private Result<AppointExecution> appoint(@PathVariable("bookId") Long bookId, @RequestParam("studentId") Long studentId) {
         if (studentId == null || studentId.equals("")) {
             return new Result<>(false, "学号不能为空");        }
       //AppointExecution execution = bookService.appoint(bookId, studentId);//错误写法,不能统一返回,要处理异常(失败)情况        AppointExecution execution = null;        
       try {            execution = bookService.appoint(bookId, studentId);        } catch (NoNumberException e1) {            execution = new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);        } catch (RepeatAppointException e2) {            execution = new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);        } catch (Exception e) {            execution = new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);        }        return new Result<AppointExecution>(true, execution);    } }

因为我比较懒,所以我们就不测试controller了,好讨厌写前端,呜呜呜~

到此,我们的SSM框架整合配置,与应用实例部分已经结束了,我把所有源码和jar包一起打包放在了我的GitHub上,需要的可以去下载,喜欢就给个star吧,这篇东西写了两个晚上也不容易啊。

转载自 《李奕锋》 的博客

内容已稍加修改

http://blog.csdn.net/qq598535550/article/details/51703190






想看更多精彩内容?

  1. 在文章最后给小编留言吧



关注方式:长按二维码

选择“识别图中二维码”即可关注


01


微店

旨在发现世界中的可耐小萌物! ^_^

[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)





02


脑机科技

为大家提供各种有关手机电脑的保养与维修知识。

还有不定期的 各种红包 、理财、各业新资讯等福利 ^_^

[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)





03


每月签到有红包!

每逢节日有红包!!!

做任务得【脑机】,换红包

[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(二)





04


脑机娱乐-留存记忆

每月签到有红包!

每逢节日有红包!!!

做任务得【脑机】,换红包!





05


脑机娱乐-沧海桑田

每月签到有红包!

每逢节日有红包!!!

做任务得【脑机】,换红包





以上是关于[技术篇(java)] 教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis的主要内容,如果未能解决你的问题,请参考以下文章

史上最详细的IDEA优雅整合Maven+SSM框架(详细思路+附带源码)

优雅的ssm三大框架整合(全注解配置篇)转载

Java EE互联网轻量级框架整合开发— SSM框架(中文版带书签)原书代码

SSM框架快速整合实例——学生查询

SSM框架简介及整合教程

一步一步教你SSM整合swagger