官方文档:
英文:https://mybatis.org/mybatis-3/
中文:https://mybatis.org/mybatis-3/zh/index.html
MyBatis概述
什么是MyBatis
MyBatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。
MyBatis 避免了几乎所有的JDBC 代码和手动设置参数以及获取结果集。
MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
Mybatis与其他持久化方式对比
MyBatis是一个半自动化的持久化框架
JDBC是SQL夹在Java代码中,耦合度高导致硬编码,维护不易且实际开发中SQL会经常变化
Hibernate和JPA是内部自动产生的SQL语句,不容易做特殊优化,长而复杂的SQL,hibernate处理也不容易,是基于全映射的全自动化框架,大量子弹的pojo进行部分映射比较困难,导致数据库性能下降
对于开发人员,核心SQL需要自己优化,所以需要SQL和java编码分开,功能界面明显,一个专注业务,一个专注数据
文档资料
下载地址:https://github.com/mybatis/mybatis-3
中文文档:http://www.mybatis.org/mybatis-3/zh/index.html
基本使用
随着Maven的流行,现在几乎很少有使用jar的方式来搭建开发环境,这里也就不在单个使用Mybatis去操作数据库,不会的可以自行百度,MyBatis只是一个持久化框架,只有和其他框架整合才能更好的使用,例如SpringMVC,SpringBoot等,与Spring整合后,Mybatis的一些配置文件都会交于Spring管理。
MyBatis的全局配置文件
概述
MyBatis的全局配置文件可以配置的属性如下:
- properties 属性
- settings 设置
- typeAliases 类型别名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- environment 环境变量
- databaseIdProvider 数据库厂商标识
- mappers 映射器
properties属性
MyBatis使用properties来引入外部properties配置文件的内容,resource:引入类路径下的资源,url引入网络路径或者磁盘路径下的资源。可以用于将数据源连接信息放在properties文件中,与Spring整合后就写在Spring的配置文件中。
引入外部properties文件
<properties resource="org/mybatis/example/config.properties"></properties>
使用引入的properties文件
<dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource>
settings运行时设置
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。下表描述了设置中各项的意图、默认值等。
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局开启或关闭配置文件中的所有映射器任何缓存 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关 | true | false | false |
aggressive LazyLoading | 开启,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载 | true | false | false |
multipleResult SetsEnabled | 是否允许单一语句返回多结果集 | true | false | true |
useColumnLabel | 使用列标签代替列名。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键 如果设置为 true 则这个设置强制使用自动生成主键 | true | false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射; PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集 | NONE, PARTIAL, FULL | PARTIAL |
autoMapping Unknown ColumnBehavior | 指定发现自动映射目标未知列(或者未知属性类型)的行为。 NONE : 不做任何反应 WARNING : 输出提醒 |
NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
default StatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | 任意正整数 | |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | |
safeRow BoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。 | true | false | False |
safeResult HandlerEnabled | 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。 | true | false | True |
mapUnderscore ToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true | false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | OTHER | |
lazyLoadTrigger Methods | 指定哪个对象的方法触发一次延迟加载。 | ||
defaultScripting Language | 指定动态 SQL 生成的默认语言。 | ||
defaultEnum TypeHandler | 指定 Enum 使用的默认 TypeHandler 。 (从3.4.5开始) |
||
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true | false | false |
returnInstance ForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回null 。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始) |
true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | ||
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象用到的代理工具。 | CGLIB | JAVASSIST |
常用的Setting设置
设置参数 | 描述 | 默认值 |
---|---|---|
mapUnderscoreToCamelCase | 是否开启驼峰命名规则映射A_COLUNM到aColumn | false |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数 |
Settings设置示例
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
typeAliases别名
类型别名是为 Java 类型设置一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余,但是往往我们不会使用别名,是为了方便查看代码。
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>
指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,给包和子包下的所有类起一个默认的别名(类名小写)
<typeAliases> <package name="domain.blog"/> </typeAliases>
每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。
@Alias("author") public class Author {}
typeHandlers 类型处理器
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean , boolean |
数据库兼容的 BOOLEAN |
ByteTypeHandler |
java.lang.Byte , byte |
数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler |
java.lang.Short , short |
数据库兼容的 NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler |
java.lang.Integer , int |
数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler |
java.lang.Long , long |
数据库兼容的 NUMERIC 或 LONG INTEGER |
FloatTypeHandler |
java.lang.Float , float |
数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler |
java.lang.Double , double |
数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler |
java.math.BigDecimal |
数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler |
java.lang.String |
CHAR , VARCHAR |
ClobReaderTypeHandler |
java.io.Reader |
- |
ClobTypeHandler |
java.lang.String |
CLOB , LONGVARCHAR |
NStringTypeHandler |
java.lang.String |
NVARCHAR , NCHAR |
NClobTypeHandler |
java.lang.String |
NCLOB |
BlobInputStreamTypeHandler |
java.io.InputStream |
- |
ByteArrayTypeHandler |
byte[] |
数据库兼容的字节流类型 |
BlobTypeHandler |
byte[] |
BLOB , LONGVARBINARY |
DateTypeHandler |
java.util.Date |
TIMESTAMP |
DateOnlyTypeHandler |
java.util.Date |
DATE |
TimeOnlyTypeHandler |
java.util.Date |
TIME |
SqlTimestampTypeHandler |
java.sql.Timestamp |
TIMESTAMP |
SqlDateTypeHandler |
java.sql.Date |
DATE |
SqlTimeTypeHandler |
java.sql.Time |
TIME |
ObjectTypeHandler |
Any | OTHER 或未指定类型 |
EnumTypeHandler |
Enumeration Type | VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引) |
EnumOrdinalTypeHandler |
Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称)。 |
InstantTypeHandler |
java.time.Instant |
TIMESTAMP |
LocalDateTimeTypeHandler |
java.time.LocalDateTime |
TIMESTAMP |
LocalDateTypeHandler |
java.time.LocalDate |
DATE |
LocalTimeTypeHandler |
java.time.LocalTime |
TIME |
OffsetDateTimeTypeHandler |
java.time.OffsetDateTime |
TIMESTAMP |
OffsetTimeTypeHandler |
java.time.OffsetTime |
TIME |
ZonedDateTimeTypeHandler |
java.time.ZonedDateTime |
TIMESTAMP |
YearTypeHandler |
java.time.Year |
INTEGER |
MonthTypeHandler |
java.time.Month |
INTEGER |
YearMonthTypeHandler |
java.time.YearMonth |
VARCHAR or LONGVARCHAR |
JapaneseDateTypeHandler |
java.time.chrono.JapaneseDate |
DATE |
plugins插件
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
environments环境配置
MyBatis可以配置多种环境,default代表指定使用某种环境,这样就可以快速切换环境,尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一,所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例 。每个数据库对应一个 SqlSessionFactory 实例
环境配置实例
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
环境配置说明
- 默认的环境 ID(比如:default="development")。
- 每个 environment 元素定义的环境 ID(比如:id="development")。
- 事务管理器的配置(比如:type="JDBC")。
- 数据源的配置(比如:type="POOLED")。
MyBatis映射文件
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在 。SQL 映射文件有很少的几个顶级元素 ,如下
cache
– 给定命名空间的缓存配置。cache-ref
– 其他命名空间缓存配置的引用。resultMap
– 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。sql
– 可被其他语句引用的可重用语句块。insert
– 映射插入语句update
– 映射更新语句delete
– 映射删除语句select
– 映射查询语句
Select查询
查询语句是 MyBatis 中最常用的元素之一 ,简单查询的 select 元素是非常简单的。比如
<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
该查询接受一个 int(或 Integer)类型的参数,返回一个 HashMap 类型的对象,键是列名,值是结果行中的对应值。
select 元素有很多属性允许你配置,来决定每条语句的作用细节 ,如下
<select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY">
属性 | 描述 |
---|---|
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterMap | 这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数映射和 parameterType 属性。 |
resultType |
从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。 |
resultMap |
外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。 |
flushCache |
将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。 |
useCache |
将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。 |
timeout |
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 |
fetchSize |
这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。 |
statementType |
STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultSetType |
FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。 |
databaseId |
如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
resultOrdered |
这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false 。 |
resultSets |
这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。 |
insert update delete
数据变更语句 insert,update 和 delete 的实现非常接近 ,如下
<insert id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20"> <update id="updateAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="deleteAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20">
属性说明
id |
命名空间中的唯一标识符,可被用来代表这条语句。 |
---|---|
parameterType |
将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
flushCache |
将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。 |
timeout |
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 |
statementType |
STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys |
(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 |
keyProperty |
(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset 。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn |
(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
databaseId |
如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
示例
<insert id="insertAuthor"> insert into Author (id,username,password,email,bio) values (#{id},#{username},#{password},#{email},#{bio}) </insert> <update id="updateAuthor"> update Author set username = #{username}, password = #{password}, email = #{email}, bio = #{bio} where id = #{id} </update> <delete id="deleteAuthor"> delete from Author where id = #{id} </delete>
多行插入
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id"> insert into Author (username, password, email, bio) values <foreach item="item" collection="list" separator=","> (#{item.username}, #{item.password}, #{item.email}, #{item.bio}) </foreach> </insert>
自动生成主键
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id"> insert into Author (username,password,email,bio) values (#{username},#{password},#{email},#{bio}) </insert>
映射文件小结
- Mybatis允许增删改直接定义的返回值:Integer,Long,Boolean,返回的是改变数据库表的记录数和true和false
- 支持自动生成主键的字段,设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上就OK了。
- 数据库还支持多行插入, 你也可以传入一个数组或集合,并返回自动生成的主键
映射文件参数处理
- 单个参数:MyBatis不会做特殊处理,#{参数名/任意名}:取出参数值
- 多个参数:MyBatis会做特殊处理,多个参数被封装为一个map,key:param1...param10或者参数的索引
- 命名参数,多个参数使用#{param1}来取值导致错乱,故使用命名参数,明确指定封装map的key,如下
public Person getPerson(@Param("id") Integer id,@Param("name") String laseName);
这个时候在xml文件中可以使用#{id}和#{name}来取值
- 如果传入多个参数正好是POJO:可以使用#{属性名}直接获取。
- 如果传入多个参数不是POJO,为了方便,我们可以传入map,如下
public Person getPerson(Map<String,Object> map);
这个时候也就可以在xml文件中使用#{key}直接取出传入map的key对应的值
- 如果传入多个参数不是POJO,但是查询频率高,使用map不方便,可以编写一个TO(数据传输对象)
- 如果传入了一个Collection(list,set)类型或者数组,也会特殊处理,也是吧list或者数组封装到map中,传入的类型不一样,对应的key如下:Collection(collection),List(list),数组(array),示例如下
public Person get(List<Integer> ids);
传入的是一个list集合,在xml文件中,我们如果需要取出list中的第一个元素为:#{list[0]}
#和$取值区别
- 取值的方式#{}是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
- ${}:取出的值直接拼装在sql语句中;会有安全问题;大多情况下,我们去参数的值都应该去使用#{};
- 原生jdbc不支持占位符的地方我们就可以使用${}进行取值比如分表、排序。;按照年份分表拆分
select * from ${year}_salary where xxx; select * from tbl_employee order by ${f_name} ${order}
select查询返回类型
- 返回一个集合:resultType写集合中元素的类型
- 返回一条记录的map,key为列名,value为列对应的值,例如
public Map<String ,Object> getPersonByMap(Integer id);
<select id = "getPersonByMap" resultType="map"> select * from person where id = #{id} </select>
- 返回多条记录封装的一个map,Map<Integer,Person>:key是这条记录的主键,值是记录封装后的pojo,如下
@MapKey("id") public Map<Integer,Person> getPersonByName(String name);
<select id = "getPersonByName" resultType="com.test.Person"> select * from person where last_name like #{name} </select>
@MapKey("id")注解表示使用那个属性作为返回结果map的key。
resultMap自定义结果集
示例
<restMap id="baseMap" type="com.test.Person"> <id column="id" property="id"/> <result column="last_name" property="lastName" ></result> </restMap> <select id="getPerson" restMap="baseMap"> select * from person </select>
resultMap关联属性_级联属性封装结果集
例如:员工有部门属性,联合查询返回封装结果
<restMap id="baseMap" type="com.test.Person"> <id column="id" property="id"/> <result column="last_name" property="lastName" ></result> <result column="dept_id" property="dept.id" ></result> <result column="dept_name" property="dept.name" ></result> </restMap>
使用association指定联合的java对象
<restMap id="baseMap" type="com.test.Person"> <id column="id" property="id"/> <result column="last_name" property="lastName" ></result> <association property="dept" javaType="com.test.DepartMent"> <id column="dept_id" property="id"/> <result column="dept_name" property="name" ></result> </association> </restMap>
使用collection定义关联集合的封装规则
例如,查询部门的时候,查询出当前部门下的所有员工
<restMap id="baseMap" type="com.test.DepartMent"> <id column="dept_id" property="id"/> <result column="dept_name" property="name" ></result> <collection property="persons" javaType="com.test.Person"> <id column="id" property="id"/> <result column="last_name" property="lastName" ></result> </collection> </restMap>
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>
MyBatis的动态SQL
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。 MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。 如下
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if的使用
动态 SQL 通常要做的事情是根据条件包含 where 子句的一部分。比如
注意:在xml文件中特殊符号,像<,>要使用转义字符
<select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> AND title like #{title} </if> </select>
choose,when,otherwise
有时我们不想应用到所有的条件语句,而只想从中择其一项 ,如下
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <choose> <when test="title != null"> AND title like #{title} </when> <when test="author != null and author.name != null"> AND author_name like #{author.name} </when> <otherwise> AND featured = 1 </otherwise> </choose> </select>
trim, where, set
在前面,如果所有的条件都是动态sql,那么可能会出现一下情况的SQL语句
SELECT * FROM BLOG WHERE SELECT * FROM BLOG WHERE AND title like ‘someTitle’
出现以上错误的sql语句,MyBatis提供了一种解决方式
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where> </select>
where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除 。注意:WHERE只会去掉开头第一个AND或OR
使用where会出错的情况,And放在后面
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} AND </if> <if test="title != null"> title like #{title} AND </if> <if test="author != null and author.name != null"> author_name like #{author.name} </if> </where> </select>
另外一种解决办法就是使用标签,使用where,也可能造成最后一个and,使用trim方法如下
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 属性中的内容(移除前面多余的AND 或者OR),并且插入 prefix 属性中指定的内容。 使用suffixOverrides会移除后面多余的AND或者OR。
set标签与if结合实现动态更新
<update id="updateAuthorIfNecessary"> update Author <set> <if test="username != null">username=#{username},</if> <if test="password != null">password=#{password},</if> <if test="email != null">email=#{email},</if> <if test="bio != null">bio=#{bio}</if> </set> where id=#{id} </update>
这里,set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号,也可以使用trim,注意这里我们删去的是后缀值,同时添加了前缀值。
<trim prefix="SET" suffixOverrides=","> ... </trim>
foreach
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:
<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")" index="i"> #{item} </foreach> </select>
说明:
- collection:指定要遍历的集合
- item:将当前遍历的每一个元素赋给指定的变量
- separator:每一个元素之间的分隔符
- open:遍历出所有的结果拼接一个开始的字符
- close:遍历出所有的结果拼接一个结束的字符
- index:遍历list的就是索引,遍历map的时候就是map的key,item是map的值
Mysql下的批量插入
public void addEmp(@Param("emps") List<Employee> emps);
<insert id="addEmp"> INSERT into employee(name,age)values <foreach item="emp" index="index" collection="emps" open="(" separator="," close=")" index="i"> #{emp.name}, #{emp.age} </foreach> </insert>
bind
bind
元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如
<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern} </select>
如果是模糊查询,使用下面的方式是行不通的,如下
<select> select * from person <if test="lastName != null"> where lastName like '%#{lastName}%' </if> </select>
解决方式之一,可以使用$符号(不安全)
<select> select * from person <if test="lastName != null"> where lastName like '%${lastName}%' </if> </select>
解决方式之二,使用bind标签
<select> <bind name="_lastName" value="'%'+lastName+'%'"></bind> select * from person <if test="lastName != null"> where lastName like #{_lastName} </if> </select>
<bind name="_lastName" value="'_'+lastName+'%'"></bind><!--表示以什么开始,后面是参数的模糊查询-->
MyBatis的缓存机制
概述
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存(本地缓存)
与数据库同一次会话期间查询的数据会放在本地缓存中,以后如果需要获取相同数据,直接从缓存中拿,不再查询数据库
一级缓存失效的四种情况
一级缓存是sqlSession级别的缓存,也就说一个sqlSession拥有自己的一级缓存,一级缓存是一直开启的,没有使用一级缓存的情况
- 不同的sqlSession对应不同的一级缓存
- 同一个sqlSession,但是查询条件不一样
- 同一个sqlSession两次查询期间执行了任何一次增删改操作
- 同一个sqlSession两次查询期间手动清空了缓存
二级缓存(全局缓存)
- 二级缓存基于namespace默认不开启,需要手动配置
- MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
- 二级缓存在SqlSession 关闭或提交之后才会生效
工作机制
- 一个会话,查询一条数据,这个数据就会放在当前会话的一级缓存中
- 如果会话关闭,一级缓存中的数据就会保存到二级缓存中,新的查询信息,就参照二级缓存
使用步骤
- 全局配置文件中开启二级缓存
<setting name="cacheEnabled" value="true"/>
- 需要使用二级缓存的映射文件mapper.xml处使用cache配置缓存
<cach></cach>
- 注意:POJO需要实现Serializable接口
Mybatis提供了整合ehcache缓存,具体整合方法参考官网文档,
MyBatis的逆向工程
概述
MyBatis Generator:简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写
官方文档地址:http://www.mybatis.org/generator/
官方工程地址:https://github.com/mybatis/generator/releases
MBG使用步骤
- 编写MBG的配置文件(重要几处配置)
- jdbcConnection配置数据库连接信息
- javaModelGenerator配置javaBean的生成策略
- sqlMapGenerator配置sql映射文件生成策略
- javaClientGenerator配置Mapper接口的生成策略
- table配置要逆向解析的数据表
- tableName:表名
- domainObjectName:对应的javaBean名
- 运行代码生成器生成代码
注意:
Context标签
targetRuntime=“MyBatis3“可以生成带条件的增删改查
targetRuntime=“MyBatis3Simple“可以生成基本的增删改查
如果再次生成,建议将之前生成的数据删除,避免xml向后追加内容出现的问题。
MBG配置文件
<generatorConfiguration> <context id="DB2Tables" targetRuntime="MyBatis3"> //数据库连接信息配置 <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/bookstore0629" userId="root" password="123456"> </jdbcConnection> //javaBean的生成策略 <javaModelGenerator targetPackage="com.atguigu.bean" targetProject=".\src"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> //映射文件的生成策略 <sqlMapGenerator targetPackage="mybatis.mapper" targetProject=".\conf"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> //dao接口java文件的生成策略 <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.dao" targetProject=".\src"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> //数据表与javaBean的映射 <table tableName="books" domainObjectName="Book"></table> </context> </generatorConfiguration>
生成器代码
public static void main(String[] args) throws Exception { List<String> warnings = new ArrayList<String>(); boolean overwrite = true; File configFile = new File("mbg.xml"); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new efaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null); }
QBC风格的带条件查询
@Test public void test01(){ SqlSession openSession = build.openSession(); DeptMapper mapper = openSession.getMapper(DeptMapper.class); DeptExample example = new DeptExample(); //所有的条件都在example中封装 Criteria criteria = example.createCriteria(); //select id, deptName, locAdd from tbl_dept WHERE //( deptName like ? and id > ? ) criteria.andDeptnameLike("%部%"); criteria.andIdGreaterThan(2); List<Dept> list = mapper.selectByExample(example); for(Dept dept : list) { System.out.println(dept); }
Mybatis的插件开发
PageHelper分页插件
项目地址:https://github.com/pagehelper/Mybatis-PageHelper
文档地址:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
使用步骤
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>最新版本</version> </dependency>
在 MyBatis 配置 xml 中配置拦截器插件
<plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 --> <property name="param1" value="value1"/> </plugin> </plugins>
代码中使用方法
//第二种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.startPage(1, 10); List<Country> list = countryMapper.selectIf(1); //第三种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.offsetPage(1, 10); List<Country> list = countryMapper.selectIf(1); //第六种,ISelect 接口方式 //jdk6,7用法,创建接口 Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() { @Override public void doSelect() { countryMapper.selectGroupBy(); } }); //jdk8 lambda用法 Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy()); //也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() { @Override public void doSelect() { countryMapper.selectGroupBy(); } }); //对应的lambda用法 pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy()); //count查询,返回一个查询语句的count数 long total = PageHelper.count(new ISelect() { @Override public void doSelect() { countryMapper.selectLike(country); } }); //lambda total = PageHelper.count(()->countryMapper.selectLike(country));
常用方法介绍
RowBounds方式的调用
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(1, 10));
使用这种调用方式时,你可以使用RowBounds参数进行分页,这种方式侵入性最小,我们可以看到,通过RowBounds方式调用只是使用了这个参数,并没有增加其他任何内容。分页插件检测到使用了RowBounds参数时,就会对该查询进行物理分页
PageHelper.startPage静态方法调用
在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage
静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页。
PageInfo用法
//获取第1页,10条内容,默认查询总数count PageHelper.startPage(1, 10); List<Country> list = countryMapper.selectAll(); //用PageInfo对结果进行包装 PageInfo page = new PageInfo(list); //测试PageInfo全部属性 //PageInfo包含了非常全面的分页属性 assertEquals(1, page.getPageNum()); assertEquals(10, page.getPageSize()); assertEquals(1, page.getStartRow()); assertEquals(10, page.getEndRow()); assertEquals(183, page.getTotal()); assertEquals(19, page.getPages()); assertEquals(1, page.getFirstPage()); assertEquals(8, page.getLastPage()); assertEquals(true, page.isFirstPage()); assertEquals(false, page.isLastPage()); assertEquals(false, page.isHasPreviousPage()); assertEquals(true, page.isHasNextPage());
Mybatis批量保存
在MyBatis的全局设置中有设置,defaultExecutorType 配置默认的执行器
- SIMPLE 就是普通的执行器;
- REUSE 执行器会重用预处理语句(prepared statements);
- BATCH 执行器将重用语句并执行批量更新
但是,如果在全局设置中设置批量执行器,那么每一个mapper中的方法都会执行批量操作,所以我们一般都是在与Spring整合后在Application.xml中配置一个可以执行批量操作的sqlSession,如下
<!--创建出SqlSessionFactory对象 --> <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!-- configLocation指定全局配置文件的位置 --> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!--mapperLocations: 指定mapper文件的位置--> <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg> <constructor-arg name="executorType" value="BATCH"></constructor-arg> </bean>
在Service中自动注入SQLSession
@Autowired private SqlSession sqlSession; public List<Employee> getEmps(){ EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); return mapper.getEmps(); }
来源:https://www.cnblogs.com/wbyixx/p/12196065.html