Mybatis基础知识-02-C-2020-11-10

日志编号说明
C-2020-11-10第一次创建

Mybatis-config.xml配置讲解

配置总览

下面这个图是从mybatis官网上截图来的。
mybatis配置

在Mybatis配置文件顶层,可以看到如下的内容:
dtd截图而spring配置文件中的顶层对应的xsd。
xsd这里先科普一下DTD和XSD的区别。
最明显的差异在于DTD类型,里面确定了每一层元素应该存在的位置,比如,在Mybatis中,如果把properties元素定义在setting下面,配置文件会报错。但是,在XSD的文件中,除了元素与子元素之间,各个元素中并没有顺序定义。
所以,在配置mybatis时,需要按照DTD定义好的顺序去配置。
现在,按照上面Mybatis官网给出的顺序进行Mybatis全局配置文件讲解。

properties

引入外部配置文件,类似于Spring中的property-placeholder。
在properties中有两个属性,一个是resource,从类路径引入,另一个是url,从磁盘路径或者网络路径引入。
被properties标注的属性,可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件(其他properties文件)中配置这些属性,也可以在 properties 元素的子元素中设置。
下面的例子,现在properties中的子元素里定义了两个属性。

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。 driver 和 url 属性将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。
也可以在 SqlSessionFactoryBuilder.build() 方法中传入属性值。例如:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
// ... 或者 ...
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);

结论:
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。例如:

<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${username:ut_user}"/> <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user',“:”就是默认的占位符 -->
</dataSource>

这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性。

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 -->
</properties>

如果你在属性名中使用了 “:” 字符(如:db:username),或者在 SQL 映射中使用了 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : ‘global_constants’}),就需要设置特定的属性来修改分隔属性名和默认值的字符。例如:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- 修改默认值的分隔符 -->
</properties>
<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${db:username?:ut_user}"/>
</dataSource>

settings

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。简单来说,是MyBatis的运行时参数的配置。
这部分内容非常多,这里给出mybatis官网上的信息,方便大家进行查询。MyBatis配置信息.

typeAliases

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
alias是别名,type是类的全路径信息。

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

对于有多个需要配置的类的时候,可以用下面这样的简易方式:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

typeHandlers

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。typeHandlers默认提供的类型转换.
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。注意,在继承BaseTypeHandler的时候,需要指明泛型的类型。例子中的泛型是String类型。Java代码如下:

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)//这个注解会将这个类型映射到一个JDBC类型
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}

写完了之后,还需要将他配置在MyBatis中。

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

使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器
要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

  • 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType=“String”);
  • 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略

通过两种方式来指定关联的 JDBC 类型

  • 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType=“VARCHAR”);
  • 在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。

当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null 的组合来选择一个类型处理器。
这意味着使用 @MappedJdbcTypes 注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap 中将不会生效
如果希望能在 ResultMap 中隐式地使用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。
然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true,那么这个类型处理器也会是 ResultMap 使用 Java 类型时的默认处理器。

最后,可以让 MyBatis 帮你查找类型处理器,注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型:

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

objectFactory 和 plugins

这两个标签,使用的频率不高,并且对于基础知识而言,更是涉及不到。在这里不做更多讲解。

environments

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单。

environments 元素定义了如何配置环境。

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

上面这段示例中,有如下需要注意的点:

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。

默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
environment元素下,有两个非常重要的子元素,下面进行讲解。

transactionManager

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

  • JDBC: 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。

如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置(Spring配置文件里,定义了transactionManager)。

dataSource

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

在dataSource元素中,有一个属性type,这个type决定了mybatis数据源类型。在mybatis中,有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")。

  • UNPOOLED:这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。
  • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
  • JNDI:这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

除此之外,就是通过property标签去定义数据库信息,比如,驱动,数据库连接,用户名,密码等信息。

databaseIdProvider

示例如下:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

针对databaseIdProvider的解释,更推荐直接去官网查看。databaseIdProvider.

Mappers

这是在MyBatis-config.xml中出现的最后一个元素,用来配置Mapper文件的位置。先提一点,因为平时都是用SSM整合,或者SpringBoot,但是现在的例子中,并不是SSM整合或者SpringBoot,因此在配置的时候有一定的局限性,没法做成平时习惯的对于接口和XML文件的分别配置。
现在一个最简单的例子

<mappers >
    <mapper resource="UserDao.xml" />
