Mybatis学习笔记
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}"%" |
Leave a Reply