mybatis是如何集成到spring的之托管mapper接口

2023-11-05

前言

mybatis集成到spring可以参考spring mvc集成mybatis进行数据库访问 ,其中mybatis集成到spring最重要的两个配置分别是SqlSessionFactoryBean和MapperScannerConfigurer,如下所示:

<!--mybatis sqlSeesionFactory配置-->
		<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
			<property name="dataSource" ref="dataSource" />
			<property name="configLocation" value="classpath:mybatis-config.xml" />
			<property name="mapperLocations" value="classpath:mapper/*Mapper.xml" />
			<property name="typeAliasesPackage" value="com.gameloft9.demo.dataaccess.model" />
		</bean>

		<!--mapper自动扫描配置-->
		<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
			<property name="basePackage" value="com.gameloft9.demo.dataaccess.dao" />
			<!-- 这里不要定义sqlSessionFactory, 定义了会导致properties文件无法加载 -->
		</bean>

在之前的文章mybatis是如何集成到spring的之SqlSessionFactoryBean 中我们已经对SqlSessionFactoryBean有了初步的探讨,接下来我们继续追问mapper接口是如何被spring管理起来的
我们平时使用mapper进行数据库操作时十分简单,首先定义好Dao层的接口:

**
 * 用户Mapper
 * Created by gameloft9 on 2017/11/28.
 */
public interface UserMapper {
    /**
    * 根据主键查询用户信息
    */
    UserTest selectByPrimaryKey(String id);
}

然后在repository层通过@Autowire就可以自动注入并使用了:
在这里插入图片描述
多么神奇的魔法!但我们需要对其一探究竟。我们先根据现有的知识提出几个问题,然后带着问题找答案:

1、我并没有定义任何UserMapper的Bean,它是怎么自动创建的?

MapperScannerConfigurer

MapperScannerConfigurer是专门用来扫描mapper接口并创建对应的spring bean。它的结构如下所示:
在这里插入图片描述
MapperScannerConfigurer实现了两个非常重要的接口BeanDefinitionRegistryPostProcessor和InitializingBean,其中InitializingBean我们已经很熟悉了。BeanDefinitionRegistryPostProcessor用的人比较少:

BeanDefinitionRegistryPostProcessor可以在bean被初始化前,让你修改applicationContext内部的BeanDefinition,加一些你想要的东西。

由此可见,MapperScannerConfigurer里面对某些BeanDefinition动了手脚,我们进去看看它做了什么:
在这里插入图片描述
看来MapperScannerConfigurer比较懒,它把工作丢给了ClassPathMapperScanner来做了。由于我们只配置了basePackage,所以被叉掉的部分不用去管它,重点是这个scan方法(basePackage可以是用分隔符分割的包,这里做了简单的分割。)在看scan方法前,我们先简单整理下流程:
在这里插入图片描述
MapperScannerConfigurer主要是读取需要扫描mapper的包路径,然后对其做一个简单的检查,然后提供一个可以对BeanDefinition做修改的入口,具体的修改工作交给了ClassPathMapperScanner。

ClassPathMapperScanner

好像ClassPathMapperScanner才是真正做苦力的那位老实人,在spring扫描完我们提供的包路径并创建完BeanDefinition后,ClassPathMapperScanner开始对BeanDefinition进行魔改。其中最重要的两点就是把mapper的className传给了构造函数以及篡改了bean的class为MapperFactoryBean。
在这里插入图片描述
在这里插入图片描述
在修改完Bean定义后,根据spring Bean的生命周期,肯定会创建Bean的实例。我们再看看修改这两个东西有什么用?

MapperFactoryBean

MapperFactoryBean的类结构如下:
在这里插入图片描述
实际上看到FactoryBean基本就可以断定mapper的代理bean是通过getObject创建的,实际上也确实如此:
在这里插入图片描述
对FactoryBean不熟悉的同学这里再简单提一下:

Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象。
一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。

