# Mybatis面试问题
# 什么是Mybatis
- Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时 只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性 能,灵活度高。
- MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数 据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
- 通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最 后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返 回 result 的过程)
# MyBatis的优点
- 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任 何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
- 与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不 需要手动开关连接;
- 很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持)。
- 能够与 Spring 很好的集成;
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射 标签,支持对象关系组件维护。
# MyBatis框架的缺点
- SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求。
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
# MyBatis框架适用场合
- MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。
- 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis 将是不错的选择。
# MyBatis与Hibernate有哪些不同?
- Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要 程序员自己编写 Sql 语句。
- Mybatis 直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高,非常 适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需 求变化要求迅速输出成果。但是灵活的前提是 mybatis 无法做到数据库无关性, 如果需要实现支持多种数据库的软件,则需要自定义多套 sql 映射文件,工作量大。
- Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
# 当实体类中的属性名和表中的字段名不一样,怎么办?
- 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id="selectorder" parametertype="int" resultetype="net.chenqiong.shop.order">
select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>
- 自定义resultMap标签,设置字段和属性的对应关系。
<select id="getOrder" parameterType="int" resultMap="orderresultmap">
select * from orders where order_id=#{id}
</select>
<resultMap type="net.chenqiong.shop.order" id="orderresultmap">
<!–用 id 属性来映射主键字段–>
<id property="id" column="order_id">
<!–用 result 属性来映射非主键字段,property 为实体类属性名,column为数据表中的属性–>
<result property = "orderno" column ="order_no"/>
<result property="price" column="order_price" />
</reslutMap>
# 模糊查询like语句该怎么写?
在 Java 代码中添加 sql 通配符。
string wildcardname = "%smi%";
list<name> names = mapper.selectlike(wildcardname);
<select id="selectlike">
select * from foo where bar like #{value}
</select>
在 sql 语句中拼接通配符,会引起 sql 注入
string wildcardname = "smi";
list<name> names = mapper.selectlike(wildcardname);
<select id="selectlike">
select * from foo where bar like "%"#{value}"%"
</select>
# Mybatis的一级、二级缓存?
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后, 该 Session 中的所有 Cache 就 将清空,默认打开一级缓存。
- 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置;
- 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
# 简述Mybatis的插件运行原理?
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。 编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
# Mybatis的一对一、一对多的关联查询?
<mapper namespace="com.lcb.mapping.userMapper">
<!--association 一对一关联查询 -->
<select id="getClass" parameterType="int" resultMap="ClassesResultMap">
select * from class c,teacher t where c.teacher_id=t.t_id and
c.c_id=#{id}
</select>
<resultMap type="com.lcb.user.Classes" id="ClassesResultMap">
<!-- 实体类的字段名和数据表的字段名映射 -->
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" javaType="com.lcb.user.Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
</resultMap>
<!--collection 一对多关联查询 -->
<select id="getClass2" parameterType="int" resultMap="ClassesResultMap2">
select * from class c,teacher t,student s where c.teacher_id=t.t_id
and c.c_id=s.class_id and c.c_id=#{id}
</select>
<resultMap type="com.lcb.user.Classes" id="ClassesResultMap2">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" javaType="com.lcb.user.Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
<collection property="student" ofType="com.lcb.user.Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
</collection>
</resultMap>
</mapper>
# Mybatis不同的 Xml 映射文件,id 是否可以重复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;原因就是 namespace+id 是作为 Map的 key使用的, 如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。
# Xml 映射文件中标签有那些?
<select>、<insert>、<update>、<delete>、<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>。
# Mybatis动态sql有什么用?
Mybatis 动态 sql 可以在 Xml 映射文件内,以标签的形式编写动态 sql,执行原理 是根据表达式的值 完成逻辑判断并动态拼接 sql 的功能。
Mybatis 提供了 9 种动态 sql 标签:<trim> 、 <where> 、< set >、<foreach> 、<if> 、 <choose> 、< when> 、<otherwise> 、 <bind>。
加上动态 sql 的 9 个标签,其中为 sql 片段标签,通过标签引入 sql 片段,为不支持自增的主键生成策略标签。
# Mybatis的流程理解?
MyBatis配置文件
- config.xml:配置了全局配置文件,配置了MyBatis的运行环境等信息。
- mapper,xml:sql的映射文件,配置了操作数据库的sql语句,此文件需在config.xml中加载。
SqlSessionFactory
通过MyBatis环境等配置信息构造SqlSessionFactory(会话工厂)。
SqlSession
通过会话工厂创建SqlSession(会话),对数据库进行增删改查操作。
Exector执行器
MyBatis底层自定义了Exector执行器接口来具体操作数据库,Exector接口有两个实现,一个基本执行器(默认),一个是缓存执行器,SqlSession底层是通过Exector接口操作数据库。
MappedStatement
- MyBatis的一个底层封装对象,它包装了MyBatis配置信息与sql映射信息等。mapper.xml中的insert/select/update/delete标签对应一个MappedStatement对象。标签的id就是MappedStatement的id。
- MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo、Executor通过MappedStatement在执行sql前将输入的Java对象映射至sql中,输入参数映射就是JDBC编程对preparedStatement设置参数。
- MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射就是JDBC编程对结果的解析处理过程。
# 使用 MyBatis的mapper接口调用时有哪些要求?
- Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同;
- Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同;
- Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同;
- Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径。
# 阐述Session加载实体对象的过程?
- Session在调用数据库查询功能之前,首先会在一级缓存中通过实体类型和主键进行查找,如果一级缓存查找命中且数据状态合法,则直接返回;
- 如果一级缓存没有命中,接下来Session会在当前NonExists记录(相当于一个查询黑名单,如果出现重复的无效查询可以迅速做出判断,从而提升性能)中进行查找,如果NonExists中存在同样的查询条件,则返回null;
- 如果一级缓存查询失败查询二级缓存,如果二级缓存命中直接返回;
- 如果之前的查询都未命中,则发出SQL语句,如果查询未发现对应记录则将此次查询添加到Session的NonExists中加以记录,并返回null;
- 根据映射配置和SQL语句得到ResultSet,并创建对应的实体对象;
- 将对象纳入Session(一级缓存)的管理;
- 如果有对应的拦截器,则执行拦截器的onLoad方法;
- 如果开启并设置了要使用二级缓存,则将数据对象纳入二级缓存;
- 返回数据对象。
# 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批处理相同。
# 什么是MyBatis的接口绑定?有哪些实现方式?
接口绑定:就是在 MyBatis 中任意定义接口,然后把接口里面的方法和 SQL 语句绑定, 我们直接调用接口方法就可以,这样比起原来了 SqlSession 提供的方法我们可以有更加灵活的选择和设置。
# 接口绑定有两种实现方式:
- 一种是通过注解绑定,就是在接口的方法上面加上@Select、@Update 等注解,里面包含 Sql 语句来绑定;
- 另外一种就是通过 xml里面写 SQL 来绑定, 在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名。当 Sql 语句比较简单时候,用注解绑定, 当 SQL 语句比较复杂时候,用 xml 绑定,一般用 xml 绑定的比较多。
# 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 实现一对一有几种方式?具体怎么操作的?
- 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap 里面配置 association 节点配置一对一的类就可以完成;
- 嵌套查询是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置。
# 怎么解决SQL注入的方法
- 使用#{}而不是 ${}:在MyBatis中,使用#{}而不是${},可以很大程度防止sql注入。因为#{}是一个参数占位符,对于字符串类型,会自动加上"",其他类型不加。由于Mybatis采用预编译,其后的参数不会再进行SQL编译,所以一定程度上防止SQL注入。${}是一个简单的字符串替换,字符串是什么,就会解析成什么,存在SQL注入风险
- 不要暴露一些不必要的日志或者安全信息,比如避免直接响应一些sql异常信息:如果SQL发生异常了,不要把这些信息暴露响应给用户,可以自定义异常进行响应。
- 过滤参数中含有的一些数据库关键词:可以加个参数校验过滤的方法,过滤union,or等数据库关键词
- 适当的权限控制:在你查询信息时,先校验下当前用户是否有这个权限。比如说,实现代码的时候,可以让用户多传一个企业Id什么的,或者获取当前用户的session信息等,在查询前,先校验一下当前用户是否是这个企业下的等等,是的话才有这个查询员工的权限。
# Mybatis是如何进行分页的?
数据进行分页是最基础的功能,一般可以把分页分成两类:
- 逻辑分页,先查询出所有的数据缓存到内存,再根据业务相关需求,从内存数据中筛选出合适的数据进行分页。
- 物理分页 ,直接利用数据库支持的分页语法来实现,比如Mysql里面提供了分页关键词Limit。
Mybatis提供了四种分页方式:
- 在Mybatis Mapper配置文件里面直接写分页SQL,这种方式比较灵活,实现也简单。
- RowBounds实现逻辑分页,也就是一次性加载所有符合查询条件的目标数据,根据分页参数值在内存中实现分页。 当然,在数据量比较大的情况下,JDBC驱动本身会做一些优化,也就是不会把所有结果存储在ResultSet里面,而是只加载一部分数据,再根据需求去数据库里面加载。这种方式不适合数据量较大的场景,而且有可能会频繁访问数据库造成比较大的压力。
- Interceptor拦截器实现,通过拦截需要分页的select语句,然后在这个sql语句里面动态拼接分页关键字,从而实现分页查询。
- 拦截执行器方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截SQL语法构建的处理
这种方式的好处,就是可以提供统一的处理机制,不需要我们再单独去维护分页相关的功能。 4. 插件(PageHelper)及(MyBaits-Plus、tkmybatis)框架实现,这些插件本质上也是使用Mybatis的拦截器来实现的。 只是他们帮我们实现了扩展和封装,节省了分页扩展封装的工作量,在实际开发中,只需要拿来即用即可。
总结一下,对于任何ORM框架,分页的实现逻辑无外乎两种,不管怎么包装,最终给到开发者的,只是使用上的差异而已。
我认为有三种方式来实现分页:
- 第一种,直接在Select语句上增加数据库提供的分页关键字,然后在应用程序里面传递当前页,以及每页展示条数即可。
- 第二种,使用Mybatis提供的RowBounds对象,实现内存级别分页。
- 第三种,基于Mybatis里面的Interceptor拦截器,在select语句执行之前动态拼接分页关键字。
# Mybatis中#{}和${}的区别是什么?
首先,Mybatis提供到的#号占位符和$号占位符,都是实现动态SQL的一种方式,通过这两种方式把参数传递到XML之后,在执行操作之前,Mybatis会对这两种占位符进行动态解析。
#号占位符,等同于jdbc里面的?号占位符。它相当于向PreparedStatement中的预处理语句中设置参数,而PreparedStatement中的sql语句是预编译的, SQL语句中使用了占位符,规定了sql语句的结构。并且在设置参数的时候,如果有特殊字符,会自动进行转义。所以#号占位符可以防止SQL注入。
而使用$的方式传参,相当于直接把参数拼接到了原始的SQL里面,Mybatis不会对它进行特殊处理。
# Mybatis中预编译原理
- 数据库接受到sql语句之后,需要词法和语义解析,优化sql语句,制定执行计划。
- 如果一条sql语句需要反复执行,每次都进行语法检查和优化,会浪费很多时间。
- 预编译语句就是将sql语句中的值用占位符替代,即将sql语句模板化。一次编译、多次运行,省去了解析优化等过程。
- mybatis是通过PreparedStatement和占位符来实现预编译的。
- mybatis底层使用PreparedStatement,默认情况下,将对所有的sql进行预编译。将#替换为?,然后将带有占位符?的sql模板发送至mysql服务器
- 由服务器对此无参数的sql进行编译后,将编译结果缓存,然后直接执行带有真实参数的sql。
# Mybatis中预编译的作用
- 预编译阶段可以优化 sql的执行。预编译之后的sql多数情况下可以直接执行,数据库服务器不需要再次编译,可以提升性能。
- 预编译语句对象可以重复利用。把一个sql预编译后产生的PreparedStatement 对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的PreparedState 对象。
- 防止SQL注入。使用预编译,而其后注入的参数将不会再进行SQL编译。也就是说其后注入进来的参数系统将不会认为它会是一条SQL语句,而默认其是一个参数。
# Mybatis缓存机制原理
首先,Mybatis里面设计了二级缓存来提升数据的检索效率,避免每次数据的访问都需要去查询数据库。
一级缓存,是SqlSession级别的缓存,也叫本地缓存,因为每个用户在执行查询的时候都需要使用SqlSession来执行, 为了避免每次都去查数据库,Mybatis把查询出来的数据保存到SqlSession的本地缓存中,后续的SQL如果命中缓存,就可以直接从本地缓存读取了。
如果想要实现跨SqlSession级别的缓存?那么一级缓存就无法实现了,因此在Mybatis里面引入了二级缓存,就是当多个用户 在查询数据的时候,只有有任何一个SqlSession拿到了数据就会放入到二级缓存里面,其他的SqlSession就可以从二级缓存加载数据。
每个一级缓存的具体实现原理是:
在SqlSession 里面持有一个Executor,每个Executor中有一个LocalCache对象。 当用户发起查询的时候,Mybatis会根据执行语句在Local Cache里面查询,如果没命中,再去查询数据库并写入到LocalCache,否则直接返回。 所以,以及缓存的生命周期是SqlSessiion,而且在多个Sqlsession或者分布式环境下,可能会导致数据库写操作出现脏数据。
二级缓存的具体实现原理是:
使用CachingExecutor装饰了Executor,所以在进入一级缓存的查询流程之前,会先通过CachingExecutor进行二级缓存的查询。 开启二级缓存以后,会被多个SqlSession共享,所以它是一个全局缓存。因此它的查询流程是先查二级缓存,再查一级缓存,最后再查数据库。 另外,MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时缓存粒度也能够到namespace级别, 并且还可以通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强。
# 请你介绍Mybatis的工作原理
- Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了JDBC,开发时 只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性 能,灵活度高。
- Mybatis的框架的初始化操作
- 处理SQL的流程请求。
public void test1() throwsException{
// 1.获取配置文价
Inputstream in=Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象,sqlsessionFactory 的实例我们没有通过DefaultsqlSessionFactory直接来获取,而是通过一个Builder对象来建造的.
// sqlsessionFactory 生产SqlSession对象的 SqlSessionFactory应该是单例//全局配'置文件和映射文件也只需要在系统启动的时候完成加载操作
// 通过建造者模式来构建复杂的对象:1.完成配置文件的加载解析 2.完成SqlSessionFactory的创建
sqlsessionFactory factory=new sqlsessionFactoryBuilder().build(in);
// 3.根据SqlsessionFactory对象获取Sqlsession对象
sqlsession sqlsession=factory.opensession();
// 4.通过Sqlsession中提供的API方法来操作数据库
List<User> list=sqlSession.selectList(s:"com.boge.mapper.UserHapper.selectUserList");
// 获取接口的代码对象﹑得到的其实是通过JDBC代理模式获取的一个代理对象
// UserHapper mapper = sqlSession.getMapper(UserMapper.class);l / PageHelper.startPage(1,2);
//List<User> list = mapper.selectUserList();
system.out.println("list.size() = "+list.size());system.out.println(" - ------------");
system.out.println("list.size() = "+list.size());
// 5.关闭会话
sqlsession.close() // 关闭session 清空一级缓存
// sqlSession = factory.opensession() ;
// mapper = sqlSession.getMapper(UserMapper.class);
// list = mapper.selectUserList() ;
// system .out.println( "list.size() =" + list.size() );
// sqlsession.close();
}
# MyBatis二级缓存的四个不推荐之处
在使用 MyBatis 框架时,可以选择开启二级缓存来提高性能。但是,使用 MyBatis 的二级缓存需要谨慎,并且在大多数情况下,并不推荐使用二级缓存。以下是一些原因:
数据不一致性: 二级缓存是跨会话的,可能存在多个会话同时操作同一数据的情况。当其中一个会话修改了数据后,其他会话获取到的仍然是缓存中的旧数据,导致数据不一致的问题。 需要注意的是,在实际应用中,具体的数据不一致性问题可能更加复杂,涉及到分布式环境、集群部署、缓存管理策略等因素。 因此,在使用 MyBatis 的二级缓存时,需要根据具体的应用场景和需求来评估并合理处理数据一致性的问题。
内存占用: 二级缓存需要将查询结果缓存在内存中,对于大量数据或者查询频繁的场景,会占用较大的内存空间。当系统内存有限时,使用二级缓存可能导致内存溢出的问题。 当使用 MyBatis 的二级缓存时,会将查询的结果对象缓存在内存中,以便在后续的查询中直接使用,从而减少数据库访问次数,提高性能。但是,如果数据量较大或者缓存管理不当,二级缓存可能会占用过多的内存。 这其中涉及到整个查询结果集的存储,包括用户对象及其关联对象等,如果数据量较大,二级缓存可能导致内存的过度占用,从而影响系统的性能和稳定性。为了解决这个问题,可以采取以下策略:
- 调整二级缓存的配置,例如使用 LRU(最近最少使用)策略、设置合理的缓存大小等,以平衡内存占用和性能之间的权衡。
- 考虑在查询中使用分页来限制缓存的大小,只查询需要的数据量,避免加载过多的数据到内存中。
- 根据具体业务场景,评估是否真正需要使用二级缓存,有些查询可能更适合直接从数据库获取最新数据。
需要根据实际情况和具体需求来评估并合理处理二级缓存可能导致的内存占用问题。
延迟问题: 由于需要维护缓存的一致性,二级缓存可能引入一定的延迟。当数据发生更新时,会导致缓存失效,下一次查询需要重新从数据库加载数据,可能引起稍微的延迟。 当使用 MyBatis 的二级缓存时,由于缓存的存在,可能会导致数据的延迟更新问题。这意味着在数据库中执行更新操作后,其他会话或线程可能仍然使用旧的缓存数据,而不是最新的数据库数据。 这就是二级缓存可能导致的延迟更新问题。这对于需要及时获取最新数据的场景来说,可能是一个潜在的风险。为了解决这个问题,可以采取以下策略:
- 可以通过手动清除缓存或者设置合理的刷新策略,确保在更新操作后及时刷新缓存数据。
- 根据具体业务需求,评估是否真正需要使用二级缓存。有些场景可能更适合关闭二级缓存,直接从数据库获取最新数据。
需要根据实际情况和具体需求来评估并合理处理二级缓存可能导致的延迟更新问题。
不适用于复杂查询: 二级缓存适用于简单且频繁被访问的查询,对于复杂的查询语句、多表关联查询等,缓存的效果可能并不理想。 这是因为复杂查询涉及多个表或者多个条件,缓存的命中率较低,反而增加了额外的开销。
MyBatis 的二级缓存主要适用于经常被重复查询的简单查询场景。对于复杂查询,二级缓存可能会导致缓存的命中率下降,甚至产生错误的结果。 因此,在复杂查询的情况下,不建议使用 MyBatis 的二级缓存。
这就是在复杂查询场景下,MyBatis 的二级缓存可能产生错误结果的问题。由于二级缓存无法感知关联数据的变化,当关联数据发生更新时,会导致缓存的数据不一致。 针对复杂查询场景,建议关闭 MyBatis 的二级缓存,通过手动清除缓存或设置合理的刷新策略,确保查询结果的准确性。
尽管 MyBatis 提供了二级缓存功能,但在大多数情况下,我们更推荐通过合理优化 SQL 查询、数据库索引等手段来提高性能。如果需要缓存查询结果, 可以考虑使用其他缓存方案,如 Redis、Memcached 等,这些方案通常提供更好的可扩展性和缓存管理机制。 同时,使用本地缓存(一级缓存)来减少数据库查询次数也是一个不错的选择。综上所述,根据具体场景选择合适的缓存方案是很重要的。
# 如何扩展Mybatis的缓存?
- 创建Cache接口的实现类,重写getObject和putObject方法。
- 配置xml 文件中
<caphe type="org.mybatis.caches.ehcache.Ehcachecache"/>
# MyBatis的涉及的设计模式?
- 在缓存中使用的装饰器模式: 在装饰器模式用用来被装饰的对象缓存中的基本缓存处理的实现其实就是一个HashMap的基本操作。 PerpetualCache针对缓存实现灵活的配置需求,1.缓存数据淘汰机制、2.缓存数据的存放机制、3.缓存数据添加是否同步/阻塞、4.缓存对象是否做同步处理。
- 日志模块中使用适配器模式: 在Mybatis的日志类中实现多种日志的框架,适配了常见的日志框架。
- JDBC中使用的代理模式。实现使用代理对象连接数据库。
- Mappering 使用代理模式。
- 反射模块中使用工厂模式与的装饰器模式。
- SqlSessionFactory 建造者模式。
- 模板方法。
# 谈谈你对SqlSessionFactory的理解
- SqlSessionFactory是MyBatis中非常核心的一个API。是一个SqlSessionFactory工厂。目的是创建sqlSession对象。SqlSessionFactory应该是单例。
- SqlSessionFactory对象的创建是通过SqlSessionFactoryBuilder来实现。在SqlSessionFactoryBuilder即完成了SqlSessionFactory对象的创建。 也完成了全局配置文件和相关的映射文件的加载和解析操作。相关的加载解析的信息会被保存在Configuration对象中。
- 而且涉及到了两种涉及模式:工厂模式,建造者模式。
# 谈谈你对SqlSession的理解
- SqlSession是MyBatis中非常核心的一个API,SqlSession的作用是通过相关API来实现对应的数据库数据的操作。
- SqlSession对象的获取需要通过SqlSessionFactory来实现。是一个会话级别的。当一个新的会话到来的时候。我们需要新建一个SqlSession对象来处理。当一个会话结束后我们需要关闭相关的会话资源。
- 处理请求的方式:1.通过相关的增删改查的API直接处理 2.可以通过getMapper(xxx.class)来获取相关的mapper接口的代理对象来处理。
# 谈谈你对Mybatis的理解
- MyBatis应该是我们在工作中使用频率最高的一个ORM框架。持久层框架,提供非常方便的API来实现增删改查操作。
- 支持灵活的缓存处理方案,—级缓存、二级缓存,三级缓存。
- 还支持相关的延迟数据加载的处理
- 还提供了非常多的灵活标签来实现复杂的业务处理,if forech where trim set bind ...
- 相比于Hibernate会更加的灵活。相比于Hibernate全自动框架来说对于复杂的业务更具有灵活性。
# 谈谈MyBatis中的分页原理?
- 谈谈分页的理解:数据太多。用户并不需要这么多。我们的内存也放不下这么多的数据SQL: MySQL : limit关键字,Oracle: rowid关键字
- 谈谈MyBatis中的分页实现在MyBatis中实现分页有两种实现: 1.逻辑分页:RowBounds、2.物理分页:拦截器实现(通过的拦截器实现的关键字的拼接来实现分页)
# Spring中是如何解决DefaultsqlSession的数据安全问题
每个都应该有它自己的Sqlsession实例。 sqlsession实例不是线程安全的,因此是不能共享的,所以它的最佳的作用划是请求方法作用域。 绝对不能将sqlsession实例的引用放在一个类的静态域,甚至一个类的实例变量都是不行的。也绝对不能将sqlsession的实例引用放在任何类型托管作用域中,比如servlet框架中的httpsession。 如果你现在正在使用一种Web 框架,考虑将SqlSession 放在一个和HTTP请求相似的作用域中。换句话说,每次收到HTTP请求,就可以打开—个SqlSession, 返回一个响应后,就关闭它。这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到finally块中。
DefaultSqlSession是线程非安全的。也就意味着我们不能够把DefaultSqlSession声明在成员变量中。
在Spring中提供了一个SqlSession Template来实现SqlSession的相关的定义。然后在SqlSessionTemplate中的每个方法都通过SqlSessionProxy来操作。 这个是一个动态代理对象。然后在动态代理对象中通过方法级别的DefaultSqlSession来实现相关的数据库的操作。
# Mybatis的插件应用
mybatis的插件: MyBatis允许你在映射语句执行过程中的某一点进行拦截调用。 MyBatis中的插件设计的目的是什么:方便我们开发人员实现对MyBatis功能的增强。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
我们可以通过拦截器做哪些操作
- 检查执行的SQL。比如sql中有select * . delete from .. 。
- 对执行的SQL的参数做处理
- 对查询的结果做装饰处理4.对查询SQL的分表处理
# 如何获取MyBatis中自增的主键
需要获取自增的主键:在同一个事务中操作多表。我们需要关联的id信息。
<insert id="" useGenerateKeys="true" keyProperty="id"></insert>
User user=new user();
userMapper.insert(user)
System.out.print("使用的主键是:"+user.getId())
# 不同Mapper中的id是否可以相同?
可以相同:每一个映射文件的namespace都会设置为对应的mapper接口的全类路径名称。也就是保证了每一个Mapper映射文件的namespace是惟一的。 那么我们只需要满足在同一个映射文件中的id是不同的就可以了。
- UserMapper.xml: com.boge.mapper.UserMapper #selectList
- RoleMapper.xml com.boge.mapper.RoleMapper # selectList
# 谈谈对传统JDBC开发的不足?
- 我们需要频繁的创建和释放数据库库的连接对象,会造成系统资源的浪费,从而影响系统的性能,针对与这个情况我们的解决方案是数据库连接池。 然后在MyBatis中的全局配置文件中我们可以设置相关的数据库连接池。当然和Spring整合后我们也可以配置相关的数据库连接。
- SQL语句我们是直接添加到了代码中了,造成维护的成本增加。所以对应SQL的动态性要求比较高。这时我们可以考虑把SQL和我们的代码分离, 在MyBatis中专门提供了映射文件。我们在映射文件中通过标签来写相关的SQL。
- 向SQL中传递参数也很麻烦,因为SQL语句的where条件不一定。可能有很多值也可能很少。占位符和参数需要——对应。 在MyBatis中自动完成java对象和sql中参数的映射。
- 对于结果集的映射也很麻烦,主要是SQL本身的变化会导致解析的难度。我们的解决方案。在MyBatis中通过ResultSetHandler来自动把结果集映射到对应的Java对象中。
- 传统的JDBC操作不支持事务。缓存。延迟加载等功能。在MyBatis都提供了相关的实现。
# MyBatis中的编程步骤是什么?
- 创建SqlSessionFactory--》SqlSessionFactoryBuilder --》建造成模式--》Configuration
- 通过创建的SqlSessionFactory对象来获取SqlSession对象--》Executor执行器
- 通过SqlSession对象执行数据库操作-》API和Mapper接口代理对象--》缓存--》装饰者模式
- 调用SqISession中的commit方法来显示的提交事务--》数据源和事务模块--》JDBC和Managed
- 调式SqlSession中的close方法来关闭会话
# SqlsessionFactory是怎么创建的?
使用 MyBatis 首先是使用配置或者代码去生产SqlSessionFactory,而 MyBatis 提供了构造器 SqlSessionFactoryBuilder。 MyBatis提供了一个类org.apache.ibatis.session.Configuration 作为引导,采用的是Builder模式。具体的分步则是在Configuration类里面完成的。 在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过Java代码的形式去生成 SqlSessionFactory。 推荐采用 XML 的形式,因为代码的方式在需要修改的时候会比较麻烦。当配置了 XML 或者提供代码后,MyBatis 会读取配置文件,通过 Configuration 类对象构建整个 MyBatis 的上下文。
SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和 DefaultSqlSessionFactory。 一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory,
public class TestMyBatis {
public static void main(String[] args) {
try {
// 基本mybatis环境
// 1.定义mybatis_config文件地址
String resources = "mybatis_config.xml";
// 2.获取InputStreamReaderIo流
Reader reader = Resources.getResourceAsReader(resources);
// 3.获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 4.获取Session
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5.操作Mapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserEntity user = mapper.getUser(2);
System.out.println(user.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
# Configuration的对象创建?
Mybatis所有的配置信息以及mapper的配置信息,全部存储于Configuration对象中,Configuration相当于Mybatis的对象实体。 Configuration对象从初始创建会一直贯穿Mybatis运行的整个生命周期,为Mybatis的运行提供必要的配置信息。Mybatis全局配置对象创建的过程,就是给Configuration对象中属性赋值的过程。
首先,在Mybatis开始运行时,首先需要创建SqlSessionFactoryBuilder对象,在调用该对象的build方法时,把读取了主配置文件的输入流作为参数来创建SqlSessionFactory对象。
可以直接进入到build的方法中查看其执行过程,在SqlSessionFactoryBuilder类中存在多个重载的方法,名字都为build,最终都会执行参数为configuration的方法,而configuration对象就是由XMLConfigbuilder对象的parse方法返回。
# 建造者模式与工厂模式的区别?
- 在工厂方法模式里,我们关注的是一个产品整体,如超人整体,无须关心产品的各部分是如何创建出来的;但在建造者模式中,一个具体产品的产生是依赖各个部件的产生以及装配顺序,它关注的是“由零件一步一步地组装出产品对象”。简单地说,工厂模式是一个对象创建的粗线条应用,建造者模式则是通过细线条勾勒出一个复杂对象,关注的是产品组成部分的创建过程。
- 工厂方法模式创建的产品一般都是单一性质产品,如成年超人,都是一个模样,而建造者模式创建的则是一个复合产品,它由各个部件复合而成,部件不同产品对象当然不同。这不是说工厂方法模式创建的对象简单,而是指它们的粒度大小不同。一般来说,工厂方法模式的对象粒度比较粗,建造者模式的产品对象粒度比较细。
两者的区别有了,那在具体的应用中,我们该如何选择呢?是用工厂方法模式来创建对象,还是用建造者模式来创建对象,这完全取决于我们在做系统设计时的意图,如果需要详细关注一个产品部件的生产、安装步骤,则选择建造者,否则选择工厂方法模式。
# 配置文件加载流程?
# 配置的文件解析流程?
# 缓存中装饰器模式实现?
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式 它是作为现有的类的一个包装。简单说就是对同一接口添加新功能时,需要新增一个类,被改造的接口定义为delete(委托)。
以mybatis中的二级缓存Cache为推荐。在定义缓存Cache接口,实现各类缓存操作。
- PerpetualCache,基于HashMap实现了内存的缓存操作。
- LruCache,基于LRU策略删除缓存的实现类。
- FifoCache,缓存基于FIFO先进先出的策略获取缓存的实现类。
- LoggingCache,为日志统一添加日志的实现类。
# mybatis的代理设计模式的体现?
代理模式(Proxy Pattern):给某⼀个对象提供⼀个代理,并由代理对象控制对原对象的引⽤。它是⼀种对象结构型模式。 代理模式可以认为是Mybatis的核⼼使⽤的模式,正是由于这个模式,我们只需要编写Mapper.java接口,不需要实现,由Mybatis后台帮我们完成具体SQL的执⾏。
MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。 MethodSignature用来封装方法的参数以及返回类型,在execute的方法中又回到了SqlSession中的接口调用,和 自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,这就是整个动态代理的实现过程了。
# 谈谈对MyBatis的执行器的理解/Executor的原理?
Executor的类型有三类:
- SIMPLE:默认SimpleExecutor:每次操作都是一个新的Statement对象。
- REUSE: ReuseExecutor,会根据SQL缓存Statement对象。实现Statement对象的复用。
- BATCH: BatchExecutor批处理。
如何指定我们需要使用的类型呢?
- 可以通过SqlSessionFactory的openSession方法中来指导对应的处理器类型。
- 可以通过全局配置文件中的settings来配置默认的执行器。
# MyBatis中如何实现多个传参?
- 顺序传值
public void selectUser(String name,int deptId);
<select id=""selectuser" resultNap="baseResultNap">
select * from t_user where user_name = #{0} and dept_id= #{1}
</select>
#{}里面的数字代表的是入参的顺序,但是这种方法不建议使用,SQL层次表达不直观,而且一旦循序错了很难找到。
- @Param注解传值
public void selectUser(@Param("name")String name,@Param("deptId")int deptId);
<select id=""selectuser" resultNap="baseResultNap">
select * from t_user where user_name = #{name} and dept_id= #{deptId}
</select>
#{}里面的名称对应的就是@Param注解中修饰的名称。
- Map注解传值
public void selectUser(Map<String,object> map);
<select id="selectUser” parameterType="java.util.Map" resultMap="baseResultMap">
select * from t_user where user_name = #{name} and dept_id= #{deptId}
</select>
#{}里面的名称就是Map中对应的Key,这种方案适合传递多个参数,且参数灵活应变值得推荐。
- 通过自定义的对象传递
public void selectUser(User user);
<select id="selectUser” parameterType="com.zhuangxiaoyan.mybatis" resultMap="baseResultMap">
select * from t_user where user_name = #{name} and dept_id= #{deptId}
</select>
#{}里面的名称就是自定义对象的属性名称。这样的方式很直观的。也是推荐使用。
# 谈谈你对MyBatis中的日志的理解?
- MyBatis中的日志模块使用了适配器模式。
- 如果我们需要适配MyBatis没有提供的日志框架。那么对应的需要添加相关的适配类,并实现的log的接口。
- 在全局配置文件中设置日志的的实现。
- 在Mybatis中框架中提供jdbc的package 这个包里面实现了jdbc的相关操作的日志记录。
# MyBatis中记录SQL日志的原理?
在MyBatis中对执行JDBC操作的日志记录的本质是创建了相关核心对象的代理对象.
- Connection -- ConnectionLogger
- PreparedStatement -- PreparedStatementLogger
- ResultSet --ResultSetLogger
本质就是通过代理对象来实现的。代理对象中完成相关的日志操作。然后再调用对应的目标对象完成相关的数据库的操作处理。
# 代理模式装饰器区别?
代理模式(Proxy Design Pattern)原始定义是:让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。
代理模式的适用场景
- 功能增强:当需要对一个对象的访问提供一些额外操作时,可以使用代理模式
- 远程(Remote)代理:实际上,RPC框架也可以看作一种代理模式,GoF的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。 客户端在使用RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
- 防火墙(Firewall)代理: 当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
- 保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
装饰模式(decorator pattern)的原始定义是:动态的给一个对象添加一些额外的职责.就扩展功能而言,装饰器模式提供了一种比使用子类更加灵活的替代方案.
装饰器模式的适用场景
- 快速动态扩展和撤销一个类的功能场景。比如,有的场景下对API接口的安全性要求较高,那么就可以使用装饰模式对传输的字符串数据进行压缩或加密。如果安全性要求不高,则可以不使用。
- 不支持继承扩展类的场景。比如,使用final关键字的类,或者系统中存在大量通过继承产生的子类。
装饰器模式与代理模式的区别
对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。 他们之间的边界确实比较模糊,两者都是对类的方法进行扩展,具体区别如下:
- 装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已; 代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
- 装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;
- 装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;
# MyBatis中数据源模块的设计?
UnpooledDataSource:非数据源连接池的实现 PooledDataSource:数据库连接池的实现
- 从连接池中获取连接对象:如果有空闲连接直接返回。活跃连接数是否超过了最大连接数。是否有连接超时的连接.
- 数据库连接池关闭连接。如果空闲连接没有超过最大连接数那么就放回空闲队列中。否则关闭真实的连接。
# MyBatis中事务模块的设计?
- 谈谈你的事务的理解(ACID)
- mybatis的事务配置在XML文件中配置:
<transactionManager type="JDBC">
- 在Transaction接口类中定义了事务的基本行为。
- 在MyBatis的事务管理中有两个选择:
- jdbc:在MyBatis中自己处理事务的管理.
- Managed:在MyBatis中没有处理任何的事务操作。这种情况下事务的处理会交给Spring容器来管理.
- 在MyBatis中执行DML操作事务的处理逻辑: SqlSesgion.commit();
# MyBatis中Mapper接口的设计?
- 谈一下Mybatis中Mapper接口对应的规则:
- Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同;
- Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同;
- Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同;
- Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径。
- 谈下MyBatis中的Mapper接口的设计原理--代理模式的使用。
- 代理对就执行的逻辑的本质还是会执行SqlSession中相关的DML操作的方法
# 为什么需要使用代理对象来处理
// 不适用代理对象的写法
sqlSession.selectList( statement: "com.boge.mapper.UserMapper.selectUser");
// 使用代理对象的写法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.selectUserById(1);
导致statement出现固定的,耦合性比较强。
# 谈谈你对Reflector模块的理解
- Reflector是MyBatis中提供的一个针对反射封装简化的模块:简化反射的相关操作。MyBatis是一个ORM框架。表结构的数据和Java对象中数据的映射。那么不可避免的会存在非常多的反射操作。
- Reflector是一个独立的模块。我们是可以把这个模块单独抽取出来直接使用的。|
- 每一个Reflector对象都对应一个Java类。
- 反射的具体实现类的分析。
# 谈谈你对MyBatis中的类型转换模块的理解
MyBatis在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成Java类型。
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个JDBC类型。比如:
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
类型转换处理器的设计: TypeHandler --》 BaseTypeHandler---》具体的TypeHandler 预处理的占位赋值操作。
# 谈谈MyBatis和Spring的整合的理解
回答的比较简单些。梳理下MyBatis和Spring整合的步骤
- 单纯的Spring和MyBatis的整合在
- SpringBoot项目中的整合
- 添加整合依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring/artifactId><version>2.0.4</version>
</dependency>
- 在mybatis中的配置的数据库源
<!--配置数据源-->
<bean class="com.mchange.v2.c3p0.comboPooledDataSource" id="datasource" >
<property name="driverclass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysq\://localhost:3306/mybatis-vip?characterEncoding=utf-8&serverTimezone=shanghai"</property>
<property name="user" value="root"></property>
<property name="password" value="1236456"></property></bean>
<bean>
- 配置Bean
<! --整合mybatis -->
<bean class="org.mybatis.spring.sqlsessionFactoryBean" id="sqlsessionFactoryBean" >
<! --关联数据源–->
<property name="dataSource" ref="datasource" /><!--关联mybatis的配置文件-->
<property name="configLocation" value="classpath:mybatis-cfg.xml"/><!--添加别名设置-->
<property name="typeAliasesPackage" value="com. boge.pojo" /><!--l映射文件和接口文件不在同一个目录下的时候-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/></bean>
- 配置扫描的路径
<!--配置扫描的路径-->
<bean class="org.mybatis.spring.mapper.MapperscannerConfigurer" >
<property name="basePackage" value="com.boge.mapper" />
</bean>
# 谈谈你对MyBatis的理解
- MyBatis是一个非常主流的半自动的ORM框架,非常简便的帮助我们完成相关的数据库操作。
- 提供动态SQL,缓存和延迟加载等高级功能。
- 然后整体的架构非常简单,分为三层:外层接口、核心处理、层基础模块。
# 如何判定一个类是否线程安全?
- 是否有共享的可变状态
- 是否使用了线程安全的数据结构
- 是否使用了同步机制
- 如果类中的共享资源是不可变的
# SqlSessionFactory和SqlSession是线程安全的吗?
SqlSessionFactory是一个接口,具体是查看实现类DefaultSqlSessionFactory ,其中只有一个变量private final Configuration configuration
因此是线程安全的。
SqISession也是一个接口,具体是查看实现类DefaultSqISession。这个类单独使用的时候是线程不安全的。但是整个Spring后是线程安全的。当使用事务的时候的,从当前事务的ThreadLocal中拿到SqlSession。
如果不使用事务的时候,每次执行userMapper.selectXXX的时候都会重新创建一个opensqlsession对象。