文章目录
- 连表查询
- 逻辑删除
- 多租户
连表查询
引入 mybatis-plus-join-boot-starter
依赖
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId>
<version>1.5.1</version>
</dependency>
继承 MPJBaseMapper
public interface RoleMapper extends MPJBaseMapper<Role> {}
public interface UserMapper extends MPJBaseMapper<User> {
public UserRole findUserRole();
}
观察源码,MPJBaseMapper 继承了 BaseMapper,并添加了连表查询的功能。
public interface MPJBaseMapper<T> extends BaseMapper<T>, JoinMapper<T> {}
定义一个 JavaBean 存储用户和角色信息的组合。包含用户 ID (id
)、角色 ID (rid
)、用户名 (userName
) 和角色名 (roleName
)。
@Data
public class UserRole {
private Integer id;
private Integer rid;
private String userName;
private String roleName;
}
分别通过 MPJLambdaWrapper
和 MPJQueryWrapper
来构建查询,获取包含用户和角色信息的 UserRole
对象列表。
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void mpj(){
MPJLambdaWrapper<User> lambdaWrapper = JoinWrappers.lambda(User.class)
.select(User::getId,User::getUserName)
.selectAs(Role::getId,UserRole::getRid)
.select(Role::getRoleName)
.leftJoin(Role.class,Role::getId,User::getUserRole);
List<UserRole> userRoleList = userMapper.selectJoinList(UserRole.class, lambdaWrapper);
userMapper.findUserRole();
}
}
MPJQueryWrapper<User> queryWrapper = new MPJQueryWrapper<>();
queryWrapper.select("t.id,userName,t1.id as rid,roleName");
queryWrapper.leftJoin("smbms_role t1 on t.userRole = t1.id");
List<UserRole> userRoleList = userMapper.selectJoinList(UserRole.class, queryWrapper);
userMapper.findUserRole();
以上两种方式较少使用,最常用的还是使用 XML 文件来编写 SQL 语句
这种方式可以提供更细粒度的控制,并且使得 SQL 语句更加集中和清晰。
<mapper namespace="com.hz.mapper.UserMapper">
<select id="findUserRole" resultType="com.hz.pojo.dto.userRole">
SELECT t.id,userName,t1.id as rid,roleName
FROM smbms_user t LEFT JOIN smbms_role t1 on t.userRole = t1.id
</select>
</mapper>
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void mpj(){
userMapper.findUserRole();
}
}
逻辑删除
逻辑删除是一种优雅的数据管理策略,它通过在数据库中标记记录为“已删除”而非物理删除,来保留数据的历史痕迹,同时确保查询结果的整洁性。MyBatis-Plus 提供了便捷的逻辑删除支持,使得这一策略的实施变得简单高效。
MyBatis-Plus 的逻辑删除功能会在执行数据库操作时自动处理逻辑删除字段。以下是它的工作方式:
- 插入:逻辑删除字段的值不受限制。
- 查找:自动添加条件,过滤掉标记为已删除的记录。
- 更新:防止更新已删除的记录。
- 删除:将删除操作转换为更新操作,标记记录为已删除。
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
使用步骤
1、配置全局属性
在 application.yml
中配置 MyBatis-Plus 的全局逻辑删除属性。
mybatis-plus:
global-config:
db-config:
logic-delete-field: isdeleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
2、使用 @TableLogic
注解
确保数据库表中已添加了逻辑删除字段,并设置默认值为逻辑未删除值。在实体类中,对应数据库表的逻辑删除字段上添加 @TableLogic
注解。
import com.baomidou.mybatisplus.annotation.TableLogic;
public class User {
@TableLogic
private Integer isdeleted;
}
3、测试
@SpringBootTest
public class ProviderServiceImplTest {
@Resource
private ProviderService providerService;
@Test
public void find() {
QueryWrapper<Provider> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "proCode", "proDesc", "proName");
providerService.list(queryWrapper);
}
}
JDBC Connection [HikariProxyConnection@23218037 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e7f19b4] will not be managed by Spring
==> Preparing: SELECT id,proCode,proDesc,proName FROM smbms_provider WHERE isdeleted=0
==> Parameters:
<== Columns: id, proCode, proDesc, proName
<== ...
再测试一下删除
@Test
public void delete() {
providerService.removeById(17);
}
==> Preparing: UPDATE smbms_provider SET isdeleted=1 WHERE id=? AND isdeleted=0
==> Parameters: 17(Integer)
<== Updates: 1
公共实体类(Common Entity Class)将多个实体类共有的属性和行为封装到一个单独的类中。
@Data
@TableName("smbms_provider")
public class Provider extends BaseEntity{
// 数据库自增 ID 作为主键
@TableId(value = "id", type = IdType.AUTO)
private Integer id; //id
private String proCode; //供应商编码
// 映射到数据库字段 proName as pname
@TableField("proName")
private String pname; //供应商名称
private String proDesc; //供应商描述
private String proContact; //供应商联系人
private String proPhone; //供应商电话
private String proAddress; //供应商地址
private String proFax; //供应商传真
}
@Data
public class BaseEntity implements Serializable {
private Integer createdBy; //创建者
private String creationDate; //创建时间
private Integer modifyBy; //更新者
private String modifyDate;//更新时间
@TableLogic
private Integer isdeleted;
}
多租户
MyBatis-Plus 提供了 TenantLineInnerInterceptor
插件用于实现多租户的数据隔离。通过这个插件,可以确保每个租户只能访问自己的数据,从而实现数据的安全隔离。
关键属性
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
tenantLineHandler | TenantLineHandler | 租户处理器( TenantId 行级 ) |
TenantLineHandler
接口定义了以下方法:
public interface TenantLineHandler {
/**
* 获取租户 ID 值表达式,只支持单个 ID 值
*
* @return 租户 ID 值表达式
*/
Expression getTenantId();
/**
* 获取租户字段名
* 默认字段名叫: tenant_id
*
* @return 租户字段名
*/
default String getTenantIdColumn() {
return "tenant_id";
}
/**
* 根据表名判断是否忽略拼接多租户条件
* 默认都要进行解析并拼接多租户条件
*
* @param tableName 表名
* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
*/
default boolean ignoreTable(String tableName) {
return false;
}
/**
* 忽略插入租户字段逻辑
*
* @param columns 插入字段
* @param tenantIdColumn 租户 ID 字段
* @return
*/
default boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
return columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(tenantIdColumn));
}
}
使用一下,先定义一个实现类 CustomTenantHandle
继承 TenantLineHandler
接口
@Component
public class CustomTenantHandler implements TenantLineHandler {
@Override
public Expression getTenantId() {
int userId = 2;
return new LongValue(userId);
}
@Override
public String getTenantIdColumn() {
return "createdBy";
}
@Override
public boolean ignoreTable(String tableName) {
return false; // 拼接
}
}
多租户数据库中,不同租户的数据存储在同一张表中,通过租户ID来区分。分页查询结合租户过滤条件可以确保每个租户只能访问到自己的数据。另外,分页也可以限制查询结果的数量,从而提高查询效率。
@Configuration
public class MybatisPlusConfig {
@Resource
private CustomTenantHandler customTenantHandler;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(customTenantHandler);
interceptor.addInnerInterceptor(tenantInterceptor);
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试一下
@Test
public void findpage() {
Page<Provider> page = new Page<>(1,5);
providerService.page(page,null);
}
查询到 4 条记录
JDBC Connection [HikariProxyConnection@227080339 wrapping com.mysql.cj.jdbc.ConnectionImpl@259b85d6] will not be managed by Spring
==> Preparing: SELECT COUNT(*) AS total FROM smbms_provider WHERE isdeleted = 0 AND smbms_provider.createdBy = 2
==> Parameters:
<== Columns: total
<== Row: 4
<== Total: 1
==> Preparing: SELECT id, proCode, proName AS pname, proDesc, proContact, proPhone, proAddress, proFax, createdBy, creationDate, modifyBy, modifyDate, isdeleted FROM smbms_provider WHERE isdeleted = 0 AND smbms_provider.createdBy = 2 LIMIT ?
==> Parameters: 5(Long)
<== ...