看到getSqlSession().getMapper(this.mapperInterface)是不是很熟悉了?后面就正式进去到mybatis内部创建mapper代理对象的流程里去了,spring和mybatis的连接就建立起来了!
现在可以回答刚才的问题了,BeanDefinition修改了BeanClass为MapperFactoryBean.class,那么创建的时候是MapperFactoryBean的实例,然后因为添加了构造函数入参为原始的接口,所以原始的mapper的class也一并传递了进去,这样mapperFactoryBean的mapperInterface字段就保存着原始的mapper接口名称。
又由于MapperFactoryBean实现了FactoryBean接口,所以获取mapper实例的时候,实际调用的是getObject方法,把真正要创建的mapper接口传给getMapper方法,这样原始的mapper的代理对象就可以通过mybatis创建了。

举一反三

1、MapperFactoryBean继承SqlSessionDaoSupport有什么用?

MapperFactoryBean继承了SqlSessionDaoSupport实际上有好几个作用:
1、最重要的,是可以获取SqlSession对象,这样才可以创建mapper的代理对象
2、做一些简单的检查,例如mapperInterface不能为空

2、篡改BeanDefinition和复杂bean初始化的延伸思考

mapper bean的动态创建,通过扫描包并修改BeanDefinition,然后自定义的复杂的初始化逻辑通过实现InitializingBean+FactoryBean接口来达到目的。
作者曾经做过一个支付项目,支付渠道系统开放出来的远程dubbo服务就一个:

/**
* 微信渠道服务
*/
public interface WxDubboService {
   // 服务调用,所有服务都走它,包括下单,查询,退款等操作
   // jsonStr里面有具体的操作的msgType,根据它组装不通过服务处理链路
   String invoke(String jsonStr);
}

内部具体业务怎么处理全部通过一个msgType来区分,具体的业务服务配置大概长这个样子:

<bean id="refundService" class="com.xx.xx.xx.BusinessService">
		<property name="msgType">
			<util:constant static-field="com.xx.xx.wx.config.WXConstant$MsgType.REFUND" />
		</property>
		<property name="targetSys">
			<util:constant static-field="com.xx.xx.framework.config.Constant$TargetSys.WX" />
		</property>
		<property name="name" value="微信交易退款" />
		<property name="requestClass">
			<bean class="com.xx.xx.wx.jsonbean.RefundRequest" />
		</property>
		<property name="filterTemplate" ref="refundFilterTemplate" />
		<property name="businessHandler" ref="refundServiceHandler" />
	</bean>

然后服务的调用实现实际上是根据不同的msgType,然后通过注册中心拿到具体的BusinessService进行处理。这样的好处显而易见,内部子系统调用接口不需要新增,只需要新增msgType和对应的BusinessService即可,减少了接口数量。坏处也很明显,内部调用的逻辑复杂,必须知道msgType才知道最后走的是哪个调用。排查问题和理解难度都比普通dubbo服务要高。从本篇文章我们能有什么启发呢?
通过对比,我们可以大致看到和mybatis的逻辑是类似的,底层通过统一的服务来处理所有的逻辑,然后需要对外支持不同的接口。因此,我们可以类似的创建不同接口的Bean,然后通过FactoryBean将其实现丢给原来统一过的调用逻辑。
首先类似MapperScannerConfigurer我们配置一个ServiceScannerConfigurer:

     <!--service自动扫描配置-->
	<bean class="com.gameloft9.demo.mgrframework.mapper.ServiceScannerConfigurer">
		<property name="basePackage" value="com.gameloft9.demo.service.api.out" />
	</bean>

basePackage就是我们要提供的服务所在的package,我们在out package里面写一个测试接口:

/**
 * 微信订单服务
 */
public interface WxRefundService {
    // 退款
    String refund(String json);
}

剩下的就是一样的套路,我们在ServiceScannerConfigurer篡改BeanDefinition:

public class ServiceScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean {
    // api包
    private String basePackage;

    public void afterPropertiesSet() throws Exception {
        notNull(this.basePackage, "Property 'basePackage' is required");
    }

