- 什么是mybatis?
a) mybatis是一个半ORM框架,内部封装了JDBC,开发时只需要关注SQL语句本身,编写原生态的sql语句,灵活度比较高
b) mybatis通过xml文件或者注解的方式将各种statement进行配置,并通过Java对象和statement中的sql的动态参数映射生成最终的sql语句,最后由mybatis矿建执行sql并将记过映射为对象返回 - mybatis的优缺点?
a) 优点
i. 基于sql语句编程,可以重用sql语句,比较灵活;sql写在XML中,解除sql与程序代码的耦合;还支持动态sql语句
ii. 消除JDBC大量冗余代码,不需要手动创建连接、关闭连接等
iii. 支持对象与数据库进行关系映射;支持对象关系组件的维护
b) 缺点:
i. sql语句编写工作量比较大,对开发人员编写sql语句的能力有一定要求
ii. sql依赖数据库,移植性差,不能随意更换数据库 - mybatis和hibernate有哪些不同?
a) mybatis不是一个完全的ORM框架,需要手动编写sql语句;hibernate是一个ORM 框架,可以通过直接操作对象来操作数据库
b) hibernate对象关系映射能力强,移植性好,适用于对关系模型要求高的项目,可以节省开发成本;mybatis需要自行管理映射关系
c) hibernate自动生成sql语句,有些语句比较繁琐,会多消耗一些性能;mybatis手动编写sql,可以避免不必要的查询,提高系统性能 - #{}和${}的区别是什么?
a) 是 字 符 串 替 换 , M y b a t i s 在 处 理 {}是字符串替换,Mybatis在处理 是字符串替换,Mybatis在处理{}时,就是把${}替换成变量的值
b) #{}是预编译处理,Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;适用#{}可以防止sql注入 - 数据库中的字段名与对象中的属性名不同,如何处理?
a) 给查询的字段取别名,别名与对象属性名对应即可 - 多表查询时,出现重复字段名,如何解决?
a) 给重复的字段取别名,然后利用来映射别名和对象属性名 - mybatis模糊查询怎么写?利用contact拼接,只适用于mysql数据库(可以适用bind标签定义一个变量,这样oracle数据库也适用)
select * from t_user where uname like concat('%',#{uname},'%')
<bind name="str" value=" '%' + uname + '%' "></bind>
select * from t_user where uname like #{str}
- Mapper接口的工作原理?
a) mapper接口的工作原理是JDK动态代理,mybatis运行时会通过动态代理为mapper接口产生一个代理对象,代理对象拦截接口方法,然后去执行MapperStatement对应的sql语句,最后返回sql的执行结果 - mapper接口可以重载函数吗?
a) 不可以,因为mybatis是通过接口的全限名+方法名匹配对应的sql语句的
b) 接口的全限名就是mapper.xml中的namespace;接口的方法名就是mapper.xml中MapperStatement(配置文件中的每一个、等标签都是一个statement)的id;接口方法的参数就是传给mapper.xml中sql的参数 - 物理分页和逻辑分页
a) 物理分页:每次从数据库中取出需要的数据(访问数据库多次)
b) 逻辑分页:从数据库中取出所有数据,再根据参数截取指定的数据(访问数据库一次) - mybatis分页插件,分页插件的原理是什么?
a) mybatis使用RowBounds进行分页,需要分页的时候传入一个RowBounds对象,sql语句只需要查询所有即可,mybatis的拦截器会根据RowBounds将数据进行分页操作
RowBounds rowBounds = new RowBounds(offset, page.getPageSize()); // offset起始行 // limit是当前页显示多少条数据
public List<ProdProduct> findRecords(HashMap<String,Object> map,RowBounds rowBounds);
b) 此外,还可以自定义拦截器实现分页操作,适用于数据量大的情况
c) 分页插件的原理:使用mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截要执行的sql,然后重写sql(添加分页的参数),最后执行重写的sql
12. mybatis如何将sql的执行结果封装成对象返回?
a) 使用,设置对象属性名数据库表列名的映射关系
b) 使用sql取别名的功能,将别名与对象属性名一一对应
c) 数据库表与对象设置后映射关系后,mybatis通过反射创建对象,然后再通过反射给对象的属性赋值再返回(没有映射关系的无法赋值)
13. 如何获取自动生成的主键值?
a) insert默认返回一个int值,代表插入的行数
b) 当主键采用自增策略,可以通过设置usegeneratedkeys=”true”,这样插入时产生的主键会赋值到传入的对象中。也就是自增策略下,插入会将主键值赋值给传入的对象中
14. mapper中如何传入多个参数?
a) 直接使用#{0}、#{1}……获取参数(不需要指定参数类型)
<select id="selectUser" resultMap="BaseResultMap">
select * t_user where name = #{0} and area=#{1}
</select>
b) 使用 @param 注解,再参数前面加入@param(“参数名”),然后在sql语句中就可以直接使用#{param中的参数名}获取参数值
c) 将数据封装到map或者list集合中
15. mybatis的动态sql作用?原理是什么?有哪些动态sql?
a) 作用:在xml文件中用标签的形式编写动态sql语句,解决手动拼接sql的麻烦
b) 原理:根据表达式的值,通过逻辑判断来完成sql的拼接
c) 常用动态sql标签
i. if标签,进行逻辑判断实现条件拼接
<select id="selectUserById" parameterType="Integer" resultType="User">
select * from t_user where 1=1
<if test="uid != null and uid != '' ">
and uid = #{uid}
</if>
</select>
ii. where标签:使用该标签拼接条件后有多余的or/and,where标签会清除多余的or/and
<select id="selectUser" parameterType="User" resultType="User">
<where>
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
</where>
</select>
iii. foreach标签:相当于循环语句,可以用于遍历集合、批量删除
<select id="findCustomer" resultType="Customer">
select * from t_customer where id in
<foreach item="id" index="index" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
foreach不同属性的作用,上面foreach的结果就是"(id1,id2,id3……)":
item:代表当前循环的变量,也就是会从遍历的集合中取出数据放到id中,在foreach内部直接使用#{id}引用即可
index:当前循环的变量在集合中的下标
collection:代表要遍历的集合(这里填写的是参数类型名的小写,例如List就是list,Map就是map,Array就是array,千万不要写参数名)
open:表示用什么符号与集合中的元素拼接,并拼接在最前面
separator:表示用什么符号隔开集合中的元素
close:表示用什么符号与集合中的元素拼接,并拼接在最后面
iv. set标签:用来动态拼接update时set更新字段,可以去除最后一个多余的逗号
<update id="updateCustomer" parameterType="Customer">
update t_customer
<set>
<if test="username !=null and username !=''">
username=#{username},
</if>
<if test="jobs !=null and jobs !=''">
jobs=#{jobs},
</if>
</set>
where id=#{id}
</update>
v. 、、标签,功能跟switch、case、default相似
<select id="findCustomer" resultType="Customer">
select * from t_customer where 1=1
<choose>
<when test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</when>
<when test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</when>
<otherwise>
and phone is not null
</otherwise>
</choose>
</select>
vi. bind标签:可以定义变量
- 例如你分页的时候需要计算起始值,mybatis不支持直接在sql语句中计算,此时你可以先利用bind标签定义一个变量起始值,然后再sql语句中引用它(其中currentPage、pageSize都是对象PageBean中的属性)
<select id="getPage" parameterType="PageBean" resultMap="CommentMap">
<bind name="start" value="currentPage*pageSize"></bind>
select * from t_comment
limit #{start},#{pageSize}
</select>
- 进行字符串的拼接,可以解决sql注入,且移植性好(常用数据库都适用),其中的username是Customer中的属性
<select id="findCustomer" parameterType=" Customer" resultType=Customer">
<bind name="pattern_username" value="'%'+username+'%'" />
select * from t_customer
where username like #{pattern_username}
</select>
vii. trim标签:通过配置可以是心啊set、where标签的功能’
- 实现where功能:prefix代表语句的前缀,prefixOverrides代表要去除的多余符号,可以是and或者or
<select id="findCustomer" parameterType=" Customer" resultType=Customer">
select * from t_customer
<trim prefix="where" prefixOverrides="and">
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
</trim>
</select>
- 实现set功能:
<update id="updateCustomer" parameterType="Customer">
update t_customer
<trim prefix="set" prefixOverrides=", ">
<if test="username !=null and username !=''">
username = #{username},
</if>
<if test="jobs !=null and jobs !=''">
jobs = #{jobs},
</if>
</trim>
</update>
- 其他标签
a) sql标签:可以将公共的sql语句抽取出来,成为一个常量,方便重复调用
<sql id="Column_List">
id,username,age,hobby
</sql>
b) include标签:将sql定义的常量引用到sql语句中(通过refid属性引用上面定义的Column_List)
<select id="selectAll" resultMap="User">
select
<include refid="Column_List" />
from t_user
</select>
c) resultMap标签:设置对象属性与数据库表字段的映射关系
i. resultMap的tyep属性表示这是哪个类的关系映射,id用于被引用时的标志
ii. 通常主键使用id标签设置映射,其他普通属性使用result标签(result标签也可以用于主键,不过不建议)
iii. resultMap内部标签的property属性都代表对象属性名;普通属性使用column映射数据库表的列名;自定义类对象使用javaType表示对象类型;自定义对象集合使用ofType表示对象类型
iv. 当对象中存在其他对象,例如下面的Blog对象有BlogType对象,这时需要使用association标签设置映射关系,内部标签跟resultMap内部标签遵循一样的规则
v. 当对象中含有自定义的对象集合时,例如下面的Blog对象有Comment集合,这时需要使用collection标签设置映射关系,内部标签跟resultMap内部标签遵循一样的规则
<resultMap type="Blog" id="BlogMap">
<id property="id" column="id"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<association property="blogType" javaType="BlogType">
<id property="id" column="typeId"/>
<result property="typename" column="typename"/>
</association>
<collection property="comment" ofType="Comment">
<id property="id" column="commentId"/>
<result property="replyContent" column="reply_content"/>
</collection>
</resultMap>
- 多表查询的两种方式:联合查询和嵌套查询
a) 联合查询:只查询一次数据库,通过resultMap中的association或collection进行配置,使用javaType 和ofType配置映射关系属于联合查询,且需要在association或collection标签体内配置相应属性的映射关系(通过一条查询语句)
BlogMapper.xml文件
<mapper namespace="com.blog.mapper.BlogMapper">
<resultMap type="Blog" id="BlogMap">
<id property="bid" column="bid"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<association property="blogType" javaType="BlogType">
<id property="tid" column="tid"/>
<result property="typename" column="typename"/>
</association>
<collection property="comment" ofType="Comment">
<id property="cid" column="cid"/>
<result property="replyContent" column="reply_content"/>
</collection>
</resultMap>
<select id="selectAllBlog" resultMap="BlogMap">
select * from t_blog a,t_blogtype b,t_comment c where a.tid=b.tid and a.cid=c.cid
</select>
</mapper>
b) 嵌套查询:先查询一个表,然后再利用这个表的外键去查询另一个表(通过多条查询语句,查询语句写在不同类对应的xml文件中),需要使用column进行关系映射,还需要用select属性配置另一条查询语句的id
BlogMapper.xml文件:
<mapper namespace="com.blog.mapper.BlogMapper">
<resultMap type="Blog" id="BlogMap">
<id property="bid" column="bid"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<association property="blogType" column="tid" select="BlogTypeMapper.selectBlogTypeById"></association>
<collection property="comment" column="cid" select="CommentMapper.selectCommentById"></collection>
</resultMap>
<select id="selectAllBlog" resultMap="BlogMap">
select * from t_blog
</select>
</mapper>
BlogTypeMapper.xml文件:
<mapper namespace="com.blog.mapper.BlogTypeMapper">
<select id="selectBlogTypeById" resultType="BlogType">
select * from t_blogtype where tid=#{tid}
</select>
</mapper>
CommentMapper.xml文件:
<mapper namespace="com.blog.mapper.CommentMapper">
<select id="selectCommentById" resultType="Comment">
select * from t_comment where cid=#{cid}
</select>
</mapper>
- mybatis是否支持延迟加载?延迟加载的原理是什么?
a) 支持association关联对象和collection关联对象集合的延迟加载,可以配置lazyLoadingEnable="true"实现延迟加载
b) 原理:mybatis通过cglib动态创建延迟加载对象的代理对象,当延迟加载对象调用自身属性方法时,代理对象会去数据库中查询数据并返回查询结果 - mybatis的一级缓存和二级缓存
a) 一级缓存:基于PerpetualCache的HashMap本地缓存,存储作用域是Session,当session执行flush或close方法后,该session的缓存将清空,默认打开一级缓存
b) 二级缓存:机制与一级缓存相同,不同的是二级缓存的作用域是整个mapper(即同一个命名空间内),默认不打开二级缓存,可以利用Ehcache自定义存储源,开启二级缓存的类需要实现Serializable序列化接口
c) 缓存更新机制:当某个作用域(一级缓存Session/二级缓存namespace)执行C/U/D操作后,默认该作用域下的所有select的缓存将clear并重新更新,开启二级缓存则根据配置决定是否刷新 - mybatis使用接口开发需要注意一下四点:
a) 接口中的方法名与对应的mapper.xml文件定义的sql的id一一对应
b) 接口中方法的参数类型与对应的sql中的paramterType的类型相同
c) 接口中方法的返回值与对应sql中resultType/resultMap相同
d) 接口的全包名与对应mapper.xml文件的namespace相同 - 插件运行原理,以及如何自定义插件?
a) 运行原理:mybatis可以编写针对Executor、StatementHandler、ParameterHandler、ResultSetHandler四个接口的插件,mybatis使用JDK的动态代理为需要拦截的接口生成代理对象,然后实现接口的拦截方法,所以当执行需要拦截的接口方法时,会进入拦截方法(AOP面向切面编程的思想)
b) 自定义插件
i. 编写Intercepror接口的实现类
ii. 设置插件的签名,告诉mybatis拦截哪个对象的哪个方法
iii. 最后将插件注册到全局配置文件中
在全局配置文件中注册插件:
//插件签名,告诉mybatis当前插件拦截哪个对象的哪个方法,type表示要拦截的目标对象,method表示要拦截的方法,args表示要拦截方法的参数
@Intercepts({
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {
//拦截目标对象的目标方法执行
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin拦截目标对象:"+invocation.getTarget()+"的目标方法:"+invocation.getMethod());
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
//包装目标对象:为目标对象创建代理对象
@Override
public Object plugin(Object target) {
System.out.println("MyFirstPlugin为目标对象"+target+"创建代理对象");
//this表示当前拦截器,target表示目标对象,wrap方法利用mybatis封装的方法为目标对象创建代理对象(没有拦截的对象会直接返回,不会创建代理对象)
Object wrap = Plugin.wrap(target, this);
return wrap;
}
//设置插件在配置文件中配置的参数值
@Override
public void setProperties(Properties properties) {
System.out.println("MyFirstPlugin配置的参数:"+properties);
}
}
在全局配置文件中注册插件
<plugins>
<plugin interceptor="com.mybatis_demo.plugin.MyFirstPlugin">
<property name="username" value="acodebird"/>
<property name="password" value="123456"/>
</plugin>
<plugin interceptor="com.mybatis_demo.plugin.MySecondPlugin"></plugin>
</plugins>