背景
在一些数据表名称根据业务需求变化的业务场景中,需要使用 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 中使用(如以上示例)。
关于 #
和 $
,它们的主要用法如下:
-
#
将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #{user_id} ,如果传入的值是 111,那么解析成 SQL 时的值为 order by “111”,如果传入的值是 id,则解析成的 sql 为 order by “id”。 -
$
将传入的数据直接显示生成在 SQL 中。如:order by ${user_id},如果传入的值是 111,那么解析成 SQL 时的值为 order by user_id,如果传入的值是id,则解析成的 SQL 为 order by id。 -
#
能够很大程度防止 SQL 注入,$
无法防止 SQL 注入。 -
$
方式一般用于传入数据库对象,例如传入表名。 -
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 动态建表其实用 $
替换 #
即可。