综合概述
在有些应用场景中,我们会有需要动态创建和操作表的需求。比如因为单表数据存储量太大而采取分表存储的情况,又或者是按日期生成日志表存储系统日志等等。这个时候就需要我们动态的生成和操作数据库表了。而我们都知道,以往我们使用MyBatis是需要提前生成包括Model,Mapper和XML映射文件的,显然因为动态生成和操作表的需求一开始表都是不存在的,所以也就不能直接通过MyBatis连接数据库来生成我们的数据访问层代码并用来访问数据库了。还好MyBatis提供了动态SQL,我们可以通过动态SQL,传入表名等信息然组装成建表和操作语句,接下来,我们就通过一个具体的案例来了解一下。
实现案例
先说一下我们要实现的案例,本案例中每个用户都会有一个自己日志表,其中的user_log_config表就是用户名和对应的日志表配置,每次往这个表添加配置的时候,系统就会根据配置信息给该用户生成一个日志存储表,表名是获取日志配置表里配置的表名称,并统一提供对这个日志表的操作接口。本教程案例基于 Spring Boot + Mybatis + MySQL 实现。
生成项目模板
为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站。
2. 根据页面提示,选择构建工具,开发语言,项目信息等。
3. 点击 Generate the project,生成项目模板,生成之后会将压缩包下载到本地。
4. 使用IDE导入项目,我这里使用Eclipse,通过导入Maven项目的方式导入。
创建数据库表
这里使用MySQL数据库,版本是8.0.16,在项目根目录下新建db目录,然后在其中编写一个数据库脚本文件。
在MySQL数据库新建一个springboot数据库,然后在此数据库中执行下面的脚本创建项目相关的表。
脚本文件
SQL脚本内容,注意,这里的user_log并不需要用到,事实上,user_log就是我们要生成的表结构,但为了一会儿MyBatis代码的生成,先创建一下,具体后续会讲到。
springboot.sql
-- ----------------------------
-- Table structure foruser_log_config-- ----------------------------DROP TABLE IF EXISTS `user_log_config`;
CREATE TABLE `user_log_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(50) NOT NULL COMMENT '用户名',
`table_name` varchar(150) DEFAULT NULL COMMENT '用户对应的日志存储表',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8 COMMENT='用户日志表配置';-- ----------------------------
-- Table structure foruser_log-- ----------------------------DROP TABLE IF EXISTS `user_log`;
CREATE TABLE `user_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`operation` varchar(50) DEFAULT NULL COMMENT '用户操作',
`method` varchar(200) DEFAULT NULL COMMENT '请求方法',
`params` varchar(5000) DEFAULT NULL COMMENT '请求参数',
`time` bigint(20) NOT NULL COMMENT '执行时长(毫秒)',
`ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2897 DEFAULT CHARSET=utf8 COMMENT='用户操作日志';
添加相关依赖
需要添加Spring Boot,Mybatis,MySQL,Swagger相关依赖。Swagger方便用来测试接口。
pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.5.RELEASE
com.louis.springboot
demo
0.0.1-SNAPSHOT
demo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.0.0
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
src/main/java
**/sqlmap/*.xml
false
src/main/resources
**/*.*
true
添加配置类
1. 添加swagger 配置
添加一个swagger 配置类,在工程下新建 config 包并添加一个 SwaggerConfig 配置类。
SwaggerConfig.java
package com.louis.springboot.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2public classSwaggerConfig {
@BeanpublicDocket createRestApi(){return newDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build();
}privateApiInfo apiInfo(){return newApiInfoBuilder()
.title("SpringBoot API Doc")
.description("This is a restful api document of Spring Boot.")
.version("1.0")
.build();
}
}
2.添加MyBatis配置
添加MyBatis配置类,配置相关扫描路径,包括DAO,Model,XML映射文件的扫描。
在config包下新建一个MyBatis配置类,MybatisConfig.java。
MybatisConfig.java
package com.louis.springboot.demo.config;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
@Configuration
@MapperScan("com.louis.springboot.**.dao") //扫描DAO
public classMybatisConfig {
@AutowiredprivateDataSource dataSource;
@BeanpublicSqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory= newSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage("com.louis.springboot.**.model"); //扫描Model
PathMatchingResourcePatternResolver resolver= newPathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath*:**/sqlmap/*.xml")); //扫描映射文件
returnsessionFactory.getObject();
}
}
3.添加数据源配置
将application.properties文件改名为application.yml ,并在其中添加MySQL数据源连接信息。
注意:
这里需要首先创建一个MySQL数据库,并输入自己的用户名和密码。这里的数据库是springboot。
另外,如果你使用的是MySQL 5.x及以前版本,驱动配置driverClassName是com.mysql.jdbc.Driver。
application.yml
server:
port:8080spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
username: root
password:123456
生成MyBatis模块
由于手动编写MyBatis的Model、DAO、XML映射文件比较繁琐,通常都会通过一些生成工具来生成。MyBatis官方也提供了生成工具(MyBaits Generator),另外还有一些基于官方基础上改进的第三方工具,比如MyBatis Plus就是国内提供的一款非常优秀的开源工具,网上相关教程比较多,这里就不再赘述了。
这里提供一些资料作为参考。
代码生成好之后,分別将MODEL、DAO、XML映射文件拷贝到相应的包里。
生成的Model,如下是user_log_config表对应的Model对象UserLogConfig。
UserLogConfig.java
package com.louis.springboot.demo.model;public classUserLogConfig {privateLong id;privateString name;privateString tableName;publicLong getId() {returnid;
}public voidsetId(Long id) {this.id =id;
}publicString getName() {returnname;
}public voidsetName(String name) {this.name = name == null ? null: name.trim();
}publicString getTableName() {returntableName;
}public voidsetTableName(String tableName) {this.tableName = tableName == null ? null: tableName.trim();
}
}
打开Mapper,我们看到MyBatis Generator给我们默认生成了一些增删改查的方法,另外添加一个查询全部的方法。
UserLogConfigMapper.java
package com.louis.springboot.demo.dao;
import java.util.List;
import com.louis.springboot.demo.model.UserLogConfig;public interfaceUserLogConfigMapper {intdeleteByPrimaryKey(Long id);intinsert(UserLogConfig record);intinsertSelective(UserLogConfig record);
UserLogConfig selectByPrimaryKey(Long id);intupdateByPrimaryKeySelective(UserLogConfig record);intupdateByPrimaryKey(UserLogConfig record);public ListselectAll();
}
在UserLogConfigMapper.xml中编写selectAll的SQL语句。
UserLogConfigMapper.xml
id, name, table_name
select
fromuser_log_configwhere id = #{id,jdbcType=BIGINT}
deletefromuser_log_configwhere id = #{id,jdbcType=BIGINT}
insert into user_log_config (id, name, table_name
)
values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{tableName,jdbcType=VARCHAR}
)
insert into user_log_config
id,
name,
table_name,
#{id,jdbcType=BIGINT},
#{name,jdbcType=VARCHAR},
#{tableName,jdbcType=VARCHAR},
update user_log_config
name= #{name,jdbcType=VARCHAR},
table_name= #{tableName,jdbcType=VARCHAR},
where id = #{id,jdbcType=BIGINT}
update user_log_configset name = #{name,jdbcType=VARCHAR},
table_name= #{tableName,jdbcType=VARCHAR}where id = #{id,jdbcType=BIGINT}
select
fromuser_log_config
编写UserLogConfig的服务接口。
UserLogConfigService.java
package com.louis.springboot.demo.service;
import java.util.List;
import com.louis.springboot.demo.model.UserLogConfig;public interfaceUserLogConfigService {/**
* 保存用户日志配置
* @return*/
voidsave(UserLogConfig userLogConfig);/**
* 查找全部用户日志配置
* @return*/ListfindAll();
}
编写UserLogConfig的服务实现类。
UserLogConfigServiceImpl.java
package com.louis.springboot.demo.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.louis.springboot.demo.dao.UserLogConfigMapper;
import com.louis.springboot.demo.model.UserLogConfig;
import com.louis.springboot.demo.service.UserLogConfigService;
@Servicepublic classUserLogConfigServiceImpl implements UserLogConfigService {
@AutowiredprivateUserLogConfigMapper userLogConfigMapper;
@Overridepublic voidsave(UserLogConfig userLogConfig) {if(userLogConfig.getId() != null && !"".equals(userLogConfig.getId())) {//更新
userLogConfigMapper.updateByPrimaryKeySelective(userLogConfig);
}else{//插入
userLogConfigMapper.insertSelective(userLogConfig);
}
}
@Overridepublic ListfindAll() {returnuserLogConfigMapper.selectAll();
}
}
编写UserLogConfig的控制器。
UserLogConfigController.java
package com.louis.springboot.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.louis.springboot.demo.model.UserLogConfig;
import com.louis.springboot.demo.service.UserLogConfigService;
@RestController
@RequestMapping("user/log/config")public classUserLogConfigController {
@AutowiredprivateUserLogConfigService userLogConfigService;
@PostMapping(value="/save")publicObject save(@RequestBody UserLogConfig userLogConfig) {
userLogConfigService.save(userLogConfig);return 1;
}
@GetMapping(value="/findAll")publicObject findAll() {returnuserLogConfigService.findAll();
}
}
现在我们来讲解如何动态实现动态生成用户日志存储表(user_log的表结构),之前我们通过MyBatis生成了user_log的服务访问层代码,下面是Model类UserLog,你可以直接用或改个名称都行,我们这里就不改了。
UserLog.java
package com.louis.springboot.demo.model;public classUserLog {privateLong id;privateString userName;privateString operation;privateString method;private String params;privateLong time;privateString ip;publicLong getId() {returnid;
}public voidsetId(Long id) {this.id =id;
}publicString getUserName() {returnuserName;
}public voidsetUserName(String userName) {this.userName = userName == null ? null: userName.trim();
}publicString getOperation() {returnoperation;
}public voidsetOperation(String operation) {this.operation = operation == null ? null: operation.trim();
}publicString getMethod() {returnmethod;
}public voidsetMethod(String method) {this.method = method == null ? null: method.trim();
}publicString getParams() {return params;
}public void setParams(String params) {this.params = params == null ? null : params.trim();
}publicLong getTime() {returntime;
}public voidsetTime(Long time) {this.time =time;
}publicString getIp() {returnip;
}public voidsetIp(String ip) {this.ip = ip == null ? null: ip.trim();
}
}
修改UserLogMapper,将原有的接口都加上tableName参数,传入表名以确定要操作的表。另外额外添加了三个跟建表有关的方法。
UserLogMapper.java
package com.louis.springboot.demo.dao;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.louis.springboot.demo.model.UserLog;public interfaceUserLogMapper {int deleteByPrimaryKey(@Param("tableName")String tableName, @Param("id")Long id);int insert(@Param("tableName")String tableName, @Param("userLog") UserLog userLog);int insertSelective(@Param("tableName")String tableName, @Param("userLog") UserLog record);
UserLog selectByPrimaryKey(@Param("tableName")String tableName, @Param("id")Long id);int updateByPrimaryKeySelective(@Param("tableName")String tableName, @Param("userLog") UserLog record);int updateByPrimaryKey(@Param("tableName")String tableName, @Param("userLog") UserLog record);/**
* 查找全部
* @param tableName
* @return*/List selectAll(@Param("tableName")String tableName);/**
* 是否存在表
* @param tableName
* @return*/
int existTable(@Param("tableName")String tableName);/**
* 删除表
* @param tableName
* @return*/
int dropTable(@Param("tableName")String tableName);/**
* 创建表
* @param tableName
* @return*/
int createTable(@Param("tableName")String tableName);
}
修改UserLogMapper.xml,将原来用表名user_log的地方替换成${tableName},表示表名由外部方法传入。另外编写另外三个建表相关的语句,检查表是否存在和删除表的语句比较简单,创建表的只要把建表语句拷贝过来,然后把表名替换成${tableName}就行了。
UserLogMapper.xml
id, user_name, operation, method,params, time, ip
select
from${tableName}where id = #{id,jdbcType=BIGINT}
deletefrom${tableName}where id = #{id,jdbcType=BIGINT}
insert into ${tableName} (id, user_name, operation,
method,params, time,
ip)
values (#{id,jdbcType=BIGINT}, #{userName,jdbcType=VARCHAR}, #{operation,jdbcType=VARCHAR},
#{method,jdbcType=VARCHAR}, #{params,jdbcType=VARCHAR}, #{time,jdbcType=BIGINT},
#{ip,jdbcType=VARCHAR})
insert into ${tableName}
id,
user_name,
operation,
method,
params,
time,
ip,
#{id,jdbcType=BIGINT},
#{userName,jdbcType=VARCHAR},
#{operation,jdbcType=VARCHAR},
#{method,jdbcType=VARCHAR},
#{params,jdbcType=VARCHAR},
#{time,jdbcType=BIGINT},
#{ip,jdbcType=VARCHAR},
update ${tableName}
user_name= #{userName,jdbcType=VARCHAR},
operation= #{operation,jdbcType=VARCHAR},
method= #{method,jdbcType=VARCHAR},
params = #{params,jdbcType=VARCHAR},
time= #{time,jdbcType=BIGINT},
ip= #{ip,jdbcType=VARCHAR},
where id = #{id,jdbcType=BIGINT}
update ${tableName}set user_name = #{userName,jdbcType=VARCHAR},
operation= #{operation,jdbcType=VARCHAR},
method= #{method,jdbcType=VARCHAR},params = #{params,jdbcType=VARCHAR},
time= #{time,jdbcType=BIGINT},
ip= #{ip,jdbcType=VARCHAR}where id = #{id,jdbcType=BIGINT}
select
from${tableName}
select count(*)frominformation_schema.TABLESwhere table_name=#{tableName}
DROP TABLE IF EXISTS ${tableName}
CREATE TABLE ${tableName} (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`operation` varchar(50) DEFAULT NULL COMMENT '用户操作',
`method` varchar(200) DEFAULT NULL COMMENT '请求方法',
`params` varchar(5000) DEFAULT NULL COMMENT '请求参数',
`time` bigint(20) NOT NULL COMMENT '执行时长(毫秒)',
`ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2897 DEFAULT CHARSET=utf8 COMMENT='用户操作日志';
编写用户日志的服务接口,包含一个保存方法和一个查询方法。
UserLogService.java
package com.louis.springboot.demo.service;
import java.util.List;
import com.louis.springboot.demo.model.UserLog;public interfaceUserLogService {/**
* 保存用户日志
* @return*/
voidsave(String tableName, UserLog userLog);/**
* 查找全部用户日志
* @return*/ListfindAll(String tableName);
}
编写用户日志的服务实现类,包含保存方法和查询方法的实现。
UserLogServiceImpl.java
package com.louis.springboot.demo.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.louis.springboot.demo.dao.UserLogMapper;
import com.louis.springboot.demo.model.UserLog;
import com.louis.springboot.demo.service.UserLogService;
@Servicepublic classUserLogServiceImpl implements UserLogService {
@AutowiredprivateUserLogMapper userLogMapper;
@Overridepublic voidsave(String tableName, UserLog userLog) {//插入
userLogMapper.insertSelective(tableName, userLog);
}
@Overridepublic ListfindAll(String tableName) {returnuserLogMapper.selectAll(tableName);
}
}
为了接口传表名方便,我们这里在UserLog类里加入一个tableName属性,用来给接口传入表名。
UserLog.java
package com.louis.springboot.demo.model;public classUserLog {privateLong id;privateString userName;privateString operation;privateString method;private String params;privateLong time;privateString ip;privateString tableName;publicLong getId() {returnid;
}public voidsetId(Long id) {this.id =id;
}publicString getUserName() {returnuserName;
}public voidsetUserName(String userName) {this.userName = userName == null ? null: userName.trim();
}publicString getOperation() {returnoperation;
}public voidsetOperation(String operation) {this.operation = operation == null ? null: operation.trim();
}publicString getMethod() {returnmethod;
}public voidsetMethod(String method) {this.method = method == null ? null: method.trim();
}publicString getParams() {return params;
}public void setParams(String params) {this.params = params == null ? null : params.trim();
}publicLong getTime() {returntime;
}public voidsetTime(Long time) {this.time =time;
}publicString getIp() {returnip;
}public voidsetIp(String ip) {this.ip = ip == null ? null: ip.trim();
}publicString getTableName() {returntableName;
}public voidsetTableName(String tableName) {this.tableName =tableName;
}
}
编写服务控制器UserLogController并调用服务的相关接口。
UserLogController.java
package com.louis.springboot.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.louis.springboot.demo.model.UserLog;
import com.louis.springboot.demo.service.UserLogService;
@RestController
@RequestMapping("user/log")public classUserLogController {
@AutowiredprivateUserLogService userLogService;
@PostMapping(value="/save")publicObject save(@RequestBody UserLog userLog) {
String tableName=userLog.getTableName();
userLogService.save(tableName, userLog);return 1;
}
@GetMapping(value="/findAll")publicObject findAll(String tableName) {returnuserLogService.findAll(tableName);
}
}
修改UserLogConfigServiceImpl的save方法,实现在添加配置的时候生成对应的表。
UserLogConfigServiceImpl.java
package com.louis.springboot.demo.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.louis.springboot.demo.dao.UserLogConfigMapper;
import com.louis.springboot.demo.dao.UserLogMapper;
import com.louis.springboot.demo.model.UserLogConfig;
import com.louis.springboot.demo.service.UserLogConfigService;
@Servicepublic classUserLogConfigServiceImpl implements UserLogConfigService {
@AutowiredprivateUserLogConfigMapper userLogConfigMapper;
@AutowiredprivateUserLogMapper userLogMapper;
@Overridepublic voidsave(UserLogConfig userLogConfig) {//插入
userLogConfigMapper.insertSelective(userLogConfig);//添加配置时,创建日志存储表
String tableName =userLogConfig.getTableName();if(userLogMapper.existTable(tableName) > 0) {
userLogMapper.dropTable(tableName);
}
userLogMapper.createTable(tableName);
}
@Overridepublic ListfindAll() {returnuserLogConfigMapper.selectAll();
}
}
编译测试运行
1. 右键项目 -> Run as -> Maven install,开始执行Maven构建,第一次会下载Maven依赖,可能需要点时间,如果出现如下信息,就说明项目编译打包成功了。
2. 右键文件 DemoApplication.java -> Run as -> Java Application,开始启动应用,当出现如下信息的时候,就说明应用启动成功了,默认启动端口是8080。
4. 测试UserLogConfigMapper的save保存接口,输入以下参数进行测试。
{"id": 1,"name": "xiaoming","tableName": "xiaoming"}
成功之后调UserLogConfigMapper的findAll接口,可以看到配置信息已经成功插入。
并且我们可以通过MySQL客户端查看到,在配置生成的同时生成了一个表名为xiaoming的数据库表。
5. 测试UserLogController的save保存接口,输入以下参数进行测试。
{"id": 1,"ip": "139.123.123.100","method": "save","operation": "save","params": "string name","tableName": "xiaoming","time": 0,"userName": "xiaoming"}
成功之后调UserLogController的findAll接口,可以看到配置信息已经成功插入。
测试到此,我们成功的保存了配置信息,并且动态创建了一个表,然后成功的往表里插入了一点数据,并通过接口查询出了插入的数据。
参考资料
相关导航
源码下载