    // 扫描api包,并篡改BeanDefitnition
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        ClassPathServiceScanner scanner = new ClassPathServiceScanner(registry);
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }
public class ClassPathServiceScanner extends ClassPathBeanDefinitionScanner {
    // Service工厂Bean
    private ServiceFactoryBean<?> serviceFactoryBean = new ServiceFactoryBean<Object>();

    public ClassPathServiceScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {
            logger.warn("No service was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

    // 篡改BeanDefinition 
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();

            // 保存原始interface
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            // 篡改BeanClass为serviceFactoryBean
            definition.setBeanClass(this.serviceFactoryBean.getClass());
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
    }

    // 注册过滤器
    public void registerFilters() {
        boolean acceptAllInterfaces = true;

         // 支持接口
        if (acceptAllInterfaces) {
            // default include filter that accepts all classes
            addIncludeFilter(new TypeFilter() {
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    return true;
                }
            });
        }

        // 去掉 package-info.java
        addExcludeFilter(new TypeFilter() {
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info");
            }
        });
    }

    // 保证接口可以注册BeanDefinition
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            logger.warn("Skipping ServiceFactoryBean with name '" + beanName
                    + "' and '" + beanDefinition.getBeanClassName() + "' serviceInterface"
                    + ". Bean already defined with the same name!");
            return false;
        }
    }
    
    public ServiceFactoryBean<?> getServiceFactoryBean() {
        return serviceFactoryBean;
    }

    public void setServiceFactoryBean(ServiceFactoryBean<?> serviceFactoryBean) {
        this.serviceFactoryBean = serviceFactoryBean;
    }
}

然后在ServiceFactoryBean中创建代理类:

public class ServiceFactoryBean<T> implements FactoryBean<T>, ApplicationContextAware {

    /**
     * 要代理的接口类
     */
    private Class<T> serviceInterface;

    // 可以通过context获取其他spring bean
    private ApplicationContext context;

    public ServiceFactoryBean() {
    }

    public ServiceFactoryBean(Class<T> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }

    // 创建代理对象
    public T getObject() throws Exception {
        // 内部服务注册中心
        ServiceRegister serviceRegister = (ServiceRegister ) context.getBean("serviceRegister");
        
        // 不管是直接走动态代理还是调用其他处理逻辑,背后一定少不了动态代理的东西
        return (T)newProxyInstance(
                ServiceFactoryBean.class.getClassLoader(),
                new Class[] { serviceInterface },
                new ServiceInterceptor(serviceRegister));
    }

    public Class<?> getObjectType() {
        return this.serviceInterface;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       context = applicationContext;
    }

    // 代理
    private class ServiceInterceptor implements InvocationHandler{

        private ServiceRegister serviceRegister;

        public ServiceInterceptor(ServiceRegister serviceRegister){
            this.serviceRegister= serviceRegister;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           // 请求参数
           JsonObject json = JSON.pareObject(args[0]);
          
            // 真正的服务处理
            BusinessService service = serviceRegister.get(json.get("msgType"));
            return service.invoke(args[0]);
        }
    }

    public Class<T> getServiceInterface() {
        return serviceInterface;
    }

    public void setServiceInterface(Class<T> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }
}

ServiceRegister 可以理解为一个HashMap,里面根据msgType注册了具体的业务处理类。我们来写一个测试类:

@Slf4j
@ActiveProfiles("local")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class WxRefundServiceTest {

    // 自动注入
    @Autowired
    WxRefundService wxRefundService;

    @Test
    public void test(){
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setMerOrderId("xxxx");
        orderEntity.setAmount(1L);
        orderEntity.setMemo("付款备注");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("msgType","refund");
        jsonObject.put("order",orderEntity);
        String result = wxRefundService.refund(jsonObject.toString());
        log.info("{}",result);
    }
}

