MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
第一步、引入maven依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
第二步、构建 SqlSessionFactory
MyBatis 的应用都是以一个 SqlSessionFactory
的实例为核心的,SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
获得。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
简单的mybatis-config.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>
<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://127.0.0.1:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/PersonMapper.xml"/>
</mappers>
</configuration>
第三步、SqlSessionFactory 中获取 SqlSession
SqlSession session = sqlSessionFactory.openSession();
//com.kun.start.mapper.PersonMapper是PersonMapper.xml的namespace,selectAll具体sql的ID
List<Object> persons = session.selectList("com.kun.start.mapper.PersonMapper.selectAll");
for (Object person : persons) {
System.out.println(person);
}
也可以使用接口的方式调用
SqlSession session = sqlSessionFactory.openSession();
//会根据PersonMapper全限定命找到对应的PersonMapper.xml,动态代理此接口
PersonMapper mapper = session.getMapper(PersonMapper.class);
List<Person> persons = mapper.selectAll();
for (Person person : persons) {
System.out.println(person);
}
作用域(Scope)和生命周期
SqlSessionFactoryBuilder
:一旦创建了 SqlSessionFactory,就不再需要它,即会被丢弃。
SqlSessionFactory
:SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建,应用中应该只存在一个。
SqlSession
:每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域,不可以放在ThreadLocal当中
Mapper Instances
:用过之后即可废弃,并不需要显式地关闭映射器实例。
参数 | 描述 | 默认值 |
---|---|---|
lazyLoadingEnabled | 懒加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType 属性来覆盖该项的开关状态。 |
false |
aggressiveLazyLoading | 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 | false (true in ≤3.4.1) |
mapUnderscoreToCamelCase | 否开启自动驼峰命名规则(camel case)映射 | False |
lazyLoadTriggerMethods | 指定对象的哪个方法触发延迟加载 | equals,clone,hashCode,toString |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。 (从3.4.5开始) |
org.apache.ibatis.type.EnumTypeHandler |
类型别名是为 java 类型设置一个短的名字,也可以在类上使用@Alias("xxx")
。它只和 XML 配置有关。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
</typeAliases>
类型处理器 | Java 类型 | JDBC 类型 |
---|---|---|
EnumTypeHandler | Enumeration Type | VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引) |
EnumOrdinalTypeHandler | Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称)。 |
自定义枚举处理器:
枚举类接口,定义统一规范
public interface BaseEnum<E extends Enum<?>,T> {
T getCode();
String getMessage();
}
自定义枚举类
public enum InfoEnum implements BaseEnum<InfoEnum,String>{
SUCCESS("200","成功"),
FAILD("500","错误");
private String code;
private String message;
static Map<String,InfoEnum> enumMap=new HashMap<String, InfoEnum>();
static{
(InfoEnum type:InfoEnum.values()){
enumMap.put(type.getCode(), type);
}
}
public static Map<String,InfoEnum> getAll() {
return enumMap;
}
private InfoEnum(String code, String message) {
this.code = code;
this.message = message;
}
@Override
public String getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
}
自定义枚举类型处理器
public class InfoEnumTypeHandler extends BaseTypeHandler<InfoEnum>{
@Override
public void setNonNullParameter(PreparedStatement ps, int i, InfoEnum parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter.getCode());
}
@Override
public InfoEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
String columnValue = rs.getString(columnName);
return InfoEnum.getAll().get(columnValue);
}
@Override
public InfoEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String columnValue = rs.getString(columnIndex);
return InfoEnum.getAll().get(columnValue);
}
@Override
public InfoEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String columnValue = cs.getString(columnIndex);
return InfoEnum.getAll().get(columnValue);
}
}
配置自定义类型处理器
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="com.kun.start.typehandler.InfoEnumTypeHandler"/>
</typeHandlers>
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
例自定义插件:
@Intercepts(value= {@Signature(type=Executor.class,method="query",args= {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})
public class ExamplePlugin implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
for (Object arg : invocation.getArgs()) {
if(arg != null && arg instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) arg;
System.out.println("id = " +ms.getId());
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
System.out.println(properties.get("pro"));
}
}
SQL 映射文件几个顶级元素
cache
– 给定命名空间的缓存配置。cache-ref
– 其他命名空间缓存配置的引用。resultMap
– 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。sql
– 可被其他语句引用的可重用语句块。insert
– 映射插入语句update
– 映射更新语句delete
– 映射删除语句select
– 映射查询语句基本,#{id}
会在预编译语中被替换为?
,当只有一个参数时候,#{param}
可以是任意名称,当参数个数大于1时,使用#{arg0},#{arg1},#{arg2}...
或者#{param1},#{param2},#{param3}...
,此时习惯使用@Param("[参数名称]")
来指定参数的名称。
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
常用可选属性
属性 | 描述 |
---|---|
parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset |
resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用 |
resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用 |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false |
useCache | 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED |
常用可选属性(insert、update、delete)
属性 | 描述 |
---|---|
parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true |
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)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表 |
有些数据库(例如Oracle)主键生成策略是序列化表,所以select
引入子标签selectKey
<insert id="insertUser" >
<selectKey resultType="int" keyProperty="id" order="BEFORE">
select SEQ_USER_ID.nextval as id from dual
</selectKey>
insert into user (id,name,password) values (#{id},#{name},#{password})
</insert>
selectKey相关属性
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表 |
resultType | 结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。 |
order | 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用 |
statementType | 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型 |
这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化. 比如:
<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>
使用 #{}
格式的语法会导致 MyBatis 创建 PreparedStatement
参数并安全地设置参数(使用 ?
)。不过有时需要在 SQL 语句中插入一个不转义的字符串。比如 ORDER BY 就需要使用${}
Mybatis默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强处理循环。开启二级缓存需要加入cache
标签
cache
的效果如下:
<!--eviction:回收策略,flushInterval:刷新间隔,默认不设置,size:引用数目,默认1024,readOnly:返回对象是否只读(保证安全性),默认false-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
收回策略有:
LRU
– 最近最少使用的:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK
– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。使用cache-ref
标签贡献缓存配置实例
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
第一步:引入maven依赖
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>版本号</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>版本号</version>
</dependency>
第二步:配置ehcache配置文件
<!--name很重要,必须和Mapper的命名空间对应-->
<cache name="com.kun.xxx.PersonMapper" maxElementsInMemory="1024" external="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true"/>
第三步:在mybatis配置文件中配置别名
<typeAlias type="org.mybatis.caches.ehcache.EhcacheCache" alias="ehcache" />
第四步:在需要引用缓存的Mapper文件中引用
<cache type="ehcache"/>
resultMap子元素
使用constructor
子标签 ,注意多个参数,需要在POJO类构造函数上使用@Param指定名称
<resultMap type="com.kun.start.model.Person" id="person">
<constructor>
<idArg column="id" name="id"/> <!--指定主键提高效率-->
<arg column="name" name="name"/>
</constructor>
</resultMap>
使用association
子标签,进行多对一或者一对一关联查询
,直接映射结构
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
使用association
子标签,进行多对一或者一对一关联的嵌套查询
,这种写法可能引起“N+1”问题,可以使用懒加载或关联的嵌套结果查询
<!--查询博客的作者-->
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
使用association
子标签,进行多对一或者一对一关联的嵌套结果查询
,避免了“N+1”问题
<!--查询博客的作者-->
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>
<resultMap id="authorResult" type="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"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
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
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
使用collection
子标签,进行集合的嵌套查询
,注意ofType指的是元素类型,javaType能推测出来可以不写
<!--查询博客下有多少文章-->
<resultMap id="blogResult" type="Blog">
<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
使用collection
子标签,进行集合的嵌套结果查询
,避免多次发送SQL,提高效率
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>
使用discriminator
子标签完成鉴别器工作,相当于java中的switch语句。一般用于同表描述继承关系(不建议使用)
<!--根据鉴别字段的值自动映射成对应的子类-->
<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"/>
<!-- javaType:返回的值类型(用于比较的列)-->
<discriminator javaType="int" column="vehicle_type">
<!-- resultType:对应哪个子类结果集,result子标签:子类特有属性 -->
<case value="1" resultType="carResult">
<result property="doorCount" column="door_count" />
</case>
<case value="2" resultType="truckResult">
<result property="boxSize" column="box_size" />
<result property="extendedCab" column="extended_cab" />
</case>
<case value="3" resultType="vanResult">
<result property="powerSlidingDoor" column="power_sliding_door" />
</case>
<case value="4" resultType="suvResult">
<result property="allWheelDrive" column="all_wheel_drive" />
</case>
</discriminator>
</resultMap>
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句
<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>
用于解决where后跟无意义条件例如where 1=1
问题
<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解决select语句问题,set解决update语句问题
<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 语句的后面留下这些逗号
where和set底层都是使用trim。prefix/suffix-加入的前(后)缀,prefixOverrides/suffixOverrides-前后覆盖的字符
<!--相当于set标签-->
<trim prefix="set" prefixOverrides="," suffixOverrides=",">
...
</trim>
<!--相当于where标签-->
<trim prefix="where" prefixOverrides="and|or" suffixOverrides=",">
...
</trim>
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:
<select id="selectPersonIn" resultType="com.kun.Person">
SELECT * FROM Person WHERE ID in
<foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
#{item}
</foreach>
</select>
item-变量集合去除的元素,index-元素的索引,collection-集合参数名称。
集合类型可以是list、set、map的key、map的value。【index 作为map 的key。item为map的值】,例如:
<foreach collection="ent" item="id" separator="," open="(" close=")">
#{id}
</foreach>
可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值。
<select id="getEmps" resultType="com.hand.mybatis.bean.Employee">
<bind name="bindeName" value="'%'+eName+'%'"/> <!--eName是employee中一个属性值-->
SELECT * FROM emp
<if test="_parameter!=null">
where ename like #{bindeName}
</if>
</select>