拾忆🍂拾忆🍂
  • cpp
  • MySQL
  • Oracle
  • PostgreSQL
  • MyBatis
  • API升级
  • HMOS
  • 百变小组件
  • hdc
  • 元服务
  • Java
  • MinIO
  • Stream
  • JSP & Struts2
  • Spring
  • FFmpeg
  • Linux
  • Git
  • Nginx
  • Ollama
  • Adobe Audition
  • Aseprite
  • Excel
  • Markdown基本用法
  • MuseScore 4.x
  • UVR
  • Windows
  • emoji-cheat-sheet
  • IDE快捷键
  • obs-studio
  • YOLO
  • Python
  • VuePress 2.x
  • 内置组件
  • markdown-container
  • markdown-ext
  • markdown-hint
  • markdown-preview
  • markdown-tab
  • Markdown扩展语法
  • 插件配置
  • prismjs
  • 样式
  • CSS
  • JS
  • TS
  • Vue3
主页
梦的开始🌅
  • cpp
  • MySQL
  • Oracle
  • PostgreSQL
  • MyBatis
  • API升级
  • HMOS
  • 百变小组件
  • hdc
  • 元服务
  • Java
  • MinIO
  • Stream
  • JSP & Struts2
  • Spring
  • FFmpeg
  • Linux
  • Git
  • Nginx
  • Ollama
  • Adobe Audition
  • Aseprite
  • Excel
  • Markdown基本用法
  • MuseScore 4.x
  • UVR
  • Windows
  • emoji-cheat-sheet
  • IDE快捷键
  • obs-studio
  • YOLO
  • Python
  • VuePress 2.x
  • 内置组件
  • markdown-container
  • markdown-ext
  • markdown-hint
  • markdown-preview
  • markdown-tab
  • Markdown扩展语法
  • 插件配置
  • prismjs
  • 样式
  • CSS
  • JS
  • TS
  • Vue3
主页
梦的开始🌅
  • 「从开始,到永久」
  • C艹

    • cpp
  • Database

    • MySQL
    • Oracle
    • PostgreSQL
    • MyBatis
  • HarmonyOS

    • API升级
    • HMOS
    • 百变小组件
    • hdc
    • 元服务
  • Java

    • Java
    • MinIO
    • Stream
    • JSP & Struts2
    • Spring
  • Linux

    • FFmpeg
    • Linux
    • Git
    • Nginx
  • LLM

    • Ollama
  • Others

    • Adobe Audition
    • Aseprite
    • Excel
    • Markdown基本用法
    • MuseScore 4.x
    • UVR
    • Windows
    • emoji-cheat-sheet
    • IDE快捷键
    • obs-studio
    • YOLO
  • Python

    • Python
  • VuePress

    • VuePress 2.x
    • 内置组件
    • markdown-container
    • markdown-ext
    • markdown-hint
    • markdown-preview
    • markdown-tab
    • Markdown扩展语法
    • 插件配置
    • prismjs
    • 样式
  • Web

    • CSS
    • JS
    • TS
    • Vue3
  • 主页

注意事项

  • <if test="assetsName != null and assetsName != '' ">中间and不能用大写字母

  • 当你将查询结果的 resultType 设置为 java.util.Map 时,MyBatis 会为查询结果集中的每一行创建一个 Map 对象。

collection 一对多

在 MyBatis 中,一对多的对应关系通常是通过 SQL 的 JOIN 来实现的,其中使用 ON 关键字来定义关联条件。通过 JOIN,可以将两个相关的表连接在一起,然后将相关数据合并成一个结果集。

在一对多关系中,通常是通过在主表的查询结果中包含子表的多个匹配记录来表示。例如,一个主表记录对应多个子表记录。

在 MyBatis 中,你可以使用 JOIN 和 ON 条件来定义一对多的对应关系,以及如何将子表的多个匹配记录映射到主表的属性中。这可以通过 resultMap 或者 association 和 collection 元素来实现。