在这里插入图片描述
可以看到我们只是写了一个接口,却可以通过@Autuwired把bean给注入成功,而且调用wxRefundService.refund比调用wxDubboService .invoke更容易理解了。(这里省略了dubbo服务的发布改造)

总结

mybatis集成到spring最重要的两个配置分别是SqlSessionFactoryBean和MapperScannerConfigurer,其中MapperScannerConfigurer帮助我们把mapper对象托管到spring。使得我们没有定义任何Mapper的Bean,却可以通过@Autowired进行注入,大大简化了我们开发的难度,让我们把重心放在了sql逻辑本身。
这是一种非常好的设计思路,当我们日常工作中想把类托管到spring但又无法定义Bean或者不好定义Bean的时候,可以采取这种解决方案。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

mybatis是如何集成到spring的之托管mapper接口 的相关文章

  • Java:如何从转义的 URL 获取文件?

    我收到了一个定位本地文件的 URL 事实上我收到的 URL 不在我的控制范围内 URL 按照 RFC2396 中的定义进行有效转义 如何将其转换为 Java File 对象 有趣的是 URL getFile 方法返回一个字符串 而不是文件
  • org.apache.sling.api.resource,version=[2.3,3) -- 无法解析

    您好 我无法访问我的项目内容 我已经上传了从 CQ 访问内容所需的所有包 我唯一能看到的是 org apache sling api resource version 2 3 3 无法解析 这是否是异常的原因 如果是 请告诉我如何解决 中Q
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • Android在排序列表时忽略大小写

    我有一个名为路径的列表 我目前正在使用以下代码对字符串进行排序 java util Collections sort path 这工作正常 它对我的 列表进行排序 但是它以不同的方式处理第一个字母的情况 即它用大写字母对列表进行排序 然后用
  • wait() 在游戏中如何工作?

    在 playframework 的文档中here http www playframework org documentation 1 2 1 asynchronous已写 public static void loopWithoutBlo
  • JavaFX 中具有自定义内容的 ListView

    How i can make custom ListView with JavaFx for my app I need HBox with image and 2 Labels for each line listView 您可以通过查看
  • Cassandra java驱动程序协议版本和连接限制不匹配

    我使用的java驱动程序版本 2 1 4卡桑德拉版本 dsc cassandra 2 1 10cql 的输出给出以下内容 cqlsh 5 0 1 Cassandra 2 1 10 CQL spec 3 2 1 Native protocol
  • Hibernate.createBlob() 方法从 Hibernate 4.0.1 开始已弃用,并移至 Hibernate.getLobCreator(Session session).createBlob()

    Method Hibernate createBlob 已弃用自休眠4 0 1并搬到Hibernate getLobCreator Session session createBlob 任何解决方案我应该在方法内传递什么getLobCrea
  • Hazelcast 分布式锁与 iMap

    我们目前使用 Hazelcast 3 1 5 我有一个简单的分布式锁定机制 应该可以跨多个 JVM 节点提供线程安全性 代码非常简单 private static HazelcastInstance hInst getHazelcastIn
  • hibernate锁等待超时超时;

    我正在使用 Hibernate 尝试模拟对数据库中同一行的 2 个并发更新 编辑 我将 em1 getTransaction commit 移至 em1 flush 之后我没有收到任何 StaleObjectException 两个事务已成
  • 匿名类上的 NotSerializedException

    我有一个用于过滤项目的界面 public interface KeyValFilter extends Serializable public static final long serialVersionUID 7069537470113
  • 普罗米修斯指标 - 未找到

    我有 Spring Boot 应用程序 并且正在使用 vertx 我想监控服务和 jvm 为此我选择了 Prometheus 这是我的监控配置类 Configuration public class MonitoringConfig Bea
  • Netty:阻止调用以获取连接的服务器通道?

    呼吁ServerBootstrap bind 返回一个Channel但这不是在Connected状态 因此不能用于写入客户端 Netty 文档中的所有示例都显示写入Channel从它的ChannelHandler的事件如channelCon
  • 我可以创建自定义 java.* 包吗?

    我可以创建一个与预定义包同名的自己的包吗在Java中 比如java lang 如果是这样 结果会怎样 这难道不能让我访问该包的受保护的成员 如果不是 是什么阻止我这样做 No java lang被禁止 安全管理器不允许 自定义 类java
  • HQL Hibernate 内连接

    我怎样才能在 Hibernate 中编写这个 SQL 查询 我想使用 Hibernate 来创建查询 而不是创建数据库 SELECT FROM Employee e INNER JOIN Team t ON e Id team t Id t
  • 将 Azure AD 高级自定义角色与 Spring Security 结合使用以进行基于角色的访问

    我创建了一个演示 Spring Boot 应用程序 我想在其中使用 AD 身份验证和授权 并使用 AD 和 Spring Security 查看 Azure 文档 我执行了以下操作 package com myapp contactdb c
  • 具有特定参数的 Spring AOP 切入点

    我需要创建一个我觉得很难描述的方面 所以让我指出一下想法 com x y 包 或任何子包 中的任何方法 一个方法参数是接口 javax portlet PortletRequest 的实现 该方法中可能有更多参数 它们可以是任何顺序 我需要
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String
  • 在 RESTful Web 服务中实现注销

    我正在开发一个需要注销服务的移动应用程序 登录服务是通过数据库验证来完成的 现在我陷入了注销状态 退一步 您没有提供有关如何在应用程序中执行身份验证的详细信息 并且很难猜测您在做什么 但是 需要注意的是 在 REST 应用程序中 不能有会话

