文章目录
1. MyBatis简介
1.1 什么是MyBatis?
MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
1.2 MyBatis的核心特点
- 简单易学:相比JPA/Hibernate,MyBatis更容易理解和掌握
- SQL控制:开发者可以完全控制SQL语句,适合复杂查询
- 高性能:直接执行SQL,性能优异
- 灵活映射:支持复杂的结果集映射
- 与Spring集成:与Spring框架无缝集成
- 动态SQL:支持动态构建SQL语句
1.3 MyBatis vs JPA/Hibernate
1.3.1 MyBatis的优势
- SQL可控性强:可以编写复杂的原生SQL
- 学习成本低:对于熟悉SQL的开发者更友好
- 性能优化:可以针对具体业务优化SQL
- 灵活性高:适合复杂的业务逻辑
1.3.2 JPA/Hibernate的优势
- 对象关系映射:自动处理对象和表的映射
- 跨数据库:支持多种数据库,自动生成SQL
- 缓存机制:内置强大的缓存功能
- 开发效率:减少SQL编写工作量
1.4 MyBatis核心组件
1.4.1 SqlSessionFactory
- MyBatis的核心工厂类
- 用于创建SqlSession
- 整个应用生命周期中应该只有一个实例
1.4.2 SqlSession
- 执行SQL命令的主要接口
- 线程不安全,每次使用后需要关闭
- 包含了所有执行SQL的方法
1.4.3 Mapper接口
- 定义数据访问方法的接口
- MyBatis会自动生成实现类
- 通过注解或XML配置SQL语句
1.4.4 Configuration
- MyBatis的配置信息
- 包含数据源、映射文件等配置
2. SpringBoot集成MyBatis
2.1 添加依赖
2.1.1 Maven依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>mybatis-demo</artifactId>
<version>1.0.0</version>
<name>mybatis-demo</name>
<description>SpringBoot集成MyBatis示例项目</description>
<properties>
<java.version>11</java.version>
<mybatis.version>3.5.13</mybatis.version>
</properties>
<dependencies>
<!-- SpringBoot Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- HikariCP连接池(SpringBoot默认) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- 数据验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 开发工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MyBatis测试 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
<!-- H2数据库(测试用) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
<!-- 代码生成器(可选) -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- MyBatis Generator插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.2</version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.1.2 Gradle依赖配置
plugins {
id 'org.springframework.boot' version '2.7.14'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
}
group = 'com.example'
version = '1.0.0'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1'
implementation 'mysql:mysql-connector-java'
implementation 'com.github.pagehelper:pagehelper-spring-boot-starter:1.4.6'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.3.1'
testImplementation 'com.h2database:h2'
}
2.2 数据库配置
2.2.1 application.yml配置
# 服务器配置
server:
port: 8080
servlet:
context-path: /
# Spring配置
spring:
application:
name: mybatis-demo
# 数据源配置
datasource:
# MySQL配置
url: jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# HikariCP连接池配置
hikari:
# 连接池名称
pool-name: MyBatisHikariCP
# 最大连接数
maximum-pool-size: 20
# 最小空闲连接数
minimum-idle: 5
# 连接超时时间(毫秒)
connection-timeout: 20000
# 空闲连接存活最大时间(毫秒)
idle-timeout: 300000
# 连接池最大存活时间(毫秒)
max-lifetime: 1200000
# 连接测试查询
connection-test-query: SELECT 1
# MyBatis配置
mybatis:
# 映射文件位置
mapper-locations: classpath:mapper/*.xml
# 实体类包路径
type-aliases-package: com.example.mybatisdemo.entity
# MyBatis配置文件位置
config-location: classpath:mybatis-config.xml
# 执行器类型:SIMPLE, REUSE, BATCH
executor-type: simple
# 是否检查映射文件存在
check-config-location: true
# MyBatis配置参数
mybatis:
configuration:
# 开启驼峰命名转换
map-underscore-to-camel-case: true
# 开启二级缓存
cache-enabled: true
# 延迟加载全局开关
lazy-loading-enabled: true
# 当触发方法时,是否加载该对象的所有属性
aggressive-lazy-loading: false
# 是否允许单一语句返回多结果集
multiple-result-sets-enabled: true
# 使用列标签代替列名
use-column-label: true
# 允许JDBC自动生成主键
use-generated-keys: false
# 指定默认执行器
default-executor-type: simple
# 设置超时时间
default-statement-timeout: 25000
# 设置查询返回值数量
default-fetch-size: 100
# 允许在嵌套语句中使用分页
safe-row-bounds-enabled: false
# 是否开启自动驼峰命名规则映射
map-underscore-to-camel-case: true
# 本地缓存机制
local-cache-scope: session
# 当没有为参数提供特定的JDBC类型时,为空值指定JDBC类型
jdbc-type-for-null: other
# 指定哪个对象的方法触发一次延迟加载
lazy-load-trigger-methods: equals,clone,hashCode,toString
# 指定动态SQL中用于开启和关闭的关键字
default-scripting-language: org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
# 指定动态SQL生成的默认语言
call-setters-on-nulls: false
# 当返回行的所有列都是空时,MyBatis默认返回null
return-instance-for-empty-row: false
# 指定MyBatis增加到日志名称的前缀
log-prefix:
# 指定MyBatis所用日志的具体实现
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 分页插件配置
pagehelper:
# 数据库类型
helper-dialect: mysql
# 启用合理化,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页
reasonable: true
# 支持通过Mapper接口参数来传递分页参数
support-methods-arguments: true
# 分页参数
params: count=countSql
# 返回PageInfo类型
return-page-info: check
# 日志配置
logging:
level:
# MyBatis日志
com.example.mybatisdemo.mapper: DEBUG
# SQL日志
org.mybatis: DEBUG
# Spring事务日志
org.springframework.transaction: DEBUG
# HikariCP日志
com.zaxxer.hikari: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
2.2.2 多环境配置示例
application-dev.yml(开发环境):
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_demo_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: dev_user
password: dev_password
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
root: DEBUG
application-prod.yml(生产环境):
spring:
datasource:
url: jdbc:mysql://prod-server:3306/mybatis_demo_prod?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 50
minimum-idle: 10
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
logging:
level:
root: INFO
com.example.mybatisdemo.mapper: WARN
2.3 MyBatis配置文件
2.3.1 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 设置 -->
<settings>
<!-- 开启驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载的全局开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 是否允许单一语句返回多结果集 -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 使用列标签代替列名 -->
<setting name="useColumnLabel" value="true"/>
<!-- 允许JDBC自动生成主键 -->
<setting name="useGeneratedKeys" value="true"/>
<!-- 指定默认执行器 -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 设置超时时间 -->
<setting name="defaultStatementTimeout" value="25000"/>
<!-- 设置查询返回值数量 -->
<setting name="defaultFetchSize" value="100"/>
<!-- 允许在嵌套语句中使用分页 -->
<setting name="safeRowBoundsEnabled" value="false"/>
<!-- 本地缓存机制 -->
<setting name="localCacheScope" value="SESSION"/>
<!-- 当没有为参数提供特定的JDBC类型时,为空值指定JDBC类型 -->
<setting name="jdbcTypeForNull" value="OTHER"/>
<!-- 指定哪个对象的方法触发一次延迟加载 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<!-- 指定动态SQL中用于开启和关闭的关键字 -->
<setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
<!-- 当返回行的所有列都是空时,MyBatis默认返回null -->
<setting name="callSettersOnNulls" value="false"/>
<!-- 当返回行的所有列都是空时,MyBatis默认返回null -->
<setting name="returnInstanceForEmptyRow" value="false"/>
<!-- 指定MyBatis所用日志的具体实现 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<!-- 类型别名 -->
<typeAliases>
<!-- 为包内的所有类注册别名 -->
<package name="com.example.mybatisdemo.entity"/>
</typeAliases>
<!-- 类型处理器 -->
<typeHandlers>
<!-- 自定义类型处理器 -->
<typeHandler handler="com.example.mybatisdemo.typehandler.JsonTypeHandler"/>
</typeHandlers>
<!-- 插件 -->
<plugins>
<!-- 分页插件 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<property name="reasonable" value="true"/>
<property name="supportMethodsArguments" value="true"/>
<property name="params" value="count=countSql"/>
</plugin>
</plugins>
</configuration>
3. 实体类设计
3.1 基础实体类设计
3.1.1 User实体类
package com.example.mybatisdemo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.validation.constraints.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户实体类
*/
public class User {
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
private String password;
/**
* 邮箱
*/
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
/**
* 手机号
*/
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
/**
* 真实姓名
*/
@Size(max = 20, message = "真实姓名长度不能超过20个字符")
private String realName;
/**
* 年龄
*/
@Min(value = 1, message = "年龄必须大于0")
@Max(value = 150, message = "年龄不能超过150")
private Integer age;
/**
* 性别:1-男,2-女,0-未知
*/
private Integer gender;
/**
* 状态:1-正常,0-禁用
*/
private Integer status;
/**
* 用户类型:1-管理员,2-普通用户
*/
private Integer userType;
/**
* 头像URL
*/
private String avatar;
/**
* 个人简介
*/
@Size(max = 500, message = "个人简介不能超过500个字符")
private String bio;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 创建者ID
*/
private Long createBy;
/**
* 更新者ID
*/
private Long updateBy;
/**
* 用户角色列表(一对多关系)
*/
private List<Role> roles;
/**
* 用户详细信息(一对一关系)
*/
private UserProfile profile;
// 构造方法
public User() {}
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
this.status = 1; // 默认正常状态
this.userType = 2; // 默认普通用户
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
// Getter和Setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getUserType() {
return userType;
}
public void setUserType(Integer userType) {
this.userType = userType;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getBio() {
return bio;
}
public void setBio(String bio) {
this.bio = bio;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public Long getCreateBy() {
return createBy;
}
public void setCreateBy(Long createBy) {
this.createBy = createBy;
}
public Long getUpdateBy() {
return updateBy;
}
public void setUpdateBy(Long updateBy) {
this.updateBy = updateBy;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public UserProfile getProfile() {
return profile;
}
public void setProfile(UserProfile profile) {
this.profile = profile;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", realName='" + realName + '\'' +
", age=" + age +
", gender=" + gender +
", status=" + status +
", userType=" + userType +
", createTime=" + createTime +
'}';
}
}
3.1.2 Role实体类
package com.example.mybatisdemo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
/**
* 角色实体类
*/
public class Role {
/**
* 角色ID
*/
private Long id;
/**
* 角色名称
*/
@NotBlank(message = "角色名称不能为空")
@Size(max = 50, message = "角色名称长度不能超过50个字符")
private String roleName;
/**
* 角色编码
*/
@NotBlank(message = "角色编码不能为空")
@Size(max = 20, message = "角色编码长度不能超过20个字符")
private String roleCode;
/**
* 角色描述
*/
@Size(max = 200, message = "角色描述不能超过200个字符")
private String description;
/**
* 状态:1-正常,0-禁用
*/
private Integer status;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 权限列表(多对多关系)
*/
private List<Permission> permissions;
// 构造方法
public Role() {}
public Role(String roleName, String roleCode) {
this.roleName = roleName;
this.roleCode = roleCode;
this.status = 1;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
// Getter和Setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public List<Permission> getPermissions() {
return permissions;
}
public void setPermissions(List<Permission> permissions) {
this.permissions = permissions;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", roleName='" + roleName + '\'' +
", roleCode='" + roleCode + '\'' +
", description='" + description + '\'' +
", status=" + status +
", createTime=" + createTime +
'}';
}
}
3.1.3 Permission实体类
package com.example.mybatisdemo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
/**
* 权限实体类
*/
public class Permission {
/**
* 权限ID
*/
private Long id;
/**
* 权限名称
*/
@NotBlank(message = "权限名称不能为空")
@Size(max = 50, message = "权限名称长度不能超过50个字符")
private String permissionName;
/**
* 权限编码
*/
@NotBlank(message = "权限编码不能为空")
@Size(max = 50, message = "权限编码长度不能超过50个字符")
private String permissionCode;
/**
* 权限类型:1-菜单,2-按钮,3-接口
*/
private Integer permissionType;
/**
* 父权限ID
*/
private Long parentId;
/**
* 权限路径
*/
private String path;
/**
* 权限描述
*/
@Size(max = 200, message = "权限描述不能超过200个字符")
private String description;
/**
* 状态:1-正常,0-禁用
*/
private Integer status;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
// 构造方法
public Permission() {}
public Permission(String permissionName, String permissionCode, Integer permissionType) {
this.permissionName = permissionName;
this.permissionCode = permissionCode;
this.permissionType = permissionType;
this.status = 1;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
// Getter和Setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPermissionName() {
return permissionName;
}
public void setPermissionName(String permissionName) {
this.permissionName = permissionName;
}
public String getPermissionCode() {
return permissionCode;
}
public void setPermissionCode(String permissionCode) {
this.permissionCode = permissionCode;
}
public Integer getPermissionType() {
return permissionType;
}
public void setPermissionType(Integer permissionType) {
this.permissionType = permissionType;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "Permission{" +
"id=" + id +
", permissionName='" + permissionName + '\'' +
", permissionCode='" + permissionCode + '\'' +
", permissionType=" + permissionType +
", status=" + status +
", createTime=" + createTime +
'}';
}
}
3.1.4 UserProfile实体类
package com.example.mybatisdemo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 用户详细信息实体类
*/
public class UserProfile {
/**
* 主键ID
*/
private Long id;
/**
* 用户ID(外键)
*/
private Long userId;
/**
* 昵称
*/
private String nickname;
/**
* 生日
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
/**
* 地址
*/
private String address;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 区县
*/
private String district;
/**
* 邮政编码
*/
private String zipCode;
/**
* 个人网站
*/
private String website;
/**
* QQ号码
*/
private String qq;
/**
* 微信号
*/
private String wechat;
/**
* 微博
*/
private String weibo;
/**
* 职业
*/
private String profession;
/**
* 公司
*/
private String company;
/**
* 学历
*/
private String education;
/**
* 毕业学校
*/
private String school;
/**
* 兴趣爱好
*/
private String hobbies;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
// 构造方法
public UserProfile() {}
public UserProfile(Long userId) {
this.userId = userId;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
// Getter和Setter方法(省略部分,只展示几个关键的)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
// ... 其他getter和setter方法
@Override
public String toString() {
return "UserProfile{" +
"id=" + id +
", userId=" + userId +
", nickname='" + nickname + '\'' +
", birthday=" + birthday +
", address='" + address + '\'' +
", profession='" + profession + '\'' +
", company='" + company + '\'' +
'}';
}
}
3.2 数据库表结构设计
3.2.1 用户表(t_user)
CREATE TABLE `t_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`email` varchar(50) NOT NULL COMMENT '邮箱',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`real_name` varchar(20) DEFAULT NULL COMMENT '真实姓名',
`age` int DEFAULT NULL COMMENT '年龄',
`gender` tinyint DEFAULT '0' COMMENT '性别:1-男,2-女,0-未知',
`status` tinyint DEFAULT '1' COMMENT '状态:1-正常,0-禁用',
`user_type` tinyint DEFAULT '2' COMMENT '用户类型:1-管理员,2-普通用户',
`avatar` varchar(200) DEFAULT NULL COMMENT '头像URL',
`bio` varchar(500) DEFAULT NULL COMMENT '个人简介',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` bigint DEFAULT NULL COMMENT '创建者ID',
`update_by` bigint DEFAULT NULL COMMENT '更新者ID',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_email` (`email`),
KEY `idx_status` (`status`),
KEY `idx_user_type` (`user_type`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
3.2.2 角色表(t_role)
CREATE TABLE `t_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_code` varchar(20) NOT NULL COMMENT '角色编码',
`description` varchar(200) DEFAULT NULL COMMENT '角色描述',
`status` tinyint DEFAULT '1' COMMENT '状态:1-正常,0-禁用',
`sort_order` int DEFAULT '0' COMMENT '排序号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_code` (`role_code`),
KEY `idx_status` (`status`),
KEY `idx_sort_order` (`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
3.2.3 权限表(t_permission)
CREATE TABLE `t_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`permission_name` varchar(50) NOT NULL COMMENT '权限名称',
`permission_code` varchar(50) NOT NULL COMMENT '权限编码',
`permission_type` tinyint NOT NULL COMMENT '权限类型:1-菜单,2-按钮,3-接口',
`parent_id` bigint DEFAULT '0' COMMENT '父权限ID',
`path` varchar(200) DEFAULT NULL COMMENT '权限路径',
`description` varchar(200) DEFAULT NULL COMMENT '权限描述',
`status` tinyint DEFAULT '1' COMMENT '状态:1-正常,0-禁用',
`sort_order` int DEFAULT '0' COMMENT '排序号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_permission_code` (`permission_code`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_permission_type` (`permission_type`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
3.2.4 用户角色关联表(t_user_role)
CREATE TABLE `t_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_role` (`user_id`,`role_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_role_id` (`role_id`),
CONSTRAINT `fk_user_role_user` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_user_role_role` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
3.2.5 角色权限关联表(t_role_permission)
CREATE TABLE `t_role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`permission_id` bigint NOT NULL COMMENT '权限ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_permission` (`role_id`,`permission_id`),
KEY `idx_role_id` (`role_id`),
KEY `idx_permission_id` (`permission_id`),
CONSTRAINT `fk_role_permission_role` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_role_permission_permission` FOREIGN KEY (`permission_id`) REFERENCES `t_permission` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
3.2.6 用户详细信息表(t_user_profile)
CREATE TABLE `t_user_profile` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`birthday` date DEFAULT NULL COMMENT '生日',
`address` varchar(200) DEFAULT NULL COMMENT '地址',
`province` varchar(20) DEFAULT NULL COMMENT '省份',
`city` varchar(20) DEFAULT NULL COMMENT '城市',
`district` varchar(20) DEFAULT NULL COMMENT '区县',
`zip_code` varchar(10) DEFAULT NULL COMMENT '邮政编码',
`website` varchar(100) DEFAULT NULL COMMENT '个人网站',
`qq` varchar(20) DEFAULT NULL COMMENT 'QQ号码',
`wechat` varchar(50) DEFAULT NULL COMMENT '微信号',
`weibo` varchar(50) DEFAULT NULL COMMENT '微博',
`profession` varchar(50) DEFAULT NULL COMMENT '职业',
`company` varchar(100) DEFAULT NULL COMMENT '公司',
`education` varchar(20) DEFAULT NULL COMMENT '学历',
`school` varchar(100) DEFAULT NULL COMMENT '毕业学校',
`hobbies` varchar(200) DEFAULT NULL COMMENT '兴趣爱好',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_id` (`user_id`),
CONSTRAINT `fk_user_profile_user` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户详细信息表';
4. Mapper接口设计
4.1 基础CRUD操作
4.1.1 UserMapper接口
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.entity.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* 用户Mapper接口
*/
@Repository
@Mapper
public interface UserMapper {
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户信息
*/
User selectById(Long id);
/**
* 根据用户名查询用户
* @param username 用户名
* @return 用户信息
*/
User selectByUsername(String username);
/**
* 根据邮箱查询用户
* @param email 邮箱
* @return 用户信息
*/
User selectByEmail(String email);
/**
* 查询所有用户
* @return 用户列表
*/
List<User> selectAll();
/**
* 分页查询用户
* @param offset 偏移量
* @param limit 限制数量
* @return 用户列表
*/
List<User> selectByPage(@Param("offset") int offset, @Param("limit") int limit);
/**
* 根据条件查询用户
* @param conditions 查询条件
* @return 用户列表
*/
List<User> selectByConditions(Map<String, Object> conditions);
/**
* 查询用户总数
* @return 用户总数
*/
long countAll();
/**
* 根据条件查询用户数量
* @param conditions 查询条件
* @return 用户数量
*/
long countByConditions(Map<String, Object> conditions);
/**
* 插入用户
* @param user 用户信息
* @return 影响行数
*/
int insert(User user);
/**
* 批量插入用户
* @param users 用户列表
* @return 影响行数
*/
int insertBatch(List<User> users);
/**
* 更新用户
* @param user 用户信息
* @return 影响行数
*/
int update(User user);
/**
* 选择性更新用户
* @param user 用户信息
* @return 影响行数
*/
int updateSelective(User user);
/**
* 批量更新用户状态
* @param ids 用户ID列表
* @param status 状态
* @return 影响行数
*/
int updateStatusBatch(@Param("ids") List<Long> ids, @Param("status") Integer status);
/**
* 根据ID删除用户
* @param id 用户ID
* @return 影响行数
*/
int deleteById(Long id);
/**
* 批量删除用户
* @param ids 用户ID列表
* @return 影响行数
*/
int deleteBatch(List<Long> ids);
/**
* 逻辑删除用户(更新状态为禁用)
* @param id 用户ID
* @return 影响行数
*/
int logicDeleteById(Long id);
/**
* 查询用户及其角色信息
* @param id 用户ID
* @return 用户信息(包含角色)
*/
User selectWithRoles(Long id);
/**
* 查询用户及其详细信息
* @param id 用户ID
* @return 用户信息(包含详细信息)
*/
User selectWithProfile(Long id);
/**
* 查询用户完整信息(包含角色和详细信息)
* @param id 用户ID
* @return 用户完整信息
*/
User selectFullInfo(Long id);
/**
* 根据角色ID查询用户列表
* @param roleId 角色ID
* @return 用户列表
*/
List<User> selectByRoleId(Long roleId);
/**
* 模糊查询用户
* @param keyword 关键词
* @return 用户列表
*/
List<User> selectByKeyword(String keyword);
/**
* 根据年龄范围查询用户
* @param minAge 最小年龄
* @param maxAge 最大年龄
* @return 用户列表
*/
List<User> selectByAgeRange(@Param("minAge") Integer minAge, @Param("maxAge") Integer maxAge);
/**
* 查询指定日期后注册的用户
* @param date 日期
* @return 用户列表
*/
List<User> selectByCreateTimeAfter(String date);
/**
* 检查用户名是否存在
* @param username 用户名
* @return 是否存在
*/
@Select("SELECT COUNT(1) FROM t_user WHERE username = #{username}")
boolean existsByUsername(String username);
/**
* 检查邮箱是否存在
* @param email 邮箱
* @return 是否存在
*/
@Select("SELECT COUNT(1) FROM t_user WHERE email = #{email}")
boolean existsByEmail(String email);
/**
* 使用注解方式查询用户基本信息
* @param id 用户ID
* @return 用户信息
*/
@Select("SELECT id, username, email, phone, real_name, age, gender, status, user_type, " +
"avatar, bio, create_time, update_time FROM t_user WHERE id = #{id}")
@Results(id = "userResultMap", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(property = "email", column = "email"),
@Result(property = "phone", column = "phone"),
@Result(property = "realName", column = "real_name"),
@Result(property = "age", column = "age"),
@Result(property = "gender", column = "gender"),
@Result(property = "status", column = "status"),
@Result(property = "userType", column = "user_type"),
@Result(property = "avatar", column = "avatar"),
@Result(property = "bio", column = "bio"),
@Result(property = "createTime", column = "create_time"),
@Result(property = "updateTime", column = "update_time")
})
User selectByIdWithAnnotation(Long id);
/**
* 使用注解方式插入用户
* @param user 用户信息
* @return 影响行数
*/
@Insert("INSERT INTO t_user(username, password, email, phone, real_name, age, gender, " +
"status, user_type, avatar, bio, create_time, update_time) " +
"VALUES(#{username}, #{password}, #{email}, #{phone}, #{realName}, #{age}, " +
"#{gender}, #{status}, #{userType}, #{avatar}, #{bio}, #{createTime}, #{updateTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertWithAnnotation(User user);
/**
* 使用注解方式更新用户
* @param user 用户信息
* @return 影响行数
*/
@Update("UPDATE t_user SET username = #{username}, email = #{email}, phone = #{phone}, " +
"real_name = #{realName}, age = #{age}, gender = #{gender}, status = #{status}, " +
"user_type = #{userType}, avatar = #{avatar}, bio = #{bio}, update_time = #{updateTime} " +
"WHERE id = #{id}")
int updateWithAnnotation(User user);
/**
* 使用注解方式删除用户
* @param id 用户ID
* @return 影响行数
*/
@Delete("DELETE FROM t_user WHERE id = #{id}")
int deleteByIdWithAnnotation(Long id);
}
4.1.2 RoleMapper接口
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* 角色Mapper接口
*/
@Repository
@Mapper
public interface RoleMapper {
/**
* 根据ID查询角色
* @param id 角色ID
* @return 角色信息
*/
Role selectById(Long id);
/**
* 根据角色编码查询角色
* @param roleCode 角色编码
* @return 角色信息
*/
Role selectByRoleCode(String roleCode);
/**
* 查询所有角色
* @return 角色列表
*/
List<Role> selectAll();
/**
* 根据状态查询角色
* @param status 状态
* @return 角色列表
*/
List<Role> selectByStatus(Integer status);
/**
* 根据条件查询角色
* @param conditions 查询条件
* @return 角色列表
*/
List<Role> selectByConditions(Map<String, Object> conditions);
/**
* 查询角色总数
* @return 角色总数
*/
long countAll();
/**
* 插入角色
* @param role 角色信息
* @return 影响行数
*/
int insert(Role role);
/**
* 更新角色
* @param role 角色信息
* @return 影响行数
*/
int update(Role role);
/**
* 选择性更新角色
* @param role 角色信息
* @return 影响行数
*/
int updateSelective(Role role);
/**
* 根据ID删除角色
* @param id 角色ID
* @return 影响行数
*/
int deleteById(Long id);
/**
* 批量删除角色
* @param ids 角色ID列表
* @return 影响行数
*/
int deleteBatch(List<Long> ids);
/**
* 查询角色及其权限信息
* @param id 角色ID
* @return 角色信息(包含权限)
*/
Role selectWithPermissions(Long id);
/**
* 根据用户ID查询角色列表
* @param userId 用户ID
* @return 角色列表
*/
List<Role> selectByUserId(Long userId);
/**
* 检查角色编码是否存在
* @param roleCode 角色编码
* @return 是否存在
*/
boolean existsByRoleCode(String roleCode);
}
4.1.3 PermissionMapper接口
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.entity.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* 权限Mapper接口
*/
@Repository
@Mapper
public interface PermissionMapper {
/**
* 根据ID查询权限
* @param id 权限ID
* @return 权限信息
*/
Permission selectById(Long id);
/**
* 根据权限编码查询权限
* @param permissionCode 权限编码
* @return 权限信息
*/
Permission selectByPermissionCode(String permissionCode);
/**
* 查询所有权限
* @return 权限列表
*/
List<Permission> selectAll();
/**
* 根据权限类型查询权限
* @param permissionType 权限类型
* @return 权限列表
*/
List<Permission> selectByType(Integer permissionType);
/**
* 根据父权限ID查询子权限
* @param parentId 父权限ID
* @return 权限列表
*/
List<Permission> selectByParentId(Long parentId);
/**
* 查询顶级权限(父权限ID为0或null)
* @return 权限列表
*/
List<Permission> selectTopLevel();
/**
* 根据条件查询权限
* @param conditions 查询条件
* @return 权限列表
*/
List<Permission> selectByConditions(Map<String, Object> conditions);
/**
* 查询权限总数
* @return 权限总数
*/
long countAll();
/**
* 插入权限
* @param permission 权限信息
* @return 影响行数
*/
int insert(Permission permission);
/**
* 更新权限
* @param permission 权限信息
* @return 影响行数
*/
int update(Permission permission);
/**
* 选择性更新权限
* @param permission 权限信息
* @return 影响行数
*/
int updateSelective(Permission permission);
/**
* 根据ID删除权限
* @param id 权限ID
* @return 影响行数
*/
int deleteById(Long id);
/**
* 批量删除权限
* @param ids 权限ID列表
* @return 影响行数
*/
int deleteBatch(List<Long> ids);
/**
* 根据角色ID查询权限列表
* @param roleId 角色ID
* @return 权限列表
*/
List<Permission> selectByRoleId(Long roleId);
/**
* 根据用户ID查询权限列表
* @param userId 用户ID
* @return 权限列表
*/
List<Permission> selectByUserId(Long userId);
/**
* 检查权限编码是否存在
* @param permissionCode 权限编码
* @return 是否存在
*/
boolean existsByPermissionCode(String permissionCode);
}
4.1.4 UserRoleMapper接口
package com.example.mybatisdemo.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 用户角色关联Mapper接口
*/
@Repository
@Mapper
public interface UserRoleMapper {
/**
* 为用户分配角色
* @param userId 用户ID
* @param roleId 角色ID
* @return 影响行数
*/
int insert(@Param("userId") Long userId, @Param("roleId") Long roleId);
/**
* 批量为用户分配角色
* @param userId 用户ID
* @param roleIds 角色ID列表
* @return 影响行数
*/
int insertBatch(@Param("userId") Long userId, @Param("roleIds") List<Long> roleIds);
/**
* 删除用户角色关联
* @param userId 用户ID
* @param roleId 角色ID
* @return 影响行数
*/
int delete(@Param("userId") Long userId, @Param("roleId") Long roleId);
/**
* 删除用户的所有角色
* @param userId 用户ID
* @return 影响行数
*/
int deleteByUserId(Long userId);
/**
* 删除角色的所有用户关联
* @param roleId 角色ID
* @return 影响行数
*/
int deleteByRoleId(Long roleId);
/**
* 查询用户的角色ID列表
* @param userId 用户ID
* @return 角色ID列表
*/
List<Long> selectRoleIdsByUserId(Long userId);
/**
* 查询角色的用户ID列表
* @param roleId 角色ID
* @return 用户ID列表
*/
List<Long> selectUserIdsByRoleId(Long roleId);
/**
* 检查用户是否拥有指定角色
* @param userId 用户ID
* @param roleId 角色ID
* @return 是否存在
*/
boolean exists(@Param("userId") Long userId, @Param("roleId") Long roleId);
/**
* 统计用户的角色数量
* @param userId 用户ID
* @return 角色数量
*/
int countByUserId(Long userId);
/**
* 统计角色的用户数量
* @param roleId 角色ID
* @return 用户数量
*/
int countByRoleId(Long roleId);
}
4.1.5 UserProfileMapper接口
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.entity.UserProfile;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
* 用户详细信息Mapper接口
*/
@Repository
@Mapper
public interface UserProfileMapper {
/**
* 根据ID查询用户详细信息
* @param id 主键ID
* @return 用户详细信息
*/
UserProfile selectById(Long id);
/**
* 根据用户ID查询用户详细信息
* @param userId 用户ID
* @return 用户详细信息
*/
UserProfile selectByUserId(Long userId);
/**
* 插入用户详细信息
* @param userProfile 用户详细信息
* @return 影响行数
*/
int insert(UserProfile userProfile);
/**
* 更新用户详细信息
* @param userProfile 用户详细信息
* @return 影响行数
*/
int update(UserProfile userProfile);
/**
* 选择性更新用户详细信息
* @param userProfile 用户详细信息
* @return 影响行数
*/
int updateSelective(UserProfile userProfile);
/**
* 根据ID删除用户详细信息
* @param id 主键ID
* @return 影响行数
*/
int deleteById(Long id);
/**
* 根据用户ID删除用户详细信息
* @param userId 用户ID
* @return 影响行数
*/
int deleteByUserId(Long userId);
/**
* 检查用户是否已有详细信息
* @param userId 用户ID
* @return 是否存在
*/
boolean existsByUserId(Long userId);
}
5. XML映射文件详解
5.1 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.example.mybatisdemo.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="User">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<result column="email" property="email" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR"/>
<result column="real_name" property="realName" jdbcType="VARCHAR"/>
<result column="age" property="age" jdbcType="INTEGER"/>
<result column="gender" property="gender" jdbcType="TINYINT"/>
<result column="status" property="status" jdbcType="TINYINT"/>
<result column="user_type" property="userType" jdbcType="TINYINT"/>
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
<result column="bio" property="bio" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="create_by" property="createBy" jdbcType="BIGINT"/>
<result column="update_by" property="updateBy" jdbcType="BIGINT"/>
</resultMap>
<!-- 用户与角色关联结果映射 -->
<resultMap id="UserWithRolesResultMap" type="User" extends="BaseResultMap">
<collection property="roles" ofType="Role"
select="com.example.mybatisdemo.mapper.RoleMapper.selectByUserId"
column="id" fetchType="lazy"/>
</resultMap>
<!-- 用户与详细信息关联结果映射 -->
<resultMap id="UserWithProfileResultMap" type="User" extends="BaseResultMap">
<association property="profile" javaType="UserProfile"
select="com.example.mybatisdemo.mapper.UserProfileMapper.selectByUserId"
column="id" fetchType="lazy"/>
</resultMap>
<!-- 用户完整信息结果映射 -->
<resultMap id="UserFullInfoResultMap" type="User" extends="BaseResultMap">
<collection property="roles" ofType="Role"
select="com.example.mybatisdemo.mapper.RoleMapper.selectByUserId"
column="id" fetchType="lazy"/>
<association property="profile" javaType="UserProfile"
select="com.example.mybatisdemo.mapper.UserProfileMapper.selectByUserId"
column="id" fetchType="lazy"/>
</resultMap>
<!-- 基础列名 -->
<sql id="Base_Column_List">
id, username, password, email, phone, real_name, age, gender,
status, user_type, avatar, bio, create_time, update_time, create_by, update_by
</sql>
<!-- 查询条件 -->
<sql id="Base_Where_Clause">
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="phone != null and phone != ''">
AND phone = #{phone}
</if>
<if test="realName != null and realName != ''">
AND real_name LIKE CONCAT('%', #{realName}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="gender != null">
AND gender = #{gender}
</if>
<if test="status != null">
AND status = #{status}
</if>
<if test="userType != null">
AND user_type = #{userType}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="createTimeStart != null">
AND create_time >= #{createTimeStart}
</if>
<if test="createTimeEnd != null">
AND create_time <= #{createTimeEnd}
</if>
<if test="keyword != null and keyword != ''">
AND (username LIKE CONCAT('%', #{keyword}, '%')
OR real_name LIKE CONCAT('%', #{keyword}, '%')
OR email LIKE CONCAT('%', #{keyword}, '%')
OR phone LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
</sql>
<!-- 根据ID查询用户 -->
<select id="selectById" parameterType="Long" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE id = #{id}
</select>
<!-- 根据用户名查询用户 -->
<select id="selectByUsername" parameterType="String" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE username = #{username}
</select>
<!-- 根据邮箱查询用户 -->
<select id="selectByEmail" parameterType="String" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE email = #{email}
</select>
<!-- 查询所有用户 -->
<select id="selectAll" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
ORDER BY create_time DESC
</select>
<!-- 分页查询用户 -->
<select id="selectByPage" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
<!-- 根据条件查询用户 -->
<select id="selectByConditions" parameterType="Map" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
<include refid="Base_Where_Clause"/>
ORDER BY create_time DESC
</select>
<!-- 查询用户总数 -->
<select id="countAll" resultType="long">
SELECT COUNT(1) FROM t_user
</select>
<!-- 根据条件查询用户数量 -->
<select id="countByConditions" parameterType="Map" resultType="long">
SELECT COUNT(1) FROM t_user
<include refid="Base_Where_Clause"/>
</select>
<!-- 插入用户 -->
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user (
username, password, email, phone, real_name, age, gender,
status, user_type, avatar, bio, create_time, update_time, create_by, update_by
) VALUES (
#{username}, #{password}, #{email}, #{phone}, #{realName}, #{age}, #{gender},
#{status}, #{userType}, #{avatar}, #{bio}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}
)
</insert>
<!-- 批量插入用户 -->
<insert id="insertBatch" parameterType="List" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user (
username, password, email, phone, real_name, age, gender,
status, user_type, avatar, bio, create_time, update_time, create_by, update_by
) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.password}, #{user.email}, #{user.phone}, #{user.realName},
#{user.age}, #{user.gender}, #{user.status}, #{user.userType}, #{user.avatar},
#{user.bio}, #{user.createTime}, #{user.updateTime}, #{user.createBy}, #{user.updateBy})
</foreach>
</insert>
<!-- 更新用户 -->
<update id="update" parameterType="User">
UPDATE t_user SET
username = #{username},
password = #{password},
email = #{email},
phone = #{phone},
real_name = #{realName},
age = #{age},
gender = #{gender},
status = #{status},
user_type = #{userType},
avatar = #{avatar},
bio = #{bio},
update_time = #{updateTime},
update_by = #{updateBy}
WHERE id = #{id}
</update>
<!-- 选择性更新用户 -->
<update id="updateSelective" parameterType="User">
UPDATE t_user
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="phone != null and phone != ''">
phone = #{phone},
</if>
<if test="realName != null and realName != ''">
real_name = #{realName},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="userType != null">
user_type = #{userType},
</if>
<if test="avatar != null">
avatar = #{avatar},
</if>
<if test="bio != null">
bio = #{bio},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="updateBy != null">
update_by = #{updateBy},
</if>
</set>
WHERE id = #{id}
</update>
<!-- 批量更新用户状态 -->
<update id="updateStatusBatch">
UPDATE t_user SET status = #{status}, update_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 根据ID删除用户 -->
<delete id="deleteById" parameterType="Long">
DELETE FROM t_user WHERE id = #{id}
</delete>
<!-- 批量删除用户 -->
<delete id="deleteBatch" parameterType="List">
DELETE FROM t_user WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 逻辑删除用户 -->
<update id="logicDeleteById" parameterType="Long">
UPDATE t_user SET status = 0, update_time = NOW() WHERE id = #{id}
</update>
<!-- 查询用户及其角色信息 -->
<select id="selectWithRoles" parameterType="Long" resultMap="UserWithRolesResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE id = #{id}
</select>
<!-- 查询用户及其详细信息 -->
<select id="selectWithProfile" parameterType="Long" resultMap="UserWithProfileResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE id = #{id}
</select>
<!-- 查询用户完整信息 -->
<select id="selectFullInfo" parameterType="Long" resultMap="UserFullInfoResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE id = #{id}
</select>
<!-- 根据角色ID查询用户列表 -->
<select id="selectByRoleId" parameterType="Long" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user u
INNER JOIN t_user_role ur ON u.id = ur.user_id
WHERE ur.role_id = #{roleId}
ORDER BY u.create_time DESC
</select>
<!-- 模糊查询用户 -->
<select id="selectByKeyword" parameterType="String" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE username LIKE CONCAT('%', #{keyword}, '%')
OR real_name LIKE CONCAT('%', #{keyword}, '%')
OR email LIKE CONCAT('%', #{keyword}, '%')
OR phone LIKE CONCAT('%', #{keyword}, '%')
ORDER BY create_time DESC
</select>
<!-- 根据年龄范围查询用户 -->
<select id="selectByAgeRange" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE age BETWEEN #{minAge} AND #{maxAge}
ORDER BY age ASC, create_time DESC
</select>
<!-- 查询指定日期后注册的用户 -->
<select id="selectByCreateTimeAfter" parameterType="String" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_user
WHERE create_time >= #{date}
ORDER BY create_time DESC
</select>
</mapper>
5.2 RoleMapper.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.example.mybatisdemo.mapper.RoleMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="Role">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="role_name" property="roleName" jdbcType="VARCHAR"/>
<result column="role_code" property="roleCode" jdbcType="VARCHAR"/>
<result column="description" property="description" jdbcType="VARCHAR"/>
<result column="status" property="status" jdbcType="TINYINT"/>
<result column="sort_order" property="sortOrder" jdbcType="INTEGER"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- 角色与权限关联结果映射 -->
<resultMap id="RoleWithPermissionsResultMap" type="Role" extends="BaseResultMap">
<collection property="permissions" ofType="Permission"
select="com.example.mybatisdemo.mapper.PermissionMapper.selectByRoleId"
column="id" fetchType="lazy"/>
</resultMap>
<!-- 基础列名 -->
<sql id="Base_Column_List">
id, role_name, role_code, description, status, sort_order, create_time, update_time
</sql>
<!-- 查询条件 -->
<sql id="Base_Where_Clause">
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="roleName != null and roleName != ''">
AND role_name LIKE CONCAT('%', #{roleName}, '%')
</if>
<if test="roleCode != null and roleCode != ''">
AND role_code = #{roleCode}
</if>
<if test="status != null">
AND status = #{status}
</if>
<if test="keyword != null and keyword != ''">
AND (role_name LIKE CONCAT('%', #{keyword}, '%')
OR role_code LIKE CONCAT('%', #{keyword}, '%')
OR description LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
</sql>
<!-- 根据ID查询角色 -->
<select id="selectById" parameterType="Long" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_role
WHERE id = #{id}
</select>
<!-- 根据角色编码查询角色 -->
<select id="selectByRoleCode" parameterType="String" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_role
WHERE role_code = #{roleCode}
</select>
<!-- 查询所有角色 -->
<select id="selectAll" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_role
ORDER BY sort_order ASC, create_time DESC
</select>
<!-- 根据状态查询角色 -->
<select id="selectByStatus" parameterType="Integer" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_role
WHERE status = #{status}
ORDER BY sort_order ASC, create_time DESC
</select>
<!-- 根据条件查询角色 -->
<select id="selectByConditions" parameterType="Map" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_role
<include refid="Base_Where_Clause"/>
ORDER BY sort_order ASC, create_time DESC
</select>
<!-- 查询角色总数 -->
<select id="countAll" resultType="long">
SELECT COUNT(1) FROM t_role
</select>
<!-- 插入角色 -->
<insert id="insert" parameterType="Role" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_role (
role_name, role_code, description, status, sort_order, create_time, update_time
) VALUES (
#{roleName}, #{roleCode}, #{description}, #{status}, #{sortOrder}, #{createTime}, #{updateTime}
)
</insert>
<!-- 更新角色 -->
<update id="update" parameterType="Role">
UPDATE t_role SET
role_name = #{roleName},
role_code = #{roleCode},
description = #{description},
status = #{status},
sort_order = #{sortOrder},
update_time = #{updateTime}
WHERE id = #{id}
</update>
<!-- 选择性更新角色 -->
<update id="updateSelective" parameterType="Role">
UPDATE t_role
<set>
<if test="roleName != null and roleName != ''">
role_name = #{roleName},
</if>
<if test="roleCode != null and roleCode != ''">
role_code = #{roleCode},
</if>
<if test="description != null">
description = #{description},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="sortOrder != null">
sort_order = #{sortOrder},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
</set>
WHERE id = #{id}
</update>
<!-- 根据ID删除角色 -->
<delete id="deleteById" parameterType="Long">
DELETE FROM t_role WHERE id = #{id}
</delete>
<!-- 批量删除角色 -->
<delete id="deleteBatch" parameterType="List">
DELETE FROM t_role WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 查询角色及其权限信息 -->
<select id="selectWithPermissions" parameterType="Long" resultMap="RoleWithPermissionsResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_role
WHERE id = #{id}
</select>
<!-- 根据用户ID查询角色列表 -->
<select id="selectByUserId" parameterType="Long" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_role r
INNER JOIN t_user_role ur ON r.id = ur.role_id
WHERE ur.user_id = #{userId}
ORDER BY r.sort_order ASC, r.create_time DESC
</select>
<!-- 检查角色编码是否存在 -->
<select id="existsByRoleCode" parameterType="String" resultType="boolean">
SELECT COUNT(1) > 0 FROM t_role WHERE role_code = #{roleCode}
</select>
</mapper>
5.3 动态SQL详解
5.3.1 if标签使用
<!-- 动态查询条件示例 -->
<select id="selectByConditions" parameterType="Map" resultMap="BaseResultMap">
SELECT * FROM t_user
<where>
<!-- if标签:条件判断 -->
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
<!-- 数值类型判断 -->
<if test="age != null and age > 0">
AND age = #{age}
</if>
<!-- 日期范围查询 -->
<if test="startDate != null">
AND create_time >= #{startDate}
</if>
<if test="endDate != null">
AND create_time <= #{endDate}
</if>
<!-- 列表判断 -->
<if test="userIds != null and userIds.size() > 0">
AND id IN
<foreach collection="userIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
ORDER BY create_time DESC
</select>
5.3.2 choose、when、otherwise标签使用
<!-- 多条件选择示例 -->
<select id="selectByType" parameterType="Map" resultMap="BaseResultMap">
SELECT * FROM t_user
<where>
<choose>
<when test="type == 'admin'">
AND user_type = 1
</when>
<when test="type == 'user'">
AND user_type = 2
</when>
<when test="type == 'guest'">
AND user_type = 3
</when>
<otherwise>
AND status = 1
</otherwise>
</choose>
<!-- 排序方式选择 -->
<choose>
<when test="orderBy == 'username'">
ORDER BY username ASC
</when>
<when test="orderBy == 'createTime'">
ORDER BY create_time DESC
</when>
<otherwise>
ORDER BY id DESC
</otherwise>
</choose>
</where>
</select>
5.3.3 foreach标签详解
<!-- 批量操作示例 -->
<insert id="insertBatch" parameterType="List">
INSERT INTO t_user (username, email, status) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.email}, #{user.status})
</foreach>
</insert>
<!-- IN条件查询 -->
<select id="selectByIds" parameterType="List" resultMap="BaseResultMap">
SELECT * FROM t_user
WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量更新示例 -->
<update id="updateBatch" parameterType="List">
<foreach collection="list" item="user" separator=";">
UPDATE t_user
SET username = #{user.username}, email = #{user.email}
WHERE id = #{user.id}
</foreach>
</update>
<!-- 复杂的foreach使用 -->
<select id="selectByMultipleConditions" parameterType="Map" resultMap="BaseResultMap">
SELECT * FROM t_user
<where>
<if test="conditions != null and conditions.size() > 0">
<foreach collection="conditions" item="condition" separator=" OR ">
(
<if test="condition.username != null">
username = #{condition.username}
</if>
<if test="condition.email != null">
<if test="condition.username != null"> AND </if>
email = #{condition.email}
</if>
)
</foreach>
</if>
</where>
</select>
5.3.4 set和trim标签使用
<!-- set标签:动态更新 -->
<update id="updateSelective" parameterType="User">
UPDATE t_user
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
</set>
WHERE id = #{id}
</update>
<!-- trim标签:更灵活的动态SQL -->
<select id="selectByTrimConditions" parameterType="Map" resultMap="BaseResultMap">
SELECT * FROM t_user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</trim>
</select>
<!-- trim在INSERT中的使用 -->
<insert id="insertSelective" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">username,</if>
<if test="email != null">email,</if>
<if test="password != null">password,</if>
<if test="status != null">status,</if>
<if test="createTime != null">create_time,</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="username != null">#{username},</if>
<if test="email != null">#{email},</if>
<if test="password != null">#{password},</if>
<if test="status != null">#{status},</if>
<if test="createTime != null">#{createTime},</if>
</trim>
</insert>
5.3.5 bind标签使用
<!-- bind标签:创建变量 -->
<select id="selectByLikeName" parameterType="String" resultMap="BaseResultMap">
<bind name="pattern" value="'%' + _parameter + '%'" />
SELECT * FROM t_user
WHERE username LIKE #{pattern}
</select>
<!-- 复杂的bind使用 -->
<select id="selectByComplexConditions" parameterType="Map" resultMap="BaseResultMap">
<bind name="usernamePattern" value="username != null ? '%' + username + '%' : null" />
<bind name="emailPattern" value="email != null ? '%' + email + '%' : null" />
SELECT * FROM t_user
<where>
<if test="usernamePattern != null">
AND username LIKE #{usernamePattern}
</if>
<if test="emailPattern != null">
AND email LIKE #{emailPattern}
</if>
</where>
</select>
</code_block_to_apply_changes_from>
6. MyBatis高级特性
6.1 关联查询和结果映射
6.1.1 一对一关联(association)
<!-- 用户与用户详情一对一关联 -->
<resultMap id="UserWithProfileResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<!-- 一对一关联 -->
<association property="profile" javaType="UserProfile">
<id column="profile_id" property="id"/>
<result column="nickname" property="nickname"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
</association>
</resultMap>
<!-- 嵌套查询方式 -->
<resultMap id="UserWithProfileNestedResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<association property="profile" javaType="UserProfile"
select="selectUserProfileByUserId" column="id"/>
</resultMap>
<!-- 联合查询 -->
<select id="selectUserWithProfile" resultMap="UserWithProfileResultMap">
SELECT u.id, u.username, u.email,
p.id as profile_id, p.nickname, p.birthday, p.address
FROM t_user u
LEFT JOIN t_user_profile p ON u.id = p.user_id
WHERE u.id = #{id}
</select>
6.1.2 一对多关联(collection)
<!-- 用户与角色一对多关联 -->
<resultMap id="UserWithRolesResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<!-- 一对多关联 -->
<collection property="roles" ofType="Role">
<id column="role_id" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_code" property="roleCode"/>
</collection>
</resultMap>
<!-- 联合查询 -->
<select id="selectUserWithRoles" resultMap="UserWithRolesResultMap">
SELECT u.id, u.username, u.email,
r.id as role_id, r.role_name, r.role_code
FROM t_user u
LEFT JOIN t_user_role ur ON u.id = ur.user_id
LEFT JOIN t_role r ON ur.role_id = r.id
WHERE u.id = #{id}
</select>
6.1.3 多对多关联
<!-- 角色与权限多对多关联 -->
<resultMap id="RoleWithPermissionsResultMap" type="Role">
<id column="id" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_code" property="roleCode"/>
<collection property="permissions" ofType="Permission">
<id column="permission_id" property="id"/>
<result column="permission_name" property="permissionName"/>
<result column="permission_code" property="permissionCode"/>
</collection>
</resultMap>
<select id="selectRoleWithPermissions" resultMap="RoleWithPermissionsResultMap">
SELECT r.id, r.role_name, r.role_code,
p.id as permission_id, p.permission_name, p.permission_code
FROM t_role r
LEFT JOIN t_role_permission rp ON r.id = rp.role_id
LEFT JOIN t_permission p ON rp.permission_id = p.id
WHERE r.id = #{id}
</select>
6.2 缓存机制
6.2.1 一级缓存(本地缓存)
// 一级缓存示例
@Test
public void testFirstLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询,从数据库查询
User user1 = mapper.selectById(1L);
System.out.println("第一次查询:" + user1);
// 第二次查询,从一级缓存查询
User user2 = mapper.selectById(1L);
System.out.println("第二次查询:" + user2);
System.out.println("两次查询结果是否相同:" + (user1 == user2)); // true
} finally {
sqlSession.close();
}
}
6.2.2 二级缓存配置
<!-- 在Mapper.xml中启用二级缓存 -->
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- 或者使用Redis作为二级缓存 -->
<cache type="org.mybatis.caches.redis.RedisCache"
eviction="LRU"
flushInterval="60000"/>
// 在实体类中实现Serializable接口
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// ... 其他属性
}
6.2.3 缓存配置说明
<!-- 缓存配置参数详解 -->
<cache
eviction="LRU" <!-- 缓存回收策略:LRU、FIFO、SOFT、WEAK -->
flushInterval="60000" <!-- 缓存刷新间隔(毫秒) -->
size="512" <!-- 缓存大小 -->
readOnly="false" <!-- 是否只读 -->
blocking="false"/> <!-- 是否阻塞 -->
<!-- 针对特定查询禁用缓存 -->
<select id="selectById" useCache="false" resultMap="BaseResultMap">
SELECT * FROM t_user WHERE id = #{id}
</select>
<!-- 针对特定操作清空缓存 -->
<insert id="insert" flushCache="true" parameterType="User">
INSERT INTO t_user ...
</insert>
6.3 分页插件详解
6.3.1 PageHelper配置
# application.yml中的配置
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
return-page-info: check
page-size-zero: false
auto-runtime-dialect: false
6.3.2 分页使用示例
// 服务层分页查询
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 分页查询用户
*/
public PageInfo<User> findUsers(int pageNum, int pageSize, String keyword) {
// 设置分页参数
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<User> users;
if (keyword != null && !keyword.trim().isEmpty()) {
users = userMapper.selectByKeyword(keyword);
} else {
users = userMapper.selectAll();
}
// 封装分页信息
return new PageInfo<>(users);
}
/**
* 带排序的分页查询
*/
public PageInfo<User> findUsersWithSort(int pageNum, int pageSize, String sortField, String sortOrder) {
// 构建排序字符串
String orderBy = sortField + " " + sortOrder;
PageHelper.startPage(pageNum, pageSize, orderBy);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
/**
* 自定义分页查询
*/
public PageInfo<User> findUsersByConditions(UserQueryVO queryVO) {
PageHelper.startPage(queryVO.getPageNum(), queryVO.getPageSize());
Map<String, Object> conditions = new HashMap<>();
conditions.put("username", queryVO.getUsername());
conditions.put("email", queryVO.getEmail());
conditions.put("status", queryVO.getStatus());
conditions.put("minAge", queryVO.getMinAge());
conditions.put("maxAge", queryVO.getMaxAge());
List<User> users = userMapper.selectByConditions(conditions);
PageInfo<User> pageInfo = new PageInfo<>(users);
// 设置额外信息
pageInfo.setNavigatePages(8); // 导航页码数
return pageInfo;
}
}
6.3.3 控制器层分页处理
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 分页查询用户
*/
@GetMapping
public ResponseEntity<Map<String, Object>> getUsers(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "createTime") String sortField,
@RequestParam(defaultValue = "desc") String sortOrder) {
try {
PageInfo<User> pageInfo;
if (keyword != null && !keyword.trim().isEmpty()) {
pageInfo = userService.findUsers(pageNum, pageSize, keyword);
} else {
pageInfo = userService.findUsersWithSort(pageNum, pageSize, sortField, sortOrder);
}
Map<String, Object> response = new HashMap<>();
response.put("users", pageInfo.getList());
response.put("total", pageInfo.getTotal());
response.put("pageNum", pageInfo.getPageNum());
response.put("pageSize", pageInfo.getPageSize());
response.put("pages", pageInfo.getPages());
response.put("hasNextPage", pageInfo.isHasNextPage());
response.put("hasPreviousPage", pageInfo.isHasPreviousPage());
response.put("navigatePages", pageInfo.getNavigatePages());
response.put("navigatepageNums", pageInfo.getNavigatepageNums());
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", "查询失败:" + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
}
6.4 类型处理器(TypeHandler)
6.4.1 自定义JSON类型处理器
// JSON类型处理器
@Component
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private Class<T> type;
public JsonTypeHandler(Class<T> type) {
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
try {
ps.setString(i, objectMapper.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("Error converting object to JSON", e);
}
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json);
}
private T parseJson(String json) throws SQLException {
if (json == null || json.trim().isEmpty()) {
return null;
}
try {
return objectMapper.readValue(json, type);
} catch (JsonProcessingException e) {
throw new SQLException("Error parsing JSON", e);
}
}
}
6.4.2 枚举类型处理器
// 枚举类
public enum UserStatus {
ACTIVE(1, "正常"),
INACTIVE(0, "禁用"),
PENDING(2, "待审核");
private final int code;
private final String description;
UserStatus(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() { return code; }
public String getDescription() { return description; }
public static UserStatus fromCode(int code) {
for (UserStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("Unknown status code: " + code);
}
}
// 枚举类型处理器
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
int code = rs.getInt(columnName);
return rs.wasNull() ? null : UserStatus.fromCode(code);
}
@Override
public UserStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int code = rs.getInt(columnIndex);
return rs.wasNull() ? null : UserStatus.fromCode(code);
}
@Override
public UserStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int code = cs.getInt(columnIndex);
return cs.wasNull() ? null : UserStatus.fromCode(code);
}
}
6.4.3 在MyBatis中注册类型处理器
<!-- mybatis-config.xml中注册 -->
<typeHandlers>
<typeHandler handler="com.example.mybatisdemo.typehandler.JsonTypeHandler"/>
<typeHandler handler="com.example.mybatisdemo.typehandler.UserStatusTypeHandler"
javaType="com.example.mybatisdemo.enums.UserStatus"/>
</typeHandlers>
6.5 MyBatis拦截器(插件)
6.5.1 自定义分页拦截器
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
})
})
public class PaginationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
// 如果需要分页
if (rowBounds != null && rowBounds != RowBounds.DEFAULT) {
// 获取原始SQL
BoundSql boundSql = ms.getBoundSql(parameter);
String originalSql = boundSql.getSql();
// 构建分页SQL
String pagingSql = originalSql + " LIMIT " + rowBounds.getOffset() + ", " + rowBounds.getLimit();
// 创建新的BoundSql
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), pagingSql,
boundSql.getParameterMappings(), parameter);
// 创建新的MappedStatement
MappedStatement newMs = copyMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
args[0] = newMs;
args[2] = RowBounds.DEFAULT;
}
return invocation.proceed();
}
private MappedStatement copyMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(),
ms.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
public static class BoundSqlSqlSource implements SqlSource {
private BoundSql boundSql;
public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
}
6.5.2 SQL执行时间监控拦截器
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
})
})
public class SqlTimeInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlTimeInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long endTime = System.currentTimeMillis();
long sqlTime = endTime - startTime;
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
String sqlId = mappedStatement.getId();
logger.info("SQL执行时间统计 - {}:{}ms", sqlId, sqlTime);
if (sqlTime > 1000) {
logger.warn("慢SQL警告 - {}:{}ms", sqlId, sqlTime);
}
}
}
}
6.6 常见问题解决
6.6.1 N+1查询问题
// 问题:N+1查询
// 查询用户时,每个用户都会触发一次角色查询
public List<User> getBadUsers() {
List<User> users = userMapper.selectAll(); // 1次查询
for (User user : users) {
List<Role> roles = roleMapper.selectByUserId(user.getId()); // N次查询
user.setRoles(roles);
}
return users;
}
// 解决方案1:使用联合查询
<select id="selectUsersWithRoles" resultMap="UserWithRolesResultMap">
SELECT u.*, r.id as role_id, r.role_name, r.role_code
FROM t_user u
LEFT JOIN t_user_role ur ON u.id = ur.user_id
LEFT JOIN t_role r ON ur.role_id = r.id
</select>
// 解决方案2:分批查询
public List<User> getGoodUsers() {
List<User> users = userMapper.selectAll();
if (!users.isEmpty()) {
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
List<UserRole> userRoles = userRoleMapper.selectByUserIds(userIds);
// 将角色信息分组并设置到用户对象中
Map<Long, List<Role>> roleMap = userRoles.stream()
.collect(Collectors.groupingBy(UserRole::getUserId,
Collectors.mapping(UserRole::getRole, Collectors.toList())));
users.forEach(user -> user.setRoles(roleMap.get(user.getId())));
}
return users;
}
6.6.2 大数据量查询优化
// 流式查询处理大数据量
@Select("SELECT * FROM t_user WHERE status = 1")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)
void selectAllUsersWithCursor(ResultHandler<User> handler);
// 使用示例
public void processAllUsers() {
userMapper.selectAllUsersWithCursor(resultContext -> {
User user = resultContext.getResultObject();
// 处理单个用户
processUser(user);
});
}
// 分页处理大数据量
public void processAllUsersByPage() {
int pageSize = 1000;
int pageNum = 1;
List<User> users;
do {
PageHelper.startPage(pageNum, pageSize, false); // 不查询总数
users = userMapper.selectAll();
for (User user : users) {
processUser(user);
}
pageNum++;
} while (users.size() == pageSize);
}
7. 最佳实践总结
7.1 性能优化建议
- 合理使用缓存:开启二级缓存,使用Redis集群
- 避免N+1查询:使用联合查询或分批查询
- 优化SQL语句:添加适当索引,避免全表扫描
- 分页查询:大数据量时使用分页,避免内存溢出
- 连接池配置:合理配置数据库连接池参数
7.2 代码规范
- Mapper接口命名:使用动词+名词形式,如selectById、updateUser
- SQL语句:保持简洁清晰,适当添加注释
- 参数验证:在Service层进行参数校验
- 异常处理:统一异常处理机制
- 事务管理:合理使用@Transactional注解
7.3 安全考虑
- SQL注入防护:使用参数化查询,避免字符串拼接
- 敏感信息保护:密码等敏感信息加密存储
- 权限控制:实现细粒度的权限控制
- 日志记录:记录关键操作日志
这个MyBatis集成指南涵盖了从基础配置到高级特性的全面内容,为新手提供了详细的学习路径和实用示例。通过学习这些内容,您可以熟练掌握MyBatis在SpringBoot项目中的使用。