映射器是MyBatis的核心组件,由一个接口和对应的XML文件组成。在映射器中可以配置参数、SQL语句、存储过程、缓存、级联等内容,可以通过映射规则映射到相应的实体对象上,相对于JDBC,节省了大量的底层代码。
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
mapper>
映射器内部常见的配置元素如下:
select:查询语句,返回结果集
insert:插入语句,返回受影响条数
update:更新语句,返回受影响条数
delete:删除语句,返回受影响条数
sql:定义一部分的sql,然后在其他语句引用。比方说定义数据库的字段,在语句中使用 include 引用
resultMap:描述如何从数据库结果集中加载对象
cache:该命名空间的缓存配置。
cache-ref: 引用其它命名空间的缓存配置。
在mapper映射器中有两种获取值的方法:
${}:本质是字符串拼接,使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号
#{}:本质是占位符赋值。使用占位符赋值的方式拼接sql,为字符串类型或日期类型的字段进行赋值时,可以自
动添加单引号
sql元素时可以定义一条sql语句的一部分,方便后续的sql引用,最常用的就是设置一套数据库字段名的sql。
<sql id="Base_Column_List">
user_id, username, `password`, deleted
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select <include refid="Base_Column_List" />
from `user`
where user_id = #{userId,jdbcType=INTEGER}
select>
查询语句是使用最多的语句,它内部常用配置如下:
| 元素 | 说明 | 备注 |
|---|---|---|
| id | 和 mapper 的 namespace 组合使用,用来对应接口中的方法名 | 需要唯一,否则会出现异常 |
| parameterType | sql 语句接受的参数类型。 | 支持基本数据类型和 JavaBean、Map 等复杂数据类型 |
| resultType | sql 语句返回的结果类型(字段名和属性名完全一致时使用) | 如果是全表返回对应的实体类,如果是单独一条字段返回对应的数据类型 |
| resultMap | sql 语句返回的结果类型(当数据库的字段和类的属性不同时,使用其设置,将其进行转换) | 和resultType之间只能同时使用一个,是最复杂的元素,需要配置映射规则、级联等等 |
| flushCache | 将其设置为 true 后,在调用SQL后,清空本地缓存和二级缓存 | 默认值:false。 |
| useCache | 启动二级缓存,将本此结果缓存起来 | 默认值: true。 |
| timeout | 超时参数,超时抛出异常 | 单位 s,默认JDBC设置的秒数 |
| fetchSize | 获取记录的总条数设置 | |
| statementType | 告诉MyBatis使用那个JDBC的Statement工作 | STATEMENT:对应Statement PREPARED(默认) :对应PreparedStatement CALLABLE:对应CallableStatement |
| resultSetType | 对JDBC的resultSet而言, | FORWARD_ONLY(只允许向前访问) SCROLL_SENSITIVE(双向滚动,但不及时更新) SCROLLJNSENSITIVE(双向滚动,及时更新) |
| databaseId | 配合配置文件中databaseIdProvider使用,配置所使用的的数据库 |
//接口的方法
User selectByPrimaryKey(int id);
<select id="selectByPrimaryKey" resultType="user" parameterType="java.lang.Integer">
select <include refid="Base_Column_List"/>
from `user`
where id=#{id}
select>
MyBatis的自动映射是默认开启的,只要实体类名和属性与数据库名和字段对应就会形成自动映射。这个设置在xml配置文件的settings中,autoMappingBehavior 是自动映射,mapUnderscoreToCamelCase 是驼峰式命名。
使用的不多,在sql语句中传递参数时,需要知道map 的键才能传递参数。
int selectByMap(Map<String,Object> district);
<select id="selectByMap" resultType="" parameterType="map">
SELECT i<include refid="Base_Column_List"/>
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
select>
Map<String,String> map = new HashMap();
map.put("name","aaa");
map.put("url","11111");
districtMapper.insertDistrict(map);
使用 @Param注解去定义映射器的参数名称,可以提高阅读性,当参数少时建议使用。
//接口设置方法
List<User> selectByPrimaryKey(@Param("username")String username,@Param("note")String note);
<select id="selectByPrimaryKey" resultType="user">
select
<include refid="Base_Column_List">include>
from user where username=#{username} and note=#{note}
select>
通过传递实体类,映射器可以识别属性,当参数多时,建议使用
//接口设置方法
List<User> selectByPrimaryKey(User user);
<select id="selectByPrimaryKey" resultType="user" parameterType="user">
select
<include refid="Base_Column_List"/>
from user where username=#{username} and sex=#{note}
select>
RowBounds是用来处理分页的
mapper接口指定方法
List<User> selectByRowBounds(RowBounds rowBounds);
xml映射文件,RowBounds是MyBatis自动识别的,所以在映射文件中不需要配置这块的内容
<select id="selectByRowBounds" resultType="user">
select <include refid="Base_Column_List"/> from `user`
select>
test测试:前五条数据
RowBounds rowBounds = new RowBounds(0,5); (起始,每页数量)
List<User> list = userMapper.findByRowBounds(rowBounds);
System.out.println(list);
insert(增)、update(改)、delete(删)三者很接近,主要有以下属性
| 元素 | 说明 | 备注 |
|---|---|---|
| id | 和 mapper 的 namespace 组合使用,用来对应接口中的方法名 | 需要唯一,负责会出现异常 |
| parameterType | sql 语句接受的参数类型。 | |
| flushCache | 将其设置为 true 后,在调用SQL后,清空本地缓存和二级缓存 | 默认值:false。 |
| timeout | 超时参数,超时抛出异常 | 单位 s,默认JDBC设置的秒数 |
| statementType | 告诉MyBatis使用那个JDBC的Statement工作 | STATEMENT:对应Statement PREPARED(默认) :对应PreparedStatement CALLABLE:对应CallableStatement |
| useGeneratedKeys | 是否使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键, | 默认false |
| keyProperty=“id字段名” | 在新增、修改的时候可以获得修改的那条数据的 主键 | (insert 和 update) |
| keyColumn | 设置生成键值在表中的列名,当主键列不是表中的第一列的时候,是必须设置的 | ( insert 和 update) |
void insertUser(User user);
<insert id="insertUser" parameterType="user">
insert into user(username, password,sex) values (#{username},#{password},#{sex})
insert>
我们在新增时并没有插入主键,因为主键采用了自增主键,所以mysql会自动生成主键,但是很多时候我们需要获取到这条主键以进行其他的操作,这个就是主键回填
<insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into user(username, password,sex)
values (#{username},#{password},#{sex})
insert>
userMapper.insert(user);
sqlSession.commit();
System.out.println(user.toString());

在使用自增主键时不需要去管主键,但是如果主键的规则是我们自己设定的,比如初始为1,每次自增2。
这时可以使用selectKey标签
<insert id="insertUser" parameterType="user">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
select if(max(id)=null,1,max(id)+2) from `user`
selectKey>
insert into user(id,username, password,sex)
values (#{id},#{username},#{password},#{sex})
insert>
update和delete的使用和insert类似
<update id="updataById">
update user set user.usernam=#{username},user.password=#{password} where id=#{id}
update>
<delete id="deleteById" parameterType="user">
delete from user where id=#{id}
delete>
它是用来定义映射规则、级联、类型转化等等,是最复杂的一块。比如:A表中有B表的id字段,在建实体类时属性会创建b的对象,而不是只是一个id属性,这就需要resultMap来定义映射的规则。
MyBatis在association、collection关联集合对象时支持延迟加载,即在需要使用数据时才进行加载,不用数据时就不进行加载。(一对多,多对多采用延迟加载。一对一、多对一采用立即加载)
不能和resultType共用。
<resultMap id="" type="" >
<constructor>
<idArg/>
<arg />
constructor>
<id />
<result/>
<association property=""/>
<discriminator javaType="">
<case value="">case>
discriminator>
<collection property=""/>
resultMap>
使用 id、result 配置映射关系,数据库的字段和属性值的类型能对应上,可以只是名字上的不同,没有复杂的级联关系。
<resultMap id="userMap" type="user">
<id column="字段" property="属性" javaType="java类型" jdbcType="数据库类型" typeHandler="类型转换器"/>
<result column="字段" property="属性" javaType="java类型" jdbcType="数据库类型" typeHandler="类型转换器"/>
resultMap>
<resultMap id="userMap" type="user">
<id column="字段名" jdbcType="INTEGER" property="属性名" />
<result column="字段名" jdbcType="VARCHAR" property="属性名" />
.......
resultMap>
<select id="selectByPrimaryKey" resultMap="userMap">
select>
association 和 collection 主要是用来处理级联关系的映射,有 3 种级联关系:
在数据中表A保存了表B的id,这是其对应的实体类的属性,必须是B的实体类,所以简单的映射无法满足,需要使用association进行配置,它配置的是一个对象,用来表示一对一的映射关系。
| 属性 | 说明 |
|---|---|
| property | 指定映射到实体类的对象属性。 |
| column | 指定表中对应的字段 |
| javaType | 指定映射到实体对象属性的类型。 |
| select | 指定引入嵌套查询的子 SQL 语句,用于关联映射中的嵌套查询。 |
association 分步查询
Student的实体类
//学生表中有班级id。实体类中创建班级的实体属性
private Integer sId;
private String name;
private Classes classes; //数据中保存的是班级id
Student实体类对应的xml映射器,它是分两部,先通过select语句查出Student的信息后,再通过association设置的select方法差相关的班级,再将班级的值添到对应的属性上
<resultMap id="BaseResultMap" type="com.yu.mybatis.entity.Student">
<id column="s_id" jdbcType="INTEGER" property="sId" />
<result column="student_name" jdbcType="VARCHAR" property="name" />
<association property="classes" javaType="com.yu.mybatis.entity.Classes" column="classes_id"
select="com.yu.mybatis.mapper.ClassesMapper.selectByPrimaryKey"/>
resultMap>
<select id="selectStudentAll" resultMap="BaseResultMap">
select * from student
select>
classes实体对应的映射器
<select id="selectClassesById" resultMap="BaseResultMap">
select * from classes where c_id = #{id}
select>
执行了两次 sql

也可以在 association 的内部定义:这种定义时通过一个SQL直接查询出来的数据,不需要第二次再去查询了
<resultMap id="BaseResultMap2" type="com.yu.mybatis.entity.Student">
<id column="s_id" jdbcType="INTEGER" property="sId" />
<result column="student_name" jdbcType="VARCHAR" property="studentName" />
<association property="classes" javaType="com.yu.mybatis.entity.Classes">
<result column="c_id" property="CId"/>
<result column="classes_name" property="classesName"/>
association>
resultMap>
<select id="selectByClassesId" parameterType="java.lang.Integer" resultMap="BaseResultMap2">
SELECT * FROM student,classes
WHERE student.classes_id = classes.c_id AND student.s_id = #{sId,jdbcType=INTEGER}
select>
只执行一次sql

一对多的映射关系使用collection处理,collection 可以将关联查询的多条记录映射到一个 list 集合属性中。和association使用方式类似
| 属性 | 介绍 |
|---|---|
| property | 指定映射到实体类的对象属性。 |
| column | 指定表中对应的字段 |
| select | 指定引入嵌套查询的子 SQL 语句 |
| javaType | 指定映射到实体对象属性的类型 |
分步查询:这种查询是先根据select的方法查询出数据后,再次根据resultMap中设置的对应select方法查询
//班级内很多学生
private Integer cId;
private String name;
private List<Student> studentList;
<resultMap id="BaseResultMap2" type="classes">
<id column="c_id" jdbcType="INTEGER" property="cId"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<collection property="studentList" ofType="student">
<result column="s_id" property="SId"/>
<result column="s_name" property="name"/>
collection>
resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap2">
select
<include refid="Base_Column_List" />
from classes
where c_id = #{cId,jdbcType=INTEGER}
select>
这种查询是分了两步的,所以执行了两次SQL语句

内部定义
<resultMap id="BaseResultMap3" type="com.yu.mybatis.entity.Classes">
<id column="c_id" jdbcType="INTEGER" property="cId" />
<result column="classes_name" jdbcType="VARCHAR" property="classesName" />
<collection property="students" ofType="com.yu.mybatis.entity.Student">
<result column="s_id" property="SId"/>
<result column="student_name" property="studentName"/>
collection>
resultMap>
<select id="selectClassesAndStudent" parameterType="java.lang.Integer" resultMap="BaseResultMap3">
select <include refid="Base_Column_List" />,s_id,student_name
from student,classes
where student.classes_id = classes.c_id AND classes.c_id = #{CId,jdbcType=INTEGER}
select>
这种方式SQL语句只执行了一次

动态SQL是对sql语句进行动态的组装, 是 MyBatis 的强大特性之一。它可以通过判断自动选择要添加的sql语句,主要有以下元素:
类似于 Java 中的 if 语句,是 MyBatis 中最常用的判断语句。使用 if 标签可以节省许多拼接 SQL 的工作,把精力集中在 XML 的维护上。
if 语句使用方法简单,常常与 test 属性联合使用
<select id="" parameterType="string">
select id,username,password,sex from user where 1=1
<if test="id != null and id != ''">
and id=#{id};
if>
<if test=" name != null and name !=''">
and username=#{name};
if>
select>
choose、when、otherwise类似于java中的Switch、case、default的功能
<select id="" parameterType="string">
select id,username,password,sex from user where 1=1
<choose>
<when test="username != null and username != ''">
and user.username = #{username}
when>
<when test="password != null and password != ''">
and user.password = #{password}
when>
<otherwise>
and user.sex = '男'
otherwise>
choose>
select>
上面的几种情况可能会出现where后面没有条件的情况,所以在where后加了一句 1=1,来保证sql语句的正确性,但是我们期望的是where关键字根据条件而自己决定出现不出现,所以就有了,trim、where、set元素
where:至少有一个子元素成立,才会添加where元素,并且第一个条件的and、or元素会自动去除
<select id="" parameterType="string">
select id,username,password,sex from user
<where>
<if test="usernae != null and usernae != ''">
user.username = #{username}
if>
<if test="password != null and password != ''">
and user.password = #{password}
if>
where>
select>
set:用于动态设置update语句中的set条件,可以删除无关的 “,”
<update id="">
update user
<set>
<if test="usernae != null and usernae != ''">
user.username = #{username},
if>
<if test="sex != null and sex != ''">
user.sex = #{sex},
if>
set>
where id=#{id}
update>
trim:可以代替where和set
<trim prefix="where" prefixOverrides="and">
where是前缀的时候,会去掉and
trim>
<trim prefix="set" suffixOverrides=",">
set是前缀的时候,会去掉,
trim>
循环遍历,可以很好的支持数组、list、set集合。用于sql语句的 in 关键字。
<select id="" resultType="">
SELECT * from role where id in
<foreach item="id" index="index" collection="list" open="(" separator="," close=")">
#{id}
foreach>
select>
对一些常用的且不经常改动的数据进行缓存,能有效的避免频繁的连接数据库,一定程度上解决高并发情况下的系统性能问题。
MyBatis允许使用缓存, 内部定义了两级缓存,和一个缓存接口:
一级缓存是默认开启的,作用域为SqlSession,当一个SqlSession对象执行同一条SQL语句时,第一次是在会去查询数据库,并将其写在缓存中,后续的就直接在缓存中加载。(每次查询时现在缓存中找,缓存中没有就去数据库中查询,并将数据卸载缓存中)
当两次SQL查询时,发生了增删改的操作时,缓存会清空。
SqlSession的缓存使用 HashMap,key为hashCode+statementId+sql语句,value为映射的结果集。
一级缓存从获取一个SqlSession到close方法关闭之间有效,默认开启,无法关闭。

缓存失效:
二级缓存是 SqlSession 级别的,对应一个Mapper映射文件,需要在映射文件中自行设置开启,所以二级缓存存在于所在的整个映射文件中。
第一次调用Mapper内的SQL查询时,数据会存放到对应的二级缓存区。第二次调用namespace下的mapper映射文件,向的sql会去对应的二级缓存区查找。
开启方法:
在mybatis-config.xml 文件中的settings中打开全局缓存
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
1、在所需要的mapper.xml文件中开启二级缓存
<cache/> 默认的
<cache eviction = "FIFO" flushInterval = "60000" size = "512" readOnly = "true" /> 自定义的
eviction:缓存回收策略
LRU – 最近最少使用:删除最长时间未使用的对象。
FIFO – 先进先出:按照对象进入缓存的顺序删除对象。
SOFT – 软引用:根据垃圾收集器状态和软引用规则删除对象。
WEAK – 弱引用:根据垃圾收集器状态和弱引用规则更积极地删除对象。
flushInterval属性:刷新间隔,单位毫秒
size属性:引用数目,正整数,代表缓存最多可以存储多少个对象
readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例,效能最高。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。效率稍慢但是安全,默认是false。
2、在mapper接口上添加注解
@CacheNamespace(blocking = true)
如果使用默认的配置,readOnly 默认为false,所以这个时候MyBatis会序列化和反序列化实体类,所以实体类需要实现Serializable接口。否则将会抛出异常
二级缓存必须在SqlSession关闭或提交之后有效
一级缓存和二级缓存的顺序:先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。如果二级缓存没有命中,再查询一级缓存,如果一级缓存也没有命中,则查询数据库,SqlSession关闭之后,一级缓存中的数据会写入二级缓存。
Mybitis 除了内置了缓存外,还可以引入第三方的缓存,而EHCache 就是实现 mybatis 二级缓存的产品之一。它是一种广泛使用的开源Java分布式缓存,具有快速、简单、多种缓存策略,缓存数据有内存和硬盘的特点。
使用ehcache需要注意的是,ehcache的依赖 slf4j 这个日志的jar包,会和log4j的jar冲突,导致日志不能显示,所以需要整合他们,导入联合jar包
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.1version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.26version>
dependency>
然后在sql映射文件中的mapper标签下配置开启ehcace缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
在mybatis-config.xml 文件中的settings中打开全局缓存
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
创建ehcache的配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="D:\java\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
ehcache>
可以直接运行测试: