一、mybatis的作用,到底是干什么的?
主要作用就是替代老旧的JDBC代码以及设置参数获取结果的冗余代码。它的本质就是对JDBC进行封装,类似于我们常见的各类template组件。总之一句话,是一个优秀的持久层框架,灵活、简单、高效。
二、关键组件及作用,里面到底有什么东西?
1)SqlSessionFactory
它能产生SqlSession,由SqlSessionFactoryBuilder的build方法构建而成。
那么SqlSessionFactoryBuilder是怎么产生的,是从通过new对象的方法产生的,同时需要传入配置参数的输入流。样例如下(这个是官网的的样例):
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2)SqlSession
它能执行各种通过映射文件传过来的sql,由SqlSessionFactory产生。样例如下:
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
为什么要加在try块里面,因为这样我们就不需要手动关闭了。
3)作用域(Scope)和生命周期
SqlSessionFactoryBuilder 这个就是用来创建SqlSessionFactory,一旦创建完成就不需要了。因为大多数场景的情况下,我们项目一般都只有一个数据源,且对应的业务也是相通的。既然通过build方法拿到了SqlSessionFactory,你就可以通过SqlSessionFactory打开多个opensession来执行sql就行了。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。简单来说,这个工厂类就类似于我们常用的连接池,在应用运行期间需要不断的产生opensession和回收opensession,所以在应用运行期间它是一直存在的。
SqlSession 作用域是请求或方法作用域。简单的说,如果你开启了一个事务,那么在它进行commit或者rollback之后,当前的这个session就应该被安全的关闭。如果你没有开启事务,那么你执行的那条sql执行完成后,就应该被安全的关闭,它是跟着sql执行走的。
三、到底有哪些配置
这个配置就是说SqlSessionFactoryBuilder在build时候传入进来的配置文件的文件流,具体的作用就可以配置数据源、mybatis自身运行行为的相关参数、类型别名、类型处理器、环境配置、映射器等。等于就是说,我要new这个SqlSessionFactoryBuilder对象,那么这个对象如何构建起来的,需要通过这个mybatis核心配置文件来支撑构建。下面就一个一个来。
首先这些配置都在一个配置文件里面,文件名叫mybatis-config.xml,一般就是这个名字,当然名字你也可以自定义。
1)属性(properties)作用就是定义各类参数,然后在xml文件中引入这个properties文件来进行变量赋值
分两种,一种就是你自己新建一个.properties文件,另外一种在xml文件中通过<properties>标签来进行定义。样例如下
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
2)设置(settings)
这个就是来定义mybatis在运行期间的行为,比如是否开启懒加载,是否开启缓存,是否允许自动生成主键等等。里面定义的参数特别多,我的建议是不要每个都要了解,知道里面几个关键的就行了,其他想要知道的,可以直接去官网查。下面列举几个我认为比较重要的。
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 默认值 true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType
属性来覆盖该项的开关状态 默认值是false。
这个参数简单解释下,就是说你开启了之后,如果你在resultmap里面通过collection或者association搞了多对一或者一对多的级联查询时,如果你当前的sql里面压根查不到级联对象里面的值,那么这段sql就不会执行。当然你也可以通过fetchType来更细粒度的控制是否执行懒加载。
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。默认为false。这个参数建议不要深究,默认值是false就满足你要求了。因为如果你开启了全局懒加载,可以在resultmap中的collection或者association通过fetchtype来进行细粒度的控制懒加载的方式,一般就满足你的业务需求了。
useGeneratedKeys 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby) 。
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
这个属性的作用就是你的实体对象中属性的名字叫bigData,但是数据库里面字段的名字叫big_data,这样的话属性和字段就没法关联映射上了。设置为true之后就可以映射了。但是我仍然建议你忽略这个参数,最好的办法就是通过配置resultmap或者在sql语句中用别名就搞定了。
3)类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在简化冗余的全限定类名书写。
这个配置的作用就是在select标签中定义的reslutType,javaType等属性值需要设置一个java实体对象时,可以直接使用别名,方便书写。样例如下:
这个是一个类一个类设置
<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>
这个是包路径设置,(建议采用这种方法,这样实体对象都可以使用别名了。要么就不要设置直接用全限定名)
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
4)类型处理器(typeHandlers)
这个就是数据库中字段类型需要转型才能和实体对象的属性类型进行映射,mybatis已经提供了很多类型处理器,你也可以自己自定义类型处理器,只要继承BaseTypeHandler<String>实现里面的方法即可。
5)环境配置(environments)
这个配置的意思就是说,首先当你程序运行的时候,你需要指定一个运行环境,比如下面样例中,程序应该用哪个数据源,用哪种事务管理方式都在这里面配置。
样例如下:
<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>
6)映射器(mappers)
建议直接使用这种用包名的方式,意思就是将如下包名下面的接口全部注册成映射器。
这里稍微提一嘴,当然也是我自己的理解。这么配置之后,mybatis这个所谓的半自动化映射框架才真正的起作用。也就是说它把我们定义的,比如UserMapper.java的接口类和UserMapper.xml通过java代理模式动态生成一个bean对象,这个对象里面的方法就是接口里面的方法,这个对象方法的具体实现就是xml文件里面配置的各项sql执行语句。这样才能真正的通过对象来实现对象方法。
样例如下:
<!-- 将包内的映射器接口全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
四、XML 映射器
这个是mybatis最重要最核心的地方,也是你实际开发中写sql的地方。首先你写了一个XXXMapper.java定义了一个接口,然后和这个接口映射的实现类就是这个XXXMapper.xml文件,很重要,但也简单,我们来一个一个看。
1)这里面主要的标签有哪些,分别有什么作用
cache
– 该命名空间的缓存配置。 你只要配置<cache/
>,那么就代表了这个xml接受缓存控制,这个一般是用来设置二级缓存用的。因为一级缓存默认生效。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
需要强调的是上面的增删改查标签都必须要有id,这个id的值必须跟接口mapper的方法名一致,这样才能映射上。
2)select元素
主要标签值
id :跟mapper接口的方法名一致,加上包名要保证全局唯一。
parameterType:就是传递给sql语句的参数的类型。可以是java基本类型,也可以是map,也可以是java实体对象。
resultType:可以是java基本类型,也可以是java实体对象。
resultMap:这个就是整个映射文件最复杂的配置,它对应的是一个自定义的对应关系map,将java对象属性和数据库字段进行对应(这个文章后面会在详细描述)。
样例如下:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
3)insert, update 和 delete元素
返回值都是sql影响的条数,为整数。这三个里面唯一有点不一样的就是insert,因为insert涉及到主键自增,样例如下:
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
解释下,useGeneratedKeys=true就是开启主键自增,keyproperty="id",意思就是说自增的字段名字叫id。就这么简单。
下面的样例是增删改的样例,
<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>
4)sql 片段,这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值
样例如下:
<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>
5)结果映射
resultMap
元素是 MyBatis 中最重要最强大的元素。
a)先直接上一个简单的样例,其实在实际应用过程中已经满足大部分要求了。
这是一个简单的resultMap配置,类型是Uer对象,然后将属性和字段对应起来。再次强调下,mybatis是半自动映射持久化框架。它干的主要一件事情就是将数据库里面查询出来的结果集通过java对象进行映射,然后将数据展示出来。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
下面是实际的查询语句
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
b) 一对多的结果集映射,意思就是类似于1个班级对应多个学生
<resultMap id="classResult" type="Class">
<collection property="Student" javaType="ArrayList" ofType="Student" select="selectStudentsForClass"/>
</resultMap>
c) 多对一的结果集映射,意思就是类似于多个学生对应1个班级
<resultMap id="StudentResult" type="Student">
<association property="Class" javaType="Class" select="selectClassForStudent"/>
</resultMap>
五、动态sql
动态sql就是根据传递过来的参数值的情况,灵活配置sql,从而达到业务需要,主要有以下几个主要标签
1、if 根据值是否为空,来决定是否要拼接上sql
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
这段xml 这么写是没有问题的,但是有一点就是如果where条件后面没有state这个查询条件,而且下面的判断条件都为空的情况下,就会出现sql语句中只有这个where,造成sql语句错误,需要注意。
2、choose when otherwise
下面的配置类似于if else if else 这样的判断语句结构,就是说在<choose>这个标签之下,会至少产生一段拼接上的sql。
<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>
3、trim
trim在拼接sql中的主要作用就是能够根据设定的前缀和后缀规则来增加或者删除相应符号
trim 标签可以根据需求组织sql,可以代替where标签或者是set标签
prefix:在trim标签内sql语句加上前缀。
suffix:在trim标签内sql语句加上后缀。
prefixOverrides:指定去除多余的前缀内容
suffixOverrides:指定去除多余的后缀内容,
4、where
正如第1条的注意事项,如果没有state这个搜索条件,那么语句就会变成
SELECT * FROM BLOG WHERE
而这明显是一个不正确的sql语句。而where标签恰好的解决这个问题,就是如果where 后面没有任何条件内容,则不显示where。样例如下:
<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标签会过滤到如果第一个条件为空,而后面的条件生效的情况下,就会出现 where 和and 在一起的情况,where标签会自动过滤这种情况,但是这仅过滤and或者or在条件之前的情况。如果在条件判断的后面则不会自动过滤,需要使用第3个trim来解决。
5、set
<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>
可以用trim标签来代替上面的set标签
<trim prefix="SET" suffixOverrides=",">
<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>
</trim>
6、foreach
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
稍微解释一下上面的语句,
item 就是每次循环的得到的实体
index 就是实体的索引
collection 就是循环本身集合
open 就是拼接这个循环得到的元素的前缀
separator就是循环得到的实体用逗号拼接
close就是拼接这个循环得到元素的后缀
nullable 就是是否可以为空
上面的sql最终得到的sql为 select * from post p where
id in (item1,item2)
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
7、bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
六、缓存
1、一级缓存 默认开启 sqlsession级别
就是sqlSession第一次查询sql得到结果集,那么第二次就查询了同样的sql则使用了缓存,不会去查询数据库。也就是同一个sqlsession在没有被打断的情况下,连续的查询同一个sql是从缓存里面查询数据的。
所以:
1)、如果两次查询之间,进行了增删改
2)、如果两次查询的字段一样, 但是条件不一样
3)、手动清空了缓存
都会导致不会命中缓存。
2、二级缓存 默认未开启,sqlsessionfactory级别
首先要明确一点,sqlSessionFactory是产生sqlSession的,它既然作为二级缓存,顾名思义,就是他的作用域要大于sqlSession。所以如果开启了二级缓存,sql查询会先从二级缓存查询,然后才会到sqlsession中查询。
开启需要满足4个条件:
1)、在核心配置文件中mybatis-config.xml文件中,设置参数cachedenabled 开始状态
2)、在XXXmapper.xml文件中,设置<cached/>标签
3)、java实体对象要实现序列化
4)、只有在sqlSession实现commit或rollback之后才会生效
七、总结
mybatis作为当前比较主流的半自动化映射框架,能够灵活的配置sql,使得代码开发更加简洁易懂,是随着互联网发展起来的持久化框架。
开发经验总结:
- 参数传递
在mapper接口中定义的方法参数设置,最好遵循下面的原则
1)、可以通过map传递,但是可读性比较低
2)、可以通过在方法中通过@param(“参数名”),适合参数的个数小于等于5个
3)、可以通过javabean方法传参,在javabean中编写属性的getseter和setter方法即可。
- 别名设置
设置别名会是的xml文件方便阅读,建议使用,在核心配置文件中配置实体类包即可。
- 模糊查询
1)、like '%${参数名}%'
2)、like concat('%',#{参数名},'%')
3)、like "%"#{参数名}"%"
但是冥冥之中,我有一种预感,类似于Heibernate那种从数据库映射自动生成对象的全自动持久化框架会在未来5-10年左右会卷土从来!