以下是一个示例,假设你有两个表 users 和 orders,一个用户可以有多个订单,表 orders 中有一个外键 user_id 关联到表 users 的 id 字段。

  1. 在 User 实体类中定义一个集合属性 List<Order> orders,表示一对多关系。
public class User {
    private Long id;
    private String username;
    private List<Order> orders;

    // Getters and setters...
}
  1. 创建 user_mapper.xml 文件,并在其中定义一个 resultMap 来映射查询结果。
<!-- user_mapper.xml -->

<resultMap id="UserResultMap" type="User">
    <id property="id" column="id" />
    <result property="username" column="username" />
    <!-- 其他属性映射... -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id" />
        <result property="amount" column="amount" />
        <!-- Order 实体类的其他属性映射... -->
    </collection>
</resultMap>

<select id="selectUserWithOrders" resultMap="UserResultMap">
    SELECT
        u.id, u.username, o.id as order_id, o.amount
    FROM
        users u
    LEFT JOIN
        orders o ON u.id = o.user_id
    WHERE
        u.id = #{userId}
</select>

在上面的 resultMap 定义中,使用 <collection> 元素来配置 orders 属性的映射关系。其中,property 属性指定了集合属性名,ofType 属性指定了集合元素的类型(即 Order 类型),然后在 <collection> 元素内部配置了 Order 实体类的属性映射关系。

  1. 在 UserMapper 接口中添加查询方法。
public interface UserMapper {
    User selectUserWithOrders(@Param("userId") Long userId);
}
  1. 调用查询方法并获取结果。
public class YourService {
    @Autowired
    private UserMapper userMapper;

    public User getUserWithOrders(Long userId) {
        return userMapper.selectUserWithOrders(userId);
    }
}

通过上述配置,MyBatis 将会通过 JOIN 操作获取用户信息以及关联的多个订单信息,并将结果映射到 User 对象的 orders 属性中,实现了一对多的查询。

association 一对一

提问:在你上方的例子中,如果又有一个User对应单个收货地址,怎么增加一个一对一

如果你的 User 表对应着一个单个的收货地址,那么在 MyBatis 中可以通过 association 元素来定义一对一的对应关系。这样,你可以在 resultMap 中同时定义 collection 元素表示一对多关系和 association 元素表示一对一关系。

假设你有两个实体类 User 和 Address,其中 User 包含一个集合属性 List<Order> orders 表示一对多关系,以及一个属性 Address address 表示一对一关系。

  1. 在 User 类中,定义集合属性 orders 和一对一属性 address,并添加对应的 getter 和 setter 方法。
javaCopy codepublic class User {
    private Long id;
    private String username;
    private List<Order> orders;
    private Address address;

    // Getters and setters...
}
  1. 创建 user_mapper.xml 文件,并在其中定义一个 resultMap 来映射查询结果。
xmlCopy code<!-- user_mapper.xml -->

<resultMap id="UserResultMap" type="User">
    <id property="id" column="id" />
    <result property="username" column="username" />
    <!-- 其他属性映射... -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id" />
        <result property="amount" column="amount" />
        <!-- Order 实体类的其他属性映射... -->
    </collection>
    <association property="address" javaType="Address">
        <id property="id" column="address_id" />
        <result property="city" column="city" />
        <!-- Address 实体类的其他属性映射... -->
    </association>
</resultMap>

<select id="selectUserWithOrdersAndAddress" resultMap="UserResultMap">
    SELECT
        u.id, u.username, o.id as order_id, o.amount, a.id as address_id, a.city
    FROM
        users u
    LEFT JOIN
        orders o ON u.id = o.user_id
    LEFT JOIN
        address a ON u.address_id = a.id
    WHERE
        u.id = #{userId}
</select>

在上面的 resultMap 定义中,我们通过 <association> 元素来配置 address 属性的映射关系。其中,property 属性指定了一对一属性名,javaType 属性指定了一对一属性的类型(即 Address 类型),然后在 <association> 元素内部配置了 Address 实体类的属性映射关系。

  1. 在 UserMapper 接口中添加查询方法。
