mybatis使用官方文档已经比较好,此处边笔记边学习(大部分复制粘贴)
开发环境:SpringBoot(借用上篇开发环境)
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,通过SqlSessionFactory 创建session连接操作DB,基本使用
SqlSessionFactory 有六个方法可以用来创建 SqlSession 实例。通常来说,如何决定是你 选择下面这些方法时:
- Transaction (事务): 你想为 session 使用事务或者使用自动提交(通常意味着很多 数据库和/或 JDBC 驱动没有事务)?
- Connection (连接): 你想 MyBatis 获得来自配置的数据源的连接还是提供你自己
- Execution (执行): 你想 MyBatis 复用预处理语句和/或批量更新语句(包括插入和 删除)?
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession:
- 会开启一个事务(也就是不自动提交)
- 连接对象会从由活动环境配置的数据源实例中得到。
- 事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
开启自动提交, “true” 传递 给可选的 autoCommit 参数。 提供自定义的连接,传递一个 Connection 实例给 connection 参数。注意没有覆盖同时设置 Connection 和 autoCommit 两者的方法,因为 MyBatis 会使用当前 connection 对象提供的设 置。 MyBatis 为事务隔离级别调用使用一个 Java 枚举包装器, 称为 TransactionIsolationLevel, 否则它们按预期的方式来工作,并有 JDBC 支持的 5 级 ( NONE,READ_UNCOMMITTED,READ_COMMITTED,REPEA TABLE_READ,SERIALIZA BLE)
还有一个可能对你来说是新见到的参数,就是 ExecutorType。这个枚举类型定义了 3 个 值:
- ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情。它为每个语句的执行创建一个新的预处理语句。
- ExecutorType.REUSE: 这个执行器类型会复用预处理语句。
- ExecutorType.BATCH: 这个执行器会批量执行所有更新语句,如果 SELECT 在它们中间执行还会标定它们是 必须的,来保证一个简单并易于理解的行为
基于Mapper
package mybatis;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import mybatis.mapper.CityMapper;
import mybatis.model.City;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MybatisApplicationTests {
@Autowired
SqlSessionFactory sqlSessionFactory;
@Test
public void selectBySql() {
System.out.println("");
SqlSession session = sqlSessionFactory.openSession();
try {
CityMapper cityMapper = session.getMapper(CityMapper.class);
System.out.println(cityMapper.selectCityById(1));
// 此种不推荐
//System.out.println((City)session.selectOne("mybatis.mapper.CityMapper.selectCityById", 1));
} finally {
session.close();
}
}
}
不过我们在配置
SqlSessionFactory通过设置setMapperLocations已经实例化一批Mapper,直接注入使用即可
依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器(mapper)并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。对于显示创建或这是需要注意其作用域与生命周期
作用域与生命周期
- SqlSessionFactoryBuilder 创建SqlSessionFactory,方法作用域即可
- SqlSessionFactory 应用运行期
- SqlSession 线程不安全,请求或方法作用域,用完即关(保证线程专属)
- mapper 作用域通SqlSession.最好控制在方法作用域
基于注解
xml配置
properties
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
属性也可以被传递到 SqlSessionFactoryBuilder.build()方法中。例如:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
加载顺序
- 在 properties 元素体内指定的属性首先被读取。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
<dataSource type="POOLED">
<property name="username" value="${username:ut_user}"/> <!-- If 'username' property not present, username become 'ut_user' -->
</dataSource>
默认关闭,开启,可自定义分隔符
<properties resource="org/mybatis/example/config.properties">
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- Enable this feature -->
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- Change default value of separator -->
</properties>
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
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/><!--bean-->
<package name="domain.blog"/><!--package-->
</typeAliases>
每一个在包
domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如
domain.blog.Author
的别名为
author
;若有注解,则别名为其注解值。(测试,首字母大写也可)
@Alias("author")
public class Author {
...
}
已经为许多常见的 Java 类型内建了相应的类型别名,如int -> _int, Integer ->integer
typeHandlers
@MappedJdbcTypes(JdbcType.VARCHAR)//JDBC type,或xml的typeHandler 中配置jdbcType="VARCHAR"(xml优先级高)
@MappedTypes(String.class)// java type,或xml的typeHandler 中配置 javaType="String"
public class ExampleTypeHandler 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);
}
}
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"/><!--batch scan-->
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
当决定在
ResultMap
中使用某一TypeHandler时,此时java类型是已知的(从结果类型中获得),但是JDBC类型是未知的。 因此Mybatis使用
javaType=[TheJavaType], jdbcType=null
的组合来选择一个TypeHandler。 这意味着使用
@MappedJdbcTypes
注解可以
限制
TypeHandler的范围,同时除非显示的设置,否则TypeHandler在
ResultMap
中将是无效的。 如果希望在
ResultMap
中使用TypeHandler,那么设置
@MappedJdbcTypes
注解的
includeNullJdbcType=true
即可。 然而从Mybatis 3.4.0开始,如果
只有一个
注册的TypeHandler来处理Java类型,那么它将是
ResultMap
使用Java类型时的默认值(即使没有
includeNullJdbcType=true
)。
Enum
- EnumTypeHandler 默认,映射名称
- EnumOrdinalTypeHandler 映射ordinal
objectFactory
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}}
配置
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
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 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者共享相同 Schema 的多个生产数据库, 想使用相同的 SQL 映射。许多类似的用例.
如果你想连接两个数据库,就需要创建两个 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>
databaseIdProvider
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
Mappers
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<package name="org.mybatis.builder"/>
</mappers>
Mapper XML 文件
- cache – 给定命名空间的缓存配置。
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap– 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
select
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">
sql
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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>
<property>中定义的属性sql中可以通过${}进行调用
Parameters
Result Maps
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
通过其id进行引用
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
高级结果印射
<!-- Very Complex Statement -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
印射结果集
<!-- Very Complex Result Map -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
ResultMap属性
- id 唯一标识,用于标识一个result map
- type类的全限定名
- autoMapping 如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性autoMappingBehavior。默认值为:unset
- constructor - 类在实例化时,用来注入结果到构造方法中
- idArg - ID 参数;标记结果作为 ID 可以帮助提高整体效能
- arg - 注入到构造方法的一个普通结果
- id – 一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能
- result – 注入到字段或 JavaBean 属性的普通结果
- association – 一个复杂的类型关联;许多结果将包成这种类型
- 嵌入结果映射 – 结果映射自身的关联,或者参考一个
- collection – 复杂类型的集
- 嵌入结果映射 – 结果映射自身的集,或者参考一个
- discriminator – 使用结果值来决定使用哪个结果映射
- case – 基于某些值的结果映射
- 嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相 同的元素,或者它可以参照一个外部的结果映射。
- case – 基于某些值的结果映射
id or result
- property java类属性
- column db列名
- javaType java类型
- jdbcType db类型
- typeHandler 类型处理器
constructor
association
collection
discriminator
自动映射
自动映射是默认开启(setting中设置autoMappingBehavior),指当查询列名与java类属性名一致就会自动映射(resultType就是运用自动),当查询不匹配时,我们就需要手动印射(使用resultMap)
可以设置一些自动印射的策略,如通过setting设置mapUnderscoreToCamelCase,只是查询列名与驼峰式java属性匹配
autoMappingBehavior有三种等级
- NONE - 禁用自动映射。仅设置手动映射属性。
- PARTIAL - 默认,将自动映射结果除了那些有内部定义内嵌结果映射的(joins).
- FULL - 自动映射所有。
cache
通过BaseExecutor实现一级缓存,CachingExecutor实现二级缓存
mybatis除了局部的session缓存,默认是开启二级缓存,但必须xml中使用<cache/>标签使用二级缓存
一级缓存有session与statement,setting中通过localCacheScope设置
<setting name="localCacheScope" value="SESSION"/>
- session 默认,一个session内有效
- statement 执行完mapper便清楚缓存
<setting name="cacheEnabled" value="true" />
使用二级缓存
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
或(引用其他mapper的cache,夸mapper命中)
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
cache优先级高,配置cache,相关属性
- blocking 默认false,查询加锁,-BlockingCache
- eviction 回收策略 -PerpetualCache(LruCache,FifoCache...)
- LRU – 默认,最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- flushInterval 清空缓存的时间间隔(ms),默认不清除 -ScheduleCache
- readOnly 默认false ,false每次都获取缓存对象的拷贝(序列化)
- size 缓存个数,LRU默认1024
- type 缓存实现类,如LRU默认LruCache,可自定cache
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
自定义cache
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
其中property 相当于set方法
Collection<Cache> caches =session.getConfiguration().getCaches();
当然可以使用地方缓存插件,比如
ehcache
动态SQL
if + where + choose,when,otherwise
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<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>
<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>
</where>
</select>
trim
trim定义一个语句的开头以及忽略的结尾,以下等价于where,set的trim#set
<trim prefix="SET" suffixOverrides=",">
...
</trim>
#where
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefix前缀,
prefixOverrides忽略的后缀
foreach
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
bind
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
Multi-db vendor support
一个配置了“_databaseId”变量的 databaseIdProvider 对于动态代码来说是可用的,这样就可以根据不同的数据库厂商构建特定的语句
<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>
Logging
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。
配置
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>