您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

彻底搞清mybaties plus实现原理

时间:2022-11-18 10:16:56  来源:今日头条  作者:奋斗的架构师

讨论主题

主要想搞清楚几个问题

  1. MyBatis plus是依赖了mybatis,他们之间的关系是什么?
  2. mybatis plus中“字段自动填充功能”实现源码,包括id自动生成的原理。
  3. mybatis plus 基本的增删改查为什么不用写sql?以及sql注入器的原理。

本篇文章需要理解mybaties的源码为基础,否则看本篇文章会吃力。mybaties源码分析可以看我上一篇文章,如下 彻底看懂springboot mybaties源码流程

mybaties plus是依赖了mybaties,他们之间的关系是什么?

mybatis plus是基于mybatis实现的,下面来具体看看他们直接的关系,以及是怎样依赖的?

首先看mybatis plus自动配置类中的核心方法sqlSessionFactory,如下:

MybatisPlusAutoConfiguration

public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // mybaties plus使用 MybatisSqlSessionFactoryBean ,mybaties使用SqlSessionFactoryBean
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        ApplyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }
        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }
        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
        if (this.properties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
        }
        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            factory.setTypeHandlers(this.typeHandlers);
        }
        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }
        this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.languageDrivers)) {
            factory.setScriptingLanguageDrivers(this.languageDrivers);
        }
        Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);
        // 提供了可定制MybatisSqlSessionFactoryBean的类,如果你想定制它,你可以自定定义SqlSessionFactoryBeanCustomizer类型的类。
        applySqlSessionFactoryBeanCustomizers(factory);

        // mybaties plus定义的全局的配置类,供后续方便使用。
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        // 从spring容器中获取定义的MetaObjectHandler类型的实例,并设置到globalConfig供后续使用,这个就是
        // 字段自动填充功能的类。
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        // 从spring容器中获取IKeyGenerator类型实例(主要是实现主键生成器),并设置到globalConfig供后续使用
        this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));
        // 从spring容器中获取ISqlInjector类型实例(Sql注入器),并设置到globalConfig供后续使用
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        //  从spring容器中获取IdentifierGenerator类型实例(主要是实现ID生成器),并设置到globalConfig供后续使用
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        // 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
        factory.setGlobalConfig(globalConfig);
        return factory.getObject();
    }

可以看到mybatis plus使用
MybatisSqlSessionFactoryBean (mybaties中使用的是SqlSessionFactoryBean),然后针对mybatis plus进行了一系列的初始化操作,并把相关的实例都设置到了GlobalConfig中,这块是mybatis plus扩展的初始化位置。

mybaties plus定制了mybaties很多核心类,总结如下:

mybaties plus

mybaties

功能描述

MybatisSqlSessionFactoryBean

SqlSessionFactoryBean

调用buildSqlSessionFactory创建SqlSessionFactory类

MybatisConfiguration

Configuration

用于描述 MyBatis 主配置文件信息,MyBatis 框架在启动时自动配置类中,会加载mapper配置文件,将配置信息转换为 Configuration 对象,然后把该对象传入给sqlSessionFactory供后续使用

MybatisMapperAnnotationBuilder

MapperAnnotationBuilder

解析Mapper方法中用注解方式定义的sql。

MybatisMapperRegistry

MapperRegistry

Mapper注册器,其实就是加入到一个内部数组中。

MybatisParameterHandler

DefaultParameterHandler

用于处理 SQL 中的参数占位符,为参数占位符设置值

上面是mybaties plus扩展mybaties的核心类。

答疑时刻

mybatis plus扩展了mybatis的很多功能,添加了很多实用功能。比如最主要的基于对象的增删改查(不需要写sql),基于雪花算法的ID生成器,字段自动填充功能,逻辑删除功能等。

mybatis plus中“字段自动填充功能”实现源码,包括id自动生成的原理。

字段自动填充功能可以干什么?

在开发过程中表中经常会建创建时间,创建人,更新时间,更新人字段,正常自己维护这几个字段的时候,插入数据的时候需要自己给创建时间创建人赋值。更新数据的时候需要自己给更新时间,更新人字段赋值。

mybaties plus “字段自动填充功能”就是来解决这个问题的,可以在插入数据的时候指定要更新哪些字段,更新数据的时候指定更新哪些字段。用起来还是很方便的。

下面是使用例子:

public class User {
    // 注意!这里需要标记为填充字段
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT)
    private String createUserName;
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
    @TableField(fill = FieldFill.UPDATE)
    private String updateUserName;
}

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); 
        this.strictInsertFill(metaObject, "createUserName", () -> "wanglining", String.class);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class);
        this.strictInsertFill(metaObject, "updateUserName", () -> "wanglining", String.class)
     }
}

