MyBatis 动态创建表

项目中需要记录接口调用的日志,由于目前数量不大,决定根据年进行动态创建表(invoke_interface_log_2021)。使用MyBatis实现(动态SQL)。

Dao.xml

<mapper namespace="com.xxx.xxx.dao.manager.system.InvokeInterfaceLogDao">
    <!-- 根据表名统计已存在的数量 -->
    <select id="countTables" parameterType="string" resultType="int" databaseId="mysql">
        SELECT count(1)
        FROM information_schema.TABLES
        WHERE LCASE(table_name) = #{tableName}
    </select>

    <!-- 创建表SQL,参数为表名 -->
    <update id="createTable" parameterType="string" databaseId="mysql">
        CREATE TABLE ${tableName} (
            `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
            `client_id` varchar(50) DEFAULT NULL COMMENT '客户端标识',
            `interface_type` tinyint(4) unsigned DEFAULT NULL COMMENT '接口类型',
            `interface_name` varchar(50) DEFAULT NULL COMMENT '接口名称',
            `invoke_time` datetime DEFAULT NULL COMMENT '调用时间',
            `is_success` tinyint(1) unsigned DEFAULT '0' COMMENT '是否成功',
            `fail_type` tinyint(4) unsigned DEFAULT NULL COMMENT '失败类型',
            `fail_msg` varchar(255) DEFAULT NULL COMMENT '失败信息',
            PRIMARY KEY (`id`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='调用接口日志表';
    </update>

    <insert id="insert" parameterType="com.xxx.xxx.xxx.manager.system.InvokeInterfaceLogDO" databaseId="mysql">
        INSERT INTO ${tableName} (
            client_id,
            interface_type,
            interface_name,
            invoke_time,
            is_success,
            fail_type,
            fail_msg
        )
        VALUES
            (
                #{invokeInterfaceLog.clientId},
                #{invokeInterfaceLog.interfaceType},
                #{invokeInterfaceLog.interfaceName},
                #{invokeInterfaceLog.invokeTime},
                #{invokeInterfaceLog.success},
                #{invokeInterfaceLog.failType},
                #{invokeInterfaceLog.failMsg}
            )
    </insert>
</mapper>
复制代码

Dao.java

/**
 * 接口调用日志数据访问层接口
 *
 * @author: yemingxiang
 */
public interface InvokeInterfaceLogDao {

    /**
     * 统计存在的表数量
     *
     * @param tableName 表名
     * @return 存在的表数量
     */
    int countTables(String tableName);

    /**
     * 创建表
     *
     * @param tableName 表名
     */
    void createTable(@Param("tableName") String tableName);

    /**
     * 新增
     *
     * @param tableName 表名
     * @param invokeInterfaceLogDO 调用接口日志数据对象
     * @return 新增成功的记录数
     */
    int insert(@Param("tableName") String tableName,
        @Param("invokeInterfaceLog") InvokeInterfaceLogDO invokeInterfaceLogDO);
}
复制代码

业务层

实现如下:

/**
 * 调用接口日志业务逻辑层实现
 *
 * @author yemingxiang
 */
@Service
public class InvokeInterfaceLogServiceImpl implements InvokeInterfaceLogService {

    private static final Logger logger = LoggerFactory.getLogger(InvokeInterfaceLogServiceImpl.class);

    @Autowired
    private InvokeInterfaceLogDao invokeInterfaceLogDao;
    
    @Autowired
    private RedisService redisService;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public boolean save(InvokeInterfaceLogDO invokeInterfaceLogDO) {
        int currentYear = LocalDate.now().getYear();
        String key = String.format("system:invokeInterfaceLog:%s:string", currentYear);
        logger.info("接口调用日志表Redis Key:" + key);
        String tableName = redisService.getString(key);
        logger.info("接口调用日志表表名:" + tableName);
        if (tableName != null) {
            return invokeInterfaceLogDao.insert(tableName, invokeInterfaceLogDO) == 1;
        }

        String newTableName = String.format("invoke_interface_log_%s", currentYear);
        String lockKey = newTableName;
        try {
            RLock redissonLock = redissonClient.getLock(lockKey);
            redissonLock.lock();
            if (!Thread.currentThread().isInterrupted()) {
                log.info("get redis lock, key: {}", lockKey);
                try {
                    // 第二层判断,防范作用
                    tableName = redisService.getString(key);
                    logger.info("再次获取接口调用日志表表名:" + tableName);
                    if (tableName != null) {
                        return invokeInterfaceLogDao.insert(tableName, invokeInterfaceLogDO) == 1;
                    }

                    int count = invokeInterfaceLogDao.countTables(newTableName);
                    if (count == 0) {
                        logger.info("创建接口调用日志表,并将表名保存到Redis");
                        invokeInterfaceLogDao.createTable(newTableName);
                        redisService.set(key, newTableName);
                    }
                    return invokeInterfaceLogDao.insert(newTableName, invokeInterfaceLogDO) == 1;
                } finally {
                    redissonLock.unlock();
                    log.info("release redis lock, key: {}", lockKey);
                }
            } else {
                log.error("{} does not get lock.", Thread.currentThread().getName());
                throw new ThirdPartyServiceException(ThirdPartyServiceErrorCodeEnum.CACHE_SERVICE_TIMEOUT);
            }
        } catch (Exception e) {
            if (e instanceof ThirdPartyServiceException) {
                throw e;
            }
            throw new BaseException("处理失败_" + e.getMessage(), e);
        }
        return false;
    }
}
复制代码

大功告成^_^

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享