javaCopy codepublic interface UserMapper {
    User selectUserWithOrdersAndAddress(@Param("userId") Long userId);
}
  1. 调用查询方法并获取结果。
javaCopy codepublic class YourService {
    @Autowired
    private UserMapper userMapper;

    public User getUserWithOrdersAndAddress(Long userId) {
        return userMapper.selectUserWithOrdersAndAddress(userId);
    }
}

通过以上配置,MyBatis 将会通过 JOIN 操作获取用户信息以及关联的多个订单信息和单个收货地址信息,并将结果映射到 User 对象的 orders 属性和 address 属性中,实现了一对多和一对一的查询。

使用自定义查询进行一对一

问题:继续你上方的例子,如果我的对应关系是根据一个已经写好的用户-收货地址的一对一查询sql,怎么更改xml文件

如果你已经有一个独立的 SQL 查询语句用于用户和收货地址的一对一查询,那么你可以在 MyBatis 的 resultMap 中使用 <association> 元素来配置一对一的映射关系。这样,你可以在 resultMap 中引用已有的一对一查询结果,而不必重新定义查询逻辑。

以下是在 MyBatis 的 resultMap 中使用 <association> 元素来配置一对一关系的示例:

假设你已经有一个 SQL 查询语句 selectUserWithAddress 用于用户和收货地址的一对一查询,这个查询语句返回了用户信息和对应的收货地址信息。

  1. 创建 user_mapper.xml 文件,并在其中定义一个 resultMap 来映射查询结果。
<!-- user_mapper.xml -->

<resultMap id="UserResultMap" type="User">
    <id property="id" column="id" />
    <result property="username" column="username" />
    <!-- 其他属性映射... -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id" />
        <result property="amount" column="amount" />
        <!-- Order 实体类的其他属性映射... -->
    </collection>
    <association property="address" select="selectUserWithAddress" />
</resultMap>

<select id="selectUserWithOrdersAndAddress" resultMap="UserResultMap">
    SELECT
        u.id, u.username, o.id as order_id, o.amount
    FROM
        users u
    LEFT JOIN
        orders o ON u.id = o.user_id
    WHERE
        u.id = #{userId}
</select>

<select id="selectUserWithAddress" resultType="Address">
    -- 这里写你已经定义的用户-收货地址的一对一查询SQL
    -- 例如:SELECT * FROM address WHERE user_id = #{userId}
</select>

在上面的 resultMap 定义中,我们通过 <association> 元素来配置 address 属性的映射关系。其中,property 属性指定了一对一属性名,select 属性指定了引用的已有查询语句 selectUserWithAddress。在这里,我们假设你已经在 XML 文件中定义了名为 selectUserWithAddress 的查询语句,该查询语句返回了用户对应的收货地址信息。

请注意,select 属性中的值是 selectUserWithAddress,这是一个自定义的查询语句的 id,用于引用已有的查询逻辑。

  1. 在 UserMapper 接口中添加查询方法。
public interface UserMapper {
    User selectUserWithOrdersAndAddress(@Param("userId") Long userId);
}
  1. 调用查询方法并获取结果。
public class YourService {
    @Autowired
    private UserMapper userMapper;

    public User getUserWithOrdersAndAddress(Long userId) {
        return userMapper.selectUserWithOrdersAndAddress(userId);
    }
}

通过以上配置,MyBatis 将会在查询用户信息时,自动调用 selectUserWithAddress 查询语句来获取用户对应的收货地址信息,并将结果映射到 User 对象的 address 属性中,实现了一对多和一对一的查询。

foreach

dept IN
<foreach collection="deptIds" item="id" open="(" close=")" separator=",">
	#{id}
</foreach>

SqlSession的四大对象

1、SqlSession下的四大对象介绍

