Mybatis源码之映射器解析

Stella981
• 阅读 693

Mybatis源码之映射器解析

点击上方蓝字关注我!

Mybatis映射器

映射器是MyBatis最强大的⼯具,也是我们使用MyBatis时⽤得最多的工具,因此熟 练掌握它⼗分必要。MyBatis是针对映射器构造的SQL构建的轻量级框架,并且通过配置 生成对应的JavaBean返回给调用者,⽽这些配置主要便是映射器,在MyBatis中你可以根 据情况定义动态SQL来满足不同场景的需要,它比其他框架灵活得多。MyBatis还支持⾃动绑定JavaBean, 我们只要让SQL返回的字段名和JavaBean 的属性名保持一致(或者采⽤驼峰式命名),便可以省掉这些繁琐的映射配置

目录:

  • Mybatis映射器

  • 映射器的主要元素

  • Select元素

  • insert元素

  • sql元素

  • resultMap元素

  • cache元素

  • 映射器的内部组成

我们先再回顾下映射器的主要元素

映射器的主要元素

映射器是由Java接口和XML文件(或注解)共同组成的,Java接口主要定义调用者接口,XML文件是配置映射器的核心文件,包括以下元素:


  • select 查询语句,可以自定义参数,返回结果集;

  • insert 插入语句,返回一个整数,表示插入的条数;

  • update 更新语句,返回一个整数,表示更新的条数;

  • delete 删除语句,返回一个整数,表示删除的条数;

  • sql 允许定义一部分SQL,然后再各个地方引用;

  • resultMap 用来描述从数据库结果集中来加载对象,还可以配置关联关系,提供映射规则;

  • cache 给定命名空间的缓存配置


Select元素

select元素帮助我们 从数据库中读出数据,组装数据给业务人员。执行select语句前,我们需要定义参数,它 可以是⼀个简单的参数类型,例如int, float , String,也可以是⼀个复杂的参数类型 例如 JavaBean、 Map等,这些都是MyBatis接受的参数类型。

执⾏SQL后,MyBatis也提供了 强⼤的映射规则,自动映射来帮助我们把返回的结果集绑定到JavaBean中。

select 元素的配置众多,下面简单说明下:

  • id: id和Mapper的命名空间组成唯一值,提供给Mybatis调用,如果不唯一将会报错

  • paramterType:传入的参数类型,可以是基本类型、map、自定义的java bean;

  • resultType:返回的结果类型,可以是基本类型、自定义的java bean;

  • resultMap:它是最复杂的元素,可以配置映射规则、级联、typeHandler等,与ResultType不能同时存在;

  • flushCache:在调用SQL后,是否要求清空之前查询的本地缓存和二级缓存,主要用于更新缓存,默认为false;

  • useCache:启动二级缓存的开关,默认只会启动一级缓存;

  • timeout:设置超时参数,等超时的时候将抛出异常,单位为秒;

  • fetchSize:获取记录的总条数设定;


select实例

需求:  查询名称等于JAVA宝典的用户

在UserDao中定义接口方法:

User findIdByName(String name);

定义UserMapper.xml

<select id="findIdByName" parameterType="string" resultType="User">         select         u.*         from t_user u         where u.name=#{name} </select>

对操作步骤进行归纳概括:

  • Id标出了了这条SQL

  • parameterType定义参数类型

  • resuitType定义返回值类型

上面的例子只是传入单个参数,多个参数可以使用Map,JavaBean,使用注解方式,等等,下面我们会单独介绍.


insert元素

insert属性和select大部分都相同, 说下3个不同的属性:

  • keyProperty:指定哪个列是主键,如果是联合主键可以用逗号隔开;

  • keyColumn:指定第几列是主键,不能和keyProperty共用;

  • useGeneratedKeys:是否使用自动增长,默认为false;当useGeneratedKeys设为true时,在插入的时候,会回填Java Bean的id值,通过返回的对象可获取主键值。

如果想根据一些特殊关系设置主键的值,可以在insert标签内使用selectKey标签

<insert id="insertRole" useGeneratedKeys="true" keyProperty="id" >     <selectKey keyProperty="id" resultType="int" order="before">         select if(max(id) is null,1,max(id)+2) as newId from t_role       </selectKey>  </insert>

update和delete 就不单独过多介绍了

sql元素

sql元素的意义,在于我们可以定义⼀串串SQL语句的组成部分,其他的语句可以通过引⽤来使⽤它。

例如,你有一条SQL需要select⼏⼗个字段映射到JavaBean中去,我的第二 条SQL也是这⼏⼗个字段映射到JavaBean中去,显然这些字段写两遍不太合适。那么我们 就⽤sql元素来完成

定义:

<sql id="columns">     id,     name,     remark,     tid </sql>

使用:

<select id="getOne" resultType="com.liangtengyu.entity.Ttest">   select  <include refid="columns"/>  from t_test where id  = #{id}  </select>

resultMap元素

resultMap是MyBatis里面最复杂的元素,它的作用是定义映射规则、级联的更新、定制类型转换器等。

由以下元素构成:

<resultMap>     <constructor> <!-- 配置构造方法 -->         <idArg/>         <arg/>     </constructor>     <id/> <!--指明哪一列是主键-->     <result/> <!--配置映射规则-->     <association/> <!--一对一-->     <collection/> <!--一对多-->     <discriminator> <!--鉴别器级联-->         <case/>     </discriminator> </resultMap>

有的实体不存在没有参数的构造方法,需要使用constructor配置有参数的构造方法:

<resultMap id="role" type="com.liangtengyu.entity.Role">     <constructor>         <idArg column="id" javaType="int"/>         <arg column="role_name" javaType="string"/>     </constructor> </resultMap>

id指明主键列,result配置数据库字段和POJO属性的映射规则:

association、collection用于配置级联关系的,分别为一对一和一对多,实际中,多对多关系的应用不多,因为比较复杂,会用一对多的关系把它分解为双向关系。

discriminator用于这样一种场景:比如我们去体检,男和女的体检项目不同,如果让男生去检查妇科项目,是不合理的, 通过discriminator可以根据性别,返回不同的对象。

级联关系的配置比较多,就不在此演示了,可查看文档进行了解。

cache元素

在没有显示配置缓存时,只开启一级缓存,一级缓存是相对于同一个SqlSession而言的,在参数和SQL完全一样的情况下,使用同一个SqlSession对象调用同一个Mapper的方法,只会执行一次SQL。如果是不同的SqlSession对象,因为不同SqlSession是相互隔离的,即使用相同的Mapper、参数和方法,还是会再次发送SQL到数据库去执行。

二级缓存是SqlSessionFactory层面上的,需要进行显示配置

这样很多设置是默认的,有如下属性可以配置:

  • eviction:代表缓存回收策略,可选值有LRU最少使用、FIFO先进先出、SOFT软引用,WEAK弱引用;

  • flushInterval:刷新间隔时间,单位为毫秒,如果不配置,当SQL被执行时才会刷新缓存;

  • size:引用数目,代表缓存最多可以存储多少对象,不宜设置过大,设置过大会导致内存溢出;

  • readOnly:只读,意味着缓存数据只能读取不能修改;

在大型服务器上,可能会使用专用的缓存服务器,比如Redis缓存,可以通过实现org.apache.ibatis.cache.Cache接口很方便的实现:

public interface Cache {         String getId(); //缓存编号     void putObject(Object var1, Object var2); //保存对象     Object getObject(Object var1); //获取对象     Object removeObject(Object var1); //移除对象     void clear(); //清空缓存     int getSize(); //获取缓存对象大小     ReadWriteLock getReadWriteLock(); //获取缓存的读写锁     }


映射器的内部组成

一般而言,一个映射器是由3个部分组成:

打开Mybatis源码,在mapping包中可以找到他们

Mybatis源码之映射器解析

  • MappedStatement,它保存映射器的一个节点(select|insert|delete|update)并且包括许多我们配置的sql,sql的id、缓存信息,resultMap,parameterType、resultType、languageDriver等重要的配置内容。

  • SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。它的主要作用是根据参数和其他规则组装sql。

  • BoundSql,它是建立SQL和参数的地方,他有3个常用的属性:SQLparameterObjectparameterMappings 这3个等会介绍.

idea生成的依赖图

Mybatis源码之映射器解析

MappedStatement创建过程

首先我们介绍一下MappedStatement

在SqlSessionFactoryBuilder.build()方法,它会帮我们创建一个SqlSessionFactory,它的build方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {     try {       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);       return build(parser.parse());//此处使用解析XML返回的数据构建 SqlSessionFactory 跟进去..     } catch (Exception e) {       throw ExceptionFactory.wrapException("Error building SqlSession.", e);     } finally {       ErrorContext.instance().reset();       try {         inputStream.close();       } catch (IOException e) {         // Intentionally ignore. Prefer previous error.       }     }   }

进入到parser.parse()方法看看:

public Configuration parse() {     if (parsed) { //如果一个xml文件解析两次 就会报错 否则才去解析       throw new BuilderException("Each XMLConfigBuilder can only be used once.");     }     parsed = true;     parseConfiguration(parser.evalNode("/configuration"));//解析节点configuration,跟进去...     return configuration;   }

跟进代码parseConfiguration(parser.evalNode("/configuration"));

private void parseConfiguration(XNode root) {     try {       propertiesElement(root.evalNode("properties"));       Properties settings = settingsAsProperties(root.evalNode("settings"));       loadCustomVfs(settings);       loadCustomLogImpl(settings);       typeAliasesElement(root.evalNode("typeAliases"));       pluginElement(root.evalNode("plugins"));       objectFactoryElement(root.evalNode("objectFactory"));       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));       reflectorFactoryElement(root.evalNode("reflectorFactory"));       settingsElement(settings);       environmentsElement(root.evalNode("environments"));       databaseIdProviderElement(root.evalNode("databaseIdProvider"));       typeHandlerElement(root.evalNode("typeHandlers"));       mapperElement(root.evalNode("mappers"));//解析了很多,但是今天我们的主角是这个..跟进去.     } catch (Exception e) {       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);     }   }

private void mapperElement(XNode parent) throws Exception {     if (parent != null) {       for (XNode child : parent.getChildren()) {         if ("package".equals(child.getName())) {           String mapperPackage = child.getStringAttribute("name");           configuration.addMappers(mapperPackage);         } else {           String resource = child.getStringAttribute("resource");//获取三种方式指定的路径 进行判断 告诉MyBatis 到哪里去找映射文件           String url = child.getStringAttribute("url");           String mapperClass = child.getStringAttribute("class");           if (resource != null && url == null && mapperClass == null) {             ErrorContext.instance().resource(resource);             InputStream inputStream = Resources.getResourceAsStream(resource);             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());             mapperParser.parse();//调用 mapperParser.parse();解析xml  跟进去...           } else if (resource == null && url != null && mapperClass == null) {             ErrorContext.instance().resource(url);             InputStream inputStream = Resources.getUrlAsStream(url);             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());             mapperParser.parse();//调用 mapperParser.parse();解析xml           } else if (resource == null && url == null && mapperClass != null) {              Class<?> mapperInterface = Resources.classForName(mapperClass);             configuration.addMapper(mapperInterface);           } else {             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");           }         }       }     }   }

可以看到在解析mapper标签

`public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));//解析xml的mapper标签,并返回调用configurationElement()方法
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();//缺少资源的再尝试一下.具体逻辑我们不深入走这里了,有兴趣的可以看看源码.
    parsePendingCacheRefs();
    parsePendingStatements();
  }`

configurationElement()方法

private void configurationElement(XNode context) {     try {       String namespace = context.getStringAttribute("namespace");       if (namespace == null || namespace.isEmpty()) {         throw new BuilderException("Mapper's namespace cannot be empty");       }       builderAssistant.setCurrentNamespace(namespace);       cacheRefElement(context.evalNode("cache-ref"));       cacheElement(context.evalNode("cache"));       parameterMapElement(context.evalNodes("/mapper/parameterMap"));       resultMapElements(context.evalNodes("/mapper/resultMap"));       sqlElement(context.evalNodes("/mapper/sql"));       buildStatementFromContext(context.evalNodes("select|insert|update|delete"));     } catch (Exception e) {       throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);     }   }

buildStatementFromContext()方法:

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {     for (XNode context : list) {       final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);       try {         statementParser.parseStatementNode();//会调用此处,继续跟进       } catch (IncompleteElementException e) {         configuration.addIncompleteStatement(statementParser);       }     }   }

statementParser.parseStatementNode();

`public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre:  and  were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    //SqlSource是在这里创建的 记住这里等会我们再来分析她.先继续走完流程.
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    //对各种属性进行了解析并且调用addMappedStatement方法.
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, 
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }`

addMappedStatement()方法:

`public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class parameterType,       String resultMap,       Class resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);//configuration内部放入了一个Map<String, MappedStatement>
    return statement;//最终返回MappedStatement
  }
`

至此,MappedStatement创建完毕


SqlSource的创建过程

回到parseStatementNode()方法找到:

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

createSqlSource方法是LanguageDriver接口的一个方法 她有4个实现

Mybatis源码之映射器解析 我们看一下xml实现

`public class XMLLanguageDriver implements LanguageDriver {

  @Override
  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
  }

  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();//进行解析,我们跟进..
  }
  ....
}
`

public SqlSource parseScriptNode() {     MixedSqlNode rootSqlNode = parseDynamicTags(context);//对动态标签的处理     SqlSource sqlSource;//初始化Null     if (isDynamic) {//判断是否动态,来调用不同的SqlSource实例.       sqlSource = new DynamicSqlSource(configuration, rootSqlNode);     } else {//跟进RawSqlSource()       sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);     }     return sqlSource;   }

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {     SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);     Class<?> clazz = parameterType == null ? Object.class : parameterType;     sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());   }

sqlSourceParser.parse()方法要对sql进行处理,这里我们打个断点,可以看到原始参数sql为

insert into t_test set context = #{context}

Mybatis源码之映射器解析

parse方法处理完毕返回:insert into t_test set context = ?并且调用了StaticSqlSource的构造方法Mybatis源码之映射器解析

StaticSqlSource类继实现了SqlSource接口,

`public class StaticSqlSource implements SqlSource {

  private final String sql;
  private final List parameterMappings;
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {
    this(configuration, sql, null);
  }

  public StaticSqlSource(Configuration configuration, String sql, List parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {//这个方法是不是眼熟,BoundSql的构建
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

}

`

至此,SqlSource创建完毕.


BoundSql

`public class BoundSql {

  private final String sql;
  private final List parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;

........
}
`

创建SqlSource时,就把参数放入BoundSql类中来构建一个new BoundSql

public BoundSql getBoundSql(Object parameterObject) {     return new BoundSql(configuration, sql, parameterMappings, parameterObject);   }

configuration, sql, parameterMappings, parameterObject这些参数是非常重要的我们逐个分析下

configuration

这个不用多说了吧 如果不认识她,那直接打回新手村

sql

这个属性保存的是我们在映射文件xml中写的sql语句

例子就是刚刚解析出来的Sql语句:insert into t_test set context = ?

parameterMappings

List<ParameterMapping> parameterMappings;

它是一个list,每一个元素都是parameterMapping对象,这个对象会描述包括属性、名称、表达式、javaType、jdbcType、typeHandler等重要信息。

ParameterMapping

`public class ParameterMapping {

  private Configuration configuration;

  private String property;
  private ParameterMode mode;
  private Class javaType = Object.class;   private JdbcType jdbcType;   private Integer numericScale;   private TypeHandler typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;
  ......
  }

`

parameterObject

parameterObject是参数,可以传递简单对象,pojo、map或者@param注解的参数

传入简单对象 (包括int、 float、 double等),⽐如当我们传递int类型时,MyBatis 会把参数包装成为Integer对象传递,类似的long、 float、 double也是如此.

传入pojo、map那么这个parameterObject参数就是你传入的POJO或者Map不变

传入多个参数,不带@Param注解那么MyBatis就会把 parameterObject变为一个Map<String, Object>对象,其键值的关系是按顺序来规划 的,类似于这样的形式{"1":Obj1,"2":Obj2,"3":Obj3…}所以在编写的时候我们都可以使用#{param 1}或者#{1}去引⽤第⼀个参数

传入多个参数,带@Param注解如果我们使⽤@Param注解,那么MyBatis就会把parameterObject变为一个Map<String, Object>对象类似于没有@Param注解,只是把其数字的键值对应置换为了@Param注解的键值。

⽐如我们注解 (@Param{"name"} String var1,@Param{"age"} int var2)数字的键值对应置换为了name和age

  (完)

扫描二维码

加学习群

扫码加群

Mybatis源码之映射器解析

Mybatis源码之映射器解析

本文分享自微信公众号 - java宝典(java_bible)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这