一、实现原理

参考文档

  • Using table-valued parameters
  • System requirements for the JDBC driver
  • Microsoft JDBC Driver for SQL Server

1、微软官方封装了 JDBC 驱动 jar 包,提供 SQLServerDataTable 类;

2、Mybatis 官方提供自定义类型处理接口 TypeHandler ,可实现自定义类型与参数的绑定关系;

3、通过实现 Collection 类装载数据记录,如定义 List<User> users 来装载 User 表中的记录,那么 users 相当于一张中间表;

4、在实现 TypeHandler 接口时,对 mapper.xml 中 SQL 模板的参数进行赋值,此处可以注入 SQLServerDataTable 类作为执行参数;

5、在 mapper.xml 中的 SP 语句指定第 N 个参数处理器为自定义的 TypeHandler ,在 mybatis 解析 SQL 模板时不再使用基础类型处理器 BaseTypeHandler ,否则会抛出 UNKNOW 异常。

二、开发环境

  • java 1.8
  • docker mcr.mocrosoft.com/mssql/server:2019-latest

三、Pom 依赖

主要添加 Spring MVC、Mybatis、JDBC、Jackson 依赖

 <!-- https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc --><dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><version>6.2.0.jre8</version></dependency><!-- java.time.LocalDateTime 支持,并在 mapper 中启用--><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId><version>2.13.0</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>3.4.3.1</version></dependency><!-- sqlserver --><dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>sqljdbc4</artifactId><version>4.0</version></dependency><!-- 注解 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- jackson 2 databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.3</version></dependency><!-- jackson 2 core --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.13.3</version></dependency><!-- jackson 2 annotations --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.13.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>

四、Yml 配置

配置数据源和 mybatis 映射

spring:datasource:url: jdbc:sqlserver://127.0.0.1:1434;DatabaseName=TestDBusername: rootpassword: rootdriver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
mybatis:mapper-locations: classpath:mapper/*.xml

五、自定义 Type

CREATE TYPE [dbo].[MyTableType] AS TABLE([MyKey] [VARCHAR](50) NOT NULL,[MyValue] [VARCHAR](50) NOT NULL
)

六、自定义存储过程

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GOCREATE PROCEDURE [dbo].[MybatisTestPro]
@MyTable MyTableType READONLY,
@Code varchar(10) OUT,
@Msg varchar(10) OUTAS
SET NOCOUNT ON;SELECT MyKey,MyValue FROM @MyTableSELECT @Code = '200', @Msg = 'Success'GO

七、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.example.demo.dao.TestDao"><resultMap id="Map1" type="java.util.HashMap"></resultMap><select id="proTest" resultType="java.util.List" resultMap="Map1" parameterType="Map" statementType="CALLABLE">{CALL MyBatisTestPro(#{MyTable,mode=IN,jdbcType=OTHER,typeHandler=com.example.demo.utils.MyTableTypeHandler},#{Code,mode=OUT,jdbcType=VARCHAR}, #{Msg,mode=OUT,jdbcType=VARCHAR})}</select></mapper>

八、TestDao

package com.example.demo.dao;import org.apache.ibatis.annotations.Mapper;import java.util.List;
import java.util.Map;@Mapper
public interface TestDao {List<Map<String,Object>> proTest(Map<String,Object> map);}

九、Implement TypeHandler

package com.example.demo.utils;import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;/*** 实现处理器:表变量传参* <p>* 不需要实现 get 方法,返回的结果集就是表数据*/@MappedJdbcTypes({JdbcType.OTHER})  // 对应数据库类型
@MappedTypes({ArrayList.class})     // java 数据类型
public class MyTableTypeHandler implements TypeHandler<ArrayList<?>> {@Overridepublic void setParameter(PreparedStatement ps, int i, ArrayList<?> parameter, JdbcType jdbcType) throws SQLException {ps.setObject(i, SQLServerDataTableFactory.getSqlServerDataTableInstance(parameter));}@Overridepublic ArrayList<?> getResult(ResultSet rs, String columnName) throws SQLException {return null;}@Overridepublic ArrayList<?> getResult(ResultSet rs, int columnIndex) throws SQLException {return null;}@Overridepublic ArrayList<?> getResult(CallableStatement cs, int columnIndex) throws SQLException {return null;}
}

十、SQLServerDataTableFactory

package com.example.demo.utils;import com.example.demo.data.MyTable;
import com.microsoft.sqlserver.jdbc.SQLServerDataTable;
import com.microsoft.sqlserver.jdbc.SQLServerException;import java.sql.Types;
import java.util.ArrayList;/*** SQL 表值参数工厂**/
public class SQLServerDataTableFactory {private final static String DOT_SPLIT = "\\.";private final static String MY_TABLE = "MyTable";private final static String MY_DATA_FIELD_KEY = "MyKey";private final static String MY_DATA_FIELD_VALUE = "MyValue";/*** 返回临时表实例** @param dataList 泛型对象数组* @return SQLServerDataTable* @throws SQLServerException SQL 异常*/public static SQLServerDataTable getSqlServerDataTableInstance(ArrayList<?> dataList) throws SQLServerException {if (dataList != null && dataList.size() > 0) {String className = dataList.get(0).getClass().getName();String[] split = className.split(DOT_SPLIT);className = split[split.length - 1];switch (className) {case MY_TABLE: {return getMyTableInstance(dataList);}default: {return null;}}}return null;}/*** 返回 MyData 类型的临时表** @param dataList 泛型对象数组* @return SQLServerException* @throws SQLServerException SQL 异常*/private static SQLServerDataTable getMyTableInstance(ArrayList<?> dataList) throws SQLServerException {// 定义临时表SQLServerDataTable serverDataTable = new SQLServerDataTable();serverDataTable.addColumnMetadata(MY_DATA_FIELD_KEY, Types.VARCHAR);serverDataTable.addColumnMetadata(MY_DATA_FIELD_VALUE, Types.VARCHAR);// 使用并行流往表中写入数据dataList.stream().parallel().forEach(p -> {MyTable data = (MyTable) p;try {serverDataTable.addRow(data.getMyKey(), data.getMyValue());} catch (SQLServerException e) {e.printStackTrace();}});// 返回临时表return serverDataTable;}}

十一、MyTable

package com.example.demo.data;import lombok.Data;/*** 自定义数据类型** @author Jiansheng Ma* @since 2022/11/17 16:28*/
@Data
public class MyTable {private String MyKey;private String MyValue;
}

十二、模拟数据