通过前面的分析,我们应该知道在Mybatis中的,首先是通过SqlSessionFactoryBuilder加载全局配置文件(包括SQL映射器),这些配置都会封装在Configuration中,其中每一条SQL语句的信息都会封装在MappedStatement中。然后创建SqlSession,这时还会初始化Executor执行器。最后通过调用sqlSession.getMapper()来动态代理执行Mapper中对应的SQL语句。而当一个动态代理对象进入到了MapperMethod的execute()方法后,它经过简单地判断就进入了SqlSession的delete、update、insert、select等方法,这里是真正执行SQL语句的地方。那么这些方法是如何执行呢?答:实际上SqlSession的执行过程是通过Executor、StatementHandler、ParameterHandler和ResultSetHandler来完成数据库操作和结果返回的,它们简称为四大对象:

  • Executor:代表执行器,由它调度StatementHandler、ParameterHandler、ResultSetHandler等来执行对应的SQL,其中StatementHandler是最重要的。
  • StatementHandler:作用是使用数据库的Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的。
  • ParameterHandler:是用来处理SQL参数的。
  • ResultSetHandler:是进行数据集(ResultSet)的封装返回处理的,它非常的复杂,好在不常用。

这四个对象都是通过Mybatis的插件来完成的,在实例化Executor、StatementHandler、ParameterHandler、ResultSetHandler四大接口对象的时候都是调用interceptorChain.pluginAll() 方法插入进去的(Mybatis的插件实现原理可以参考:链接1 ,链接2 )。下面依次分析这四大对象的生成和运作原理。

开放的核心接口

MyBatis 是一个持久层框架,提供了许多接口和类用于数据库访问。以下是 MyBatis 中一些常见的接口及其功能的列表:

接口功能主要方法/回调
SqlSessionFactoryMyBatis 的工厂接口,用于创建 SqlSession 实例openSession、getConfiguration
SqlSession用于执行 SQL 命令,提供了数据库操作的方法select、insert、update、delete 等
Mapper映射器接口,定义了与数据库交互的方法根据方法名执行 SQL 语句,方法参数与 SQL 参数绑定
SqlSessionFactoryBuilder用于创建 SqlSessionFactory 实例的构建器build
ConfigurationMyBatis 的配置类,包含了 MyBatis 的全部配置信息getMapper、addMapper、setXXX 等
MapperProxy映射器接口的动态代理实现invoke
Executor执行器接口,定义了 SQL 语句的执行方式query、update 等
Transaction事务接口,定义了事务的开始、提交、回滚等方法begin、commit、rollback 等
语句处理器接口,用于处理预处理 SQL 语句、Statement 等,许多重要的插件都是通过拦截它来实现的。prepare、parameterize、batch 等
参数处理器接口,用于处理 SQL 语句的参数setParameters
结果集处理器接口,用于处理从数据库返回的结果集handleResultSets、handleOutputParameters 等
ResultHandler结果处理器接口,用于自定义处理查询结果handleResult
ResultSetType结果集类型枚举,用于指定 Statement 的结果集类型FORWARD_ONLY、SCROLL_INSENSITIVE 等
ResultSetConcurrency结果集并发性枚举,用于指定 Statement 的结果集并发性CONCUR_READ_ONLY、CONCUR_UPDATABLE 等
类型处理器接口,用于处理 Java 类型与 JDBC 类型之间的转换setParameter、getResult 等
TypeAliasRegistry类型别名注册表,用于管理 MyBatis 中的类型别名registerAlias、resolveAlias 等
MappedStatement映射语句对象,表示一个映射配置文件中的 SQL 语句配置getId、getSqlCommandType、getBoundSql 等
DataSource数据源接口,定义了获取数据库连接的方法getConnection
SqlNodeSQL 节点接口,表示 XML 映射文件中的一个 SQL 节点apply
SqlSourceSQL 源接口,表示 XML 映射文件中的一个 SQL 配置项的来源getBoundSql
Cache缓存接口,定义了缓存的基本操作putObject、getObject、clear 等
LanguageDriver语言驱动接口,定义了动态 SQL 语句的生成方式createSqlSource、parse
拦截器接口,用于拦截 MyBatis 的执行过程,如给所有语句加上status != 0intercepr

这些接口是 MyBatis 框架中的核心组件,通过实现这些接口,可以在不同的阶段对数据库操作进行自定义处理。在实际使用中,开发者通常会使用 MyBatis 的注解或 XML 配置来定义 SQL 映射关系,而这些接口则是 MyBatis 在底层实现中使用的关键组件。

<foreach>

  • 遍历Map<String, Object>(动态拼接sql),index不是索引!是Map中的key再次骂一次傻逼GPT,item是Map中的value。

    • insert

      <insert id="insertBatchByMap" parameterType="java.util.Map">
          insert into t_student
          (class_id, // 这里模拟有业务需要插入前端没有传入,但后端需要记录的一个值,也可以在走的Mapper之前手动put一个,这里只是展示这种写法
          <foreach collection="studentMap.keySet()" item="columnItem" separator=",">
              ${columnItem}
          </foreach>)
          values
          ('101',
          <foreach collection="studentMap.values()" item="valueItem" separator=",">
              #{valueItem}
          </foreach>)
      </insert>
      
    • update

      <update id="updateByMap" parameterType="java.util.Map">
          UPDATE t_student
          set
          <foreach collection="studentMap.entrySet()" index="columnName" item="columnValue"  separator=",">
              ${columnName} = #{columnValue}
          </foreach>
          WHERE stu_id = '105'
      </update>
      

需求

我们可以通过结合Spring AOP和MyBatis的拦截器来实现:访问不同Controller来拼接不同sql的处理

首先,我们创建一个拦截器 SqlInterceptor 用于动态修改SQL语句的后缀:

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlInterceptor implements Interceptor {

    private static final ThreadLocal<String> dynamicSqlSuffix = new ThreadLocal<>();

    public static String getDynamicSqlSuffix() {
        return dynamicSqlSuffix.get();
    }

    public static void setDynamicSqlSuffix(String suffix) {
        dynamicSqlSuffix.set(suffix);
    }

    public static void clear() {
        dynamicSqlSuffix.remove();
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        ParameterHandler parameterHandler = statementHandler.getParameterHandler();

        // 获取原始SQL语句
        String originalSql = statementHandler.getBoundSql().getSql();

        // 获取动态SQL后缀
        String dynamicSuffix = getDynamicSqlSuffix();
        if (dynamicSuffix != null && !dynamicSuffix.isEmpty()) {
            // 在原始SQL语句后面添加动态后缀
            // 加上判断是否包含WHERE的逻辑,以免直接AND报错
            originalSql += dynamicSuffix;
        }

        // 将修改后的SQL语句设置回去
        statementHandler.getBoundSql().setSql(originalSql);

        // 继续执行原来的操作
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以在这里配置一些属性
    }
}

然后,我们创建一个切面类 SqlAspect 来根据Controller的访问路径设置动态SQL后缀:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SqlAspect {

    @Before("execution(* com.example.controllers.AController.*(..))")
    public void beforeAController(JoinPoint joinPoint) {
        // 设置动态SQL后缀为 " AND status != 1"
        SqlInterceptor.setDynamicSqlSuffix(" AND status != 1");
    }

    @Before("execution(* com.example.controllers.BController.*(..))")
    public void beforeBController(JoinPoint joinPoint) {
        // 设置动态SQL后缀为 " AND status = 0"
        SqlInterceptor.setDynamicSqlSuffix(" AND status = 0");
    }
    
    // 加上对应@After清空动态sql:SqlInterceptor.clear()
}

在上述代码中,SqlAspect 类使用 @Before 注解,分别匹配了 AController 和 BController 的执行前通知,根据访问的Controller设置了相应的动态SQL后缀。

最后,在你的MyBatis配置文件中注册拦截器:

<!-- mybatis-config.xml -->
<configuration>
    <!-- 其他配置 -->

    <plugins>
        <plugin interceptor="com.example.SqlInterceptor"/>
    </plugins>
</configuration>

并确保你的Spring AOP配置生效,可以通过 @ComponentScan 或者其他方式确保 SqlAspect 被扫描到。

这样,当访问 /a 的Controller时,所有的SQL语句都会被添加上 AND status != 1 的后缀;而访问 /b 的Controller时,所有的SQL语句都会被添加上 AND status = 0 的后缀。

——————————

Mybatis-Plus

  • 如果需要,参考

    <dependency>
      <groupId>com.github.yulichang</groupId>
      <artifactId>mybatis-plus-join</artifactId>
      <version>1.4.6</version> <!-- 请使用最新版本 -->
    </dependency>
    

分页插件

⚠️ 提示

于 v3.5.9 起,PaginationInnerInterceptor 已分离出来。如需使用,则需单独引入 mybatis-plus-jsqlparser 依赖 , 具体请查看 安装 一章。

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
  <version>3.5.9</version>
</dependency>

<!-- 分页插件 -->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-jsqlparser</artifactId>
  <version>3.5.9</version> <!-- 确保版本和 MyBatis Plus 主包一致 -->
</dependency>

使用

public interface SysUserMapper extends BaseMapper<SysUser> {}
public interface ISysUserService extends IService<SysUser> {}
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {}
Page<CustomerStoreType> page = new Page<>(current, size);
LambdaQueryWrapper<实体类> lqw = new LambdaQueryWrapper<>();
lqw
  .like(StrUtil.isNotBlank(param.getTypeCode()),CustomerStoreType::getTypeCode, param.getTypeCode())
  .orderByAsc(CustomerStoreType::getSortOrder);

Page<CustomerStoreType> result = customerStoreTypeServiceImpl.page(page, lqw);
return Result.success(result);
  • 如果 Mapper 文件在 SpringBoot 启动类所在包及其子包下,则不需要给启动类加 @MapperScan 注解;

  • 如果 Mapper 文件加了@Mapper注解,则不需要 @MapperScan 也可以扫描到这个 Mapper;

  • mybatis-plus.mapper-locations 配置(如 classpath*:/mapper/*.xml,classpath*:com/enlin/mapper/*.xml,classpath*:com/enlin/mapper/**/*.xml)仅用于定位 XML 映射文件,与 Mapper 接口的注册无关。

实体类自动插入创建人、时间等等

UserSession.java

package com.enlin.session;

import com.enlin.commons.cache.RedisCacheUtil;
import com.enlin.entity.SysUser;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;

/**
 * @author 
 * @projectName SpringBoot_MongoDB
 * @description 用户缓存
 * @date 2023/11/3 11:53
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserSession {

    private static final String BEARER_PREFIX = "Bearer ";
    private static final int BEARER_PREFIX_LENGTH = 7;
    
    @Autowired
    protected HttpServletRequest request;
    private SysUser user;
    private final RedisCacheUtil redisCacheUtil;


    public SysUser getUser() {
        // 从request中获取token

        String authorization = request.getHeader("Authorization");

        // 判断token是否为空
        if (authorization == null) {
            return null;
        }
        String token = authorization.replace(BEARER_PREFIX, "");
        if (user==null){
            user = getUser(token);
        }
        return user;
    }

    /**
     * 根据token获取用户信息
     * @param token 用户token
     * @return 用户信息,如果未找到则返回null
     */
    public SysUser getUser(String token) {
        if (!StringUtils.hasText(token)) {
            return null;
        }
        
        return redisCacheUtil.get(token, SysUser.class);
    }

    /**
     * 设置用户会话信息
     * @param token 用户token
     * @param user 用户信息
     * @return 是否设置成功
     */
    public boolean setUser(String token, SysUser user) {
        if (!StringUtils.hasText(token) || user == null) {
            log.warn("设置用户会话失败:token或用户信息为空");
            return false;
        }
        
        boolean success = redisCacheUtil.set(token, user, BEARER_PREFIX_LENGTH, TimeUnit.DAYS);
        if (success) {
            log.debug("用户会话设置成功,token: {}, userId: {}", token, user.getId());
        }
        return success;
    }
    /**
     * 根据token移除用户会话
     * @param token 用户token
     * @return 是否移除成功
     */
    public boolean removeUser(String token) {
        if (!StringUtils.hasText(token)) {
            return false;
        }
        return redisCacheUtil.delete(token);
    }
    /**
     * 检查用户会话是否存在
     * @param token 用户token
     * @return 是否存在
     */
    public boolean exists(String token) {
        if (!StringUtils.hasText(token)) {
            return false;
        }
        return redisCacheUtil.exists(token);
    }
    /**
     * 刷新用户会话过期时间
     * @param token 用户token
     * @return 是否刷新成功
     */
    public boolean refresh(String token) {
        if (!StringUtils.hasText(token)) {
            return false;
        }
        return redisCacheUtil.expire(token, BEARER_PREFIX_LENGTH, TimeUnit.DAYS);
    }
    /**
     * 获取用户会话过期时间
     * @param token 用户token
     * @return 过期时间,单位:毫秒
     */
    public long getExpire(String token) {
        if (!StringUtils.hasText(token)) {
            return -1;
        }
        return redisCacheUtil.getExpire(token);
    }

}

