JavaWeb MyBatisPlus添加一个能够批量插入的Mapper通用方法

众所周知,mybatisplus提供的BaseMapper里只有单条插入的方法,没有批量插入的方法,
而在Service层的批量插入并不是真的批量插入,实际上是遍历insert,但也不是一次insert就一次IO,而是到一定数量才会去IO一次,性能不是很差,但也不够好。

怎么才能实现真正的批量插入呢?

这里是mybatisplus官方的演示仓库,可以先去了解一下。

一、注册自定义通用方法流程

  1. 把自定义方法写到BaseMapper,因为没法改BaseMapper,所以继承一下它
public interface MyBaseMapper<T> extends BaseMapper<T> {

    int batchInsert(@Param("list") List<T> entityList);
}

MyBaseMapper扩展了原有的BaseMapper,所以你之后的Mapper层都继承自MyBaseMapper而不是BaseMapper即可。

  1. 把通用方法注册到mybatisplus
@Component
public class MySqlInjector extends DefaultSqlInjector {
	@Override
	public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
		List<AbstractMethod> defaultMethodList = super.getMethodList(mapperClass, tableInfo);
		defaultMethodList.add(new BatchInsert("batchInsert"));
		return defaultMethodList;
	}
}

关键的一句在于defaultMethodList.add(new BatchInsert("batchInsert"));,意为注册一个新的方法叫batchInsert,具体实现在BatchInsert类。

  1. 实现BatchInsert类
public class BatchInsert extends AbstractMethod {
	public BatchInsert(String name) {
		super(name);
	}

	@Override
	public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
		KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
		SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
		String columnScript = getAllInsertSqlColumn(tableInfo.getFieldList());
		String valuesScript = SqlScriptUtils.convertForeach(LEFT_BRACKET + getAllInsertSqlProperty("item.", tableInfo.getFieldList()) + RIGHT_BRACKET,
				LIST, null, "item", COMMA);
		String keyProperty = null;
		String keyColumn = null;
		// 表包含主键处理逻辑,如果不包含主键当普通字段处理
		if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
			if (tableInfo.getIdType() == IdType.AUTO) {
				/* 自增主键 */
				keyGenerator = Jdbc3KeyGenerator.INSTANCE;
				keyProperty = tableInfo.getKeyProperty();
				// 去除转义符
				keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
			} else if (null != tableInfo.getKeySequence()) {
				keyGenerator = TableInfoHelper.genKeyGenerator(methodName, tableInfo, builderAssistant);
				keyProperty = tableInfo.getKeyProperty();
				keyColumn = tableInfo.getKeyColumn();
			}
		}
		String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
		SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
		return this.addInsertMappedStatement(mapperClass, modelClass, methodName, sqlSource, keyGenerator, keyProperty, keyColumn);
	}

	/**
	 * 获取 insert 时所有列名组成的sql片段
	 * @param fieldList 表字段信息列表
	 * @return sql 脚本片段
	 */
	private String getAllInsertSqlColumn(List<TableFieldInfo> fieldList) {
		return LEFT_BRACKET + fieldList.stream()
				.map(TableFieldInfo::getColumn).filter(Objects::nonNull).collect(joining(COMMA + NEWLINE)) + RIGHT_BRACKET;
	}

	/**
	 * 获取 insert 时所有属性值组成的sql片段
	 * @param prefix 前缀
	 * @param fieldList 表字段信息列表
	 * @return sql 脚本片段
	 */
	private String getAllInsertSqlProperty(final String prefix, List<TableFieldInfo> fieldList) {
		final String newPrefix = prefix == null ? EMPTY : prefix;
		return fieldList.stream()
				.map(i -> i.getInsertSqlProperty(newPrefix).replace(",", ""))
				.filter(Objects::nonNull)
				.collect(joining(COMMA + NEWLINE));
	}
}

二、BatchInsert具体实现逻辑解析

以如下简单的表user举例,

列名描述类型
id主键,自增bigint
user_name用户名varchar(64)
user_age用户年龄int

对于的entity大抵如下

@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("user")
public class User{
	@TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String userName;
    private int userAge;
}

那么对于batchInsert,我们希望传入List<User>并希望得到类似如下的mybtaisplus xml sql语句

    <insert id="batchInsert" parameterType="java.util.List">
        insert into user(
            id,
            user_name,
            user_age
        )values
        <foreach collection="list" item="item" separator=",">
        (   
        	#{item.id},
            #{item.userName},
            #{item.userAge}
        )
        </foreach>
    </insert>

但是我们并不自己写这个xml,不然这需要对每一个数据表都要写一个,就像不那么硬的硬代码一样,我们希望有段逻辑,只需要传入entity,就能自己解析其中列名和对应的属性名,生成这段xml实现批量插入的功能。

假设你的UserMapper已经继承自MyBaseMapper,如果调用UserMapper.bacthInsert(List<User> entityList),那么会进入这个函数

	@Override
	public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo)

其中mapperClass是映射器类,modelClass是模型类,我们并不需要了解,最主要的是tableInfo,这是表信息,它包含了关于数据库表的各种信息,如表名、列名、主键等。这个参数提供了详细的表信息,这对于生成针对特定表的SQL语句是必要的。

然后执行如下

//如果你的表名没有主键,那么你需要指定keyGenerator 为NoKeyGenerator,
//因为重写injectMappedStatement最后需要返回return this.addInsertMappedStatement
//其中就需要KeyGenerator 
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
//SqlMethod.INSERT_ONE就是"INSERT INTO %s %s VALUES %s"
//我们依据表的信息生成列名sql片段和属性名sql片段后填入%s就可以得到近似最后的xml sql
SqlMethod sqlMethod = SqlMethod.INSERT_ONE;

然后执行如下

//tableInfo.getFieldList()会得到一个包含数据表列信息(不包含主键)的TableFieldInfo类组成的List
String columnScript = getAllInsertSqlColumn(tableInfo.getFieldList());
//这行代码就是在调用这个函数
private String getAllInsertSqlColumn(List<TableFieldInfo> fieldList) {
	return LEFT_BRACKET + fieldList.stream()
			//从TableFieldInfo中只拿取列名
			.map(TableFieldInfo::getColumn)
			//过滤null
			.filter(Objects::nonNull)
			//在元素间以逗号和分行分割
			.collect(joining(COMMA + NEWLINE)) + RIGHT_BRACKET;
}
//对于User表,这个函数返回以下String
/*
(user_name,
user_age)
*/

然后执行如下

//首先调用了getAllInsertSqlProperty
String valuesScript = SqlScriptUtils.convertForeach(
	// 这也是个内置函数,可以直接去看看
	LEFT_BRACKET + getAllInsertSqlProperty("item.", tableInfo.getFieldList()) + RIGHT_BRACKET,
	LIST, 
	null, 
	"item", 
	COMMA
);
//LEFT_BRACKET + getAllInsertSqlProperty("item.", tableInfo.getFieldList()) + RIGHT_BRACKET
//得到
/*
(#{userName},
#{userAge})
*/
//经过convertForeach函数后,得到如下字符串
/*
<foreach collection="list" item="item" separator=",">
(#{userName},
#{userAge})
 </foreach>
*/

//getAllInsertSqlProperty函数如下
private String getAllInsertSqlProperty(final String prefix, List<TableFieldInfo> fieldList) {
	//这里newPrefix 就是"item."
	final String newPrefix = prefix == null ? EMPTY : prefix;
	return fieldList.stream()
			//i.getInsertSqlProperty("item.")是内置函数,假设i现在遍历到了user_name列
			//那么得到的就是"#{userName},"
			//然后,被删了
			//所以本来每个元素从TableFieldInfo变成了形如"#{userName}"的字符串
			.map(i -> i.getInsertSqlProperty(newPrefix).replace(",", ""))
			.filter(Objects::nonNull)
			//在元素间插入逗号和分行
			.collect(joining(COMMA + NEWLINE));
}
//对于User表,这个函数返回以下String
/*
#{userName},
#{userAge}
*/

然后执行如下

		//定义主键属性名
		String keyProperty = null;
		//定义主键列名
		String keyColumn = null;
		// 表包含主键处理逻辑,如果不包含主键当普通字段处理
		if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
			if (tableInfo.getIdType() == IdType.AUTO) {
				/* 自增主键 */
				keyGenerator = Jdbc3KeyGenerator.INSTANCE;
				keyProperty = tableInfo.getKeyProperty();
				// 去除转义符
				keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
			} else if (null != tableInfo.getKeySequence()) {
				keyGenerator = TableInfoHelper.genKeyGenerator(methodName, tableInfo, builderAssistant);
				keyProperty = tableInfo.getKeyProperty();
				keyColumn = tableInfo.getKeyColumn();
			}
		}
//这段代码没什么好说的,就是根据不同情况,得到三个变量
//keyGenerator keyProperty keyColumn 

然后执行如下

String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
//就是把表名user,列名片段,属性名片段,填入%s中,得到如下
/*
INSERT INTO user (user_name,
user_age) VALUES <foreach collection="list" item="item" separator=",">
(#{userName},
#{userAge})
 </foreach>
*/

然后执行如下

//这两句没有什么可说的,是重写injectMappedStatement函数的必要的操作
//自定义的内容就在于sql和主键
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, methodName, sqlSource, keyGenerator, keyProperty, keyColumn);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以使用JavaWeb来实现批量添加数据到MySQL数据库。下面是一个简单的示例代码: ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class BatchInsertExample { public static void main(String[] args) { Connection connection = null; PreparedStatement statement = null; try { // 连接数据库 String url = "jdbc:mysql://localhost:3306/mydatabase"; String username = "root"; String password = "password"; connection = DriverManager.getConnection(url, username, password); // 设置批量插入的SQL语句 String sql = "INSERT INTO mytable (name, age) VALUES (?, ?)"; statement = connection.prepareStatement(sql); // 批量插入数据 for (int i = 0; i < 10; i++) { statement.setString(1, "User " + i); statement.setInt(2, 20 + i); statement.addBatch(); } // 执行批量插入操作 int[] result = statement.executeBatch(); // 输出插入结果 System.out.println("成功插入了 " + result.length + " 条数据"); } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭连接和语句 try { if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } } } ``` 请确保您已经下载并安装了适合您数据库版本的MySQL驱动程序,并将其添加到您的项目中。在上述示例中,我们使用JDBC API来连接到MySQL数据库,并使用预编译语句和批量插入技术来执行批量插入操作。 您需要将`url`、`username`和`password`替换为您自己的数据库连接信息,并根据您的表结构修改SQL语句和参数设置部分。 这是一个简单的示例,您可以根据自己的实际需求进行修改和扩展。希望对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值