随机推荐

  • 简单的线性单向链表

    数组的不足 我们之前用的数组也是一种数据结构 数组是顺序存储的 数组逻辑关系上相邻的两个元素在物理位置上也相邻 这就导致了在对数组进行插入或删除操作时 需移动大量数组元素 并且数组的长度是固定的 而且必须预先定义 数组的长度难以缩放 对长度
  • glut实现雪花动态效果

    glut实现雪花动态效果 实验题目 总体思路 3 2主要函数说明 按键操作 实验结果 实验题目 1 绘制雪花 2 在屏幕的多个随机位置绘制雪花 3 使每朵雪花绕自己的中心旋转 4 使每朵雪花下降 5 翻页键控制相机视野 按UP键增加物体与观
  • Vue中使用element-plus中的el-dialog定义弹窗-内部样式修改-v-model实现-demo

    效果图 实现代码
  • Three.js系列: 造个海洋球池来学习物理引擎

    github地址 https github com hua1995116 Fly Three js 大家好 我是秋风 继上一篇 Three js系列 游戏中的第一 三人称视角 今天想要和大家分享的呢 是做一个海洋球池 海洋球大家都见过吧 就
  • 北京突然宣布,元宇宙重大消息

    北京青年报记者从2022全球数字经济大会新闻发布会上了解到 2022全球数字经济大会将于7月28日至30日在国家会议中心举行 本届大会将聚焦绿色创新发展 数字贸易 数据价值化 全球规则治理等热点议题 深度探讨互联网3 0 数据要素 开源 5
  • JS的100道经典面试题(一)只看这四篇就够了,收藏起来以后偷偷看

    年轻人你不讲武德 耗子尾汁 总结就是为了形成自己的js知识网 提升自己 加油 开始干 1 介绍js的基本数据类型 答 Undefined Null Boolean Number String 2 js有哪些内置对象 答 数据封装类对象 Ob
  • 深度学习优化学习方法(一阶、二阶)

    深度学习优化学习方法总结 一阶为主 https blog csdn net sunflower sara article details 81321886 常用的优化算法 梯度下降法 牛顿法 拟牛顿法 共轭梯度法 二阶为主 https bl
  • Block底层原理读书笔记-《高级编程- iOS与OS多线程和内存管理》(更新中)

    1 一个Block 真正的底层都有些什么 Block会被解析成一个结构体 这里成为Block结构体 这个结构体里有 1 isa指针 说明Block的本质是一个对象 指向Stack 堆 2 有函数指针 这个函数指针指向一个函数体 该函数体的内
  • C# 企业微信接口发送消息出现错误代码60020解决方案,希望能给大家带来帮助。

    这是企业微信接口发送消息调用的代码源地址 https blog csdn net wanglui1990 article details 79744407 代码运行起来是没有问题的 但唯一出现的问题就是错误代码60020 点击企业微信 应用
  • 数据结构——单链表OJ题(第二弹)

    单链表OJ题 前言 一 返回链表开始入环的第一个节点 思路一 思路二 二 返回链表的深度拷贝 总结 前言 此次练习题有两道 有点小难度 但相信难不住大家的 我也会给出两道OJ题的链接 大家也赶快去试一试吧 一 返回链表开始入环的第一个节点
  • vue2.x与vue3.x中自定义指令详解

    目录 前言 一 自定义指令分类 二 Vue2 x自定义指令钩子函数 1 bind与update区别 2 update与componentUpdated区别 3 钩子函数的参数 4 局部自定义指令 5 全局自定义指令 6 简写形式 三 Vue
  • springboot下配置mybatis的call-setters-on-nulls属性

    使用Mybatis时 如果查询语句中某些字段值是null的 则这个字段就无法返回 对于后台数据处理来说 这是一个致命的问题 于是通过修改Mybatis的配置来解决这个问题 在springmvc下 在mybatis的配置文件里面增加以下配置即
  • C++ opencv处理kinect红外数据和彩色数据

    kinect好像已是明日黄花 但现在需要用这个做交互的人还不少 要做手势识别 于是入手一枚二手kinect2 0 入坑玩玩 做手势识别 直觉上要用opencv 从网上搜的资料来看 大多是通过openNi来操作kinect 而且要openNi
  • grpc-go源码剖析三十五之滑动窗口基本介绍以及整体流程图介绍?

    已发表的技术专栏 0 grpc go protobuf multus cni 技术专栏 总入口 1 grpc go 源码剖析与实战 文章目录 2 Protobuf介绍与实战 图文专栏 文章目录 3 multus cni 文章目录 k8s多网
  • 使用aircrack-ng套件破解wifi密码

    一 准备工作 1 需要有一个无线网卡 需要支持monitor模式 2 Kali系统 自行单独安装套件也可以 3 一个完善的密码字典 二 监听工作 首先将无线网卡连接到kali iwconfig 查看是否连接成功 airmon ng 可以查看
  • Vim 小技巧:自动写入文件头

    Vim 小技巧 情景一 自动写入文件头 在编写 C 程序时 总有一些东西会在每个头文件中出现 比如 ifndef lt File Name MACRO gt define lt File Name MACRO gt endif lt Fil
  • STM32H7 LwIP 主RAM选择 DTCM AXIRAM UDP 收发问题

    STM32H7 LwIP 主RAM选择 DTCM AXIRAM UDP 这段时间一直在调试STM32H743 期间掉进了不少坑 最大的坑还是网络这一块 例如LwIP移植 已经有前人踩过的坑 我以为我能避免 结果自己还是踩了 耽误了不少时间
  • Android --- 控件属性的属性值为 @null

    1 控件属性值为 null 1 RadioButton里面的属性android button null 是去掉前面的圆点 2 android background null 是控件自带的背景设为空
  • 《深入浅出数据分析》第九章——R语言

    文章目录 记录第一次接触R语言 一 R语言下载安装 二 运行 三 补充 1 加载csv文件 2 hist函数 记录第一次接触R语言 深入浅出数据分析 第九章讲到R语言 在这记录一下 就当给自己做的笔记 一 R语言下载安装 安装地址 http
  • mybatis是如何集成到spring的之托管mapper接口

    前言 mybatis集成到spring可以参考spring mvc集成mybatis进行数据库访问 其中mybatis集成到spring最重要的两个配置分别是SqlSessionFactoryBean和MapperScannerConfig