MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
工作原理
每个基于Mybatis的应用都是以一个SqlSessionFactory的实例为中心的,可通过SqlSessionFactoryBuilder获得(通过XML配置文件构建获得),然后从SqlSessionFactory中获取SqlSession实例,可通过该实例直接执行已映射的SQL语句,但现在更多的是通过mapper接口。
SqlSessionFactoryBuilder的作用域为方法作用域(局部方法变量),因为一旦创建了SqlSessionFactory便不再需要。
SqlSessionFactory应该在运行期间一直存在,即应用作用域,使用单例模式或静态单例模式。
SqlSession每个线程都该有它自己的SqlSession实例,它不是线程安全的,因此不能共享,最佳作用域是请求或方法作用域,每次收到HTTP请求,打开一个SqlSession,返回一个响应就关闭它,要把关闭操作放在finally中以确保一定关闭。
Mapper Instances映射器实例从SqlSession中获取,所以最大的作用域和请求他们的SqlSession相同,最佳的为方法作用域。
增删改查的简单示例代码
- resultmap
<resultMap id="BaseResultMap" type="StudentInfo"> <id column="id" jdbcType="INTEGER" property="id"/> <result column="student_name" jdbcType="VARCHAR" property="studentName"/> <result column="student_age" jdbcType="INTEGER" property="studentAge"/> </resultMap>
你的查询返回结果往往需要一个JavaBean或POJO,这时候需要resultmap,比如需要的是一个StudentInfo的POJO,它的属性有三个,id,studentName,studentAge,其中<id>栏为studentinfo的标识属性,而<resultMap>中的id是作为resultmap的唯一标识。resultmap有一些标签如association,collection.
- select
<select id="selectStudentById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
SELECT * FROM studentTable WHERE id = #{id,jdbcType=INTEGER}
</select>
- insert
<insert id="insertStudent" parameterType="StudentInfo"
INSERT INTO studentTable
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="studentName != null">
student_name,
</if>
<if test="studentAge != null">
student_age,
</if>
</trim>
<trim prefix="VALUES(" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="studentName != null">
#{studentName,jdbcType=VARCHAR},
</if>
<if test="studentAge != null">
#{studentAge,jdbcType=INTEGER},
</if>
</trim>
</insert>
- update
<update id="updateStudentByCondition" parameterType="StudentInfo">
UPDATE studentTable
<set>
<if test="studentName != null">
student_name = #{studentName,jdbcType=VARCHAR},
</if>
<if test="studentAge != null">
student_age = #{studentAge,jdbcType=INTEGER},
</if>
</set>
WHERE id = #{id,jdbcType=INTEGER}
</update>
- delete
<delete id="deleteById" parameterType="java.lang.Integer">
DELETE FROM studentTable
WHERE id = #{id,jdbcType=INTEGER}
</delete>
其中studentName是StudentInfo中的属性,student_name是存在数据库中的属性栏的名称。
mybatis的一些高级属性
- sql:用于定义可重用的sql代码段,可以包含在其他语句中,可以被用在include元素的refid属性里。
<sql id="Base_Column_List"> id,student_name,student_age </sql>
<sql id="findCondition"> <where> <if test="id != null"> AND id = #{id,jdbcType=INTEGER} </if> <if test="studentName != null"> AND student_name = #{studentName,jdbcType=VARCHAR} </if> <if test="studentAge != null"> AND student_age = #{studentAge,jdbcType=INTEGER} </if> </where> </sql>
然后select语句可以写成如下:
<select id="selectStudentByCondition" parameterType="java.lang.Integer" resultMap="BaseResultMap"> SELECT <include refid="Base_Column_List"> FROM studentTable <include refid="findCondition"> </select>
这个select不再是根据ID来查询,而是根据查询条件来查询,这里的<where>标签会自动删除第一个AND。
- resultmap
<association>---关联元素处理一个POJO中的属性为另一个POJO的情况。
<resultMap id="BaseResultMap" type="StudentInfo">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="student_name" jdbcType="VARCHAR" property="studentName"/>
<result column="student_age" jdbcType="INTEGER" property="studentAge"/>
<association column="student_teacher" javaType="teacherInfo" resultMap="teacherResult">
</resultMap>
<resultMap id="teacherResult" type="teacherInfo">
<id column="id" jdbcType="INTEGER" property="id"/>
<id column="teacher_name" jdbcType="VARCHAR" property="teacherName"/>
</resultMap>
这时的select语句如下
<select id="selectStudentById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
SELECT
S.id,
S.student_name,
S.student_age,
T.id,
T.teacher_name
FROM studentTable AS S
LEFT OUT JOIN teacherTable AS T ON S.teacher_id = T.id
WHERE S.id = #{id,jdbcType=INTEGER}
</select>
<collection>---一个复杂类型的集合的关联,类似上面的association,但是是一个集合。如下是一个学生对应几篇文章,文章的student_id对应于学生的id。
public class PaperInfo {
public Integer id;
public Integer studentId;
public String paperName;
}
public class StudentInfo {
public Integer id;
public String studentName;
public Integer studentAge;
public List<PaperInfo> paper;
}
<resultMap id="BaseResultMap" type="StudentInfo">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="student_name" jdbcType="VARCHAR" property="studentName"/>
<result column="student_age" jdbcType="INTEGER" property="studentAge"/>
<collection column="id" javaType="ArrayList" property="paper" ofType="PaperInfo" select="selectPaperOfStudent"/>
</resultMap>
<select id="selectStudentById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
SELECT * FROM studentTable WHERE id = #{id,jdbcType="INTEGER"}
</select>
<select id="selectPaperOfStudent" resultType="PaperInfo">
SELECT * FROM paperTable WHERE student_id = #{id,jdbcType="INTEGER"}
</select>
上面是集合的嵌套查询,若不想用嵌套查询也可改用嵌套结果,即不使用collection的select属性。
<resultMap id="BaseResultMap" type="StudentInfo">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="student_name" jdbcType="VARCHAR" property="studentName"/>
<result column="student_age" jdbcType="INTEGER" property="studentAge"/>
<collection property="paper" ofType="PaperInfo">
<id column="paper_id" property="id"/>
<result column="paper_name" property="paperNme"/>
</collection>
</resultMap>
<select id="selectStudentById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
SELECT
S.id,
S.student_name,
S.student_age,
P.paper_id,
P.paper_name
FROM studentTable AS S
LEFT OUTER JOIN paperTable AS P ON P.student_id = S.id
WHERE S.id = #{id,jdbcType="INTEGER"}
</select>
- 缓存
Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache),还可以使用自己的缓存或其他第三方缓存。
每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询语句本身都会被保存在本地缓存中,那么,相同的查询语句和相同的参数所产生的更改就不会二度影响数据库了。本地缓存会被增删改、提交事务、关闭事务以及关闭 session 所清空。
默认情况下,本地缓存数据可在整个 session 的周期内使用,这一缓存需要被用来解决循环引用错误和加快重复嵌套查询的速度,所以它可以不被禁用掉,但是你可以设置 localCacheScope=STATEMENT 表示缓存仅在语句执行时有效。
注意,如果 localCacheScope 被设置为 SESSION,那么 MyBatis 所返回的引用将传递给保存在本地缓存里的相同对象。对返回的对象(例如 list)做出任何更新将会影响本地缓存的内容,进而影响存活在 session 生命周期中的缓存所返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。
动态SQL
根据不同条件拼接 SQL 语句十分痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。常用的有where,choose,trim,foreach.
- <where>前面有提及,可以避免条件都不成立时where为空和能自动删除第一个AND或OR,同样的<if>能自动在合适的情况下删除语句后面的逗号,这便是所谓的动态sql。
- <choose>类似于java中的switch,若查询条件包含paperName或id就根据条件查询,若都没有就返回所有studentId=1的paper。
<select id="findPaperLike" resultType="PaperInfo">
SELECT * FROM paperTable WHERE studentId = 1
<choose>
<when test="paperName != null">
AND paper_name like #{paperName}
</when>
<when test="id != null">
AND id like #{id}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
<select>
- <trim>有prefix,suffix,和suffixOverrides,可以添加和删除一些符号等,用法如下:
<insert id="insert" parameterType="StudentInfo"> INSERT INTO studentTable <trim prefix="(" suffix=")" suffixOverrides=","> <if test="studentName != null"> student_name, </if> <if> test="studentAge != null"> student_age, </if> </trim> <trim prefix="VALUES(" suffix=")" suffixOverrides=","> <if test="studentName != null"> #{studentName,jdbcType=VARCHAR}, </if> <if> test="studentAge != null"> #{studentAge,jdbcType=INTEGER}, </if> </trim> </insert>
报错:result maps collection already contains value for xxx
mybatis在创建SessionFactoryBean解析xml时候,会把xml中的resultMap放入到一个HashMap的子类StrictMap中,key是mapper的namespace与resultmap的id拼接成的。