</mappers>

在Mappers下,可以出现两个子元素,分别是mapper和package

mapper

在mapper元素的基础写法如上所示,除了resource,还提供了url和class这两个属性。

  • resource:在类路径下寻找sql映射文件
  • url:可以从磁盘或者网络路径查找sql映射文件
  • class:直接引入接口的全类名,可以将xml文件放在dao的同级目录下,并且设置相同的文件名称,同时可以使用注解的方式来进行相关的配置

package

当有多个配置文件时,可以简便的通过package进行引入,在这里有两种做法。

把xml放在src/main/java

如果把xml文件放在了java目录下并且是maven项目的话,首先需要在pom文件里添加build信息,因为maven项目在编译的时候,java文件夹下只编译了.java文件,因此需要把xml也配置进去。

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

当要在java里面放Mapper文件的时候,这里还存在一个不一样的地方。对于整合到Spring中的时候,不需要要求xml和interface在同一个目录下,也不要求他们的名字必须一致。但是,在只是用mybatis的时候,这两点是必须的。

  • mapper.xml和dao接口必须在相同的包路径下
  • mapper.xml和dao接口必须是相同的文件名
    满足上述内容后,可以用package属性,引入对应的包,这样这个包下的所有mapper和dao接口都能被mybatis识别到。
<mappers>
    <package name="com.phl.mybatis.hello.dao"/>
</mappers>
把xml放在resource中

除了在实际项目编写时,把dao和xml都放在java中更方便之外,就没有任何好处,或者站得住脚的原因支持我们把xml也放在了java中。更合适,更标准的做法依旧是把mapper放在resource中。
当有多个mapper在resource里的时候,通过Mapper元素依次引入会很麻烦,此时,可以在resource中维护好一个与dao同样层级的目录关系,之后把mapper都放在这个dao里面。
维护相同的层级目录当这么做了之后,首先,现在没有在java中再出现xml文件,因此不需要在pom里面维护build的信息。直接通过package进行引入即可。
注意,这里同样需要遵守这两个规则。

  • mapper.xml和dao接口必须在相同的包路径下
  • mapper.xml和dao接口必须是相同的文件名
<mappers>
    <package name="com.phl.mybatis.hello.dao"/>
</mappers>

至此,对于mybatis-config.xml的讲解完毕,下面会开始讲解Mapper的配置信息。

Mapper配置讲解

虽然在接口上通过注解也能实现相同的作用,但是对于复杂的SQL来讲,写在注解中看起来不舒服,会让注解太长了,还是写在xml中更美观,更易于更改。
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
在Mapper中,只有几个顶级元素,下面列举出了元素以及简单的定义:

  • cache:该命名空间的缓存配置。
  • cache-ref:引用其它命名空间的缓存配置。
  • resultMap:描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql:可被其它语句引用的可重用语句块。
  • insert:映射插入语句。
  • update:映射更新语句。
  • delete:映射删除语句。
  • select:映射查询语句。
  • parameterMap:老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。

在每个顶级元素标签中可以添加很多个属性,下面我们开始详细了解下具体的配置。

insert,update,delete

这三个顶级元素内的属性很相似,放在一起进行讲解。

属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

下面是 insert,update 和 delete 语句的示例:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

如前所述,插入语句的配置规则更加丰富,在插入语句里面有一些额外的属性和子元素用来处理主键的生成,并且提供了多种生成方式。

useGeneratedKeys和keyProperty 子元素

首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

selectKey元素

对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。
这里有一个简单(也很傻)的示例,它可以生成一个随机 ID(不建议实际使用,这里只是为了展示 MyBatis 处理问题的灵活性和宽容度)。

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

在上面的示例中,首先会运行 selectKey 元素中的语句,并设置 Author 的 id,然后才会调用插入语句。这样就实现了数据库自动生成主键类似的行为,同时保持了 Java 代码的简洁。

selectKey 元素描述如下表

属性描述
keyPropertyselectKey语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
resultType结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。
order可以设置为 BEFOREAFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句。
statementType和前面一样,MyBatis 支持 STATEMENTPREPAREDCALLABLE 类型的映射语句,分别代表 Statement, PreparedStatementCallableStatement 类型。

综上,对于有自增主键的数据库,适用于useGeneratedKeyskeyProperty子元素,对于不提供自增主键的数据库,可以通过selectKey元素实现同样的效果。

