基本的*Mapper.xml文件配置就不熬述了具体可参考:html
http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.htmljava
一、sql元素算法
这个元素能够被用来定义可重用的 SQL 代码段,能够包含在其余语句中。它能够被静态地(在加载参数) 参数化. 不一样的属性值经过包含的实例变化. 好比:sql
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
这个 SQL 片断能够被包含在其余语句中,例如:数据库
<select id="selectUsers" resultType="map"> select <include refid="userColumns"><property name="alias" value="t1"/></include>, <include refid="userColumns"><property name="alias" value="t2"/></include> from some_table t1 cross join some_table t2 </select>
属性值能够用于包含的refid属性或者包含的字句里面的属性值,例如:缓存
<sql id="sometable"> ${prefix}Table </sql> <sql id="someinclude"> from <include refid="${include_target}"/> </sql> <select id="select" resultType="map"> select field1, field2, field3 <include refid="someinclude"> <property name="prefix" value="Some"/> <property name="include_target" value="sometable"/> </include> </select>
上面这段SQL首先引用了someinclude而后再someinclude里面又引用了sometable。利用该功能配合if就能动态生成SQL语句了。安全
二、使用Mybatis优雅的解决N+1查询问题(关联的嵌套查询)session
归纳地讲,N+1 查询问题能够是这样引发的:mybatis
以上问题可使用关联嵌套查询来解决问题,如下介绍一下嵌套查询的标签<association>属性的做用。app
先举一个简单的例子:客户表customer和car是有一对多的关系,假设有实体类以下:
package com.zealzhangz.entity; import java.io.Serializable; import java.util.List; public class Customer implements Serializable { private static final long serialVersionUID = 7500918067281634276L; private String name; // 名称 private String sex; // 性别 private String idCar; private Car customerCar; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getIdCar() { return idCar; } public void setIdCar(String idCar) { this.idCar = idCar; } public Car getCustomerCar() { return customerCar; } public void setCustomerCar(Car customerCar) { this.customerCar = customerCar; } }
若是要获取客户和车辆的信息,通常的作法须要两次查询才能查出结果。固然能够换一种方式,把Car中字段拿到Customer中来,SQL JOIN查询也能查出结果,对于List<car>这种状况JOIN就无能为力了。可是mybatis已经提供了直接优雅
完成这种查询的功能SQL以下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="CustomerMapper"> <resultMap type="Customer" id="customerMap"> <association property="customerCar" column="idCar" javaType="Car" select="selectCar"/> </resultMap> <select id="selectCustomer" resultMap="customerMap" parameterType="string"> select name,sex,cc.id_car AS idCar from customer c INNER JOIN customer_car cc ON c.pk_id=cc.id_customer where c.pk_id = CAST(#{pk_id} AS UNSIGNED) </select> <select id="selectCar" resultType="Car"> select car_prefix AS carPrefix,car_no AS carNo,vin,car_brand_name AS carBrandName, car_series_mame AS carSeriesMame from car where pk_id = CAST(#{id} AS UNSIGNED) </select> </mapper>
结果以下:
{ "customerCar": { "carBrandName": "Jeep", "carNo": "A666666", "carPrefix": "苏", "carSeriesMame": "Compass [指南者]", "vin": "1C4NJCCA9CD645865" }, "name": "张先生", "sex": "先生" }
更复杂的情形有多辆车的状况,这种状况咱们稍后考虑。
注意:以上联合查询是一个分离的复杂的联合查询,下面这个是一个很是简单的示例 来讲明它如何工做。代替了执行一个分离的语句:
<select id="selectCustomerCar" resultType="Customer" parameterType="string"> SELECT name, sex, cc.id_car AS idCar, r.car_prefix AS carPrefix, r.car_no AS carNo, vin, car_brand_name AS carBrandName, car_series_mame AS carSeriesMame FROM tm_customer c INNER JOIN tm_customer_car cc ON c.pk_id = cc.id_customer INNER JOIN tm_car r ON cc.id_car = r.pk_id WHERE c.pk_id = CAST(#{pk_id} AS UNSIGNED) </select>
下面使用两个resultMap来联合映射上面的查询结果:
<resultMap id="customerCarResult" type="Customer"> <id property="idCustomer" column="idCustomer"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <association property="customerCar" column="idCar" javaType="Car" resultMap="carResult"/> </resultMap> <resultMap id="carResult" type="Car"> <id property="idCar" column="idCar"/> <result property="carPrefix" column="carPrefix"/> <result property="carNo" column="carNo"/> <result property="vin" column="vin"/> <result property="carBrandName" column="carBrandName"/> <result property="carSeriesMame" column="carSeriesMame"/> </resultMap>
很是重要: id元素在嵌套结果映射中扮演着非 常重要的角色。你应该老是指定一个或多个能够惟一标识结果的属性。实际上若是你不指定它的话, MyBatis仍然能够工做,可是会有严重的性能问题。在能够惟一标识结果的状况下, 尽量少的选择属性。主键是一个显而易见的选择(即便是复合主键)。
如今,上面的示例用了外部的结果映射元素来映射关联。这使得Car结果映射能够 重用。然而,若是你不须要重用它的话,或者你仅仅引用你全部的结果映射合到一个单独描 述的结果映射中。你能够嵌套结果映射。这里给出使用这种方式的相同示例:
<resultMap id="customerCarResultUnion" type="Customer"> <id property="idCustomer" column="idCustomer"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <association property="customerCar" javaType="Car"> <id property="idCar" column="idCar"/> <result property="carPrefix" column="carPrefix"/> <result property="carNo" column="carNo"/> <result property="vin" column="vin"/> <result property="carBrandName" column="carBrandName"/> <result property="carSeriesMame" column="carSeriesMame"/> </association> </resultMap>
假设有一种业务,每一个customer有一辆平常用车还有一辆备用车(这个业务现实中可能并不存在),客户实体就变成这个样子了:
import java.io.Serializable; import java.util.List; public class Customer implements Serializable { private static final long serialVersionUID = 7500918067281634276L; private String idCustomer; private String name; // 名称 private String sex; // 性别 private String idCar; private Car customerCar; private Car backupCar; //备用车 public String getIdCustomer() { return idCustomer; } public void setIdCustomer(String idCustomer) { this.idCustomer = idCustomer; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getIdCar() { return idCar; } public void setIdCar(String idCar) { this.idCar = idCar; } public Car getCustomerCar() { return customerCar; } public void setCustomerCar(Car customerCar) { this.customerCar = customerCar; } public Car getBackupCar() { return backupCar; } public void setBackupCar(Car backupCar) { this.backupCar = backupCar; } }
两个Car类型的字段:customerCar、backupCar,这个时候SQL查询怎么区分呢?
由于结果中的列名与resultMap中的列名不一样。 你须要指定columnPrefix去重用映射ba_car结果的resultMap。
<resultMap id="carResult" type="Car"> <id property="idCar" column="idCar"/> <result property="carPrefix" column="carPrefix"/> <result property="carNo" column="carNo"/> <result property="vin" column="vin"/> <result property="carBrandName" column="carBrandName"/> <result property="carSeriesMame" column="carSeriesMame"/> </resultMap> <resultMap id="customerCarResultAll" type="Customer"> <id property="idCustomer" column="idCustomer"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <association property="customerCar" resultMap="carResult"/> <association property="backupCar" resultMap="carResult" columnPrefix="ba_"/> </resultMap> <select id="selectCustomerCarAll" resultMap="customerCarResultAll" parameterType="string"> SELECT c.pk_id AS idCustomer, c.name, c.sex, cc.id_car AS idCar, r.car_prefix AS carPrefix, r.car_no AS carNo, r.vin, r.car_brand_name AS carBrandName, r.car_series_mame AS carSeriesMame, '1314182348972' AS ba_idCar, '云' AS ba_carPrefix, 'AR6248' AS ba_carNo, '1231sdfhs623462e7' AS ba_vin, '五羊' AS ba_carBrandName, '本田' AS ba_carSeriesMame FROM tm_customer c INNER JOIN tm_customer_car cc ON c.pk_id = cc.id_customer INNER JOIN tm_car r ON cc.id_car = r.pk_id WHERE c.pk_id = CAST(#{pk_id} AS UNSIGNED) </select>
结果以下:
{ "backupCar": { "carBrandName": "五羊", "carNo": "AR6248", "carPrefix": "云", "carSeriesMame": "本田", "idCar": "1314182348972", "vin": "1231sdfhs623462e7" }, "customerCar": { "carBrandName": "Jeep", "carNo": "A666666", "carPrefix": "苏", "carSeriesMame": "Compass [指南者]", "idCar": "10545406337939703159", "vin": "1C4NJCCA9CD645865" }, "idCustomer": "10545406337939703158", "name": "张先生", "sex": "先生" }
假设这种场景,一个客户有不少辆车且车和车之间没有逻辑关系,这时候咱们会用private List<Car> cars;来存储全部车辆的信息。这种状况改怎么写查询呢?
<resultMap id="customerCarListResult" type="Customer"> <id property="idCustomer" column="idCustomer"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <collection property="cars" javaType="ArrayList" column="idCustomer" ofType="Car" select="selectCarByIdCustomer"/> </resultMap> <select id="selectCarByIdCustomer" resultType="Car"> SELECT r.car_prefix AS carPrefix, r.car_no AS carNo, r.vin, r.car_brand_name AS carBrandName, r.car_series_mame AS carSeriesMame FROM tm_customer_car cc INNER JOIN tm_car r ON cc.id_car = r.pk_id WHERE cc.id_customer = CAST(#{idCustomer} AS UNSIGNED) </select> <select id="selectCustomerListCar" resultMap="customerCarListResult" parameterType="string"> select pk_id AS idCustomer,name,sex from tm_customer where pk_id = CAST(#{pk_id} AS UNSIGNED) </select>
结果以下图:
{ "cars": [ { "carBrandName": "Jeep", "carNo": "A666666", "carPrefix": "苏", "carSeriesMame": "Compass [指南者]", "vin": "1C4NJCCA9CD645865" }, { "carBrandName": "宝马", "carNo": "A86866", "carPrefix": "苏", "carSeriesMame": "X5 xDrive35i", "vin": "5UXZV4C51D0B11025" } ], "idCustomer": "10545406337939703158", "name": "张先生", "sex": "先生" }
这里咱们应该注意不少东西,但大部分代码和上面的关联元素是很是类似的。首先,你应 该注意咱们使用的是集合元素。而后要注意那个新的“ofType”属性。这个属性用来区分 JavaBean(或字段)属性类型和集合包含的类型来讲是很重要的。因此你能够读出下面这个 映射:
<collection property="cars" javaType="ArrayList" column="idCustomer" ofType="Car" select="selectCarByIdCustomer"/>
读做: “在Car类型的 ArrayList 中的 cars 的集合。”
javaType 属性是不须要的,由于 MyBatis 在不少状况下会为你算出来。因此你能够缩短 写法:
<collection property="cars" column="idCustomer" ofType="Car" select="selectCarByIdCustomer"/>
至此,咱们能够猜想集合的嵌套结果是如何来工做的,由于它和关联彻底相同,除了它应 用了一个“ofType”属性,请看以下SQL:
<select id="selectCustomerListCarUnion" resultMap="selectCustomerListCarUnionResult"> SELECT c.pk_id AS idCustomer, c.name, c.sex, cc.id_car AS idCar, r.car_prefix AS carPrefix, r.car_no AS carNo, r.vin, r.car_brand_name AS carBrandName, r.car_series_mame AS carSeriesMame FROM tm_customer c INNER JOIN tm_customer_car cc ON c.pk_id = cc.id_customer LEFT JOIN tm_car r ON r.pk_id = cc.id_car WHERE c.pk_id = CAST(#{idCustomer} AS UNSIGNED) </select>
咱们在一个SQL中查询出了一个客户的全部车辆信息,查询的结果形式以下:
客户1 车辆1
客户1 车辆2
咱们又一次联合了客户和车辆表,结果列标签的简单映射。现 在用汽车映射集合映射客户,能够简单写为:
<resultMap id="selectCustomerListCarUnionResult" type="Customer"> <id property="idCustomer" column="idCustomer"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <collection property="cars" ofType="Car"> <id property="idCar" column="idCar"/> <result property="carPrefix" column="carPrefix"/> <result property="carNo" column="carNo"/> <result property="vin" column="vin"/> <result property="carBrandName" column="carBrandName"/> <result property="carSeriesMame" column="carSeriesMame"/> </collection> </resultMap>
注意这里的<collection property="cars" ofType="Car">和上面<association property="customerCar" javaType="Car">不一样支出就是ofType是针对集合的。
一样,要记得 id 元素的重要性,若是你不记得了,请阅读上面的关联部分。
一样, 若是你引用更长的形式容许你的结果映射的更多重用, 你可使用下面这个替代 的映射:
<resultMap id="selectCustomerListCarUnionCustomerResult" type="Customer"> <id property="idCustomer" column="idCustomer"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <collection property="cars" ofType="Car" resultMap="carListUnion" columnPrefix="car_"/> </resultMap> <resultMap id="carListUnion" type="Car"> <id property="idCar" column="idCar"/> <result property="carPrefix" column="carPrefix"/> <result property="carNo" column="carNo"/> <result property="vin" column="vin"/> <result property="carBrandName" column="carBrandName"/> <result property="carSeriesMame" column="carSeriesMame"/> </resultMap> <select id="selectCustomerListCarUnion" resultMap="selectCustomerListCarUnionCustomerResult"> SELECT c.pk_id AS idCustomer, c.name, c.sex, cc.id_car AS car_idCar, r.car_prefix AS car_carPrefix, r.car_no AS car_carNo, r.vin AS car_vin, r.car_brand_name AS car_carBrandName, r.car_series_mame AS car_carSeriesMame FROM tm_customer c INNER JOIN tm_customer_car cc ON c.pk_id = cc.id_customer LEFT JOIN tm_car r ON r.pk_id = cc.id_car WHERE c.pk_id = CAST(#{idCustomer} AS UNSIGNED) </select>
有的时候结果集的映射须要根据结果集的每一个字段进行动态映射,好比结果实体有继承关系时。 鉴别器很是容易理 解,由于它的表现很像 Java 语言中的 switch 语句。好比:
<select id="selectCarDiscriminator" resultMap="carResultDiscriminator"> SELECT pk_id AS idCar, car_prefix AS carPrefix, car_no AS carNo, vin AS vin, car_brand_name AS carBrandName, car_series_mame AS carSeriesMame, 2 AS flag FROM tm_car WHERE pk_id = CAST(#{pk_id} AS UNSIGNED) </select> <resultMap id="carResultDiscriminator" type="Car"> <id property="idCar" column="idCar"/> <result property="carPrefix" column="carPrefix"/> <result property="carNo" column="carNo"/> <result property="vin" column="vin"/> <result property="carBrandName" column="carBrandName"/> <result property="carSeriesMame" column="carSeriesMame"/> <discriminator javaType="int" column="flag"> <case value="1" resultMap="test1Result"/> <case value="2" resultMap="test2Result"/> </discriminator> </resultMap> <resultMap id="test1Result" type="Car"> <result property="test1" column="flag"/> </resultMap> <resultMap id="test2Result" type="Car"> <result property="test2" column="vin"/> </resultMap>
以上逻辑<discriminator javaType="int" column="flag">中flag的值(1或2)动态映射test1Result或test2Result
以上result可简写为以下:
<resultMap id="carResultDiscriminator" type="Car"> <id property="idCar" column="idCar"/> <result property="carPrefix" column="carPrefix"/> <result property="carNo" column="carNo"/> <result property="vin" column="vin"/> <result property="carBrandName" column="carBrandName"/> <result property="carSeriesMame" column="carSeriesMame"/> <discriminator javaType="int" column="flag"> <case value="1" resultMap="test1Result"> <result property="test1" column="flag"/> </case> <case value="2" resultMap="test2Result"> <result property="test2" column="vin"/> </case> </discriminator> </resultMap>
当自动映射查询结果时,MyBatis会获取sql返回的列名并在java类中查找相同名字的属性(忽略大小写)。 这意味着若是Mybatis发现了ID列和id属性,Mybatis会将ID的值赋给id。一般数据库列使用大写单词命名,单词间用下划线分隔;而java属性通常遵循驼峰命名法。 为了在这两种命名方式之间启用自动映射,须要将 mapUnderscoreToCamelCase设置为true。
有三种自动映射等级:
默认值是PARTIAL,这是有缘由的。当使用FULL时,自动映射会在处理join结果时执行,而且join取得若干相同行的不一样实体数据,所以这可能致使非预期的映射。好比两个实体都有id属性,SELECT出来的id就不知道映射给谁。
MyBatis 包含一个很是强大的查询缓存特性,它能够很是方便地配置和定制。MyBatis 3 中的缓存实现的不少改进都已经实现了,使得它更增强大并且易于配置。
默认状况下是没有开启缓存的,除了局部的 session 缓存,能够加强变现并且处理循环 依赖也是必须的。要开启二级缓存,你须要在你的 SQL 映射文件中添加一行:
<cache/>
字面上看就是这样。这个简单语句的效果以下:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置建立了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,并且返回的对象被认为是只读的,所以在不一样线程中的调用者之间修改它们会 致使冲突。
可用的收回策略有:
默认的是 LRU。
flushInterval(刷新间隔)能够被设置为任意的正整数,并且它们表明一个合理的毫秒 形式的时间段。默认状况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)能够被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
readOnly(只读)属性能够被设置为 true 或 false。只读的缓存会给全部调用者返回缓 存对象的相同实例。所以这些对象不能被修改。这提供了很重要的性能优点。可读写的缓存 会返回缓存对象的拷贝(经过序列化) 。这会慢一些,可是安全,所以默认是 false
注意:若是手动修改了数据库的数据就可能会发生脏读,由于手动改数据触发不了mybatis的缓存更新机制。
除了这些自定义缓存的方式, 你也能够经过实现你本身的缓存或为其余第三方缓存方案 建立适配器来彻底覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
这个示 例展 示了 如何 使用 一个 自定义 的缓 存实 现。type 属 性指 定的 类必 须实现 org.mybatis.cache.Cache 接口。这个接口是 MyBatis 框架中不少复杂的接口之一,可是简单 给定它作什么就行。
public interface Cache { String getId(); int getSize(); void putObject(Object key, Object value); Object getObject(Object key); boolean hasKey(Object key); Object removeObject(Object key); void clear(); }
记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。所以,全部 在相同命名空间的语句正如绑定的缓存同样。 语句能够修改和缓存交互的方式, 或在语句的 语句的基础上使用两种简单的属性来彻底排除它们。默认状况下,语句能够这样来配置:
<select ... flushCache="false" useCache="true"/> <insert ... flushCache="true"/> <update ... flushCache="true"/> <delete ... flushCache="true"/>
由于那些是默认的,你明显不能明确地以这种方式来配置一条语句。相反,若是你想改 变默认的行为,只能设置 flushCache 和 useCache 属性。好比,在一些状况下你也许想排除 从缓存中查询特定语句结果,或者你也许想要一个查询语句来刷新缓存。类似地,你也许有 一些更新语句依靠执行而不须要刷新缓存。
这个特殊命名空间的惟一缓存会被使用或者刷新相同命名空间内 的语句。也许未来的某个时候,你会想在命名空间中共享相同的缓存配置和实例。在这样的 状况下你可使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>