Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Mybatis学习笔记

1
Mar
2012

Mybatis学习笔记

By Alex
/ in Java
/ tags Mybatis, 学习笔记
0 Comments
简介

Mybatis是一个持久化层的Java框架,但是它并不是完整的ORM方案,它是以SQL为中心的,更像JOOQ而不是Hibernate。这意味着,如果使用Mybatis,你在某种程度上需要抛弃OO的领域模型设计(以对象为中心),转而以数据库表为中心进行设计。

Mybatis的特色是可以定制SQL语句(甚至是存储过程),这让你有很好的机会执行SQL优化,但很容易丢失数据库方面的可移植性。能够定制SQL,也使Mybatis能够很好的支持遗留数据库。

HelloWorld

本章结合一个非常简单的例子,介绍Mybatis的基本组件和基础用法。

Maven依赖
pom.xml
XML
1
2
3
4
5
6
7
8
9
10
11
12
<!-- Mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>x.x.x</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.36</version>
</dependency>
SqlSessionFactory 

每个Mybatis应用都是以一个SqlSessionFactory实例为中心的,你可以基于XML或者Java的方式,提供SqlSessionFactory的初始化参数。

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。

基于XML初始化

Java代码:

Java
1
2
InputStream is = Resources.getResourceAsStream( "cc/gmem/study/ssm/mybatis/mybatis-config.xml" );
SqlSessionFactory sf = new SqlSessionFactoryBuilder().build( is ); 

对应主配置文件:

/cc/gmem/study/ssm/mybatis/mybatis-config.xml
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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>
    <!-- 事务管理和连接池配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- UserMapper.xml是一个映射器,包含SQL代码与Java类之间的映射信息 -->
    <mappers>
        <mapper resource="cc/gmem/study/ssm/entity/UserMapper.xml"/>
    </mappers>
</configuration>
基于Java代码初始化
Java
1
2
3
4
5
6
7
8
9
10
11
DataSource dataSource = new PooledDataSource(
        "com.mysql.jdbc.Driver",
        "jdbc:mysql://localhost:3306/test",
        "root", "root"
);
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment( "development", transactionFactory, dataSource );
Configuration configuration = new Configuration( environment );
// UserMapper也是映射器,它是一个配置了注解的Java类
configuration.addMapper( UserMapper.class );
SqlSessionFactory sf = new SqlSessionFactoryBuilder().build( configuration );
映射器

上面两种初始化SqlSessionFactory的方式中,分别引用了UserMapper.xml文件、USerMapper类,这两个文件就是所谓的映射器(Mapper)。

映射器指定了SQL语句和Java类型之间的映射关系。Mybatis支持XML文件、Java注解两种映射器配置方式。

XML映射器

对应映射器文件,被主配置文件引用:

/cc/gmem/study/ssm/entity/UserMapper.xml
XML
1
2
3
4
5
6
7
8
<?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">
<!-- namespace为映射语句提供统一的命名空间,在项目复杂的时候避免出现重复的映射语句id -->
<mapper namespace="cc.gmem.study.ssm.entity.UserMapper">
    <select id="selectUser" resultType="cc.gmem.study.ssm.entity.User">
        select * from User where id = #{id}
    </select>
</mapper> 
注解映射器
/cc/gmem/study/ssm/entity/UserMapper.java
1
2
3
4
5
6
import org.apache.ibatis.annotations.Select;
 
public interface UserMapper {
    @Select( "SELECT * FROM User WHERE id = #{id}" )
    User selectUser( int id );
} 

尽管基于注解的配置在各大框架的用户中都越来越流行,但是由于注解自身的限制,对于很多Mybatis的高级映射,XML映射方式是必须的。

如果存在和UserMapper.class在类路径上一致的UserMapper.xml文件,Mybatis会自动将其加载进来作为映射补充。如果两者存在重复映射语句,Mybatis会报错。

SqlSession 

该类包含了面向数据库执行 SQL 命令所需的所有方法,日常工作主要通过它进行。

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,不能被共享。

基于XML映射器的用法
Java
1
2
3
4
5
6
7
SqlSession session = sf.openSession();
try {
    // 参数由:名字空间 + 映射语句构成
    User user = session.selectOne( "cc.gmem.study.ssm.entity.UserMapper.selectUser", 1 );
} finally {
    session.close();
}
基于注解映射器的用法
Java
1
2
3
4
5
6
7
8
SqlSession session = sf.openSession();
try {
    // 映射器类的实例,其生命周期一般限定在一个方法内部
    UserMapper mapper = session.getMapper( UserMapper.class );
    User user = mapper.selectUser( 1 );
} finally {
    session.close();
}

可以看到,对于简单语句来说,注解使代码显得更加简洁。但是Java注解对于稍微复杂的语句就会力不从心并且会显得更加混乱,因此业务复杂的话,最好使用XML风格的映射器。

主配置文件

前面提到的mybatis-config.xml,就是Mybatis主配置文件,定制此文件可以在很大程度上改变Mybatis的行为。该配置文件的根元素是configuration。

properties

可以定义一系列属性,让配置文件其它地方基于 ${prop} 的语法引用。你可以引用外部的Java属性文件:

XML
1
<properties resource="cc/gmem/study/ssm/config.properties" />

也可以使用子元素直接指定属性:

XML
1
2
3
<properties>
  <property name="username" value="root"/>
</properties>

甚至两种方式混合使用。 属性优先级由高到低:

  1. SqlSessionFactoryBuilder.build()中传递的属性
  2. 通过resource指定的Java属性文件中的属性
  3. property子元素指定的属性
settings

该元素的setting子元素可以改变Mybatis的行为,在子元素setting中,可以指定以下项:

设置项 说明
cacheEnabled Boolean=true,所有映射器中配置的缓存的全局开关
lazyLoadingEnabled Boolean=false,如果true,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
aggressiveLazyLoading Boolean=true,如果true,对任意延迟属性的调用会使带有延迟加载属性的对象(该属性引用的对象)完整(激进)加载;反之,每种属性将会按需加载
multipleResultSetsEnabled Boolean=true,是否允许单一语句返回多结果集(需要兼容驱动)
useColumnLabel Boolean=true,使用列标签代替列名。不同的驱动在这方面会有不同的表现
useGeneratedKeys Boolean=false,如果true,强制自动生成主键
defaultExecutorType

Enum(SIMPLE|REUSE|BATCH)=SIMPLE。设置默认的SQL执行器:

  1. SIMPLE,普通的执行器
  2. REUSE,重用预处理语句
  3. BATCH,重用预处理语句并执行批量更新
defaultStatementTimeout SQL执行超时的秒数
defaultFetchSize 给驱动提示,通知抓取数据的跳数,单个查询可以覆盖之
safeRowBoundsEnabled Boolean=false,是否允许在嵌套语句中使用分页(RowBounds),如果允许设置为fasle
safeResultHandlerEnabled Boolean=true,是否允许在嵌套语句中使用分页(ResultHandler),如果允许设置为fasle
mapUnderscoreToCamelCase

Boolean=false,是否把基于下划线的数据库列映射为驼峰式大小写

该设置项和自动映射机制有关,Mybatis的默认映射方式是:列名、属性名不区分大小写的想等,则匹配

localCacheScope

Enum(SESSION | STATEMENT)=SESSION。Mybatis使用本地缓存机制(Local Cache)来防止循环引用、加速重复嵌套查询:

  1. SESSION,会话中所有语句共享缓存,类似于Hibernate一级缓存
  2. STATEMENT,仅单个语句内部使用缓存
lazyLoadTriggerMethods Enum(equals|clone|hashCode|toString),逗号分隔。哪些方法会触发对象的延迟加载
defaultScriptingLanguage 指定动态SQL生成使用的默认语言
callSettersOnNulls Boolean=false,当结果集中值为NULL时,是否调用实体的setter或者Map的Put
logImpl Enum(SLF4J | LOG4J|...)指定Mybatis使用的日志实现,默认自动查找
proxyFactory Enum(JAVASSIST|CGLIB)=JAVASSIS,创建延迟加载对象时使用的代理库
typeAliases

该参数仅用于XML配置风格,其子元素typeAlias为某个Java类设置别名:

XML
1
2
3
<typeAliases>
  <typeAlias alias="User" type="cc.gmem.study.entity.User"/>
</typeAliases>

或者指定Java类所在的包名,这样,包内所有Java类的Base name自动别设置为别名:

XML
1
2
3
<typeAliases>
    <package name="cc.gmem.study.ssm.entity"/>
</typeAliases>

你可以在映射器配置中,使用别名来引用一个Java类,别名更加短小。

内置别名

Mybatis为常用的JDK内置类型已经建好了大小写无关的别名,例如:

别名 Java类型
_byte byte
byte Byte
decimal BigDecimal
object Object
collection Collection
map Map
hashmap HashMap
list List
arraylist ArrayList
iterator Iterator
typeHandlers

该元素的typeHandler子元素用于注册自定义的类型处理器。

类型处理器用于执行类型转换。无论在Mybatis在预处理语句中设置一个值时,还是在它从结果集中获取一个值时,都会调用类型处理器在JDBC类型和Java类型之间进行适当的转换。类型处理器的职责类似于Hibernate的类型系统。

Mybatis已经内置了常用的类型处理器,除非你需要定制自己的类型处理器,否则不需要配置该元素。

自定义类型处理器

若要定制自己的类型处理器,可以继承BaseTypeHandler类:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 覆盖Mybatis内置的处理Java的String类型属性和JDBC的VARCHAR参数/结果的类型处理器
@MappedJdbcTypes( JdbcType.VARCHAR )
public class MyTypeHandler 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 );
    }
}

然后在此设置项中注册新的类型处理器:

Java
1
2
3
<typeHandlers>
    <typeHandler handler="cc.gmem.mybatis.ths.MyTypeHandler"/>
</typeHandlers>

你也可以指定包,Mybaits在包中查找多个类型处理器:

XML
1
2
3
<typeHandlers>
    <package name="cc.gmem.mybatis.ths"/>
</typeHandlers>
处理枚举类型

要让Mybatis支持枚举类型,可以注册EnumTypeHandler或者EnumOrdinalTypeHandler,后者默认已经注册。这两者的区别是:

  1. EnumTypeHandler,把枚举值对应的名字存入数据库
  2. EnumOrdinalTypeHandler,把枚举值对应的数字存入数据库

除了以全局方式来指定枚举处理方式以外,你还可以针对单个枚举设置:

XML
1
2
<!-- 此设置仅当Java类型是RoundingMode时生效 -->
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>

甚至针对单个映射语句设置:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
    <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="funkyNumber" property="funkyNumber"/>
        <!-- 在查询时为字段指定枚举处理器 -->
        <result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
    </resultMap>
    <select id="getUser" resultMap="usermap">
        select * from Users
    </select>
    <!-- 在插入时为字段指定枚举处理器 -->
    <insert id="insert">
        insert into Users (id, roundingMode) values (
        #{id}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
        )
    </insert>
 
</mapper>
objectFactory

Mybatis在获取结果集后,需要依据映射器将结果集转换为Java对象,而它创建Java对象就是通过对象工厂来完成的。

默认的对象工厂仅仅是调用Java类的默认构造器,如果你需要修改此默认行为,可以实现自己的对象工厂,并注册:

XML
1
2
3
4
<objectFactory type="cc.gmem.study.ssm.DebugingObjectFactory">
    <!-- 这些配置项通过工厂的setProperties传递给工厂 -->
    <property name="debug" value="true"/>
</objectFactory>
plugins

用于注册Mybatis插件,每个plugin子元素对应一个插件。

Mybatis设计了一个很简单的插件扩展机制——允许你拦截映射语句执行过程的某个点,并加入扩展逻辑。可以拦截的点包括:

  1. Executor的update, query, flushStatements, commit, rollback, getTransaction, close, isClosed方法
  2. ParameterHandler的getParameterObject, setParameters方法
  3. ResultSetHandler的handleResultSets, handleOutputParameters方法
  4. StatementHandler的prepare, parameterize, batch, update, query方法 
实现拦截器

要拦截以上方法,你只需要实现拦截器接口,并添加必要的注解:

/cc/gmem/study/ssm/mybatis/plugins/MyInterceptor.java
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package cc.gmem.study.ssm.mybatis.plugins;
 
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
 
import java.util.Properties;
 
/* 指定被拦截的方法的签名 */
@Intercepts( { @Signature(
        type = Executor.class,
        method = "update",
        args = { MappedStatement.class, Object.class } )
} )
public class MyInterceptor implements Interceptor {
    private boolean debug;
 
    public Object intercept( Invocation invocation ) throws Throwable {
        if ( debug ) {
            logDebugInfo( invocation );
        }
        // 下面的调用执行Mybatis的默认行为
        return invocation.proceed();
    }
 
    public Object plugin( Object target ) {
        return Plugin.wrap( target, this );
    }
 
    public void setProperties( Properties properties ) {
        if ( properties.containsKey( "debug" ) ) {
            this.debug = true;
        }
    }
}
注册插件
XML
1
2
3
4
5
<plugins>
    <plugin interceptor="cc.gmem.study.ssm.mybatis.plugins.MyInterceptor">
        <property name="debug" value="true"/>
    </plugin>
</plugins>
environments

你可以在此元素下定义多个环境,并指定其中一个为默认环境。

所谓环境,是指一个数据源及操控它的事务管理器。通过定义多个环境,Mybatis可以快速的在不同类型的数据库、不同数据实例之间进行切换。

每个SqlSessionFactory只能使用一个环境,你可以使用如下签名的方法为SqlSessionFactory指定(非默认的)环境:

Java
1
2
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);

单个环境的配置,看起来大概是这个样子:

XML
1
2
3
4
5
6
7
<!-- id为此环境的唯一标识  -->
<environment id="development">
    <!-- 事务管理器配置 -->
    <transactionManager type="JDBC"/>
    <!-- 数据源配置 -->
    <dataSource type="POOLED" />
</environment>
transactionManager

该元素定义某个环境使用的事务管理器,Mybatis支持两种类型的事务管理器:

  1. JDBC,直接使用JDBC API进行提交和回滚,依赖于从数据源得到的连接来管理事务
  2. MANAGED,该配置让容器来管理事务的整个生命周期。默认情况下,该事务管理器会负责连接的关闭,如果需要禁止此行为,可以:
    XML
    1
    2
    3
    <transactionManager type="MANAGED">
        <property name="closeConnection" value="false"/>
    </transactionManager>

当你整合Mybatis和Spring时,不需要配置事务管理器。

数据源

Mybatis支持三种数据源:

  1. UNPOOLED,不启用连接池的简单数据源。配置项:
    配置项 说明
    driver JDBC 驱动的 Java 类的完全限定名
    url JDBC URL 地址
    username 登录数据库的用户名
    password 登录数据库的密码
    defaultTransactionIsolationLevel 默认的连接事务隔离级别
    driver.*** 将***属性传递给JDBC驱动
  2. POOLED,使用连接池实现连接复用。除了UNPOOLED提供的配置项外,还支持:
    配置项 说明
    poolMaximumActiveConnections 任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10
    poolMaximumIdleConnections 任意时间可能存在的空闲连接数
    poolMaximumCheckoutTime 在被强制返回之前,池中连接被拿出来使用的最大时间,默认20000ms
  3. JNDI,使用JavaEE容器提供的JNDI上下文来查找数据源
databaseIdProvider

Mybatis可以依据数据库产品的不同,来执行不同的SQL语句。一般你可以使用默认值:

XML
1
<databaseIdProvider type="DB_VENDOR" />

来支持尽可能多的主流数据库。

mappers

指定映射器的引用,即SQL映射语句所在的XML文件或者Java类的引用:

XML
1
2
3
4
5
6
7
8
<!-- 指定一个类路径下的XML文件作为映射器 -->
<mapper resource="cc/gmem/study/ssm/entity/UserMapper.xml"/>
<!-- 指定文件系统中的XML文件作为映射器 -->
<mapper url="file:///var/mappers/UserMapper.xml"/>
<!-- 指定一个Java接口为映射器 -->
<mapper class="cc.gmem.study.ssm.entity.UserMapper"/>
<!-- 指定一个包,包中所有所有接口被作为映射器 -->
<package name="cc.gmem.study.ssm.mappers"/>
映射器配置

在使用Mybatis进行开发的过程中,映射器配置是一项主要的工作。合理使用映射器,可以减少90%以上的SQL代码。

OGNL

原样字符串(${})、动态SQL元素中,可以使用OGNL表达式。OGNL的基础知识和语法,请参考Struts2学习笔记。

在映射器配置中,OGNL上下文对象 #this 是一个类似于Apache Struts值栈的对象。你可以直接访问查询参数的任何属性,以及以下特殊属性:

特殊属性 说明
_parameter 查询参数对象本身 
_databaseId 当前数据库类型,用于多数据库支持 
select

这是映射器配置最常用的一个元素,用于定义一个将结果集映射到Java对象的查询语句。下面是一个简单的例子:

XML
1
2
3
<select id="selectUser" parameterType="int" resultType="hashmap">
    SELECT * FROM USER WHERE ID = #{id}
</select>

这个配置的含义是,定义一个标识符为selectUser的映射语句,该语句:

  1.  接受一个int型的参数
  2. 查询结果映射为java.util.HashMap。该Map的键是数据库列的别名,值则是结果行中的对应的值
  3. #{id} 告知Mybatis,创建一个预编译SQL语句,并在此处设置一个位置参数,就像这样:
    Java
    1
    2
    3
    String selectUser = "SELECT * FROM USER WHERE ID=?";
    PreparedStatement ps = conn.prepareStatement(selectUser);
    ps.setInt(1,id); 
属性列表
属性 说明
id String,在当前命名空间中唯一的标识符,可以被用来引用这条语句
parameterType

Type/TypeAlias,传入这条语句的参数类型,可选,因为Mybatis可以基于类型处理器推断参数类型

如果该语句有多个参数,你可以指定参数类型为一个复合的对象类型,并在SQL语句中使用#{propName}引用对象的属性作为参数

resultType

Type/TypeAlias,从该语句结果集构造的Java对象的类型。对于期望返回集合类型的,这里指定的是集合元素的类型

不能和resultMap一起使用

resultMap String,引用外部定义的结果映射规则
flushCache Boolean,如果设置为true,那么该语句一旦被执行,本地缓存、二级缓存皆被清空
useCache Boolean,对于SELECT语句默认为true,如果为true则该语句的结果被二级缓存
timeout Number,语句执行超时的秒数
fetchSize Number,提示JDBC,每次抓取数据的行数
statementType

Enum(TATEMENT | PREPARED | CALLABLE) = PREPARED

指示Mybatis创建何种类型的JDBC语句

resultSetType

Enum(FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE)

提示Mybatis创建何种类型的结果集,默认取决于驱动

resultOrdered

Boolean=false,仅仅针对嵌套结果SELECT语句使用

resultSets

仅对多结果集的情况,逗号分隔的结果集名称

insert|update|delete