MyMetaObjectHandler.java

package com.enlin.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;

import com.enlin.entity.SysUser;
import com.enlin.session.UserSession;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 自定义元数据对象处理器
 */
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Autowired
    private UserSession userSession;
    /**
     * 插入操作,自动填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        SysUser user;
        String userId = "1";
        try {
            user = userSession.getUser();
            if (user != null) {
                userId = user.getId();
            }
        } catch (Exception e) {
            log.warn("获取当前用户信息失败,使用默认用户ID: {}", userId, e);
        }
        metaObject.setValue("createTime", System.currentTimeMillis());
        metaObject.setValue("updateTime", System.currentTimeMillis());
        metaObject.setValue("createBy",userId);
        metaObject.setValue("updateBy",userId);
        metaObject.setValue("deleted", false);
    }

    /**
     * 更新操作,自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        SysUser user;
        String userId = "1";
        try {
            user = userSession.getUser();
            if (user != null) {
                userId = user.getId();
            }
        } catch (Exception e) {
            log.warn("获取当前用户信息失败,使用默认用户ID: {}", userId, e);
        }
        metaObject.setValue("updateTime", System.currentTimeMillis());
        metaObject.setValue("updateBy", userId);
    }
}

实体类

package com.enlin.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

/**
 * <p>
 * 登录账号表
 * </p>
 *
 * @author Jay.Bao
 * @since 2025-08-13
 */
@Getter
@Setter
@ToString
@TableName("t_sys_user")
public class SysUser extends BaseEntity implements Serializable  {

    private static final long serialVersionUID = 1L;
    
    /**
     * 账号编码(唯一)
     */
    @TableField("c_code")
    private String code;


}

BaseEntity.java

package com.enlin.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

@Data
public class BaseEntity {
    
    /**
     * 雪花算法生成的唯一ID
     */
    @TableId(value = "c_id", type = IdType.ASSIGN_ID)
    private String id;
    
    @TableField(value = "c_create_by", fill = FieldFill.INSERT)
    private String createBy;

    @TableField(value = "c_create_time", fill = FieldFill.INSERT)
    private Long createTime;

    @TableField(value = "c_update_by", fill = FieldFill.INSERT_UPDATE)
    private String updateBy;

    @TableField(value = "c_update_time", fill = FieldFill.INSERT_UPDATE)
    private Long updateTime;

    /**
     * 是否逻辑删除
     */
    @TableField(value = "c_deleted", fill = FieldFill.INSERT)
    private Boolean deleted;
}
最近更新: 2025/11/11 01:02
Contributors: Enlin
Prev
PostgreSQL