Mybatis:动态建表

郎家岭伯爵 2023年02月10日 511次浏览

背景

在一些数据表名称根据业务需求变化的业务场景中,需要使用 Mybatis 实现动态建表。

实现

pom.xml

在 pom.xml 中引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

application.properties

在 application.properties 文件中添加数据库配置以及 Mybatis 配置:

spring.datasource.username=root
spring.datasource.password=
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.mapper-locations=classpath:mapper/*.xml

# 开启 mybatis 驼峰映射
mybatis.configuration.map-underscore-to-camel-case=true

mapper.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.langjialing.springbootmybatisplusdemo1.mapper.UserMapper">

    <update id="createTable" parameterType="java.lang.String">
        CREATE TABLE if not exists ${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='调用接口日志表_${tableName}'
    </update>

    <update id="updateUser" parameterType="java.lang.String">
        update user set password=#{password, jdbcType=VARCHAR}
        WHERE user_id=#{userId, jdbcType=VARCHAR}
    </update>

</mapper>

这里主要看 ID 为 createTable 部分即可。动态建表的关键点即为把 # 替换为$ 同时 $ 还可以拼接在 SQL 字段或者 COMMENT 中使用(如以上示例)。

关于 #$ ,它们的主要用法如下:

  1. # 将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #{user_id} ,如果传入的值是 111,那么解析成 SQL 时的值为 order by “111”,如果传入的值是 id,则解析成的 sql 为 order by “id”。

  2. $ 将传入的数据直接显示生成在 SQL 中。如:order by ${user_id},如果传入的值是 111,那么解析成 SQL 时的值为 order by user_id,如果传入的值是id,则解析成的 SQL 为 order by id。

  3. # 能够很大程度防止 SQL 注入,$ 无法防止 SQL 注入。

  4. $ 方式一般用于传入数据库对象,例如传入表名。

  5. MyBatis 排序时使用 order by 动态参数时需要注意,用 $ 而不是 #

Mapper接口类

package com.langjialing.springbootmybatisplusdemo1.mapper;

import org.apache.ibatis.annotations.Param;

/**
 * <p>
 * 用户表 Mapper 接口
 * </p>
 *
 * @author 郎家岭伯爵
 * @since 2023-02-01
 */
public interface UserMapper{

    /**
     * 动态创建数据表。
     * @param tableName 数据表名称
     * @return
     */
    void createTable(@Param("tableName") String tableName);

    /**
     * 修改用户信息。
     * @param userId 用户ID
     * @param password 用户密码
     * @return
     */
    boolean updateUser(@Param("userId") String userId, @Param("password") String password);
}

需要注意的是:

  • 这里的 @Param() 注解是 import org.apache.ibatis.annotations.Param; 包里的。不要导错包。
  • Mybatis 中 update 代码块执行建表语句 CREATE TABLE 时没有返回值;而在执行更新语句 UPDATE TABLE 时有返回值,用 int 或者 boolean 类型参数接收均可(本文中写了两个业务代码,大家可用这两个进行测试比对)。

Service层

在 Service 层传入 tableName,使 UserMapper.xml 文件中对应的 SQL 拼接为完整的建表 SQL,实现动态见表。

Service 接口类:

package com.langjialing.springbootmybatisplusdemo1.service;

/**
 * <p>
 * 用户表 服务类
 * </p>
 *
 * @author 郎家岭伯爵
 * @since 2023-02-01
 */
public interface IUserService {

    void createTable();

    boolean updateUser(String userId, String password);

}

Service 接口实现类:

package com.langjialing.springbootmybatisplusdemo1.service.impl;

import com.langjialing.springbootmybatisplusdemo1.mapper.UserMapper;
import com.langjialing.springbootmybatisplusdemo1.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author 郎家岭伯爵
 * @since 2023-02-01
 */
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void createTable() {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");

        userMapper.createTable("USER_" + sdf.format(new Date()));
    }

    @Override
    public boolean updateUser(String userId, String password) {
        return userMapper.updateUser(userId, password);
    }
}

Controller层

package com.langjialing.springbootmybatisplusdemo1.controller;

import com.langjialing.springbootmybatisplusdemo1.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 用户表 前端控制器
 * </p>
 *
 * @author 郎家岭伯爵
 * @since 2023-02-01
 */
@RestController
@RequestMapping("/user")
@Api(tags = "用户操作")
public class UserController {

    @Autowired
    private IUserService userService;

    @ApiOperation("创建数据表")
    @GetMapping("/createTable")
    public void createTable(){
        userService.createTable();
    }

    @ApiOperation("更新用户信息")
    @PostMapping("/updateUser")
    public boolean updateUser(@RequestParam String userId, @RequestParam String password){
        return userService.updateUser(userId, password);
    }
}

主启动类

别忘了在主启动类添加 @MapperScans() 注解扫描 mapper 类:

@MapperScan("com.langjialing.springbootmybatisplusdemo1.mapper")

总结

使用 Mybatis 动态建表其实用 $ 替换 # 即可。