青铜 - 后端CRUD之MyBatis栈演进1

在上一节,我们安装了IDEA,也看了两个基础的例子,你可能很容易把例子跑起来,但是不懂它是怎么工作的。在初学之际这是正常的,这个阶段你了解的不够多,你也是没有太多的分辨和提问的能力的, 无需焦虑,一步步坚持高效学习就可以了,做对的事,其它交给时间。本节主要介绍最基础的增删查改的例子的第一部分,用国内最流行的框架SpringBoot+MyBatis栈,这里我不会每一个步骤都写,原因我在前面也写了,你更缺的是方向性的指引,循序渐进的进阶例子会让你有大局观(而不是过早的沉溺于细节本身, 所以这里的例子也是适合有一定经验的开发人员研究和反思的。@pdai

为避免大量内容涌入,而给刚学习的开发者造成困扰,这里将后端CRUD之MyBatis栈演进分成两篇文章,本文是第第一篇, 下面是青铜 - 后端CRUD之MyBatis栈演进2

学前几点(给刚入门的)

TIP

对于初级的开发者,通常头脑中是一团浆糊,你需要做的是把用来焦虑的时间投入到学习中来;在学习前,从如下几个小点来进阶吧。对于有一定开发经验的开发者来说,后续几个例子仍你是很好的学习和开发的模板。@pdai

为什么用SpringBoot

框架的出现就是为了将繁杂的重复性工作抽象出去,让开发逐步用最小的代码量快速实现功能。@pdai

  • 为什么学SpringBoot之前我会推荐学习SpringMVC

我在前文也提到,我推荐先学习《跟开涛学 SpringMVC》。因为SpringMVC是基础,可以构筑你的web认知,而且你看的懂,这样循序渐进你可以很快理解。这是有很好的承接的,而不是脱离了SpringBoot你啥也不知道。

  • 为什么写代码是用SpringBoot

最主要还是因为SpringBoot目前已经成为开发主流的选择。我们顺便看下网上关于springboot的核心功能和优势:

Spring Boot 核心功能

  • 1)独立运行的 Spring 项目 Spring Boot 可以以 jar 包的形式独立运行,运行一个 Spring Boot 项目只需通过 java–jar xx.jar 来运行。
  • 2)内嵌 Servlet 容器 Spring Boot 可选择内嵌 Tomcat、Jetty 或者 Undertow,这样我们无须以 war 包形式部署项目。
  • 3)提供 starter 简化 Maven 配置 Spring 提供了一系列的 starter pom 来简化 Maven 的依赖加载,例如,当你使用了spring-boot-starter-web 时,会自动加入如依赖包。
  • 4)自动配置 Spring Spring Boot 会根据在类路径中的 jar 包、类,为 jar 包里的类自动配置 Bean,这样会极大地减少我们要使用的配置。当然,Spring Boot 只是考虑了大多数的开发场景,并不是所有的场景,若在实际开发中我们需要自动配置 Bean,而 Spring Boot 没有提供支持,则可以自定义自动配置。
  • 5)准生产的应用监控 Spring Boot 提供基于 http、ssh、telnet 对运行时的项目进行监控。
  • 6)无代码生成和 xml 配置 Spring Boot 的神奇的不是借助于代码生成来实现的,而是通过条件注解来实现的,这是 Spring 4.x 提供的新特性。Spring 4.x 提倡使用 Java 配置和注解配置组合,而 Spring Boot 不需要任何 xml 配置即可实现 Spring 的所有配置。

Spring Boot的优点

  • 快速构建项目。
  • 对主流开发框架的无配置集成。
  • 项目可独立运行,无须外部依赖Servlet容器。
  • 提供运行时的应用监控。
  • 极大地提高了开发、部署效率。
  • 与云计算的天然集成。

什么是JDBC,ORM,MyBatis

  • 什么是JDBC

