MyBatis运行原理 (面试官说的)
SpringBoot里MyBatis - SQL执行过程
SpringBoot中整合MyBatis
Mybatis拦截器
常见QA
什么是MyBatis?
MyBatis是一个可以自定义SQL、存储过程和高级映射的数据访问层框架,底层是对 JDBC 的封装.
为什么说Mybatis是半自动ORM映射工具?与全自动的区别在哪里?
Mybatis在查询 关联对象 或 关联集合对象 时,需要手动编写sql来完成,所以称之为半自动ORM映射工具。Hibernate可以根据对象关系模型直接获取,所以它是全自动的。
MyBatis与Hibernate有哪些不同?
1)Mybatis是半自动持久层框架,hibernate是全自动持久层框架
2)Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高。
3)Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis仅支持 association
关联对象和collection
关联集合对象的延迟加载。
在Mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled = true | false
它的原理是:
使用CGLIB
创建目标对象的代理对象后,当调用目标方法时,进入拦截器方法。
比如调用a.getB().getName()
,拦截器invoke()方法发现a.getB()
是null
值,那么就会单独发送事先保存好的查询关联B对象的sql
,把B查询上来,然后调用a.setB(b)
,于是a的对象b属性就有值了,接着完成a.getB().getName()
方法的调用。这就是延迟加载的基本原理。
MyBatis的好处是什么?
- MyBatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写,维护便利。
- MyBatis封装了底层
JDBC API
的调用细节,并能自动将结果集转换成Java Bean对象,大大简化了Java数据库编程的重复工作。 - 因为MyBatis需要程序员自己去编写sql语句,程序员可以结合数据库自身的特点灵活控制sql语句,因此能够实现比Hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。
MyBatis环境搭建
1. 导入 jar / Maven工程引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.7</version>
</dependency>
2. 全局配置文件 (mybatis.xml)
- 没有名称和地址要求
- 在全局配置文件中引入 DTD 或 schema(如果导入 dtd 后没有提示 Window–> preference --> XML --> XMl catalog --> add 按钮)
- DTD(Document Type Definition),文档类型定义 xml文件的一个语法检查器,帮我们来限制这个文件中写的内容,
- XSD(XML Schema Definition) 描述 XML 文档的结构,网络应用程序中可以取代 DTD
<?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>
<!-- default引用enviroment的id,表示当前所使用的环境 -->
<environments default="default">
<!-- 声明可以使用的换环境 -->
<environment id="default">
<!-- 1.1.1 JDBC – 这个配置直接简单使用了 JDBC 的提交和回滚设置 -->
<!-- 1.1.2 MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接。而它会让容器来管理事务的整个生命周期(比如 Spring )
以后唯一能用到这个东西的时候也就是把mybatis的事物交给spring处理的时候用这个,其他时候基本用不上,都是JDBC。
如果用了这个参数的话,会把原生 JDBC 事务赋值false 即 setAutoMapping(false);-->
<transactionManager type="JDBC"></transactionManager>
<!-- 1.2.1 POOLED 使用数据库连接池 -->
<!-- 1.2.2 UNPOOLED 不实用数据库连接池,和直接使用JDBC一样 -->
<!-- 1.2.3 JNDI :这个数据源的实现是为了使用如 Spring 或应用服务器这类的容器, 容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用. -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/ssm" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/mapper/FlowerMapping.xml" />
</mappers>
</configuration>
3. 新建XXX.mapper 包 添加 XXXMapper.xml
XXXMapper.xml 里编写需要执行的 SQL 命令,mybatis 底层就是把这个xml文件
解析成了一个实现类
简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
Mybatis将所有 Xml 配置信息都封装到 All-In-One 重量级对象 Configuration
内部。
在Xml映射文件中,
<parameterMap>
标签会被解析为ParameterMap
对象,其每个子元素会被解析为ParameterMapping
对象。<resultMap>
标签会被解析为ResultMap
对象,其每个子元素会被解析为ResultMapping
对象。- 一个
<select>
、<insert>
、<update>
、<delete>
标签均会被解析为MappedStatement
对象,标签内的sql
会被解析为BoundSql
对象。
Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
namespace+id
是作为mappedStatements
的key
使用的;有了namespace
,就算id
重复,namespace+id
依旧不同。如果没有namespace
只剩下id
,id
重复会导致数据互相覆盖。
因为namespace
不是必须的,只是最佳实践而已。所以不同的Xml映射文件,如果配置了namespace
,那么id
可以重复,如果没有配置namespace
,那么id
不能重复。
4. 使用
<mapper namespace="a.b">
<select id="selAll" resultType="com.pojo.Flower">
select * from flower
</select>
<select id="selById" resultType="com.pojo.Flower">
select * from flower where id=1
</select>
<select id="selCountById" resultType="int">
select count(*) from flower
</select>
<select id="c" resultType="com.pojo.Flower">
select * from flower
</select>
</mapper>
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
session三种查询方式:第一种
selectList() 返回值为 List<resultType> 适用于返回结果为多行数据;
List<Flower> selectList = sqlSession.selectList("a.b.selAll");
for (Flower flower : selectList) {
System.out.println(flower.toString());
}
System.out.println("-------------------------");
session三种查询方式:第二种
selectOne() 返回值 Object,适用于返回结果 只是变量 或 一行数据时
Flower flower = sqlSession.selectOne("a.b.selById");
System.out.println(flower.toString());
System.out.println("-------------------------");
Integer count = sqlSession.selectOne("a.b.selCountById");
System.out.println(count);
System.out.println("-------------------------");
session三种查询方式:第三种
selectMap() 通过某列的值取到这行数据,数据库中的那个列的值当做 map 的 key;
key重复时候随map特性会覆盖上一条数据;
Map<Object, Object> map = sqlSession.selectMap("a.b.c", "name");
System.out.println(map);
不关闭的话一直和数据库长连接状态
sqlSession.close();
Xml映射文件中,除了常见的select | insert | updae | delete 标签之外,还有哪些标签?
还有很多其他的标签,<resultMap>
、<parameterMap>
、<sql>
、<include>
、<selectKey>
,
加上动态sql的9个标签,trim
|where
|set
|foreach
|if
|choose
|when
|otherwise
|bind
等
mybatis 整合 log4j
- pom.xml 文件引入依赖
<!-- log4j 日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j-version}</version>
</dependency>
- 在
mybatis.xml
中开启 log4j (在 mybatis 全局配置文件中通过<settings>
标签控制 mybatis 全局开关) - 在 src 下有配置文件
log4j.properties
parameterType 属性
List<People> list = sqlSession.selectList("a.b.c"); //无参数查询
People p = sqlSession.selectOne("a.b.selById", 2);
List<People> peopleList = sqlSession.selectList("a.b.page", map);
Map<Object, Object> map = sqlSession.selectMap("a.b.c", "name");
sqlSession.insert("a.b.insert", person)
sqlSession.update("a.b.update", person);
在 Mapper.xml
中可以通过 parameterType
控制参数类型 , #{}
获取参数内容
1. 参数为基本数据类型:
- 使用索引从
0
开始,#{0}
表示第一个参数 - 也可以使用
#{param1}
第一个参数 - 如果只有一个参数 (
基本数据类型
或String
),mybatis
对#{}
里面内容没有要求随便写
<select id="selById" resultType="com.pojo.People" parameterType="int">
<!-- select * from people where id=#{0} -->
<!-- select * from people where id=#{param1} -->
select * from people where id=#{sdadsa}
</select>
2. 参数是 map
- 写成
#{key}
<select id="page" resultType="People" parameterType="map">
select *
from people limit #{pageStart}, #{pageSize}
</select>
3. 参数是对象:
#{属性名}
,属性名一定要有get/set
方法
<select id="selById4" resultType="com.pojo.People" parameterType="com.pojo.People">
select * from people where id=#{id}
</select>
#{}
和 ${}
的区别是什么?
#{}
是预编译处理,${}
是字符串替换。
- Mybatis在处理
#{}
时,会将sql中的#{}
替换为?
占位符,调用PreparedStatement
的set方法
来赋值; - Mybatis在处理
${}
时,就是把${变量}
替换成变量
的值,默认找变量
的set
方法,如果是数字,传进来的就是这个数字
。
使用#{}
可以有效的防止SQL注入,提高系统安全性。
<select id="selById4" resultType="com.pojo.People" parameterType="com.pojo.People">
select * from people where id=#{id}
</select>
People p5 = new People();
p5.setId(1);
p5.setName("张三");
People p6 = sqlSession.selectOne("a.b.selById4", p5);
System.out.println(p6);
org.apache.ibatis.logging.jdbc.BaseJdbcLogger==> Preparing: select * from people where id=?
org.apache.ibatis.logging.jdbc.BaseJdbcLogger==> Parameters: 1(Integer)
org.apache.ibatis.logging.jdbc.BaseJdbcLogger<== Total: 1
<select id="selById3" resultType="com.pojo.People" parameterType="com.pojo.People">
select * from people where id=${id}
</select>
People p3 = new People();
p3.setId(1);
p3.setName("张三");
People p4 = sqlSession.selectOne("a.b.selById3", p3);
System.out.println(p4);
org.apache.ibatis.logging.jdbc.BaseJdbcLogger==> Preparing: select * from people where id=1
org.apache.ibatis.logging.jdbc.BaseJdbcLogger==> Parameters:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger<== Total: 1
<select id="selById2" resultType="com.pojo.People" parameterType="int">
select * from people where id=${1}
</select>
People p = sqlSession.selectOne("a.b.selById2", 2);
System.out.println(p);
org.apache.ibatis.logging.jdbc.BaseJdbcLogger==> Preparing: select * from people where id=1
org.apache.ibatis.logging.jdbc.BaseJdbcLogger==> Parameters:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger<== Total: 1
Mybatis是如何进行分页的?分页插件的原理是什么?
1. 直接编写sql实现分页
不允许在关键字前后
进行数学运算,需要在代码中计算完成 后传递到 mapper.xml
中
int pageSize = 2;
int pageNum = 2;
Map<String, Object> mapPage = new HashMap<>();
mapPage.put("pageStart", pageSize * (pageNum - 1));
mapPage.put("pageSize", pageSize);
List<People> peopleList = sqlSession.selectList("a.b.page", mapPage);
<!-- 分页查询 -->
<select id="page" resultType="People" parameterType="map">
select * from people limit #{pageStart}, #{pageSize}
</select></select>
2. 使用RowBounds进行分页
Mybatis的逻辑分页类 RowBounds(int offset, int limit)
比较简单,简单来说就是取出所有满足条件的数据,然后舍弃掉前面offset
条数据,然后再取剩下的数据的limit
条
int pageSize = 2;
int pageNum = 2;
Map<String, Object> map = new HashMap<>();
map.put("pageStart", pageSize * (pageNum - 1));
map.put("pageSize", pageSize);
RowBounds rowBounds = new RowBounds(pageSize * (pageNum - 1), pageSize);
List<People> pageInfoList = session.selectList("a.b.selByPage1", null, rowBounds);
<select id="selByPage1" resultType="People" parameterType="map">
select * from people
</select>
3. 使用Mybatis的分页插件
MyBatis插件开发:简单分页插件
分页插件的原理:实现Mybatis提供的接口来自定义插件,在插件的拦截方法内 拦截待执行的sql,然后重写sql。
比如:举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10
typeAliases 别名
1. 系统内置别名
部分别名
2. 自定义别名
<typeAliases>
<typeAlias type="com.pojo.People" alias="peo"/>
<package name="com.pojo"/>
</typeAliases>
alias= ”peo”
,就可以在mapper.xml
中用 peo
引用 People
类;
直接给某个包下所有类起别名,别名为类名,不区分大小写;
原来写法
<select id="selById3" resultType="com.pojo.People" parameterType="com.pojo.People">
select * from people where id=${id}
</select>
现在写法
<select id="selById3" resultType="peo" parameterType="peo">
select * from people where id=${id}
</select>
或
<select id="selById3" resultType="people" parameterType="People">
select * from people where id=${id}
</select>
非自动提交事务
-
session.commit()
来提交事务. -
mybatis 中默认是关闭了JDBC的自动提交功能,每一个
SqlSession
都默认不自动提交事务.
SqlSession session =factory.openSession();
public class DefaultSqlSessionFactory implements SqlSessionFactory {
...
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
...
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
...
}
...
}
-
如何改成事务自动提交?
-
java.sql.Connection.setAutoCommit(true);
-
SqlSession session = sqlSessionFactory.openSession(true)
-
源码如下:
public interface SqlSessionFactory {
...
SqlSession openSession(boolean autoCommit);
...
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
...
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
...
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
...
}
...
}
mybatis 底层是对 JDBC 的封装.
-
JDBC 中
executeUpdate()
执行新增,删除,修改的 SQL后 返回值是int
, 表示受影响的行数。所以mybatis
中< insert>
< delete>
< update>
标签没有resultType
属性, 只返回int
; -
sqlSessionFactory.openSession()
时Mybatis
创建SqlSession
时,同时创建一个Transaction
(事务对象)并且autoCommit
设为false
;
3. session只要没关,那么session里都是同一个事务;如果出现异常,应该session.rollback()
回滚事务.
MyBatis实现新增的步骤
<insert id="insert" parameterType="People">
insert into People values(default,#{name},#{age})
<!--values(default,${name},${age}) --><!-- 会报错 原因还没搞懂-->
</insert>
People p8 = new People();
p8.setName("新增");
p8.setAge(18);
sqlSession.insert("a.b.insert", p8)
sqlSession.commit();
sqlSession.close();
MyBatis 实现修改
<update id="update" parameterType="People">
update people set age = age +1 where name =#{name}
</update>
People p9 = new People();
p9.setName("王五");
p9.setAge(14);
sqlSession.update("a.b.update", p9);
sqlSession.commit();
sqlSession.close();
mybatis 实现删除
<delete id="del" parameterType="int">
delete from people where id = #{0}
</delete>
int del = session.delete("a.b.del",3);
if(del>0) {
System.out.println("成功");
}else{
System.out.println("失败");
}
session.commit();
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
Mybatis有三种基本的Executor执行器,SimpleExecutor
、ReuseExecutor
、BatchExecutor
。
1)SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
2)ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map
3)BatchExecutor:完成批处理。
Mybatis中如何指定使用哪一种Executor执行器?
- 默认使用ExecutorType.SIMPLE
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
...
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
- 也可以openSession时传递ExecutorType类型参数。
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
- 在Mybatis全局配置文件中,指定默认的ExecutorType执行器类型,
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
Mybatis中如何执行批处理?
使用BatchExecutor
完成批处理。
Mybatis执行批量插入,能返回数据库主键列表吗?
能,JDBC都能,Mybatis当然也能
什么是MyBatis的接口绑定,有什么好处?
接口绑定就是任意定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口方法来执行SQL;
这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置.
接口绑定有几种实现方式?
通过xml里面写SQL来绑定
以前指点mapper的方式:
<mappers>
<mapper resource="com/mapper/AccountMapper.xml" />
<mapper resource="com/mapper/LogMapper.xml" />
</mappers>
现在指定mapper的方式:
在 mybatis.xml 中使用标签来进行扫描接口和 mapper.xml
在这种情况下,要指定xml映射文件里面的namespace
必须为接口的全路径名
.
Mapper.xml
文件中的namespace
和mapper接口
的类路径
相同。mapper.xml
文件中的sql的id
和Mapper接口
的方法名
相同。mapper.xml
文件中的sql 的parameterType类型
和Mapper接口
方法的输入参数类型
相同- 如果接口中方法为多个参数,可以省略
parameterType
mapper.xml
文件中的sql 的resultType类型
和Mapper接口
方法的输出参数类型
相同
LogMapper.xml
<!-- 这里路径要和接口完全一致 -->
<mapper namespace="com.mapper.LogMapper">
<!-- Id要和接口里面的方法完全一致 -->
<select id="selAll" resultType="LogInfo">
select * from log
</select>
</mapper>
LogMapper.java
package com.mapper;
public interface LogMapper {
List<LogInfo> selAll();
}
以前执行方法方式:
List<LogInfo> list = session.selectList("com.mapper.LogMapper.selLog", map)
接口绑定以后执行方法方式:
LogMapper logMapper = session.getMapper(LogMapper.class);
List<LogInfo> selAll = logMapper.selAll();
注解绑定
作用
为了简化配置文件.
- Mybatis 的注解
简化
mapper.xml 文件, - 但是如果涉及
动态SQL
依然使用mapper.xml - mapper.xml 和注解可以
共存
. - 使用注解时mybatis.xml中
<mappers>
使用<package/>
或者<mapper class=””/>
<mappers>
<!-- <mapper class="com.mapper.TeacherMapper"/> -->
<package name="com.mapper"/>
</mappers>
在接口的方法上面加上@Select @Update
等注解里面包含Sql语句来绑定,
- 实现查询
- 实现新增
- 实现修改
- 实现删除
public interface TeacherMapper {
@Insert("insert into teacher values(default,#{name})")
int insTeacher(Teacher teacher);
@Update("update teacher set name=#{name} where id =#{id}")
int updTeacher(Teacher teacher);
@Delete("delete from teacher where id=#{0}")
int delTeacher(int id);
@Select("select * from teacher")
List<Teacher> selAll();
}
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
Teacher t = new Teacher();
t.setId(4);
t.setName("老师4");
int insTeacher = teacherMapper.insTeacher(t);
int insTeacher1 = teacherMapper.updTeacher(t);
List<Teacher> selAll = teacherMapper.selAll();
for (Teacher teacher : selAll) {
System.out.println(teacher);
}
int insTeacher2 = teacherMapper.delTeacher(3);
什么情况下用注解绑定,什么情况下用xml绑定?
当Sql语句比较简单时候,用注解绑定;
当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多
通常一个Xml映射文件,都会写一个Mapper接口与之对应, Mapper接口的工作原理,是否可以重载?
不能重载,因为通过Mapper接口
寻找Xml对应的sql的时候是 包名+类名+方法名 的保存和寻找策略,没有把参数考虑进来,所以不能重载。
接口工作原理
为动态代理原理
,运行时会为mapper接口生成proxy,代理对象会 拦截 接口方法,去执行对应的sql,返回数据。
后面 mybatis
和 spring
整合时使用的是这个方案
在mapper中如何传递多个参数?
在接口中声明方法
List<LogInfo> selByAccOutAccIn1(String accOut, String accIn);
List<LogInfo> selByAccOutAccIn2(String accOut, String accIn);
List<LogInfo> selByAccOutAccIn3(@Param("accOut") String accOut, @Param("accIn") String accIn);
在 mapper.xml
中添加对应信息
#{}
中使用 0,1,2
或 param1,param2
;
第三种 mybatis 就把参数转换成map了,其中@Param("accOut")
里的 accOut 为key; 后面的 accOut
为内容;
<!-- 多参数是不需要写parameterType -->
<select id="selByAccOutAccIn1" resultType="LogInfo">
select* from log where accout=#{0} and accin=#{1}
</select>
<select id="selByAccOutAccIn2" resultType="LogInfo">
select* from log where accout=#{param1} and accin=#{param2}
</select>
<select id="selByAccOutAccIn3" resultType="LogInfo">
select* from log where accout=#{accOut} and accin=#{accIn}
</select>
使用
List<LogInfo> selAll1 = logMapper.selByAccOutAccIn1("1", "2");
List<LogInfo> selAll2 = logMapper.selByAccOutAccIn2("1", "2");
List<LogInfo> selAll3 = logMapper.selByAccOutAccIn3("1", "2");
动态 SQL
定义
Mybatis动态sql是在mapper.xml
中添加逻辑判断等,根据不同的条件需要执行不同的SQL 命令.称为动态SQL。
OGNL表达式,直接由key 或者对象的属性,不需要添加任何特殊字符号
能简述一下动态sql的执行原理不?
mybatis中动态sql执行原理
都有哪些动态sql?
< if>
package com.mapper;
public interface LogMapper {
List<LogInfo> selByAccOutAccIn1(@Param("accOut") String accOut, @Param("accIn") String accIn);
}
<mapper namespace="com.mapper.LogMapper">
<select id="selByAccOutAccIn1" resultType="LogInfo">
select * from log where 1=1
<if test="accOut!=null and accOut !=''">
and accOut=#{accOut}
</if>
<if test="accIn!=null and accIn !=''">
and accIn=#{accIn}
</if>
</select>
</mapper>
List<LogInfo> selByAccOutAccIn1 = logMapper.selByAccOutAccIn1("1", "");
< where>
- 当编写
<where>
标签时,如果内容中第一个是and
则去掉第一个and
- 如果
<where>
中有内容会生成where
关键字,如果没有内容不生成where
关键 <where>
比直接使用<if>
少写where 1=1
List<LogInfo> selByAccOutAccIn2(@Param("accOut") String accOut, @Param("accIn") String accIn);
<select id="selByAccOutAccIn2" resultType="LogInfo">
select * from log
<where>
<if test="accOut!=null and accOut !=''">
and accOut=#{accOut}
</if>
<if test="accIn!=null and accIn !=''">
and accIn=#{accIn}
</if>
</where>
</select>
package com.mapper;
public interface LogMapper {
List<LogInfo> selByAccOutAccIn2 = logMapper.selByAccOutAccIn2("1", "");
}
< choose> < when> < otherwise>
只有有一个成立,其他都不执行.
即使accin
和 accout
都成立,生成的 sql
也只有 where accin=?
package com.mapper;
public interface LogMapper {
List<LogInfo> selByAccOutAccIn3(@Param("accOut") String accOut, @Param("accIn") String accIn);
}
<!-- 只有有一个成立,其他都不执行 -->
<select id="selByAccOutAccIn3" resultType="LogInfo">
select * from log
<where>
<choose>
<when test="accIn!=null and accIn !=''">and accIn=#{accIn}</when>
<when test="accOut!=null and accOut !=''">and accOut=#{accOut}</when>
</choose>
</where>
</select>
List<LogInfo> selByAccOutAccIn3 = logMapper.selByAccOutAccIn3("1", "");
< set>
作用
- 去掉最后一个逗号
- 如果
<set>
里面有内容生成set
关键字,没有就不生成 id=#{id}
目的防止<set>
中没有内容,mybatis 不生成set
关键字,如果修改中没有set
从句 SQL 语法错误。
package com.mapper;
public interface LogMapper {
int updLogInfo(LogInfo logInfo);
}
<update id="updLogInfo" parameterType="LogInfo">
update log
<set>
<!-- 写id=#{id}是防止不生成set语法错误 -->
id=#{id},
<if test="accOut!=null and accOut !=''">
accOut=#{accOut},
</if>
<if test="accIn!=null and accIn !=''">
accIn=#{accIn},
</if>
</set>
where id=#{id}
</update>
LogInfo LogInfo = new LogInfo();
LogInfo.setId(3);
LogInfo.setAccIn("8");
LogInfo.setAccOut("9");
LogInfo.setMoney(522);
logMapper.updLogInfo(LogInfo);
session.commit();
session.close();
< trim>
prefix
在前面添加内容prefixOverrides
去掉前面内容suffix
在后面添加内容suffixOverrieds
去掉后面内容- 执行顺序去掉内容后添加内容
package com.mapper;
public interface LogMapper {
List<LogInfo> selByAccOutAccIn4(@Param("accOut") String accOut, @Param("accIn") String accIn);
int updLogInfo2(LogInfo logInfo);
}
<select id="selByAccOutAccIn4" resultType="LogInfo">
select * from log
<trim prefix="where" prefixOverrides="and">
and accOut=#{accOut}
</trim>
</select>
<update id="updLogInfo2" parameterType="LogInfo">
update log
<!-- 多用于处理钱 ¥100 之类的操作 ,不过中间会自动加一个空格,要想没有空格参考下面的方法 -->
<trim prefix="set" suffixOverrides=",">
accOut=#{accOut},accIn=#{accIn},
</trim>
where id=#{id}
</update>
List<LogInfo> selByAccOutAccIn4 = logMapper.selByAccOutAccIn4("1", "");
< bind>
作用
给参数重新赋值
场景:
- 模糊查询
- 在原内容前或后添加内容
package com.mapper;
public interface LogMapper {
List<LogInfo> selByAccOutAccIn5(@Param("accOut") String accOut, @Param("accIn") String accIn);
}
<select id="selByAccOutAccIn5" resultType="LogInfo">
<!-- 传过来值的基础上重新设置一下值,主要用于模糊查询 -->
<bind name="accOut" value="'%'+accOut+'%'" />
select * from log
<trim prefix="where" prefixOverrides="and">
and accOut like #{accOut}
</trim>
</select>
List<LogInfo> selByAccOutAccIn5 = logMapper.selByAccOutAccIn5("1", "");
模糊查询like语句该怎么写?
1)在java中拼接通配符,通过#{}
赋值
2)在Sql语句中拼接通配符 (不安全 会引起Sql注入)
< foreach>
循环参数内容,还具备在内容的前后添加内容,还具备添加分隔符功能.。
适用场景:
in
查询中.批量新增中(mybatis 中 foreach 效率比较 低)
- 如果希望批量新增,SQL 命令
package com.mapper;
public interface LogMapper {
List<LogInfo> selIn(List<Integer> list);
}
<select id="selIn" parameterType="list" resultType="LogInfo">
select * from log where id in
<!-- foreach多用于in查询 -->
<foreach collection="list" item="abc" open="(" close=")" separator=",">
#{abc}
</foreach>
</select>
List<Integer> intList = new ArrayList();
intList.add(1);
intList.add(2);
intList.add(3);
List<LogInfo> selByAccOutAccIn6 = logMapper.selIn(intList);
- collectino=”” 要遍历的集合
item
迭代变量, #{迭代变量名}
获取内容
open
循环后左侧添加的内容
close
循环后右侧添加的内容
separator
每次循环时,元素之间的分隔符
package com.mapper;
public interface LogMapper {
int ins(List<Integer> list);
}
<insert id="ins" parameterType="list">
insert into log values
<trim suffixOverrides=",">
<foreach collection="list" item="abc">
(default,#{abc},2,666),
</foreach>
</trim>
</insert>
List<Integer> intList = new ArrayList();
intList.add(1);
intList.add(2);
intList.add(3);
List<LogInfo> selByAccOutAccIn6 = logMapper.selIn(intList);
< sql> 和< include>
代码复用
package com.mapper;
public interface LogMapper {
List<LogInfo> selByAccOutAccIn6(@Param("accOut") String accOut, @Param("accIn") String accIn);
}
某些 SQL 片段如果希望复用,可以使用<sql>
定义这个片段
<sql id="mysql">
id,accout,accin,money
</sql>
在<select>
或<delete>
或<update>
或<insert>
中使用<include>
引用
<select id="selByAccOutAccIn6" resultType="LogInfo">
select
<include refid="mysql"></include>
from log where accOut=#{accOut}
</select>
List<LogInfo> selByAccOutAccIn7 = logMapper.selByAccOutAccIn6("1", "");
Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。
原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。
MyBatis 缓存
应用程序
和数据库
交互的过程是一个相对比较耗时
的过程
缓存存在的意义:
让应用程序减少
对数据库的访问
,提升程序运行效率
1. SqlSession
缓存
MyBatis 中默认 SqlSession
缓存开启
- 同一个
SqlSession
对象调用同一个<select>
时,只有第一次访问数据库,第一次之后把查询结果缓存到SqlSession
缓存区(内存)中。 - 有效范围必须是同一个
SqlSession
对象 - 缓存的是
statement
对象. - 在
myabtis
时一个<select>
对应一个statement
对象
序列化:把内存中的临时数(瞬时数据)持久化到硬盘上
反序列化:把硬盘上的永久数据读取到内存中成为临时数据的过程
同一个session
不同方法,缓存不可用
SqlSession session = factory.openSession();
List<LogInfo> selectList = session.selectList("com.mapper.LogMapper.selAll");
List<LogInfo> selectList1 = session.selectList("com.mapper.LogMapper.selAll1");
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,138 ==> Preparing: select * from log
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,225 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,306 <== Total: 43
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:05:22,309 Cache Hit Ratio [com.mapper.LogMapper]: 0.0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,310 ==> Preparing: select * from log
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,310 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,324 <== Total: 43
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:05:22,325 Cache Hit Ratio [com.mapper.LogMapper]: 0.0
不同session
同一个方法,缓存不可用
SqlSession session = factory.openSession();
List<LogInfo> selectList1 = session.selectList("com.mapper.LogMapper.selAll1");
SqlSession session1 = factory.openSession();
List<LogInfo> selectList11 = session1.selectList("com.mapper.LogMapper.selAll1");
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,310 ==> Preparing: select * from log
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,310 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,324 <== Total: 43
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:05:22,325 Cache Hit Ratio [com.mapper.LogMapper]: 0.0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,335 ==> Preparing: select * from log
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,336 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,356 <== Total: 43
缓存流程
- 步骤一: 先去缓存区中找是否存在
statement
- 步骤二:返回结果
- 步骤三:如果没有缓存
statement
对象,去数据库获取数据 - 步骤四:数据库返回查询结果
- 步骤五:把查询结果放到对应的缓存区中
List<LogInfo> selectList2 = session.selectList("com.mapper.LogMapper.selAll2");//两次查询只有一个sql
List<LogInfo> selectList3 = session.selectList("com.mapper.LogMapper.selAll2");//两次查询只有一个sql
同一个session
同一个方法用上了缓存
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:05:22,356 Cache Hit Ratio [com.mapper.LogMapper]: 0.0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,357 ==> Preparing: select * from log
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,357 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:05:22,369 <== Total: 43
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:05:22,371 Cache Hit Ratio [com.mapper.LogMapper]: 0.0
2. SqlSessionFactory 缓存 又叫:二级缓存
有效范围:
同一个 factory
内哪个 SqlSession
都可以获取
什么时候使用二级缓存
当数据频繁被使用,很少被修改
使用二级缓存步骤
-
在
mapper.xml
中添加<cache readOnly="true"></cache>
如果不加readOnly="true
就要把所有的pojo
类都要实现序列化接口 -
当
SqlSession.close()
时或commit()
时会把SqlSession
缓存的数据刷(flush
)到SqlSessionFactory
缓存区中
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session2 = factory.openSession();
List<LogInfo> session2selectList2 = session2.selectList("com.mapper.LogMapper.selAll1");
session2.close();// 注意这里不关闭 以及缓存不会往二级缓存里面存
SqlSession session3 = factory.openSession();
List<LogInfo> session3selectList3 = session3.selectList("com.mapper.LogMapper.selAll1");
SqlSession session4 = factory.openSession();
List<LogInfo> session4selectList4 = session4.selectList("com.mapper.LogMapper.selAll2");
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:38:21,251 Cache Hit Ratio [com.mapper.LogMapper]: 0.0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:38:21,786 ==> Preparing: select * from log
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:38:21,858 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:38:21,941 <== Total: 43
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:38:21,946 Cache Hit Ratio [com.mapper.LogMapper]: 0.5
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- org.apache.ibatis.cache.decorators.LoggingCache 2021-08-26 19:38:21,948 Cache Hit Ratio [com.mapper.LogMapper]: 0.3333333333333333
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:38:21,949 ==> Preparing: select * from log
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:38:21,950 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 19:38:21,966 <== Total: 43
以上执行结果,同一个factory里,同方法哪怕不是同session
也会查缓存,不同方法则不会查缓存
讲下MyBatis的缓存
MyBatis的缓存分为一级缓存和二级缓存
一级缓存
缓存存储位置
一级缓存放在SqlSession
里面。
缓存的内容
缓存的是statement
对象。
缓存可见范围
同一个SqlSession 对象调用同一个<select >
时
缓存数据的来源
第一次访问数据库,把第一次之后把查询结果缓存到SqlSession
缓存区(内存)中。
缓存启用的配置条件
默认就有。
二级缓存
缓存存储位置
二级缓存放在它的命名空间里,默认是不打开的。
缓存的内容
缓存的是statement
对象。
缓存访问范围
同一个factory 内不同SqlSession
同一个<select>
。
缓存数据的来源
当SqlSession
对象close()
时或commit()
时会把SqlSession
缓存的数据刷(flush
)到SqlSessionFactory
缓存区中。
缓存启用的配置条件
默认是不打开的,使用二级缓存需要满足一下条件
- pojo需要实现
Serializable
序列化接口(可用来保存对象的状态) - 或者在
mapper.xml
中添加<cache readOnly="true">< /cache>
缓存流程
参照上面
MyBatis 实现多表查询
实现多表查询方式
- 业务装配:对两个表编写单表查询语句,在业务(
Service
)把查询 的两个结果进行关联. - 使用
Auto Mapping
特性,在实现两表联合查询时通过别名完成射。 - 使用
<resultMap>
标签进行实现.
多表查询时,类中包含另一个类的对象的分类?
- 单个对象
- 集合对象.
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
当实体类中的属性名和表中的字段名不一样,如果将查询的结果封装到指定pojo?
-
使用
<resultType>
标签时,就是使用Auto Mapping
特性;属性名和表中的字段名不一样时,sql语句中定义字段名的别名
;MyBatis默认使用
Auto Mapping`特性.。 -
使用
<resultMap>
标签来映射字段名和实体类属性名的一 一对应的关系。 -
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
<resultType>
标签
Auto Mapping 自动映射
通过同名方式,实体类属性名
和数据库字段名
要一样,如果不一样需要用 sql字段别名
控制。
- 3.2版本以前找的是
set方法
- 3.2版本以后直接找的是
属性
,没set方法也能找到
<resultMap>
标签
由程序员逐一定义列名和对象属性名之间的映射关系,控制SQL查询结果与实体类的映射关系.。
使用 resultMap
实现单表映射关系
mybatis里配置
<mappers>
<mapper resource="com/mapper/TeacherMapper.xml" />
<mapper resource="com/mapper/StudentMapper.xml" />
</mappers>
<mapper namespace="com.mapper.TeacherMapper">
<!-- teacher不是表名而是实体类名字 -->
<resultMap id="mymap" type="com.pojo.Teacher" >
<!-- 主键使用id标签配置映射关系 -->
<id column="id" property="id1" />
<!-- 其他列使用result标签来配置映射关系;如果不配置对应关系就找不到值,就在数据库中查不到然后赋值null -->
<result column="name" property="name1" />
</resultMap>
<!--使用resultMap 实现单表映射关系 -->
<select id="selAll" resultMap="mymap">
select * from teacher
</select>
</mapper>
SqlSession sqlSession = sqlSessionFactory.openSession();
List<Teacher> list = sqlSession.selectList("com.mapper.TeacherMapper.selAll");
for (Teacher teacher : list) {
System.out.println(teacher);
}
sqlSession.close();
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 20:59:40,923 ==> Preparing: select * from teacher
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 20:59:41,027 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 20:59:41,082 <== Total: 7
Teacher [id1=1, name1=老师1, studentList=null]
Teacher [id1=2, name1=老师2, studentList=null]
Teacher [id1=4, name1=老师4, studentList=null]
Teacher [id1=5, name1=老师4, studentList=null]
Teacher [id1=6, name1=老师4, studentList=null]
Teacher [id1=7, name1=老师4, studentList=null]
Teacher [id1=8, name1=老师4, studentList=null]
使用 resultMap 实现关联单个对象(N+1)
N+1 查询方式,先查询出某个表的全部信息,根据这个表的信息查询另一个表的信息
与业务装配的区别:
在 service
里面写的代码现在由 mybatis
完成装配。
实现步骤
- 在 Student 实现类中包含了一个 Teacher 对象
public class Student {
private int id;
private String name;
private int age;
private int tid;
private Teacher teacher;
}
public class Teacher {
private int id;
private String name;
private List<Student> studentList;
}
2.在 TeacherMapper.xml
中提供一个查询
<mapper namespace="com.mapper.TeacherMapper">
<select id="selById" parameterType="int" resultType="com.pojo.Teacher" >
select * from teacher where id =#{0}
</select>
</mapper>
3.在 StudentMapper.xml
中
<mapper namespace="com.mapper.StudentMapper">
<!-- 使用<resultMap>实现关联单个对象 (多表N+1次查询方式) -->
<resultMap id="stumap" type="Student"><!-- Student是实体类 -->
<id column="id" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
<result column="tid" property="tid" />
<!-- 如果关联一个对象就是用这个标签 -->
<association property="teacher" select="com.mapper.TeacherMapper.selById" column="tid"></association>
</resultMap>
<select id="selAll" resultMap="stumap">
select * from student
</select>
</mapper>
<association>
装配一个对象时使用
property
: 指定类中的属性名
select
:通过哪个查询查询出这个对象的信息
column
: 把当前表的哪个列的值做为参数传递给另一个查询
大前提:使用N+1 方式时,如果列名和属性名相同可以不配置,使用Auto mapping 特性.
但是mybatis 默认只会给列专配一次。
所以以上代码可以简化
<resultMap id="stumap1" type="Student">
<result column="tid" property="tid" />
<association property="teacher" select="com.mapper.TeacherMapper.selById" column="tid"></association>
</resultMap>
<select id="selAll" resultMap="stumap1">
select * from student
</select>
使用
SqlSession sqlSession = sqlSessionFactory.openSession();
List<Student> list1 = sqlSession.selectList("com.mapper.StudentMapper.selAll");
for (Student student : list1) {
System.out.println(student);
}
sqlSession.close();
结果
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,573 ==> Preparing: select * from student
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,645 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,701 ====> Preparing: select * from teacher where id =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,703 ====> Parameters: 1(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,716 <==== Total: 1
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,723 ====> Preparing: select * from teacher where id =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,724 ====> Parameters: 2(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,726 <==== Total: 1
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-09-15 11:35:04,727 <== Total: 11
Student [id=1, name=学生1, age=1, tid=1, teacher=Teacher2 [id=1, name=老师1, studentList=null]]
Student [id=2, name=学生2, age=2, tid=1, teacher=Teacher2 [id=1, name=老师1, studentList=null]]
Student [id=3, name=学生3, age=3, tid=1, teacher=Teacher2 [id=1, name=老师1, studentList=null]]
Student [id=4, name=学生4, age=4, tid=1, teacher=Teacher2 [id=1, name=老师1, studentList=null]]
Student [id=5, name=学生5, age=5, tid=1, teacher=Teacher2 [id=1, name=老师1, studentList=null]]
Student [id=6, name=学生6, age=6, tid=1, teacher=Teacher2 [id=1, name=老师1, studentList=null]]
Student [id=7, name=学生7, age=7, tid=2, teacher=Teacher2 [id=2, name=老师2, studentList=null]]
Student [id=8, name=学生8, age=8, tid=2, teacher=Teacher2 [id=2, name=老师2, studentList=null]]
Student [id=9, name=学生9, age=9, tid=2, teacher=Teacher2 [id=2, name=老师2, studentList=null]]
Student [id=10, name=学生10, age=10, tid=2, teacher=Teacher2 [id=2, name=老师2, studentList=null]]
Student [id=11, name=学生11, age=11, tid=2, teacher=Teacher2 [id=2, name=老师2, studentList=null]]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
使用 resultMap 实现关联单个对象(联合查询)
<mapper namespace="com.mapper.StudentMapper">
<!-- 使用resultMap 实现关联单个对象(联合查询方式) -->
<resultMap type="Student" id="stuMap1">
<id column="sid" property="id" />
<result column="sname" property="name" />
<result column="age" property="age" />
<result column="tid" property="tid" />
<association property="teacher" javaType="Teacher">
<id column="tid" property="id1" />
<result column="tname" property="name1" />
</association>
</resultMap>
<select id="selAll2" resultMap="stuMap1">
select s.id sid,s.name sname,age age,t.id tid,t.name tname
FROM student s
left outer join teacher t
on s.tid=t.id
</select>
</mapper>
使用
SqlSession sqlSession = sqlSessionFactory.openSession();
List<Student> list2 = sqlSession.selectList("com.mapper.StudentMapper.selAll2");
for (Student student : list2) {
System.out.println(student);
}
sqlSession.close();
结果
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:24:39,305 ==> Preparing: select s.id sid,s.name sname,age age,t.id tid,t.name tname FROM student s left outer join teacher t on s.tid=t.id
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:24:39,411 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:24:39,465 <== Total: 11
Student [id=1, name=学生1, age=1, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=2, name=学生2, age=2, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=3, name=学生3, age=3, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=4, name=学生4, age=4, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=5, name=学生5, age=5, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=6, name=学生6, age=6, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=7, name=学生7, age=7, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=8, name=学生8, age=8, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=9, name=学生9, age=9, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=10, name=学生10, age=10, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=11, name=学生11, age=11, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
N+1 方式和联合查询方式对比
N+1:需求不确定时.
联合查询:需求中确定查询时两个表一定都查询.
N+1 名称由来
举例:学生中有3 条数据
需求:查询所有学生以及授课老师信息
需要执行的SQL命令查询全部学生信息:select * from 学生
,执行3 遍select * from 老师where id=学生的外键
使用多条SQl 命令查询两表数据时,如果希望把需要的数据都查询出来,需要执行N+1
条SQl 才能把所有数据库查询出来.
缺点:
效率低
优点:
如果有的时候只需要查询学生不查询老师.只需要执行一个select * from student;
适用场景:
有的时候需要查询学生同时查询老师,有的时候只需要查询学生.
如果解决N+1
查询带来的效率低的问题
默认带的前提: 每次都是两个都查询。使用两表联合查询.
使用resultMap 实现关联集合对象(N+1)
- 在
Teacher
中添加List<Student>
public class Teacher {
private int id1;
private String name1;
private List<Student> studentList;
}
2.在 StudentMapper.xml
中添加通过 tid 查询
<select id="selById" parameterType="int" resultType="student"><!-- resultMap="stumap" 会内存溢出 -->
select * from student where tid =#{0}
</select>
3.在 TeacherMapper.xml
中添加查询全部
<!-- 使用<resultMap>查询关联集合对象(表N+1次查询方式) -->
<resultMap type="teacher" id="mymap1">
<id column="id" property="id1" />
<result column="name" property="name1" />
<!-- 如果关联一个集合对象就是用这个标签 -->
<collection property="studentList" select="com.mapper.StudentMapper.selById" column="id"></collection>
</resultMap>
<select id="selAll1" resultMap="mymap1">
select * from teacher
</select>
使用
SqlSession sqlSession = sqlSessionFactory.openSession();
/* 使用<resultMap>查询关联 集合对象(表N+1次查询方式) */
List<Teacher> list3 = sqlSession.selectList("com.mapper.TeacherMapper.selAll1");
for (Teacher teacher : list3) {
System.out.println(teacher);
}
sqlSession.close();
结果
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,592 ==> Preparing: select * from teacher
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,667 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,779 ====> Preparing: select * from student where tid =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,782 ====> Parameters: 1(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,800 <==== Total: 6
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,801 ====> Preparing: select * from student where tid =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,802 ====> Parameters: 2(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,808 <==== Total: 5
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,810 ====> Preparing: select * from student where tid =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,811 ====> Parameters: 4(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,814 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,816 ====> Preparing: select * from student where tid =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,818 ====> Parameters: 5(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,820 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,823 ====> Preparing: select * from student where tid =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,824 ====> Parameters: 6(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,828 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,829 ====> Preparing: select * from student where tid =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,830 ====> Parameters: 7(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,834 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,835 ====> Preparing: select * from student where tid =?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,837 ====> Parameters: 8(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,839 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:45:08,841 <== Total: 7
Teacher [id1=1, name1=老师1, studentList=[Student [id=1, name=学生1, age=1, tid=1, teacher=null], Student [id=2, name=学生2, age=2, tid=1, teacher=null], Student [id=3, name=学生3, age=3, tid=1, teacher=null], Student [id=4, name=学生4, age=4, tid=1, teacher=null], Student [id=5, name=学生5, age=5, tid=1, teacher=null], Student [id=6, name=学生6, age=6, tid=1, teacher=null]]]
Teacher [id1=2, name1=老师2, studentList=[Student [id=7, name=学生7, age=7, tid=2, teacher=null], Student [id=8, name=学生8, age=8, tid=2, teacher=null], Student [id=9, name=学生9, age=9, tid=2, teacher=null], Student [id=10, name=学生10, age=10, tid=2, teacher=null], Student [id=11, name=学生11, age=11, tid=2, teacher=null]]]
Teacher [id1=4, name1=老师4, studentList=[]]
Teacher [id1=5, name1=老师4, studentList=[]]
Teacher [id1=6, name1=老师4, studentList=[]]
Teacher [id1=7, name1=老师4, studentList=[]]
Teacher [id1=8, name1=老师4, studentList=[]]
使用resultMap 实现关联集合对象(联合查询)
- 在
teacherMapper.xml
中添加
<!-- 使用<resultMap>实现加载集合数据(联合查询) -->
<resultMap id="mymap2" type="teacher" >
<id column="tid" property="id1" />
<result column="tname" property="name1" />
<collection property="studentList" ofType="Student">
<id column="sid" property="id" />
<result column="sname" property="name" />
<result column="age" property="age" />
<result column="tid" property="tid" />
</collection>
</resultMap>
<select id="selAll2" resultMap="mymap2">
select t.id tid,t.name tname,s.id sid,s.name sname, age,tid
from teacher t
left join student s
on t.id=s.tid;
</select>
mybatis
可以通过主键判断对象是否被加载过,不需要担心创建重复Teacher
使用
SqlSession sqlSession = sqlSessionFactory.openSession();
/* 使用<resultMap>实现加载集合数据(联合查询) */
List<Teacher> list4 = sqlSession.selectList("com.mapper.TeacherMapper.selAll2");
for (Teacher teacher : list4) {
System.out.println(teacher);
}
sqlSession.close();
结果
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:49:59,221 ==> Preparing: select t.id tid,t.name tname,s.id sid,s.name sname, age,tid from teacher t left join student s on t.id=s.tid;
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:49:59,363 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:49:59,489 <== Total: 16
Teacher [id1=1, name1=老师1, studentList=[Student [id=1, name=学生1, age=1, tid=1, teacher=null], Student [id=2, name=学生2, age=2, tid=1, teacher=null], Student [id=3, name=学生3, age=3, tid=1, teacher=null], Student [id=4, name=学生4, age=4, tid=1, teacher=null], Student [id=5, name=学生5, age=5, tid=1, teacher=null], Student [id=6, name=学生6, age=6, tid=1, teacher=null]]]
Teacher [id1=2, name1=老师2, studentList=[Student [id=7, name=学生7, age=7, tid=2, teacher=null], Student [id=8, name=学生8, age=8, tid=2, teacher=null], Student [id=9, name=学生9, age=9, tid=2, teacher=null], Student [id=10, name=学生10, age=10, tid=2, teacher=null], Student [id=11, name=学生11, age=11, tid=2, teacher=null]]]
Teacher [id1=4, name1=老师4, studentList=[Student [id=0, name=null, age=0, tid=4, teacher=null]]]
Teacher [id1=5, name1=老师4, studentList=[Student [id=0, name=null, age=0, tid=5, teacher=null]]]
Teacher [id1=6, name1=老师4, studentList=[Student [id=0, name=null, age=0, tid=6, teacher=null]]]
Teacher [id1=7, name1=老师4, studentList=[Student [id=0, name=null, age=0, tid=7, teacher=null]]]
Teacher [id1=8, name1=老师4, studentList=[Student [id=0, name=null, age=0, tid=8, teacher=null]]]
使用Auto Mapping 结合别名实现多表查询.
只能使用多表联合查询方式.
实现方式
在SQL是关键字符,两侧添加反单引号
<select id="selAll1" resultType="student">
select t.id `teacher.id1`,t.name`teacher.name1`,s.id id,s.name name,age,tid
from student s
left join teacher t
on t.id=s.tid;
</select>
使用
SqlSession sqlSession = sqlSessionFactory.openSession();
/* 使用auto mapping 结合别名实现多表查询(只能使用多表联合查询) 要求查询的列名和表字段名一样 只能单个对象,集合对象不行 */
List<Student> list5 = sqlSession.selectList("com.mapper.StudentMapper.selAll1");
for (Student student : list5) {
System.out.println(student);
}
sqlSession.close();
结果
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:54:52,100 ==> Preparing: select t.id `teacher.id1`,t.name`teacher.name1`,s.id id,s.name name,age,tid from student s left join teacher t on t.id=s.tid;
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:54:52,200 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 22:54:52,297 <== Total: 11
Student [id=1, name=学生1, age=1, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=2, name=学生2, age=2, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=3, name=学生3, age=3, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=4, name=学生4, age=4, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=5, name=学生5, age=5, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=6, name=学生6, age=6, tid=1, teacher=Teacher [id1=1, name1=老师1, studentList=null]]
Student [id=7, name=学生7, age=7, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=8, name=学生8, age=8, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=9, name=学生9, age=9, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=10, name=学生10, age=10, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
Student [id=11, name=学生11, age=11, tid=2, teacher=Teacher [id1=2, name1=老师2, studentList=null]]
使用注解 关联单个对象(N+1方式)
@Results(value= {
@Result(id=true,column="id",property="id"),
@Result(property="name",column="name"),
@Result(property="age",column="age"),
@Result(property="tid",column="tid"),
@Result(property="teacher",column="tid",one=@One(select="com.mapper.TeacherMapper.selById"))
})
@Select("select * from student")
List<Student> selAll1();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
List<Student> selAll2 = studentMapper.selAll1();
for (Student student : selAll2) {
System.out.println(student);
}
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,957 ==> Preparing: select * from student
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,957 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,961 ====> Preparing: select * from teacher where id=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,962 ====> Parameters: 1(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,965 <==== Total: 1
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,974 ====> Preparing: select * from teacher where id=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,978 ====> Parameters: 2(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,981 <==== Total: 1
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:14:15,984 <== Total: 11
Student [id=1, name=学生1, age=1, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=2, name=学生2, age=2, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=3, name=学生3, age=3, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=4, name=学生4, age=4, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=5, name=学生5, age=5, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=6, name=学生6, age=6, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=7, name=学生7, age=7, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=8, name=学生8, age=8, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=9, name=学生9, age=9, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=10, name=学生10, age=10, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=11, name=学生11, age=11, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
使用注解 关联单个对象(联合查询方式)
@Select("select t.id `teacher.id` ,t.name `teacher.name` ,s.id id ,s.name name ,age ,tid from student s left join teacher t on t.id=s.tid")
List<Student> selAll();
使用
StudentMapper studentMapper1 = sqlSession.getMapper(StudentMapper.class);
List<Student> selAll21 = studentMapper1.selAll();
for (Student student : selAll21) {
System.out.println(student);
}
结果
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:16:41,879 ==> Preparing: select t.id `teacher.id`,t.name`teacher.name`,s.id id,s.name name, age,tid from student s left join teacher t on t.id=s.tid
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:16:41,879 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:16:41,889 <== Total: 11
Student [id=1, name=学生1, age=1, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=2, name=学生2, age=2, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=3, name=学生3, age=3, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=4, name=学生4, age=4, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=5, name=学生5, age=5, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=6, name=学生6, age=6, tid=1, teacher=Teacher [id=1, name=老师1, studentList=null]]
Student [id=7, name=学生7, age=7, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=8, name=学生8, age=8, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=9, name=学生9, age=9, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=10, name=学生10, age=10, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
Student [id=11, name=学生11, age=11, tid=2, teacher=Teacher [id=2, name=老师2, studentList=null]]
使用注解 关联集合对象(N+1方式)
public interface TeacherMapper {
@Results(value= {
@Result(id=true,column="id",property="id"),
@Result(property="name",column="name"),
@Result(property="studentList",column="id",many=@Many(select="com.mapper.StudentMapper.selById"))
})
@Select("select * from teacher")
List<Teacher> selAll1();
}
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> selAll1 = teacherMapper.selAll1();
for (Teacher teacher : selAll1) {
System.out.println(teacher);
}
结果
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,078 ==> Preparing: select * from teacher
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,165 ==> Parameters:
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,226 ====> Preparing: select * from student where tid=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,227 ====> Parameters: 1(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,242 <==== Total: 6
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,244 ====> Preparing: select * from student where tid=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,244 ====> Parameters: 2(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,248 <==== Total: 5
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,250 ====> Preparing: select * from student where tid=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,250 ====> Parameters: 4(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,252 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,253 ====> Preparing: select * from student where tid=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,254 ====> Parameters: 5(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,255 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,257 ====> Preparing: select * from student where tid=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,258 ====> Parameters: 6(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,261 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,262 ====> Preparing: select * from student where tid=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,263 ====> Parameters: 7(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,264 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,265 ====> Preparing: select * from student where tid=?
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,267 ====> Parameters: 8(Integer)
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,269 <==== Total: 0
- org.apache.ibatis.logging.jdbc.BaseJdbcLogger 2021-08-26 23:11:41,270 <== Total: 7
Teacher [id=1, name=老师1, studentList=[Student [id=1, name=学生1, age=1, tid=1, teacher=null], Student [id=2, name=学生2, age=2, tid=1, teacher=null], Student [id=3, name=学生3, age=3, tid=1, teacher=null], Student [id=4, name=学生4, age=4, tid=1, teacher=null], Student [id=5, name=学生5, age=5, tid=1, teacher=null], Student [id=6, name=学生6, age=6, tid=1, teacher=null]]]
Teacher [id=2, name=老师2, studentList=[Student [id=7, name=学生7, age=7, tid=2, teacher=null], Student [id=8, name=学生8, age=8, tid=2, teacher=null], Student [id=9, name=学生9, age=9, tid=2, teacher=null], Student [id=10, name=学生10, age=10, tid=2, teacher=null], Student [id=11, name=学生11, age=11, tid=2, teacher=null]]]
Teacher [id=4, name=老师4, studentList=[]]
Teacher [id=5, name=老师4, studentList=[]]
Teacher [id=6, name=老师4, studentList=[]]
Teacher [id=7, name=老师4, studentList=[]]
Teacher [id=8, name=老师4, studentList=[]]
使用注解 关联集合对象(联合查询方式)
待定
MyBatis实现一对一有几种方式?具体怎么操作的?
联合查询:几个表联合查询,只查询一次,通过在resultMap里面配置;
N+1查询是先查一个表,根据这个表里面的结果作为外键id,去再另外一个表里面查询数据。
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别? 还没理解
能,
Mybatis不仅可以执行一对一、一对多的关联查询,还可以执行多对一,多对多的关联查询
一对一,一个学生对一个老师
一对多,一个老师对多个学生
多对一查询,其实就是一对一查询,只需要把selectList()修改为selectOne()即可;
多对多查询,其实就是一对多查询,只需要把selectOne()修改为selectList()即可。