主要两步:

  1. 在对象上标记要填充的字段。
  2. 定义字段填充handler。

经过上面两个步骤,自动填充功能就完成了,当你调用mybaties plus的BaseMapper中提供的insert方法的时候,会给createTime,createUserName两个字段填充你在handler中指定的值。相应的调用BaseMapper提供的update方法的时候,会给updateTime,updateUserName两个字段填充你在handler中指定的值。

注意:一定得是调用BaseMapper中提供的方法,如果你自己再xml中定义sql语句是不会有作用的。

实现原理

下面开始讲解它的实现原理。

在上面提到mybaties plus定制了参数处理器(ParameterHandler),mybaties plus中的实现为MybatisParameterHandler,它的作用是“用于处理 SQL 中的参数占位符,为参数占位符设置值”, mybaties plus就是用在jdbc真正执行sql前,通过MetaObjectHandler给对象相应字段赋值。进而mybaties把对象解析到了sql中进行执行。

MybatisParameterHandler

private void process(Object parameter) {
        if (parameter != null) {
            TableInfo tableInfo = null;
            Object entity = parameter;
            if (parameter instanceof Map) {
                // 处理单参数使用注解标记的时候,尝试提取et来获取实体参数
                Map<?, ?> map = (Map<?, ?>) parameter;
                if (map.containsKey(Constants.ENTITY)) {
                    Object et = map.get(Constants.ENTITY);
                    if (et != null) {
                        entity = et;
                        tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
                    }
                }
            } else {
                tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
            }
            if (tableInfo != null) {
                //到这里就应该转换到实体参数对象了,因为填充和ID处理都是针对实体对象处理的,不用传递原参数对象下去.
                MetaObject metaObject = this.configuration.newMetaObject(entity);
                if (SqlCommandType.INSERT == this.sqlCommandType) {
                    // 这个方法实现了给主键ID赋值的,根据你指定的策略生成id值,并赋值给主键ID。
                    populateKeys(tableInfo, metaObject, entity);
                    // 这里会进一步调用上面定义的handler里面的方法
                    insertFill(metaObject, tableInfo);
                } else {
                    // 这里会进一步调用上面定义的handler里面的方法
                    updateFill(metaObject, tableInfo);
                }
            }
        }
    }
    protected void insertFill(MetaObject metaObject, TableInfo tableInfo) {
        // 这里会先获取到你上面定义的MyMetaObjectHandler。
        GlobalConfigUtils.getMetaObjectHandler(this.configuration).ifPresent(metaObjectHandler -> {
            // 判断openInsertFill是否为true,模式的就是返回true
            // isWithInsertFill判断是否为true,这个方法返回的就是TableInfo类中的withInsertFill属性
            // TableInfo就是dao层对象以及字段注解解析出来的一个对应数据库表的对象,其中withInsertFill属性
            // 就是根据类的字段上是否有@TableField(fill = FieldFill.INSERT)注解,如果有那么withInsertFill
            // 最终会是true
            if (metaObjectHandler.openInsertFill() && tableInfo.isWithInsertFill()) {
                // 调用MyMetaObjectHandler的insertFill方法
                metaObjectHandler.insertFill(metaObject);
            }
        });
    }
    // 这个方法就不写注释了,跟上面一样。
    protected void updateFill(MetaObject metaObject, TableInfo tableInfo) {
        GlobalConfigUtils.getMetaObjectHandler(this.configuration).ifPresent(metaObjectHandler -> {
            if (metaObjectHandler.openUpdateFill() && tableInfo.isWithUpdateFill()) {
                metaObjectHandler.updateFill(metaObject);
            }
        });
    }


metaObjectHandler.insertFill会调用上面自己定义的MyMetaObjectHandler的insertFill方法,然后会继续调用strictInsertFill方法。

分析下strictInsertFill方法如下:

MetaObjectHandler

    default MetaObjectHandler strictInsertFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        return strictFill(true, tableInfo, metaObject, strictFills);
    }
    /**
     * 严格填充,只针对非主键的字段,只有该表注解了fill 并且 字段名和字段属性 能匹配到才会进行填充(null 值不填充)
     *
     * @param insertFill  是否验证在 insert 时填充
     * @param tableInfo   cache 缓存
     * @param metaObject  metaObject meta object parameter
     * @param strictFills 填充信息
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictFill(boolean insertFill, TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        // 先过滤表上withInsertFill或withUpdateFill属性是否为true,其实就是看dao层对应的类属性上是否有
        // @TableField(fill = FieldFill.INSERT)或@TableField(fill = FieldFill.UPDATE)注解
        if ((insertFill && tableInfo.isWithInsertFill()) || (!insertFill && tableInfo.isWithUpdateFill())) {
            strictFills.forEach(i -> {
                final String fieldName = i.getFieldName();
                final Class<?> fieldType = i.getFieldType();
                tableInfo.getFieldList().stream()
                    // 过滤对象字段,把字段上有@TableField(fill = FieldFill.INSERT)或@TableField(fill = FieldFill.UPDATE)注解
                    // 的字段过滤出来。
                    .filter(j -> j.getProperty().equals(fieldName) && fieldType.equals(j.getPropertyType()) &&
                        ((insertFill && j.isWithInsertFill()) || (!insertFill && j.isWithUpdateFill()))).findFirst()
                    // 针对过滤出来的字段赋值。
                    .ifPresent(j -> strictFillStrategy(metaObject, fieldName, i.getFieldVal()));
            });
        }
        return this;
    }

上面分析完了MybatisParameterHandler处理字段自动填充流程的核心逻辑,是从它的方法process说起的,那这个方法又是怎么被触发的?怎么和mybaties执行流程对接上的?

下面是mybaties 查询方法完成的调用时序图

 

上面重点关注下MybatisParameterHandler类的方法,主要两步:

  1. 在执行器进行具体jdbc操作前,先初始化了StatementHandler,StatementHandler里面会创建MybatisParameterHandler,在MybatisParameterHandler的构造方法中最终会调用process方法,进而为对象进行字段填充操作。
  2. 在prepareStatement阶段,jdbc执行prepare操作完成后会返回一个Statement,StatementHandler会调用MybatisParameterHandler的setParameters方法,解析dao层对象属性,并把参数值设置到prepare返回的Statement中。

经过上面两步ParameterHandler的任务就完成了。

mybatis plus 基本的增删改查为什么不用写sql?以及sql注入器的原理。

mybatis plus通过定义sql注入器实现了此功能。核心类

DefaultSqlInjector

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        Stream.Builder<AbstractMethod> builder = Stream.<AbstractMethod>builder()
            // 注入BaseMapper的insert方法
            .add(new Insert())
            // 注入BaseMapper的delete方法
            .add(new Delete())
            .add(new DeleteByMap())
            .add(new Update())
            .add(new SelectByMap())
            .add(new SelectCount())
            .add(new SelectMaps())
            .add(new SelectMapsPage())
            .add(new SelectObjs())
            // 注入BaseMapper的selectList方法
            .add(new SelectList())
            .add(new SelectPage());
        if (tableInfo.havePK()) {
            builder.add(new DeleteById())
                .add(new DeleteBatchByIds())
                .add(new UpdateById())
                .add(new SelectById())
                .add(new SelectBatchByIds());
        } else {
            logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
                tableInfo.getEntityType()));
        }
        return builder.build().collect(toList());
    }

这里以insert举例看下Insert内部实现。

Insert

/**
 * 插入一条数据(选择字段插入)
 *
 * @author hubin
 * @since 2018-04-06
 */
public class Insert extends AbstractMethod {

    public Insert() {
        super(SqlMethod.INSERT_ONE.getMethod());
    }

    /**
     * @param name 方法名
     * @since 3.5.0
     */
    public Insert(String name) {
        super(name);
    }

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
        // INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>nINSERT INTO %s %s VALUES %sn</script>")
        // INSERT_ONE枚举里面定义了sql脚本,以及BaseMapper中对应方法名
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        // 通过dao类对应表信息,获取表中的列并且拼“接插入sql”的列部分
        String columnScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlColumnMaybeIf(null),
            LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
        // 通过dao类对应表信息,获取表中的列并且拼接“插入sql”的值部分
        String valuesScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlPropertyMaybeIf(null),
            LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        // 获取主键部分的属性和对应列信息
        if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主键 */
                keyGenerator = Jdbc3KeyGenerator.INSTANCE;
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else if (null != tableInfo.getKeySequence()) {
                keyGenerator = TableInfoHelper.genKeyGenerator(this.methodName, tableInfo, builderAssistant);
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            }
        }
        // 通过上面获取的插入sql脚本,以及列和值部分,拼接成最终的sql脚本字符串。
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        // 通过上面的信息生成对应的MapperStatement,并且注册到mybaties容器中。供后续真正调用BaseMapper对应方法
        // 的时候再取出来使用。
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }

通过上面的代码,可以想象到mybaties plus初始化过程中肯定会调用到Insert的injectMappedStatement方法,把它对应的MapperStatement注入到mybaties容器中,然后后续通过动态代理调用BaseMapper相关方法的时候就可以根据MapperStatement对应的信息去执行对应的sql了。

现在分析下injectMappedStatement方法是怎么被调用到的?

调用时序图如下:

 

这里关注下Insert的injectMappedStatement调用流程。

这里再重点分析MybatisMapperRegistry的addMapper代码,因为它的信息量比较大。如下:

MybatisMapperRegistry

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // TODO 如果之前注入 直接返回
                return;
                // TODO 这里就不抛异常了
//                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
                // 对于mybaties plus用的MybatisMapperProxyFactory,mybaties用MapperProxyFactory
                // 这句代码很重要,把动态代理工厂加入到了knownMappers中,这里的type是你定义的Mapper类。
                // 下面的getMapper会通过MybatisMapperProxyFactory生成对应mapper的动态代理,进而执行mapper
                // 的各个方法。
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                // TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder
                // 解析Mapper定义的方法中用注解方式定义的sql。这里最终会在你定义的Mapper类中添加mybaties plus
                // 为你注入的Insert,Delete ,Update,Select等方法。其实就是把对应的MapperStatement注入到了Mybaties中。
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    @Override
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
        // fix https://github.com/baomidou/mybatis-plus/issues/4247
        MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            // 上面addMapper会在knownMappers添加对应的Factory
            mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.entrySet().stream()
                .filter(t -> t.getKey().getName().equals(type.getName())).findFirst().map(Map.Entry::getValue)
                .orElseThrow(() -> new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry."));
        }
        try {
            // 生成mapper对应的动态代理对象
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

答疑时刻

mybatis plus定义了增删改查对应的sql注入器,在解析注册mapper的时候,会把相应的方法作为MapperStatement注入到mybaties中。看起来没有定义对应的xml文件,其实是mybaties plus用sql注入器的方式,在代码中默认都提供了对应的sql了。

后面当你调用Mapper对应方法的时候,会通过对应mapper的动态代理获取到方法对应的MapperStatement,进而再通过执行器进行一系列的处理,最终执行该方法对应的sql。


通过上面的原理分析,也可以注入自己的sql,比如批量插入方法,mybaties plus默认没有提供该方法,你可以自己定义一个BaseMapper然后扩展出这个批量插入的方法,下一篇文章再讲解吧。



Tags:   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
讨论主题主要想搞清楚几个问题 mybatis plus是依赖了mybatis,他们之间的关系是什么? mybatis plus中“字段自动填充功能”实现源码,包括id自动生成的原理。 mybatis plus 基本...【详细内容】
2022-11-18  Tags:   点击:(0)  评论:(0)  加入收藏
之前推荐了很多很多的优质软件,那么这些软件都在哪里下载呢(其实我有给到网站)?不过呢,有些版本,还是需要有特定的下载站,更多的选择,可以让我们找到更适合自己的软件版本,今天给大家...【详细内容】
2022-11-18  Tags:   点击:(1)  评论:(0)  加入收藏
一、集群与资源启动顺序 启动流程步骤层次梳理第一层:OHASD 启动: cssdagent - 负责启动 CSSD 的 Agent。 orarootagent - 负责启动所有 root 用户下的 ohasd 资源 的Agent...【详细内容】
2022-11-18  Tags:   点击:(1)  评论:(0)  加入收藏
缘由什么是 KMS ?要了解 KMS,首先要了解 DRM。DRM 是 Direct Rendering Manager 的缩写,最初只是用来支持 GPU 的,最初它负责: Initialize GPU card, load its firmware, etc. ...【详细内容】
2022-11-18  Tags:   点击:(3)  评论:(0)  加入收藏
大家好,我是 Echa。本文将带你了解 Javascript 中常见的错误类型,处理同步和异步 JavaScript/Node.js 代码中错误和异常的方式,以及错误处理最佳实践!1. 错误概述JavaScript 中...【详细内容】
2022-11-18  Tags:   点击:(2)  评论:(0)  加入收藏
BusyBox 现在越来越流行,特别是在 docker 用户中,许多 Docker 镜像使用 BusyBox 为您提供最小镜像。如果您认为 Linux 命令是理所当然的,这可能会让许多用户感到特别困惑,您认为...【详细内容】
2022-11-18  Tags:   点击:(1)  评论:(0)  加入收藏
在日常开发过程中,你是不是经常回到搜索引擎,搜索某个功能的实现方式,比如:Javascript 数组排序、正则表达式等等。今天给大家推荐的这个网站,就可以满足大家的需求,它叫:30seconds...【详细内容】
2022-11-18  Tags:   点击:(2)  评论:(0)  加入收藏
前言准确点说,这不是《从零打造项目》系列的第一篇文章,模版代码生成的那个项目讲解算是第一篇,当时就打算做一套项目脚手架,为后续进行项目练习做准备。因时间及个人经验问题...【详细内容】
2022-11-18  Tags:   点击:(2)  评论:(0)  加入收藏
作者 | 褚杏娟近日,GitHub 前 CTO Jason Warner 在推特上表示,“我确信过去十年中,最大的架构错误之一就是全面使用微服务。”从单体应用到微服务的规划顺序,Warner 的建议是:单...【详细内容】
2022-11-18  Tags:   点击:(3)  评论:(0)  加入收藏
Alex 发自 凹非寺量子位 | 公众号 QbitAIHTML不是编程语言,但这并不妨碍前端工程师把它玩出花儿来。比如,有人就拿HTML来访问用户的相机&mdash;&mdash;在网页上,点击按钮即可直...【详细内容】
2022-11-17  Tags:   点击:(4)  评论:(0)  加入收藏
▌简易百科推荐
讨论主题主要想搞清楚几个问题 mybatis plus是依赖了mybatis,他们之间的关系是什么? mybatis plus中“字段自动填充功能”实现源码,包括id自动生成的原理。 mybatis plus 基本...【详细内容】
2022-11-18  奋斗的架构师  今日头条  Tags:   点击:(0)  评论:(0)  加入收藏
缘由什么是 KMS ?要了解 KMS,首先要了解 DRM。DRM 是 Direct Rendering Manager 的缩写,最初只是用来支持 GPU 的,最初它负责: Initialize GPU card, load its firmware, etc. ...【详细内容】
2022-11-18  老吴嵌入式   今日头条  Tags:Display框架   点击:(3)  评论:(0)  加入收藏
前言准确点说,这不是《从零打造项目》系列的第一篇文章,模版代码生成的那个项目讲解算是第一篇,当时就打算做一套项目脚手架,为后续进行项目练习做准备。因时间及个人经验问题...【详细内容】
2022-11-18  Java热点  今日头条  Tags:SpringBoot   点击:(2)  评论:(0)  加入收藏
作者 | 褚杏娟近日,GitHub 前 CTO Jason Warner 在推特上表示,“我确信过去十年中,最大的架构错误之一就是全面使用微服务。”从单体应用到微服务的规划顺序,Warner 的建议是:单...【详细内容】
2022-11-18  InfoQ  今日头条  Tags:GitHub   点击:(3)  评论:(0)  加入收藏
一、Scrapy框架简介Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据,只需要实现少量的代码,就能够快速的抓...【详细内容】
2022-11-16  运维笔记ywbj  今日头条  Tags:Scrapy   点击:(4)  评论:(0)  加入收藏
这里首先需要说明的就是为什么要进行配置文件加密。在当今这个移动互联网横行的时代里,无论是企业的隐私还是个人的隐私都是需要得到保护的。所以我们在实际的操作中会采用...【详细内容】
2022-11-14  架构师面试宝典  今日头条  Tags:Spring Boot   点击:(12)  评论:(0)  加入收藏
深度自定义mybatis回顾mybatis的操作的核心步骤 编写核心类SqlSessionFacotryBuild进行解析配置文件 深度分析解析SqlSessionFacotryBuild干的核心工作 编写核心类SqlSessio...【详细内容】
2022-11-13    网易号  Tags:mybatis   点击:(6)  评论:(0)  加入收藏
WebRTC(Web Real-Time Communication)是为了让开发者在浏览器实现多媒体交换的技术,于2011年被加入W3C规范。当前的支持情况可以见下图。 WebRTC的核心在于建立PeerConnection...【详细内容】
2022-11-11  效能IT哥  今日头条  Tags:WEBRtc   点击:(16)  评论:(0)  加入收藏
由于hystrix官方已经停止维护了,目前spring-cloud推荐使用resilience4j来代替hystrix实现熔断、限流。熔断一般指客户端调用服务端接口出现异常时客户端侧的处理,当然也可以是...【详细内容】
2022-11-10  vivi2020一  今日头条  Tags:spring   点击:(27)  评论:(0)  加入收藏
Spring MVC 的请求处理流程 DispatcherServletDispatcherServlet继承结构 DispatcherServlet对请求的处理流程大致步骤 绑定⼀些 Attribute WebApplicationContext / Loc...【详细内容】
2022-11-08  刷刷的破壳日记  今日头条  Tags:Spring MVC   点击:(14)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条