1,什么是Mybatis
1 Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时
只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建
statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性
能,灵活度高。
2 MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数
据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过
java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最
后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返
回 result 的过程)。
2,Mybatis的优点
1、简单易学。mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar加配置几个sql映射文件,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现;
2、灵活。mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多;
3、解除sql与程序代码的耦合。通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性;
4、提供映射标签,支持对象与数据库的orm字段关系映射;
5、提供对象关系映射标签,支持对象关系组建维护;
6、提供xml标签,支持编写动态sql。
3,Mybatis的缺点
SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写
SQL 语句的功底有一定要求。
SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
4,MyBatis框架适用场合:
(1)MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
(2)对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
5,#{}和${}的区别是什么?
1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id".
$将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by 111, 如果传入的值是id,则解析成的sql为order by id.
2. #方式能够很大程度防止sql注入。
$方式无法防止Sql注入。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;Mybatis在处理${}时,就是把${}替换成变量的值。
select * from emp where empno = 7360 or 1=1 -- ${}
select * from emp where empno = '7369' -- #{}
没有特殊需求情况下,都要使用#{}
6,Mybatis执行流程
1、 mybatis配置
SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、 MappedStatement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
6、 MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
7、 MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
7,Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列别名书写为对象属性名,有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
(数据库中部门对象列为dname,实体为deptName select deptno,dname deptName,loc from dept )
8,Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
答:Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,多对一,collection指的就是一对多,多对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
延迟加载代码实现(一对多collection 查询部门时,查出该部门的员工):
实体:Dept Emp
需要在Dept中加入private List<Emp> empList; 和getter setter
dao:DeptDao
/**
* 查询所有部门
* @return
*/
List<Dept> listAll();
EmpDao:
/**
* 根据部门编号获取员工
* @param deptNo
* @return
*/
List<Emp> listEmpByDeptNo(int deptNo);
配置文件:DeptMapper.xml :
<!--查询所有部门-->
<select id="listAll" resultMap="deptEmp">
select * from dept
</select>
<!--映射-->
<resultMap id="deptEmp" type="com.aaa.mybatis.entity.Dept">
<id property="deptNo" column="deptno"/>
<result property="deptName" column="deptname"/>
<result property="loc" column="loc"/>
<collection property="empList" column="deptno" ofType="com.aaa.mybatis.entity.Emp"
select="com.aaa.mybatis.dao.EmpDao.listEmpByDeptNo">
</collection>
</resultMap>
EmpMapper.xml:
<!--根据部门编号获取员工-->
<select id="listEmpByDeptNo" resultType="com.aaa.mybatis.entity.Emp">
select empno,empname,salary from emp where deptno=#{deptNo}
</select>
主配置文件:mybatis-config.xml 中的 settings
<!--开启全局懒加载-->
<setting name="lazyLoadingEnabled" value="true"></setting>
测试:
/**
* 测试延时加载
*/
@Test
public void testListAll(){
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFacotryUtil.getSqlSession();
DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
List<Dept> depts = deptDao.listAll();
if(depts!=null&&depts.size()>0){
for (Dept dept : depts) {
dept.getEmpList();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(sqlSession!=null)
sqlSession.close();
}
}
如果下面代码注释掉,开启懒加载,底层查询数据库时,只执行了部门查询,如果不开启懒加载,部门和该部门的员工都查询了
if(depts!=null&&depts.size()>0){
for (Dept dept : depts) {
dept.getEmpList();
}
}
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器intercept()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。(ctrl+n ProxyFactory 实现类:JavassistProxyFactory 的invoke和CglibProxyFactory的 intercept)
https://my.oschina.net/wenjinglian/blog/1857581?from=singlemessage
(Javaassist 就是一个用来 处理 Java 字节码的类库。)
当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
9,模糊查询like语句该怎么写?
在Java代码中添加sql通配符。
List<Dept> depts = deptDao.listByLike("%dev%");
mapper中:
<!--模糊查询 like-->
<select id="listByLike" parameterType="string" resultType="com.aaa.mybatis.entity.Dept">
select deptno,dname as deptName,loc from dept where dname like #{param}
</select>
在sql语句中拼接通配符
select deptno,dname as deptName,loc from dept where dname like '%${param}%'
'%#{param}%' 写法错误的
select deptno,dname as deptName,loc from dept where dname like concat('%',# {param},'%')
oracle : '%'||# {param}||'%' concat(concat('%',#{param}),'%')
mysql: concat('%',# {param},'%')
mysql 或者oracle '%${param}%'
10,请问MyBatis中的动态SQL是什么意思?
对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,需要根据用户指定的条件动态生成SQL语句。如果不使用持久层框架我们可能需要自己拼装SQL语句,还好MyBatis提供了动态SQL的功能来解决这个问题。MyBatis中用于实现动态SQL的元素主要有:
- if
- choose / when / otherwise
- trim
- where
- set
- foreach
11,请说明一下MyBatis中命名空间(namespace)的作用是什么?
在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。
12,如何执行批量插入?
SQL 基础:
-- 批量插入
-- mysql
insert into dept values (18,'dt1','1floor'),(19,'dt1','1floor'),(20,'dt1','1floor');
-- oracle
insert into dept
select 18,'dt1','1floor' from dual UNION
select 19,'dt1','1floor' from dual UNION
select 20,'dt1','1floor' from dual
dao:
/**
* 批量插入
* @param depts
* @return
*/
int batchAdd(List<Dept> depts);
mapper:
<!--批量插入-->
<insert id="batchAdd">
insert into dept values
<foreach collection="list" item="dept" index="i" separator=",">
(null,#{dept.deptName},#{dept.loc})
</foreach>
</insert>
测试:
List<Dept> deptList = new ArrayList<Dept>(5);
Dept dept = new Dept();
dept.setDeptName("team33");
dept.setLoc("33floor");
deptList.add(dept);
Dept dept1 = new Dept();
dept1.setDeptName("team44");
dept1.setLoc("44floor");
deptList.add(dept1);
Dept dept2 = new Dept();
dept2.setDeptName("team55");
dept2.setLoc("55floor");
deptList.add(dept2);
Dept dept3 = new Dept();
dept3.setDeptName("team66");
dept3.setLoc("66floor");
deptList.add(dept3);
Dept dept4 = new Dept();
dept4.setDeptName("team77");
dept4.setLoc("77floor");
deptList.add(dept4);
int i = deptDao.batchAdd(deptList);
sqlSession.commit();
13,如何获取自动生成的(主)键值?
MySQL:Mapper文件insert语句设置 useGeneratedKeys="true" keyProperty="id"
<!--添加部门并返回自增ID-->
<insert id="add" useGeneratedKeys="true" keyProperty="deptNo">
insert into dept values(null,#{deptName},#{loc})
</insert>
自增生成之后的值赋值实体中的属性deptNo
Oracle:Mapper文件insert语句增加
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
select xxx_SEQ.nextval from dual
</selectKey>
14,为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
hql:" from Emp" Emp 实体类
session.save(emp);
session.saveOrUpdate(emp);
session.update(emp);
session.deleteByke(empno);
session.delete(emp);
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的(hibernate可以操作实体直接查询,不需要手写语句)。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
15,在mapper中如何传递多个参数?
(1)第一种:#{0}或者是#{arg0} 看是否是3.4.2及之前#{0} 或者之后#{arg0}
dao:
/**
* 添加,传递多个参数
* @param dname
* @param loc
* @return
*/
int addA(String dname,String loc);
mapper:
<!-- 添加,多个参数 3.4.2版本之前#{0} 之后#{arg0} https://www.cnblogs.com/zhangmingcheng/p/9922236.html-->
<insert id="addA">
insert into dept values(null,#{arg0},#{arg1})
</insert>
(2)第二种: 使用 @param 注解:
dao:
/**
* 添加,@Param传递多个参数
* @param dname
* @param loc
* @return
*/
int addB(@Pa <!--添加,@Param传递多个参数-->
mapper:
<insert id="addB">
insert into dept values(null,#{deptName},#{loc})
</insert>ram("deptName") String dname,@Param("loc") String loc);
(3)第三种:多个参数封装成map或者是实体
dao方法:
/**
* 添加
* @param dept
* @return
*/
int add(Dept dept);
mapper:
<!--添加部门并返回自增ID-->
<insert id="add" useGeneratedKeys="true" keyProperty="deptNo">
insert into dept values(null,#{deptName},#{loc})
</insert>
15, Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
原因就是namespace+id是作为Map<String, MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
但是,在以前的Mybatis版本的namespace是可选的,不过新版本的namespace已经是必须的了。
一对一、一对多的关联查询 ?
参考以前上课时的例子(参考刚才讲的懒加载的例子也行)
16, Mybatis的一级、二级缓存
参考项目(mybatis_interview_cache)
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新
17,使用MyBatis的mapper接口调用时有哪些要求?
① Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同(配置文件中可以省略,如果写,必须相同);
③ Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同(通过resultMap也可以,更新方法没有返回值配置的);
④ Mapper.xml文件中的namespace即是mapper接口的类路径。
18, Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?
还有很多其他的标签,<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上 动态 sql 的 9 个标签,trim|where|set|foreach|if|choose|when|otherwise|bind 等,其中<sql>为 sql 片段标签,通过<include>标签引入 sql 片段,<selectKey>为不支持自增的主键生成策略标签。
19,通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao 接口,就是人们常说的 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有 实 现 类 的 , 当 调 用 接 口 方 法 时 , 接 口 全 限 名 + 方 法 名 拼 接 字 符 串 作 为 key 值 , 可 唯 一 定 位 一 个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到
namespace 为 com.mybatis3.mappers.StudentDao 下 面 id = findStudentById 的
MappedStatement。在 Mybatis 中,每一个<select>、<insert>、<update>、<delete>标签,都会被解
析为一个 MappedStatement 对象。
Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行MappedStatement 所代表的 sql,然后将 sql 执行结果返回。
DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
https://www.cnblogs.com/hopeofthevillage/p/11384848.html
20 Mybatis都有哪些Executor执行器?它们之间的区别是什么?
答:Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
21 Mybatis中如何指定使用哪一种Executor执行器?
答:在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。
// sqlSessionFactory.openSession(ExecutorType.SIMPLE);
// sqlSessionFactory.openSession(ExecutorType.REUSE);
// sqlSessionFactory.openSession(ExecutorType.BATCH);
22 Mybatis是如何进行分页的?分页插件的原理是什么?PagerHelper
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
mybatis分页插件(非官网)
1,添加jar包
<!-- 分页插件pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-autoconfigure</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency> <!-- 分页插件pagehelper -->
2,添加springboot配置
https://pagehelper.github.io/
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
#分页插件
#helperDialect属性来指定分页插件使用哪种方言
pagehelper.helper-dialect=oracle
#当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。
pagehelper.reasonable=true
#支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
pagehelper.support-methods-arguments=true
#增加了该参数来配置参数映射,用于从对象中根据属性名取值
pagehelper.params=count=countSql
3,具体使用
与以前controller区别
//设置当前第几页和每页显示数量 PageHelper.startPage(Integer.valueOf(map.get("pageNo")+""),Integer.valueOf(map.get("pageSize")+""));
//用PageInfo对结果进行包装
PageInfo<Map> pageInfo =new PageInfo<Map>(newsService.getList());
/**
* 分页部门查询
* @param map
* @return
*/
@ResponseBody
@RequestMapping("page")
public Object page(@RequestParam Map map){
int pageNo = Integer.valueOf(map.get("pageNo")+"");
int pageSize = Integer.valueOf(map.get("pageSize")+"");
//初始化配置
PageHelper.offsetPage(pageNo,pageSize);
PageInfo<Map> pageInfo = new PageInfo<Map>(deptService.getList());
//如果使用easyui可以这样封装,其他框架,自己根据pageInfo解析
Map tmap = new HashMap();
tmap.put("total",pageInfo.getTotal());
tmap.put("rows",pageInfo.getList());
return tmap;
}