MyBatis + SQL Server Using Table-Valued Parameters

news2024/9/28 21:26:58

一、实现原理

参考文档

  • 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=TestDB
    username: root
    password: root
    driver-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
GO

CREATE PROCEDURE [dbo].[MybatisTestPro] 
@MyTable MyTableType READONLY,
@Code varchar(10) OUT,
@Msg varchar(10) OUT

AS
SET NOCOUNT ON;

SELECT MyKey,MyValue FROM @MyTable

SELECT @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<?>> {

    @Override
    public void setParameter(PreparedStatement ps, int i, ArrayList<?> parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, SQLServerDataTableFactory.getSqlServerDataTableInstance(parameter));
    }

    @Override
    public ArrayList<?> getResult(ResultSet rs, String columnName) throws SQLException {
        return null;
    }

    @Override
    public ArrayList<?> getResult(ResultSet rs, int columnIndex) throws SQLException {
        return null;
    }

    @Override
    public 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));

        // 调用  SP
        System.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 配置
     */
    @PostConstruct
    private 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.SSSZ
        mapper.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 性能,请尽量避免使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/96864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python学习笔记-Pygame

目录 一、Pygame概述 1.安装Pyganme 2.Pygame常用模块介绍 2.1 display模块常用方法 2.2 pygame.event模块常用方法 2.3 Surface对象的常用方法 记述关于Pyganme开发的基本知识。 一、Pygame概述 Pygame是跨平台的python模块&#xff0c;转为电子游戏设计&#xff08;包…

Vue打包后的不同版本解析

vue源码打包版本 这里选取我们开发中常见的几个版本进行说明。 1、vue(.runtime).global(.prod).js 在html页面中通过 <script src“...”> 标签直接使用。通过CDN引入和npm下载的Vue就是这个版本。会暴露一个全局的Vue来使用。&#xff08;.runtime&#xff09;和&…

2022年12月python的字符串常用操作

字符串在整整个开发的过程中&#xff0c;使用频率相对来说是较高的。 在此总结几个字符串的常用操作&#xff0c; 字符串的操作&#xff0c;转换后即生成为新字符串 【长度统计 切片&#xff1a; 【 根据索引进行切片str[开始索引:结束索引:步长] 根据指定标识符进行切片str.sp…

【码极客精讲】桶排序

桶排序 (Bucket sort)或所谓的箱排序&#xff0c;是一个排序算法&#xff0c;工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序&#xff08;有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序&#xff09;。桶排序是鸽巢排序的一种归纳结果。当要被排…

【硬币识别】形态学硬币计数【含Matlab源码 683期】

⛄一、硬币图像识别简介 本设计为硬币图像识别统计装置&#xff0c;通过数码相机获取平铺无重叠堆积的硬币的图像&#xff0c;并通过Matlab工具处理后统计硬币的数目。 1 图像格式转换 取的图像格式为RGB彩色图像&#xff0c;需要先将其转换为8位256级的灰度图像。本程序采用…

SAP Gateway 里的 REST 概念

SAP Gateway 有助于轻松配置和使用 SAP Business Suite 系统的业务逻辑和内容&#xff0c;用于移动和 Web 应用程序。它降低了访问 SAP 数据所需的复杂性和技能组合&#xff0c;从而消除了部署障碍。使用简单的界面有助于缩短开发时间。 SAP Gateway 使以人为本的应用程序能够…

【笔记:模拟CMOS集成电路】MOS特性仿真分析

【笔记&#xff1a;模拟CMOS集成电路】MOS特性仿真分析前言一、电路图二、电路仿真&#xff08;1&#xff09;Ids与Vds的关系仿真仿真结果仿真结果分析&#xff08;2&#xff09;Ids与Vgs的关系仿真仿真结果仿真结果分析前言 本文为本人学习模拟集成电路相关知识的的学习笔记&a…

USB接口WIFI(MT7601芯片)的驱动源码移植过程详解(驱动源码编译、wpa_supplicant工具交叉编译、文件系统移植)

1、MT7601的移植步骤 (1)确认你的WT7601网卡硬件是正常的&#xff1b; (2)修改驱动源码&#xff0c;依赖内核源码树编译并加载&#xff1b; (3)交叉编译wpa_supplicant工具&#xff0c;移植到根文件系统里&#xff1b; (4)添加驱动和wpa_supplicant工具依赖的配置文件&#xff…

2022 年时间序列分析最顶流的 Python 库