sql

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 片段可以在其它语句中使用,例如:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

上面的例子中,sql内部定义的${alias},通过include内部的property元素进行了赋值。
同样也可以在 include 元素的 refid 属性或内部语句中使用属性值,例如:

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

这里通过两个例子讲解了sqlinclude,其实在实际使用中,很少会出现这种sql中套用别的sql,这里只是举例了这种用法。

注意,这类灵活的操作,说白了就是定义参数,以及通过property对参数赋值。这个技巧很好用,需要灵活使用。对于properties和property,详见上面的内容:properties和property.

select

查询语句是 MyBatis 中最常用的元素之一——光能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。 MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单的。例如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

参数传递

注意,现在说的参数传递,并不是只适用于select标签,只是借由select去进行讲解。因为select中自由度更高。

  1. 只传入一个参数
    在mybatis里只传入一个参数的时候,会分成两个情况。
    • 基础数据类型:使用#{里面的名字随便写}
    • 引用数据类型:使用#{里面类的属性名称}
  2. 传入多个参数
    当查询的时候传入多个参数的时候,就无法简单的通过#{参数名}来获取值了,默认是需要通过arg0,arg1…或者param1,param2等方式来获取值。原因就在于,mybatis在传入多个参数的时候,会将这些参数封装到一个map中,此时map中的key就是arg0,arg1,param1,param2这些值,但是很明显,这样的传值方式不是很友好,没有办法根据参数的名称来获取具体的值,因此可以使用如下的方式来指定参数的key是什么。
    @param(“key”),通过这个参数写在接口的参数列表中,就可以通过配置的key值去获取参数了。
  3. 使用map传递参数
    依然是直接使用#{key}来获取具体的属性值。

参数获取

在mybatis中,有两种方式获取从方法中传递过来的参数。${}和#{}。

  • ${}:采用的是直接跟sql语句进行拼接的方式。
  • #{}:采用的是参数预编译的方式,参数的位置使用?进行替代,不会出现sql注入的问题。

如何去接收返回的集合

在日常使用中,resultType更多应用于两类场景。

  • SQL返回单条结果,并且结果集字段名完全被某个对象类匹配,此时可以直接用resultType指定为对应的类。
<select id="searchUserById" resultType="com.phl.mybatis.hello.bean.User">
    select * from tbl_user where id= #{id}
</select>
  • 当SQL返回了一个集合时,这种情况下又细分了三个情况。
  1. 返回一个List
    当返回结果集中每个记录的所有字段完全被某个对象类匹配,此时的resultType属性需要配置成这个对象的类型。

接口

List<User> searchAllUser();

Mapper

<select id="searchAllUser" resultType="com.phl.mybatis.hello.bean.User">
    select * from tbl_user
</select>
  1. 把一个对象放在map中返回
    这里说的把一个对象放在map中返回,本质上也结果集中只有一个记录,并且这个记录最终会放在一个Map里进行返回。这个过程中会把查询结果集中的列名当做Map的Key,列中订单值作为value,在这种情况下,resultType需要配成map。

接口:

Map<String, Object> searchUserMapById(Integer id);

Mapper:

<select id="searchUserMapById" resultType="map">
    select * from tbl_user where id= #{id}
</select>
  1. 把一个集合用Map进行返回
    这里说的是,SQL执行后的结果集中包含不定条记录,并且把每一个记录作为Map的value,放在一个Map中进行返回。这个场景与上一个场景有两个明显区别,首先这个场景中SQL执行结果集是多条记录,上一个是单条记录。另一个是,这个场景中,需要显示的指定Map中的key,而上一个场景中,Map的key是查询结果集的列名。
    这个场景中,需要通过@MapKey注解指明,在将结果集封装到Map的过程中要当做Key的属性的值。
    并且,这里场景里,resultType与第一种场景一样,设置成集合内部元素的类型。

接口:

@MapKey("id")
Map<Integer,User> selectAllUserMap();

Mapper:

<select id="selectAllUserMap" resultType="com.phl.mybatis.hello.bean.User">
    select * from tbl_user
</select>

resultType和resultMap

在上面讲解如何去接收集合类型的返回值时,一直在用resultType属性。现在讲解一下resultType和resultMap。

属性名示意
resultType期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。

从上面的描述可以看出来,首先在一个查询中,这二者只能使用其中一个。另外,两者在使用场景,使用规则上没有区别,只是resultType对应了一个Java类,而resultMap是自定义结果集,这样的自定义结果集在使用中更灵活。
通过一个基础的例子讲解resultType和resultMap的关系。
Mapper:

<select id="searchUserById" resultType="com.phl.mybatis.hello.bean.User">
    select * from tbl_user where id= #{id}
</select>

上面的mapper中,没有显示定义resultMap,而是定义了一个resultType。在这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)。

resultMap

上面讲解了resultType与resultMap之间的关系。下面对resultMap的属性进行讲解。
一个简单的resultMap和resultMap的应用。
demo:

<resultMap id="userResultMap" type="com.phl.mybatis.hello.bean.User">
    <id property="id" column="id"></id>
    <result property="name" column="name"></result>
    <result property="gender" column="gender"></result>
</resultMap>

<select id="searchUserById" resultMap="userResultMap">
    select * from tbl_user where id= #{id}
</select>

上面给出的例子就是resultMap最基本的使用方式:定义自定义结果集,并且将自定义结果集绑定到对应的select语句上。并且,resultMap是允许重复使用到多个select语句上。

就像上面说的,mybatis进行查询的时候,mybatis默认会帮我们进行结果的封装,但是要求列名跟属性名称一一对应上。在实际的使用过程中,我们会发现有时候数据库中的列名跟我们类中的属性名并不是一一对应的,此时就需要起别名。
起别名有两种实现方式:

  1. 在编写sql语句的时候添加别名
  2. 自定义封装结果集----------------------这个就是我们要讲解的resultMap

resultMap属性

属性名称说明
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或者一个类型别名。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。
extends配合discriminator使用。

resultMap子元素

子元素名称说明
constructor用于在实例化类时,注入结果到构造方法中。
id一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能。
result注入到字段或 JavaBean 属性的普通结果。
association一个复杂类型的关联;适用于数据关系中的1对1的情况。
collection一个复杂类型的关联;适用于数据关系中的1对多的情况。
discriminator使用结果值来决定使用哪个。
resultMap子元素:id和result
属性说明
property映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。简单来讲,就是Java对象中的某个属性名。
column数据库中的列名,或者是列的别名。
javaType一个 Java 类的全限定名,或一个类型别名(mybatis中内置了常用类和累的别名)。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
typeHandler这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

支持的JDBC类型图上面讲解了resultMap的基本用法,以及里面最常用的两个子元素,id和result,以及id和result子元素中的属性。
下面继续介绍resultMap中其余的子元素。

resultMap子元素:association和collection
关联Java对象

在定义实体关系的时候,总会出现这样的联系 :1对1,1对多,多对1,多对多这四个情况,其中最后的多对多会通过第三张表进行关联。
在上面讲解的resultMap部分,都只是应对单一实体的操作。那么改如何应对这种多个实体之间的关联关系呢?

一切开始之前,先把使用场景告诉大家,这样更方便大家往后看。

  • association:应对1对1关系;
  • collection:应对1对多关系;

现在对之前的测试代码进行一些更改。之前代码中有一个Bean对象User(用户类),现在要给用户类关联上TA的个人信息(PersonalInformation)。

package com.phl.mybatis.hello.bean;

public class PersonalInformation {
    private Integer id;
    private Integer userId;
    private String email;
    private String idNumber;

    public PersonalInformation() {
    }

    @Override
    public String toString() {
        return "PersonalInformation{" +
                "id=" + id +
                ", userId=" + userId +
                ", email='" + email + '\'' +
                ", idNumber='" + idNumber + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getIdNumber() {
        return idNumber;
    }

    public void setIdNumber(String idNumber) {
        this.idNumber = idNumber;
    }
}

上面给出了个人信息这个类,这里提一下,不论是User或者这个类,都没有用类似于身份证号这样的确实不会重复的值当做主键,而是定义了单纯的主键ID,意义在于,使用逻辑主键,可以降低业务的耦合度。
在个人信息中,通过属性userId关联上了User对象。一个User对应一个PersonalInformation,一个PersonalInformation对应一个User,两者之间是1对1关系。
并且,我们对User类做出修改,在User类中增加一个PersonalInformation类型的属性。

package com.phl.mybatis.hello.bean;

public class User {

    private Integer id;
    private String name;
    private String gender;
    private PersonalInformation personalInformation;

    public User() {
    }

    public PersonalInformation getPersonalInformation() {
        return personalInformation;
    }

