文章依照官网文档所写,使用的mybatis版本为3.5.4,使用数据库为mysql
本文以使用案例为主,基础性讲解较少,请结合文档阅读。也是初学者,能力有限,有错麻烦指正,谢谢
本文所用代码上传至个人GitHub,全部运行方法都在测试实例中,数据库生成与数据添加代码在sql.sql内,为了演示不同的功能,不同功能的测试代码和配置文件完全分离。
Getting Start
本章内容的测试代码在test/java/basic
中,使用到的配置文件在src/main/resources/basic
中
基本使用
运行测试实例basic.Test
方法所示,从结果可以看出,运行要先通过如下几步
SqlSessionFactoryBuilder
创建工厂构建类;- 通过
Resources
获取配置文件的路径的Reader
对象 - 将配置文件传给工厂构建类,通过
build
方法创建工厂; - 调用
openSession
创建SqlSession
; - 通过
SqlSession
获取dao
的代理对象,调用代理对象方法的数据库操作方法;或者直接调用SqlSession
的数据库操作方法
生命周期
对于上述几个对象的使用约束如下:
SqlSessionFactoryBuilder
:这个对象可以 随意被创建、使用或者丢弃,不需要一直持有这个对象;SqlSessionFactory
:一旦创建,这个工厂需要在应用的整个周期内存活;SqlSession
:每个线程持有一个SqlSession
,不要在线程之间共享SqlSession
,并且每次用完之后要记得关闭掉,可以使用java8提供的try-catch-resource语法;Mapper Instances
:建议在方法内使用Mapper
对象,即在方法中获取,用完就丢弃。
Configuration配置
即传入SqlSessionFactoryBuilder.build
方法构建SqlSessionFactory
的xml配置文件的书写规则,其约束为
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
其根标签为configuration
,直接子标签包括properties
,settings
,typeAliases
,typeHandlers
,objectFactory
,plugins
,environments
,atabaseIdProvider
,mappers
共9个,这些标签的配置必须按照上述顺序依次配置,例如typeHandlers
要配置在objectFactory
之前
配置XML——properties
本章内容的测试代码在test/java/xml/properties
中,使用到的配置文件在src/main/resources/xml/properties
中。实验结果如下
这个element无非就是给配置文件设定属性键值对,以便后续配置根据key获取value
属性
属性指的是跟标签上的配置,如
<properties resource="xxx">
,resource
指的就是这个标签的一个属性
resource
代表着从本地文件里读取配置信息
获取值
通过${key}
来获取即可
获取默认值
${username:root}
,可以看到本章使用的配置信息里没有username
,那么${username:root}
的含义就是如果没有username
,那么就使用root
,默认这个功能是不开启的,需要设置<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
即可使用这个功能,可以看到分隔符默认是:
,可以通过<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
将分隔符改为?:
,即${username?:root}
配置XML——settings
涉及内容过多,暂时略过
配置XML——typeAliases
本章内容的测试代码在test/java/xml/typeAliases
中,使用到的配置文件在src/main/resources/xml/typeAliases
中。实验结果如下
typeAliases
是给类名起个别名,这些别名可以用在mapper.xml文件里,因为每次都使用全限定类名就很麻烦,使用别名可方便很多
mybatis里有很多内置的typeAliase,例如_float
指的java类为基本数据类型float
,而float
指的是引用数据类型Float
配置XML——typeHandlers
本章内容的测试代码在test/java/xml/typeHandlers
中,使用到的配置文件在src/main/resources/xml/typeHandlers
中。实验结果如下
typeHandlers
定义了java类型和jdbc类型的转换关系,如代码中所示,AuthorName
类是一个单独的类,我们希望其能对应数据库author中的name这一列,那么AuthorNameHandler
就是用于处理这两者之间的转换关系的,例如
AuthorName selectAuthorNameById(int id);
方法是希望通过id获取一个AuthorName
对象,执行该方法可以看到控制台有打印通过列名调用getNullableResult
,说明TypeHandler生效;Author selectByAuthorNameObj(AuthorName authorName);
方法是希望传入一个AuthorName
对象,从而获取Author
对象,注意此时mapper文件中的sql为select * from author where name=#{authorName};
,其中#{authorName}
就会导致AuthorNameHandler
的setNonNullParameter
方法调用;- 当然也可以构造一个可以处理多个类型的
handler
,例如public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E>
; AuthorNameHandler
头上挂的注解都可以通过xml标签里指定,并且xml标签里配置的优先级更高,会覆盖注解,如文件中所示;EnumTypeHandler
就是处理Enum
类型的映射关系,如果查询结果为MALE
,那么就会取出对应Javatype枚举类型Sex
的MALE
对象。通过看源码可知,数据库sex
列类型可以为字符类型或者enum类型,因为结果都是通过rs.getString(columnName);
获取的。EnumOrdinalTypeHandler
是另一种处理Enum
类型的映射关系,如果数据库内返回对象为数字0,那么就会取出对应Javetype枚举类型中的第一个枚举对象;只不过这个功能需要在mybatis-config.xml里手动指定,否则默认使用的是EnumTypeHandler
;AuthorNameHandler
可以在Mapper
文件中单独使用,后面会讲到。
配置XML——objectFactory
本章内容的测试代码在test/java/xml/objectFactory
中,使用到的配置文件在src/main/resources/xml/objectFactory
中。
Each time MyBatis creates a new instance of a result object, it uses an ObjectFactory instance to do so. 从结果可以看出,创建Author对象的时候,打印了两次interface java.util.List
,这是因为返回结果是一个,但内部仍然调用的是selectList方法,然后取list里的第0个元素作为返回值,至于为啥打印两边,这是因为无参构造方法create也会调用有参的create构造方法
配置XML——plugins
本章内容的测试代码在test/java/xml/plugins
中,使用到的配置文件在src/main/resources/xml/plugins
中。
实例代码参考自MyBatis-Plugins,功能为拦截了StatementHandler
的prepare
方法,获取了执行方法的信息,并修改了sql语句
要小心使用这些方法
配置XML——environments
示例略,详情请看文档,该标签用于配置环境
- 两种事务处理器,JDBC与MANAEGED;
- 三种内置数据源,pooled、unpooled以及JNDI;同时mybatis支持第三方数据源,例如C3P0和druid
配置XML——databaseIdProvider
示例略,详情请看文档,该标签用于多数据库支持,举例来讲,实际应用场景有可能会在多种类型的数据库中切换。
MyBatis之databaseIdProvider多数据库支持
配置XML——mappers
示例略,mapper就是配置mapper.xml的路径,有两类方法
直接配置xml路径
- 配置路径
<mapper resource="xml/plugins/dao/BlogMapper.xml"/>
- 配置url
<mapper url="file:///D:/code/mybatis/how2mybatis_xml/src/main/resources/xml/plugins/dao/BlogMapper.xml"/>
配置接口类
- 直接配置全限定类名,这种方法需要注意的是,mybatis也会同时加载
classpath:dao/BlogMapper.xml
,如果找不到该文件,并且接口内方法也没有注解的话,就会报错
<mapper class="dao.BlogMapper"/>
- 直接配置包名,要求与配置全限定类名一样,也会加载与类同名的xml配置文件
<package name="dao"/>
Mapper XML——select
Mapper里用于进行查操作的标签,select
标签上有很多属性,这些属性多数与JDBC相关,例如fetchSize
以及resultSetType
等,自行百度。其中最重要的属性为
id
:指明mapper接口的具体的方法;parameterType
:指明方法的参数类型,Mybatis (ParameterType) 如何传递多个不同类型的参数;使用参数的方法后面会介绍,里面通过索引获取参数的方法好像已经失效了。不要这么用。resultType
:指明方法返回类型;resultMap
:指明方法返回map;
Mapper XML——insert, update and delete
本章内容的测试代码在test/java/mapper/insert
中,使用到的配置文件在src/main/resources/mapper/insert
中。
Mapper里用于执行增删改操作的标签,实验结果不言自明
文档里提到了selectKey
这个标签,我觉得应该用不到
实验完之后记得把数据库恢复原样,以便后续实验
delete from how2mybatis.blog where id>=5;
ALTER TABLE how2mybatis.blog auto_increment=5;
Mapper XML——sql
本章内容的测试代码在test/java/mapper/sql
中,使用到的配置文件在src/main/resources/mapper/sql
中。
改标签的功能就是定义一些可以复用的sql片段,不同的sql片段可以相处嵌套
Mapper XML——parameter
本章内容的测试代码在test/java/mapper/parameter
中,使用到的配置文件在src/main/resources/mapper/parameter
中。
这不是一个标签,这是取值从bean对象取值而构建sql的语法,使用就无非是#
和$
mybatis中#与$的区别
两者的区别在于,#
会认为这是个参数,$
会直接进行字符串替换,例如select * from ${tabelName}
就会将${tabelName}
直接替换成tableName
的值;而select * from ${tabelName}
则会被编译成select * from ?
,这种语句是没法进行setParameter
从而成功执行的。
该案例里使用了五个方法,可以看到我在AuthorMapper
里的参数添加了@Param
这个注解。这是因为默认情况下,java8的反射机制是无法获取方法的参数名的,例如某个方法method(String name)
,通过反射的方法获取该方法的参数名只能获得arg0
,因此需要手动配置编译模式,如Java8获取参数名称所说的方法。修改完之后记得rebuild工程,否则用的还是修改前的class文件
但由于我懒= =懒得折腾这个了,我就用了另一种方法,给参数直接挂上注解,从而让mybatis框架识别参数名。接下来重点说#
如果方法传入参数只有1个
对于传入的是存在内置typeHandler的对象(如基本数据类型int,float等):
- 如果传入的参数带了
@Param('a') String arg
,那么#{a}
就代表了arg
本身,如果使用#{asdf}
会报错,告诉你可用的参数名只有a
和param1
; - 如果不带
@Param
注解,如Date date
,那么#{}
里不管写什么,都能读取到这个date;虽然起什么名都行,但乱起名终归是个坏习惯,最好保证参数名相同。
如果传入的是不存在存在内置typeHandler的对象(javabean):
- 如果传入的参数带了
@Param('a')Bean arg
,那么#{a}
就代表了对象arg
本身,对其可以使用内置的handler或者自定义的typeHandler。同时#{a.prop}
就代表其prop属性; - 如果传入的参数不带注解
Bean bean
,那么#{arg}
会去搜索这个javabean的arg
属性,即getArg()
。注意,即使此时开启了-parameters
参数,#{bean}
指的也不是这个对象bean
,仍然是去找叫做bean
的属性;
如果传入的参数不止1个
假设传递参数为AuthorName authorName,int id
,注意此时假设开启了-parameters
编译模式
${authorName}
和${id}
指的就是authorName
这个对象和id
这个整数,对于javabean也不会去认为authorName是一个属性,你可以通过#{authorName.prop}
获取其Prop属性;- 如果不开启
-parameters
功能,就只能通过@Param
注解实现上面的功能了;
无论怎样,#
都会自动调用对应的handler,例如,如果传入的参数是一个int
类型,那么IntegerTypeHandler
这个内置的处理器会被调用。
mapper XML——resultMap
本章内容的测试代码在test/java/mapper/resultMap
中,使用到的配置文件在src/main/resources/mapper/resultMap
中。
- resultMap里仍然可以用各种handler;
id
和result
没什么大的不同,The only difference between the two is that id will flag the result as an identifier property to be used when comparing object instances.constructor
标签的作用就是调用带参构造方法来创建新对象,constructor
的子标签idArg
和arg
的标签属性name指的就是构造函数的参数名,需要注意的是,如果不开启-parameters
编译,则需要加@Param
,我没有开启-parameters
,后面不再复述;association
处理的是has-one这种关系,例如,每个Blog对象都持有一个Author对象,association
里面也是一个小型的resultMap
,有Nested Select、Nested Results和Multiple ResultSets三种方式,Nested Select就是再调用另外一个select方法,Nested Select可以开启懒加载。- 讨论对象唯一性问题,在本章代码中,
selectAllBlogsWithNestedSelectAssociation
,返回一个blog的列表,每个blog都has one author,由于使用的是nested select ,第一条博客和第二条博客持有的author对象是同一个;而selectAllBlogsWithNestedResultsAssociation
使用的是nested result,第一条博客和第二条博客持有的author对象不是同一个; collection
处理的是has-many这种关系,例如,一个Author对象可能会发布了多条Blog,其也有Nested Select、Nested Results和Multiple ResultSets三种方式,我只展示了Nested Select的用法;- 讨论对象唯一性问题,
selectAuthorsByIdWithNestedSelectColleciton
使用了nested select collection语法,从数据库中读取全部作者,并且让每个author持有作者id小于等于自己作者id的全部blog,这说明2号author会持有1号作者和2号作者的blog。从结果可以看出,即使使用的是select,1号author持有的blog,与2号持有的1号作者的blog也不是同一对象。这说明collection语法中总会新建对象; discriminator
这个标签是用来作为判别器使用的,有点类似于Java里的switch语法,MyBatis系列(十三):使用discriminator鉴别器映射。注意事项:Javatype
是用于指定以什么类型进行比较的;如果discriminator
里有一个case
满足条件了,那么这个resultMap之外的discriminator
标签外面的其他result
或者id
都会失效,doc原文如下
<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"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>
In this example, MyBatis would retrieve each record from the result set and compare its vehicle type value. If it matches any of the discriminator cases, then it will use the resultMap specified by the case. This is done exclusively, so in other words, the rest of the resultMap is ignored
extends
属性可以引入另一个resultMap
除去discriminator
的部分
Mapper XML——autoMapping
本章内容的测试代码在test/java/mapper/autoMapping
中,使用到的配置文件在src/main/resources/mapper/autoMapping
中。这段代码只显示了autoMapping
为full时候的危险。
NONE disables auto-mapping. PARTIAL will only auto-map results with no nested result mappings defined inside. FULL will auto-map result mappings of any complexity (containing nested or otherwise).
- 在join表达式中,autoMapping的运行模式如None一样,即不指定的参数不会被映射;
- 示例代码里的风险在于,
association
里的自动读取了结果,从而把blog的id映射到author的id上了。
动态Sql——if
本章内容的测试代码在test/java/dynamicSql/dynamicIf
中,使用到的配置文件在src/main/resources/dynamicSql/dynamicIf
中。
动态Sql——where set trim
本章内容的测试代码在test/java/dynamicSql/dynamicWhereSet
中,使用到的配置文件在src/main/resources/dynamicSql/dynamicWhereSet
中。
where和set都是根据判定结果自动在头添加where或者set,并且处理首个判定成功条件内容开头的AND |OR
或者最后一个判定成功条件末尾的,
而我们可以通过trim
对其定制化,prefix
和suffix
属性代表着在头或者尾添加的内容,prefixOverrides
代表首个判定成功的表达式的开头需要被自动处理的;suffixOverrides
则表示最后一个判定成功的表达式的末尾需要被处理的内容,注意这两个override对空格都是敏感的。
动态Sql——foreach
本章内容的测试代码在test/java/dynamicSql/dynamicForeach
中,使用到的配置文件在src/main/resources/dynamicSql/dynamicForeach
中。
foreach
可以用于iteratable
对象,比如list
和set
,index
代表序号,item
代表持有的对象,当然数组对象也可;foreach
可以用于Map
或者Collection of Map.Entry
对象,比如list
和set
,index
代表key,item
代表value;
动态Sql——bind
本章内容的测试代码在test/java/dynamicSql/dynamicBind
中,使用到的配置文件在src/main/resources/dynamicSql/dynamicBind
中。
就是在标签里进行运算然后进行参数绑定,看代码不言自明