Mybatis SQL映射文件详解

1
2
3
4
5
6
7
8
9
10
**映射文件中,可以编写以下的顶级元素标签**
cache – 该命名空间的缓存配置。
cache-ref – 引用其它命名空间的缓存配置。
resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
sql – 可被其它语句引用的可重用语句块。
insert – 映射插入语句。
update – 映射更新语句。
delete – 映射删除语句。
select – 映射查询语句。

增删改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--insert delete update分别用来标识不同类型的sql语句
id:用来标识跟dao接口中匹配的方法,必须与方法的名字一一对应上
flushCache:用来标识当前sql语句的结果是否进入二级缓存
statementType:用来选择执行sql语句的方式
statement:最基本的jdbc的操作,用来表示一个sql语句,不能防止sql注入
PREPARED: preareStatement:采用预编译的方式,能够防止sql注入,设置参数的时候需要该对象来进行设置
CALLABLE:调用存储过程
useGeneratedKeys:放完成插入操作的时候,可以将自增生成的主键值返回到具体的对象
keyProperty:指定返回的主键要赋值到哪个属性中
在编写sql语句的过程中,无论你是否配置了驼峰标识的识别setting,都需要在sql语句中写具体的表的属性名,不能写对象名称
-->
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into emp(empno,ename) values(#{empno},#{ename})
</insert>
<update id="update">
update emp set sal=#{sal} where empno = #{empno}
</update>
<delete id="delete">
delete from emp where empno = #{empno}
</delete>
1
2
3
public Integer save(Emp emp);
public Integer update(Emp emp);
public Integer delete(Integer empno);

select

基本语法

1
2
3
4
5
6
<!--在mybatis中,会包含非常多的查询操作,因此查询的参数比较麻烦
id:用来设置当前sql语句匹配的dao接口的方法,必须要跟方法的名字统一
resultType:表示返回的结果的类型,一般使用的并不多,此类型只能返回单一的对象,而我们在查询的时候特别是关联查询的时候,需要自定义结果集
当返回的结果是一个集合的时候,并不需要resultmap,只需要使用resulttype指定集合中的元素类型即可
resultMap:当进行关联查询的时候,在返回结果的对象中还包含另一个对象的引用时,此时需要自定义结果集合,使用resultmap
-->

参数传递

1
2
3
4
5
6
7
8
9
10
11
<!--参数的获取值的方式:
每次在向sql语句中设置结果值的时候,可以使用#{},还可以使用${}这样的方式,那么哪种比较好?
#{}:select * from emp where empno = ?
${}: select * from emp where empno = 7369
通过sql语句可以得出结论:
#{}的处理方式是使用了参数预编译的方式,不会引起sql注入的问题
${}的处理方式是直接拼接sql语句,得到对应的sql语句,会有sql注入的危险
因此,我们推荐大家使用#{}的方式
但是要注意,${}也是有自己的使用场景的?
当需要传入动态的表名,列名的时候就需要使用${},就是最直接的拼接字符串的行为
-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--当查询语句中包含多个参数的时候,我们应该如何获取需要的参数
1、如果是单个参数,
基本数据类型:那么可以使用#{}随便获取
引用数据类型:使用#{}获取值的是必须要使用对象的属性名
2、如果是多个参数:
我们在获取参数值的时候,就不能简单的通过#{}来获取了,只能通过arg0,arg1,param1,param2...这样的方式来获取参数的值
原因在于,mybatis在传入多个参数的时候,会讲这些参数的结果封装到map结构中,在map中key值就是(arg0,arg1,...)
(param1,param2...),这种方式非常不友好,没有办法根据属性名来获取具体的参数值
如果想要使用参数的话,可以进行如下的设置:
public List<Emp> selectEmpByEmpnoAndSal2(@Param("empno") Integer empno, @Param("sal") Double sal);
这样的方式其实是根据@Param来进行参数的获取
3、自定义map结构
-->

<select id="selectEmpByEmpnoAndSal" resultType="com.kuro.bean.Emp">
select * from emp where empno = #{emono}
</select>

<select id="selectEmpByEmpnoAndSal2" resultType="com.kuro.bean.Emp">
select * from emp where empno = #{empno} and sal >#{sal}
</select>

<select id="selectEmpByEmpnoAndSal3" resultType="com.kuro.bean.Emp">
select * from emp where empno = #{empno} and sal >#{sal}
</select>
1
2
3
public List<Emp> selectEmpByEmpnoAndSal(Emp emp);
public List<Emp> selectEmpByEmpnoAndSal2(@Param("empno") Integer empno, @Param("sal") Double sal);
public List<Emp> selectEmpByEmpnoAndSal3(Map<String,Object> map);

返回集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--当返回值的结果是集合的时候,返回值的类型依然写的是集合中具体的类型-->
<select id="selectAll" resultType="Emp">
select * from emp
</select>

<!--当需要返回的结果是一个map的集合的时候,同时map中包含多个对象,那么此时必须要在dao的方法上添加@MapKey注解,来标识到底是哪一个属性值作为key-->
<select id="selectAll2" resultType="Emp">
select * from emp
</select>

<!--当返回值是map结构的时候,会把查询结构的字段值作为key,结果作为value-->
<select id="selectEmpByEmpnoReturnMap" resultType="map">
select * from emp where empno = #{empno}
</select>
1
2
3
4
public List<Emp> selectAll();
@MapKey("ename")
public Map<String,Emp> selectAll2();
public Map<Object,Object> selectEmpByEmpnoReturnMap(Integer empno);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!--在使用mybatis的时候,有些情况下需要我们封装结果集,一般情况下mybatis会帮我们自动封装(字段名跟属性值必须一一对应),但是如果字段的值和类中的值
不匹配的时候,怎么处理?
1、可以在sql语句中添加别名字段,来保证赋值成功,但是太麻烦了,而且不可重用
2、resultMap:
-->

<mapper namespace="com.kuro.dao.DogDao">
<!--自定义结果集
id:表示当前结果集的唯一标识
type:表示当前结果集的类型
-->
<resultMap id="myDog" type="com.kuro.bean.Dog">
<!--
id:表示指定对应的主键
property:实体类中对应的属性值
column:表中字段的名称
-->
<id property="id" column="id"></id>
<!--除主键外的其他字段映射-->
<result property="name" column="dname"></result>
<result property="age" column="dage"></result>
<result property="gender" column="dgender"></result>
</resultMap>

<select id="selectDogById" resultMap="myDog">
select * from dog where id = #{id}
</select>
</mapper>

联合查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--将每一个属性值都映射成对象中的数据,如果有实体类对象,就写成对象.属性的方式-->
<!-- <resultMap id="myEmp" type="com.kuro.bean.Emp">-->
<!-- <id column="empno" property="empno"></id>-->
<!-- <result column="ename" property="ename"></result>-->
<!-- <result column="job" property="job"></result>-->
<!-- <result column="mgr" property="mgr"></result>-->
<!-- <result column="hiredate" property="hiredate"></result>-->
<!-- <result column="sal" property="sal"></result>-->
<!-- <result column="comm" property="comm"></result>-->
<!-- <result column="deptno" property="dept.deptno"></result>-->
<!-- <result column="dname" property="dept.dname"></result>-->
<!-- <result column="loc" property="dept.loc"></result>-->
<!-- </resultMap>-->
<!--使用第二种方式-->

<resultMap id="myEmp" type="com.kuro.bean.Emp">
<id column="empno" property="empno"></id>
<!--数据是多对一的情况时,使用 association 如多个emp对应一个dept-->
<association property="dept" javaType="com.kuro.bean.Dept">
<id property="deptno" column="deptno"></id>
<result property="dname" column="dname"></result>
<result property="loc" column="loc"></result>
</association>
</resultMap>
<select id="selectEmpByEmpno" resultMap="myEmp">
select * from emp left join dept on emp.deptno = dept.deptno where empno = #{empno}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<resultMap id="myDept" type="com.kuro.bean.Dept">
<id column="deptno" property="deptno"></id>
<result property="dname" column="dname"></result>
<result property="loc" column="loc"></result>
<!--数据是一对多的情况时,使用 collection 如一个dept对应多个emp-->
<collection property="emps" ofType="com.kuro.bean.Emp">
<id column="empno" property="empno"></id>
<result column="ename" property="ename"></result>
<result column="job" property="job"></result>
<result column="mgr" property="mgr"></result>
<result column="hiredate" property="hiredate"></result>
<result column="sal" property="sal"></result>
<result column="comm" property="comm"></result>
</collection>
</resultMap>
<select id="selectDeptByDeptno" resultMap="myDept">
select * from dept left join emp on dept.deptno = emp.deptno where dept.deptno = #{deptno}
</select>
1
2
3
4
5
6
7
8
9
10
11
public class Emp {
private Integer empno;
private Dept dept;
}
public class Dept {
private Integer deptno;
private String dname;
private String loc;

private List<Emp> emps;
}

分步查询

1
2
3
4
5
6
7
8
9
10
11
<select id="selectDeptByStemp2" resultMap="deptEmp">
select * from dept where deptno = #{deptno}
</select>
<resultMap id="deptEmp" type="com.kuro.bean.Dept">
<id column="deptno" property="deptno"></id>
<result property="dname" column="dname"></result>
<result property="loc" column="loc"></result>
<collection property="emps" ofType="com.kuro.bean.Emp" select="com.kuro.dao.EmpDao.selectEmpByStep2" column="deptno" fetchType="lazy">

</collection>
</resultMap>
1
2
3
public Dept selectDeptByStemp2(Integer deptno);

public Emp selectEmpByStep2(Integer deptno);

延迟查询

当我们在进行表关联的时候,有可能在查询结果的时候不需要关联对象的属性值,那么此时可以通过延迟加载来实现功能。在全局配置文件mybatis-config.xml中添加如下属性

1
2
3
4
<settings>
<!--开启延时加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>

如果设置了全局加载,但是希望在某一个sql语句查询的时候不适用延时策略,可以添加如下属性:

1
<association property="dept" select="com.kuro.dao.DeptDao.getDeptAndEmpsBySimple" column="deptno" fetchType="eager"/>

动态SQL

if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

1
2
3
4
5
6
7
8
9
10
11
12
<select id="selectEmpByCondition" resultType="com.kuro.bean.Emp">
select * from emp
<where>
<if test="empno!=null">
empno > #{empno}
</if>
<if test="ename!=null">
and ename=#{ename}
</if>
</where>

</select>
1
public Emp selectEmpByCondition(Emp emp);

choose

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="selectEmpByCondition" resultType="com.kuro.bean.Emp">
select * from emp
<where>
<choose>
<when test="empno!=null">
empno = #{empno}
</when>
<when test="ename!=null">
ename=#{ename}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>

trim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
trim:截取字符串,可以自定义where的格式
prefix:为sql语句整体添加一个前缀
prefixOverrides:去除整体sql语句前面多余的字符串
suffixOverriede:去除整体sql语句后面多余的字符串
-->
<select id="selectEmpByCondition" resultType="com.kuro.bean.Emp">
select * from emp
<trim prefix="where" prefixOverrides="and" suffixOverrides="and|or">
<if test="empno!=null">
empno = #{empno} and
</if>
<if test="ename!=null">
ename=#{ename} or
</if>
</trim>
</select>

foreach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--
foreach:遍历集合中的元素
collection:指定要遍历的集合
separator:分隔符
open:以什么开始
close:以什么结束
item:遍历过程中的每一个元素值
index:表示索引
-->
<select id="selectEmpByDeptnos" resultType="com.kuro.bean.Emp">
select * from emp where deptno in
<foreach collection="deptnos" separator="," open="(" item="deptno" index="idx" close=")">
#{deptno}
</foreach>
</select>
1
public List<Emp> selectEmpByDeptnos(@Param("deptnos") List<Integer> deptnos);

缓存机制

1
2
3
4
5
6
7
8
如果没有缓存,那么每次查询的时候都需要从数据库中加载数据,这回造成io的性能问题,所以,在很多情况下
如果连续执行两条相同的sql语句,可以直接从缓存中获取,如果获取不到,那么再去查询数据库,这意味着查询完成的结果
需要放到缓存中。

缓存分类:
1、一级缓存:表示sqlSession级别的缓存,每次查询的时候会开启一个会话,此会话相当于一次连接,关闭之后自动失效
2、二级缓存:全局范围内的缓存,sqlsession关闭之后才会生效
3、第三方缓存:继承第三方的组件,来充当缓存的作用

一级缓存

1
2
3
4
5
6
7
一级缓存:表示将数据存储在sqlsession中,关闭之后自动失效,默认情况下是开启的
在同一个会话之内,如果执行了多个相同的sql语句,那么除了第一个之外,所有的数据都是从缓存中进行查询的
在某些情况下,一级缓存可能会失效?
1、在同一个方法中,可能会开启多个会话,此时需要注意,会话跟方法没有关系,不是一个方法就只能由一个会话,所以严格记住,缓存的数据是保存在sqlsession中的
2、当传递对象的时候,如果对象中的属性值不同,也不会走缓存
3、在同一个连接中,如果修改了数据,那么缓存会失效,不同连接中,相互不受影响
4、如果在一个会话过程中,手动清空了缓存,那么缓存也会失效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test() throws IOException {
SqlSession sqlSession = sqlSessionFactory.openSession();

EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = mapper.selectEmpByEmpno(7369);
System.out.println(emp);

//同一个SqlSession中,走缓存获取
EmpDao mapper2 = sqlSession.getMapper(EmpDao.class);
Emp emp2 = mapper2.selectEmpByEmpno(7369);
System.out.println(emp2);
sqlSession.close();
}

二级缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
二级缓存:表示的是全局缓存,必须要等到sqlsession关闭之后才会生效
默认是不开启的,如果需要开启的话,需要进行如下设置
1、修改全局配置文件,在settings中添加配置
<setting name="cacheEnabled" value="true"/>
2、指定在哪个映射文件中使用缓存的配置
<cache></cache>
3、对应的java实体类必须要实现序列化的接口
在使用二级缓存的时候,可以包含多个属性值:
eviction:缓存淘汰机制:
LRU: 最近最少使用
FIFO:先进先出,按照添加缓存的顺序执行
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
flushInterval:设置间隔多长时间进行缓存刷新
size:引用的条数,是一个正整数,缓存中可以存储多少个对象,一般不设置,如果设置的话不要太大,会导致内存溢出
readonly:只读属性:
true:只读缓存,会给所有的调用的方法返回该对象的实例,不安全
false:读写缓存,只是返回缓存对象的拷贝,比较安全

一级缓存跟二级缓存有没有可能同时存在数据?
不会同时存在,因为二级缓存生效的时候,是在sqlsession关闭的时候
当查询数据的时候,我们是先查询一级缓存还是先查询二级缓存?
**先查询二级缓存,然后再查询一级缓存**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test() throws IOException {
//第一个SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = mapper.selectEmpByEmpno(7369);
System.out.println(emp);
//关闭SqlSession,使得它从一级缓存中推到二级缓存
sqlSession.close();

//第二个SqlSession,走缓存
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
Emp emp2 = mapper2.selectEmpByEmpno(7369);
System.out.println(emp2);

sqlSession2.close();
}

第三方缓存

1
2
3
4
5
可以使用第三方缓存,如`Ehcache`
1.新增`maven`依赖
1.在映射文件中使用缓存的配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
2.新建配置文件`ehcache.xml`并配置相关属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.0-alpha1</version>
<scope>test</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- ehcache.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\ehcache" />

<defaultCache
maxElementsInMemory="1"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
属性说明:
diskStore:指定数据在磁盘中的存储位置。
defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

以下属性是必须的:
maxElementsInMemory - 在内存中缓存的element的最大数目
maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:
timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

代码生成器

1
2
3
4
可以通过generator反向从数据库表动态生成实体类和一些基础的查询API:
1.引入依赖
2.编写`mybatis-generator.xml`配置
3.运行代码生成映射
1
2
3
4
5
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- mybatis-generator.xml -->
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!--总配置标签-->
<generatorConfiguration>
<!--具体配置的上下文环境-->
<!-- <context id="simple" targetRuntime="MyBatis3DynamicSql">-->
<context id="simple" targetRuntime="MyBatis3Simple">
<!--指向我们需要连接的数据库-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://192.168.85.111:3306/demo?serverTimezone=UTC"
userId="root" password="123456"/>

<!--生成对应的实体类
targetPackage:指定存放的包
targetProject:指定当前工程的目录
-->
<javaModelGenerator targetPackage="com.kuro.bean" targetProject="src/main/java"/>

<!--生成对应的SQL映射文件-->
<!--MyBatis3DynamicSql的话,就不需要这个-->
<sqlMapGenerator targetPackage="com.kuro.dao" targetProject="src/main/resources"/>

<!--生成对应的DAO接口-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.kuro.dao" targetProject="src/main/java"/>
<!-- <javaClientGenerator targetPackage="com.kuro.dao" targetProject="src/main/java"/>-->

<!--指定需要反向生成的表-->
<table tableName="emp" />
<table tableName="dept" />
</context>
</generatorConfiguration>
1
2
3
4
5
6
7
8
9
10
11
12
public class MyTest {
public static void main(String[] args) throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("mybatis-generator.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
}

最后更新: 2021年02月21日 20:19

原始链接: https://midkuro.gitee.io/2020/11/09/mybatis-mapper/

× 请我吃糖~
打赏二维码