    public void setPersonalInformation(PersonalInformation personalInformation) {
        this.personalInformation = personalInformation;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", personalInformation=" + personalInformation +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
}

增加了新的属性,并且设置了getset方法,重写了toString方法,显示提供了无参构造方法。

像是上面说到的,1对1关系应该使用association,现在给出对应的resultMap,看看这个具体是要怎么使用。

<resultMap id="userAndInfo" type="com.phl.mybatis.hello.bean.User">
    <id property="id" column="id"></id>
    <result property="name" column="name"></result>
    <result property="gender" column="gender"></result>
    <association property="personalInformation" javaType="com.phl.mybatis.hello.bean.PersonalInformation">
        <id property="id" column="id"></id>
        <result property="userId" column="user_id"></result>
        <result property="email" column="email"></result>
        <result property="idNumber" column="id_number"></result>
    </association>
</resultMap>

<select id="searchUserAndInfoByUserId" resultMap="userAndInfo">
    select u.*,i.* from tbl_user u left join personal_information i on u.id = i.user_id where u.id = #{id}
</select>

SQL语句的部分很简单,我们忽略,着重讲解一下association的部分。

在这个resultMap中,有一个association子元素,association元素中通过property属性表示了他对应了User中的哪一个属性,并且通过JavaType属性指明了这个association是哪个Java类。association内的子元素与resultMap的一样。
注意一下我上面的配置,这是一个错误示范:
错误示范虽然一个是嵌套在resultmap中,另一个是在association中,并且分别都指定了对应的JavaType,但是在从结果集里进行赋值的时候,他俩都只会匹配上结果集中第一个名字是id的列。从SQL语句可以看出来,这两个值最终都只会取到tbl_user的id值。
所以,在存在嵌套结果中有相同属性名,字段名的情况下,需要对查询的字段取别名(通过SQL),意义就在于让整个resultMap中没有重名的property。

现在再增加一个对象,给用户增加常用地址信息,一个User可以对应多个UserAddress对象。
Java:

package com.phl.mybatis.hello.bean;

import java.util.List;

public class User {

    private Integer id;
    private String name;
    private String gender;
    private PersonalInformation personalInformation;
    private List<UserAddress> userAddressList;

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", personalInformation=" + personalInformation +
                ", userAddressList=" + userAddressList +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public PersonalInformation getPersonalInformation() {
        return personalInformation;
    }

    public void setPersonalInformation(PersonalInformation personalInformation) {
        this.personalInformation = personalInformation;
    }

    public List<UserAddress> getUserAddressList() {
        return userAddressList;
    }

    public void setUserAddressList(List<UserAddress> userAddressList) {
        this.userAddressList = userAddressList;
    }
}

ResultMap:

<resultMap id="userAllInfo" type="com.phl.mybatis.hello.bean.User">
    <id property="id" column="id"></id>
    <result property="name" column="name"></result>
    <result property="gender" column="gender"></result>
    <association property="personalInformation" javaType="com.phl.mybatis.hello.bean.PersonalInformation">
        <id property="id" column="p_id"></id>
        <result property="userId" column="p_user_id"></result>
        <result property="email" column="p_email"></result>
        <result property="idNumber" column="p_id_number"></result>
    </association>
    <collection property="userAddressList" ofType="com.phl.mybatis.hello.bean.UserAddress">
        <id property="id" column="a_id"></id>
        <result property="userId" column="a_user_id"></result>
        <result property="address" column="a_address"></result>
    </collection>
</resultMap>

注意,这里有一个需要注意的地方。
association元素中是通过javaType标注关联对象的类型;collection则是通过ofType进行标注。
collection元素的子元素也与resultMap中的一样。

这里还需要提醒一点。当把resultMap写好之后,一定记得在写SQL的时候,要维护好表和表之间的关联关系。

关联select语句

上面介绍association和collection的时候,是直接把关联的Java对象和属性进行了关联。除了这种写法之外,还可以关联select语句。
先说说本质上的区别。

  • 关联Java对象:通过resultMap创建出一个符合查询结果的映射关系,满足了表和表之间的关联关系。在查询的过程里,只会执行一个SQL语句。
  • 关联SQL语句:通过关联子查询一句,将上面提到的一个SQL进行多表关联查询的情况,转换成了进行多次查询的场景。这样的转换带来了一个新的特性,lazyLoading(延迟加载)。

还是沿用上面说到的三个类,分别是User,PersonalInformation和UserAddress。之前对于PersonalInformation和UserAddress都是通过UserDao进行的关联查询,现在需要用他们自身的方法进行查询,所以在开始之前,需要先补充对应dao和mapper。

package com.phl.mybatis.hello.dao;

import com.phl.mybatis.hello.bean.PersonalInformation;

public interface PersonalInformationDao {

    PersonalInformation searchInformationByUserId(Integer id);
}

package com.phl.mybatis.hello.dao;

import com.phl.mybatis.hello.bean.UserAddress;

public interface UserAddressDao {

    UserAddress searchAddressByUserId(Integer id);
}

<?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.phl.mybatis.hello.dao.UserAddressDao">

    <resultMap id="addressMap" type="com.phl.mybatis.hello.bean.UserAddress">
        <id property="id" column="id"></id>
        <result property="userId" column="user_id"></result>
        <result property="address" column="address"></result>
    </resultMap>

    <select id="searchAddressByUserId" resultMap="addressMap">
        select * from address where user_id = #{id}
    </select>
</mapper>
<?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.phl.mybatis.hello.dao.PersonalInformationDao">

    <resultMap id="informationMap" type="com.phl.mybatis.hello.bean.PersonalInformation">
        <id property="id" column="id"></id>
        <result property="userId" column="user_id"></result>
        <result property="email" column="email"></result>
        <result property="idNumber" column="id_number"></result>
    </resultMap>

    <select id="searchInformationByUserId" resultMap="informationMap">
        select * from personal_information where user_id = #{id}
    </select>
</mapper>

在添加完对应的接口和Mapper之后,重新回到UserDaoMapper中,在里面编写对应的查询和resultMap。

<resultMap id="UserStep" type="com.phl.mybatis.hello.bean.User">
    <id property="id" column="id"></id>
    <result property="name" column="name"></result>
    <result property="gender" column="gender"></result>
    <association property="personalInformation" select="com.phl.mybatis.hello.dao.PersonalInformationDao.searchInformationByUserId"
                 column="id"></association>
    <collection property="userAddressList" select="com.phl.mybatis.hello.dao.UserAddressDao.searchAddressByUserId"
                column="id"></collection>
</resultMap>

<select id="searchUserAllByUserIdPerStep" resultMap="UserStep">
    select * from tbl_user where id = #{id}
</select>

上面例子中,每个SQL的语句都是简单的单表查询,通过resultMap中的association和collection元素,实现了多个查询语句的关联。
因为association和collection的用法一样,因此这里只用association进行讲解。
association元素中,property还是指代了类中的属性,这个和之前介绍的一样。
但是,在之前的例子中,后续就使用了JavaType这样的属性还关联了一个对象,这次不同了。
这里通过select关联了一个目标查询。在指定目标查询的时候,若是同一个接口中的方法,则直接用方法名即可。若不是同一个接口的方法,则需要通过类的完全限定名+方法名进行定位,就像这里写的com.phl.mybatis.hello.dao.PersonalInformationDao.searchInformationByUserId。最后的column,则表示要用当前查询结果集中的哪个字段,作为嵌套查询的关联字段进行关联。比如这个例子中,另外两张表里都有user_id这样的值记录了user表中的id值,因此,此处用于关联的就是从user中查询出来的id,并且这个id最终会作为下面两个SQL的入参,进行查询,这两个SQL就是分别定义在另外两个mapper中的内容。

<select id="searchInformationByUserId" resultMap="informationMap">
    select * from personal_information where user_id = #{id}
</select>
<select id="searchAddressByUserId" resultMap="addressMap">
    select * from address where user_id = #{id}
</select>

最终运行结果如下:
运行结果##### 延迟加载
这里穿插着讲一个mybatis的设置延迟加载
延迟加载的意思很明显,不要在一开始就进行加载,而是在使用的时候再进行加载。
上面的运行结果我们看到,这里运行了3条SQL,但是并非每一次都需要把3条SQL都执行完全。因此就会需要延迟加载的情况。
mybatis配置文件中(mybatis-config.xml)中,可以进行如下配置:
mybatis延迟加载设置当开启这个全局属性之后,刚才写的那个分部查询的例子就支持了延迟加载。延迟加载之后的运行效果:
延迟加载效果图并且,在开启了延迟加载之后,如果某个SQL不想进行延迟加载,可以在对应的关联嵌套上进行配置,从而覆盖全局配置的值。

<resultMap id="UserStep" type="com.phl.mybatis.hello.bean.User">
        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
        <result property="gender" column="gender"></result>
        <association property="personalInformation" select="com.phl.mybatis.hello.dao.PersonalInformationDao.searchInformationByUserId"
                     column="id" fetchType="eager"></association>
        <collection property="userAddressList" select="com.phl.mybatis.hello.dao.UserAddressDao.searchAddressByUserId"
                    column="id" fetchType="lazy"></collection>
    </resultMap>

如上,我分别在association和collection中,添加了fetchType属性并指明了eager和lazy去覆盖全局配置。

综上,就是对association和collection的讲解。不论是关联查询,或者分部查询,在实际使用中不做具体限制,需要根据数据量,数据库中的索引,业务场景等诸多情况进行分析之后,选择最合适的写法。

动态SQL

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
详细内容详见官网:动态SQL.

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

现在对常用几种进行介绍。

if

if元素用在SQL的内容,例如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

if元素内部只有一个属性test,里面要写一个boolean类型的表达式。如果表达式为true,则会拼接上if内部的内容,否则不会进行拼接。if元素的固定写法如下:

<if test="">
            
</if>

但是,在使用if的时候需要注意到,因为if的判断不一定总是true,所以会出现字符拼接的问题,因此推荐与where一起连用。

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
	<if test="id != null">
		and id = #{id}
	</if>
	<if test="name != null">
		and name = #{name}
	</if>
  </where>
</select>

使用了where元素后,SQL会动态的将where字句中,第一个多余的and或者or移除掉。因此在写这个条件的时候,不用再去担心多出来的and了。

choose

这个是一组元素,choose,when,otherwise。对应了java中的switch case的语句。注意,在这组条件中,如果匹配上了第一个when的话,剩余的条件就不会进行判断和拼接了。整体语法如下:

<select id="" >
select ......
<choose>
    <when test="">

    </when>
    <when test="">

    </when>
    <otherwise>

    </otherwise>
</choose>
</select>

在choose中,可以出现多个when,并且提供了默认的otherw(可以不出现)。在when元素里,同样只有一个属性test,同样里面要写一个boolren类型的表达式。一旦满足了when的条件,就会把when里面的语句进行拼接,并且,一旦有匹配上的内容,就不会再去比对剩余条件。

foreach

foreach语句是用来进行集合循环的。
语法如下:

<foreach collection="" index="" open="" close="" item="" separator="">
            
</foreach>
<select id="getEmpByDeptnos" resultType="Emp">
    select * from emp where deptno in 
    <foreach collection="deptnos" close=")" index="idx" item="deptno" open="(" separator=",">
        #{deptno}
    </foreach>
</select>
  • collection:“deptnos” 指定要遍历的集合
  • index:给定一个索引值
  • open:表示以什么开始
  • close:表示以什么结束
  • item:遍历的每一个元素的值
  • separator:表示多个元素的分隔符
set

用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

但是,在上面的例子中,如果bio是空的话,整体SQL语句会出现多了个逗号的情况,这里就需要使用另一个标签进行支持。

trim

trim是一个更灵活,更强大的拼接元素。类似在查询的时候,where只能替换位于句首的,第一个的and或者or,但是关联的and或者or写在句子末尾就没有办法了。或者上面的update语句,是可能出现多了个逗号的情况。在这种情况下,就需要更强大的,自定义的,trim元素,如法如下:

<trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
            
</trim>
  • prefix:前缀,为sql整体添加一个前缀
  • prefixOverrides:去除整体字符串前面多余的字符
  • suffix:为sql整体添加一个后缀
  • suffixOverrides:去除后面多余的字符串

例子:

<select id="getEmpByCondition" resultType="com.mashibing.bean.Emp">
        select * from emp
    <trim prefix="where" prefixOverrides="and" suffixOverrides="and" suffix="order by empno">
        <if test="empno!=null">
            empno > #{empno} and
        </if>
        <if test="ename!=null">
            ename like #{ename} and
        </if>
        <if test="sal!=null">
            sal > #{sal} and
        </if>
    </trim>
</select>

当在prefixOverrides或者suffixOverrides中有多个目标参数的时候。需要用“|”进行分割,例如:prefixOverrides="AND |OR "

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值