Mybatis 学习笔记1:构造方法注入

news2024/9/25 3:27:14

Mybatis 构造方法注入

最近阅读 Mybatis 3.5.8-SNAPSHOT 版本源码,在调试过程中遇到如下异常:

org.apache.ibatis.builder.BuilderException:
    Error in result map 'MyEmployee.empResultMap'.
    Failed to find a constructor in 'MyEmployee' by arg names [id, name, company]. 

堆栈信息如下:

Caused by: org.apache.ibatis.builder.BuilderException: Error in result map 'MyEmployee.empResultMap'. Failed to find a constructor in 'MyEmployee' by arg names [id, name, company]. There might be more info in debug log.
	at org.apache.ibatis.mapping.ResultMap$Builder.build(ResultMap.java:134)
	at org.apache.ibatis.builder.MapperBuilderAssistant.addResultMap(MapperBuilderAssistant.java:208)
	at org.apache.ibatis.builder.ResultMapResolver.resolve(ResultMapResolver.java:47)
	at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:348)
	at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:262)
	at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElements(XMLMapperBuilder.java:254)
	at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:127)
	... 2 more

分析异常原因,在 ResultMap.Builder 构建 ResultMap 对象时,会调用 argNamesOfMatchingConstructor() 方法检验实体类中是否存在 constructorResultMappings 集合对应的构造方法,即检验实体类构造参数的个数、参数名、参数类型是否和 constructorResultMappings 集合的长度和集合中 resultMapping 元素的 property 属性和 javaType 属性(即 mapper.xml 文件中 constructor 元素的子元素个数、子元素的 name 属性、javaType 属性)一致,如果不一致就会抛出 BuilderException。

构造参数名不匹配
调试发现上图 157 行代码根据实体类构造方法获得的参数名集合为 [arg0, arg1, arg2],和 constructorResultMappings 参数提供的参数名集合 [id, name, company] 不一致,因此抛出异常。

MyEmployee、MyCompany 实体类如下:
注意:MyEmployee 构造方法并没有使用 @Param 注解。

public class MyEmployee {
    private Integer id;
    private String name;
    private MyCompany company;
    // 构造方法参数并没有使用@Param注解
    public MyEmployee(Integer id, String name, MyCompany company) {
        this.id = id;
        this.name = name;
        this.company = company;
    }
}

public class MyCompany {
    private Integer id;
    private String name;
}

MyEmployee.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="MyEmployee">
    <resultMap id="empResultMap" type="MyEmployee">
        <constructor>
            <!--如果是构造属性,根据 type 和 name 属性获取 javaType-->
            <!--如果没有配置 typeHandler 属性,在解析出 javaType 之后,
                org.apache.ibatis.mapping.ResultMapping.Builder.build() 方法
                会调用 resolveTypeHandler() 方法,
                resolveTypeHandler() 方法根据 javaType 和 jdbcType
                从 TypeHandlerRegistry 中查找对应的 TypeHandler-->
            <idArg column="emp_id" name="id"/>
            <arg column="emp_name" name="name"/>
            <arg name="company" resultMap="comResultMap"/>
        </constructor>
    </resultMap>

    <resultMap id="comResultMap" type="MyCompany">
        <!--如果不是构造属性,根据 type 和 property 属性获取 javaType-->
        <id column="com_id" property="id"/>
        <result column="com_name" property="name"/>
    </resultMap>
</mapper>

阅读 Mybatis 官方文档,在构造方法一节,内容如下:

构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBean 属性来完成注入,但有一些人更青睐于通过构造方法进行注入。constructor 元素就是为此而生的。

当你在处理一个带有多个形参的构造方法时,很容易搞乱 arg 元素的顺序。版本 3.4.3 开始,可以在指定参数名称的前提下,以任意顺序编写 arg 元素。
为了通过名称来引用构造方法参数,你可以:

  1. 添加 @Param 注解
  2. 使用 ‘-parameters’ 编译选项并启用 useActualParamName 选项(默认开启)来编译项目。
private List<String> getArgNames(Constructor<?> constructor) {
    // ...
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        String name = null;
        for (Annotation annotation : paramAnnotations[paramIndex]) {
            if (annotation instanceof Param) {
                // 获取构造方法参数的@Param注解的value值
                name = ((Param) annotation).value();
                break;
            }
        }
        if (name == null && resultMap.configuration.isUseActualParamName()) {
            if (actualParamNames == null) {
                // 调用java.lang.reflect.Executable#getParameters()方法
                actualParamNames = ParamNameUtil.getParamNames(constructor);
            }
            if (actualParamNames.size() > paramIndex) {
                name = actualParamNames.get(paramIndex);
            }
        }
        paramNames.add(name != null ? name : "arg" + paramIndex);
    }
    return paramNames;
}

在构造方法参数没有使用 @Param 注解的情况下,ResultMap.Builder#getArgNames(Constructor) 方法会调用 java.lang.reflect.Executable#getParameters() 方法,代码如下:

public Parameter[] getParameters() {
    // Need to copy the cached array to prevent users from messing
    // with it.  Since parameters are immutable, we can
    // shallow-copy.
    return privateGetParameters().clone();
}

private Parameter[] privateGetParameters() {
    // Use tmp to avoid multiple writes to a volatile.
    Parameter[] tmp = parameters;
    if (tmp == null) {
        // Otherwise, go to the JVM to get them
        try {
            tmp = getParameters0();
        } catch(IllegalArgumentException e) {
        }
        // If we get back nothing, then synthesize parameters
        if (tmp == null) {
            tmp = synthesizeAllParams();
        }
    }
    return tmp;
}

private Parameter[] synthesizeAllParams() {
    final int realparams = getParameterCount();
    final Parameter[] out = new Parameter[realparams];
    for (int i = 0; i < realparams; i++)
        out[i] = new Parameter("arg" + i, 0, this, i);
    return out;
}

getParameters0
上图 getParameters0() 方法并没有从 JVM 中获取到构造方法的参数列表,于是调用 synthesizeAllParams() 方法生成参数名为 [arg0, arg1, arg2] 的参数列表,也就导致和集合 [id, name, company] 不一致,因此抛出异常。

正如官方文档所说,需要使用 “-parameters” 选项来编译项目。

javac -parameters MyEmployee.java MyCompany.java
javap -v MyEmployee.class

使用 “-parameters” 选项编译生成的 class 文件如下:

public class MyEmployee
    minor version: 0
    major version: 54
    flags: (0x0021) ACC_PUBLIC, ACC_SUPER
    this_class: #5                          // MyEmployee
    super_class: #6                         // java/lang/Object
    interfaces: 0, fields: 3, methods: 1, attributes: 1
Constant pool:
     #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
     #2 = Fieldref           #5.#21         // MyEmployee.id:Ljava/lang/Integer;
     #3 = Fieldref           #5.#22         // MyEmployee.name:Ljava/lang/String;
     #4 = Fieldref           #5.#23         // MyEmployee.company:LMyCompany;
     #5 = Class              #24            // MyEmployee
     #6 = Class              #25            // java/lang/Object
     #7 = Utf8               id
     #8 = Utf8               Ljava/lang/Integer;
     #9 = Utf8               name
    #10 = Utf8               Ljava/lang/String;
    #11 = Utf8               company
    #12 = Utf8               LMyCompany;
    #13 = Utf8               <init>
    #14 = Utf8               (Ljava/lang/Integer;Ljava/lang/String;LMyCompany;)V
    #15 = Utf8               Code
    #16 = Utf8               LineNumberTable
    #17 = Utf8               MethodParameters
    #18 = Utf8               SourceFile
    #19 = Utf8               MyEmployee.java
    #20 = NameAndType        #13:#26        // "<init>":()V
    #21 = NameAndType        #7:#8          // id:Ljava/lang/Integer;
    #22 = NameAndType        #9:#10         // name:Ljava/lang/String;
    #23 = NameAndType        #11:#12        // company:LMyCompany;
    #24 = Utf8               MyEmployee
    #25 = Utf8               java/lang/Object
    #26 = Utf8               ()V
{
    public MyEmployee(java.lang.Integer, java.lang.String, MyCompany);
        descriptor: (Ljava/lang/Integer;Ljava/lang/String;LMyCompany;)V
        flags: (0x0001) ACC_PUBLIC
        Code:
            stack=2, locals=4, args_size=4
                0: aload_0
                1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                4: aload_0
                5: aload_1
                6: putfield      #2                  // Field id:Ljava/lang/Integer;
                9: aload_0
               10: aload_2
               11: putfield      #3                  // Field name:Ljava/lang/String;
               14: aload_0
               15: aload_3
               16: putfield      #4                  // Field company:LMyCompany;
               19: return
            LineNumberTable:
                line 24: 0
                line 25: 4
                line 26: 9
                line 27: 14
                line 28: 19
        MethodParameters:
            Name                           Flags
            id
            name
            company
}
SourceFile: "MyEmployee.java"

相比于不使用 “-parameters” 选项编译生成的 class 文件,

  1. 常量池多出了一项 CONSTANT_Utf8_info #17:
    #17 = Utf8               MethodParameters
    
  2. 构造方法多出了一个属性 MethodParameters:
    MethodParameters:
        Name                           Flags
        id
        name
        company
    

com.sun.tools.javac.jvm.ClassWriter#writeMethod(MethodSymbol) 方法代码如下:

void writeMethod(MethodSymbol m) {
    // ...
    // 如果设置了“-parameters”选项,调用writeMethodParametersAttr()方法
    if (options.isSet(PARAMETERS)) {
        if (!m.isLambdaMethod()) // Per JDK-8138729, do not emit parameters table for lambda bodies.
            acount += writeMethodParametersAttr(m);
    }
    // ...
}

int writeMethodParametersAttr(MethodSymbol m) {
    // ...
    if (m.params != null && allparams != 0) {
        // 将“MethodParameters”字符串写入常量池并返回常量池索引
        final int attrIndex = writeAttr(names.MethodParameters);
        databuf.appendByte(allparams);
        // ...
        // Now write the real parameters
        for (VarSymbol s : m.params) {
            final int flags =
                ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) |
                ((int) m.flags() & SYNTHETIC);
            // 将方法参数的名称写入常量池,并将常量池索引写入class文件字节流
            databuf.appendChar(pool.put(s.name));
            // 将方法参数的flag写入class文件字节流
            databuf.appendChar(flags);
        }
        // ...
        endAttr(attrIndex);
        return 1;
    } else
        return 0;
}

Java 前端编译

com.sun.tools.javac.main.Arguments 表示 Java 前端编译(javac 命令行和 Compiler API)的选项和参数

public class Arguments {
    private Set<String> classNames;// classNames集合
    private Set<Path> files;// java源文件路径集合
    private final Options options;// javac选项集合
}

枚举类 com.sun.tools.javac.main.Option 定义了编译的选项

// 枚举值 PARAMETERS
PARAMETERS("-parameters","opt.parameters", STANDARD, BASIC)
// 构造方法
Option(String text, String descrKey, OptionKind kind, OptionGroup group) {
        this(text, null, descrKey, kind, group, null, null, ArgKind.NONE);
    }
// 构造方法
private Option(String text,// 选项名称
               String argsNameKey,
               String descrKey,
               // 0.OptionKind.STANDARD
               // 表示该选项是标准选项,由“javac -help”注释
               // 1.OptionKind.EXTENDED
               // 表示该选项是扩展选项,由“javac -X”注释
               // 2.OptionKind.HIDDEN
               // 表示该选项是隐藏选项,没有注释
               OptionKind kind,
               // 0.OptionGroup.BASIC
               // 表示该选项是基本选项,javac命令行和Compiler API均支持
               // 1.OptionGroup.FILEMANAGER
               // 表示该选项由JavaFileManager支持,其他FileManager可能不支持
               // 2.OptionGroup.INFO
               // 表示该选项用于请求信息,例如“-help”、“-version”
               // 3.OptionGroup.OPERAND
               // 表示该选项用于指定java源文件路径或className
               OptionGroup group,
               // 0.ChoiceKind.ONEOF
               // 表示该选项的值是choices中的某一个
               // 1.ChoiceKind.ANYOF
               // 表示该选项的值是choices中的一个或多个
               ChoiceKind choiceKind,
               Set<String> choices,
               // 0.ArgKind.NONE
               // 表示该选项没有值
               // 1.ArgKind.REQUIRED
               // 表示该选项和值之间使用“:”或“=”连接
               // 2.ArgKind.ADJACENT
               // 表示该选项和值相邻(使用空格连接)
               ArgKind argKind) {
        this.names = text.trim().split("\\s+");
        this.primaryName = names[0];
        this.argsNameKey = argsNameKey;
        this.descrKey = descrKey;
        this.kind = kind;
        this.group = group;
        this.choiceKind = choiceKind;
        this.choices = choices;
        this.argKind = argKind;
    }

com.sun.tools.javac.util.Context 表示编译的上下文环境

未完待续…

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

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

相关文章

python基于flask企业会议交换机设备维修批量运维管理系统设计与实现6py09

Python 中存在众多的 Web 开发框架&#xff1a;Flask、Django、Tornado、Webpy、Web2py、Bottle、Pyramid、Zope2 等。近几年较为流行的&#xff0c;大概也就是 Flask 和 Django 了解决的思路&#xff1a; &#xff08;1&#xff09;通过进行需求分析&#xff0c;建立用例模型&…

ansible学习使用

1、ansible官网 官方文档 https://docs.ansible.com/ansible/latest/index.html ansible github页面 https://github.com/ansible/ansible 安装手册&#xff1a;https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html 2、ansible架构简述 …

JavaScrpt_11 Web API 事件流、委托事件、其它事件、元素尺寸与位置

JavaScrpt_11 Web API 事件流、委托事件、其它事件、元素尺寸与位置 前言一、事件流1. 捕获和冒泡2. 阻止冒泡 二、委托事件三、其它事件1. 页面加载事件 2. 元素滚动事件 3. 页面尺寸事件 四、元素尺寸与位置 前言 进一步学习 事件进阶&#xff0c;实现更多交互的网页特效&…

手写CDN基本原理

本文已收录于专栏 《中间件合集》 目录 追本溯源概念说明需求分析核心功能代码实现A用户模块本地DNS模块CDN服务模块缓存服务模块原服务端 总结提升 追本溯源 CDN的发展源于对互联网内容传输速度和用户体验的需求。在互联网发展初期&#xff0c;内容的传输主要依赖于源服务器&a…

Unity/Shader 零碎知识点

坐标系 Unity使用的是左手坐标系&#xff1b;观察空间&#xff0c;通俗来讲就是以摄像机为原点的坐标系&#xff0c;摄像机的前向是z轴的负方向&#xff0c;与模型和世界空间中的定义相反&#xff0c;z轴的坐标减少意味着场景深度的增加 点积 abba|a||b|cos<a,b> 结果为常…

联想ThinkPad如何用U盘重装系统?联想ThinkPad用U盘重装系统教程

联想ThinkPad如何用U盘重装系统&#xff1f;当联想ThinkPad出现系统问题或需要更新操作系统时&#xff0c;使用U盘进行重装是一种快速而可靠的解决方案&#xff0c;使用U盘重装系统不仅简单易行&#xff0c;还能有效避免出现磁盘损坏或网络不稳定所带来的问题&#xff0c;那具体…

零零信安-DD数据泄露报警日报【第203期】

2023.07.10共发现匿名网络资讯信息83,544条&#xff1b;最近7天同比增长-22.2%&#xff1b;最近30天共发现匿名网络资讯信息2,578,034条。 北京零零信安科技有限公司成立于2020年&#xff0c;是国内首家专注于外部攻击面管理&#xff08;EASM&#xff09;的网络安全公司。基于…

DragGAN 离线版V1.3 解压就能用-DragGAN Windows GUI

一、DragGAN是什么 DragGAN是由Max Planck研究所开发的一种新的人工智能工具&#xff0c;它允许用户通过几个点击和拖动来真实地修改照片。DragGAN模型本质上是给各种GAN开发的一种交互式图像操作方法&#xff0c;实现了通过鼠标拉伸图像即可自动生成新图像的神奇功能。用户先…

Shader Graph22-卡通风格(上)

一、打开Unreal&#xff0c;新建Material命名为DemoToonPaint。 二、0.5、0.5、0.5为模拟的光线方向&#xff0c;VertexNormalWS为法线的世界坐标&#xff0c;Dot点积之后得到指定光线方向分布&#xff0c;Multiply(,0.5)和Add(,0.5)是去掉负值。Texture我们需要用photoshop制作…

