文章目录
- 1.#{}和${}的区别是什么?
- 2.当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
- 3.模糊查询like语句该怎么写?
- 4.Mybatis 一对一,一对多的xml怎么写?
- 5.Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?*
- 6.mybatis如何做批量插入?*
- 7.mybatis如何获取自动生成的(主)键值?
- 8.在mapper中如何传递多个参数?
- 9.什么是MyBatis的接口绑定?有哪些实现方式?*
- 10.mybatis 有几种分页方式?
- 11.RowBounds 是一次性查询全部结果吗?为什么?
- 12.MyBatis 逻辑分页和物理分页的区别是什么?
- 13.mybatis 是否支持延迟加载?延迟加载的原理是什么?
- 14.说一下 mybatis 的一级缓存和二级缓存?
- 15.二级缓存会造成的问题和解决办法?
- 16.mybatis 有哪些执行器(Executor)?*
- 17.mybatis 和 hibernate 的区别有哪些?
- 18.mybatis 如何编写一个自定义插件?*
- 19.MyBatis编程步骤是什么样的?*
- 20. Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?*
- 21. Mybatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?*
1.#{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
2.当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
也可以问:Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
select order_id as id, order_no as orderno ,order_price as price form orders where order_id=#{id};
</select>
第2种: 通过<resultMap>
来映射字段名和实体类属性名的一一对应的关系。
有了字段名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回
3.模糊查询like语句该怎么写?
第1种:在Java代码中添加sql通配符。
string wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like #{value}
</select>
第2种:在sql语句中拼接通配符,会引起sql注入
string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like "%"${value}"%"
</select>
第三种,使用ES搜索。
4.Mybatis 一对一,一对多的xml怎么写?
<!-- association :配置一对一属性 -->
<resultMap type="one.to.one.Classes" id="getClassesMap">
<id column="cid" property="cid"/>
<result column="cname" property="cname"/>
<association property="teacher" javaType="one.to.one.Teacher">
<id column="tid" property="tid"></id>
<result column="tname" property="tname"/>
</association>
</resultMap>
<resultMap type="one.to.many.User" id="userOrderResultMap">
<id property="id" column="id" />
<result property="username" column="username" />
<result property="birthday" column="birthday" />
<result property="sex" column="sex" />
<result property="address" column="address" />
<!-- 配置一对多的关系 -->
<collection property="orders" javaType="list" ofType="one.to.many.Order">
<!-- 配置主键,是关联Order的唯一标识 -->
<id property="id" column="oid" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
</collection>
</resultMap>
5.Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?*
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中MappedStatement的 id 值,接口方法内的参数,就是传递给 sql 的参数。mapper接口与xml映射(应该是通过读取xml文件,xml有dom树,应该通过读取并解析dom完成的)。
具体可以查看:解析Mapper接口映射xml文件🔗
Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
6.mybatis如何做批量插入?*
mybatis中,批量保存的两种方式
- 使用mybatis foreach标签
这种方式需要数据库连接属性allowMutiQueries=true的支持,也就是在jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true
。
<insert id="addEmpsBatch">
INSERT INTO emp(ename,gender,email,did)
VALUES
<foreach collection="emps" item="emp" separator=",">
(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>
或者
<insert id="addEmpsBatch">
<foreach collection="emps" item="emp" separator=";">
INSERT INTO emp(ename,gender,email,did)
VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>
- mybatis ExecutorType.BATCH
list<string> names = new arraylist();
names.add(“fred”);
names.add(“barney”);
names.add(“betty”);
names.add(“wilma”);
// 注意这里 executortype.batch
sqlsession sqlsession = sqlsessionfactory.opensession(ExecutorType.BATCH);
try {
namemapper mapper = sqlsession.getmapper(namemapper.class);
for (string name : names) {
mapper.insertname(name);
}
sqlsession.commit();
}catch(Exception e){
e.printStackTrace();
sqlSession.rollback();
throw e;
}
finally {
sqlsession.close();
}
<insert id=”insertname”>
insert into names (name) values (#{value})
</insert>
7.mybatis如何获取自动生成的(主)键值?
insert 方法总是返回一个int值 ,这个值代表的是插入的行数。
如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
<insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>
insert into names (name) values (#{name})
</insert>
name name = new name();
name.setname(“fred”);
int rows = mapper.insertname(name);
// 完成后,id已经被设置到对象中
system.out.println(“rows inserted = ” + rows);
system.out.println(“generated key value = ” + name.getid());
8.在mapper中如何传递多个参数?
(1)第一种:
DAO层的函数:
Public UserselectUser(String name,String area);
对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser" resultMap="BaseResultMap">
select * from user where user_name = #{0} and user_area=#{1}
</select>
(2)第二种: 使用 @param 注解:
DAO层的函数:
user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);
然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
<select id=”selectuser” resulttype=”user”>
select id, username, hashedpassword
from some_table
where username = #{username}
and hashedpassword = #{hashedpassword}
</select>
(3)第三种:采用Map传多参数:
DAO层的函数:
@Insert("INSERT INTO APP_USER (USER_ID,USER_NAME,PASSWORD) values(#{userId},#{userName},#{password})")
int insertUser(Map paramMap);
(4)第四种:传对象:
@Insert("INSERT INTO APP_USER (USER_ID,USER_NAME,PASSWORD) values(#{userId},#{userName},#{password})")
int insertUserInfo(UserInfo userInfo);
9.什么是MyBatis的接口绑定?有哪些实现方式?*
接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
10.mybatis 有几种分页方式?
- 获取全部数据,然后在List中做截取。
//查询全部数据
List<Student> students = studentMapper.queryStudentsByArray();
//从第几条数据开始
int firstIndex = (currPage - 1) * pageSize;
//到第几条数据结束
int lastIndex = currPage * pageSize;
return students.subList(firstIndex, lastIndex); //直接在list中截取
- sql分页
<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
select * from student limit #{currIndex} , #{pageSize}
</select>
- RowBounds分页
数据量小时,RowBounds不失为一种好办法。
mybatis接口加入RowBounds参数
public List<UserBean> queryUsersByPage(String userName, RowBounds rowBounds);
service
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.SUPPORTS)
public List<RoleBean> queryRolesByPage(String roleName, int start, int limit) {
return roleDao.queryRolesByPage(roleName, new RowBounds(start, limit));
}
- 拦截器分页
比如PageHelper。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数
11.RowBounds 是一次性查询全部结果吗?为什么?
RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。
12.MyBatis 逻辑分页和物理分页的区别是什么?
-
逻辑分页依赖的是程序员编写的代码。数据库返回的不是分页结果,而是全部数据,然后再由程序员通过代码获取分页数据,常用的操作是一次性从数据库中查询出全部数据并存储到List集合中,因为List集合有序,再根据索引获取指定范围的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。
(使用RowBounds对象进行逻辑(逻辑内存中)分页,逻辑分页会将所有的结果都查询到,然后根据RowBounds中提供的offset和limit值来获取最后的结果) -
物理分页物理分页就是数据库本身提供了分页方式,如MySQL的limit、offset。它从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题。这样做弊端是不同的数据库提供的物理分页方式是不一样的。
(使用pageHelper插件进行物理分页(其实是依赖物理数据库实体))
13.mybatis 是否支持延迟加载?延迟加载的原理是什么?
在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()方法的调用。这就是延迟加载的基本原理。
14.说一下 mybatis 的一级缓存和二级缓存?
一级缓存:一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。
二级缓存:二级缓存是mapper下级别的缓存(按namespace划分),多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
先查二级缓存(如果开启了),再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此。
当在同一个sqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
如果SqlSession执行了更新操作或者删除操作,并且提交到数据库,MyBatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存中存储的是最新的信息,避免出现脏读现象。使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。
15.二级缓存会造成的问题和解决办法?
二级缓存可能造成脏读问题:
- 对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
- 对关联表的查询,关联的所有表的操作都必须在同一个namespace。关系型数据库常会用到多表联合查询。在关联多表查询时肯定会将该查询放到某个命名空间下的映射文件中,这样一个多表的查询就会缓存在该命名空间下的二级缓存中。涉及这些表的增删改查操作通常不在一个映射文件中,因此在其他命名空间下的二级缓存发生改变时,多表查询的二级缓存可能并没有改变,就会发生脏读。
对于关联表,需要借助参照缓存,当某几个表可以作为一个业务整体时,通常是让几个会关联的ER表同时使用同一个二级缓存,这样就能解决脏数据问题。
<mapper namespace="UserMapper">
<cache-ref namespace="RoleMapper"/>
<!--其他配置-->
</mapper>
二级缓存适用场景:
- 以查询为主的应用中,只有尽可能少的增删改操作
- 绝大多数以表单操作存在时,由于很少存在互相关联的情况,因此不会出现脏数据。
- 可以按业务划分对表进行分组时,如关联表比较少,可以通过参照缓存设置。
- 如果脏读对系统无影响,也可以考虑使用。
二级缓存
- 优点是:降低数据库访问量。
- 缺点是:对细粒度的数据级别的缓存实现不好。
比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。
16.mybatis 有哪些执行器(Executor)?*
Mybatis有三种基本的Executor执行器:
SimpleExecutor、ReuseExecutor、BatchExecutor。
在 Mybatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。
-
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
-
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
-
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
17.mybatis 和 hibernate 的区别有哪些?
1)hibernate是全自动,而mybatis是半自动
hibernate完全可以通过对象关系模型(ORM)实现对数据库的操作,mybatis需要在xml配置文件(或者注解的方式)中写sql语句。
2)sql直接优化上,mybatis要比hibernate方便很多
由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大。
3)缓存机制上,hibernate要比mybatis更好一些
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。
而Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。
18.mybatis 如何编写一个自定义插件?*
- 编写Interceptor的实现类
- 使用@Intercepts注解完成插件签名 说明插件的拦截四大对象之一的哪一个对象的哪一个方法
- 将写好的插件注册到全局配置文件中4大对象。
1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) --执行sql
2.ParameterHandler (getParameterObject, setParameters) --获取、设置参数
3.ResultSetHandler (handleResultSets, handleOutputParameters) --处理结果集
4.StatementHandler (prepare, parameterize, batch, update, query) --记录sql
19.MyBatis编程步骤是什么样的?*
① 创建SqlSessionFactory
②通过SqlSessionFactory创建SqlSession
③ 通过sqlsession执行数据库操作
④ 调用session.commit()提交事务
⑤ 调用session.close()关闭会话
20. Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?*
<resultMap>、<assossiation>、<sql>、<include>、<selectKey>
,加上动态 sql 的 9 个标签,trim|where|set|foreach|if|choose|when|otherwise|bind
等。
Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
21. Mybatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?*
虽然 Mybatis 解析 Xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可以定义在任何地方,Mybatis 都可以正确识别。
原理是,Mybatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在,此时,Mybatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,包含 B 标签,待所有标签解析完毕,Mybatis 会重新解析那些被标记为未解析的标签,此时再解析 A 标签时,B 标签已经存在,A 标签也就可以正常解析完成了。