四. MyBatis的框架架构设计是怎么样的
这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。
- 加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
- SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
- SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
- 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
五. 什么是DBMS
DBMS: 数据库管理系统(database management system)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数zd据库,简称dbms。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。用户通过dbms访问数据库中的数据,数据库管理员也通过dbms进行数据库的维护工作。它可使多个应用程序和用户用不同的方法在同时版或不同时刻去建立,修改和询问数据库。DBMS提供数据定义语言DDL(Data Definition Language)与数据操作语言DML(DataManipulation Language),供用户定义数据库的模式结构与权限约束,实现对数据的追加权、删除等操作。
六. 为什么需要预编译
定义
- SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
为什么需要预编译
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。
还有一个重要的原因,复制SQL注入
七. 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生命周期范围内。
八. Mybatis中如何指定使用哪一种Executor执行器?
- 在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
- 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(preparedstatements); BATCH 执行器将重用语句并执行批量更新。
九. Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
- Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,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,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的
映射器
一. #{} 和 ${} 的区别
#{}
是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。- Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
#{}
可以有效的防止SQL注入,提高系统安全性;${}
不能防止SQL 注入。
#{}
的变量替换是在DBMS 中;${}
的变量替换是在 DBMS 外。
二. 模糊查询like语句该怎么写
’%${question}%’
可能引起SQL注入,不推荐"%"#{question}"%"
注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ '
,所以这里%
需要使用双引号" "
,不能使用单引号' '
,不然会查不到任何结果。CONCAT(’%’,#{question},’%’)
使用CONCAT()
函数,(推荐)- 使用bind标签(不推荐)
<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User"> <bind name="pattern" value="'%' + username + '%'" /> select id,sex,age,username,password from person where username LIKE #{pattern} </select>
三. 在mapper中如何传递多个参数
方法1:顺序传参法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap"> select * from userwhere user_name = #{0} and dept_id = #{1} </select>
- #{}里面的数字代表传入参数的顺序。
- 这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。
方法2:@Param注解传参法
public User selectUser(@Param("userName") String name, @Param("deptId") int deptId);
<select id="selectUser" resultMap="UserResultMap"> select * from userwhere user_name = #{userName} and dept_id = #{deptId} </select>
- #{}里面的名称对应的是注解@Param括号里面修饰的名称。
- 这种方法在参数不多的情况还是比较直观的,(推荐使用)。
方法3:Map传参法
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap"> select * from userwhere user_name = #{userName} and dept_id = #{deptId} </select>
- #{}里面的名称对应的是Map里面的key名称。
- 这种方法适合传递多个参数,且参数易变能灵活传递的情况。(推荐使用)。
方法4:Java Bean传参
public User selectUser(User user);
<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap"> select * from userwhere user_name = #{userName} and dept_id = #{deptId} </select>
- #{}里面的名称对应的是User类里面的成员属性。
- 这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。(推荐使用)。