flutter开发实战-长链接WebSocket使用stomp协议stomp_dart_client

flutter开发实战-长链接WebSocket使用stomp协议stomp_dart_client 在app中经常会使用长连接进行消息通信&#xff0c;这里记录一下基于websocket使用stomp协议的使用。 一、stomp&#xff1a;流文本定向消息协议 1.1 stomp介绍 stomp&#xff0c;Streaming Text Orientate…

chatGLM2中的Multi Query Attention

目录 原理简介 代码实现和耗时比较 总结分析 近期一直在玩大模型&#xff0c;对中文支持比较好的就是清华的chatGLM&#xff0c;目前chatGLM由v1升级到了chatGLM2。在gihub上介绍信息如下&#xff1a; 试用了一下&#xff0c;效果和速度确实有所提升。 这个得益于chatGLM2应…

pnpm + workspace + changesets

pnpm workspace changesets 构建你的 monorepo 工程 什么是monorepo&#xff1f; 什么是 monorepo&#xff1f;以及和 multirepo 的区别是什么? 关于这些问题&#xff0c;在之前的一篇**介绍 lerna** 的文章中已经详细介绍过&#xff0c;感兴趣的同学可以再回顾下。 简而…

nacos学习积累

官方文档&#xff1a;https://nacos.io/zh-cn/docs/quick-start.html 1 注册中心简介 注册中心对比和选型&#xff1a;Zookeeper、Eureka、Nacos、Consul和ETCD 如果消费者直接连接的提供者。这样做的问题是&#xff0c;若提供者出现宕机&#xff0c;或消费者存在高并发情况&…

SSRF漏洞

前言 作者简介&#xff1a;不知名白帽&#xff0c;网络安全学习者。 博客主页&#xff1a;不知名白帽的博客_CSDN博客-网络安全,CTF,内网渗透领域博主 网络安全交流社区&#xff1a;https://bbs.csdn.net/forums/angluoanquan 目录 SSRF漏洞原理 产生CSRF的函数 SSRF中常见手…

MySQL原理探索——27 主库出问题了,从库怎么办

在前面的第24、25和26篇文章中&#xff0c;介绍了 MySQL 主备复制的基础结构&#xff0c;但这些都是一主一备的结构。 大多数的互联网应用场景都是读多写少&#xff0c;因此你负责的业务&#xff0c;在发展过程中很可能先会遇到读性能的问题。而在数据库层解决读性能问题&#…

Atlassian Bamboo Enterprise Crack

Atlassian Bamboo Enterprise Crack Bamboo Server是专业组织如持续集成、安装和运输的选择。 从代码到安装的连续运输。 在一个工作流中集中发布自动生成、测试和发布。 构建&#xff1a;专注于编码&#xff0c;依靠Bamboo作为自己的CI&#xff0c;构建一个主机!创建多阶段构建…

3.8.cuda运行时API-使用cuda核函数加速yolov5后处理

目录 前言1. Yolov5后处理2. 后处理案例2.1 cpu_decode2.2 gpu_decode 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习精简…

【*2200线段树Pushup】CF1567 E

Problem - E - Codeforces 题意&#xff1a; 思路&#xff1a; 维护这些信息即可 Code&#xff1a; #include <bits/stdc.h>#define int long longusing namespace std;const int mxn2e510; const int mxe2e510; const int mod1e97; const int Inf1e18;struct info{in…

【C语言】gcc编译时报错 fatal error: stdio.h: 没有那个文件或目录

零、问题 在Ubuntu20.04.6中使用GCC编译一个HelloWorld代码时遇到如下问题&#xff1a; 首先确认了&#xff0c;自己单词没有拼写错。 然后再检查GCC的版本&#xff0c;确实没问题&#xff1a; 我用的是Ubuntu20.04.6的版本。 壹、解决 没有标准的头文件需要安装build-es…

和鲸社区数据分析每周挑战【第九十七期:技术博客文本分析】

和鲸社区数据分析每周挑战【第九十七期&#xff1a;技术博客文本分析】 文章目录 和鲸社区数据分析每周挑战【第九十七期&#xff1a;技术博客文本分析】一、背景描述二、数据说明三、问题描述四、数据导入五、数据探索性分析六、对文章标题进行文本分类预测1、数据预处理2、逻…