这三类元素分别表示插入、更新、删除语句,它们的实现方式很接近:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
<insert id="insertUser">
    INSERT INTO USER (ID,NAME AGE,DOB) VALUES (#{id},#{name},#{age},#{dob})
</insert>
<update id="updateUser">
    UPDATE USER SET
        NAME = #{name},
        AGE = #{age},
        DOB = #{dob}
    WHERE ID = #{id}
</update>
<delete id="deleteUser">
    DELETE FROM USER WHERE ID = #{id}
</delete>
属性列表
属性 说明
id 参见select元素的属性说明
parameterType
flushCache
timeout
statementType
useGeneratedKeys Boolean=false,仅针对INSERT/UPDATE语句。如果设置为true,则Mybatis会利用JDBC的getGeneratedKeys方法来取回数据库内部自动生成的列值,MySQL、MS SQL Server等数据库支持自增长方式的列值自动生成
keyProperty

设置为一个属性名,Mybatis将通过getGeneratedKeys的返回值,或者insert语句的selectKey子元素来设置该属性的值

如果你想得到多个自动生成的列,可以设置逗号分隔的多个属性

回填主键

插入单个对象时,你可以把数据库自动生成的主键回填给Java对象的某个属性:

XML
1
2
3
4
<!-- 回填给参数的id属性 -->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO USER (NAME, AGE, DOB) VALUES ( #{name}, #{age}, #{dob} )
</insert>

对应Java代码:

Java
1
2
3
4
5
session = openSqlSession();
User user = new User( "Alex", 30, "1986-09-12" );
session.insert( "insertUser", user );
Assert.assertTrue( user.getId() != 0 );
session.commit();
selectKey

如果数据库不支持自动生成主键,你可以让Mybatis生成,并插入到数据库:

XML
1
2
3
4
5
6
<insert id="insertUser">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
        SELECT SEQ_MINE.nextval FROM DUAL
    </selectKey>
    INSERT INTO USER (ID, NAME, AGE, DOB) VALUES ( #{id}, #{name}, #{age}, #{dob} )
</insert>

selectKey属性说明如下:

statementTypeÈ

属性 说明
keyProperty 与参数的什么属性匹配,支持逗号分隔多个值
keyColumn 匹配属性对应的列名称,支持逗号分隔多个值
resultType 属性值的类型
order

Enum(BEFORE|AFTER):

  1. 如果设置为BEFORE,先执行此selectKey语句,得到ID,然后执行插入语句
  2. 如果设置为AFTER,则先执行插入语句
statementType

Enum(TATEMENT | PREPARED | CALLABLE)

多行插入

如果数据库支持多行插入,你可以传递一系列对象:

XML
1
2
3
4
5
6
7
8
9
10
11
<insert id="insertUsers" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO USER (NAME, AGE, DOB) VALUES
    <!--
        collection,参数对象的什么属性作为别迭代的集合
        item,为迭代元素指定变量名
        separator,分隔每个迭代项的字符串
    -->
    <foreach collection="users" item="item" separator=",">
        ( #{item.name}, #{item.age}, #{item.dob} )
    </foreach>
</insert> 

对应Java代码:

Java
1
2
3
4
5
6
7
8
List<User> users = new ArrayList<User>();
users.add( new User( "Alex", 30, "1986-09-12" ) );
users.add( new User( "Meng", 26, "1989-11-06" ) );
users.add( new User( "Cai", 1, "2014-11-27" ) );
Map<String, Object> params = new HashMap<String, Object>();
params.put( "users", users );
session.insert( "insertUsers", params );
session.commit();

注意:Mybatis无法在批量插入时获得数据库自动生成的列。 

sql

该元素用来定义可重复使用的SQL片断,它可以被包含在其它语句中。sql支持参数化:

XML
1
<sql id="userColumns">${alias}.ID, ${alias}.NAME, ${alias}.AGE, ${alias}.DOB</sql>

在其它语句中,你可以引用sql元素:

XML
1
2
3
4
5
6
7
<select id="selectUser" resultType="User">
    SELECT
        <include refid="userColumns">
            <property name="alias" value="u" />
        </include>
    FROM USER u WHERE ID = #{id}
</select>

sql也可以引用sql元素,甚至引用者的refid都可以参数化:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<sql id="dict">T_DICT_${basename}</sql>
<sql id="from">
    FROM
    <include refid="${tabtype}" />
</sql>
 
<select id="selectDept">
    SELECT *
    <include refid="from">
        <!-- 这个属性传递给直接引用 -->
        <property name="tabtype" value="dict" />
        <!-- 这个属性传递给简介引用,即dict -->
        <property name="basename" value="DEPT" />
    </include>
</select>
参数

前面很多地方已经用到了参数,你可以为语句明确的指定参数类型(parameterType) ,例如:

XML
1
2
3
<insert id="insertUser" parameterType="User">
    INSERT INTO USER (NAME, AGE, DOB) VALUES ( #{name}, #{age}, #{dob} )
</insert>

这样,当调用此语句时,你传入了一个User类型的对象,则它的name、age、dob属性都会通过反射机制找到,然后传入到预编译语句之中。

属性占位符

对于语句中的占位符,不仅可以指定一个名字,还可以指定:

  1. javaType,该属性的Java类型,除非该属性的容器对象是一个Map,一般不需要设置
  2. jdbcType,该属性对应的JDBC类型,如果null被当作值传递,那么对于所有可能为空的列,该属性都是需要的
  3. numericScale,对于数值类型,你可以用此字段指定小数位数
  4. typeHandler,你可以为属性指定一个类型处理器
  5. mode,可以设置为IN|OUT|INOUT,如果设置为OUT|INOUT,则参数对象的属性值可能会被改变

例如: #{property,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}  

参数转换

通过SqlSession的API调用映射语句时,如果:

  1. 传入的是一个数组或者列表,则Mybatis会自动将其转换为Map,此Map的键分别为array、list
  2. 传入的是一个简单类型,那么属性占位符的名称可以随意取
原样字符串

如果你想往SQL里插入一个字面值(而不是预编译语句的位置参数),可以使用${},例如: ORDER BY ${columnName} 。columnName具体是何种,取决于上下文参数。

结果映射

当我们指定resultType为Map或者实体类时,已经在间接的使用resultMap,只不过映射方式是Mybatis自动推导出来的:

  1. 如果结果类型是Map,则把列名映射为Map的键
  2. 如果结果类型是实体类,则把列名映射为实体类的属性

假设实体类属性和列名不是完全匹配的,你就必须使用SQL的列别名来提示Mybatis进行映射:、

XML
1
2
3
4
5
6
7
8
9
<!-- 列别名指向对象属性名 -->
<select id="selectUser" resultType="User">
    SELECT
        ID as "userId",
        NAME as "userName",
        AGE as "age",
        DOB as "birthday"
    FROM USER u WHERE ID = #{id}
</select>
resultMap简介

你可以为语句指定resultMap而不是resultType属性, 前者引用一个resultMap元素。

resultMap元素定义一个数据库列——Java属性的映射规则,例如:

XML
1
2
3
4
5
6
7
8
9
10
<resultMap id="userResult" type="User">
    <id property="userId" column="ID"/>
    <result property="userName" column="NAME"/>
    <result property="birthday" column="DOB"/>
</resultMap>
 
<!-- 引用一个resultMap -->
<select id="selectUser" resultMap="userResult">
    SELECT * FROM USER u WHERE ID = #{id}
</select>
自动映射

在简单的场景下,我们可以让Mybatis自动完成属性——列映射,在复杂的场景下,我们可以使用resultMap。

Mybatis默认映射规则是:如果列名和Java属性名在不区分大小写的情况下相等,那么它们匹配成功。但是有些时候,我们数据库列名采用全大写、下划线风格,而Java属性一般都是驼峰式大小写。使用默认的自动映射规则无法满足需求,这时候,我们可以打开设置项mapUnderscoreToCamelCase,改变自动映射的规则。

即使在resultMap中,自动映射也起作用,因此你不必手工编写所有字段的映射规则。

缓存

Mybatis3的缓存实现改进了很多。默认情况下,只有局部缓存被启用。要启用SqlSessionFactory级别的二级缓存(跨Session),需要在映射文件中添加一行:

XML
1
<cache/>

注意,缓存配置是绑定到映射文件的名字空间的。

属性列表
属性 说明
eviction

缓存满了以后,如何进行清理。默认LRU,可选值:

  1. LRU,最近最少使用,移除最长时间不被使用的对象
  2. FIFO,先进先出,按照对象进入缓存的顺序来移除
  3. SOFT ,软引用,移除基于垃圾回收器状态和软引用规则的对象
  4. WEAK,弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInterval 缓存刷新间隔,毫秒数。默认不设置,即仅仅在调用语句时才刷新
size 缓存对象的最大数量,默认1024
readOnly 是否只读方式使用缓存,如果true可以提高性能。默认false
type 可以指定自定义缓存实现
自定义缓存

你可以实现自己的缓存,只需要实现org.mybatis.cache.Cache接口。要使用自定义缓存机制。

语句级设置

你可以设置每个映射语句的缓存规则,默认规则是这样的:

XML
1
2
3
4
<select  flushCache="false" useCache="true"/>
<insert  flushCache="true"/>
<update  flushCache="true"/>
<delete  flushCache="true"/>
resultMap元素

映射器中的resultMap元素非常强大和重要,因此我们在此详细讨论它。

上面我们尝试了使用resultMap解决数据库字段名、实体类名不对应的问题。实际上resultMap能做的远远不止这点,它可以很好的解决关联映射问题——把结果集映射到一个对象图中。

resultMap属性 
属性 说明
id resultMap在名字空间中的唯一标识
type 此resultMap将结果集映射为何种对象
autoMapping 覆盖全局的自动映射设置
resultMap子元素概要 
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap>
    <!-- 用什么构造器来示例化实体类-->
    <constructor>
        <!-- 构造器参数:注入到实体标识符-->
        <idArg></idArg>
        <!-- 构造器参数:注入到普通属性  -->
        <arg></arg>
    </constructor>
    <!-- 注入到实体对象的标识符 -->
    <id></id>
    <!-- 注入到实体对象的普通属性 -->
    <result></result>
    <!-- 声明一个关联映射 -->
    <association></association>
    <!-- 进行一个集合映射 -->
    <collection></collection>
    <!-- 根据结果值决定使用哪个结果映射 -->
    <discriminator></discriminator>
</resultMap>

下面依次介绍这些子元素的功能

id|result

这两个子元素都是把单个列的值映射到简单数据类型(数字、字符串、日期、布尔等),只是id提示Mybatis该字段是实体类的标识符,Mybatis会用此信息提升缓存、嵌入结果映射(联合映射)的性能。示例:

XML
1
2
<id property="id" column="ID"/>
<result property="age" column="AGE"/>
属性列表 
属性 说明
property Java端属性名,你可以使用点号导航,映射到对象图的深处
column 数据库列名,或者重命名后的列标签
javaType 该字段的Java类型,仅在映射到Map的时候需要
jdbcType 该字段的JDBC类型,仅仅对INSERT/UPDATE/DELETE语句处理可能为空的列时需要
typeHandler 使用的类型处理器
constructor

该子元素用于指定构造实体对象时,使用的构造器。constructor的两个子元素和id、result元素类似。

association

该子元素用于处理Has-A关系,例如一个用户有一个部门。Mybatis提供了两种方式来处理这种关联关系:

  1. 嵌套查询:执行另外一个SQL映射语句,来返回关联对象
  2. 嵌套结果:使用嵌套结果映射(nested result mappings)来处理重复的JOIN结果的子(多个用户具有同一部门,那么JOIN的结果中必然有重复的部门数据)集,这要求你手工编写一个JOIN查询,JOIN到关联对象的表
属性列表
属性 说明
property 参考id|result子元素
javaType
jdbcType
typeHandler
嵌套查询相关属性
column

当前对象表中,引用关联对象的外键列

如果使用复合主键,需要以 {prop1=col1,prop2=col2} 这样的方式来指定该属性,prop*为关联对象的属性,而col*为当前表的列名

select

指定另外一个映射语句的ID,该映射语句的结果类型必须和关联对象的类型兼容

如果使用复合主键,column配置中的prop*将作为参数传递给该映射语句

fetchType 抓取类型,可选值lazy、eager。用于覆盖全局设置
嵌套结果相关属性
resultMap 指定一个结果映射,该结果映射用于把当前结果集中某些列映射到关联对象
columnPrefix 用于关联对象的列的统一前缀
notNullColumn

默认情况下,当前结果集中映射到关联对象属性的那些列,只要有一个为非空值,则关联对象就会被创建

设置此属性,指定逗号分隔的若干列,只有这些列中至少一个不为空才创建关联对象

autoMapping 是否启用自动映射,覆盖全局值
嵌套查询示例
XML
1
2
3
4
5
6
7
8
9
10
<resultMap id="userResult" type="User">
    <association property="dept" column="DEPT_ID" select="selectDept"></association>
</resultMap>
 
<select id="selectUser" resultMap="userResult">
    SELECT * FROM USER WHERE ID = #{id}
</select>
<select id="selectDept" resultType="Dept">
    SELECT * FROM DEPT WHERE ID = #{id}
</select>

实体类和测试代码:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 实体类
public class Dept {
    private int id;
    private String name;
}
public class User {
    private int id;
    private String name;
    private int age;
    private Date dob;
    private Dept dept;
}
 
// 测试代码
session = openSqlSession();
User alex = session.selectOne( "selectUser", 1 );
Assert.assertEquals( alex.getDept().getName(), "Development" );

这种方式获得关联对象,想对简单,但是会导致所谓N + 1问题:

  1. 执行一个映射语句,获得User的列表,此所谓1
  2. 对于每个User,都需要执行一个语句来获得Dept,N个用户需要执行N此SQL

如果N数量很大,会导致大量零散的SQL,引起性能问题。延迟加载能够在某些场景下缓解此问题,但是如果你需要对User列表进行迭代,此问题就无法避免。

嵌套结果示例

这里仍然以User-Dept为例,最基本的形式:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select id="selectUser" resultMap="userResult">
    SELECT
      U.ID,
      U.AGE,
      D.ID   AS DEPT_ID,
      D.NAME AS DEPT_NAME
    FROM USER U LEFT JOIN DEPT D ON D.ID = U.DEPT_ID
    WHERE U.ID = #{id}
</select>
<resultMap id="userResult" type="User">
    <id property="id" column="ID"/>
    <result property="age" column="AGE"/>
    <association property="dept" javaType="Dept">
        <id property="id" column="DEPT_ID"/>
        <result property="name" column="DEPT_NAME"/>
    </association>
</resultMap>

我们可以把association内部的映射规则独立为resultMap,便于重用:

XML
1
2
3
4
5
6
7
8
9
<resultMap id="userResult" type="User">
    <id property="id" column="ID"/>
    <result property="age" column="AGE"/>
    <association property="dept" javaType="Dept" resultMap="deptResult"></association>
</resultMap>
<resultMap id="deptResult" type="Dept">
    <id property="id" column="DEPT_ID"/>
    <result property="name" column="DEPT_NAME"/>
</resultMap> 

最好把DEPT_前缀给移除,这样deptResult被单独使用时可以不必编写列别名,设置association的columnPrefix即可(前提你给那些属于关联对象的列予以规范化的别名):

XML
1
2
3
4
5
6
7
8
9
10
<resultMap id="userResult" type="User" autoMapping="true" >
    <!-- 必须指定id,否则会出问题。而且貌似一旦决定手工映射,必须property、column一起提供 -->
    <id property="id" column="ID"/>
    <result property="age" column="AGE"/>
    <association property="dept" javaType="Dept" resultMap="deptResult" columnPrefix="DEPT_"/>
</resultMap>
<resultMap id="deptResult" type="Dept">
    <id property="id" column="ID"/>
    <result property="name" column="NAME"/>
</resultMap>

columnPrefix还可以用于多次连接到同一类关联对象时,使用同一resultMap时区分各自的列。

collection

上面我们学习了如何通过association子元素处理Has-A关系(many-to-one),如果遇到Has-Many情况怎么办呢,比如一个用户具有多个通信地址的情况?

这时可以使用collection子元素。我们以User-Address为例学习collection的用法,首先扩展一下实体类:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class User {
    private int id;
    private String name;
    private int age;
    private Date dob;
    private Dept dept;
    private List<Address> addresses;
}
public class Address {
    private int id;
    private String province;
    private String city;
    private String street;
    private int zip;
}

collection和association的属性很类似,我们直接以示例说明它们的差异。

嵌套查询示例
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap id="userResultWithAddress" type="User">
    <!--
        javaType 可选,一般Mybatis可以自动推导
        ofType 集合元素的类型
        column 当前结果集中,哪个字段作为嵌套查询的参数
        select 嵌套查询语句的ID
    -->
    <collection property="addresses" javaType="arraylist" ofType="Address" column="ID" select="selectAddressForUser" />
</resultMap>
<select id="selectUser" resultMap="userResultWithAddress">
    SELECT * FROM USER WHERE ID = #{id}
</select>
<select id="selectAddressForUser" resultType="Address">
    SELECT * FROM ADDRESS WHERE USER_ID = #{id}
</select> 
嵌套结果示例

嵌套结果总是基于JOIN来实现的: 

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap id="userResultWithAddress" type="User">
    <id property="id" column="ID" />
    <collection property="addresses" ofType="Address" resultMap="addressResult" columnPrefix="A_"/>
</resultMap>
<select id="selectUser" resultMap="userResultWithAddress">
    SELECT
      U.*,
      A.ID AS A_ID,
      A.CITY AS A_CITY,
      A.PROVINCE AS A_PROVINCE,
      A.STREET AS A_STREET,
      A.ZIP AS A_ZIP
    FROM USER U LEFT JOIN ADDRESS A
      ON U.ID = A.USER_ID
      WHERE U.ID = #{id}
</select>
<resultMap id="addressResult" type="Address">
    <id property="id" column="ID" />
</resultMap>

可以看到和association版的非常类似,只是多了一个ofType属性指定集合元素的类型。

discriminator

某些情况下,一个查询语句可能返回多行不同类型的数据,这些数据需要采用不同方式来映射。此时需要用到discriminator。

这里举一个通行工具(Vehicle)的例子,通行工具包括小汽车(Car)、卡车(Truck)、货车(Van)、SUV等类别,在这个例子里面Vehicle与后面的几个是父子类关系,但是存放在一张表里,通过一个鉴别字段区分。

我们看看Vehicle的结果映射:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<resultMap id="vehicleResult" type="Vehicle">
    <!-- 通用映射字段  -->
    <id property="id" column="id"/>
    <result property="vin" column="vin"/>
    <result property="year" column="year"/>
    <result property="make" column="make"/>
    <result property="model" column="model"/>
    <result property="color" column="color"/>
    <!-- vehicle_type用于区分交通工具的具体类型 -->
    <discriminator javaType="int" column="vehicle_type">
        <!-- 如果vehicle_type为1,那么转而使用 resultMap 映射当前行-->
        <case value="1" resultMap="carResult"/>
        <case value="2" resultMap="truckResult"/>
        <case value="3" resultMap="vanResult"/>
        <case value="4" resultMap="suvResult"/>
    </discriminator>
</resultMap>

再看看Car的结果映射:

XML
1
2
3
<resultMap id="carResult" type="Car" extends="vehicleResult">
    <result property="doorCount" column="door_count"/>
</resultMap>

 首先,当前行会映射为Car类型,另外由于指定了extends自vehicleResult,所以后者定义的映射规则自动被合并进来。 

动态SQL

Mybatis的动态SQL功能类似于Struts2的标签库,它利用一些特殊的XML元素,实现流程控制。

if

该元素主要用于实现有条件的WHERE子句包含:

XML
1
2
3
4
5
6
7
8
9
<select id="findActiveUserLike" resultType="User">
    SELECT * FROM USER WHERE state = 'ACTIVE'
    <if test="age != null">
        AND AGE like #{age}
    </if>
    <if test="dept != null and dept.name != null">
        AND DEPT_NAME like #{dept.name}
    </if>
</select>
choose - when - otherwise

类似于Java的switch语句:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findActiveUserLike"  resultType="USer">
    SELECT * FROM User WHERE STATE = 'ACTIVE'
    <choose>
        <when test="title != null">
            AND TITLE like #{title}
        </when>
        <when test="dept != null and dept.name != null">
            AND DEPT_NAME like #{author.name}
        </when>
        <otherwise>
            AND AGE = #{age}
        </otherwise>
    </choose>
</select>
where

该元素保证:只有一个以上if条件匹配的情况下,才往SQL中插入WHERE子句,若WHERE子句以OR、AND结尾,该元素也会把它们删除:

XML
1
2
3
4
5
6
7
8
9
10
11
12
<select id="findActiveUserLike" resultType="User">
    SELECT * FROM USER
    <where>
        state = 'ACTIVE'
        <if test="age != null">
            AND AGE like #{age}
        </if>
        <if test="dept != null and dept.name != null">
            AND DEPT_NAME like #{dept.name}
        </if>
    </where>
</select>
trim

该元素可以为其内部SQL设置前缀、后缀;也可以当内部SQL出现指定前缀、后缀的情况下,将其消除。与上面where等价的trim版本是:

XML
1
2
3
4
5
6
7
8
9
10
11
12
<select id="findActiveUserLike" resultType="User">
    SELECT * FROM USER
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        state = 'ACTIVE'
        <if test="age != null">
            AND AGE like #{age}
        </if>
        <if test="dept != null and dept.name != null">
            AND DEPT_NAME like #{dept.name}
        </if>
    </trim>
</select>
set

与where类似,添加必要的SET前缀,而且仅仅添加需要更新的列,处理结尾的逗号:

XML
1
2
3
4
5
6
7
8
9
10
<update id="updateUserIfNecessary">
    update USER
    <set>
        <if test="username != null">NAME=#{name},</if>
        <if test="password != null">PASSWD=#{password},</if>
        <if test="email != null">EMAIL=#{email},</if>
        <if test="bio != null">DOB=#{DOB}</if>
    </set>
    where ID=#{id}
</update>

与之等价的trim版本是: <trim prefix="SET" suffixOverrides=","></trim>  

foreach

当需要对集合进行遍历,例如IN查询、批量插入时,可以使用该元素:

XML
1
2
3
4
5
6
7
8
9
10
11
12
<select id="selectUserIn" resultType="User">
    SELECT *
    FROM USER U
    WHERE ID in
    <!--
        item 每次迭代的变量,index 当前迭代索引,collection 上下文对象(参数对象)的哪个属性被用来迭代
        open 前缀符号,close 后缀符号,separator 分隔每个迭代的符号
    -->
    <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>
bind

可以估算一个OGNL表达式,将结果绑定到上下文对象(参数对象)的一个属性:

XML
1
2
3
4
5
<select id="selectUsersLike" resultType="User">
  <bind name="pattern" value="'%' + _parameter.getName() + '%'" />
  SELECT * FROM USER
  WHERE NAME LIKE #{pattern}
</select>
多数据库支持

利用Mybatis动态SQL特性,我们可以很容易实现多数据库支持。我们可以随时访问变量_databaseId,来获得数据库厂商类型:

XML
1
2
3
4
5
6
7
8
9
10
11
<insert id="insert">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
        <if test="_databaseId == 'oracle'">
            select seq_users.nextval from dual
        </if>
        <if test="_databaseId == 'db2'">
            select nextval for seq_users from sysibm.sysdummy1"
        </if>
    </selectKey>
    insert into users values (#{id}, #{name})
</insert>
Java API
SqlSessionFactoryBuilder 

该类提供了多种构件SqlSessionFactory的方法:

Java
1
2
3
4
5
6
7
8
9
10
// 从配置文件读取信息
SqlSessionFactory build( InputStream inputStream );
// 从配置文件读取信息,设置使用的环境
SqlSessionFactory build( InputStream inputStream, String environment );
// 从配置文件读取信息,同时读取属性文件,后者填充配置文件的占位符
SqlSessionFactory build( InputStream inputStream, Properties properties );
// 从配置文件读取信息,设置使用的环境,使用属性文件填充占位符
SqlSessionFactory build( InputStream inputStream, String env, Properties props );
// 根据一个编程式的配置对象创建工厂
SqlSessionFactory build( Configuration config );
Environment 
Java
1
2
3
4
5
// 数据源与事务管理工厂
DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
// 创建一个环境对象
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration 

基于XML的主配置的Java编程式等价物:

Java
1
2
3
4
5
6
7
8
Configuration configuration = new Configuration(environment);
// 启用延迟加载
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
// 注册别名
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
// 添加映射器
configuration.addMapper(BoundBlogMapper.class);
SqlSessionFactory

主要用来获取SqlSession,有很多方法变体:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
// 开启事务(不自动提交)、从连接池获取连接对象、事务隔离级别使用驱动默认、不复用预处理语句、不批量更新
SqlSession openSession();
// 指定是否自动提交事务
SqlSession openSession(boolean autoCommit);
// 给出底层连接对象
SqlSession openSession(Connection connection);
// 设置事务隔离级别
SqlSession openSession(TransactionIsolationLevel level);
// 设置执行器类型:SIMPLE、REUSE、BATCH
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, Connection connection);
SqlSession

包含日常工作需要使用的大部分方法: 

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 根据参数对象(上下文)填充语句,执行查询,返回单个对象
<T> T selectOne(String statement, Object parameter);
// 类似上面,返回列表
<E> List<E> selectList(String statement, Object parameter);
// 类似上面,返回映射,mapKey是作为映射Key的对象属性
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey);
// DML操作
int insert(String statement, Object parameter);
int update(String statement, Object parameter);
int delete(String statement, Object parameter);
 
// 限定其实位置、返回结果数量,用于分页查询
int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds);
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds);
 
// 结果处理器用于定制行处理方式
void select (String statement, Object parameter, ResultHandler<T> handler);
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler);
 
// 事务控制方法
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
 
// 清理会话级缓存
void clearCache();
 
// 确保 SqlSession 被关闭
void close();
SQL语句构建器

有时候你不得不在Java代码中编写SQL语句,为了减轻拼写SQL的痛苦,你可以使用MyBatis提供的SQL语句构建器:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SQL sql = 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();
与Spring集成
Maven依赖
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 与Spring集成模块 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.2.3</version>
</dependency>
<!-- 连接池 -->
<dependency>
    <groupId>proxool</groupId>
    <artifactId>proxool</artifactId>
    <version>0.9.1</version>
</dependency>
 
<!-- Spring JDBC-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<!-- Spring事务管理 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
基本配置
applicationContext.xml
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        ">
    <context:annotation-config/>
    <context:component-scan base-package="cc.gmem.study.ssm"/>
    <tx:annotation-driven transaction-manager="txManager" mode="proxy"/>
 
    <bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">
        <property name="alias" value="connectionPool"/>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="driverUrl" value="jdbc:mysql://localhost:3306/test"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
        <property name="statistics" value="10s"/>
        <property name="minimumConnectionCount" value="50"/>
        <property name="maximumConnectionCount" value="300"/>
        <property name="simultaneousBuildThrottle" value="50"/>
        <property name="maximumActiveTime" value="3600000"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 用于创建SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- SqlSessionFactor使用的数据源必须和事务管理器的一致 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 可以指定映射器XML文件的位置 -->
        <property name="mapperLocations" value="classpath*:cc/gmem/study/ssm/mapper/*.xml" />
        <!-- 从1.3.0版本开始,可以直接传入Mybatis基本配置 -->
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="mapUnderscoreToCamelCase" value="true"/>
            </bean>
        </property>
    </bean>
    <!-- 这是一个线程安全的SqlSession实现,你可以在任何需要的地方注入它 -->
    <!-- 当调用selectOne/select/insert/update...等方法时、或者调用来自Mapper的方法时,SqlSessionTemplate保证关联了Spring的事务管理器-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory" />
        <!-- 你可以指定ExecutorType -->
        <constructor-arg index="1" value="BATCH" />
    </bean>
 
</beans>
SqlSessionTemplate

与Mybatis默认的SqlSession实现DefaultSqlSession不同,SqlSessionTemplate是线程安全的,你可以放心的传递它。

管理映射器接口

你可以把映射器接口配置为Bean,Spring会自动生成其代理:

XML
1
2
3
4
5
6
<!-- 你可以把映射器接口配置为Bean -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <!-- 映射器接口,对应的XML版本的配置文件会自动加载 -->
    <property name="mapperInterface" value="cc.gmem.study.ssm.mapper.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

逐个配置映射器接口太麻烦,你可以配置自动扫描:

XML
1
2
3
4
5
6
7
8
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 扫描所有子包,发现映射器接口 -->
    <property name="basePackage" value="cc.gmem.study.ssm.mapper" />
    <!-- 仅仅扫描具有如下注解的接口  -->
    <property name="annotationClass" value="cc.gmem.mybatis.MapperInterface" />
    <!-- 仅仅扫描如下接口的子接口 -->
    <property name="markerInterface" value="cc.gmem.mybatis.Mapper" />
</bean>

你可以在自己的DAO或者Service中注入这些映射器接口,并直接调用其中的SQL方法。

常见问题
重复映射导致异常

启动报错:java.lang.IllegalArgumentException: Mapped Statements collection already contains value for ...

原因:可能是因为你在XML映射器和注解映射器中指定了重复的映射语句,即XML的id与Java方法名相同。

找不到映射语句导致异常

执行语句时报错:Mapped Statements collection does not contain value for <statementName> 

可能原因:

  1. Java API中写错了语句ID
  2. 映射器没有被扫描到,注意mappers/package仅仅能识别基于注解的映射器
association不指定javaType导致异常

报错信息:

at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:122)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:113)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:73)

Caused by: java.lang.NullPointerException

at org.apache.ibatis.reflection.DefaultReflectorFactory.findForClass(DefaultReflectorFactory.java:42)
at org.apache.ibatis.reflection.MetaClass.<init>(MetaClass.java:39)
at org.apache.ibatis.reflection.MetaClass.forClass(MetaClass.java:43)
at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createResultObject(DefaultResultSetHandler.java:524) 

原因:使用嵌套结果映射时association上没有标注关联对象的类型时,出现此错误。

collection不指定必要的id子元素导致异常

报错信息:Expected one result (or null) to be returned by selectOne(), but found: N

原因:User与Address是1:N关系,通过User左连接到Address,当userResult、addressResult两个结果映射都不设置id子元素时,会出现此异常

总结:id子元素最好都写上

无法显示Mybatis日志

首先配置好Log4j的logger: org.apache.ibatis 。

然后,对于3.2以前的版本,调用下面的方法之一:

Java
1
2
3
4
5
6
org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
org.apache.ibatis.logging.LogFactory.useLog4J2Logging();
org.apache.ibatis.logging.LogFactory.useJdkLogging();
org.apache.ibatis.logging.LogFactory.useCommonsLogging();
org.apache.ibatis.logging.LogFactory.useStdOutLogging();

对于3.2以后的版本,添加类似下面的配置项即可:

XML
1
<setting name="logImpl" value="LOG4J"/>
如何编写模糊查询

可以有不同的写法:

XML
1
2
3
4
5
<!-- 方法一:注意SQL注入的可能 -->
${'%' + name + '%'}
'%${name}%'
<!-- 方法二:位置参数风格 -->
"%"#{name}"%"
← Struts2学习笔记
Linux下WordPress的相关问题 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Axis知识集锦
  • Eclipse下进行基于AspectJ的AOP编程
  • Ubuntu下安装Tomcat7
  • IntelliJ IDEA知识集锦
  • JacksonJson知识集锦

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2