 private ArrayList<MyTable> getMyDataArrayList(int cap) {System.out.println("生成模拟数据");ArrayList<MyTable> list = new ArrayList<MyTable>(cap);for (int i = 0; i < cap; i++) {MyTable data = new MyTable();data.setMyKey(UUID.randomUUID().toString());data.setMyValue(UUID.randomUUID().toString());list.add(data);}return list;}
public List<Map<String, Object>> testPro() throws JsonProcessingException {Map<String, Object> map = new HashMap<String, Object>(3);map.put("MyTable", getMyDataArrayList(100));// 调用  SPSystem.out.println("===> 开始执行存储过程:" + LocalDateTime.now().toString());List<Map<String, Object>> res = testDao.proTest(map);System.out.println("===> 执行存储过程结束:" + LocalDateTime.now().toString());// 获取响应结果集System.out.println("===> 结果集大小:" + res.size());System.out.println(JacksonUtil.toJsonString(res.get(0)));// 获取响应参数System.out.println("===> 通用响应参数");System.out.println("Code :" + map.get("Code").toString());System.out.println("Msg :" + map.get("Msg").toString());return res;}

十三、JacksonUtil

package com.example.demo.utils;import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;/*** jackson 封装** @author yushanma* @since 2022/8/13 10:54*/@Component("jacksonUtil")
public class JacksonUtil {private static final Logger logger = LogManager.getLogger(JacksonUtil.class.getName());private static ObjectMapper mapper = new ObjectMapper();;/*** 初始化 jackson 配置*/@PostConstructprivate static void init() {// 如果 json 中有新增的字段并且是实体类类中不存在的,不报错mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);// 在反序列化时忽略在 json 中存在但 Java 对象不存在的属性mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 在序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZmapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);// java.time.LocalDateTime 支持mapper.registerModule(new JavaTimeModule());// 在序列化时忽略值为 null 的属性//mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);// 忽略值为默认值的属性//mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);logger.debug("JacksonUtil Component Init Done.");}/*** Obj 转 JsonString** @param o obj* @return obj 的 json 序列化字符串* @throws JsonProcessingException json 处理异常*/public static String toJsonString(Object o) throws JsonProcessingException {if (o == null) {logger.warn("Obj 对象为空!");return "";} else {return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(o);}}/*** JsonString 转 Obj** @param jsonString Json 字符串* @param valueType Obj 类型* @param <T> 泛型* @return T 对象* @throws JsonProcessingException json 处理异常*/public static <T> T toObject(String jsonString, Class<T> valueType) throws JsonProcessingException {if (jsonString == null || jsonString.isEmpty()) {logger.warn("Json String 为空!");return null;} else {return mapper.readValue(jsonString, valueType);}}/*** InputStream 转 Obj* @param inputStream 输入流* @param valueType Obj 类型* @param <T> 泛型* @return T 对象* @throws IOException IO 异常*/public static <T> T toObject(InputStream inputStream, Class<T> valueType) throws IOException {if (inputStream == null) {logger.warn("输入流为空!");return null;} else {return mapper.readValue(inputStream, valueType);}}}

十四、测试

十五、特别说明

使用表值参数严重影响 SQL 性能,请尽量避免使用

MyBatis + SQL Server Using Table-Valued Parameters相关推荐

  1. springmvc+mybatis+sql server实现简单登录功能

    一.源码: 1.Users.java package com.login.entity;import java.io.Serializable;public class Users implement ...

  2. SQL Server: create table sql script

    ---摇奖observeh数据库设计 Function getSpace lottery /* -- Author:geovindu 涂聚文 -- Date: 20180427 为了自写生成代码.根据 ...

  3. Reindex SQL Server DB table

    DBCC DBReindex(TableName, '', 90) Or ALTER INDEX ALL ON TableName REBUILD WITH (FILLFACTOR = 90, SOR ...

  4. sql out apply_在SQL Server中CROSS APPLY和OUTER APPLY之间的区别

    sql out apply SQL Server supports table valued functions, what are functions that return data in the ...

  5. Spring Boot 集成 MyBatis和 SQL Server实践

    文章共 509字,阅读大约需要 2分钟 ! 概 述 Spring Boot工程集成 MyBatis来实现 MySQL访问的示例我们见过很多,而最近用到了微软的 SQL Server数据库,于是本文则给 ...

  6. 使用SSIS包导入SQL Server FILESTREAM数据

    初始配置 (Initial configuration) We have been exploring the SQL Server FILESTREAM feature in this ongoin ...

  7. 如何将数据从Excel文件导入SQL Server数据库

    There are many ways to import data from an Excel file to a SQL Server database using: 有多种方法可以使用以下方法将 ...

  8. sql oracle 自增长字段,在Oracle、MySQL、MS SQL Server中创设自动增长字段

    在Oracle.MySQL.MS SQL Server中创建自动增长字段 好吧,今天面试有道题,要各个数据库怎么建立自增长字段,顺便复习一下吧,最近面试很多数据库问题... 一:Oracle Orac ...

  9. mysql外键设置sql语句,SQL Server 2008之SQL语句外键

    xin3721网络学院为广大学员,准备了丰富了教学视频.为了更好的让大学配合视频进行学习,拓展学员的知 识面,我站特整理了大量的,技术文章,供学员参考.因此本教案需配合视频教程学习, 视频教程地址为: ...

最新文章

  1. android 代码设置inputtype,android – 如何正确设置EditText的InputType?
  2. ImageMagick之PDF转换成图片(image)
  3. windows下 nginx安装 使用
  4. 居然还有这种游戏...是不是有点刺激过头了啊...
  5. JSValidation 配置文件
  6. 使用SpringBoot的jackson包进行实体类型转换
  7. Adaboost算法原理分析和实例+代码(简明易懂)
  8. C# 中的委托和事件[转]
  9. python爬取数据保存为csv时生成编号_将爬取到到数据以CSV格式存储
  10. python模拟太阳系_模拟太阳系8大行星运行图:matplotlib实现
  11. clojure 开发工具_Clojure Web开发–最新技术–第2部分
  12. ubuntu 12.10 安装 QQ2012
  13. iOS用户行为追踪——无侵入埋点
  14. 惊!Linux居然可以这样破解WiFi密码,竟然是?
  15. 2G是GSM 3G是CDMA 4G是LTE,5G制式?
  16. mysql 客户端SSL错误2026 (HY000)
  17. 论文翻译[Deep Residual Learning for Image Recognition]
  18. 采用OKR框架如何使组织敏捷
  19. 2022年全球市场电子用软铁氧体粉末总体规模、主要生产商、主要地区、产品和应用细分研究报告
  20. Java Integer128陷阱详解

热门文章

  1. 使用css3做0.5px的细线
  2. ZOJ 2592 Think Positive ——(xjbg)
  3. SparkListener监听使用方式及自定义的事件处理动作
  4. 奇瑞汽车用鸿蒙,奇瑞配鸿蒙,是自我放弃还是独辟蹊径?
  5. pid算法中位置型和增量型有什么区别,分析两者优缺点
  6. 声道切换 android,[RK3288][Android6.0] Audio中的单声道到双声道的转换处理过程
  7. ICCV:Robust Multi-Modality Multi-Object Tracking鲁棒多模态多目标跟踪
  8. 解决Lombok版本过低导致的编译出错问题(You aren‘t using a compiler supported by lombok)
  9. Skynet 通过组播(Multicast)实现一个简单的世界频道
  10. 华南农业大学 计算机科学与技术,2016年华南农业大学计算机科学与技术专业最低分是多少?...