JDBC(JavaDataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句。

  • 什么是ORM

对象关系映射(Object Relational Mapping,简称ORM), 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式,具体如下:

具体映射:

  • 数据库的表(table) --> 类(class)

  • 记录(record,行数据)--> 对象(object)

  • 字段(field)--> 对象的属性(attribute)

  • 什么是MyBatis

MyBatis是对JDBC的封装, 是常用的ORM框架之一。MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录

为什么初学要从CRUD开发开始

  • 什么CRUD

crud是指在做计算处理时的增加(Create)、读取(Read)、更新(Update)和删除(Delete)几个单词的首字母简写。crud主要被用在描述软件系统中数据库或者持久层的基本操作功能。

  • 为什CRUD是基础

在常见的开发中,最为基础,也是业务单元的基础。这就是好多入门级程序员一直在做的事情,所以一开始我们需要学习CRUD,并且逐渐摸出它的套路,提升自己的效率。

SpringBoot - MySQL+MyBatis栈演进

TIP

好了,进入SpringBoot+MyBatis开发正题;在开发之前有必要了解下,基于MyBatis系列这个技术栈的发展过程以及如何通过循序渐进的例子进行提升, 这对你的进阶有很大好处,因为站的高度一开始就比别人高,大脑会潜移默化的会携带对它深入思考。

MyBatis栈技术演进

MyBatis栈经历了什么的技术演进的?

  • JDBC,自行封装JDBCUtil
    • Java5的时代,还是自行封装JDBC的Util的
  • IBatis->MyBatis,基于xml配置
    • 出现了IBatis等ORM框架,且基于xml配置的开始流行
  • MyBatis衍生:PageHelper分页
    • 衍生出一些基于MyBatis的插件,比如流行的分页实现PageHelper
  • MyBatis衍生:相关工具
    • 为了减少重复编码,衍生出了MyBatis代码生成工具
    • IDE一些工具和插件等
  • MyBatis基于注解的配置
    • 基于注解的实现逐渐替换掉基于xml配置的实现
  • MyBatis-Plus
    • 国产的融合封装

MyBatis栈技术例子

如何通过例子进阶呢?这里给5个相关例子

  • 第一个例子 项目代码 (opens new window)

    • MyBatis,基于xml配置的增删查改
    • PageHelper分页
    • 后端模板Thymeleaf
  • 第二个例子 项目代码 (opens new window)

    • 第一个例子基础上
    • 多个关联表操作
    • Druid线程池
  • 第三个例子

    • 代码生成工具
    • MyBatis IDEA相关插件
  • 第四个例子

    • 第一个例子基础上
    • 改为MyBatis基于注解配置的增删查改
  • 第五个例子

    • 第一个例子基础上
    • 改为MyBatis-plus增删查改

例子1:SpringBoot+MySQL+MyBatis配置+Thymeleaf

TIP

我们从MyBatis基于xml配置的实现方式开始学起, 最基本的增删查改例子,第一个例子多写几点,后续例子有一定的关联性,所以只会写差异的地方。

这个例子实现什么样的功能

CURD 主体功能...

  • 列表

  • 增加

  • 修改

  • 搜索

如何把例子跑起来

代码在这里:项目代码 (opens new window), 这个项目是我从开源项目Ruoyi中抠出来的并做了部分修改,方便大家学习使用。

  • 数据库

    • 导入sql 文件

    • 修改application.yml中数据库相关配置

  • 跑代码实例

  • 代码的层次结构

MyBatis相关配置和实现

MyBatis如何配置的呢

  • Pom
<!-- SpringBoot集成mybatis框架 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis.spring.boot.starter.version}</version>
</dependency>

<!-- pagehelper 分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>${pagehelper.spring.boot.starter.version}</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
  • application.yml
# orm
mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  type-aliases-package: tech.pdai.mybatis.xml.web.*.domain
  configuration:
    cache-enabled: true
    use-generated-keys: true
    default-executor-type: REUSE
    #log-impl: SLF4J
    use-actual-param-name: true
1
2
3
4
5
6
7
8
9
10
  • mapper

代码

@Mapper
public interface UserMapper {
//...
}
1
2
3
4

对应

<?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="tech.pdai.mybatis.xml.web.system.user.mapper.UserMapper">

	//...
	
</mapper> 

1
2
3
4
5
6
7
8
9
10

MyBatis 分页PageHelper

PageHelper是早期MyBatis技术栈中最为常用的分页方式。(早先常见的面试题就有:如何实现数据库的分页?有哪几种方式?)

  • 如何使用PageHelper呢?

看官网的介绍 (opens new window)

PageHelper有哪些使用方式呢?

//第一种,RowBounds方式的调用
List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));

//第二种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);

//第三种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.offsetPage(1, 10);
List<User> list = userMapper.selectIf(1);

//第四种,参数方法调用
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<User> selectByPageNumSize(
            @Param("user") User user,
            @Param("pageNum") int pageNum, 
            @Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代码中直接调用:
List<User> list = userMapper.selectByPageNumSize(user, 1, 10);

//第五种,参数对象
//如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
//有如下 User 对象
public class User {
    //其他fields
    //下面两个参数名和 params 配置的名字一致
    private Integer pageNum;
    private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<User> selectByPageNumSize(User user);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<User> list = userMapper.selectByPageNumSize(user);

//第六种,ISelect 接口方式
//jdk6,7用法,创建接口
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectGroupBy();
    }
});
//jdk8 lambda用法
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());

//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectGroupBy();
    }
});
//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy());

//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectLike(user);
    }
});
//lambda
total = PageHelper.count(()->userMapper.selectLike(user));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
  • 这个例子中是如何使用的

我们看下这个例子中是如何使用的

我们看到在UserController中:

@PostMapping("/list")
@ResponseBody
public TableDataInfo list(User user) {
    startPage();
    List<User> list = userService.selectUserList(user);
    return getDataTable(list);
}
1
2
3
4
5
6
7

其中startPage方法:

/**
  * 设置请求分页数据
  */
protected void startPage() {
    PageDomain pageDomain = TableSupport.buildPageRequest();
    Integer pageNum = pageDomain.getPageNum();
    Integer pageSize = pageDomain.getPageSize();
    if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) {
        String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
        PageHelper.startPage(pageNum, pageSize, orderBy);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

再看下PageDomain中buildPageRequest方法的封装:

public static PageDomain getPageDomain() {
    PageDomain pageDomain = new PageDomain();
    pageDomain.setPageNum(ServletUtils.getParameterToInt(Constants.PAGE_NUM));
    pageDomain.setPageSize(ServletUtils.getParameterToInt(Constants.PAGE_SIZE));
    pageDomain.setOrderByColumn(ServletUtils.getParameter(Constants.ORDER_BY_COLUMN));
    pageDomain.setIsAsc(ServletUtils.getParameter(Constants.IS_ASC));
    return pageDomain;
}
1
2
3
4
5
6
7
8

通过上述的代码,我们可以看出,这里使用的是中规中矩的第二种方式。在此基础上进行了一些封装:

  1. 考虑到其它CURD中查询也需要分页,所以将startPage放在了父类Controller中;
  2. 考虑到统一的pageNum和pageSize等,将其定为常量并封装为一个PageDomain; 同时前端也可以进行相应封装。
  3. 考虑到获取request中获取参数值,所以这里再封装一个ServletUtils...
  • PageHelper是如何实现分页的

我们知道如何使用PageHelper后,我们发现使用PageHelper.startPage(pageNum, pageSize, orderBy)方法后的第一个select是具备分页能力的,那它是如何做到的呢?

理解它的原理,有两个点:

  1. 第一,相对对于JDBC这种嵌入式的分页而言,PageHelper分页是独立的,能做到独立分页查询,那它必然是通过某个拦截点进行了拦截,这样它才能够进行解耦分离出分页
  2. 第二,我们通过PageHelper.startPage(pageNum, pageSize, orderBy)方法后的第一个select是具备分页能力的,那它必然缓存了分页信息,同时结合线程知识,这里必然使用的是本地栈ThreadLocal,即每个线程有一个本地缓存。

所以结合这两点,聪明的你就会想到它大概是如何实现的,关键就是两点(拦截,ThreadLocal), 我们看下源码:

简单看下拦截

/**
 * Mybatis拦截器方法
 *
 * @param invocation 拦截器入参
 * @return 返回执行结果
 * @throws Throwable 抛出异常
 */
public Object intercept(Invocation invocation) throws Throwable {
    if (autoRuntimeDialect) {
        SqlUtil sqlUtil = getSqlUtil(invocation);
        return sqlUtil.processPage(invocation);
    } else {
        if (autoDialect) {
            initSqlUtil(invocation);
        }
        return sqlUtil.processPage(invocation);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

进而看下sqlUtil.processPage(invocation);方法

/**
 *
 * @param invocation 拦截器入参
 * @return 返回执行结果
 * @throws Throwable 抛出异常
 */
private Object _processPage(Invocation invocation) throws Throwable {
    final Object[] args = invocation.getArgs();
    Page page = null;
    //支持方法参数时,会先尝试获取Page
    if (supportMethodsArguments) {
        // 从线程本地变量中获取Page信息,就是我们刚刚设置的
        page = getPage(args);
    }
    //分页信息
    RowBounds rowBounds = (RowBounds) args[2];
    //支持方法参数时,如果page == null就说明没有分页条件,不需要分页查询
    if ((supportMethodsArguments && page == null)
            //当不支持分页参数时,判断LocalPage和RowBounds判断是否需要分页
            || (!supportMethodsArguments && SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT)) {
        return invocation.proceed();
    } else {
        //不支持分页参数时,page==null,这里需要获取
        if (!supportMethodsArguments && page == null) {
            page = getPage(args);
        }
        // 进入查看
        return doProcessPage(invocation, page, args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

所以startPage方法和这里的getPage(args);这方法里应该包含了ThreadLocal中设置和获取分页参数的,让我们看下startPage方法即可:

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
    Page<E> page = new Page(pageNum, pageSize, count);
    page.setReasonable(reasonable);
    page.setPageSizeZero(pageSizeZero);
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) {
        page.setOrderBy(oldPage.getOrderBy());
    }

    setLocalPage(page);
    return page;
}
// ...
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();

protected static void setLocalPage(Page page) {
    LOCAL_PAGE.set(page); // 看这里
}

// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

所以这里提示下想进阶的开发者,源码的阅读是伴随着思路现行的(有了思路,简单看源码),而不是直接源码。

  • 使用PageHelper有何注意点

看官网的说明 (opens new window)

  1. 只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页。
  2. 请不要配置多个分页插件:请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xmlSpring<bean>配置方式,请选择其中一种,不要同时配置多个分页插件)!
  3. 分页插件不支持带有for update语句的分页:对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。
  4. 分页插件不支持嵌套结果映射: 由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。
  • 如何评价下这个例子中PageHelper的封装和使用

让我们进一步思考下,PageHelper在这个例子中封装和使用是否优雅。

就使用PageHelper本身而言,第二种和第三种方式较为中规中矩,它是早前绝大多数的使用方式。并不是说中规中矩就是不好的,很多场景下保守能让团队的绝大多数人接受便是好的方式。当更多的人接受Java8 lambda方式后,Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());这种方式显然对于他们来说更为优雅。

就本例对PageHelper的封装来看,通常而言针对PageHelper封装有两个方式,一种是封装PageUtils,另一种就是放在上层Controller中, 这个例子中使用第二种。它并不是非常完美的封装方式,但确实是在有限的需求内是一个完整的封装。需要记住一点,过多的设计和封装也没有必要,重点在于封装是伴随着需求的改变而适度调整而来的。这个例子中可能出现需求变更从而影响其封装的点,比如排序可能是组合排序,比如先按照A字段的升序再按照B字段的降序排序,比如SQLUtil等带来的Util冗余等。

就分页未来来看,PageHelper不是长远的趋势,真正长远的趋势应该是被ORM融合,并且考虑更多的适用场景提供各种常见实现方式的封装功能(即约定大于实现中的约定),同时结合builder参数构建和lambda方式写法等语言特性写法上做到优雅。所以你可以看到JPA,MyBatis-plus等中相关的封装和实现就是这种趋势。

Thymeleaf模板

我们可以看到前端是使用的Thyemeleaf,那Thymeleaf是如何使用,解析出页面的呢?

前端的封装

多个CURD时,肯定需要考虑前端的封装了,我们看下前端如何封装的。

例子2:SpringBoot+MySQL+MyBatis配置(多表)+Thymeleaf

TIP

上述例子是一个非常简单的单表例子,接下来我们看下如何表的关联(包含一对多和一对一)等。

项目源码

项目代码 (opens new window), 代码从ruoyi中抽出整理,形式上是第一个例子的拓展。

这个例子增加了什么功能

这个例子是上一个例子的延伸,我们看下功能上增加了什么

  • 用户管理:关联角色,关联部门,及通过部门过滤用户

  • 角色管理

  • 部门管理(树形结构)

MyBatis多表关联

我们来看下角色(一对多,一个用户有多个角色)和部门(一对一,一个用户属于一个部门)是如何实现的。

  • User
public class User extends BaseEntity {

    // ...

    /**
     * 部门对象
     */
    private Dept dept;

    private List<Role> roles;

}
1
2
3
4
5
6
7
8
9
10
11
12
  • User和Role的关联类
public class UserRole {
    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 角色ID
     */
    private Long roleId;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
                .append("userId", getUserId())
                .append("roleId", getRoleId())
                .toString();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  • UserMapper中通过association关联dept,和通过collection关联role集合。
<?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="tech.pdai.mybatis.xml.web.system.user.mapper.UserMapper">

	<resultMap type="tech.pdai.mybatis.xml.web.system.user.domain.User" id="UserResult">
		<id     property="userId"       column="user_id"      />
		<result property="deptId"       column="dept_id"      />
		<result property="loginName"    column="login_name"   />
		<result property="userName"     column="user_name"    />
		<result property="email"        column="email"        />
		<result property="phonenumber"  column="phonenumber"  />
		<result property="sex"          column="sex"          />
		<result property="avatar"       column="avatar"       />
		<result property="status"       column="status"       />
		<result property="delFlag"      column="del_flag"     />
		<result property="createBy"     column="create_by"    />
		<result property="createTime"   column="create_time"  />
		<result property="updateBy"     column="update_by"    />
		<result property="updateTime"   column="update_time"  />
		<result property="remark"       column="remark"       />
		<association property="dept"    column="dept_id" javaType="tech.pdai.mybatis.xml.web.system.dept.domain.Dept" resultMap="deptResult" />
		<collection  property="roles"   javaType="java.util.List"        resultMap="RoleResult" />
	</resultMap>
	
	<resultMap id="deptResult" type="tech.pdai.mybatis.xml.web.system.dept.domain.Dept">
		<id     property="deptId"   column="dept_id"     />
		<result property="parentId" column="parent_id"   />
		<result property="deptName" column="dept_name"   />
		<result property="orderNum" column="order_num"   />
		<result property="leader"   column="leader"   />
		<result property="status"   column="dept_status" />
	</resultMap>
	
	<resultMap id="RoleResult" type="tech.pdai.mybatis.xml.web.system.role.domain.Role">
		<id     property="roleId"       column="role_id"        />
		<result property="roleName"     column="role_name"      />
		<result property="roleKey"      column="role_key"       />
		<result property="roleSort"     column="role_sort"      />
		<result property="dataScope"    column="data_scope"     />
		<result property="status"       column="role_status"    />
	</resultMap>
	
	<sql id="selectUserVo">
        select  u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_time, u.remark,
       		    d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
       		    r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
		from sys_user u
			 left join sys_dept d on u.dept_id = d.dept_id
			 left join sys_user_role ur on u.user_id = ur.user_id
			 left join sys_role r on r.role_id = ur.role_id
    </sql>
	
	<select id="selectUserList" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" resultMap="UserResult">
		select u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
		left join sys_dept d on u.dept_id = d.dept_id
		where u.del_flag = '0'
		<if test="loginName != null and loginName != ''">
			AND u.login_name like concat('%', #{loginName}, '%')
		</if>
		<if test="status != null and status != ''">
			AND u.status = #{status}
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
		</if>
		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
		</if>
		<if test="deptId != null and deptId != 0">
			AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE FIND_IN_SET (#{deptId},ancestors) ))
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>
	
	<select id="selectAllocatedList" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" resultMap="UserResult">
	    select distinct u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.status, u.create_time
	    from sys_user u
			 left join sys_dept d on u.dept_id = d.dept_id
			 left join sys_user_role ur on u.user_id = ur.user_id
			 left join sys_role r on r.role_id = ur.role_id
	    where u.del_flag = '0' and r.role_id = #{roleId}
	    <if test="loginName != null and loginName != ''">
			AND u.login_name like concat('%', #{loginName}, '%')
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>
	
	<select id="selectUnallocatedList" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" resultMap="UserResult">
	    select distinct u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.status, u.create_time
	    from sys_user u
			 left join sys_dept d on u.dept_id = d.dept_id
			 left join sys_user_role ur on u.user_id = ur.user_id
			 left join sys_role r on r.role_id = ur.role_id
	    where u.del_flag = '0' and (r.role_id != #{roleId} or r.role_id IS NULL)
	    and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{roleId})
	    <if test="loginName != null and loginName != ''">
			AND u.login_name like concat('%', #{loginName}, '%')
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>
	
	<select id="selectUserByLoginName" parameterType="String" resultMap="UserResult">
	    <include refid="selectUserVo"/>
		where u.login_name = #{userName}
	</select>
	
	<select id="selectUserByPhoneNumber" parameterType="String" resultMap="UserResult">
		<include refid="selectUserVo"/>
		where u.phonenumber = #{phonenumber}
	</select>
	
	<select id="selectUserByEmail" parameterType="String" resultMap="UserResult">
	    <include refid="selectUserVo"/>
		where u.email = #{email}
	</select>
	
	<select id="checkLoginNameUnique" parameterType="String" resultType="int">
		select count(1) from sys_user where login_name=#{loginName}
	</select>
	
	<select id="checkPhoneUnique" parameterType="String" resultMap="UserResult">
		select user_id, phonenumber from sys_user where phonenumber=#{phonenumber}
	</select>
	
	<select id="checkEmailUnique" parameterType="String" resultMap="UserResult">
		select user_id, email from sys_user where email=#{email}
	</select>
	
	<select id="selectUserById" parameterType="Long" resultMap="UserResult">
		<include refid="selectUserVo"/>
		where u.user_id = #{userId}
	</select>
	
	<delete id="deleteUserById" parameterType="Long">
 		delete from sys_user where user_id = #{userId}
 	</delete>
 	
 	<delete id="deleteUserByIds" parameterType="Long">
 		update sys_user set del_flag = '2' where user_id in
 		<foreach collection="array" item="userId" open="(" separator="," close=")">
 			#{userId}
        </foreach> 
 	</delete>
 	
 	<update id="updateUser" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User">
 		update sys_user
 		<set>
 			<if test="deptId != null and deptId != 0">dept_id = #{deptId},</if>
 			<if test="loginName != null and loginName != ''">login_name = #{loginName},</if>
 			<if test="userName != null and userName != ''">user_name = #{userName},</if>
 			<if test="email != null and email != ''">email = #{email},</if>
 			<if test="phonenumber != null and phonenumber != ''">phonenumber = #{phonenumber},</if>
 			<if test="sex != null and sex != ''">sex = #{sex},</if>
 			<if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
 			<if test="status != null and status != ''">status = #{status},</if>
 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
 			<if test="remark != null">remark = #{remark},</if>
 			update_time = sysdate()
 		</set>
 		where user_id = #{userId}
	</update>
 	
 	<insert id="insertUser" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" useGeneratedKeys="true" keyProperty="userId">
 		insert into sys_user(
 			<if test="userId != null and userId != 0">user_id,</if>
 			<if test="deptId != null and deptId != 0">dept_id,</if>
 			<if test="loginName != null and loginName != ''">login_name,</if>
 			<if test="userName != null and userName != ''">user_name,</if>
 			<if test="email != null and email != ''">email,</if>
 			<if test="avatar != null and avatar != ''">avatar,</if>
 			<if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
 			<if test="sex != null and sex != ''">sex,</if>
 			<if test="status != null and status != ''">status,</if>
 			<if test="createBy != null and createBy != ''">create_by,</if>
 			<if test="remark != null and remark != ''">remark,</if>
 			create_time
 		)values(
 			<if test="userId != null and userId != ''">#{userId},</if>
 			<if test="deptId != null and deptId != ''">#{deptId},</if>
 			<if test="loginName != null and loginName != ''">#{loginName},</if>
 			<if test="userName != null and userName != ''">#{userName},</if>
 			<if test="email != null and email != ''">#{email},</if>
 			<if test="avatar != null and avatar != ''">#{avatar},</if>
 			<if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
 			<if test="sex != null and sex != ''">#{sex},</if>
 			<if test="status != null and status != ''">#{status},</if>
 			<if test="createBy != null and createBy != ''">#{createBy},</if>
 			<if test="remark != null and remark != ''">#{remark},</if>
 			sysdate()
 		)
	</insert>
	
</mapper> 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
  • User和Role之间是一对多关系,所以是独立的表,在操作用户时需要同步删除User和Role的关联表UserRole

比如

/**
  * 通过用户ID删除用户
  *
  * @param userId 用户ID
  * @return 结果
  */
@Override
public int deleteUserById(Long userId) {
    // 删除用户与角色关联
    userRoleMapper.deleteUserRoleByUserId(userId);
    return userMapper.deleteUserById(userId);
}
1
2
3
4
5
6
7
8
9
10
11
12

联系我

添加@pdai微信

PS:添加时请备注Java全栈,谢谢!