GVKun编程网logo

SpringBoot+Mybatis+MySQL

4

对于SpringBoot+Mybatis+MySQL感兴趣的读者,本文将会是一篇不错的选择,并为您提供关于javaday55【Mybatis连接池与事务深入、Mybatis的动态SQL语句、Mybat

对于SpringBoot+Mybatis+MySQL感兴趣的读者,本文将会是一篇不错的选择,并为您提供关于java day55【 Mybatis 连接池与事务深入 、 Mybatis 的动态 SQL 语句、 Mybatis 多表查询之一对多 、 Mybatis 多表查询之多对多】、java day56【 Mybatis 延迟加载策略 、 Mybatis 缓存、Mybatis 注解开发 】、JAVA WEB 快速入门之从编写一个基于 SpringBoot+Mybatis 快速创建的 REST API 项目了解 SpringBoot、SpringMVC REST API、Mybatis 等相关知识、Mabatis三剑客分别是:mybatis-generator、mybatis-plugin、mybatis-pagehelper的有用信息。

本文目录一览:

SpringBoot+Mybatis+MySQL

SpringBoot+Mybatis+MySQL

在学习SpringBoot时想连接数据库做一些操作,从网上找了一些博客,总感觉很多解释的不清楚。自己写一篇总结,主要是本人使用中踩过的坑。

1.准备两张带有级联关系的数据表User表和Department表,先创建Department表,在User表中维护关联关系,并向Department表中添加数据。

CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

INSERT INTO `department` VALUES (1, ''研发部'');
INSERT INTO `department` VALUES (2, ''销售部'');
INSERT INTO `department` VALUES (3, ''测试部'');
INSERT INTO `department` VALUES (4, ''商品部'');
INSERT INTO `department` VALUES (5, ''采购部'');

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `password` varchar(45) NOT NULL,
  `identify_type` tinyint(4) NOT NULL,
  `identify_number` varchar(45) NOT NULL,
  `dept_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_user_dept` (`dept_id`),
  CONSTRAINT `fk_user_dept` FOREIGN KEY (`dept_id`) REFERENCES `department` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

2.添加Maven依赖,只添加这块需要用到的依赖,MySQL驱动,MyBatis,Lombok,以及用于测试的test,其它依赖项没有展示。

<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>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.1</version>
</dependency>

3.创建POJO对象。因添加有lombok,使用@Data注解,可以代替getter/setter方法。生成一个无参构造器和全参构造器。

package com.cn.entities.mysql;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
    private Integer id;
    private String name;
}
package com.cn.entities.mysql;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String password;
    private Integer identifyType;
    private String identifyNumber;
    private Department department;
}

4.在application.properties中配置MySQL和MyBatis。

