本篇文章给大家谈谈Debug-MyBatis,SpringBoot对应关系,以及mybatis+springboot的知识点,同时本文还将给你拓展ideaspringboot2集成Mybatis使用m
本篇文章给大家谈谈Debug - MyBatis, SpringBoot对应关系,以及mybatis+springboot的知识点,同时本文还将给你拓展idea springboot2 集成Mybatis使用mybatis-generator-plugin生成代码、idea+springboot+Mybatis搭建web项目、JAVA WEB 快速入门之从编写一个基于 SpringBoot+Mybatis 快速创建的 REST API 项目了解 SpringBoot、SpringMVC REST API、Mybatis 等相关知识、Mybatis 插件之 Mybatis-Plus(SpringBoot)等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:- Debug - MyBatis, SpringBoot对应关系(mybatis+springboot)
- idea springboot2 集成Mybatis使用mybatis-generator-plugin生成代码
- idea+springboot+Mybatis搭建web项目
- JAVA WEB 快速入门之从编写一个基于 SpringBoot+Mybatis 快速创建的 REST API 项目了解 SpringBoot、SpringMVC REST API、Mybatis 等相关知识
- Mybatis 插件之 Mybatis-Plus(SpringBoot)
Debug - MyBatis, SpringBoot对应关系(mybatis+springboot)
结论
mybatis-spring-boot-starter的版本需要注意:
2.1.x版本适用于:MyBatis 3.5+、Java 8+、Spring Boot 2.1+
2.0.x版本适用于:MyBatis 3.5+、Java 8+、Spring Boot 2.0/2.1
1.3.x版本适用于:MyBatis 3.4+、Java 6+、Spring Boot 1.5
细节
参考文献
————————————————
版权声明:本文为CSDN博主「Javaesandyou」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Javaesandyou/article/details/122650700
idea springboot2 集成Mybatis使用mybatis-generator-plugin生成代码
参考博客:
详细使用步骤:https://blog.csdn.net/u010358168/article/details/86246351
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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>5.1.46</scope>
</dependency>
<!--需要手动添加的依赖-->
<!--使用durid连接池的依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- mybatis generator 自动生成代码插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<!-- 配置数据库链接及mybatis generator core依赖 生成mapper时使用 -->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<commentGenerator>
<property name="suppressDate" value="true"/>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- 数据库链接URL,用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mvctest?serverTimezone=GMT%2B8"
userId="hu"
password="123456">
</jdbcConnection>
<!-- 类型转换 -->
<javaTypeResolver>
<!-- 是否使用BigDecimals,false可自动转化以下类型(Long Integer Short等) -->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 生成模型的包名和位置-->
<javaModelGenerator targetPackage="com.example.mybatis.pojo" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<!--<property name="trimStrings" value="true"/>-->
</javaModelGenerator>
<!-- 生成映射文件的包名和位置-->
<sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 生成DAO的包名和位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mybatis.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 要生成的表 tableName是数据库中的表名或视图名 domainObjectName是实体类名-->
<table tableName="user" domainObjectName="User" >
<property name="useActualColumnNames" value="false"/>
<!-- 数据库表主键 -->
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
</context>
</generatorConfiguration>
注意问题:
如果不成功:
主要是数据库连接的版本的问题,我这里是5.1.34对应的是mysql5.7.20
idea+springboot+Mybatis搭建web项目
使用idea+springboot+Mybatis搭建一个简单的web项目。
首先新建一个项目;
在这里选择Maven项目也可以,但是IDEA为我们提供了一种更方便快捷的创建方法,即Spring Initializr。选择后点击Next;
把项目信息写好,Next;
按下面三张图勾选设置;
最后Finish。
等待Maven自动加载完成后,最初的项目结构如下图。在Springboot属性文件application.properties中,把数据库连接属性加上,同时可以设置服务端口。
1
2
3
4
5
6
7
8
|
spring.datasource.url = jdbc:mysql:
//localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
#页面热加载
spring.thymeleaf.cache =
false
#端口
server.port=
8888
|
resources目录下,static文件夹是放置各种静态资源,如css,js,img等文件的。templates文件夹则是默认放置网页的。当然也可以更改。
在static文件夹下新建一个测试css,test.css。
1
2
3
|
body{
color
:
red
;
}
|
在templates文件夹下新建一个html,要注意的是meta这个标签的结束符软件并没有自动加上,需要手动加上,否则访问网页时会报错。并引入test.css
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<link rel="stylesheet" href="test.css" type="text/css" />
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
接下来可以写一个controller了
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package
com.example.demo;
import
org.springframework.stereotype.Controller;
import
org.springframework.web.bind.annotation.RequestMapping;
@Controller
public
class
IndexController {
@RequestMapping
(
"/index"
)
public
String index(){
return
"hello"
;
}
}
|
完成之后,通过方式1和方式2都可以启动项目
接下来可以在浏览器中测试了
到此,一个简单的项目搭建完成。
JAVA WEB 快速入门之从编写一个基于 SpringBoot+Mybatis 快速创建的 REST API 项目了解 SpringBoot、SpringMVC REST API、Mybatis 等相关知识
JAVA WEB 快速入门系列之前的相关文章如下:(文章全部本人【梦在旅途原创】,文中内容可能部份图片、代码参照网上资源)
第一篇:JAVA WEB 快速入门之环境搭建
第二篇:JAVA WEB 快速入门之从编写一个 JSP WEB 网站了解 JSP WEB 网站的基本结构、调试、部署
第三篇:JAVA WEB 快速入门之通过一个简单的 Spring 项目了解 Spring 的核心(AOP、IOC)
第四篇:JAVA WEB 快速入门之从编写一个基于 SpringMVC 框架的网站了解 Maven、SpringMVC、SpringJDBC
今天是第五篇,也是该系列文章的最后一篇,接上篇《JAVA WEB 快速入门之从编写一个基于 SpringMVC 框架的网站了解 Maven、SpringMVC、SpringJDBC》,通过上篇文章的详细介绍,知道如何使用 maven 来快速构建 spring MVC 应用,也能够使用 spring MVC+springJDBC 实现网站开发,而本文所涉及的知识则是在这基础之上继续提升,核心是讲解如何使用 spring boot 来更快速的构建 spring MVC,并通过 mybatis 及代码生成相关 DAO,同时利用 VUE 前端框架开发前后端分离的网站,用户体验更好,废话不多说,直接进入本文主题。
(提示:本文内容有点长,涉及的知识点也比较多,若是新手建议耐心看完!)
一、创建 Spring Boot+SpringMVC 空项目
1.1 通过 https://start.spring.io/ 官网快速生成一个 Spring Boot+SpringMVC 空项目,如下图示:
(当然也可以通过 Eclipse 或 IDEA 的 Spring Boot 插件来创建,可参见:https://www.cnblogs.com/shaoniandream/p/9679942.html,https://blog.csdn.net/qq_32572497/article/details/62037873)
设置后点击页面的生成项目按钮,即可生成并下载 spring boot 项目代码压缩包,然后使用 IDE 导入存在的 maven project 即可。
1.2 调整项目,解决一些踩坑点
1.2.1. 调整 spring boot App 启动类(如:SpringbootdemoApplication)到根包目录或在启动类上显式添加 @ComponentScan 注解,并指定包路径,如下代码所示,cn.zuowenjun.boot 是根包目录,其余都是 cn.zuowenjun.boot 的子包
package cn.zuowenjun.boot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
//import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication //指定为Spring Boot启动入口,内含多个spring所需要的注解
@MapperScan(basePackages="cn.zuowenjun.boot.mapper")//设置Mybaits扫描的mapper包路径
//@ComponentScan(basePackages= {"cn.zuowenjun.controller"}) //如果不在根包目录,则需指定spring管理的相关包路径
@EnableTransactionManagement //启动事务管理
public class SpringbootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
1.2.2. 解决 POM 文件报:
Description Resource Path Location Type
Execution default-resources of goal org.apache.maven.plugins:maven-resources-plugin:3.1.0:resources failed: Unable to load the mojo ''resources'' (or one of its required components) from the plugin ''org.apache.maven.plugins:maven-resources-plugin:3.1.0''
直接在 POM 中添加如下 resources 依赖:
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<type>maven-plugin</type>
</dependency>
1.2.3. 设置热编译启动模式,以便可以随时更改代码后即时生效
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
设置后项目的视图就有如下显示效果:
1.3 演示请求 REST API 分别返回 JSON、XML
创建好 spring boot 空项目环境后,我们就可以开始编写相关代码了,在此仅贴出实现了 REST API 分别响应返回 JSON、XML 格式的 Controller,实现步骤如下:
1.3.1 在 cn.zuowenjun.boot.controller 包中创建 DemoController,并编写 hellojson、helloxml Action 方法,代码如下:
package cn.zuowenjun.boot.controller;
import org.springframework.web.bind.annotation.*;
import cn.zuowenjun.boot.domain.*;
@RestController
public class DemoController {
@RequestMapping(value="/hello/json",produces="application/json;charset=utf-8")
public HelloDto hellojson()
{
HelloDto dto=new HelloDto();
dto.setMessage("hello,zuowenjun.cn,hello java spring boot!");
return dto;
}
@RequestMapping(value="/hello/xml",produces="text/xml;charset=utf-8")
public HelloDto helloxml()
{
HelloDto dto=new HelloDto();
dto.setMessage("hello,zuowenjun.cn,hello java spring boot!");
return dto;
}
}
如上代码简要说明:@RestController 相当于是:@Controller、@ResponseBody,这个可以查看 @RestController 注解类代码就知道;@RequestMapping 指定请求映射,其中 produces 设置响应内容格式(可理解为服务端是生产者,而用户在浏览器端【客户端】是消费端),还有 consumes 属性,这个是指可接收请求的内容格式(可理解为用户在浏览器端发送请求是消息的生产者,而服务端接收并处理该请求为消息的消费者),当然还有其它一些属性,大家可以参见我上篇文章或网络其它大神的相关文章加以了解。
另外需要注意,默认 spring MVC 只返回 JSON 格式,若需返回 XML 格式,还需添加 XML JAR 包依赖,如下:(可以看到 version 这里我指定了版本号区间,表示 2.5.0 及以上版本都可以,有些依赖 spring-boot-starter-parent 中都有提前配置依赖管理,我们只需要指定 groupId、artifactId 即可,version 就会使用 spring boot 中的默认版本,当然也可以强制指定版本)
<!-- 如果项目中需要REST API响应(返回)XML格式的报文体则应添加该依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-xml-provider</artifactId>
<version>[2.5.0,)</version><!--$NO-MVN-MAN-VER$ -->
</dependency>
由于项目中同时添加 JSON 及 XML 的 JAR 包,按照 spring MVC 的默认响应处理流程是:如果未指定 produces,则当请求的 header 中指定了 accept 类型,则自动格式化并返回该 accept 所需的类型,如果未指定 accept 类型,则优先是响应 XML,当找不到 XML 依赖包时才会响应 JSON,故如果项目中同时有 JSON 及 XML,那么最好显式指定 produces 或者请求头上指明 accept 类型 这一点与 ASP.NET WEB API 原理相同,因为都是符合 REST 架构风格的。
效果如下:
二、使用 Mybatis 框架完成 Domain 层、DAO 层 (这里是 Mapper 层) --- 提示:由于篇幅有限,只贴出重点能体现不同知识点的代码,其余可以到 GITHUB 上查看下载源码进行详细了解
2.0:首先在 application.properties 配置 mybatis 的相关选项,如下所示:
mybatis.type-aliases-package=cn.zuowenjun.boot.domain #包类型别名,这样在XML中就可以简写成类名
mybatis.config-location=classpath:mybatis/mybatis-config.xml #指定mybatis的配置文件路径
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml #指定mapper XML的存放路径
#这里是使用SQL SERVER,如果是其它DB则使用其它驱动
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url=jdbc:sqlserver://DBIP:Port;DatabaseName=testDB
spring.datasource.username=dbuser
spring.datasource.password=dbpassword
其次添加 mybatis-spring-boot-starter maven 依赖,它会自动添加相关的 mybatis 依赖包,配置如下:
<!-- 添加 mybatis-spring-boot依赖,直接可以使用mybatis环境操作DB-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
2.1 全手写 JAVA 代码实现 Mybatis 的 CRUD;
2.1.1. 在 cn.zuowenjun.boot.domain 包【实体模型或称领域模型层,这里算不上真正的领域模型,最多算是贫血的领域模型】中定义数据实体模型(Goods: 商品信息),代码如下:


package cn.zuowenjun.boot.domain;
import java.math.BigDecimal;
import java.util.Date;
public class Goods {
private int id;
private String title;
private String picture;
private BigDecimal price;
private String introduction;
private int categoryId;
private String lastEditBy;
private Date lastEditTime;
public Goods() {
}
public Goods(int id,String title,String picture,
BigDecimal price,String introduction,int categoryId,String lastEditBy,Date lastEditTime) {
this.setId(id);
this.setTitle(title);
this.setPicture(picture);
this.setPrice(price);
this.setIntroduction(introduction);
this.setCategoryId(categoryId);
this.setLastEditBy(lastEditBy);
this.setLastEditTime(lastEditTime);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPicture() {
return picture;
}
public void setPicture(String picture) {
this.picture = picture;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getIntroduction() {
return introduction;
}
public void setIntroduction(String introduction) {
this.introduction = introduction;
}
public int getCategoryId() {
return categoryId;
}
public void setCategoryId(int categoryId) {
this.categoryId = categoryId;
}
public String getLastEditBy() {
return lastEditBy;
}
public void setLastEditBy(String lastEditBy) {
this.lastEditBy = lastEditBy;
}
public Date getLastEditTime() {
return lastEditTime;
}
public void setLastEditTime(Date lastEditTime) {
this.lastEditTime = lastEditTime;
}
}
2.1.2. 在 cn.zuowenjun.boot.mapper 包【数据映射处理层或称 DAO 层】中定义数据映射处理接口及添加相应的 SQL 注解,以实现对数据进行 CRUD,代码如下:
package cn.zuowenjun.boot.mapper;
import java.util.*;
import org.apache.ibatis.annotations.*;
import cn.zuowenjun.boot.domain.*;
public interface GoodsMapper {
@Select("select * from TA_TestGoods order by id offset (${pageNo}-1)*${pageSize} rows fetch next ${pageSize} rows only")
List<Goods> getListByPage(int pageSize,int pageNo);
@Select("select * from TA_TestGoods where categoryId=#{categoryId} order by id")
List<Goods> getList(int categoryId);
@Select("<script>select * from TA_TestGoods where id in "
+"<foreach item=''item'' index=''index'' collection=''ids'' open=''('' separator='','' close='')''>#{item}</foreach>"
+"order by id</script>")
List<Goods> getListByMultIds(@Param("ids")int...ids);
@Select("select * from TA_TestGoods where id=#{id}")
Goods get(int id);
@Insert(value="insert into TA_TestGoods(title, picture, price, introduction, categoryId, "
+ "lastEditBy, lastEditTime) values(#{title},#{picture},#{price},#{introduction},#{categoryId},#{lastEditBy},getdate())")
@Options(useGeneratedKeys=true,keyProperty="id",keyColumn="id")
void insert(Goods goods);
@Delete(value="delete from TA_TestGoods where id=#{id}")
void delete(int id);
@Update("update TA_TestGoods set title=#{title},picture=#{picture},price=#{price},introduction=#{introduction}," +
"categoryId=#{categoryId},lastEditBy=#{lastEditBy},lastEditTime=getdate() " +
"where id=#{id}")
void update(Goods goods);
}
如上代码重点说明:
a. 增删改查,对应的注解是:insert、delete、update、select;
b.SQL 注解中的参数占位符有两种,一种是:#{xxx},最后会生成?的参数化执行,另一种是:${xxx} 则最后会直接替换成参数的值,即拼 SQL(除非信任参数或一些时间、数字类型,否则不建议这种,存在 SQL 注入风险);
c.insert 时如果有自增 ID,则可以通过添加 Options 注解,并指定 useGeneratedKeys=true,keyProperty="数据实体类的属性字段名",keyColumn="表自增 ID 的字段名",这样当 insert 成功后会自动回填到数据实体类的自增 ID 对应的属性上;
d. 如果想要生成 in 子句查询,则如上代码 getListByMultIds 方法上的 select 注解中使用 <script>xxx<foreach>xx</foreach>xx</script > 格式实现,如果想用实现复杂的一对一,一对多,多对多等复杂的查询,则需要添加 results 注解并指定相应的关联关系,同时 select SQL 语句也应关联查询,可参见:https://blog.csdn.net/desert568/article/details/79079151
以上 2 步即完成一个 mapper 操作类;
2.2 全手写 AVA 代码 + Mapper XML 实现 Mybatis 的 CRUD;
2.2.1. 仍然是在 cn.zuowenjun.boot.domain 包中定义一个数据实体模型类(ShoppingCart:购物车信息),代码如下:【注意这里有一个关联商品信息的属性:inGoods】


package cn.zuowenjun.boot.domain;
import java.util.Date;
public class ShoppingCart {
private int id;
private String shopper;
private int goodsId;
private int qty;
private Date addedTime;
private Goods inGoods;
public ShoppingCart() {
}
public ShoppingCart(int id,String shopper,int goodsId,int qty,Date addedTime) {
this.id=id;
this.shopper=shopper;
this.goodsId=goodsId;
this.qty=qty;
this.addedTime=addedTime;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getShopper() {
return shopper;
}
public void setShopper(String shopper) {
this.shopper = shopper;
}
public int getGoodsId() {
return goodsId;
}
public void setGoodsId(int goodsId) {
this.goodsId = goodsId;
}
public int getQty() {
return qty;
}
public void setQty(int qty) {
this.qty = qty;
}
public Date getAddedTime() {
return addedTime;
}
public void setAddedTime(Date addedTime) {
this.addedTime = addedTime;
}
public Goods getInGoods() {
return inGoods;
}
public void setInGoods(Goods inGoods) {
this.inGoods = inGoods;
}
}
2.2.2. 仍然是在 cn.zuowenjun.boot.mapper 包中定义数据操作接口(interface),注意这里只是定义接口,并不包含 SQL 注解部份,因为这部份将在 Mapper 的 XML 代码中进行配置实现,代码如下:
package cn.zuowenjun.boot.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import cn.zuowenjun.boot.domain.*;
public interface ShoppingCartMapper {
List<ShoppingCart> getList(String shopper);
void insert(ShoppingCart shoppingCart);
void update(ShoppingCart shoppingCart);
void deleteItem(int id);
void delete(String shopper);
int getBuyCount(String shopper);
ShoppingCart get(@Param("shopper") String shopper,@Param("goodsId") int goodsId);
}
如上代码有一个重点说明:get 方法有两个参数(多个参数也类似),为了 mybatis 能够自动映射到这些参数,必需为每个参数添加 Param 注解,并指定参数名,这个参数名是与对应的 Mapper XML 中的 SQL 语句中定义的参数名相同。
2.2.3. 在 mybatis.mapper-locations 设置的 mapper xml 存放的路径中创建 XML 文件,并手动编写映射的 SQL 语句,如下所示:
<?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="cn.zuowenjun.boot.mapper.ShoppingCartMapper">
<resultMap id="shoppingCartMap" type="ShoppingCart" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="shopper" property="shopper" jdbcType="NVARCHAR" />
<result column="goodsId" property="goodsId" jdbcType="INTEGER" />
<result column="qty" property="qty" jdbcType="INTEGER"/>
<result column="addedTime" property="addedTime" jdbcType="DATE" />
<!-- referseee https://www.cnblogs.com/ysocean/p/7237499.html -->
<association property="inGoods" javaType="cn.zuowenjun.boot.domain.Goods">
<id column="id" property="id" jdbcType="INTEGER" />
<result column="title" property="title" />
<result column="picture" property="picture" />
<result column="price" property="price" />
<result column="introduction" property="introduction" />
<result column="categoryId" property="categoryId" />
<result column="lastEditBy" property="lastEditBy" />
<result column="lastEditTime" property="lastEditTime" />
</association>
</resultMap>
<!-- 如果返回的结果与某个实体类完全相同,其实完全不需要上面的resultMap,而是直接使用resultType=类名,
如:resultType=cn.zuowenjun.boot.domain.ShoppingCart(简写别名:ShoppingCart),此处是示例用法,故采取指定映射 -->
<select id="getList" parameterType="string" resultMap="shoppingCartMap">
select * from TA_TestShoppingCart a inner join TA_TestGoods b on a.goodsId=b.id
where shopper=#{shopper} order by addedTime
</select>
<select id="getBuyCount" parameterType="string" resultType="int">
select count(1) from (select goodsId from TA_TestShoppingCart where shopper=#{shopper}
group by goodsId) as t
</select>
<select id="get" resultMap="shoppingCartMap">
select * from TA_TestShoppingCart a inner join TA_TestGoods b on a.goodsId=b.id
where shopper=#{shopper} and goodsId=#{goodsId}
</select>
<insert id="insert" parameterType="ShoppingCart"
useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into TA_TestShoppingCart(shopper, goodsId, qty, addedTime)
values(#{shopper},#{goodsId},#{qty},getdate())
</insert>
<update id="update" parameterType="ShoppingCart" >
update TA_TestShoppingCart set shopper=#{shopper},goodsId=#{goodsId},qty=#{qty},addedTime=getdate()
where id=#{id}
</update>
<delete id="deleteItem" parameterType="int">
delete from TA_TestShoppingCart where id=#{id}
</delete>
<delete id="delete" parameterType="string">
delete from TA_TestShoppingCart where shopper=#{shopper}
</delete>
</mapper>
如上 XML 重点说明:
a. 凡是使用到类型的地方,可以在 mybatis-config.xml 中提前配置类型别名,以简化配置,当然 mybatis 已默认设置了一些别名以减少大家配置的工作量,如:string,对应的类型是 String 等,详见:http://www.mybatis.org/mybatis-3/zh/configuration.html#typeAliases
b. 由于这个 ShoppingCart 有关联属性:inGoods,故在查询时都会关联查询 goods 表并通过在 resultMap 中通过 association 元素来指定关联关系,更多复杂的 XML 配置详见:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
以上 3 步即完成一个 mapper 操作类,相比直接使用 mapper 接口 + SQL 注解多了一个步骤,但这样的好处是由于没有写死在代码中,可以很容易的更改 mapper 的相关 SQL 语句,减少代码改动量。
2.3 使用 Mybatis Generator 的 Maven 插件自动生成 Mybatis 的 CRUD;
通过上面的介绍,我们知道有 2 种方法来实现一个 mapper 数据操作类(dao),显然第 2 种更能适应更改的情况,但由于手写 mapper xml 文件非常的麻烦,故可以通过 Mybatis Generator 组件,自动生成相关的代码及 xml(一般是:数据实体类 domain、数据处理接口 mapper、mapper XML),具体实现步骤如下:(可以单独一个项目来生成这些文件,也可以集成在一个项目中,由于是演示,我这里是集成在一个项目中)
2.3.1. 由于要使用 Mybatis Generator 组件,故需要添加对应的 JAR 包依赖,如下所示:
<!--SQL SERVER 数据驱动,以提供数据访问支持-->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>7.0.0.jre8</version><!--$NO-MVN-MAN-VER$ -->
</dependency>
<!-- 添加mybatis生成器,以便通过maven build自动生成model、mapper及XML -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
同时需要添加对应的 maven 插件,以便通过 maven 命令可执行生成过程,如下:(通过 configurationFile 元素指定生成器的配置路径,overwrite 元素指定是否覆盖生成,这里有个坑,后面会介绍到,此处略)
<build>
<plugins>
<plugin>
<!--ref: https://gitee.com/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md -->
<!--ref: https://www.cnblogs.com/handsomeye/p/6268513.html -->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<configurationFile>src/main/resources/mybatis/generatorconfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
2.3.2. 在 cn.zuowenjun.boot.domain 包中定义相关的数据实体模型类,我这里演示的类是:ShoppingOrder(购物订单信息),代码如下:


package cn.zuowenjun.boot.domain;
import java.math.BigDecimal;
import java.util.Date;
public class ShoppingOrder {
private Integer id;
private String shopper;
private Integer totalqty;
private BigDecimal totalprice;
private Boolean iscompleted;
private String createby;
private Date createtime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getShopper() {
return shopper;
}
public void setShopper(String shopper) {
this.shopper = shopper == null ? null : shopper.trim();
}
public Integer getTotalqty() {
return totalqty;
}
public void setTotalqty(Integer totalqty) {
this.totalqty = totalqty;
}
public BigDecimal getTotalprice() {
return totalprice;
}
public void setTotalprice(BigDecimal totalprice) {
this.totalprice = totalprice;
}
public Boolean getIscompleted() {
return iscompleted;
}
public void setIscompleted(Boolean iscompleted) {
this.iscompleted = iscompleted;
}
public String getCreateby() {
return createby;
}
public void setCreateby(String createby) {
this.createby = createby == null ? null : createby.trim();
}
public Date getCreatetime() {
return createtime;
}
public void setCreatetime(Date createtime) {
this.createtime = createtime;
}
}
2.3.3. 配置 generatorconfig.xml,指定生成的各个细节,由于 generatorconfig 的配置节点比较多,如下只是贴出当前示例的配置信息,有一点要说明,注意配置节点的顺序,如果顺序不对就会报错,完整的配置方法详情介绍可参见:https://gitee.com/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md 或 https://www.cnblogs.com/handsomeye/p/6268513.html


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="application.properties" />
<!-- https://blog.csdn.net/zsy3313422/article/details/53190613 -->
<classPathEntry
location="E:/LocalMvnRepositories/com/microsoft/sqlserver/mssql-jdbc/7.0.0.jre8/mssql-jdbc-7.0.0.jre8.jar" />
<context id="my" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="javaFileEncoding" value="UTF-8" />
<commentGenerator>
<property name="suppressAllComments" value="true" />
<property name="suppressDate" value="true" />
</commentGenerator>
<jdbcConnection
driverClass="${spring.datasource.driverClassName}"
connectionURL="${spring.datasource.url}"
userId="${spring.datasource.username}"
password="${spring.datasource.password}">
</jdbcConnection>
<!-- 生成model实体类文件位置 -->
<javaModelGenerator
targetPackage="cn.zuowenjun.boot.domain"
targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 生成mapper.xml配置文件位置 -->
<!-- targetPackage这里指定包名,则会在如下的路径中生成多层级目录 -->
<sqlMapGenerator targetPackage="mybatis.mapper"
targetProject="src/main/resources">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- 生成mapper接口文件位置 -->
<javaClientGenerator
targetPackage="cn.zuowenjun.boot.mapper"
targetProject="src/main/java" type="XMLMAPPER">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<table tableName="TA_TestShoppingOrder"
domainObjectName="ShoppingOrder">
<generatedKey column="id" sqlStatement="JDBC" identity="true" /><!-- 指示ID为自增ID列,并在插入后返回该ID -->
</table>
</context>
</generatorConfiguration>
由于涉及的知识点比较多,在此就不作介绍,请参见我给出的链接加以了解。
2.3.4. 通过 maven 插件来执行生成代码(生成代码有很多种方法,详见:https://blog.csdn.net/qq_32786873/article/details/78226925),这里我使用最为方便的一种,步骤如下:
项目右键 -》RunAs 或者 DeBug-》Maven Build...-》在 goals(阶段)中输入:mybatis-generator:generate,即:设置生成阶段,最后点击 Apply 或直接 Run 即可,如图示:
执行生成后,会在控制台中显示最终的结果,如下图示:如果成功会显示 buid success,并会在相应的目录中生成对应的文件
2.4 进阶用法:自定义 Mybatis Generator 的生成过程中的插件类,以便添加额外自定义的方法
虽然使用 Mybatis Generator 减少了手工编写代码及 XML 的工作量,但由于生成的 CRUD 方法都是比较简单的,稍微复杂或灵活一点的方法都不能简单生成,如果单纯的在生成代码后再人工手动添加其它自定义的方法,又担心如果执行一次自动生成又会覆盖手动添加的自定义代码,那有没有办法解决呢?当然是有的,我(梦在旅途,zuowenjun.cn)在网络上了解到的方法大部份都是说获取 Mybatis Generator 源代码,然后进行二次开发,最后使用 “定制版” 的 Mybatis Generator,我个人觉得虽然能解决问题,但如果能力不足,可能会出现意想不到的问题,而且进行定制也不是那么简单的,故我这里采取 Mybatis Generator 框架提供的可扩展插件 plugin 来实现扩展,具体步骤如下:
2.4.1. 在项目新创建一个包 cn.zuowenjun.boot.mybatis.plugin, 然后在包里面先创建一个泛型通用插件基类(CustomAppendMethodPlugin),这个基类主要是用于附加自定义方法,故取名 CustomAppendMethodPlugin,代码如下:


package cn.zuowenjun.boot.mybatis.plugin;
import java.util.List;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.codegen.mybatis3.javamapper.elements.AbstractJavaMapperMethodGenerator;
import org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.AbstractXmlElementGenerator;
/*
* 自定义通用可添加生成自定义方法插件类
* Author:zuowenjun
* Date:2019-1-29
*/
public abstract class CustomAppendMethodPlugin<TE extends AbstractXmlElementGenerator,TM extends AbstractJavaMapperMethodGenerator>
extends PluginAdapter {
protected final Class<TE> teClass;
protected final Class<TM> tmClass;
@SuppressWarnings("unchecked")
public CustomAppendMethodPlugin(Class<? extends AbstractXmlElementGenerator> teClass,
Class<? extends AbstractJavaMapperMethodGenerator> tmClass) {
this.teClass=(Class<TE>) teClass;
this.tmClass=(Class<TM>) tmClass;
}
@Override
public boolean sqlMapDocumentGenerated(Document document,
IntrospectedTable introspectedTable) {
try {
AbstractXmlElementGenerator elementGenerator = teClass.newInstance();
elementGenerator.setContext(context);
elementGenerator.setIntrospectedTable(introspectedTable);
elementGenerator.addElements(document.getRootElement());
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.sqlMapDocumentGenerated(document, introspectedTable);
}
@Override
public boolean clientGenerated(Interface interfaze,
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable) {
try {
AbstractJavaMapperMethodGenerator methodGenerator = tmClass.newInstance();
methodGenerator.setContext(context);
methodGenerator.setIntrospectedTable(introspectedTable);
methodGenerator.addInterfaceElements(interfaze);
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
}
@Override
public boolean validate(List<String> warnings) {
// TODO Auto-generated method stub
return true;
}
}
代码比较简单,主要是重写了 sqlMapDocumentGenerated(生成 mapper xml 方法)、clientGenerated(生成 mapper 接口方法),在这里面我通过把指定泛型类型(分别继承自 AbstractXmlElementGenerator、AbstractJavaMapperMethodGenerator)加入到生成 XML 和接口的过程中,以实现生成过程的抽象。
2.4.2. 我这里由于默认生成的 ShoppingOrderDetailMapper(实体类:ShoppingOrderDetail 是购物订单详情)无法满足需要,我需要额外再增加两个方法:
List<ShoppingOrderDetail> selectByOrderId (int shoppingOrderId); 、void deleteByOrderId (int shoppingOrderId); 故在这里自定义继承自 CustomAppendMethodPlugin 的插件类:ShoppingOrderDetailMapperPlugin,具体实现代码如下:
package cn.zuowenjun.boot.mybatis.plugin;
import java.util.Set;
import java.util.TreeSet;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.javamapper.elements.AbstractJavaMapperMethodGenerator;
import org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.AbstractXmlElementGenerator;
/*
* ref see https://www.cnblogs.com/se7end/p/9293755.html
* Author:zuowenjun
* Date:2019-1-29
*/
public class ShoppingOrderDetailMapperPlugin
extends CustomAppendMethodPlugin<ShoppingOrderDetailXmlElementGenerator, AbstractJavaMapperMethodGenerator> {
public ShoppingOrderDetailMapperPlugin() {
super(ShoppingOrderDetailXmlElementGenerator.class,ShoppingOrderDetailJavaMapperMethodGenerator.class);
}
}
class ShoppingOrderDetailXmlElementGenerator extends AbstractXmlElementGenerator{
@Override
public void addElements(XmlElement parentElement) {
if(!introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime().equalsIgnoreCase("TA_TestShoppingOrderDetail")) {
return;
}
TextElement selectText = new TextElement("select * from " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()
+ " where shoppingOrderId=#{shoppingOrderId}");
XmlElement selectByOrderId = new XmlElement("select");
selectByOrderId.addAttribute(new Attribute("id", "selectByOrderId"));
selectByOrderId.addAttribute(new Attribute("resultMap", "BaseResultMap"));
selectByOrderId.addAttribute(new Attribute("parameterType", "int"));
selectByOrderId.addElement(selectText);
parentElement.addElement(selectByOrderId);
TextElement deleteText = new TextElement("delete from " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()
+ " where shoppingOrderId=#{shoppingOrderId}");
XmlElement deleteByOrderId = new XmlElement("delete");
deleteByOrderId.addAttribute(new Attribute("id", "deleteByOrderId"));
deleteByOrderId.addAttribute(new Attribute("parameterType", "int"));
deleteByOrderId.addElement(deleteText);
parentElement.addElement(deleteByOrderId);
}
}
class ShoppingOrderDetailJavaMapperMethodGenerator extends AbstractJavaMapperMethodGenerator{
@Override
public void addInterfaceElements(Interface interfaze) {
if(!introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime().equalsIgnoreCase("TA_TestShoppingOrderDetail")) {
return;
}
addInterfaceSelectByOrderId(interfaze);
addInterfaceDeleteByOrderId(interfaze);
}
private void addInterfaceSelectByOrderId(Interface interfaze) {
// 先创建import对象
Set<FullyQualifiedJavaType> importedTypes = new TreeSet<FullyQualifiedJavaType>();
// 添加Lsit的包
importedTypes.add(FullyQualifiedJavaType.getNewListInstance());
// 创建方法对象
Method method = new Method();
// 设置该方法为public
method.setVisibility(JavaVisibility.PUBLIC);
// 设置返回类型是List
FullyQualifiedJavaType returnType = FullyQualifiedJavaType.getNewListInstance();
FullyQualifiedJavaType listArgType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
returnType.addTypeArgument(listArgType);
// 方法对象设置返回类型对象
method.setReturnType(returnType);
// 设置方法名称为我们在IntrospectedTable类中初始化的 “selectByOrderId”
method.setName("selectByOrderId");
// 设置参数类型是int类型
FullyQualifiedJavaType parameterType;
parameterType = FullyQualifiedJavaType.getIntInstance();
// import参数类型对象(基本类型其实可以不必引入包名)
//importedTypes.add(parameterType);
// 为方法添加参数,变量名称record
method.addParameter(new Parameter(parameterType, "shoppingOrderId")); //$NON-NLS-1$
//
context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);
if (context.getPlugins().clientSelectByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable)) {
interfaze.addImportedTypes(importedTypes);
interfaze.addMethod(method);
}
}
private void addInterfaceDeleteByOrderId(Interface interfaze) {
// 创建方法对象
Method method = new Method();
// 设置该方法为public
method.setVisibility(JavaVisibility.PUBLIC);
// 设置方法名称为我们在IntrospectedTable类中初始化的 “deleteByOrderId”
method.setName("deleteByOrderId");
// 设置参数类型是int类型
FullyQualifiedJavaType parameterType;
parameterType = FullyQualifiedJavaType.getIntInstance();
method.addParameter(new Parameter(parameterType, "shoppingOrderId")); //$NON-NLS-1$
context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);
if (context.getPlugins().clientSelectByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable)) {
interfaze.addMethod(method);
}
}
}
从如上代码所示,核心点是自定义继承自 AbstractXmlElementGenerator、AbstractJavaMapperMethodGenerator 的 ShoppingOrderDetailXmlElementGenerator(XML 生成器类)、ShoppingOrderDetailJavaMapperMethodGenerator(mapper 接口生成器类),然后分别在 addElements、addInterfaceElements 添加自定义生成 XML 及接口方法的逻辑(如上代码中使用的是反射,若想学习了解反射请自行网上查找相关资料,C# 也有反射哦,应该好理解),注意由于插件在生成过程中每个实体类都会调用一次,故必需作相应的判断(判断当前要附加的自定义方法是符与当前实体类生成过程相符,如果不相符则忽略退出)
如下是 ShoppingOrderDetail 实体类的代码:


package cn.zuowenjun.boot.domain;
import java.math.BigDecimal;
import java.util.Date;
public class ShoppingOrderDetail {
private Integer id;
private Integer shoppingorderid;
private Integer goodsid;
private Integer qty;
private BigDecimal totalprice;
private String createby;
private Date createtime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getShoppingorderid() {
return shoppingorderid;
}
public void setShoppingorderid(Integer shoppingorderid) {
this.shoppingorderid = shoppingorderid;
}
public Integer getGoodsid() {
return goodsid;
}
public void setGoodsid(Integer goodsid) {
this.goodsid = goodsid;
}
public Integer getQty() {
return qty;
}
public void setQty(Integer qty) {
this.qty = qty;
}
public BigDecimal getTotalprice() {
return totalprice;
}
public void setTotalprice(BigDecimal totalprice) {
this.totalprice = totalprice;
}
public String getCreateby() {
return createby;
}
public void setCreateby(String createby) {
this.createby = createby == null ? null : createby.trim();
}
public Date getCreatetime() {
return createtime;
}
public void setCreatetime(Date createtime) {
this.createtime = createtime;
}
}
另外顺便解决一个踩坑点:上面提到了,我们在 POM 文件配置 mybatis-generator-maven-plugin 插件时,overwrite 设为 true,目的是确保每次执行生成时,生成的代码能够覆盖已经存在的,理想是美好的,但现实总会有点小意外,我们这样配置,只能解决生成的 mapper 接口类文件不会重复,但生成的 mapper xml 文件仍然会附加代码导致重复,故我们需要解决这个问题,而解决这个问题的关键是:GeneratedXmlFile.isMergeable,如果 isMergeable 为 true 则会合并,目前默认都是 false,所以我们只需实现将 GeneratedXmlFile.isMergeable 设为 true 即可,由于 isMergeable 是私有字段,只能采取插件 + 反射动态改变这个值了,自定义合并代码插件 OverIsMergeablePlugin 实现如下:
package cn.zuowenjun.boot.mybatis.plugin;
import java.lang.reflect.Field;
import java.util.List;
import org.mybatis.generator.api.GeneratedXmlFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
/*
* 修复mybatis-generator重复执行时生成的XML有重复代码(核心:isMergeable=false)
* Author:https://blog.csdn.net/zengqiang1/article/details/79381418
* Editor:zuowenjun
*/
public class OverIsMergeablePlugin extends PluginAdapter {
@Override
public boolean validate(List<String> warnings) {
return true;
}
@Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap,
IntrospectedTable introspectedTable) {
try {
Field field = sqlMap.getClass().getDeclaredField("isMergeable");
field.setAccessible(true);
field.setBoolean(sqlMap, false);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
2.4.3. 在 generatorconfig.xml 配置文件中增加 plugin 配置,如下:
<plugin type="cn.zuowenjun.boot.mybatis.plugin.OverIsMergeablePlugin"></plugin>
<plugin type="cn.zuowenjun.boot.mybatis.plugin.ShoppingOrderDetailMapperPlugin"></plugin>
... ...省略中间过程
<table tableName="TA_TestShoppingOrderDetail"
domainObjectName="ShoppingOrderDetail">
<generatedKey column="id" sqlStatement="JDBC" identity="true" />
</table>
2.4.4. 由于不能在同一个项目中直接使用 plugin 类(具体原因请上网查询,在此了解即可),故还需把 cn.zuowenjun.boot.mybatis.plugin 这个包中的文件单独导出生成 JAR 包,然后把这个 JAR 包复制到项目的指定目录下(本示例是放在 libs 目录下),然后再在 POM 为 mybatis-generator-maven-plugin 单独添加 system 本地依赖才行,maven 添加依赖如下:
<plugin>
<!--ref: https://gitee.com/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md -->
<!--ref: https://www.cnblogs.com/handsomeye/p/6268513.html -->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<configurationFile>src/main/resources/mybatis/generatorconfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<!-- 为mybatis-generator增加自定义插件依赖 -->
<dependency>
<groupId>cn.zuowenjun.boot.mybatis.plugin</groupId>
<artifactId>cn.zuowenjun.boot.mybatis.plugin</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/main/libs/cn.zuowenjun.boot.mybatis.plugin.jar</systemPath>
</dependency>
</dependencies>
</plugin>
如果 4 步完成后,最后执行 maven buid 的生成 mybatis 代码过程即可,最后查看生成的 mapper 及 xml 都会有对应的自定义方法,在此就不再贴出结果了。
2.5 进阶用法:利用 Mybatis 的继承机制实现添加额外自定义方法
如 2.4 节所述,我们可以通过自定义 plugin 来实现添加额外自定义的方法,而且不用担心被覆盖,但可能实现有点麻烦(里面用到了反射),有没有简单一点的办法呢?当然有,即可以先使用 Mybatis Generator 框架生成默代代码,然后再结合使用 2.2 所述方法(手写 mapper 接口类及 mapper XML),利用 mapper XML 的继承特性完成添加自定义方法的过程中,具体步骤与 2.2 相同,在此贴出(注意前提是先自动生成代码,然后再操作如下步骤)
2.5.1. 定义扩展 mapper 接口类(ShoppingOrderExtMapper,扩展 ShoppingOrderMapper,它们之间无需继承),代码如下:(很简单,就是定义了一个特殊用途的方法)
package cn.zuowenjun.boot.mapper;
import java.util.List;
import cn.zuowenjun.boot.domain.ShoppingOrder;
public interface ShoppingOrderExtMapper {
List<ShoppingOrder> selectAllByShopper(String shopper);
}
2.5.2. 编写对应的 ShoppingOrderExtMapper.xml,这里面就要用到继承,继承主要是 resultMap【实现继承用:extends = 要继承的 mapper xml resultMap】,这样就不用两个地方都为一个实体类写结果映射配置了,其余的都按一个新的 mapper 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="cn.zuowenjun.boot.mapper.ShoppingOrderExtMapper">
<resultMap id="BaseResultMap" type="cn.zuowenjun.boot.domain.ShoppingOrder"
extends="cn.zuowenjun.boot.mapper.ShoppingOrderMapper.BaseResultMap">
</resultMap>
<select id="selectAllByShopper" resultMap="BaseResultMap" parameterType="string">
select * from TA_TestShoppingOrder where shopper=#{shopper}
</select>
</mapper>
如上两步即完成扩展添加额外自定义的方法,又不用担心重复执行生成代码会被覆盖掉,只是使用时需要单独注册到 spring,单独实例,虽不完美但弥补了默认生成代码的不足也是可行的。
2.6 使用 SpringBootTest + junit 测试基于 Mybatis 框架实现的 DAO 类
在此不详情说明 junit 测试的用法,网上大把资源,只是单独说明结合 SpringBootTest 注解,完成单元测试,先看单元测试代码:


package cn.zuowenjun.springbootdemo;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import cn.zuowenjun.boot.SpringbootdemoApplication;
import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.mapper.GoodsMapper;
import cn.zuowenjun.boot.mapper.ShoppingOrderDetailMapper;
import cn.zuowenjun.boot.mapper.ShoppingOrderMapper;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=SpringbootdemoApplication.class)
public class ShoppingOrderMapperTests {
@Autowired
private ShoppingOrderMapper shoppingOrderMapper;
@Autowired
private ShoppingOrderDetailMapper shoppingOrderDetailMapper;
@Autowired
private GoodsMapper goodsMapper;
@Transactional
@Rollback(false) //不加这个,默认测试完后自动回滚
@Test
public void testInsertShoppingOrder() {
Goods goods= goodsMapper.get(1);
ShoppingOrder shoppingOrder=new ShoppingOrder();
shoppingOrder.setShopper("zuowenjun");
shoppingOrder.setIscompleted(false);
shoppingOrder.setTotalprice(BigDecimal.valueOf(0));
shoppingOrder.setTotalqty(1);
shoppingOrder.setCreateby("zuowenjun");
shoppingOrder.setCreatetime(new Date());
int orderId= shoppingOrderMapper.insert(shoppingOrder);
shoppingOrder.setId(orderId);
ShoppingOrderDetail shoppingOrderDetail=new ShoppingOrderDetail();
shoppingOrderDetail.setGoodsid(goods.getId());
shoppingOrderDetail.setShoppingorderid(shoppingOrder.getId());
shoppingOrderDetail.setQty(10);
shoppingOrderDetail.setTotalprice(BigDecimal.valueOf(shoppingOrderDetail.getQty()).multiply(goods.getPrice()));
shoppingOrderDetail.setCreateby("zuowenjun");
shoppingOrderDetail.setCreatetime(new Date());
shoppingOrderDetailMapper.insert(shoppingOrderDetail);
List<ShoppingOrderDetail> orderDetails= shoppingOrderDetailMapper.selectByOrderId(shoppingOrder.getId());
if(orderDetails!=null && orderDetails.size()>0) {
for(ShoppingOrderDetail od:orderDetails) {
System.out.println("id:" + od.getId() + ",goodsid:" + od.getGoodsid());
}
}
Assert.assertTrue(orderDetails.size()>0);
}
}
与 Junit 单元测试用法基本相同,唯 一的区别就是在单元测试的类上添加 @SpringBootTest,并指定启动类(如代码中所示:@SpringBootTest (classes=SpringbootdemoApplication.class)),另外注意一点:如果测试方法使用 @Transactional 注解,那么当测试完成后会回滚(即并不会提交事务),如果想完成事务的提交,则需如代码中所示添加 @Rollback (false),其中 false 指不回滚,true 则为回滚。
三、简单演示集成 Thymeleaf 模板引擎(这里只是用一个简单的页面演示效果,由于现在都流行前后端分离,故只需了解即可)
说明:Thymeleaf 是 spring MVC 的端视图引擎,与 JSP 视图引擎类似,只不过在 spring boot 项目中默认支持 Thymeleaf(Thymeleaf 最大的优点是视图中不含 JAVA 代码,不影响 UI 美工及前端设计),而 JSP 不建议使用,当然也可以通过添加相关的 JSP 的 JAR 包依赖,实现 JSP 视图,具体请自行网上查找资源,同时 spring MVC +JSP 视图的用法可以参见该系列的上篇文章
3.1. 添加 Thymeleaf 的 maven 依赖,POM 配置如下:
<!-- 添加thymeleaf模板引擎(用于springMVC模式,如果是rest API项目,则无需引用) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
3.2. 编写后端 controller,以便响应用户请求,代码如下:(这个与普通 spring MVC+JSP 相同,区别在 VIEW)
package cn.zuowenjun.boot.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.service.*;
@Controller
@RequestMapping("/test")
public class TestController {
@Autowired
private ShopUserService shopUserService;
@GetMapping("/userlist")
public String list(Model model) {
List<ShopUser> users= shopUserService.getAll();
model.addAttribute("title", "测试使用thymeleaf模板引擎展示数据");
model.addAttribute("users", users);
//可以在application.properties添加如下配置,以改变thymeleaf的默认设置
//spring.thymeleaf.prefix="classpath:/templates/" 模板查找路径
//spring.thymeleaf.suffix=".html" 模板后缀名
return "/test";//默认自动查找路径:src/main/resources/templates/*.html
}
}
3.3 编写前端视图 html 模板页面,最后演示效果
HTML 视图页面代码:(th:XXX 为 Thymeleaf 的模板特有的标识符,${xxx} 这是 SP EL 表达式,这个之前讲过的,很简单,不展开说明)


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test User List -power by thymeleaf</title>
<style type="text/css">
table{
border:2px solid blue;
border-collapse:collapse;
width:98%;
}
table *{
border:1px solid blue;
text-align:center;
}
thead{
background-color:purple;
color:yellow;
}
th,td{
padding:5px;
}
#copy{
margin-top:100px;
text-align: center;
}
</style>
</head>
<body>
<h1 th:text="${title}"></h1>
<table>
<thead>
<tr>
<th>SeqNo</th>
<th>userId</th>
<th>nickName</th>
<th>depositAmount</th>
</tr>
</thead>
<tbody>
<tr th:if="${users}!=null" th:each="user,iterStat:${users}">
<td th:text="${iterStat.index}+1">1</td>
<td th:text="${user.userid}">www.zuowenjun.cn</td>
<td th:text="${user.nickname}">梦在旅途</td>
<td th:text="${user.depositamount}">520</td>
</tr>
<tr th:unless="${users.size()} gt 0">
<td colspan="4">暂无相关记录!</td>
</tr>
</tbody>
</table>
<p id="copy">
Copyright ©<span th:text="${#dates.format(#dates.createToday(),''yyyy'')}"></span>
www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
</p>
</body>
</html>
最后浏览:http://localhost:8080/test/userlist,效果如下图示:
四、利用 VUE+SpringMVC Rest API 编写实现前后端分离的电商购物 Demo(浏览商品、添加购物车、下单、完成)
说明:由于数据访问层(或称:数据持久层)已由 Mybatis Generator 完成了,现在就只要编写业务领域服务层(接口层、实现层),API 接入层即可完成后端开发,然后再开发设计前端页面即可(前端与后端交互使用 AJAX)
4.1. 在 cn.zuowenjun.boot.service 包中定义相关的业务领域服务接口


//ShopUserService.java
package cn.zuowenjun.boot.service;
import java.util.List;
import cn.zuowenjun.boot.domain.ShopUser;
public interface ShopUserService {
List<ShopUser> getAll();
ShopUser get(String userId);
String getCurrentLoginUser();
String login(String uid,String pwd);
void logout();
}
//GoodsService.java
package cn.zuowenjun.boot.service;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import cn.zuowenjun.boot.domain.*;
public interface GoodsService {
List<Goods> getGoodsListByPage(int pageSize,int pageNo);
List<Goods> getGoodsListByCategory(int categoryId);
List<Goods> getGoodsListByMultIds(int...goodsIds);
Goods getGoods(int id);
void insertGoods(Goods goods,MultipartFile uploadGoodsPic);
void updateGoods(Goods goods,MultipartFile uploadGoodsPic);
void deleteGoods(int id);
List<GoodsCategory> getAllGoodsCategoryList();
void insertGoodsCategory(GoodsCategory goodsCategory);
void updateGoodsCategory(GoodsCategory goodsCategory);
void deleteGoodsCategory(int id);
}
//ShoppingOrderService.java
package cn.zuowenjun.boot.service;
import java.util.List;
import cn.zuowenjun.boot.domain.*;
public interface ShoppingOrderService {
ShoppingOrder getShoppingOrder(int id);
List<ShoppingOrder> getShoppingOrderList(String shopper);
List<ShoppingOrderDetail> getShoppingOrderDetail(int orderId);
boolean createShoppingOrderByShopper(String shopper);
void insertShoppingOrderWithDetail(ShoppingOrder order,List<ShoppingOrderDetail> orderDetails);
void deleteShoppingOrderDetail(int orderDetailId);
void deleteShoppingOrderWithDetail(int orderId);
void updateShoppingOrder(ShoppingOrder order);
List<ShoppingCart> getShoppingCartList(String shopper);
int getShoppingCartBuyCount(String shopper);
void insertShoppingCart(ShoppingCart shoppingCart);
void deleteShoppingCart(int shoppingCartId);
void clearShoppingCart(String shopper);
}
如上代码示,我仅定义了三个 service 接口,分别是:ShopUserService(用户服务)、GoodsService(商品服务【含:商品类别、商品信息】)、ShoppingOrderService(购物订单服务【含:购物车、购物订单、购物订单明细】),我说过服务层不一定是与 DB 中的表一 一对应的,而是应该体现服务内聚(即:业务领域),如果单纯的与 DAO 层一样,一个 service 与一个 dao 对应,那就失去了分层的意义,而且还增加了复杂度。个人看法。
4.2 在 cn.zuowenjun.boot.service.impl 包中实现 4.1 中相关的业务领域服务接口(代码很简单,主要是实现接口的一些方法,唯一有点特别是文件上传,事务,记录日志,这些通过代码就能看明白就不再详情描述了)


//ShopUserServiceImpl.java
package cn.zuowenjun.boot.service.impl;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import cn.zuowenjun.boot.EShopProperties;
import cn.zuowenjun.boot.domain.ShopUser;
import cn.zuowenjun.boot.mapper.ShopUserMapper;
import cn.zuowenjun.boot.service.ShopUserService;
@Service
public class ShopUserServiceImpl implements ShopUserService {
private ShopUserMapper shopUserMapper;
private EShopProperties shopProperties;
@Autowired
public ShopUserServiceImpl(ShopUserMapper shopUserMapper,EShopProperties shopProperties) {
this.shopUserMapper=shopUserMapper;
this.shopProperties=shopProperties;
}
@Override
public List<ShopUser> getAll() {
return shopUserMapper.selectAll();
}
@Override
public ShopUser get(String userId) {
return shopUserMapper.selectByPrimaryKey(userId);
}
@Override
public String getCurrentLoginUser() {
if(getRequest().getSession().getAttribute("loginUser")==null) {
return null;
}
return getRequest().getSession().getAttribute("loginUser").toString();
}
@Override
public String login(String uid, String pwd) {
if(shopProperties.getShopUserId().equalsIgnoreCase(uid) &&
shopProperties.getShopUserPwd().equals(pwd)) {
getRequest().getSession().setAttribute("loginUser", uid);
return null;
}else {
return "用户名或密码不正确!";
}
}
@Override
public void logout() {
getRequest().getSession().removeAttribute("loginUser");
}
private HttpServletRequest getRequest() {
HttpServletRequest request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
return request;
}
}
//GoodsServiceImpl.java
package cn.zuowenjun.boot.service.impl;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
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 org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.mapper.GoodsCategoryMapper;
import cn.zuowenjun.boot.mapper.GoodsMapper;
import cn.zuowenjun.boot.service.GoodsService;
@Service
public class GoodsServiceImpl implements GoodsService {
private static Logger logger=LoggerFactory.getLogger(GoodsServiceImpl.class);
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private GoodsCategoryMapper categoryMapper;
@Override
public List<Goods> getGoodsListByPage(int pageSize,int pageNo){
return goodsMapper.getListByPage(pageSize, pageNo);
}
@Override
public List<Goods> getGoodsListByCategory(int categoryId) {
return goodsMapper.getList(categoryId);
}
@Override
public List<Goods> getGoodsListByMultIds(int... goodsIds) {
return goodsMapper.getListByMultIds(goodsIds);
}
@Override
public Goods getGoods(int id) {
return goodsMapper.get(id);
}
@Transactional
@Override
public void insertGoods(Goods goods, MultipartFile uploadGoodsPic) {
String picPath= saveGoodsPic(uploadGoodsPic);
if(picPath!=null && !picPath.isEmpty()) {
goods.setPicture(picPath);
}
goodsMapper.insert(goods);
GoodsCategory gcate= categoryMapper.get(goods.getCategoryId());
gcate.setGoodsCount(gcate.getGoodsCount()+1);
categoryMapper.update(gcate);
logger.info("inserted new goods - id:" + goods.getId());
}
@Override
public void updateGoods(Goods goods,MultipartFile uploadGoodsPic) {
String picPath= saveGoodsPic(uploadGoodsPic);
if(picPath!=null && !picPath.isEmpty()) {
goods.setPicture(picPath);
}
goodsMapper.update(goods);
logger.info("update goods - id:" + goods.getId());
}
@Transactional
@Override
public void deleteGoods(int id) {
Goods g= goodsMapper.get(id);
goodsMapper.delete(g.getId());
GoodsCategory gcate= categoryMapper.get(g.getCategoryId());
gcate.setGoodsCount(gcate.getGoodsCount()-1);
categoryMapper.update(gcate);
//如果有图片,则同时删除图片
if(g.getPicture()!=null && !g.getPicture().isEmpty()) {
String picPath= getRequest().getServletContext().getRealPath("/") + g.getPicture();
File file = new File(picPath);
if(file.exists()) {
file.delete();
}
}
logger.info("deleted goods - id:" + g.getId());
}
@Override
public List<GoodsCategory> getAllGoodsCategoryList(){
return categoryMapper.getAll();
}
@Override
public void insertGoodsCategory(GoodsCategory goodsCategory) {
categoryMapper.insert(goodsCategory);
}
@Override
public void updateGoodsCategory(GoodsCategory goodsCategory) {
categoryMapper.update(goodsCategory);
}
@Override
public void deleteGoodsCategory(int id) {
categoryMapper.delete(id);
}
private String saveGoodsPic(MultipartFile uploadGoodsPic) {
if(uploadGoodsPic==null || uploadGoodsPic.isEmpty()) {
return null;
}
String fileName = uploadGoodsPic.getOriginalFilename();
String extName = fileName.substring(fileName.lastIndexOf("."));
String newFileName=UUID.randomUUID().toString()+extName;
File file = new File(getFileSavePath(newFileName));
if(!file.exists()) {
file.getParentFile().mkdirs();
}
try {
uploadGoodsPic.transferTo(file);
//return file.toURI().toURL().toString();
return getUrlPath(file.getAbsolutePath());
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String getFileSavePath(String fileName) {
String realPath =getRequest().getServletContext().getRealPath("/uploadimgs/");
return realPath + fileName;
}
private String getUrlPath(String filePath) {
String rootPath= getRequest().getServletContext().getRealPath("/");
return filePath.replace(rootPath, "").replaceAll("\\\\", "/");
}
private HttpServletRequest getRequest() {
HttpServletRequest request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
return request;
}
}
//ShoppingOrderServiceImpl.java
package cn.zuowenjun.boot.service.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.mapper.*;
import cn.zuowenjun.boot.service.ShoppingOrderService;
@Service
public class ShoppingOrderServiceImpl implements ShoppingOrderService {
@Autowired
private ShoppingOrderMapper orderMapper;
@Autowired
private ShoppingOrderDetailMapper orderDetailMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private ShoppingOrderExtMapper shoppingOrderExtMapper;
@Override
public void insertShoppingCart(ShoppingCart shoppingCart) {
ShoppingCart cart=shoppingCartMapper.get(shoppingCart.getShopper(), shoppingCart.getGoodsId());
if(cart==null) {
shoppingCartMapper.insert(shoppingCart);
}else {
cart.setQty(cart.getQty()+shoppingCart.getQty());
shoppingCartMapper.update(cart);
}
}
@Override
public void deleteShoppingCart(int shoppingCartId) {
shoppingCartMapper.deleteItem(shoppingCartId);
}
@Override
public ShoppingOrder getShoppingOrder(int id) {
return orderMapper.selectByPrimaryKey(id);
}
@Override
public List<ShoppingOrder> getShoppingOrderList(String shopper) {
return shoppingOrderExtMapper.selectAllByShopper(shopper);
}
@Override
public List<ShoppingOrderDetail> getShoppingOrderDetail(int orderId) {
return orderDetailMapper.selectByOrderId(orderId);
}
@Transactional
@Override
public boolean createShoppingOrderByShopper(String shopper) {
List<ShoppingCart> carts= shoppingCartMapper.getList(shopper);
if(carts==null || carts.size()<=0) {
return false;
}
int totalQty=0;
BigDecimal totalPrc=BigDecimal.valueOf(0);
List<ShoppingOrderDetail> orderDetails=new ArrayList<>();
for(ShoppingCart c:carts) {
totalQty+=c.getQty();
BigDecimal itemPrc=c.getInGoods().getPrice().multiply(BigDecimal.valueOf(c.getQty()));
totalPrc=totalPrc.add(itemPrc);
ShoppingOrderDetail od=new ShoppingOrderDetail();
od.setGoodsid(c.getGoodsId());
od.setQty(c.getQty());
od.setTotalprice(itemPrc);
od.setCreateby(shopper);
od.setCreatetime(new Date());
orderDetails.add(od);
}
ShoppingOrder order=new ShoppingOrder();
order.setShopper(shopper);
order.setTotalqty(totalQty);
order.setTotalprice(totalPrc);
order.setCreateby(shopper);
order.setCreatetime(new Date());
order.setIscompleted(false);
insertShoppingOrderWithDetail(order,orderDetails);
clearShoppingCart(shopper);
return true;
}
@Transactional
@Override
public void insertShoppingOrderWithDetail(ShoppingOrder order, List<ShoppingOrderDetail> orderDetails) {
orderMapper.insert(order);
int orderId=order.getId();
for(ShoppingOrderDetail od:orderDetails) {
od.setShoppingorderid(orderId);
orderDetailMapper.insert(od);
}
}
@Override
public void deleteShoppingOrderDetail(int orderDetailId) {
orderDetailMapper.deleteByPrimaryKey(orderDetailId);
}
@Transactional
@Override
public void deleteShoppingOrderWithDetail(int orderId) {
orderMapper.deleteByPrimaryKey(orderId);
orderDetailMapper.deleteByOrderId(orderId);
}
@Override
public void updateShoppingOrder(ShoppingOrder order) {
orderMapper.updateByPrimaryKey(order);
}
@Override
public List<ShoppingCart> getShoppingCartList(String shopper) {
return shoppingCartMapper.getList(shopper);
}
@Override
public int getShoppingCartBuyCount(String shopper) {
return shoppingCartMapper.getBuyCount(shopper);
}
@Override
public void clearShoppingCart(String shopper) {
shoppingCartMapper.delete(shopper);
}
}
4.3 编写基于 VUE 前端框架实现的相关 UI 界面
4.3.1.VUE 是什么?如何使用 VUE 前端框架设计页面?认真阅读官方中文教程就可以了:https://cn.vuejs.org/v2/guide/index.html ,这里只是着重说明一下,VUE 是实现了 MVVM 框架,使用 VUE 的核心组件:模板、路由、数据双向绑定等特性能够设计出很牛逼的 SPA(单 WEB 页面的多 UI 交互的应用),本人(梦在旅途)VUE 只是初学者,故在本示例中我只是使用 VUE 的最基本的一些功能属性(如:el:指定 VUE 的渲染范围(绑定的作用域)、data(数据模型 MODEL)、computed(动态计算属性)、created(VUE 初始化后触发的事件)、methods(绑定自定义方法))
4.3.2. 由于采用前后端分离,完全可以一个项目全是静态的 VUE HTML 模板,另一个项目是基于 spring boot REST Api 项目,但这里是演示,故采取在同一个项目中,我这里是在 webapp 目录下创建相关的 HTML 视图页面,如果不在同一个项目中,注意基于 spring boot REST Api 项目中需要设置能够允许跨域访问,所有 HTML 视图代码如下:
index.html(商品列表,主页)


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>梦在旅途的电商小店Demo-Power by Spring Boot+MyBatis-Boot</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
#catesbox ul li{float:left;padding:5px;margin-right:20px;border:1px solid green;display: inline-block;cursor:pointer;}
.clfx {clear:both;display:block;}
.gpic{width:100px;height:100px;text-align:center;vertical-align:middle;}
#goodsbox table {width:100%;border-collapse:collapse;}
#goodsbox table tr >*{border:1px solid blue;padding:5px;}
li.active{background-color:orange;font-weight:bold;}
#copy{
margin-top:20px;
text-align: center;
}
body{padding-top:51px;}
#topbar{height:50px;line-height:50px;margin:0;width:100%;background-color:WhiteSmoke;
position: fixed;top:0;border-bottom:1px solid darkgray;text-align: right;}
</style>
</head>
<body>
<div id="app">
<div id="topbar">
<a href="/cartlist.html" target="_blank">购物车(已加入商品数量:{{cartCount}})</a> |
<a href="/orderlist.html" target="_blank">订单中心</a> |
<a href="/admin.html" target="_blank">管理后台</a>
</div>
<h2>商品类目:</h2>
<div id="catesbox">
<ul v-for="c in cates">
<li v-on:click="getGoodsListByCategory(c)" v-bind:class="{active:c.categoryName==curcate}">{{c.categoryName}}({{c.goodsCount}})</li>
</ul>
<div class="clfx"></div>
</div>
<h2>当前浏览的商品分类:<span>{{curcate}}</span></h2>
<div id="goodsbox">
<table>
<tr>
<th>商品图片</th>
<th>商品标题</th>
<th>价格</th>
<th>操作</th>
</tr>
<tr v-for="g in goods">
<td><img v-bind:src="g.picture" class="gpic"></td>
<td><a v-bind:href="''/detail.html?gid='' + g.id" target="_blank">{{g.title}}</a></td>
<td>¥{{g.price}}</td>
<td><button v-on:click="addToShoppingCart(g)">加入购物车</button></td>
</tr>
</table>
</div>
</div>
<p id="copy">
Copyright ©2019
www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
</p>
<script type="text/javascript">
var vm = new Vue({
el:"#app",
data:{
cartCount:0,
cates:[],
goods:[],
curcate:"ALL"
},
created:function(){
var self = this;
this.$http.get(''/api/categorys'').then(function(res){
self.cates=res.body;
//alert(JSON.stringify(self.cates));
},function(){
alert("获取categorys失败!");
});
this.$http.get(''/api/cartlist'').then(function(res){
self.cartCount=res.body.length;
//alert(JSON.stringify(self.goods));
},function(){
alert("获取购物车信息失败!");
});
//按分页检索商品列表
this.getGoodsListByPage(10,1);
},
methods:{
getGoodsListByCategory:function(cate){
var self = this;
//按类别检索商品列表
this.$http.get(''/api/goods/'' + cate.id).then(function(res){
self.goods=res.body;
self.curcate=cate.categoryName;
//alert(JSON.stringify(self.goods));
},function(){
alert("获取goods失败!");
});
},
getGoodsListByPage:function(ps,pno){
var self = this;
//按分页检索商品列表
this.$http.get(''/api/goods'' +''?pagesize=''+ps +''&page='' + pno).then(function(res){
self.goods=res.body;
self.curcate="ALL";
//alert(JSON.stringify(self.goods));
},function(){
alert("获取goods失败!");
});
},
addToShoppingCart:function(goods){
//加入购物车
var self = this;
var qty=prompt(''请输入购买数量'',1);
this.$http.post(''/api/addToShoppingCart'',{goodsid:goods.id,goodsqty:qty}).then(function(res){
var rs=res.body;
alert(rs.msg);
self.cartCount=rs.data.cartCount;
},function(){
alert("加入购物车失败");
});
}
}
});
</script>
</body>
</html>
detail.html(商品详情)


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品详情 -梦在旅途的电商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
.clfx {clear:both;display:block;}
.row{width:100%;margin:10px 0;}
.lbox{float:left;width:40%;min-height: 100px;}
.rbox{float:right;width:50%;}
.rbox ul li{margin:50px auto;}
body{padding-top:51px;}
#topbar{height:50px;line-height:50px;margin:0;width:100%;background-color:WhiteSmoke;
position: fixed;top:0;border-bottom:1px solid darkgray;text-align: right;}
</style>
</head>
<body>
<div id="app">
<div id="topbar">
<a href="/cartlist.html" target="_blank">购物车(已加入商品数量:{{cartCount}})</a> |
<a href="/admin.html" target="_blank">管理后台</a>
</div>
<div class="row">
<div class="lbox">
<img :src="goods.picture" style="width:100%;height:100%;margin:0;padding:0;">
</div>
<div class="rbox">
<ul>
<li><strong>{{goods.title}}</strong></li>
<li>价格:¥{{goods.price}}</li>
<li>购买数量:<input v-model="buyqty" value="1"></li>
<li>购买价格:<span>{{buyprice}}</span></li>
<li><button @click="addToShoppingCart">加入购物车</button></li>
</ul>
</div>
<div class="clfx"></div>
</div>
<div class="row">
<h2>商品详细描述:</h2>
<hr/>
<p>{{goods.introduction}}</p>
</div>
</div>
<script type="text/javascript">
var vm=new Vue({
el:"#app",
data:{
cartCount:0,
buyqty:1,
goods:{}
},
created:function(){
var gid= getQueryString("gid");
var self = this;
this.$http.get(''/api/goods-'' + gid).then(function(res){
self.goods=res.body;
//alert(JSON.stringify(self.goods));
},function(){
alert("获取goods失败!");
});
this.$http.get(''/api/cartlist'').then(function(res){
self.cartCount=res.body.length;
//alert(JSON.stringify(self.goods));
},function(){
alert("获取购物车信息失败!");
});
},
computed:{
buyprice:function(){
return (this.buyqty * this.goods.price).toFixed(2);
}
},
methods:{
addToShoppingCart:function(){
//alert(this.buyqty);
//加入购物车
var self = this;
this.$http.post(''/api/addToShoppingCart'',{goodsid:this.goods.id,goodsqty:this.buyqty}).then(function(res){
var rs=res.body;
alert(rs.msg);
self.cartCount=rs.data.cartCount;
},function(){
alert("加入购物车失败");
});
}
}
});
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
</script>
</body>
</html>
cartlist.html(购物车)


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>购物车详情 -梦在旅途的电商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
.toolbar{margin:10px 5px;}
.carttable{width:100%;margin:0px;padding:5px;border:1px solid gray;}
.carttable tr >*{border-bottom:1px solid gray;padding:5px;text-align: center;}
.buybtn{background-color:green;border:none;width:280px;padding:20px;color:white;font-size:20pt;}
#copy{margin-top:20px;text-align: center;}
</style>
</head>
<body>
<div id="app">
<div class="toolbar">
<button @click="deleteItem()" :disabled="carts.length==0">移出购物车</button> |
<button @click="clearCart()" :disabled="carts.length==0">清空购物车</button>
</div>
<div>
<table class="carttable">
<tr>
<th>选择</th>
<th>商品ID</th>
<th>商品名称</th>
<th>预购买数量</th>
<th>价格</th>
<th>添加时间</th>
</tr>
<tr v-for="c in carts">
<td><input type="checkbox" class="chkitem" @click="checkitem(c,$event.target)" :checked="chkedItemIds.indexOf(c.id)>-1"></td>
<td>{{c.goodsId}}</td>
<td>{{c.inGoods.title}}</td>
<td>{{c.qty}}</td>
<td>¥{{(c.inGoods.price * c.qty).toFixed(2)}}</td>
<td>{{c.addedTime}}</td>
</tr>
<tr v-if="carts.length==0" style="text-align: center;">
<td colspan="6">空空如也,赶紧选购商品吧!~</td>
</tr>
</table>
</div>
<p style="text-align: center;">
<button class="buybtn" @click="createOrder()" :disabled="carts.length==0">立即下单</button>
</p>
</div>
<p id="copy">
Copyright ©2019
www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
</p>
<script type="text/javascript">
var vm=new Vue({
el:"#app",
data:{
carts:[],
chkedItemIds:[]
},
created:function(){
var self = this;
this.$http.get(''/api/cartlist'').then(function(res){
self.carts=res.body;
//alert(JSON.stringify(self.carts));
},function(){
alert("获取购物车信息失败!");
});
},
methods:{
checkitem:function(cart,chk){
//alert(chk.checked);
if(chk.checked){
this.chkedItemIds.push(cart.id);
}else{
this.chkedItemIds.remove(cart.id);
}
},
deleteItem:function(){
var self = this;
//alert(JSON.stringify(self.chkedItemIds));
this.$http.post(''/api/deletecartitems-many'',self.chkedItemIds).then(function(res){
self.carts= self.carts.filter(function(e){ return self.chkedItemIds.indexOf(e.id)<=-1;});
alert(res.body.msg);
},function(){
alert("删除失败!");
});
},
clearCart:function(){
var self = this;
this.$http.post(''/api/deletecartitems-all'').then(function(res){
self.carts=[];
alert(res.body.msg);
},function(){
alert("删除失败!");
});
},
createOrder:function(){
var self = this;
this.$http.post(''/api/createorder'').then(function(res){
alert(res.body.msg);
if(res.body.code==0){//如查下单成功,则清空购物车
self.carts=[];
}
},function(){
alert("下单失败!");
});
}
}
});
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
</script>
</body>
</html>
orderlist.html(订单中心)


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>订单详情 -梦在旅途的电商小店</title>
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
table{border:solid 1px blue;border-collapse: collapse;width:100%;margin:10px 1px;}
table tr >*{border:solid 1px blue,padding:5px;border:dotted 1px gray;}
.cfmbar{text-align: center;}
.cfmbar button{border:none;background-color:blue;color:#ffffff;padding:10px 50px;}
#copy{margin-top:20px;text-align: center;}
</style>
</head>
<body>
<div id="app">
<div>
<h2>订单列表:</h2>
<table>
<tr>
<th>订单号</th>
<th>商品数量</th>
<th>订单价格</th>
<th>完成否(收货确认)</th>
<th>创建时间</th>
<th>查看订单详情</th>
</tr>
<tr v-for="o in shoppingOrders">
<td>{{o.id}}</td>
<td>{{o.totalqty}}</td>
<td>{{o.totalprice.toFixed(2)}}</td>
<td>{{o.iscompleted?"已收货":"待收货"}}</td>
<td>{{o.createtime}}</td>
<td><button @click="showOrderDetail(o)">查看</button></td>
</tr>
<tr v-if="shoppingOrders.length==0" style="text-align: center;">
<td colspan="6">没有任何订单信息!</td>
</tr>
</table>
</div>
<div v-if="viewOrder!=null">
<h3>订单号【{{viewOrder.id}}】详情:</h3>
<table>
<tr>
<th>商品ID</th>
<th>商品名称</th>
<th>购买数量</th>
<th>费用</th>
</tr>
<tr v-for="od in viewOrderDetails.details">
<td>{{od.goodsid}}</td>
<td>{{goodsName(od)}}</td>
<td>{{od.qty}}</td>
<td>¥{{od.totalprice.toFixed(2)}}</td>
</tr>
</table>
<p class="cfmbar" v-if="!viewOrder.iscompleted">
<button @click="confirmOrderCompleted(viewOrder)" >确认完成(已收货)</button>
</p>
</div>
</div>
<p id="copy">
Copyright ©2019
www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
</p>
<script type="text/javascript">
var vm=new Vue({
el:"#app",
data:{
shoppingOrders:[],
viewOrder:null,
viewOrderDetails:null
},
created:function(){
var self = this;
this.$http.get(''/api/orders'').then(function(res){
self.shoppingOrders=res.body;
//alert(JSON.stringify(self.shoppingOrders));
},function(){
alert("获取orders失败!");
});
},
computed:{
goodsName(){//利用JS闭包实现传参
return function(od){
var goods= this.viewOrderDetails.goodss.filter(function(g){return g.id==od.goodsid })[0];
//alert(od.goodsid);
return goods.title;
}
}
},
methods:{
showOrderDetail:function(o){
var self = this;
this.$http.post(''/api/orderdetail'',{orderId:o.id}).then(function(res){
if(res.body.code==0){
self.viewOrderDetails=res.body.data;
//alert(JSON.stringify(self.viewOrderDetails));
}else{
alert(res.body.msg);
self.viewOrderDetails=null;
o=null;
}
self.viewOrder=o;
},function(){
alert("获取orderdetail失败!");
});
},
confirmOrderCompleted:function(o){
var self = this;
this.$http.post(''/api/confirmOrderCompleted'',{orderId:o.id}).then(function(res){
alert(res.body.msg);
if(res.body.code==0){
self.viewOrder.iscompleted=true;
}
}),function(){
alert("确认订单完成失败!");
};
}
}
});
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
</script>
</body>
</html>
admin.html(管理后台,由于 DEMO,故只实现商品的增、删功能,其余管理功能未实现,仅作演示)


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>管理后台 -梦在旅途的电商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
table{border:solid 1px blue;border-collapse: collapse;width:100%;margin:10px 1px;}
table tr >*{border:solid 1px blue,padding:5px;border:dotted 1px gray;}
.gpic{width:100px;height:100px;text-align:center;vertical-align:middle;}
</style>
</head>
<body>
<div id="app">
<fieldset>
<legend>管理商品:</legend>
<table>
<colgroup>
<col style="width:auto">
<col style="width:auto">
<col style="width:100px">
<col style="width:300px">
<col style="width:auto">
<col style="width:auto">
<col style="width:auto">
<col style="width:auto">
<col style="width:auto">
</colgroup>
<tr>
<th>商品ID</th>
<th>商品名称</th>
<th>商品图片</th>
<th>商品介绍</th>
<th>单价</th>
<th>类别ID</th>
<th>最后编辑者</th>
<th>最后编辑时间</th>
<th>操作</th>
</tr>
<tr style="background-color:orange;">
<td>{{editgoods.id}}</td>
<td><input type="text" v-model="editgoods.title"></td>
<td><img v-bind:src="editgoods.picture" class="gpic">
<input class="upload" type="file" id="gpicfile" @change="selectimg($event.target)" accept="image/png,image/gif,image/jpeg"></td>
<td><textarea v-model="editgoods.introduction"></textarea></td>
<td><input type="text" v-model="editgoods.price"></td>
<td>
<select v-model="editgoods.categoryId">
<option v-for="c in categorys" v-bind:value="c.id">{{c.categoryName}}</option>
</select>
</td>
<td>{{editgoods.lastEditBy}}</td>
<td>{{editgoods.lastEditTime}}</td>
<td><button @click="savegoods(editgoods)">保存</button></td>
</tr>
<tr v-for="g in goodss">
<td>{{g.id}}</td>
<td>{{g.title}}</td>
<td><img v-bind:src="g.picture" class="gpic"></td>
<td>{{g.introduction}}</td>
<td>{{g.price}}</td>
<td>{{g.categoryId}}</td>
<td>{{g.lastEditBy}}</td>
<td>{{g.lastEditTime}}</td>
<td><button @click="editgoods(g)" disabled="disabled">修改</button> | <!-- UI暂不实现修改,禁用 -->
<button @click="delgoods(g)">删除</button></td>
</tr>
</table>
</fieldset>
</div>
<script type="text/javascript">
var vm=new Vue({
el:"#app",
data:{
categorys:[],
goodss:[],
editgoods:{
id:null,
title:null,
picture:null,
price:0.00,
introduction:null,
categoryId:1,
lastEditBy:"zuowenjun",
lastEditTime:null
}
},
created:function(){
this.$http.get(''/api/categorys'').then(function(res){
this.categorys=res.body;
},function(){
alert("获取categorys失败!");
});
this.getGoodsListByPage(100,1);//DEMO,只加载第1页
},
methods:{
getGoodsListByPage:function(ps,pno){
var self = this;
//按分页检索商品列表
this.$http.get(''/api/goods'' +''?pagesize=''+ps +''&page='' + pno).then(function(res){
self.goodss=res.body;
//alert(JSON.stringify(self.goods));
},function(){
alert("获取goods失败!");
});
},
selectimg:function(el){
let gpic=el.files[0];
let type=gpic.type;//文件的类型,判断是否是图片
let size=gpic.size;//文件的大小,判断图片的大小
if(''image/gif, image/jpeg, image/png, image/jpg''.indexOf(type) == -1){
alert(''请选择我们支持的图片格式!'');
return false;
}
if(size>3145728){
alert(''请选择3M以内的图片!'');
return false;
}
var uri ='''';
this.editgoods.picture=URL.createObjectURL(gpic);
},
savegoods:function(g){
var fileDom=document.getElementById("gpicfile");
let formData = new FormData();
formData.append(''id'', this.editgoods.id);
formData.append(''title'', this.editgoods.title);
formData.append(''picture'', fileDom.files[0]);
formData.append(''price'', this.editgoods.price);
formData.append(''introduction'', this.editgoods.introduction);
formData.append(''categoryId'', this.editgoods.categoryId);
let config = {
headers: {
''Content-Type'': ''multipart/form-data''
}
}
this.$http.post(''/api/savegoods'', formData, config).then(function (res) {
alert(res.body.msg);
if(res.body.code==0){
this.goodss.unshift(res.body.data);//插入到数组最新面
this.editgoods={//重新初始化,以便实现清空所有编辑框
id:null,
title:null,
picture:null,
price:0.00,
introduction:null,
categoryId:1,
lastEditBy:"zuowenjun",
lastEditTime:null
};
}
});
},
delgoods:function(g){
this.$http.get(''/api/delgoods/'' + g.id).then(function(res){
alert(res.body.msg);
if(res.body.code==0){
this.goodss.remove(g);
}
},function(){
alert("删除goods失败!");
});
}
}
});
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
</script>
</body>
</html>
前端交互所需要 API(因为是 DEMO,故所有的 API ACTION 方法都在 Apicontroller 中),代码如下:


package cn.zuowenjun.boot.controller;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.service.*;
/*
* ALL REST API
*/
@RestController
@RequestMapping("/api")
public class ApiController {
@Autowired
private GoodsService goodsService;
@Autowired
private ShoppingOrderService shoppingOrderService;
@Autowired
private ShopUserService shopUserService;
private String getCurrentShopper() {
String shopper = shopUserService.getCurrentLoginUser();
return shopper;
}
@PostMapping(value="/login",produces = "application/json;charset=utf-8")
public ApiResultMsg login(@RequestBody Map<String,String> requestMap) {
String userid=requestMap.get("userid");
String upwd=requestMap.get("upwd");
String loginResult= shopUserService.login(userid, upwd);
if(loginResult==null) {
return new ApiResultMsg(0,"OK",null);
}else {
return new ApiResultMsg(-1,"登录失败:" + loginResult,null);
}
}
@GetMapping(value = "/categorys", produces = "application/json;charset=utf-8")
public List<GoodsCategory> getAllGoodsCategorys() {
return goodsService.getAllGoodsCategoryList();
}
@GetMapping(value = "/goods/{cateid}", produces = "application/json;charset=utf-8")
public List<Goods> getGoodsList(@PathVariable(name = "cateid") int categoryid) {
return goodsService.getGoodsListByCategory(categoryid);
}
@GetMapping(value = "/goods", produces = "application/json;charset=utf-8")
public List<Goods> getGoodsList(@RequestParam(name = "pagesize", required = false) String spageSize,
@RequestParam(name = "page", required = false) String spageNo) {
int pageSize = tryparseToInt(spageSize);
int pageNo = tryparseToInt(spageNo);
pageSize = pageSize <= 0 ? 10 : pageSize;
pageNo = pageNo <= 1 ? 1 : pageNo;
return goodsService.getGoodsListByPage(pageSize, pageNo);
}
@GetMapping(value = "/goodsmany", produces = "application/json;charset=utf-8")
public List<Goods> getGoodsListByMultIds(@RequestBody int[] ids) {
return goodsService.getGoodsListByMultIds(ids);
}
@PostMapping(value = "/addToShoppingCart", produces = "application/json;charset=utf-8")
public ApiResultMsg addToShoppingCart(@RequestBody Map<String, Integer> json) {
int goodsId = json.get("goodsid");
int qty = json.get("goodsqty");
ApiResultMsg msg = new ApiResultMsg();
if (goodsId <= 0) {
msg.setCode(101);
msg.setMsg("该商品ID无效");
return msg;
}
String shopper = getCurrentShopper();
ShoppingCart shoppingCart = new ShoppingCart(0, shopper, goodsId, qty, new Date());
shoppingOrderService.insertShoppingCart(shoppingCart);
msg.setCode(0);
msg.setMsg("添加购物车成功!");
int cartCount = shoppingOrderService.getShoppingCartBuyCount(shopper);
HashMap<String, Object> data = new HashMap<>();
data.put("cartCount", cartCount);
msg.setData(data);
return msg;
}
@GetMapping(value = "/goods-{gid}", produces = "application/json;charset=utf-8")
public Goods getGoods(@PathVariable("gid") int goodsId) {
return goodsService.getGoods(goodsId);
}
@GetMapping(value = "/cartlist", produces = "application/json;charset=utf-8")
public List<ShoppingCart> getShoppingCartList() {
String shopper = getCurrentShopper();
return shoppingOrderService.getShoppingCartList(shopper);
}
@PostMapping(value = "/deletecartitems-{mode}", produces = "application/json;charset=utf-8")
public ApiResultMsg deleteShoppingCartItems(@PathVariable("mode") String mode,
@RequestBody(required = false) int[] cartIds) {
if (mode.equalsIgnoreCase("all")) {
String shopper = getCurrentShopper();
shoppingOrderService.clearShoppingCart(shopper);
} else {
for (int id : cartIds) {
shoppingOrderService.deleteShoppingCart(id);
}
}
return new ApiResultMsg(0, "删除成功!", null);
}
@PostMapping(value = "/createorder", produces = "application/json;charset=utf-8")
public ApiResultMsg createShoppingOrder() {
String shopper = getCurrentShopper();
ApiResultMsg msg = new ApiResultMsg();
if (shoppingOrderService.createShoppingOrderByShopper(shopper)) {
msg.setCode(0);
msg.setMsg("恭喜你,下单成功!");
} else {
msg.setCode(101);
msg.setMsg("对不起,下单失败,请重试!");
}
return msg;
}
@RequestMapping(path = "/orders", produces = "application/json;charset=utf-8", method = RequestMethod.GET) // 等同于@GetMapping
public List<ShoppingOrder> getShoppingOrderList() {
String shopper = getCurrentShopper();
return shoppingOrderService.getShoppingOrderList(shopper);
}
@RequestMapping(path = "/orderdetail", produces = "application/json;charset=utf-8", method = RequestMethod.POST) // 等同于@PostMapping
public ApiResultMsg getShoppingOrderDetail(@RequestBody Map<String, String> requestJosn) {
String orderId = requestJosn.get("orderId");
List<ShoppingOrderDetail> orderDetails = shoppingOrderService.getShoppingOrderDetail(tryparseToInt(orderId));
ApiResultMsg msg = new ApiResultMsg();
if (orderDetails.size() > 0) {
int[] goodsIds = new int[orderDetails.size()];
for (int i = 0; i < orderDetails.size(); i++) {
goodsIds[i] = orderDetails.get(i).getGoodsid();
}
List<Goods> goodsList = goodsService.getGoodsListByMultIds(goodsIds);
HashMap<String, Object> data = new HashMap<>();
data.put("details", orderDetails);
data.put("goodss", goodsList);
msg.setCode(0);
msg.setData(data);
} else {
msg.setCode(101);
msg.setMsg("获取订单详情信息失败!");
}
return msg;
}
//这里示例配置多个URL请求路径
@PostMapping(path= {"/confirmOrderCompleted","/cfmordercompl"},produces="application/json;charset=utf-8")
public ApiResultMsg confirmOrderCompleted(@RequestBody Map<String, String> requestJosn) {
String reqOrderId = requestJosn.get("orderId");
ApiResultMsg msg=new ApiResultMsg();
try {
int orderId=tryparseToInt(reqOrderId);
ShoppingOrder order= shoppingOrderService.getShoppingOrder(orderId);
order.setIscompleted(true);
shoppingOrderService.updateShoppingOrder(order);
msg.setCode(0);
msg.setMsg("确认订单完成成功(已收货)");
}catch (Exception e) {
msg.setCode(101);
msg.setMsg("确认订单完成失败:" + e.getMessage());
}
return msg;
}
@PostMapping(path="/savegoods",produces="application/json;charset=utf-8",consumes="multipart/form-data")
public ApiResultMsg saveGoods(@RequestParam("picture") MultipartFile gpic,HttpServletRequest request) {
ApiResultMsg msg=new ApiResultMsg();
try
{
Goods goods=new Goods();
goods.setId(tryparseToInt(request.getParameter("id")));
goods.setTitle(request.getParameter("title"));
goods.setPrice(new BigDecimal(request.getParameter("price")));
goods.setIntroduction(request.getParameter("introduction"));
goods.setCategoryId(tryparseToInt(request.getParameter("categoryId")));
goods.setLastEditBy(getCurrentShopper());
goods.setLastEditTime(new Date());
if(goods.getId()<=0) {
goodsService.insertGoods(goods, gpic);
} else {
goodsService.updateGoods(goods, gpic);
}
msg.setCode(0);
msg.setMsg("保存成功!");
msg.setData(goods);
}catch (Exception e) {
msg.setCode(101);
msg.setMsg("保存失败:" + e.getMessage());
}
return msg;
}
@GetMapping(path="/delgoods/{gid}",produces="application/json;charset=utf-8")
public ApiResultMsg deleteGoods(@PathVariable("gid") int goodsId) {
goodsService.deleteGoods(goodsId);
ApiResultMsg msg=new ApiResultMsg();
msg.setCode(0);
msg.setMsg("删除商品成功!");
return msg;
}
private int tryparseToInt(String str) {
try {
return Integer.parseInt(str);
} catch (Exception e) {
return -1;
}
}
}
REST API controller 与普通的 MVC controller 用法上基本相同,只是 REST API ACTION 返回的是数据内容本身 (@RestController 或 @Controller+@ResponseBody),而 MVC ACTION 一般返回 view
4.4 添加身份认证拦截器、日志记录等
因为演示的是电商购物场景,既有下单又有后台管理,故这里我增加了登录视图及登录拦截器,以完成对部份页面及 API 的权限控制,实现代码如下:
4.4.1. 设计 login.html(登录)


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录入口 -梦在旅途的电商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
</head>
<body>
<div id="app">
<form method="post" @submit.prevent="loginsubmit">
<p>用户ID:</p>
<p><input type="text" v-model="uid"></p>
<p>密码:</p>
<p><input type="password" v-model="pwd"></p>
<p>
<button type="submit">登录</button>
</p>
</form>
</div>
<script type="text/javascript">
var vm=new Vue({
el:"#app",
data:{
uid:null,
pwd:null
},
methods:{
loginsubmit:function(){
this.$http.post(''/api/login'',{userid:this.uid,upwd:this.pwd}).then(function(res){
var rs=res.body;
if(rs.code==0){
window.location.href="index.html";
}else{
alert(rs.msg);
}
},function(){
alert("登录请求失败!");
});
}
}
});
</script>
</body>
</html>
4.4.2. 自定义实现 HandlerInterceptor 的登录验证拦截器:LoginInterceptor,代码如下:(注意我是将该拦截器放在根包中 cn.zuowenjun.boot)


package cn.zuowenjun.boot;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.zuowenjun.boot.domain.ApiResultMsg;
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = request.getSession();
if (session.getAttribute("loginUser") == null) {// 未登录则转到登录页面
boolean isAjaxRequest = false;
boolean isAcceptJSON = false;
if (request.getHeader("x-requested-with") != null
&& request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) {
isAjaxRequest = true;
}
if (request.getHeader("Accept") != null && request.getHeader("Accept").contains("application/json")) {
isAcceptJSON = true;
}
if(isAjaxRequest || isAcceptJSON) {
//使用jackson序列化JSON
ApiResultMsg msg=new ApiResultMsg(-1,"未登录,禁止访问",null);
ObjectMapper mapper = new ObjectMapper();
String msgJson= mapper.writeValueAsString(msg);
responseJson(response,msgJson);
}else {
response.sendRedirect("/login.html");
}
return false;
}
return true;
}
private void responseJson(HttpServletResponse response, String json) throws Exception {
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("applcation/json; charset=utf-8");
try {
writer = response.getWriter();
writer.print(json);
} catch (IOException e) {
logger.error("response error", e);
} finally {
if (writer != null)
writer.close();
}
}
}
代码比较简单,主要是判断 session 中是否有记录登录的用户名,如果没有则表示未登录,然后根据是 AJAX 请求或需要返回 JSON 的情况则返回 JSON,否则直接跳转到 login.html 页面。
4.4.3. 自定义实现 WebMvcConfigurer 的配置类:SpringbootdemoAppConfigurer,重写 addInterceptors 方法,在该方法中把 LoginInterceptor 实例加入到拦截器管道中,以便交由 spring MVC 进行管理,代码如下:(同样放在根包中)


package cn.zuowenjun.boot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class SpringbootdemoAppConfigurer implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);
registration.addPathPatterns("/**");
registration.excludePathPatterns("/*.html","/uploadimgs/*","/error","/api/login","/api/categorys","/api/goods*","/api/goods/*",
"/hello/*","/test/*");
}
}
注意我这里是拦截所有路径,然后使用 excludePathPatterns 来排除不需要拦截的路径,如果需要拦截的路径比较少,可以直接指定拦截的具体路径,这样就不用排除了。
4.4.4. 另外补充一个功能点说明:一般一个应用程度都会有日志记录,这里也不能少,spring boot 中默认实现了:slf4j+logback(slf4j 是一个日志门面接口,logback 是 slf4j 接口的实现,这样搭配比较好,可以随时更换日志实现框架),先在 application.properties 配置日志的基本参数,如下所示:(详细集成配置,可参见:https://www.jianshu.com/p/88b03b03570c)
#logging.config=xxxx #可以指定单独的日志配置XML文件,进行更丰富的设置,这里未采用
logging.level.root=info
logging.level.cn.zuowenjun.boot.mapper=debug
logging.file=springbootdemo.log
配置好后,然后在相应的类中直接使用即可,用法如下:(具体可见上面的 GoodsServiceImpl 代码)
private static Logger logger=LoggerFactory.getLogger(GoodsServiceImpl.class);//通过日志工厂获得一个日志记录实例
logger.info("日志信息");//有多个日志级别可用,但注意需要配置中的root级别相对应,比如目前是配置了info,如果使用debug,可能就不会输出日志到文件
4.5 效果展示:(全部采用 HTML+AJAX 静态交互)
通过以上的后端 API 代码 + 基于 VUE 的前端 HTML,就完成了一个简单的电商物购 DEMO,效果截图如下:
主页:(加入购物车后,右上角的” 购物车 (已加入商品数量:0)“ 数字变自动同步更新)
商品详情:
购物车:
订单管理:
后台管理(商品管理):
添加商品后,自动加入到列表第一行,如果删除则移除删除的商品所在行
本文示例项目源码,请参见 GIT:https://github.com/zuowj/springbootdemo
Mybatis 插件之 Mybatis-Plus(SpringBoot)
这边只在 SpringBoot 下进行简单查询的测试,接下来会博客会介绍增删改的操作。
数据库表结构如下:
开始测试:
1、新建工程(trymp_springboot)并把项目结构建立好
2、导入 pom.xml 的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<!--测试的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!--日志依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<!--Druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
</dependencies>
3、日志文件(log4j.properties)配置如下:
log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.Al.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
4、SpringBoot 核心配置文件(application.yml),如下:
server:
port: 8080
spring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///chw?CharacterEncoding=utf8
username: root
password: root
5、Mapper 接口内容如下:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jieku.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}
6、实体类内容如下:
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@TableName("tb_user")
public class User {
@TableId(type = IdType.AUTO) // 需要指定,否则无法新增后拿到回调的id,以及进行删除等操作
private Integer uid;
private String uname;
private Integer age;
public User(String uname, Integer age) {
this.uname = uname;
this.age = age;
}
}
7、启动类内容如下:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.jieku.mapper") // 设置mapper接口的扫描路径
public class MPApplication {
public static void main(String[] args) {
SpringApplication.run(MPApplication.class);
}
}
8、测试类内容如下:
import com.jieku.mapper.UserMapper;
import com.jieku.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest // SpringBoot的测试
public class TestMPSpringboot {
@Autowired
private UserMapper userMapper;
@Test
public void test01(){
// 1.进行查询
List<User> users = userMapper.selectList(null);
// 2.遍历打印输出
for (User user : users) {
System.out.println(user);
}
}
}
9、运行结果如下:
至此,简单查询测试结束!
关于Debug - MyBatis, SpringBoot对应关系和mybatis+springboot的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于idea springboot2 集成Mybatis使用mybatis-generator-plugin生成代码、idea+springboot+Mybatis搭建web项目、JAVA WEB 快速入门之从编写一个基于 SpringBoot+Mybatis 快速创建的 REST API 项目了解 SpringBoot、SpringMVC REST API、Mybatis 等相关知识、Mybatis 插件之 Mybatis-Plus(SpringBoot)等相关内容,可以在本站寻找。
本文标签: