MyBatis + SQL Server Using Table-Valued Parameters
一、实现原理
参考文档
- 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相关推荐
- springmvc+mybatis+sql server实现简单登录功能
一.源码: 1.Users.java package com.login.entity;import java.io.Serializable;public class Users implement ...
- SQL Server: create table sql script
---摇奖observeh数据库设计 Function getSpace lottery /* -- Author:geovindu 涂聚文 -- Date: 20180427 为了自写生成代码.根据 ...
- Reindex SQL Server DB table
DBCC DBReindex(TableName, '', 90) Or ALTER INDEX ALL ON TableName REBUILD WITH (FILLFACTOR = 90, SOR ...
- 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 ...
- Spring Boot 集成 MyBatis和 SQL Server实践
文章共 509字,阅读大约需要 2分钟 ! 概 述 Spring Boot工程集成 MyBatis来实现 MySQL访问的示例我们见过很多,而最近用到了微软的 SQL Server数据库,于是本文则给 ...
- 使用SSIS包导入SQL Server FILESTREAM数据
初始配置 (Initial configuration) We have been exploring the SQL Server FILESTREAM feature in this ongoin ...
- 如何将数据从Excel文件导入SQL Server数据库
There are many ways to import data from an Excel file to a SQL Server database using: 有多种方法可以使用以下方法将 ...
- sql oracle 自增长字段,在Oracle、MySQL、MS SQL Server中创设自动增长字段
在Oracle.MySQL.MS SQL Server中创建自动增长字段 好吧,今天面试有道题,要各个数据库怎么建立自增长字段,顺便复习一下吧,最近面试很多数据库问题... 一:Oracle Orac ...
- mysql外键设置sql语句,SQL Server 2008之SQL语句外键
xin3721网络学院为广大学员,准备了丰富了教学视频.为了更好的让大学配合视频进行学习,拓展学员的知 识面,我站特整理了大量的,技术文章,供学员参考.因此本教案需配合视频教程学习, 视频教程地址为: ...
最新文章
- android 代码设置inputtype,android – 如何正确设置EditText的InputType?
- ImageMagick之PDF转换成图片(image)
- windows下 nginx安装 使用
- 居然还有这种游戏...是不是有点刺激过头了啊...
- JSValidation 配置文件
- 使用SpringBoot的jackson包进行实体类型转换
- Adaboost算法原理分析和实例+代码(简明易懂)
- C# 中的委托和事件[转]
- python爬取数据保存为csv时生成编号_将爬取到到数据以CSV格式存储
- python模拟太阳系_模拟太阳系8大行星运行图:matplotlib实现
- clojure 开发工具_Clojure Web开发–最新技术–第2部分
- ubuntu 12.10 安装 QQ2012
- iOS用户行为追踪——无侵入埋点
- 惊!Linux居然可以这样破解WiFi密码,竟然是?
- 2G是GSM 3G是CDMA 4G是LTE,5G制式?
- mysql 客户端SSL错误2026 (HY000)
- 论文翻译[Deep Residual Learning for Image Recognition]
- 采用OKR框架如何使组织敏捷
- 2022年全球市场电子用软铁氧体粉末总体规模、主要生产商、主要地区、产品和应用细分研究报告
- Java Integer128陷阱详解
热门文章
- 使用css3做0.5px的细线
- ZOJ 2592 Think Positive ——(xjbg)
- SparkListener监听使用方式及自定义的事件处理动作
- 奇瑞汽车用鸿蒙,奇瑞配鸿蒙,是自我放弃还是独辟蹊径?
- pid算法中位置型和增量型有什么区别,分析两者优缺点
- 声道切换 android,[RK3288][Android6.0] Audio中的单声道到双声道的转换处理过程
- ICCV:Robust Multi-Modality Multi-Object Tracking鲁棒多模态多目标跟踪
- 解决Lombok版本过低导致的编译出错问题(You aren‘t using a compiler supported by lombok)
- Skynet 通过组播(Multicast)实现一个简单的世界频道
- 华南农业大学 计算机科学与技术,2016年华南农业大学计算机科学与技术专业最低分是多少?...