Mybatis学习笔记
原文: https://blog.gmem.cc/mybatis-study-note
Mybatis是一个持久化层的Java框架,但是它并不是完整的ORM方案,它是以SQL为中心的,更像JOOQ而不是Hibernate。这意味着,如果使用Mybatis,你在某种程度上需要抛弃OO的领域模型设计(以对象为中心),转而以数据库表为中心进行设计。
Mybatis的特色是可以定制SQL语句(甚至是存储过程),这让你有很好的机会执行SQL优化,但很容易丢失数据库方面的可移植性。能够定制SQL,也使Mybatis能够很好的支持遗留数据库。
本章结合一个非常简单的例子,介绍Mybatis的基本组件和基础用法。
| 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> | 
每个Mybatis应用都是以一个SqlSessionFactory实例为中心的,你可以基于XML或者Java的方式,提供SqlSessionFactory的初始化参数。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。
Java代码:
| 1 2 | InputStream is = Resources.getResourceAsStream( "cc/gmem/study/ssm/mybatis/mybatis-config.xml" ); SqlSessionFactory sf = new SqlSessionFactoryBuilder().build( is );  | 
对应主配置文件:
| 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> | 
| 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注解两种映射器配置方式。
对应映射器文件,被主配置文件引用:
| 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>  | 
| 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会报错。
该类包含了面向数据库执行 SQL 命令所需的所有方法,日常工作主要通过它进行。
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,不能被共享。
| 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(); } | 
| 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。
可以定义一系列属性,让配置文件其它地方基于 ${prop} 的语法引用。你可以引用外部的Java属性文件:
| 1 | <properties resource="cc/gmem/study/ssm/config.properties" /> | 
也可以使用子元素直接指定属性:
| 1 2 3 | <properties>   <property name="username" value="root"/> </properties> | 
甚至两种方式混合使用。 属性优先级由高到低:
- SqlSessionFactoryBuilder.build()中传递的属性
- 通过resource指定的Java属性文件中的属性
- property子元素指定的属性
该元素的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执行器: 
 | 
| 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)来防止循环引用、加速重复嵌套查询: 
 | 
| 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,创建延迟加载对象时使用的代理库 | 
该参数仅用于XML配置风格,其子元素typeAlias为某个Java类设置别名:
| 1 2 3 | <typeAliases>   <typeAlias alias="User" type="cc.gmem.study.entity.User"/> </typeAliases> | 
或者指定Java类所在的包名,这样,包内所有Java类的Base name自动别设置为别名:
| 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 | 
该元素的typeHandler子元素用于注册自定义的类型处理器。
类型处理器用于执行类型转换。无论在Mybatis在预处理语句中设置一个值时,还是在它从结果集中获取一个值时,都会调用类型处理器在JDBC类型和Java类型之间进行适当的转换。类型处理器的职责类似于Hibernate的类型系统。
Mybatis已经内置了常用的类型处理器,除非你需要定制自己的类型处理器,否则不需要配置该元素。
若要定制自己的类型处理器,可以继承BaseTypeHandler类:
| 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 );     } } | 
然后在此设置项中注册新的类型处理器:
| 1 2 3 | <typeHandlers>     <typeHandler handler="cc.gmem.mybatis.ths.MyTypeHandler"/> </typeHandlers> | 
你也可以指定包,Mybaits在包中查找多个类型处理器:
| 1 2 3 | <typeHandlers>     <package name="cc.gmem.mybatis.ths"/> </typeHandlers> | 
要让Mybatis支持枚举类型,可以注册EnumTypeHandler或者EnumOrdinalTypeHandler,后者默认已经注册。这两者的区别是:
- EnumTypeHandler,把枚举值对应的名字存入数据库
- EnumOrdinalTypeHandler,把枚举值对应的数字存入数据库
除了以全局方式来指定枚举处理方式以外,你还可以针对单个枚举设置:
| 1 2 | <!-- 此设置仅当Java类型是RoundingMode时生效 --> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/> | 
甚至针对单个映射语句设置:
| 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> | 
Mybatis在获取结果集后,需要依据映射器将结果集转换为Java对象,而它创建Java对象就是通过对象工厂来完成的。
默认的对象工厂仅仅是调用Java类的默认构造器,如果你需要修改此默认行为,可以实现自己的对象工厂,并注册:
| 1 2 3 4 | <objectFactory type="cc.gmem.study.ssm.DebugingObjectFactory">     <!-- 这些配置项通过工厂的setProperties传递给工厂 -->     <property name="debug" value="true"/> </objectFactory> | 
用于注册Mybatis插件,每个plugin子元素对应一个插件。
Mybatis设计了一个很简单的插件扩展机制——允许你拦截映射语句执行过程的某个点,并加入扩展逻辑。可以拦截的点包括:
- Executor的update, query, flushStatements, commit, rollback, getTransaction, close, isClosed方法
- ParameterHandler的getParameterObject, setParameters方法
- ResultSetHandler的handleResultSets, handleOutputParameters方法
- StatementHandler的prepare, parameterize, batch, update, query方法
要拦截以上方法,你只需要实现拦截器接口,并添加必要的注解:
| 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;         }     } } | 
| 1 2 3 4 5 | <plugins>     <plugin interceptor="cc.gmem.study.ssm.mybatis.plugins.MyInterceptor">         <property name="debug" value="true"/>     </plugin> </plugins> | 
你可以在此元素下定义多个环境,并指定其中一个为默认环境。
所谓环境,是指一个数据源及操控它的事务管理器。通过定义多个环境,Mybatis可以快速的在不同类型的数据库、不同数据实例之间进行切换。
每个SqlSessionFactory只能使用一个环境,你可以使用如下签名的方法为SqlSessionFactory指定(非默认的)环境:
| 1 2 | SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties); | 
单个环境的配置,看起来大概是这个样子:
| 1 2 3 4 5 6 7 | <!-- id为此环境的唯一标识  --> <environment id="development">     <!-- 事务管理器配置 -->     <transactionManager type="JDBC"/>     <!-- 数据源配置 -->     <dataSource type="POOLED" /> </environment> | 
该元素定义某个环境使用的事务管理器,Mybatis支持两种类型的事务管理器:
- JDBC,直接使用JDBC API进行提交和回滚,依赖于从数据源得到的连接来管理事务
- MANAGED,该配置让容器来管理事务的整个生命周期。默认情况下,该事务管理器会负责连接的关闭,如果需要禁止此行为,可以:
 123<transactionManager type="MANAGED"><property name="closeConnection" value="false"/></transactionManager>
当你整合Mybatis和Spring时,不需要配置事务管理器。
Mybatis支持三种数据源:
- UNPOOLED,不启用连接池的简单数据源。配置项:
 配置项 说明 driver JDBC 驱动的 Java 类的完全限定名 url JDBC URL 地址 username 登录数据库的用户名 password 登录数据库的密码 defaultTransactionIsolationLevel 默认的连接事务隔离级别 driver.*** 将***属性传递给JDBC驱动 
- POOLED,使用连接池实现连接复用。除了UNPOOLED提供的配置项外,还支持:
 配置项 说明 poolMaximumActiveConnections 任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10 poolMaximumIdleConnections 任意时间可能存在的空闲连接数 poolMaximumCheckoutTime 在被强制返回之前,池中连接被拿出来使用的最大时间,默认20000ms 
- JNDI,使用JavaEE容器提供的JNDI上下文来查找数据源
Mybatis可以依据数据库产品的不同,来执行不同的SQL语句。一般你可以使用默认值:
| 1 | <databaseIdProvider type="DB_VENDOR" /> | 
来支持尽可能多的主流数据库。
指定映射器的引用,即SQL映射语句所在的XML文件或者Java类的引用:
| 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代码。
原样字符串(${})、动态SQL元素中,可以使用OGNL表达式。OGNL的基础知识和语法,请参考Struts2学习笔记。
在映射器配置中,OGNL上下文对象 #this 是一个类似于Apache Struts值栈的对象。你可以直接访问查询参数的任何属性,以及以下特殊属性:
| 特殊属性 | 说明 | 
| _parameter | 查询参数对象本身 | 
| _databaseId | 当前数据库类型,用于多数据库支持 | 
这是映射器配置最常用的一个元素,用于定义一个将结果集映射到Java对象的查询语句。下面是一个简单的例子:
| 1 2 3 | <select id="selectUser" parameterType="int" resultType="hashmap">     SELECT * FROM USER WHERE ID = #{id} </select> | 
这个配置的含义是,定义一个标识符为selectUser的映射语句,该语句:
- 接受一个int型的参数
- 查询结果映射为java.util.HashMap。该Map的键是数据库列的别名,值则是结果行中的对应的值
- #{id} 告知Mybatis,创建一个预编译SQL语句,并在此处设置一个位置参数,就像这样:
 123String 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 | 仅对多结果集的情况,逗号分隔的结果集名称 | 
这三类元素分别表示插入、更新、删除语句,它们的实现方式很接近:
| 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对象的某个属性:
| 1 2 3 4 | <!-- 回填给参数的id属性 --> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">     INSERT INTO USER (NAME, AGE, DOB) VALUES ( #{name}, #{age}, #{dob} ) </insert> | 
对应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(); | 
如果数据库不支持自动生成主键,你可以让Mybatis生成,并插入到数据库:
| 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): 
 | 
| statementType | Enum(TATEMENT | PREPARED | CALLABLE) | 
如果数据库支持多行插入,你可以传递一系列对象:
| 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代码:
| 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支持参数化:
| 1 | <sql id="userColumns">${alias}.ID, ${alias}.NAME, ${alias}.AGE, ${alias}.DOB</sql> | 
在其它语句中,你可以引用sql元素:
| 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都可以参数化:
| 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) ,例如:
| 1 2 3 | <insert id="insertUser" parameterType="User">     INSERT INTO USER (NAME, AGE, DOB) VALUES ( #{name}, #{age}, #{dob} ) </insert> | 
这样,当调用此语句时,你传入了一个User类型的对象,则它的name、age、dob属性都会通过反射机制找到,然后传入到预编译语句之中。
对于语句中的占位符,不仅可以指定一个名字,还可以指定:
- javaType,该属性的Java类型,除非该属性的容器对象是一个Map,一般不需要设置
- jdbcType,该属性对应的JDBC类型,如果null被当作值传递,那么对于所有可能为空的列,该属性都是需要的
- numericScale,对于数值类型,你可以用此字段指定小数位数
- typeHandler,你可以为属性指定一个类型处理器
- mode,可以设置为IN|OUT|INOUT,如果设置为OUT|INOUT,则参数对象的属性值可能会被改变
例如: #{property,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
通过SqlSession的API调用映射语句时,如果:
- 传入的是一个数组或者列表,则Mybatis会自动将其转换为Map,此Map的键分别为array、list
- 传入的是一个简单类型,那么属性占位符的名称可以随意取
如果你想往SQL里插入一个字面值(而不是预编译语句的位置参数),可以使用${},例如: ORDER BY${columnName} 。columnName具体是何种,取决于上下文参数。
当我们指定resultType为Map或者实体类时,已经在间接的使用resultMap,只不过映射方式是Mybatis自动推导出来的:
- 如果结果类型是Map,则把列名映射为Map的键
- 如果结果类型是实体类,则把列名映射为实体类的属性
假设实体类属性和列名不是完全匹配的,你就必须使用SQL的列别名来提示Mybatis进行映射:、
| 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而不是resultType属性, 前者引用一个resultMap元素。
resultMap元素定义一个数据库列——Java属性的映射规则,例如:
| 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),需要在映射文件中添加一行:
| 1 | <cache/> | 
注意,缓存配置是绑定到映射文件的名字空间的。
| 属性 | 说明 | 
| eviction | 缓存满了以后,如何进行清理。默认LRU,可选值: 
 | 
| flushInterval | 缓存刷新间隔,毫秒数。默认不设置,即仅仅在调用语句时才刷新 | 
| size | 缓存对象的最大数量,默认1024 | 
| readOnly | 是否只读方式使用缓存,如果true可以提高性能。默认false | 
| type | 可以指定自定义缓存实现 | 
你可以实现自己的缓存,只需要实现org.mybatis.cache.Cache接口。要使用自定义缓存机制。
你可以设置每个映射语句的缓存规则,默认规则是这样的:
| 1 2 3 4 | <select  flushCache="false" useCache="true"/> <insert  flushCache="true"/> <update  flushCache="true"/> <delete  flushCache="true"/> | 
映射器中的resultMap元素非常强大和重要,因此我们在此详细讨论它。
上面我们尝试了使用resultMap解决数据库字段名、实体类名不对应的问题。实际上resultMap能做的远远不止这点,它可以很好的解决关联映射问题——把结果集映射到一个对象图中。
| 属性 | 说明 | 
| id | resultMap在名字空间中的唯一标识 | 
| type | 此resultMap将结果集映射为何种对象 | 
| autoMapping | 覆盖全局的自动映射设置 | 
| 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提示Mybatis该字段是实体类的标识符,Mybatis会用此信息提升缓存、嵌入结果映射(联合映射)的性能。示例:
| 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的两个子元素和id、result元素类似。
该子元素用于处理Has-A关系,例如一个用户有一个部门。Mybatis提供了两种方式来处理这种关联关系:
- 嵌套查询:执行另外一个SQL映射语句,来返回关联对象
- 嵌套结果:使用嵌套结果映射(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 | 是否启用自动映射,覆盖全局值 | 
| 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> | 
实体类和测试代码:
| 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问题:
- 执行一个映射语句,获得User的列表,此所谓1
- 对于每个User,都需要执行一个语句来获得Dept,N个用户需要执行N此SQL
如果N数量很大,会导致大量零散的SQL,引起性能问题。延迟加载能够在某些场景下缓解此问题,但是如果你需要对User列表进行迭代,此问题就无法避免。
这里仍然以User-Dept为例,最基本的形式:
| 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,便于重用:
| 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即可(前提你给那些属于关联对象的列予以规范化的别名):
| 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时区分各自的列。
上面我们学习了如何通过association子元素处理Has-A关系(many-to-one),如果遇到Has-Many情况怎么办呢,比如一个用户具有多个通信地址的情况?
这时可以使用collection子元素。我们以User-Address为例学习collection的用法,首先扩展一下实体类:
| 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的属性很类似,我们直接以示例说明它们的差异。
| 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来实现的:
| 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。
这里举一个通行工具(Vehicle)的例子,通行工具包括小汽车(Car)、卡车(Truck)、货车(Van)、SUV等类别,在这个例子里面Vehicle与后面的几个是父子类关系,但是存放在一张表里,通过一个鉴别字段区分。
我们看看Vehicle的结果映射:
| 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的结果映射:
| 1 2 3 | <resultMap id="carResult" type="Car" extends="vehicleResult">     <result property="doorCount" column="door_count"/> </resultMap> | 
首先,当前行会映射为Car类型,另外由于指定了extends自vehicleResult,所以后者定义的映射规则自动被合并进来。
Mybatis的动态SQL功能类似于Struts2的标签库,它利用一些特殊的XML元素,实现流程控制。
该元素主要用于实现有条件的WHERE子句包含:
| 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> | 
类似于Java的switch语句:
| 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> | 
该元素保证:只有一个以上if条件匹配的情况下,才往SQL中插入WHERE子句,若WHERE子句以OR、AND结尾,该元素也会把它们删除:
| 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> | 
该元素可以为其内部SQL设置前缀、后缀;也可以当内部SQL出现指定前缀、后缀的情况下,将其消除。与上面where等价的trim版本是:
| 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> | 
与where类似,添加必要的SET前缀,而且仅仅添加需要更新的列,处理结尾的逗号:
| 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>
当需要对集合进行遍历,例如IN查询、批量插入时,可以使用该元素:
| 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> | 
可以估算一个OGNL表达式,将结果绑定到上下文对象(参数对象)的一个属性:
| 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,来获得数据库厂商类型:
| 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> | 
该类提供了多种构件SqlSessionFactory的方法:
| 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 ); | 
| 1 2 3 4 5 | // 数据源与事务管理工厂 DataSource dataSource = BaseDataTest.createBlogDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); // 创建一个环境对象 Environment environment = new Environment("development", transactionFactory, dataSource); | 
基于XML的主配置的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); | 
主要用来获取SqlSession,有很多方法变体:
| 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); | 
包含日常工作需要使用的大部分方法:
| 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(); | 
有时候你不得不在Java代码中编写SQL语句,为了减轻拼写SQL的痛苦,你可以使用MyBatis提供的SQL语句构建器:
| 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(); | 
| 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> | 
| 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> | 
与Mybatis默认的SqlSession实现DefaultSqlSession不同,SqlSessionTemplate是线程安全的,你可以放心的传递它。
你可以把映射器接口配置为Bean,Spring会自动生成其代理:
| 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> | 
逐个配置映射器接口太麻烦,你可以配置自动扫描:
| 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>
可能原因:
- Java API中写错了语句ID
- 映射器没有被扫描到,注意mappers/package仅仅能识别基于注解的映射器
报错信息:
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上没有标注关联对象的类型时,出现此错误。
报错信息:Expected one result (or null) to be returned by selectOne(), but found: N
原因:User与Address是1:N关系,通过User左连接到Address,当userResult、addressResult两个结果映射都不设置id子元素时,会出现此异常
总结:id子元素最好都写上
首先配置好Log4j的logger: org.apache.ibatis 。
然后,对于3.2以前的版本,调用下面的方法之一:
| 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以后的版本,添加类似下面的配置项即可:
| 1 | <setting name="logImpl" value="LOG4J"/> | 
可以有不同的写法:
| 1 2 3 4 5 | <!-- 方法一:注意SQL注入的可能 --> ${'%' + name + '%'} '%${name}%' <!-- 方法二:位置参数风格 --> "%"#{name}"%" | 
 
                    
                     
                    
                 
                    
                 
                
            
         
 
         浙公网安备 33010602011771号
浙公网安备 33010602011771号