时间序列分析是一种强大的工具&#xff0c;可用于从数据中提取有价值的信息并对未来事件进行预测。它可用于识别趋势、季节性模式和变量之间的其他关系。时间序列分析还可用于预测未来事件&#xff0c;例如销售、需求或价格变动。 如果你在 Python 中处理时间序列数据&#xf…

数据库实验三:完整性语言实验

实验三 完整性语言实验 实验 3.1 实体完整性实验 1.实验目的 ​ 掌握实体完整性的定义和维护方法。 2.实验内容和要求 ​ 定义实体完整性&#xff0c;删除实体完整性。能够写出两种方式定义实体完整性的SQL语句&#xff1b;创建表时定义实体完整性、创建表后定义实体完整性…

C++ Reference: Standard C++ Library reference: Containers: map: map: key_comp

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/key_comp/ 公有成员函数 <map> std::map::key_comp key_compare key_comp() const;返回键比较对象 返回容器用于比较键的比较对象的副本。map对象的比较对象在构造&#xff08;construction&#xff…

kubernetes 挂载传播

kubernetes 挂载传播 kubernetes 的 mountPropagation 翻译成中文就是挂载传播。挂载传播提供了共享卷挂载的能力, 它允许在同一个 Pod, 甚至同一个节点内, 在多个容器之间共享卷的挂载。 说白了就是在容器或 host 内的挂载目录中 再 mount 了一个别的挂载。 kubernetes 中 卷…

第18章 条件概率

第18章 条件概率 18.1蒙特霍尔困惑 对于上一章的三个门的问题&#xff0c;有一个漏洞。假设参赛者选择门A且门B后有一只山羊&#xff0c;刚好产生3个结果&#xff1a; 以上结果出现的概率分别是1/18,1/18,1/9。 在这些结果中&#xff0c;只有最后一个结果(C,A,B)&#xff0c…

Redis常见面试题(三)

目录 1、Redis String值最大存储多少? 2、Redis事务有什么用? 3、Redis事务相关的命令有哪几个? 4、Redis事务是原子性的吗? 5、Redis持久化有什么用? 6、Redis有哪几种持久化方式? 7、Redis持久化方式如何选择? 8、如何保证Redis中的数据都是热点数据? 9、Red…

vue前后端分离项目打包成app,部署成移动端

将vue项目打包成app,在手机上运行。 1. vue打包 npm run build 先将vue的前端项目打包成dist文件夹 2. 安装hbuilderX Hbuilder官网地址 3. hbuilderX 1&#xff09;新建项目 我是vue的&#xff0c;所以直接选择的h5app&#xff0c;然后起个名字&#xff0c;选择路径。 2…

微服务实用篇6-分布式搜索elasticsearch篇2

今天我们继续学习分布式搜索引擎elasticsearch&#xff0c;今天主要学习四个模块&#xff0c;分别为DSL查询文档&#xff0c;搜索结果处理&#xff0c;RestClient查询文档&#xff0c;还有最好演示一个旅游案例。下面开始今天的学习吧。 目录 一、DSL查询文档 1.1、DSL查询分…

Hadoop学习----软件安装

Hadoop源码下载重新编译 软件下载&#xff1a;https://hadoop.apache.org/releases.html 建议是下载源码包。 源码包和官方编译安装包有什么不一样呢&#xff1f; 正常情况下&#xff0c;非生产环境直接使用官方编译安装包即可&#xff0c;但是官方提供的安装包不支持本地库。…

ANTLR4入门(二):图示说明eclipse安装Antlr4IDE插件的过程

如果你能正常通过Eclipse Market找到antlr4的插件并正常安装&#xff0c;可以忽略本文。 如果不能&#xff0c;那多半是因为网络问题导致安装Antlr4IDE插件时无法下载文件造成的。我就遇到了这个问题&#xff0c;无法下载的原因很复杂&#xff0c;我不想去深究了&#xff0c;我…

WPF/XAML关于x:key和x:name的区别,全面解读超详细

x:key和x:name的区别 x:Keyx:Name用于xaml Resources&#xff0c;ResourceDictionary用在ResourceDictionary以外任何地方使用key访问xaml指定对象使用name访问xaml对象标识资源创建和引用&#xff0c;存在于 ResourceDictionary 中的元素唯一标识对象元素&#xff0c;以便于从…

【Spring】SpringCloud

目录 一、SpringCloud 二、微服务介绍 1.系统架构演变 1.1 单体应用架构 1.2 垂直应用架构 1.3 分布式架构 1.4 SOA架构&#xff08;面向服务的架构&#xff09; 1.5 微服务架构&#xff08;服务的原子化拆分&#xff09; 2.微服务架构介绍 2.1 问题&#xff1a; 2.2…