java【MyBatis】面试题

一、MyBatis编程步骤。

  1.创建SqlSessionFactory对象。

  2.通过SqlSessionFactory获取SqlSession对象。

  3.通过SqlSession获得Mapper代理对象。

  4.通过Mapper代理对象,执行数据库操作。

  5.执行成功,则使用SqlSession提交事务。

  6.执行失败,则使用SqlSession回滚事务。

  7.最终,关闭回话。

二、#{}和${}的区别是什么?

  ${}是Proerties文件中的变量占位符,它可以用于XML标签属性值和SQL内部,属于字符串替换。例如将${driver}会被静态替换为com.mysql.jdbc.Driver:

  

<dataSource type="UNPOOLED"> 
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
</dataSource>

${}也可以对传递进来的参数原样拼接在SQL中。代码如下:

<select id="getSubject3" parameterType="Integer" resultType="Subject"> 
SELECT * FROM subject WHERE id = ${id}
</select>
  • 实际场景下,不推荐这么做。因为,可能有SQL注入的风险。

  #{}是SQL的参数占位符,Mybatis会将SQL中的#{}替换为?号,在SQL执行前会使用。PreparedStatement的参数设置方法,按序给SQL的?号占位符设置参数值,比如ps.setInt(0,parametervalue).所以,#{}是预编译处理,可以有效防止SQL注入提高系统安全性处理。

  另外,#{}和${}的取值方式非常方便。例如:#{item.name}的取值方法,为使用反射从参数对象中,获取item对象的name属性值,相当于param.getItem().getName()。

三、当实体类中的属性名和表中的字段名不一样,怎么办?

  第一种,通过在查询的SQL语句中定义字段名和别名,让字段名的别名和实体类的属性名一致。代码如下:

<select id="selectOrder" parameterType="Integer" resultType="Order"> 
SELECT order_id AS id, order_no AS orderno, order_price AS price FROM orders WHERE order_id = #{id}
</select>

这里,还有几点建议:

  • 1.数据库的关键字,统一使用大写,例如:SELECT、AS、FROM、WHERE
  • 2.每5个查询字段换一行,保持整齐。
  • 3.,的后面,和=的前后,需要有空格,更加清晰。
  • 4.SELECT、FROM、WHERE等,单独一行,高端大气。

  第二种,是第一种的特殊情况,大多数场景下,数据库字段名和实体类中的属性名,主要是前者为下划线风格,后者为驼峰风格。在这种情况下,可以直接配置如下,实现自动的下划线转驼峰的功能。

<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>

也就说,约定大于配置,非常推荐!

  第三种,通过<resultMap>来映射字段名和实体类属性名的--对应的关系,代码如下:

<resultMap type="me.gacl.domain.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" />
</resultMap>
<select id="getOrder" parameterType="Integer" resultMap="OrderResultMap">
SELECT * FROM orders WHERE order_id = #{id}
</select>
  • 此处SELECT * 仅仅作为示例只用,实际场景下,千万千万不要这么干。用多少字段,查询多少字段。
  • 相比第一种,第三种的重用性会一些。

四、XML映射文件中,除了常见的select|insert|update|delete标签之外,还有那些标签?

如下部分,可见

  • <cache/>标签,给定命名空间的缓存配置。
    • <cache-ref/>标签,其他命名空间缓存配置的引用。
  • <resultMap>标签,是最负责也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • <sql/>标签,引用<sql/>标签的语句。
  • <selectKey/>标签,不支持自增的主键生成策略标签。
  • <if/>
  • <choose/>、<when/>、<otherwise/>
  • <trim/>、<where/>、<set/>
  • <foreach/>
  • <bind/>

五、Mybatis动态SQL是做什么的?都有哪些动态SQL?能简述一下动态SQL的执行原理么?

  • Mybatis动态SQL,可以让我们在XML映射文件内,以XML标签的形式编写动态sql,完成逻辑判断和动态拼接SQL的功能。
  • Mybatis提供了9种动态SQL标签:<if/>、<choose/>、<when/>、<otherwise/>、<trim/>、<where/>、<set/>、<foreach/>、<bind/>。
  • 其执行原理为:使用OGNL的表达式,从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此完成动态SQL的功能。

六、通常一个XML映射文件,都会写一个Mappe接口与之对应。请问,这个Mapper接口的工作原理是什么?Mapper接口里的方法,参数不同时,方法能重载吗

Mapper接口,对应的关系如下:

  • 接口的全限名,就是映射文件中的“namespace”的值。
  • 接口的方法名,就是映射文件中MapperStatement的“id”值。
  • 接口方法内的参数,就是传递给SQL的参数。

  Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个对应的MapperStatement。举例:com.mybatis3.mappers.findStudentById,可以唯一找到“namespace”为 com.mybatis3.mappers.StudentDao 下面 "id" 为 findStudentById 的MappedStatement 。

  总结来说,在Mybatis中,每一个<select/>、<insert/>、<update/>、<delete/>标签,都会被解析为一个MapperStatement对象。

  另外,Mapper接口的实现类,通过Mybatis使用JDK Proxy自动生成其代理对象Proxy,而代理对象Proxy会拦截接口方法,从而“调用”对应的MappedStatement方法,最终执行SQL,返回执行结果,整体流程如下:

 

 

 

其中,SqlSession在调用Executor之前,会获得对应的MappedStatement方法。例如:

DefaultSqlSession#select(String statement, Object parameter, RowBounds rowBounds, 
ResultHandler handler) 方法,代码如下:
// DefaultSqlSession.java
@Override
 public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { 
    try {    
            // 获得 MappedStatement 对象
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 执行查询
            executor.query(ms, wrapCollection(parameter), rowBounds, handler);
        } catch (Exception e) {
 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
 ErrorContext.instance().reset(); 
}
 }

  注意:mapper接口里的方法,是不能进行重载的,因为时全限名+方法名的保存和寻找策略。所以有时,想个Mapper接口里的方法名,还是蛮闹心的。

七、Mapper接口绑定有几种实现方式,分别是怎么实现的?

  接口绑定有三种实现方式:

  • 第一种,通过XML Mapper里面写SQL来绑定。在这种情况下,要指定XML映射文件里面的“namespace”必须为接口的全路径名。
  • 第二种,通过注解绑定,就是在接口的方法上面加上@select、@update、@Insert、@Delete注解,里面包含SQL语句来绑定。
  • 第三种,是第二种的特例,也是通过注解绑定,在接口的方法上面加上@SelectProvider、@UpdateProvider、@InsertProvider、@DeleteProvider注解,通过java代码,生成对应的动态SQL。

实际场景下,最最最推荐的第一种方式。因为SQL通过注解写在java代码中,会非常杂乱,而写在XML中,更加有整体性,并且可以更加方便的使用OGNL表达式。

八、Mybatis的XML Mapper文件中,不同的XML映射文件,id是否可以重复?

  不同的XML Mapper文件,如果配置了“namespace”,那么id可以重复;如果没有配置“namespace”,那么id不能重复,毕竟“namespace”不是必须的,知识最佳实践而已。

  原因就是:namespace+id是作为Map<String,MappedStatement>的key使用的。如果没有“namespace”,就剩下id,那么id重复会导致数据互相覆盖。如果有了“namespace”,自然id就可以重复,“namespace”不同,namespace+id自然也就不同。

九、如何获取自动生成的(主)键值?

  不同的数据库,获取自动生成 的(主)键值的方式不同。

  Mysql有两种方式,但是自增主键,代码如下:

//方式一,使用 useGeneratedKeys + keyProperty 属性 
<insert id="insert" parameterType="Person" useGeneratedKeys="true" keyProperty="id"> 
INSERT INTO person(name, pswd) VALUE (#{name}, #{pswd})
</insert>
// 方式二,使用 `<selectKey />` 标签 
<insert id="insert" parameterType="Person" useGeneratedKeys="true" keyProperty="id"> 
<selectKey keyProperty="id" resultType="long" order="AFTER">
 SELECT LAST_INSERT_ID() 
</selectKey>
 INSERT INTO person(name, pswd) VALUE (#{name}, #{pswd})
 </insert>
  • 其中,方式一较为常用。
Oracle 有两种方式,序列和触发器。因为自己不了解 Oracle ,所以问了银行的朋友,他们是使用序列。而基于序 
列,根据 <selectKey /> 执行的时机,也有两种方式,代码如下:
// 这个是创建表的自增序列 
CREATE SEQUENCE student_sequence
INCREMENT BY 1
NOMAXVALUE NOCYCLE
CACHE 10;
// 方式一,使用 `<selectKey />` 标签 + BEFORE
<insert id="add" parameterType="Student">
 <selectKey keyProperty="student_id" resultType="int" order="BEFORE"> 
select student_sequence.nextval FROM dual 
</selectKey> 
INSERT INTO student(student_id, student_name, student_age) VALUES (#{student_id},#{student_name},#{student_age}) 
</insert>
// 方式二,使用 `<selectKey />` 标签 + AFTER
<insert id="save" parameterType="com.threeti.to.ZoneTO" > 
<selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" > SELECT SEQ_ZONE.CURRVAL AS id FROM dual 
</selectKey>
 INSERT INTO TBL_ZONE (ID, NAME ) VALUES (SEQ_ZONE.NEXTVAL, #{name,jdbcType=VARCHAR}) 
</insert>

他们使用第一种方式,因为使用触发器,在数据更改就会有问题:就改线上某几条数据时候,需要手动操作的时候。

十、Mybatis执行批量插入,能返回数据库主键列表么?

能,JDBC能做,Mybatis当然也可以做。

十一、在Mapper中如何传递多个参数?

第一种。使用Map集合,装在多个参数进行传递。

// 调用方法 
Map<String, Object> map = new HashMap(); 
map.put("start", start);
map.put("end", end); 
return studentMapper.selectStudents(map); 

// Mapper 接口 
List<Student> selectStudents(Map<String, Object> map); 

// Mapper XML 代码 
<select id="selectStudents" parameterType="Map" resultType="Student"> 
SELECT * FROM students LIMIT #{start}, #{end} 
</select>

第二种:保持传递多个参数,使用@Param注解。代码如下:

// 调用方法 
return studentMapper.selectStudents(0, 10); 

// Mapper 接口 
List<Student> selectStudents(@Param("start") Integer start, @Param("end") Integer end);

// Mapper XML 代码
<select id="selectStudents" resultType="Student"> 
SELECT * FROM students LIMIT #{start}, #{end} 
</select>

第三种,保持传递多个参数,不实用@Param注解。

// 调用方法 
return studentMapper.selectStudents(0, 10);

 // Mapper 接口 
List<Student> selectStudents(Integer start, Integer end); 

// Mapper XML 代码 
<select id="selectStudents" resultType="Student"> 
SELECT * FROM students LIMIT #{param1}, #{param2} 
</select>

其中,按照参数在方法中的位置,从1开始,逐个为#{param1}、#{param2}、#{param3}不断向下。

十二、Mybatis是否可以映射Enum枚举类?

Mybaits可以映射枚举类,对应的实现类为EnumTypeHandler或者EnumOrdinalTypeHandler

  • EnumTypeHandler,基于Enum.name属性(String)。默认
    • EnumOrdinnalTypeHandler,基于Enum.ordinal属性(int)。可通过<setting name="defaultEnumTypeHandler" value="EnumOrdinalTypeHandler"/>来设置。

当然,实际开发场景,我们很少使用Enum类型,更好的方式是常量。

public class Dog { 
public static final int STATUS_GOOD = 1;
 public static final int STATUS_BETTER = 2;
 public static final int STATUS_BEST = 3private int status;

 }

并且,不但可以映射枚举,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler类,实现TypeHandler的#setParameter(...)和#getResult(...)接口方法。

TypeHandler有两个作用:

  • 一个是,完成从javaType到jdbcType的转换。
  • 二是,完成jdbcType到javaType的转换。

具体体现为#setParameter(...)和#getResult(...)两个方法,分别代表设置SQl问好占位符参数和获取列查询结果。

十三、Mybatis都有哪些Excutor执行器?它们之间的区别是什么?

  Mybatis有四种Excutor执行器,分别是SimpleExcutor、ReuseExcutor、BatchExcutor、CachingExcutor。

  • SimpleExcutor:每执行一次update或者select操作,就创建一个statement对象,用完立刻关闭statement对象。
  • ReuseExcutor:执行update或者select操作,以SQL作为key查找缓存的statement对象,存在就使用,不存在就创建;用完后,不关闭statement对象,而是放置于缓存Map<String,Statement>内,供下一次使用,简而言之,就是重复使用statement对象。
  • BatchExcutor:执行update操作(没有select操作,因为JDBC批量处理不支持select操作),将所有SQL都添加到批处理中(通过addBatch方法)。等待统一执行(使用executeBatch方法)。它缓存了多个Statement对象,每个statement对象都调用addBatch方法完毕后,等待一次执行executeBatch批量处理,实际上,整个过程与JDBC批量处理是相同的。
  • CachingExecutor:在上述的三个执行器上,增加二级缓存的功能。
通过设置<setting name="defaultExecutorType" value="">的value属性,可传入SIMPLE、REUSE、BATCH三个值,分别使用SimpleExecutor、ReuseExecutor、BatchExecutor执行器。
通过设置<Setting nam=“cacheEnable” value=“”>的value属性为true时,创建CacheExecutor执行器。

十四、MyBatis 如何执行批量插入?

首先,在mapper XML编写一个简单的Insert语句,代码如下:

<insert id="insertUser" parameterType="String"> 
INSERT INTO users(name) VALUES (#{value})
</insert>

然后,在对应的mapper接口中,编写映射方法。

public interface UserMapper {

void insertUser(@Param("name") String name);

}

最后,调用该mapper接口方法,代码如下:

private static SqlSessionFactory sqlSessionFactory;

 @Test
 public void testBatch() { 
// 创建要插入的用户的名字的数组 
List<String> names = new ArrayList<>(); 
names.add("占小狼");
names.add("朱小厮"); 
names.add("徐妈");
 names.add("飞哥");
 // 获得执行器类型为 Batch 的 SqlSession 对象,并且 autoCommit = false ,禁止事务自动提交 
try (
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) { 
// 获得 Mapper 对象
 UserMapper mapper = session.getMapper(UserMapper.class);
 // 循环插入
 for (String name : names) {
 mapper.insertUser(name);
 }
// 提交批量操作
 session.commit(); 
}
 }
代码比较简单,胖友仔细看看。当然,还有另一种方式,代码如下:
INSERT INTO [表名]([列名],[列名]) 
VALUES ([列值],[列值])), ([列值],[列值])), ([列值],[列值]));
  • 对于这种方式,需要保证单条SQL不超过语句的最大限制max_allowed_packet大小,默认为1M

十五、介绍Mybatis的一级缓存和二级缓存的概念和实现原理?

 

 

十六、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

  Mybatis仅支持association关联对象和collection关联集合对象的延迟加载。其中association指的是一对一,collection指的是一对多查询。

  在Mybatis配置文件中,可以配置<setting name=“lazyLoadingEnable” value=“true”>来开启延迟加载的功能。默认情况下,延迟加载的功能是关闭的。

  它的原理是,使用CGLIB或者javassist(默认)创建目标的代理对象,当调用代理对象的延迟加载属性的getting方法时,进入拦截器方法。比如调用a.getB().getName()方法,进入拦截器invoke(...)方法,发现a.getB()方法需要延迟加载时,那么就会单独发送事先保存好的关联B对象的SQL,把B查询上来,然后a.getB(b)方法,于是a对象b属性就有数据了,接着完成a.getB().getName()方法的调用,这就是延迟加载的基本原理。

  当然了,不光是Mybatis,几乎所有的包括Hibernate在内,支持延迟加载的原理都是一样的。

十七、Mybatis能否执行一对一、一对多的关联查询吗?都有哪些实现方式,以及他们之间的区别?

能,Mybatis不仅可以执行一对一,一对多的查询,还可以执行多对一,多对多的关联查询。

  • 多对一查询,其实就是一对一查询,只需要把selectOne(。。。)修改为selectList(。。)即可。

  https://www.cnblogs.com/qingmuchuanqi48/p/13800057.html就是案例

  • 多对多查询,其实即使一对多查询,只需要把selectOne(。。。)修改为selectList(。。。)即可
posted @ 2020-10-14 01:53  King-DA  阅读(363)  评论(0编辑  收藏  举报