Mybatis 的应用2 —— XML 配置文件

MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息。

文档的顶层结构如下:

  • configuration 配置
    • properties 属性
    • settings 设置
    • typeAliases 类型命名
    • typeHandlers 类型处理器
    • objectFactory 对象工厂
    • plugins 插件
    • environments 环境
      • environment 环境变量
        • transactionManager 事务管理器
        • dataSource 数据源
    • databaseIdProvider 数据库厂商标识
    • mappers 映射器

mybatis 的 XML 配置文件中元素的放置有时需要按一定的先后顺序,所以最好按上面的结构进行配置

加载外部 properties 配置文件 —— properties

<properties> 元素的配置如下:

<properties resource="org/mybatis/example/jdbc.properties"></properties>

一旦配置后,就可以加载对应 resource 属性中指定的 properties 键值对。如添加一个 jdbc.properties :

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf-8
mysql.username=root
mysql.password=123456

在 mybatis 的 XML 配置文件的 <configuration> 元素下添加<properties> 子元素,即可在配置文件引用 properties 文件中对应 key 的值,用来替换需要动态配置的属性值。

如前面的XML 配置文件可修改如下:

<!-- mybatisConfig.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 引入 properties 配置文件 -->
    <properties resource="jdbc.properties" />
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <!-- 引用 properties 中的内容 -->
                <property name="driver" value="${mysql.driver}" />
                <property name="url"
                    value="${mysql.url}" />
                <property name="username" value="${mysql.username}" />
                <property name="password" value="${mysql.password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/lian/mapper/UserMapper.xml" />
    </mappers>
</configuration>

PS:properties 除了上述的加载方式,还可:1. 在创建 SqlSessionFactory 实例过程中的 SqlSessionFactoryBuilder 实例的 build 方法中作为参数传进去;2. 作为 <properties> 元素的子元素 <property name="username" value="root"/> 设置加载。三种方式的加载优先级从高到低为:通过方法参数传递 > resource 属性中指定的配置文件 > <properties> 元素的子元素

MyBatis 中的调整设置 —— settings

settings 是 MyBatis 中极为重要的调整设置或全局设置,它们会改变 MyBatis 的运行时行为。如下为完整的设置内容和对应的默认值

<settings>
    <!-- 该配置影响的所有映射器中配置的缓存的全局开关 -->
    <setting name="cacheEnabled" value="true"/>
    <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态 -->
    <setting name="lazyLoadingEnabled" value="false"/>
    <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods) -->
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 是否允许单一语句返回多结果集(需要兼容驱动) -->
    <setting name="multipleResultSetsEnabled" value="true"/>
    <!-- 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果 -->
    <setting name="useColumnLabel" value="true"/>
    <!-- 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby) -->
    <setting name="useGeneratedKeys" value="false"/>
    <!-- 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套) -->
    <setting name="autoMappingBehavior" value="PARTIAL"/>
    <!-- 指定发现自动映射目标未知列(或者未知属性类型)的行为。NONE: 不做任何反应;WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN);FAILING: 映射失败 (抛出 SqlSessionException) -->
    <setting name="autoMappingUnknownColumnBehavior" value="NONE"/>
    <!-- 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新 -->
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <!-- 设置超时时间,它决定驱动等待数据库响应的秒数。默认Not Set (null) -->
    <setting name="defaultStatementTimeout" value="25"/>
    <!-- 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。默认Not Set (null) -->
    <setting name="defaultFetchSize" value="100"/>
    <!-- 允许在嵌套语句中使用分页(RowBounds)。 If allow, set the false. -->
    <setting name="safeRowBoundsEnabled" value="false"/>
    <!-- 允许在嵌套语句中使用分页(ResultHandler)。 If allow, set the false -->
    <setting name="safeResultHandlerEnabled" value="true"/>
    <!-- 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射 -->
    <setting name="mapUnderscoreToCamelCase" value="false"/>
    <!-- MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据 -->
    <setting name="localCacheScope" value="SESSION"/>
    <!-- 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER -->
    <setting name="jdbcTypeForNull" value="OTHER"/>
    <!-- 指定哪个对象的方法触发一次延迟加载 -->
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    <!-- 指定动态 SQL 生成的默认语言 -->
    <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
    <!-- 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的 -->
    <setting name="callSettersOnNulls" value="false"/>
    <!-- 当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association) -->
    <setting name="returnInstanceForEmptyRow" value="false"/>
    <!-- 指定 MyBatis 增加到日志名称的前缀。默认Not set -->
    <setting name="logPrefix" value="lian's mybatis"/>
    <!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。默认Not set。可为:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING -->
    <setting name="logImpl" value="false"/>
    <!-- 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。可为:CGLIB | JAVASSIST -->
    <setting name="proxyFactory" value="JAVASSIST"/>
    <!-- 指定VFS的实现。默认Not set -->
    <setting name="vfsImpl" value="xxx"/>
    <!-- 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,并且加上-parameters选项 -->
    <setting name="useActualParamName" value="true"/>
    <!-- Specifies the class that provides an instance of Configuration. The returned Configuration instance is used to load lazy properties of deserialized objects. This class must have a method with a signature static Configuration getConfiguration() -->
    <setting name="configurationFactory" value="xxx"/>
</settings>

为 Java 类设置别名 —— typeAliases

类型别名是为 Java 类型设置一个短的名字。它只和 XML 映射文件有关,存在的意义仅在于用来减少类完全限定名的冗余。

修改上述 mybatis 的 XML 配置文件,为 Javabean User 增加 <typeAliases>:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <typeAliases>
        <typeAlias alias="User" type="com.lian.model.User"/>
    </typeAliases>
    
    <!-- ...省略其他 -->
    
</configuration>

则上文的映射文件 UserMapper.xml 中 <select> 元素的 resultType 属性可修改为 resultType="User"

<?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="com.lian.mapper.UserMapper">
  <select id="selectUserById1" resultType="User" parameterType="Integer">
    select * from tb_user where id = #{id}
  </select>
</mapper>

PS1:亦可在 <typeAliases> 中指定报名包名,则在该包中添加了 @Alias("xxx") 注解的类会添加相应的别名

<typeAliases>
  <package name="com.lian.model"/>
</typeAliases>
@Alias("User")
public class User {
    ...
}

在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名(如 user )。若有注解,则别名为其注解值。

PS2:mybatis 亦内置了常见的 Java 类型内建的类型别名,如前面的 XML 映射文件中 <select> 元素的 resultType 属性用到的 hashmap。完整常见的 Java 类型内建的类型别名见 附录 - 常见的 Java 类型内建的类型别名

配置环境 —— environments

<environments> 元素可包含多个 <environment> 子元素,每个子元素代表一种连接的配置,前面提到<environments> 元素的配置大致如下:

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
            <property name="driver" value="${mysql.driver}" />
            <property name="url"
                value="${mysql.url}" />
            <property name="username" value="${mysql.username}" />
            <property name="password" value="${mysql.password}" />
        </dataSource>
    </environment>
</environments>

每个 <environment> 子元素需要配置 事务管理器(transactionManager)、数据源(dataSource)

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>

PS:如果使用 Spring + MyBatis,则没有必要配置事务管理器, 因为 Spring 模块会使用自带的管理器来覆盖前面的配置

数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

  • UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然一点慢,它对在及时可用连接方面没有性能要求的简单应用程序是一个很好的选择。 不同的数据库在这方面表现也是不一样的,所以对某些数据库来说使用连接池并不重要,这个配置也是理想的。
    UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
    • driver – 这是 JDBC 驱动的 Java 类的完全限定名
    • url – 这是数据库的 JDBC URL 地址
    • username – 登录数据库的用户名
    • password – 登录数据库的密码
    • defaultTransactionIsolationLevel – 默认的连接事务隔离级别
    • driver.encoding=UTF8 ( 作为可选项,你也可以传递属性给数据库驱动 )
  • POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
    除了上述提到 UNPOOLED 下的属性外,会有更多属性用来配置 POOLED 的数据源:
    • poolMaximumActiveConnections – 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10
    • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数
    • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
    • poolTimeToWait – 这是一个底层设置,如果获取连接花费的相当长的时间,它会给连接池打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)
    • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否处在正常工作秩序中并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息
    • poolPingEnabled – 是否启用侦测查询。若开启,也必须使用一个可执行的 SQL 语句设置 poolPingQuery 属性(最好是一个非常快的 SQL),默认值:false
    • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的使用频度。这可以被设置成匹配具体的数据库连接超时时间,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)
  • JNDI– 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
    这种数据源配置只需要两个属性:
    • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找
    • data_source – 这是引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找
    • env.encoding=UTF8 ( 可以通过添加前缀“env.”直接把属性传递给初始上下文 )

映射器 —— mappers

有两种方式可加载 XML 映射文件,以及有两种方式可加载映射类

从 classpath 加载 XML 映射文件

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

从完全限定资源定位符(包括 file:/// 的 URL) 加载 XML 映射文件

<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

从 classpath 加载映射类

<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

扫描包加载映射类

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

typeHandlers(未完成)

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型
一些默认的类型处理器:

类型处理器 Java 类型器 JDBC 类型
row 1 col 1 row 1 col 2 row 1 col 2

自定义类型处理器:实现 TypeHandler 接口,或继承 BaseTypeHandler 类


<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

对象工厂(objectFactory)(未完成)

MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

插件(plugins)(未完成)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

使用插件只需实现 Interceptor 接口,并指定了想要拦截的方法签名即可

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象

databaseIdProvider (未完成)

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性

附录

常见的 Java 类型内建的类型别名

别名 映射的类型
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容

  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,469评论 0 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,642评论 18 139
  • Java数据持久化之mybatis 一. mybatis简介 1.1 原始的JDBC操作: Java 通过 Jav...
    小Q逛逛阅读 4,904评论 0 16
  • MyBatis配置xml层次结构,而且必须注意其顺序。 MyBatis官网中文XML映射配置文件 1.proper...
    落叶飞逝的恋阅读 2,337评论 0 4
  • 2.27烧脑吧,如何制定高级战略? 越对未来有信心 越对现在有耐心 战略有啥用呢?它就是让你拥有一个整体性,增强掌...
    雪野兔阅读 359评论 0 0