文章目录

  • SQL 语句构建器
    • 1、问题
    • 2、解决方案
    • 3、SQL 类
    • 4、SqlBuilder 和 SelectBuilder (已经废弃)
  • 日志
    • 1、日志配置

SQL 语句构建器

1、问题

Java 程序员面对的最痛苦的事情之一就是在 Java 代码中嵌入 SQL 语句。这通常是因为需要动态生成 SQL 语句,不然我们可以将它们放到外部文件或者存储过程中。如你所见,MyBatis 在 XML 映射中具备强大的 SQL 动态生成能力。但有时,我们还是需要在 Java 代码里构建 SQL 语句。此时,MyBatis 有另外一个特性可以帮到你,让你从处理典型问题中解放出来,比如加号、引号、换行、格式化问题、嵌入条件的逗号管理及 AND 连接。确实,在 Java 代码中动态生成 SQL 代码真的就是一场噩梦。例如:

String sql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, "
"P.LAST_NAME,P.CREATED_ON, P.UPDATED_ON " +
"FROM PERSON P, ACCOUNT A " +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID " +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID " +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) " +
"OR (P.LAST_NAME like ?) " +
"GROUP BY P.ID " +
"HAVING (P.LAST_NAME like ?) " +
"OR (P.FIRST_NAME like ?) " +
"ORDER BY P.ID, P.FULL_NAME";

2、解决方案

MyBatis 3 提供了方便的工具类来帮助解决此问题。借助 SQL 类,我们只需要简单地创建一个实例,并调用它的方法即可生成 SQL 语句。让我们来用 SQL 类重写上面的例子:

private String selectPersonSql() {return new SQL() {{SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");FROM("PERSON P");FROM("ACCOUNT A");INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");WHERE("P.ID = A.ID");WHERE("P.FIRST_NAME like ?");OR();WHERE("P.LAST_NAME like ?");GROUP_BY("P.ID");HAVING("P.LAST_NAME like ?");OR();HAVING("P.FIRST_NAME like ?");ORDER_BY("P.ID");ORDER_BY("P.FULL_NAME");}}.toString();
}

这个例子有什么特别之处吗?仔细看一下你会发现,你不用担心可能会重复出现的 “AND” 关键字,或者要做出用 “WHERE” 拼接还是 “AND” 拼接还是不用拼接的选择。SQL 类已经为你处理了哪里应该插入 “WHERE”、哪里应该使用 “AND” 的问题,并帮你完成所有的字符串拼接工作。


批注:

该场景用于需要在 Java 代码里构建 SQL 语句,当然我目前还没有接触过,但是这个道理却十分好明白。当我们需要使用Java代码来构建SQL语句的时候,如果使用传统的方式来构建,在编写SQL上会遇到诸多的代码编写上的不顺利,基于此,MyBatis给出了自己的解决方案,即提供一个工具类来解决我们遇到的诸多的不顺。

使用MyBatis的工具类以后,虽然我们编写的SQL相关的代码没有太大的区别,但对SQL片段与SQL片段之间有关连接的部分代码却进行改良。即用MyBatis工具类中的连接方式进行连接,能够有效的避免常规的用Java代码构建SQL语句时出现的连接问题。


3、SQL 类

这里有一些示例:

// 匿名内部类风格
public String deletePersonSql() {return new SQL() {{DELETE_FROM("PERSON");WHERE("ID = #{id}");}}.toString();
}// Builder / Fluent 风格
public String insertPersonSql() {String sql = new SQL().INSERT_INTO("PERSON").VALUES("ID, FIRST_NAME", "#{id}, #{firstName}").VALUES("LAST_NAME", "#{lastName}").toString();return sql;
}// 动态条件(注意参数需要使用 final 修饰,以便匿名内部类对它们进行访问)
public String selectPersonLike(final String id, final String firstName, final String lastName) {return new SQL() {{SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");FROM("PERSON P");if (id != null) {WHERE("P.ID like #{id}");}if (firstName != null) {WHERE("P.FIRST_NAME like #{firstName}");}if (lastName != null) {WHERE("P.LAST_NAME like #{lastName}");}ORDER_BY("P.LAST_NAME");}}.toString();
}public String deletePersonSql() {return new SQL() {{DELETE_FROM("PERSON");WHERE("ID = #{id}");}}.toString();
}public String insertPersonSql() {return new SQL() {{INSERT_INTO("PERSON");VALUES("ID, FIRST_NAME", "#{id}, #{firstName}");VALUES("LAST_NAME", "#{lastName}");}}.toString();
}public String updatePersonSql() {return new SQL() {{UPDATE("PERSON");SET("FIRST_NAME = #{firstName}");WHERE("ID = #{id}");}}.toString();
}
方法 描述
SELECT(String)
SELECT(String…)
开始新的或追加到已有的 SELECT子句。可以被多次调用,参数会被追加到 SELECT 子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意参数。
SELECT_DISTINCT(String)
SELECT_DISTINCT(String…)
开始新的或追加到已有的 SELECT子句,并添加 DISTINCT 关键字到生成的查询中。可以被多次调用,参数会被追加到 SELECT 子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意参数。
FROM(String)
FROM(String…)
开始新的或追加到已有的 FROM子句。可以被多次调用,参数会被追加到 FROM子句。 参数通常是一个表名或别名,也可以是数据库驱动程序接受的任意参数。
JOIN(String)
JOIN(String…)
INNER_JOIN(String)
INNER_JOIN(String…)
LEFT_OUTER_JOIN(String)
LEFT_OUTER_JOIN(String…)
RIGHT_OUTER_JOIN(String)
RIGHT_OUTER_JOIN(String…)
基于调用的方法,添加新的合适类型的 JOIN 子句。 参数可以包含一个由列和连接条件构成的标准连接。
WHERE(String)
WHERE(String…)
插入新的 WHERE 子句条件,并使用 AND 拼接。可以被多次调用,对于每一次调用产生的新条件,会使用 AND 拼接起来。要使用 OR 分隔,请使用 OR()
OR() 使用 OR 来分隔当前的 WHERE 子句条件。 可以被多次调用,但在一行中多次调用会生成错误的 SQL
AND() 使用 AND 来分隔当前的 WHERE子句条件。 可以被多次调用,但在一行中多次调用会生成错误的 SQL。由于 WHEREHAVING都会自动使用 AND 拼接, 因此这个方法并不常用,只是为了完整性才被定义出来。
GROUP_BY(String)
GROUP_BY(String…)
追加新的 GROUP BY 子句,使用逗号拼接。可以被多次调用,每次调用都会使用逗号将新的条件拼接起来。
HAVING(String)
HAVING(String…)
追加新的 HAVING 子句。使用 AND 拼接。可以被多次调用,每次调用都使用AND来拼接新的条件。要使用 OR 分隔,请使用 OR()
ORDER_BY(String)
ORDER_BY(String…)
追加新的 ORDER BY 子句,使用逗号拼接。可以多次被调用,每次调用会使用逗号拼接新的条件。
LIMIT(String)
LIMIT(int)
追加新的 LIMIT 子句。 仅在 SELECT()、UPDATE()、DELETE() 时有效。 当在 SELECT() 中使用时,应该配合 OFFSET() 使用。(于 3.5.2 引入)
OFFSET(String)
OFFSET(long)
追加新的 OFFSET 子句。 仅在 SELECT() 时有效。 当在 SELECT() 时使用时,应该配合 LIMIT() 使用。(于 3.5.2 引入)
OFFSET_ROWS(String)
OFFSET_ROWS(long)
追加新的 OFFSET n ROWS 子句。 仅在 SELECT() 时有效。 该方法应该配合 FETCH_FIRST_ROWS_ONLY() 使用。(于 3.5.2 加入)
FETCH_FIRST_ROWS_ONLY(String)
FETCH_FIRST_ROWS_ONLY(int)
追加新的 FETCH FIRST n ROWS ONLY 子句。 仅在 SELECT() 时有效。 该方法应该配合 OFFSET_ROWS() 使用。(于 3.5.2 加入)
DELETE_FROM(String) 开始新的 delete 语句,并指定删除表的表名。通常它后面都会跟着一个 WHERE 子句!
INSERT_INTO(String) 开始新的 insert 语句,并指定插入数据表的表名。后面应该会跟着一个或多个 VALUES() 调用,或 INTO_COLUMNS() 和 INTO_VALUES() 调用。
SET(String)
SET(String…)
对 update 语句追加 “set” 属性的列表
UPDATE(String) 开始新的 update 语句,并指定更新表的表名。后面都会跟着一个或多个 SET() 调用,通常也会有一个 WHERE() 调用。
VALUES(String, String) 追加数据值到 insert 语句中。第一个参数是数据插入的列名,第二个参数则是数据值。
INTO_COLUMNS(String…) 追加插入列子句到 insert 语句中。应与 INTO_VALUES() 一同使用。
INTO_VALUES(String…) 追加插入值子句到 insert 语句中。应与 INTO_COLUMNS() 一同使用。
ADD_ROW() 添加新的一行数据,以便执行批量插入。(于 3.5.2 引入)

提示 注意,SQL 类将原样插入 LIMITOFFSETOFFSET n ROWS 以及 FETCH FIRST n ROWS ONLY 子句。换句话说,类库不会为不支持这些子句的数据库执行任何转换。 因此,用户应该要了解目标数据库是否支持这些子句。如果目标数据库不支持这些子句,产生的 SQL 可能会引起运行错误。

从版本 3.4.2 开始,你可以像下面这样使用可变长度参数:

public String selectPersonSql() {return new SQL().SELECT("P.ID", "A.USERNAME", "A.PASSWORD", "P.FULL_NAME", "D.DEPARTMENT_NAME", "C.COMPANY_NAME").FROM("PERSON P", "ACCOUNT A").INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID", "COMPANY C on D.COMPANY_ID = C.ID").WHERE("P.ID = A.ID", "P.FULL_NAME like #{name}").ORDER_BY("P.ID", "P.FULL_NAME").toString();
}public String insertPersonSql() {return new SQL().INSERT_INTO("PERSON").INTO_COLUMNS("ID", "FULL_NAME").INTO_VALUES("#{id}", "#{fullName}").toString();
}public String updatePersonSql() {return new SQL().UPDATE("PERSON").SET("FULL_NAME = #{fullName}", "DATE_OF_BIRTH = #{dateOfBirth}").WHERE("ID = #{id}").toString();
}

从版本 3.5.2 开始,你可以像下面这样构建批量插入语句:

public String insertPersonsSql() {// INSERT INTO PERSON (ID, FULL_NAME)//     VALUES (#{mainPerson.id}, #{mainPerson.fullName}) , (#{subPerson.id}, #{subPerson.fullName})return new SQL().INSERT_INTO("PERSON").INTO_COLUMNS("ID", "FULL_NAME").INTO_VALUES("#{mainPerson.id}", "#{mainPerson.fullName}").ADD_ROW().INTO_VALUES("#{subPerson.id}", "#{subPerson.fullName}").toString();
}

从版本 3.5.2 开始,你可以像下面这样构建限制返回结果数的 SELECT 语句,:

public String selectPersonsWithOffsetLimitSql() {// SELECT id, name FROM PERSON//     LIMIT #{limit} OFFSET #{offset}return new SQL().SELECT("id", "name").FROM("PERSON").LIMIT("#{limit}").OFFSET("#{offset}").toString();
}public String selectPersonsWithFetchFirstSql() {// SELECT id, name FROM PERSON//     OFFSET #{offset} ROWS FETCH FIRST #{limit} ROWS ONLYreturn new SQL().SELECT("id", "name").FROM("PERSON").OFFSET_ROWS("#{offset}").FETCH_FIRST_ROWS_ONLY("#{limit}").toString();
}

4、SqlBuilder 和 SelectBuilder (已经废弃)

在版本 3.2 之前,我们的实现方式不太一样,我们利用 ThreadLocal 变量来掩盖一些对 Java DSL 不太友好的语言限制。现在,现代 SQL 构建框架使用的构建器和匿名内部类思想已被人们所熟知。因此,我们废弃了基于这种实现方式的 SelectBuilder 和 SqlBuilder 类。

下面的方法仅仅适用于废弃的 SqlBuilder 和 SelectBuilder 类。

方法 描述
BEGIN() RESET() 这些方法清空 SelectBuilder 类的 ThreadLocal 状态,并准备好构建一个新的语句。开始新的语句时,BEGIN() 是最名副其实的(可读性最好的)。但如果由于一些原因(比如程序逻辑在某些条件下需要一个完全不同的语句),在执行过程中要重置语句构建状态,就很适合使用 RESET()
SQL() 该方法返回生成的 SQL() 并重置 SelectBuilder 状态(等价于调用了 BEGIN()RESET())。因此,该方法只能被调用一次!

SelectBuilder 和 SqlBuilder 类并不神奇,但最好还是知道它们的工作原理。 SelectBuilder 以及 SqlBuilder 借助静态导入和 ThreadLocal 变量实现了对插入条件友好的简洁语法。要使用它们,只需要静态导入这个类的方法即可,就像这样(只能使用其中的一条,不能同时使用):

import static org.apache.ibatis.jdbc.SelectBuilder.*;
import static org.apache.ibatis.jdbc.SqlBuilder.*;

然后就可以像下面这样创建一些方法:

/* 已被废弃 */
public String selectBlogsSql() {BEGIN(); // 重置 ThreadLocal 状态变量SELECT("*");FROM("BLOG");return SQL();
}/* 已被废弃 */
private String selectPersonSql() {BEGIN(); // 重置 ThreadLocal 状态变量SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");FROM("PERSON P");FROM("ACCOUNT A");INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");WHERE("P.ID = A.ID");WHERE("P.FIRST_NAME like ?");OR();WHERE("P.LAST_NAME like ?");GROUP_BY("P.ID");HAVING("P.LAST_NAME like ?");OR();HAVING("P.FIRST_NAME like ?");ORDER_BY("P.ID");ORDER_BY("P.FULL_NAME");return SQL();
}

批注:

该方式已经废弃,但是还是有必要了解一下它的原理。

在使用Java类构建SQL语句的时候,我们需要使用BEGIN或者RESET方法来初始化对应的状态。


日志

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。当没有找到这些实现时,将会禁用日志功能。

不少应用服务器(如 Tomcat 和 WebShpere)的类路径中已经包含 Commons Logging。注意,在这种配置环境下,MyBatis 会把 Commons Logging 作为日志工具。这就意味着在诸如 WebSphere 的环境中,由于提供了 Commons Logging 的私有实现,你的 Log4J 配置将被忽略。这个时候你就会感觉很郁闷:看起来 MyBatis 将你的 Log4J 配置忽略掉了(其实是因为在这种配置环境下,MyBatis 使用了 Commons Logging 作为日志实现)。如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志实现,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。

<configuration><settings>...<setting name="logImpl" value="LOG4J"/>...</settings>
</configuration>

可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log 接口,且构造方法以字符串为参数的类完全限定名。

你也可以调用以下任一方法来选择日志实现:

org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
org.apache.ibatis.logging.LogFactory.useJdkLogging();
org.apache.ibatis.logging.LogFactory.useCommonsLogging();
org.apache.ibatis.logging.LogFactory.useStdOutLogging();

你应该在调用其它 MyBatis 方法之前调用以上的某个方法。另外,仅当运行时类路径中存在该日志实现时,日志实现的切换才会生效。如果你的环境中并不存在 Log4J,你却试图调用了相应的方法,MyBatis 就会忽略这一切换请求,并将以默认的查找顺序决定使用的日志实现。

关于 SLF4J、Apache Commons Logging、Apache Log4J 和 JDK Logging 的 API 介绍不在本文档介绍范围内。不过,下面的例子可以作为一个快速入门。有关这些日志框架的更多信息,可以参考以下链接:

  • SLF4J
  • Apache Commons Logging
  • Apache Log4j 1.x and 2.x
  • JDK Logging API

1、日志配置

你可以通过在包、映射类的全限定名、命名空间或全限定语句名上开启日志功能,来查看 MyBatis 的日志语句。

再次提醒,具体配置步骤取决于日志实现。接下来我们会以 Log4J 作为示范。配置日志功能非常简单:添加一个或多个配置文件(如 log4j.properties),有时还需要添加 jar 包(如 log4j.jar)。下面的例子将使用 Log4J 来配置完整的日志服务。一共两个步骤:

步骤 1:添加 Log4J 的 jar 包

由于我们使用的是 Log4J,我们要确保它的 jar 包可以被应用使用。为此,需要将 jar 包添加到应用的类路径中。Log4J 的 jar 包可以在上面的链接中下载。

对于 web 应用或企业级应用,你可以将 log4j.jar 添加到 WEB-INF/lib 目录下;对于独立应用,可以将它添加到 JVM 的 -classpath 启动参数中。

步骤 2:配置 Log4J

配置 Log4J 比较简单。假设你需要记录这个映射器的日志:

package org.mybatis.example;
public interface BlogMapper {@Select("SELECT * FROM blog WHERE id = #{id}")Blog selectBlog(int id);
}

在应用的类路径中创建一个名为 log4j.properties 的文件,文件的具体内容如下:

# 全局日志配置
log4j.rootLogger=ERROR, stdout
# MyBatis 日志配置
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

上述配置将使 Log4J 详细打印 org.mybatis.example.BlogMapper 的日志,对于应用的其它部分,只打印错误信息。

为了实现更细粒度的日志输出,你也可以只打印特定语句的日志。以下配置将只打印语句 selectBlog 的日志:

log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE

或者,你也可以打印一组映射器的日志,只需要打开映射器所在的包的日志功能即可:

log4j.logger.org.mybatis.example=TRACE

某些查询可能会返回庞大的结果集。这时,你可能只想查看 SQL 语句,而忽略返回的结果集。为此,SQL 语句将会在 DEBUG 日志级别下记录(JDK 日志则为 FINE)。返回的结果集则会在 TRACE 日志级别下记录(JDK 日志则为 FINER)。因此,只要将日志级别调整为 DEBUG 即可:

log4j.logger.org.mybatis.example=DEBUG

但如果你要为下面的映射器 XML 文件打印日志,又该怎么办呢?

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper"><select id="selectBlog" resultType="Blog">select * from Blog where id = #{id}</select>
</mapper>

这时,你可以通过打开命名空间的日志功能来对整个 XML 记录日志:

log4j.logger.org.mybatis.example.BlogMapper=TRACE

而要记录具体语句的日志,可以这样做:

log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE

你应该会发现,为映射器和 XML 文件打开日志功能的语句毫无差别。

提示 如果你使用的是 SLF4J 或 Log4j 2,MyBatis 会设置 tag 为 MYBATIS。

配置文件 log4j.properties 的余下内容用来配置输出器(appender),这一内容已经超出本文档的范围。关于 Log4J 的更多内容,可以参考上面的 Log4J 网站。或者,你也可以简单地做个实验,看看不同的配置会产生怎样的效果。


批注:

日志文件在项目中是一个十分重要的点,就我个人而言,定位错误全靠它。日志文件的配置并不难,只需要进行几个简单的配置就好了,当然这得得益于MyBatis的良好设计。

我们可以根据实际项目的需要,选择合适的日志进行配置


MyBatis官方文档——SQL语句构建及日志部分相关推荐

  1. Mybatis官方文档及使用简记

    Mybatis官方文档及使用简记 数据库建表 入门案例 无mapper类最传统的用法 使用mybatis generator 使用mybatis-generator mybatis-spring整合 ...

  2. Mybatis官方文档中的(XML映射文件)模块(半途凉了)

    Mybatis官方文档中的(XML映射文件)模块(半途凉了) 一.XML映射器属性的介绍 Mybatis的真正强大之处在于它的语句映射.他即可以自动的映射数据库中的字段和对象的属性. SQL映射文件有 ...

  3. MyBatis超详细介绍——SQL语句构建器类

    MyBatis超详细介绍--SQL语句构建器类 (本文作为学习笔记,了解更多请参考:MyBatis参考文档) MyBatis3提供了SQL类帮助构造SQL语句: private String sele ...

  4. Mybatis官方文档解读

    跟着Mybatis的官方文档总结一下.~ 简介 什么是mybatis? mybatis是一款优秀的持久层框架,它支持自定义SQL,存储过程以及高级映射.Mybatis免除了所有的JDBC代码以及 设置 ...

  5. springcloud官方文档_springcloud-microservice 快速构建分布式系统

    springcloud-microservice Spring Cloud 快速搭建微服务 spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由 ...

  6. lagou-mybatis-3:mybatis官方文档

    官方文档 官方文档中关于延迟加载的属性配置 设置名 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关.当开启时,所有关联对象都会延迟加载. 特定关联关系中可通过设置 fe ...

  7. Mybatis官方文档——入门

    入门 安装 要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于 classpath 中即可. 如果使用 Maven 来构建项目,则需将下面的 dependency 代码置于 ...

  8. MyBatis官方文档-Java API

    最近更新: 15 七月 2019|版本: 3.5.2 文章目录 Java API SqlSessions SqlSessionFactoryBuilder SqlSessionFactory SqlS ...

  9. MyBatis官方文档-XML 映射文件

    最近更新: 15 七月 2019|版本: 3.5.2 文章目录 XML 映射文件 insert, update 和 delete sql 结果映射 高级结果映射 结果映射(resultMap) id ...

  10. MyBatis官方文档-日志

    最近更新: 15 七月 2019|版本: 3.5.2 文章目录 日志 日志配置 步骤 1:添加 Log4J 的 jar 包 步骤 2:配置 Log4J 日志 MyBatis 的内置日志工厂提供日志功能 ...

最新文章

  1. Java面向对象之继承,方法重写,super关键字,员工类系列继承题
  2. Bag of Tricks for Efficient Text Classification(Fasttext)
  3. 鸿蒙开发-在JS中获取hml页面中Input输入的值
  4. mysql聚合函数rollup和cube
  5. 那些年,在nodejs上踩过的坑
  6. 基于CSE的微服务架构实践-轻量级架构技术选型
  7. 第十章:XAML标记扩展(三)
  8. pam_limits(sshd:session): unknown limit item 'noproc'
  9. 安全事件应急响应工具箱
  10. X264实现H264编码以及MediaMuxer的另类用法「第八章,Android音视频编码那点破事」
  11. iOS跳转到设置和其他APP的那些事
  12. [转载] 胡锡进:5000亿买一包爆米花 我不想让我的国家这样
  13. 【市场调查与预测】廊坊师范学院大学生洗发水使用情况调查(课程论文)
  14. hive中的UDF 自定义函数 : 如何创建临时/永久函数
  15. Web3j使用教程(1)
  16. ios查看苹果app的下载量,日活,销售情况等相关数据
  17. 【Docker】Docker进阶(二)
  18. 【简单3d网络游戏制作】——基于Unity
  19. 异常图标导致转码失败
  20. 【基础知识】~ 竞争/冒险

热门文章

  1. GridView里面的Item高度设置
  2. html如何在第二个网页中显示第一个网页参数_接口测试平台代码实现5:亲手创造第一个首页...
  3. 如何测一个纸杯_阿薇塔罗——4个吊坠,凭直觉选一个,测你这辈子婚姻状况如何?...
  4. 果园机器人作文开头_【360教育】写作技巧丨写作技巧小学高年级作文最全技巧100条,建议收藏!...
  5. SpringBoot系列(7):SpringBoot启动流程源码分析()
  6. 张季跃 201771010139《面向对象程序设计(java)》第八周学习总结
  7. centos 7 安装pip
  8. Linux学习笔记:wc查看文件字节数、字数、行数
  9. python爬虫之urllib
  10. Sharepoint对List增删改操作