spring.datasource.url=jdbc:mysql://localhost/test?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.type-aliases-package=com.cn.entities.mysql
mybatis.mapper-locations=classpath:mapper/*.xml
server.port=8081

需要注意的有以下几点

  • MySQL驱动版本不同,driver-class-name使用com.mysql.cj.jdbc.Driver而不是原来的com.mysql.jdbc.Driver。使用原来的也不会报错,但是会输出一个提示。所以我做了修改。
Loading class `com.mysql.jdbc.Driver''. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver''. 
The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
  • 需要添加useSSL=false&serverTimezone=UTC。不使用SSL,并设置时区。若不设置时区,会出现以下错误。
com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value ''�й���׼ʱ��'' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  • MyBatis的type-aliases-package我设置的是POJO取别名。

5.创建Dao,UserMapper.java。

package com.cn.dao.mysql;

import com.cn.entities.mysql.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Mapper
@Component
public interface UserMapper {

    User fetchById(Object id);

    List<User> fetchList(Map<String, Object> query);

    int save(User user);

    int deleteById(Object id);

    int update(User user);

}

6.在resources资源文件目录下创建mapper文件夹并创建UserMapper.xml文件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cn.dao.mysql.UserMapper">

    <!--做返回结果映射,添加了resultMap之后,在select中就可以将resultMap指定为userResultMap,结果会映射为对象-->
    <resultMap id="userResultMap" type="User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="password" column="password"/>
        <result property="identifyType" column="identify_type"/>
        <result property="identifyNumber" column="identify_number"/>
        <!--关联Department表,javaType中指定Department的类路径,property中是对象的属性-->
        <association property="department" javaType="com.cn.entities.mysql.Department">
            <!--此处的id指Department对象中的属性,dept_id是user表中的dept_id,关联字段-->
            <id property="id" column="dept_id"/>
            <result property="name" column="dept_name"/>
        </association>
    </resultMap>

    <sql id="select_statement">
        select
        u.id,
        u.name,
        u.password,
        u.identify_number,
        u.identify_type,
        u.dept_id,
        t.id,
        t.name dept_name
        from user u left join department t on u.dept_id = t.id
        where 1 = 1
    </sql>

    <select id="fetchById" parameterType="java.lang.Object" resultMap="userResultMap">
        <include refid="select_statement"/>
        and t.id = #{id}
    </select>

    <select id="fetchList" parameterType="java.util.HashMap" resultMap="userResultMap">
        <include refid="select_statement"/>
    </select>

    <insert id="save" parameterType="User">
        insert into user(name,password,identify_type,identify_number,dept_id)
        values(#{name},#{password},#{identifyType},#{identifyNumber},#{department.id})
    </insert>

    <update id="update" parameterType="User">
        update user
        <trim prefix="set" suffix="where id = #{id}" suffixOverrides=",">
            <if test="name != null">name=#{name},</if>
            <if test="password != null">password=#{password},</if>
            <if test="identifyType != null">identify_type=#{identifyType},</if>
            <if test="identifyNumber != null">identify_number=#{identifyNumber},</if>
            <if test="department.id != null">dept_id=#{department.id},</if>
        </trim>
    </update>

    <delete id="deleteById" parameterType="java.lang.Object">
        delete from user where id = #{id};
    </delete>

</mapper>

一句话总结,resultMap中property映射的是对象的属性,column映射的是sql语句中的列,若需要做对应,则需指定对应的列别名

6.创建service。IBaseService.java,BaseServiceImpl.java,IUserService.java,UserServiceImpl.java

package com.cn.service;

import java.util.List;
import java.util.Map;

public interface IBaseService<T> {
    public abstract T fetchById(Object id);

    public abstract List<T> fetchList(Map<String, Object> query);

    public abstract boolean save(T t);

    public abstract boolean update(T t);

    public abstract boolean deleteById(Object id);
}
package com.cn.service.impl;

import com.cn.service.IBaseService;

import java.util.List;
import java.util.Map;

public class BaseServiceImpl<T> implements IBaseService<T> {
    @Override
    public T fetchById(Object id) {
        return null;
    }

    @Override
    public boolean save(T t) {
        return false;
    }

    @Override
    public boolean deleteById(Object id) {
        return false;
    }

    @Override
    public List<T> fetchList(Map query) {
        return null;
    }

    @Override
    public boolean update(T t) {
        return false;
    }
}
package com.cn.service;

import com.cn.entities.mysql.User;

public interface IUserService extends IBaseService<User>{

}
package com.cn.service.impl;

import com.cn.dao.mysql.UserMapper;
import com.cn.entities.mysql.User;
import com.cn.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class UserServiceImpl extends BaseServiceImpl<User> implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public User fetchById(Object id) {
        return userMapper.fetchById(id);
    }

    @Override
    public boolean save(User user) {
        try {
            userMapper.save(user);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean deleteById(Object id) {
        try {
            userMapper.deleteById(id);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public List<User> fetchList(Map query) {
        return userMapper.fetchList(query);
    }

    @Override
    public boolean update(User user) {
        try {
            userMapper.update(user);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

7.启用MapperScan扫描dao,com.cn.dao.mysql为dao存放的包路径。

package com.cn;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@MapperScan("com.cn.dao.mysql")
@EnableScheduling
public class TissueDaoApplication {

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

}

8.进行测试

src/test/java/com/cn/service目录下创建TestUserService.java进行测试。

package com.cn.service;

import com.cn.entities.mysql.Department;
import com.cn.entities.mysql.User;
import com.cn.util.Json;
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.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestUserService {
    @Autowired
    private IUserService userService;

    @Test
    public void create(){
        User user = new User();
//      模拟数据库查询部门信息
        Department department = new Department();
        department.setId(4);
        department.setName("商品部");
        user.setDepartment(department);
        user.setName("张三");
        user.setPassword("123456");
        user.setIdentifyType(0);
        user.setIdentifyNumber("420612345678910001");
        userService.save(user);

    }

    @Test
    public void update(){
        User user = userService.fetchById(2);
        System.out.println(Json.toJson(user));
//      模拟数据库查询部门信息
        Department department = new Department();
        department.setId(4);
        department.setName("商品部");
        user.setDepartment(department);
        System.out.println(Json.toJson(user));
        userService.update(user);
    }

    @Test
    public void fetchById(){
        System.out.println(Json.toJson(userService.fetchById(1)));
    }
}

另外需要注意的是,个人建议在查询时最好是将所有的列展示出来,而不要用select *。在级联情况下具有同名字段时,最好另取别名。在查询条件中如果条件是级联表中同名字段,需要指定具体查询哪张表。

 

java day55【 Mybatis 连接池与事务深入 、 Mybatis 的动态 SQL 语句、 Mybatis 多表查询之一对多 、 Mybatis 多表查询之多对多】

java day55【 Mybatis 连接池与事务深入 、 Mybatis 的动态 SQL 语句、 Mybatis 多表查询之一对多 、 Mybatis 多表查询之多对多】

第1章 Mybatis 连接池与事务深入

1.1 Mybatis 的连接池技术

1.1.1 Mybatis 连接池的分类

1.1.2 Mybatis 中数据源的配置

1.1.3 Mybatis 中 DataSource 的存取

1.1.4 Mybatis 中连接的获取过程分析

 

1.2 Mybatis 的事务控制

1.2.1 JDBC 中事务的回顾

 

1.2.2 Mybatis 中事务提交方式

1.2.3 Mybatis 自动提交事务的设置

第2章 Mybatis 的动态 SQL 语句

2.1 动态 SQL 之标签 

2.1.1 持久层 Dao 接口

2.1.2 持久层 Dao 映射配置

2.1.3 测试

2.2 动态 SQL 之标签 

2.2.1 持久层 Dao 映射配置

2.3 动态标签之标签 

2.3.1 需求

2.3.1.1 在 QueryVo 中加入一个 List 集合用于封装参数

2.3.2 持久层 Dao 接口

2.3.3 持久层 Dao 映射配置

2.3.3.1 编写测试方法

 2.4 Mybatis 中简化编写的 SQL 片段

2.4.1 定义代码片段

2.4.2 引用代码片段

第3章 Mybatis 多表查询之一对多

3.1 一对一查询(多对一)

3.1.1 方式一

3.1.1.1 定义账户信息的实体类

3.1.1.2 编写 Sql 语句

3.1.1.3 定义 AccountUser 类

3.1.1.4 定义账户的持久层 Dao 接口

3.1.1.5 定义 AccountDao.xml 文件中的查询配置信息

3.1.1.6 创建 AccountTest 测试类

3.1.1.7 小结:

3.1.2 方式二

3.1.2.1 修改 Account 类

在 Account 类中加入 User 类的对象作为 Account 类的一个属性。

3.1.2.2 修改 AccountDao 接口中的方法

3.1.2.3 重新定义 AccountDao.xml 文件

3.1.2.4 在 AccountTest 类中加入测试方法

3.2 一对多查询

3.2.1 编写 SQL 语句

3.2.2 User 类加入 List

3.2.3 用户持久层 Dao 接口中加入查询方法

3.2.4 用户持久层 Dao 映射文件配置

3.2.5 测试方法

第4章 Mybatis 多表查询之多对多

4.1 实现 Role 到 User 多对多

4.1.1 用户与角色的关系模型

4.1.2 业务要求及实现 SQL

4.1.3 编写角色实体类

4.1.4 编写 Role 持久层接口

4.1.5 编写映射文件

4.1.6 编写测试类

4.2 实现 User 到 Role 的多对多

4.2.1 User 到 Role 的多对多

4.2.2 作业:实现 User 到 Role 的一对多查询

 

java day56【 Mybatis 延迟加载策略 、 Mybatis 缓存、Mybatis 注解开发 】

java day56【 Mybatis 延迟加载策略 、 Mybatis 缓存、Mybatis 注解开发 】

第 1 章 Mybatis 延迟加载策略

1.1 何为延迟加载?

1.2 实现需求

1.3 使用 assocation 实现延迟加载

1.3.1 账户的持久层 DAO 接口

1.3.2 账户的持久层映射文件

1.3.3 用户的持久层接口和映射文件

1.3.4 开启 Mybatis 的延迟加载策略

1.3.5 编写测试只查账户信息不查用户信息。

1.4 使用 Collection 实现延迟加载

1.4.1 在 User 实体类中加入 List 属性

1.4.2 编写用户和账户持久层接口的方法

1.4.3 编写用户持久层映射配置

1.4.4 编写账户持久层映射配置

1.4.5 测试只加载用户信息

第 2 章 Mybatis 缓存

2.1 Mybatis 一级缓存

2.1.1 证明一级缓存的存在

2.1.1.1 编写用户持久层 Dao 接口

2.1.1.2 编写用户持久层映射文件

2.1.1.3 编写测试方法

2.1.2 一级缓存的分析

2.1.3 测试一级缓存的清空

 2.2 Mybatis 二级缓存

2.2.1 二级缓存结构图

 2.2.2 二级缓存的开启与关闭

2.2.2.1 第一步:在 SqlMapConfig.xml 文件开启二级缓存

2.2.2.2 第二步:配置相关的 Mapper 映射文件

2.2.2.3 第三步:配置 statement 上面的 useCache 属性

2.2.3 二级缓存测试

2.2.4 二级缓存注意事项

第 3 章 Mybatis 注解开发

3.1 mybatis 的常用注解说明

3.2 使用 Mybatis 注解实现基本 CRUD

3.2.1 编写实体类

3.2.2 使用注解方式开发持久层接口

3.2.3 编写 SqlMapConfig 配置文件

3.2.4 编写测试方法

3.3 使用注解实现复杂关系映射开发

3.3.1 复杂关系映射的注解说明

3.3.2 使用注解实现一对一复杂关系映射及延迟加载

3.3.2.1 添加 User 实体类及 Account 实体类

3.3.2.2 添加账户的持久层接口并使用注解配置

3.3.2.3 添加用户的持久层接口并使用注解配置

3.3.2.4 测试一对一关联及延迟加载

3.3.3 使用注解实现一对多复杂关系映射

3.3.3.2 编写用户的持久层接口并使用注解配置

3.3.3.3 编写账户的持久层接口并使用注解配置

 3.3.3.4 添加测试方法

3.4 mybatis 基于注解的二级缓存

3.4.1 在 SqlMapConfig 中开启二级缓存支持

3.4.2 在持久层接口中使用注解配置二级缓存

 

JAVA WEB 快速入门之从编写一个基于 SpringBoot+Mybatis 快速创建的 REST API 项目了解 SpringBoot、SpringMVC REST API、Mybatis 等相关知识

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

}
View Code

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

    

}
View Code

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

  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>
View Code

由于涉及的知识点比较多,在此就不作介绍,请参见我给出的链接加以了解。

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

}
View Code

  代码比较简单,主要是重写了 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;
    }
}
View Code

另外顺便解决一个踩坑点:上面提到了,我们在 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); 
    }
}
View Code

  与 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 &copy;<span th:text="${#dates.format(#dates.createToday(),''yyyy'')}"></span>
    www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
    </p>
</body>
</html>
View Code

最后浏览: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);
    
    
}
View Code

  如上代码示,我仅定义了三个 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);
    }





}
View Code

  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>&nbsp;|&nbsp;
        <a href="/orderlist.html" target="_blank">订单中心</a>&nbsp;|&nbsp;
        <a href="/admin.html" target="_blank">管理后台</a>&nbsp;&nbsp;&nbsp;&nbsp;
        </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 &copy;2019 &nbsp;
    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>
View Code

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>&nbsp;|&nbsp;
        <a href="/admin.html" target="_blank">管理后台</a>&nbsp;&nbsp;&nbsp;&nbsp;
        </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>
View Code

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>&nbsp;|&nbsp;
            <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 &copy;2019 &nbsp;
    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>
View Code

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 &copy;2019 &nbsp;
    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>
View Code

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>&nbsp;|&nbsp;<!-- 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>
View Code

前端交互所需要 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;
        }
    }

}
View Code

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>
View Code

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

  代码比较简单,主要是判断 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/*");
    }

}
View Code

注意我这里是拦截所有路径,然后使用 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

 

Mabatis三剑客分别是:mybatis-generator、mybatis-plugin、mybatis-pagehelper

Mabatis三剑客分别是:mybatis-generator、mybatis-plugin、mybatis-pagehelper

<divid="content_views">

<h3 id="本文用的是mavenidea15"><a name="t0"></a> 本文用的是maven,idea15</h3> <p> Mabatis三剑客分别是:mybatis-generator、mybatis-<a href="https://www.baidu.com/s?wd=plugin&amp;tn=24004469_oem_dg&amp;rsv_dl=gh_pl_sl_csd" target="_blank">plugin</a>、mybatis-pagehelper</p> <h2 id="一mybatis-generator"><a name="t1"></a> <a name="t1"target="_blank"></a>一、mybatis-generator</h2> <p> 根据我们的数据库自动生成pojo、dao和xml文件&nbsp;<br> pojo里面放的是跟数据库字段一一对应的对象、dao层是接口,供service使用,xml是这个dao层接口的实现,sql语句都写在xml里</p> <h4 id="1引入mabatis-generator"> <a name="t2"target="_blank"></a>1.引入mabatis-generator</h4> <p> pom.xml里引入配置</p> <prename="code" onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div><span><span>&lt;</span><span><span><span>plugin</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="2"></div></div><div><div> <span><span>&lt;</span><span><span><span>groupId</span></span></span><span>&gt;</span></span>org.apache.maven.plugins<span><span>&lt;/</span><span><span><span>groupId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="3"></div></div><div><div> <span><span>&lt;</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span>maven-compiler-plugin<span><span>&lt;/</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="4"></div></div><div><div> <span><span>&lt;</span><span><span><span>configuration</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="5"></div></div><div><div> <span><span>&lt;</span><span><span><span>source</span></span></span><span>&gt;</span></span>1.7<span><span>&lt;/</span><span><span><span>source</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="6"></div></div><div><div> <span><span>&lt;</span><span><span><span>target</span></span></span><span>&gt;</span></span>1.7<span><span>&lt;/</span><span><span><span>target</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="7"></div></div><div><div> <span><span>&lt;</span><span><span><span>encoding</span></span></span><span>&gt;</span></span>UTF-8<span><span>&lt;/</span><span><span><span>encoding</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="8"></div></div><div><div> <span><span>&lt;</span><span><span><span>compilerArguments</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="9"></div></div><div><div> <span><span>&lt;</span><span><span><span>extdirs</span></span></span><span>&gt;</span></span>${project.basedir}/src/main/webapp/WEB-INF/lib/<span><span>&lt;/</span><span><span><span>extdirs</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="10"></div></div><div><div> <span><span>&lt;/</span><span><span><span>compilerArguments</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="11"></div></div><div><div> <span><span>&lt;/</span><span><span><span>configuration</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="12"></div></div><div><div><span><span>&lt;/</span><span><span><span>plugin</span></span></span><span>&gt;</span></span></div></div></li></ol></code><divdata-title="复制"></div></pre><p> 引入generatorConfig.xml&nbsp;<br> generatorConfig.xml</p> <prename="code" onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div><span><span><span>&lt;?</span>xml version=<span>"1.0"</span> encoding=<span>"UTF-8"</span><span>?&gt;</span></span></span></div></div></li><li><div><divdata-line-number="2"></div></div><div><div><span><span><span>&lt;!DOCTYPE generatorConfiguration</span></span></span></div></div></li><li><div><divdata-line-number="3"></div></div><div><div><span> PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"</span></div></div></li><li><div><divdata-line-number="4"></div></div><div><div><span> "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"&gt;</span></div></div></li><li><div><divdata-line-number="5"></div></div><div><div> </div></div></li><li><div><divdata-line-number="6"></div></div><div><div><span><span>&lt;</span><span><span><span>generatorConfiguration</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="7"></div></div><div><div> <span><span>&lt;!--导入属性配置--&gt;</span></span></div></div></li><li><div><divdata-line-number="8"></div></div><div><div> <span><span>&lt;</span><span><span><span>properties</span></span></span><span> </span><span><span><span>resource</span></span></span><span>=</span><span><span><span>"datasource.properties"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>properties</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="9"></div></div><div><div> </div></div></li><li><div><divdata-line-number="10"></div></div><div><div> <span><span>&lt;!--指定特定数据库的jdbc驱动jar包的位置--&gt;</span></span></div></div></li><li><div><divdata-line-number="11"></div></div><div><div> <span><span>&lt;</span><span><span><span>classPathEntry</span></span></span><span> </span><span><span><span>location</span></span></span><span>=</span><span><span><span>"${db.driverLocation}"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="12"></div></div><div><div> </div></div></li><li><div><divdata-line-number="13"></div></div><div><div> <span><span>&lt;</span><span><span><span>context</span></span></span><span> </span><span><span><span>id</span></span></span><span>=</span><span><span><span>"default"</span></span></span><span> </span><span><span><span>targetRuntime</span></span></span><span>=</span><span><span><span>"MyBatis3"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="14"></div></div><div><div> </div></div></li><li><div><divdata-line-number="15"></div></div><div><div> <span><span>&lt;!-- optional,旨在创建class时,对注释进行控制 --&gt;</span></span></div></div></li><li><div><divdata-line-number="16"></div></div><div><div> <span><span>&lt;</span><span><span><span>commentGenerator</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="17"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"suppressDate"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"true"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="18"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"suppressAllComments"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"true"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="19"></div></div><div><div> <span><span>&lt;/</span><span><span><span>commentGenerator</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="20"></div></div><div><div> </div></div></li><li><div><divdata-line-number="21"></div></div><div><div> <span><span>&lt;!--jdbc的数据库连接 --&gt;</span></span></div></div></li><li><div><divdata-line-number="22"></div></div><div><div> <span><span>&lt;</span><span><span><span>jdbcConnection</span></span></span></span></div></div></li><li><div><divdata-line-number="23"></div></div><div><div><span> </span><span><span><span>driverClass</span></span></span><span>=</span><span><span><span>"${db.driverClassName}"</span></span></span><span></span></div></div></li><li><div><divdata-line-number="24"></div></div><div><div> <span><span><span>connectionURL</span></span></span><span>=</span><span><span><span>"${db.url}"</span></span></span><span></span></div></div></li><li><div><divdata-line-number="25"></div></div><div><div> <span><span><span>userId</span></span></span><span>=</span><span><span><span>"${db.username}"</span></span></span><span></span></div></div></li><li><div><divdata-line-number="26"></div></div><div><div> <span><span><span>password</span></span></span><span>=</span><span><span><span>"${db.password}"</span></span></span><span>&gt;</span></div></div></li><li><div><divdata-line-number="27"></div></div><div><div> <span><span>&lt;/</span><span><span><span>jdbcConnection</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="28"></div></div><div><div> </div></div></li><li><div><divdata-line-number="29"></div></div><div><div> </div></div></li><li><div><divdata-line-number="30"></div></div><div><div> <span><span>&lt;!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制--&gt;</span></span></div></div></li><li><div><divdata-line-number="31"></div></div><div><div> <span><span>&lt;</span><span><span><span>javaTypeResolver</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="32"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"forceBigDecimals"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="33"></div></div><div><div> <span><span>&lt;/</span><span><span><span>javaTypeResolver</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="34"></div></div><div><div> </div></div></li><li><div><divdata-line-number="35"></div></div><div><div> </div></div></li><li><div><divdata-line-number="36"></div></div><div><div> <span><span><span>&lt;!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类</span></span></span></div></div></li><li><div><divdata-line-number="37"></div></div><div><div><span> targetPackage 指定生成的model生成所在的包名</span></div></div></li><li><div><divdata-line-number="38"></div></div><div><div><span> targetProject 指定在该项目下所在的路径</span></div></div></li><li><div><divdata-line-number="39"></div></div><div><div><span> --&gt;</span></div></div></li><li><div><divdata-line-number="40"></div></div><div><div> <span><span>&lt;!--&lt;javaModelGenerator targetPackage="com.mmall.pojo" targetProject=".\src\main\java"&gt;--&gt;</span></span></div></div></li><li><div><divdata-line-number="41"></div></div><div><div> <span><span>&lt;</span><span><span><span>javaModelGenerator</span></span></span><span> </span><span><span><span>targetPackage</span></span></span><span>=</span><span><span><span>"com.mmall.pojo"</span></span></span><span> </span><span><span><span>targetProject</span></span></span><span>=</span><span><span><span>"./src/main/java"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="42"></div></div><div><div> <span><span>&lt;!-- 是否允许子包,即targetPackage.schemaName.tableName --&gt;</span></span></div></div></li><li><div><divdata-line-number="43"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"enableSubPackages"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="44"></div></div><div><div> <span><span>&lt;!-- 是否对model添加 构造函数 --&gt;</span></span></div></div></li><li><div><divdata-line-number="45"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"constructorBased"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"true"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="46"></div></div><div><div> <span><span>&lt;!-- 是否对类CHAR类型的列的数据进行trim操作 --&gt;</span></span></div></div></li><li><div><divdata-line-number="47"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"trimStrings"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"true"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="48"></div></div><div><div> <span><span>&lt;!-- 建立的Model对象是否 不可改变 即生成的Model对象不会有 setter方法,只有构造方法 --&gt;</span></span></div></div></li><li><div><divdata-line-number="49"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"immutable"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="50"></div></div><div><div> <span><span>&lt;/</span><span><span><span>javaModelGenerator</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="51"></div></div><div><div> </div></div></li><li><div><divdata-line-number="52"></div></div><div><div> <span><span>&lt;!--mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 --&gt;</span></span></div></div></li><li><div><divdata-line-number="53"></div></div><div><div> <span><span>&lt;!--&lt;sqlMapGenerator targetPackage="mappers" targetProject=".\src\main\resources"&gt;--&gt;</span></span></div></div></li><li><div><divdata-line-number="54"></div></div><div><div> <span><span>&lt;</span><span><span><span>sqlMapGenerator</span></span></span><span> </span><span><span><span>targetPackage</span></span></span><span>=</span><span><span><span>"mappers"</span></span></span><span> </span><span><span><span>targetProject</span></span></span><span>=</span><span><span><span>"./src/main/resources"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="55"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"enableSubPackages"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="56"></div></div><div><div> <span><span>&lt;/</span><span><span><span>sqlMapGenerator</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="57"></div></div><div><div> </div></div></li><li><div><divdata-line-number="58"></div></div><div><div> <span><span><span>&lt;!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码</span></span></span></div></div></li><li><div><divdata-line-number="59"></div></div><div><div><span> type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象</span></div></div></li><li><div><divdata-line-number="60"></div></div><div><div><span> type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象</span></div></div></li><li><div><divdata-line-number="61"></div></div><div><div><span> type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口</span></div></div></li><li><div><divdata-line-number="62"></div></div><div><div><span> --&gt;</span></div></div></li><li><div><divdata-line-number="63"></div></div><div><div> </div></div></li><li><div><divdata-line-number="64"></div></div><div><div> <span><span>&lt;!-- targetPackage:mapper接口dao生成的位置 --&gt;</span></span></div></div></li><li><div><divdata-line-number="65"></div></div><div><div> <span><span>&lt;!--&lt;javaClientGenerator type="XMLMAPPER" targetPackage="com.mmall.dao" targetProject=".\src\main\java"&gt;--&gt;</span></span></div></div></li><li><div><divdata-line-number="66"></div></div><div><div> <span><span>&lt;</span><span><span><span>javaClientGenerator</span></span></span><span> </span><span><span><span>type</span></span></span><span>=</span><span><span><span>"XMLMAPPER"</span></span></span><span> </span><span><span><span>targetPackage</span></span></span><span>=</span><span><span><span>"com.mmall.dao"</span></span></span><span> </span><span><span><span>targetProject</span></span></span><span>=</span><span><span><span>"./src/main/java"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="67"></div></div><div><div> <span><span>&lt;!-- enableSubPackages:是否让schema作为包的后缀 --&gt;</span></span></div></div></li><li><div><divdata-line-number="68"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"enableSubPackages"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> /&gt;</span></span></div></div></li><li><div><divdata-line-number="69"></div></div><div><div> <span><span>&lt;/</span><span><span><span>javaClientGenerator</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="70"></div></div><div><div> </div></div></li><li><div><divdata-line-number="71"></div></div><div><div> </div></div></li><li><div><divdata-line-number="72"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_shipping"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"Shipping"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="73"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_cart"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"Cart"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="74"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_cart_item"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"CartItem"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="75"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_category"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"Category"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="76"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_order"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"Order"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="77"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_order_item"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"OrderItem"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="78"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_pay_info"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"PayInfo"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="79"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_product"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"Product"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="80"></div></div><div><div> <span><span>&lt;</span><span><span><span>columnOverride</span></span></span><span> </span><span><span><span>column</span></span></span><span>=</span><span><span><span>"detail"</span></span></span><span> </span><span><span><span>jdbcType</span></span></span><span>=</span><span><span><span>"VARCHAR"</span></span></span><span> /&gt;</span></span></div></div></li><li><div><divdata-line-number="81"></div></div><div><div> <span><span>&lt;</span><span><span><span>columnOverride</span></span></span><span> </span><span><span><span>column</span></span></span><span>=</span><span><span><span>"sub_images"</span></span></span><span> </span><span><span><span>jdbcType</span></span></span><span>=</span><span><span><span>"VARCHAR"</span></span></span><span> /&gt;</span></span></div></div></li><li><div><divdata-line-number="82"></div></div><div><div> <span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="83"></div></div><div><div> <span><span>&lt;</span><span><span><span>table</span></span></span><span> </span><span><span><span>tableName</span></span></span><span>=</span><span><span><span>"mmall_user"</span></span></span><span> </span><span><span><span>domainObjectName</span></span></span><span>=</span><span><span><span>"User"</span></span></span><span> </span><span><span><span>enableCountByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableUpdateByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableDeleteByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>enableSelectByExample</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span> </span><span><span><span>selectByExampleQueryId</span></span></span><span>=</span><span><span><span>"false"</span></span></span><span>&gt;</span></span><span><span>&lt;/</span><span><span><span>table</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="84"></div></div><div><div> </div></div></li><li><div><divdata-line-number="85"></div></div><div><div> </div></div></li><li><div><divdata-line-number="86"></div></div><div><div> <span><span>&lt;!-- mybatis插件的搭建 --&gt;</span></span></div></div></li><li><div><divdata-line-number="87"></div></div><div><div> <span><span>&lt;/</span><span><span><span>context</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="88"></div></div><div><div><span><span>&lt;/</span><span><span><span>generatorConfiguration</span></span></span><span>&gt;</span></span></div></div></li></ol></code><divdata-title="复制"></div></pre><h4 id="2新建datasourceproperties文件配置数据库连接信息这里配置本地数据库"> <a name="t3"target="_blank"></a>2.新建datasource.properties文件,配置数据库连接信息(这里配置本地数据库)</h4> <p> <img src="https://img-blog.csdn.net/20170501211807876?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""></p> <prename="code" onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div>db.driverLocation=<span><span>F</span></span><span><span>:</span>/IdeaProjects/mmall/src/main/tool/mysql-connector-java-</span><span><span>5.1</span></span>.<span><span>6</span></span>-bin.jar</div></div></li><li><div><divdata-line-number="2"></div></div><div><div>db.driverClassName=com.mysql.jdbc.<span>Driver</span></div></div></li><li><div><divdata-line-number="3"></div></div><div><div>db.url=<span><span>jdbc:</span></span><span><span>mysql:</span></span>/<span><span>/localhost:3306/mmall</span></span>?useUnicode=<span><span>true</span></span>&amp;characterEncoding=<span>UTF</span>-<span><span>8</span></span></div></div></li><li><div><divdata-line-number="4"></div></div><div><div>db.username=root</div></div></li><li><div><divdata-line-number="5"></div></div><div><div>db.password=<span><span>940724</span></span></div></div></li></ol></code><divdata-title="复制"></div></pre><p> 配置完之后,就点击idea的左下角有个小按钮,把右侧的maven project按钮调出来(右侧已有的请忽略)&nbsp;<br><img src="https://img-blog.csdn.net/20170501212050752?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title="">&nbsp;<br> 点击maven project,并选定插件加载(双击就行)&nbsp;<br><img src="https://img-blog.csdn.net/20170501212307103?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title="">&nbsp;<br> 下方的控制台,出现build success就是成功了,就会发现dao的包和pojo包会生成好了接口和数据对象实体类,以及生成一个mapper文件夹,里面存储着数据库里各个实体的xml文件</p> <p> 注意mapper里生成的文件夹,我在创建的时候把时间戳给加里了,如果要完美的用的话,需要把时间戳优化一下</p> <h4 id="把insert标签下的createtimejdbctypetimestamp和updatetimejdbctypetimestamp改成now"> <a name="t4"target="_blank"></a>把insert标签下的#{createTime,jdbcType=TIMESTAMP}和#{updateTime,jdbcType=TIMESTAMP}改成now()</h4> <h4 id="把update标签下的updatetimejdbctypetimestamp改成now"> <a name="t5"target="_blank"></a>把update标签下的#{updateTime,jdbcType=TIMESTAMP}改成now()</h4> <p> 这个now()方法是数据库自带的函数,表示现在的时间</p> <hr><h2 id="二mybatis-plugin"><a name="t2"></a> <a name="t6"target="_blank"></a>二、mybatis-plugin</h2> <p> 我用的是idea15,如果其他版本不好用的,可以换成idea 15&nbsp;<br> 这是一个能够追踪dao接口和mapper文件里xml的一个插件</p> <ul><li> <p> 提供Mapper接口与配置文件中对应SQL的导航</p> </li><li> <p> 提供Mapper接口与配置文件中对应SQL的导航</p> </li><li> <p> 编辑XML文件时自动补全</p> </li><li> <p> 根据Mapper接口, 使用快捷键生成xml文件及SQL标签</p> </li><li> <p> ResultMap中的property支持自动补全,支持级联(属性A.属性B.属性C)</p> </li><li> <p> 快捷键生成@Param注解</p> </li><li> <p> XML中编辑SQL时, 括号自动补全</p> </li><li> <p> XML中编辑SQL时, 支持参数自动补全(基于@Param注解识别参数)</p> </li><li> <p> 自动检查Mapper XML文件中ID冲突</p> </li><li> <p> 自动检查Mapper XML文件中错误的属性值</p> </li><li> <p> 支持<a href="https://www.baidu.com/s?wd=Find&amp;tn=24004469_oem_dg&amp;rsv_dl=gh_pl_sl_csd" target="_blank">Find</a> Usage</p> </li><li> <p> 支持重构从命名</p> </li><li> <p> 支持别名</p> </li><li> <p> 自动生成ResultMap属性</p> </li><li> <p> 快捷键: Option + Enter(<a href="https://www.baidu.com/s?wd=Mac&amp;tn=24004469_oem_dg&amp;rsv_dl=gh_pl_sl_csd" target="_blank">Mac</a>) | Alt + Enter(Windows)(直接跳转到mapper中的sql语句的快捷键是:“Ctrl+ALT+B”)</p> </li></ul><h4 id="效果点击dao可以追踪到xml同理点击xml的sql右边小箭头可以追踪到dao方法"> <a name="t7"target="_blank"></a>效果:点击dao可以追踪到xml,,同理点击xml的sql右边小箭头,可以追踪到dao方法</h4> <p> <img src="https://img-blog.csdn.net/20170501230749182?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""></p> <h4 id="1安装插件"> <a name="t8"target="_blank"></a>1、安装插件:</h4> <p> <img src="https://img-blog.csdn.net/20170501214736063?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title="">&nbsp;<br> 然后重启下idea</p> <h4 id="2mybatis-plugin插件破解"> <a name="t9"target="_blank"></a>2.mybatis-plugin插件破解</h4> <p> 到这个网址去&nbsp;<br><a href="https://github.com/myoss/profile/tree/master/idea/plugin/MybatisPlugin" rel="nofollow"target="_blank">https://github.com/myoss/profile/tree/master/idea/plugin/MybatisPlugin</a>&nbsp;<br> 你看到了一个Git仓库,先clone下来,不会clone的down下来,记住路径&nbsp;<br> 接下来,你看到一堆版本,选择你安装的mybatis_plugin版本号,比如我的是IDEA15版本的,我安装版本就是v2.64,进入v2.64你会看到一个com文件夹,牢牢记住这个文件夹有大作用</p> <p> <img src="https://img-blog.csdn.net/20170501222135388?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEa" alt="这里写图片描述" title=""></p> <p> 点击如图所示的右上角的fork,登录自己的github,登录之后,会发现右边有个绿色的Dowload图标,点击那个就可以下载别人的项目了&nbsp;<br><img src="https://img-blog.csdn.net/20170501224229186?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""></p> <h3 id="1-windows破解"><a name="t3"></a> <a name="t10"target="_blank"></a>(1) windows破解</h3> <p> 首先你要找到mybatis_plus.jar的位置,位置一般在这里&nbsp;<br> C:\Users\youname(你自己的文档).IntelliJIdea\config\plugins\mybatis_plus\lib&nbsp;<br> 用winRAR打开&nbsp;<br><img src="https://img-blog.csdn.net/20170501225736536?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title="">&nbsp;<br> 将下载好的破解,与plugin插件压缩包中的文件更换,拖入压缩包,更换就行&nbsp;<br><img src="https://img-blog.csdn.net/20170501225438518?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjg0MTIwOQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""></p> <h3 id="重启你的idea完毕破解搞定"><a name="t4"></a> <a name="t11"target="_blank"></a>重启你的IDEA,完毕,破解搞定</h3> <h3 id="2mac破解"><a name="t5"></a> <a name="t12"target="_blank"></a>(2)mac破解</h3> <p> 使用find命令在你的用户目录下查找mybatis_plus.jar这个文件</p> <prename="code" onclick="hljs.copyCode(event)"><code><span>find</span> <span>~ -name</span> <span>"mybatis_plus.jar"</span></code><divdata-title="复制"></div></pre><p> OK,拿到一个地址,然后进去</p> <prename="code" onclick="hljs.copyCode(event)"><code><span>cd</span> /Users/XXXXX/Library/Application Support/IntelliJIdea15/mybatis_plus/lib</code><divdata-title="复制"></div></pre><p> 看到了2个文件</p> <prename="code" onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div><span>#创建一个文件夹</span></div></div></li><li><div><divdata-line-number="2"></div></div><div><div>mkdir m</div></div></li><li><div><divdata-line-number="3"></div></div><div><div><span>#进去 </span></div></div></li><li><div><divdata-line-number="4"></div></div><div><div>cd m</div></div></li><li><div><divdata-line-number="5"></div></div><div><div><span>#拷贝到m文件夹中 </span></div></div></li><li><div><divdata-line-number="6"></div></div><div><div><span>cp</span> ../mybatis_plus<span>.jar</span> .</div></div></li><li><div><divdata-line-number="7"></div></div><div><div><span>#解压jar包</span></div></div></li><li><div><divdata-line-number="8"></div></div><div><div>jar xf mybatis_plus<span>.jar</span> </div></div></li><li><div><divdata-line-number="9"></div></div><div><div><span>#复制com文件夹到这里 路径根据你情况而定,版本号也根据你情况而定</span></div></div></li><li><div><divdata-line-number="10"></div></div><div><div><span>cp</span> -r ~/Workspace/github/mybatis_plus/idea/plugin/MybatisPlugin/v2<span>.7</span>\~v2<span>.83</span>/<span>com</span> .</div></div></li><li><div><divdata-line-number="11"></div></div><div><div><span>#重新打为jar包</span></div></div></li><li><div><divdata-line-number="12"></div></div><div><div>jar cf mybatis_plus<span>.jar</span> *</div></div></li><li><div><divdata-line-number="13"></div></div><div><div><span>#复制到m的上层目录</span></div></div></li><li><div><divdata-line-number="14"></div></div><div><div><span>cp</span> mybatis_plus<span>.jar</span> ../</div></div></li></ol></code><divdata-title="复制"></div></pre><h3 id="重启你的idea完毕破解搞定-1"><a name="t6"></a> <a name="t13"target="_blank"></a>重启你的IDEA,完毕,破解搞定</h3> <hr><h2 id="三mybatis-pagehelper"><a name="t7"></a> <a name="t14"target="_blank"></a>三、Mybatis-pageHelper</h2> <p> 是一个开源的分页插件(如下网址有插件的全介绍)&nbsp;<br><a href="https://github.com/pagehelper/Mybatis-PageHelper" rel="nofollow"target="_blank">https://github.com/pagehelper/Mybatis-PageHelper</a>&nbsp;<br> 它的原理,是通过spring的AOP来实现的,这个插件能在执行sql的时候,把相关的数据再执行一次</p> <h4 id="1pomxml里添加依赖"> <a name="t15"target="_blank"></a>1.pom.xml里添加依赖</h4> <prename="code" onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div><span><span>&lt;</span><span><span><span>dependency</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="2"></div></div><div><div> <span><span>&lt;</span><span><span><span>groupId</span></span></span><span>&gt;</span></span>com.github.pagehelper<span><span>&lt;/</span><span><span><span>groupId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="3"></div></div><div><div> <span><span>&lt;</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span>pagehelper<span><span>&lt;/</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="4"></div></div><div><div> <span><span>&lt;</span><span><span><span>version</span></span></span><span>&gt;</span></span>4.1.0<span><span>&lt;/</span><span><span><span>version</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="5"></div></div><div><div> <span><span>&lt;/</span><span><span><span>dependency</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="6"></div></div><div><div> </div></div></li><li><div><divdata-line-number="7"></div></div><div><div> <span><span>&lt;</span><span><span><span>dependency</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="8"></div></div><div><div> <span><span>&lt;</span><span><span><span>groupId</span></span></span><span>&gt;</span></span>com.github.miemiedev<span><span>&lt;/</span><span><span><span>groupId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="9"></div></div><div><div> <span><span>&lt;</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span>mybatis-paginator<span><span>&lt;/</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="10"></div></div><div><div> <span><span>&lt;</span><span><span><span>version</span></span></span><span>&gt;</span></span>1.2.17<span><span>&lt;/</span><span><span><span>version</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="11"></div></div><div><div> <span><span>&lt;/</span><span><span><span>dependency</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="12"></div></div><div><div> </div></div></li><li><div><divdata-line-number="13"></div></div><div><div> <span><span>&lt;</span><span><span><span>dependency</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="14"></div></div><div><div> <span><span>&lt;</span><span><span><span>groupId</span></span></span><span>&gt;</span></span>com.github.jsqlparser<span><span>&lt;/</span><span><span><span>groupId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="15"></div></div><div><div> <span><span>&lt;</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span>jsqlparser<span><span>&lt;/</span><span><span><span>artifactId</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="16"></div></div><div><div> <span><span>&lt;</span><span><span><span>version</span></span></span><span>&gt;</span></span>0.9.4<span><span>&lt;/</span><span><span><span>version</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="17"></div></div><div><div> <span><span>&lt;/</span><span><span><span>dependency</span></span></span><span>&gt;</span></span></div></div></li></ol></code><divdata-title="复制"></div></pre><h4 id="2在spring配置文件里添加配置"> <a name="t16"target="_blank"></a>2.在spring配置文件里添加配置</h4> <prename="code" onclick="hljs.copyCode(event)"><code><ol><li><div><divdata-line-number="1"></div></div><div><div> <span><span>&lt;</span><span><span><span>bean</span></span></span><span> </span><span><span><span>id</span></span></span><span>=</span><span><span><span>"sqlSessionFactory"</span></span></span><span> </span><span><span><span>class</span></span></span><span>=</span><span><span><span>"org.mybatis.spring.SqlSessionFactoryBean"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="2"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"dataSource"</span></span></span><span> </span><span><span><span>ref</span></span></span><span>=</span><span><span><span>"dataSource"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="3"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"mapperLocations"</span></span></span><span> </span><span><span><span>value</span></span></span><span>=</span><span><span><span>"classpath*:mappers/*Mapper.xml"</span></span></span><span>/&gt;</span></span></div></div></li><li><div><divdata-line-number="4"></div></div><div><div> </div></div></li><li><div><divdata-line-number="5"></div></div><div><div> <span><span>&lt;!-- 分页插件 --&gt;</span></span></div></div></li><li><div><divdata-line-number="6"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"plugins"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="7"></div></div><div><div> <span><span>&lt;</span><span><span><span>array</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="8"></div></div><div><div> <span><span>&lt;</span><span><span><span>bean</span></span></span><span> </span><span><span><span>class</span></span></span><span>=</span><span><span><span>"com.github.pagehelper.PageHelper"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="9"></div></div><div><div> <span><span>&lt;</span><span><span><span>property</span></span></span><span> </span><span><span><span>name</span></span></span><span>=</span><span><span><span>"properties"</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="10"></div></div><div><div> <span><span>&lt;</span><span><span><span>value</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="11"></div></div><div><div> dialect=mysql</div></div></li><li><div><divdata-line-number="12"></div></div><div><div> <span><span>&lt;/</span><span><span><span>value</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="13"></div></div><div><div> <span><span>&lt;/</span><span><span><span>property</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="14"></div></div><div><div> <span><span>&lt;/</span><span><span><span>bean</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="15"></div></div><div><div> <span><span>&lt;/</span><span><span><span>array</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="16"></div></div><div><div> <span><span>&lt;/</span><span><span><span>property</span></span></span><span>&gt;</span></span></div></div></li><li><div><divdata-line-number="17"></div></div><div><div> </div></div></li><li><div><divdata-line-number="18"></div></div><div><div> <span><span>&lt;/</span><span><span><span>bean</span></span></span><span>&gt;</span></span></div></div></li></ol></code><divdata-title="复制"></div></pre><p> 注意不同数据库的方言的使用</p> <h2 id="这样三剑客就都配置ok了"><a name="t8"></a> <a name="t17"target="_blank"></a>这样三剑客就都配置OK了</h2> </div>

关于SpringBoot+Mybatis+MySQL的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于java day55【 Mybatis 连接池与事务深入 、 Mybatis 的动态 SQL 语句、 Mybatis 多表查询之一对多 、 Mybatis 多表查询之多对多】、java day56【 Mybatis 延迟加载策略 、 Mybatis 缓存、Mybatis 注解开发 】、JAVA WEB 快速入门之从编写一个基于 SpringBoot+Mybatis 快速创建的 REST API 项目了解 SpringBoot、SpringMVC REST API、Mybatis 等相关知识、Mabatis三剑客分别是:mybatis-generator、mybatis-plugin、mybatis-pagehelper的相关信息,请在本站寻找。

本文标签: