IDEA 使用 Spring Boot 超快速搭建 SSM (完整版)

纵然是瞬间 提交于 2019-12-04 02:46:22

IDEA 使用Spring Boot 超快速搭建 SSM (完整版)

author:Cris

零、序

想起小王子里那句话:爱不是相互凝望,而是望向同一个方向,异地恋最大的问题其实不是距离,而是距离带来的沟通无力和词不达意

摧毁异地恋的,从来都不是时间、空间与距离,而是欲望与不坚定。庆幸遇到的这个人,给予我的安全感 ,只是因为我们喜欢彼此 ,分开的日子总有一天会过去

如果有一天,我爱的人离开我

我只回答两个字:好的

绝口不问“你怎么能这样对我”,“到底我哪里做得不对”

经历让我明白,若对方决定分开

必定准备好了理由

我不想听谋划许久冠冕堂皇的借口

凡是离开的必然本就不属于我,只祝好运

从此云淡风轻,过往一笔勾销

人生短暂,我不活在记忆里
—— 摘自虎扑App

​ 温馨提示:本篇笔记紧随上一篇 《IDEA 使用Spring Boot 超快速整合SSM(精简版)》,具体的前期搭建已经给出了详实的流程,感兴趣的同学可以参考;本篇笔记适用于熟悉或者了解或者想要了解 Spring boot 的同学,博主尽量使用易懂的语言为大家带来经过亲身验证的经验,干货满满?

该项目完整代码请参考:https://github.com/zc-cris/SpringBoot_SSM

一、环境修改以及实际场景介绍

实际场景介绍

我们这里引入实际场景来为整篇笔记做下铺垫,否则无法让大家深入体会使用 Spring boot 实际开发的好处,同时该场景也是经过大量精简后的简单场景,请勿较真~

我们模拟用户买书的场景:从查询图书的库存,到比较用户的余额和图书的价格,到最后用户成功购买图书,余额减去图书价格,图书库存-1。一个非常常见的场景,看看如何使用 Spring boot 完成

环境修改

在上一篇笔记的基础上,替换掉 JDBCTemplate,使用 Mybatis ,然后引入 IDEA 插件 MybatisCodeHelperPro 快速帮我们进行开发

具体搭建环境如下:

idea 2018.1.x

java8

spring boot 1.5.6

druid 1.1.10

MySQL 5.7

Navicat Premium 12

maven 3.5

Lombok 插件

Mybatis 3.4.6

MybatisCodeHelperPro 插件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cris</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.16.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

可以看到,pom.xml 文件还是很小的

MybatisCodeHelperPro

一款可以大大提高开发效率的 Mybatis 插件,个人感觉是非常不错的,具体的安装很简单,如下图

具体的使用技巧,参考这里

如何在 IDEA 中连接数据库

数据库可视化工具不使用 Navicat 也是可以的,因为 IDEA 自带了数据库连接工具,可以进行轻量级的开发,十分方便

后面有时间,我会对其好用的地方做一个简单的整理

除了上面的 user 表,还有一张 book

二、代码编写

目标:将实际场景用户买书的流程实现,同时使用事务控制整个流程

2.1、额外注意

项目结构图

因为引入了 Mybatis,需要在 application.yml 文件中进行简单配置

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/sql_primary_study?useSSL=false
    driver-class-name: com.mysql.jdbc.Driver
#   数据源其他配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
#   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
 # 取消 thymeleaf 的模板缓存以及指定渲染文件夹
 thymeleaf:
    cache: false
    prefix: classpath:/templates/

# 这里设置 mybatis 的 mapper 文件所在文件夹
mybatis:
  mapper-locations: classpath:/mybatis/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

banner 文件可以自己 diy,这里给出参考

${AnsiColor.BRIGHT_RED}

CRIS I LOVE U!!!

关于 Spring boot 的启动类最好如下配置

这里使用 Spring boot 自带的 Tomcat,并且因为是 1.x 版本,最好设置一下映射路径后缀

/**
 * @author zc-cris
 */
@SpringBootApplication
@MapperScan("com.cris.dao")
public class DemoApplication extends WebMvcConfigurerAdapter {

    /**
     * 设置路径匹配规则
     *
     * @param configurer 路径配置类
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
//      setUseSuffixPatternMatch : 设置是否是后缀模式匹配,如“/user”是否匹配/user.*,默认真即匹配;
        configurer.setUseSuffixPatternMatch(false);

//      setUseTrailingSlashMatch : 设置是否自动后缀路径模式匹配,如“/user”是否匹配“/user/”,默认真即匹配;
        configurer.setUseTrailingSlashMatch(false);
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

2.2、开始编码吧~

Ⅰ、Entity

/**
 * book表的对应po
 *
 * @author zc-cris
 * @version 1.0
 **/
@SuppressWarnings("unused")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Book {
    private Integer isbn;
    private Integer stock;
    private Double price;
}

/**
 * 对应user表的po
 *
 * @author zc-cris
 * @version 1.0
 **/
@SuppressWarnings("unused")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private Integer deptId;
    private Double money;
}

Ⅱ、Dao

BookMapper


/**
 * 定义对book表的基础dao操作
 *
 * @author zc-cris
 * @version 1.0
 **/
@Mapper
public interface BookMapper {

    /**
     * 根据isbn查询图书的价格
     *
     * @param isbn 图书的isbn号
     * @return 图书的价格
     */
    BigDecimal getPriceByIsbn(@Param("isbn") Integer isbn);

    /**
     * 根据isbn查询图书的库存
     *
     * @param isbn 图书的isbn号
     * @return 图书的库存量
     */
    Integer getStockByIsbn(@Param("isbn") Integer isbn);


    /**
     * 根据isbn减去一本书的库存
     *
     * @param isbn 图书的isbn号
     * @return 返回1表示(本书库存成功-1),否则返回0
     */
    Integer updateStockBySubOne(@Param("isbn") Integer isbn);


    /**
     * 根据isbn 查询图书的信息
     *
     * @param isbn 图书的isbn 号
     * @return 图书完整信息
     */
    Book getBookByIsbn(Integer isbn);
}

对应的 mapper 文件

<?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.cris.dao.BookMapper">


    <select id="getPriceByIsbn" resultType="java.math.BigDecimal">
        select price
        from sql_primary_study.book
        where isbn = #{isbn}
    </select>

    <select id="getStockByIsbn" resultType="java.lang.Integer">
        select stock
        from sql_primary_study.book
        where isbn = #{isbn}
    </select>

    <update id="updateStockBySubOne">
        update sql_primary_study.book
        set stock = stock - 1
        where isbn = #{isbn}
    </update>
    <select id="getBookByIsbn" resultType="com.cris.entity.Book">
        select
            isbn,
            stock,
            price
        from sql_primary_study.book
        where isbn = #{isbn}
    </select>
</mapper>

UserMapper

/**
 * 定义对user表的基础dao操作
 *
 * @author zc-cris
 * @version 1.0
 **/
@Mapper
public interface UserMapper {

    /**
     * 根据用户id查询余额
     *
     * @param id 用户id
     * @return 返回用户的余额
     */
    BigDecimal getMoneyById(@Param("id") Integer id);

    /**
     * 根据图书金额减去用户的余额
     *
     * @param price 图书金额
     * @param id    用户id
     * @return 返回1表示执行成功,返回0表示执行失败
     */
    Integer updateMoneyByBookPrice(@Param("price") BigDecimal price, @Param("id") Integer id);

}

对应的 mapper 文件

<?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.cris.dao.UserMapper">


    <select id="getMoneyById" resultType="java.math.BigDecimal">
        select money
        from sql_primary_study.user
        where id = #{id}
    </select>

    <update id="updateMoneyByBookPrice">
        update sql_primary_study.user
        set money = money - #{price}
        where id = #{id}
    </update>
</mapper>

值得一提的是,从 Dao 层编写代码的时候最好养成边写边测试的好习惯,否则后面除了问题就很惨咯

看看 Cris 小哥哥 的测试用例?

@SpringBootTest()
@RunWith(SpringRunner.class)
public class BookMapperTest {

    @Autowired
    BookMapper bookMapper;

    @Test
    public void getPriceByIsbn() {
        Assert.assertNotNull(bookMapper.getPriceByIsbn(1234));
    }

    @Test
    public void getStockByIsbn() {
        Assert.assertNotNull(bookMapper.getStockByIsbn(1234));
    }

    @Test
    public void updateStockBySubOne() {
        Assert.assertEquals(1, bookMapper.updateStockBySubOne(1234).intValue());
    }
}

@SpringBootTest
@RunWith(SpringRunner.class)
public class UserMapperTest {

    @Autowired
    UserMapper userMapper;

    @Test
    public void getMoneyById() {
        Assert.assertNotNull(userMapper.getMoneyById(1));
    }

    @Test
    public void updateMoneyByBookPrice() {
        Assert.assertEquals(1, userMapper.updateMoneyByBookPrice(new BigDecimal(30.0), 1).intValue());
    }
}

这里省略了 Druid 的配置类书写,感兴趣的可以去上一篇笔记查看,这里就不重复了~

Ⅲ、service

这里省略了 接口层,偷个懒难道不行嘛?

BookServiceImpl

/**
 * book的service层
 *
 * @author zc-cris
 * @version 1.0
 **/
@Service
public class BookServiceImpl {

    private final
    BookMapper bookMapper;

    @Autowired
    public BookServiceImpl(BookMapper bookMapper) {
        this.bookMapper = bookMapper;
    }

    /**
     * 根据isbn号卖出一本书
     *
     * @param isbn 图书的isbn号
     */
    public void saleOne(Integer isbn) {
        bookMapper.updateStockBySubOne(isbn);
    }

    /**
     * 根据isbn获取该书的库存
     *
     * @param isbn 图书的isbn
     * @return 图书的库存
     */
    Integer getStock(Integer isbn) {
        return bookMapper.getStockByIsbn(isbn);
    }

    /**
     * 根据isbn获取图书的价格
     *
     * @param isbn 图书的isbn
     * @return 图书的价格
     */
    BigDecimal getPrice(Integer isbn) {
        return bookMapper.getPriceByIsbn(isbn);
    }

    /**
     * 根据isbn获取图书的信息
     *
     * @param isbn 图书的isbn
     * @return 对应的图书信息
     */
    public Book getBookByIsbn(Integer isbn) {
        return bookMapper.getBookByIsbn(isbn);
    }
}

UserServiceImpl

/**
 * user的service层
 *
 * @author zc-cris
 * @version 1.0
 **/
@Service
@Slf4j
public class UserServiceImpl {

    private final
    UserMapper userMapper;

    private final
    BookServiceImpl bookService;

    @Autowired
    public UserServiceImpl(BookServiceImpl bookService, UserMapper userMapper) {
        this.bookService = bookService;
        this.userMapper = userMapper;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean buyOneBook(Integer id, Integer isbn) {
        log.info("id为{}的用户准备买isbn为{}的图书", id, isbn);
        Integer stock = bookService.getStock(isbn);
        if (stock <= 0) {
            throw new RuntimeException(String.format("isbn为%s的图书库存不足!", isbn));
        }

        BigDecimal price = bookService.getPrice(isbn);
        BigDecimal money = userMapper.getMoneyById(id);
        int compare = price.compareTo(money);
        if (compare > 0) {
            throw new RuntimeException("用户的钱不够买书");
        }

        userMapper.updateMoneyByBookPrice(price, id);
        log.info("id为{}的用户已经付款{}员", id, price);
        bookService.saleOne(isbn);
        log.info("id为{}的用户已经买了isbn为{}的一本书,库存已经减去一", id, isbn);
        return true;
    }
}

值得一提的是:开发中要保持打印日记的好习惯哦,这样子出问题了可以快速定位(⊙o⊙)

最后老样子,测试用例如下:

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class UserServiceImplTest {


    @Autowired
    UserServiceImpl userService;

    @Test
    public void buyOneBook() {
        // 测试库存不足
//        boolean b = userService.buyOneBook(1, 1234);
        // 测试用户钱不足
        boolean b = userService.buyOneBook(1, 1111);
        log.info("b=" + b);
    }
}

Ⅳ、controller

这里写一个 controller 即可

/**
 * 测试controller
 *
 * @author zc-cris
 */
@Controller
public class TestController {

    private final
    BookServiceImpl bookService;
    private final
    UserServiceImpl userService;

    @Autowired
    public TestController(BookServiceImpl bookService, UserServiceImpl userService) {
        this.bookService = bookService;
        this.userService = userService;
    }

    @PutMapping("/buy")
    public String buyBook(HttpServletRequest request) {
        String pageStr;
        Integer id = Integer.valueOf(request.getParameter("id"));
        Integer isbn = Integer.valueOf(request.getParameter("isbn"));

        try {
            userService.buyOneBook(id, isbn);
            pageStr = "success";
        } catch (Exception e) {
            pageStr = "failed";
        }
        return pageStr;
    }

    @GetMapping("/getBook/{isbn}")
    @ResponseBody
    public Book getUser(@PathVariable("isbn") Integer isbn) {
        return bookService.getBookByIsbn(isbn);
    }

    @GetMapping("/index")
    public String index() {
        return "index";
    }
}

这里使用了 restful 风格的开发风格,不知道小伙伴们看出来么?

ⅴ、静态资源

因为小哥哥实在是不擅长前端,所以这里就随性写了。好像找个前端的小姐姐带带我~

注意:页面需要放在 template 文件夹下

大坑:使用 IDEA 新建的 html5 页面如下

大家看出来 test.html 和上面的 html 页面有什么细小的区别吗?如果看不出来,静态页面是无法访问的?,大家可以自己找找看?

Spring boot 默认支持 restful 风格,不需要额外配置

2.3、让程序跑起来?

启动我们的项目后,console 清空如下:

然后测试一下是否可以直接访问 静态资源/static 文件夹下的文件)

然后开始测试

①测试获取图书信息

②测试买书(重点)

先看看数据库的数据

测试库存不足是否可以买书

再来看看数据库的数据

接下来测试用户余额不足

再来看看数据库的数据

最后测试钱够,库存够的情况

1539000741141

自此,使用 Spring boot 搭配的 SMM 完整版项目测试完毕

三、总结

  1. 该笔记是个人总结的使用 Spring boot 快速搭建 SSM 项目的相对完整骨架版本(这样说是因为实际开发的项目比该案例大的多,但是基本骨架已经搭建好了,后续只需要在上面添加各种模块即可),相比于网上的复制粘贴,个人觉得还是比较靠谱的?
  2. 该案例仅仅展示了单应用模式下的搭建流程,鉴于现在越来越流行的微服务架构,Cris 会将以前做的 Spring Cloud 搭建笔记整理并分享出来,等不及的可以看 Cris 小哥哥的 GitHub 个人地址:https://github.com/zc-cris
  3. 该项目的特点:
  • 使用 MybatisCodeHelperProLombok 大大简化开发流程
  • 整个项目使用 restful 风格
  • 通过简化的实际场景需求令读者更加清晰明了 Spring boot 的强大和优雅
  • 严格按照 阿里代码规范 进行编写,规范的测试用例?
  1. 注意点:
  • Spring bootrestful 的天然支持
  • Spring boot 1.x 版本对于访问路径后缀名的处理最好自定义
  • IDEA 生成 html5 页面的 神坑
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!