Mybatis
一、ORM
ORM(Object-Relationship-Mapping),对象关系映射,它是一种思想,是指将数据库中的每一行数据用对象的形式表现出来。
二、JPA
JPA(Java-Persistence-API):是Java持久化接口的意思,它是JavaEE关于ORM思想的一套标准接口,仅仅是一套接口,不是具体的实现。
三、MyBatis概念
MyBatis是一个优秀的基于java的持久层框架,它内部封装了JDBC,使开发者只需要关注sql本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁琐的过程。
MyBatis虽然实现了JPA但是它并不是一个完完全全的ORM组件,而是一个基于SQL开发的半ORM组件。
Mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最终由mybatis框架执行sql并将结果映射为java对象并返回。
四、MyBatis框架体系结构
1、Configuration-mybatis配置
1)、与Spring一样,可以通过配置文件或注解的形式进行配置
2)、SqlMapConfig.xml,此文件作为MyBatis的全局配置文件,配置了mybatis的运行环境等信息
3)、mapper文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要SqlMapConfig.xml中加载
4)、有了配置文件之后,通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
5)、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行
6)、sqlSession使用Executor(数据库操作执行器口)操作数据库,同Executor具体实现类实现指定dao层数据访问操作
2、Mapped Statement
框架底层封装对象(sql语句、输入参数、输出结果类型),它包装了MyBatis配置信息及sql映射信息等,mapper文件(即Mapper.xml)中一个sql对应Mapped Statement对象,sql的id即是Mapped statement的id.
3、Sql的输入映射参数
基本和简单类型、HashMap、自定义pojo等。输入参数映射就是jdbc编程中对preparedStatement设置参数,Executor通过Mapped Statement在执行sql前将输入的java对象映射到sql中。
4、Sql的输出映射参数
基本和简单类型、HashMap、自定义pojo。Statement对sql执行输出结果进行定义,输出结果映射过程相当于jdbc
五、Mybatis的优点
1、简单易上手
2、消除了JDBC大量冗余代码,不需要手动开关连接
3、很好的与各种数据库兼容
4、提供了很多第三方插件(分页插件/逆向工程)
5、能够与Spring很好的集成
六、Mybatis的缺点
Sql语句编写工作量大,尤其是字段多,关联的表多的时候
Sql语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
七、Mybatis的适用场景
Mybatis专注于sql本身,是一个足够灵活的Dao层解决方案
对性能的要求很高,或者需求变化比较多的项目,比如互联网项目
八、mybatis和hibernate有哪些不同?
灵活性:mybatis更加灵活,自己可以写sql语句,使用起来比较方便
可以移植性:mybatis有很多自己写的sql,因为每个数据的sql可能不同,所以移植性比较差
二级缓存:hibernate有更好的二级缓存,它的二级缓存可以自行更换为第三方的二级缓存
九、#{}与${}区别是什么?
#{}是预编译处理,${}是字符替换。在使用#{}时,Mybatis会将sql中?替换成#{},配合preparedStatement的set方法赋值,这样可以有效的防止sql注入,保证程序的安全。
十、当实体类的属性名和表中的字段名不一样,怎么办?
通过在sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致,通过resultMap来映射字段名和实体类属性名的一一对应关系。
*******十一、xml映射文件都会写一个dao接口与之对应,这个dao接口的工作原理是什么?dao接口里面的方法,参数不同时,方法能重载吗?
Dao接口就是mapper接口,接口的权限定名就是映射文件中的namespace的值,接口的方法名就是映射文件中mapper的statement的id值,接口方法内的参数就是传递给sql的参数。
Mapper接口是没有实现类的,当调用接口方法时,接口权限定名+方法 拼接字符串作为key值,可以唯一定位一个mapperstatement,在mybatis中,标签,都会被解析成一个mapperstatement对象。
Mapper接口里的方法,是不能重载的,因为使用全限定名+方法名 的保存和寻找策略。
Mapper接口的工作原理:是jdk动态代理,Mybatis运行时会使用jdk动态代理为mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行mapperstatement所代表的sql,然后将sql执行结果返回。
十二、在Mybatis中mysql是怎样进行分页的?
逻辑分页:rowbounds对象进行分页,一次性查询很多数据,然后在数据中进行检索。
可以在sql内直接书写带物理分页的参数来完成物理分页功能,也可以用分页插件pagehelper来完成物理分页。
分页插件的基本原理是使用mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应物理分页语句和物理分页参数。
十三、MyBatis如何将sql执行结果封装成目标对象返回的?有哪些映射方式?
第一种使用标签,注意定义数据库字段和对象属性名之间的映射关系
第二种使用sql字段的别名功能,将字段的别名书写成对象的属性名
有了字段名和属性名的映射关系后,mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
十四、如何获取自动生成的主键值
采用自增长策略,自动生成的键值在insert方法执行完成后可以被设置传入的参数对象中 。Usegeneratedkeys=true
十五、在mapper中如何传递多个参数
@Param注解
十六、MyBatis动态sql有什么用?执行原理?有哪些动态sql?
MyBatis动态sql可以在xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值完成逻辑判断并动态拼接sql的功能。
MyBatis提供了9种动态sql标签:trim、where、set、foreach、if、choose、when、otherwise、bind
十七、MyBatis的xml映射文件,不同的xml映射文件,id可以重复吗?
如果是不同的namespace,那么id可以重复;如果是相同的namespace,id不能重复。
十八、为什么说MyBatis是半自动orm,和全自动区别在哪里?
Hibernate是全自动orm,使用hibernate***查询关联对象的或者关联集合对象***时,可以根据***对象关系模型直接获取***,所以是全自动的。
而MyBatis在查询关联对象或关联对象集合时,需要手动编写sql来完成,所以是半自动的。
十九、MyBatis实现一对一有几种方式?具体怎么操作。
有联合查询和嵌套查询。
联合查询是几个表联合查询,只查询一次。通过在resultMap里面配置association节点配置一对一的类就可以完成。
嵌套查询就是先查一个表。根据这个表里面的结果的外键id去在另外一个表里面查询数据,也是通过assocation配置,但另外一个表的查询通过select属性配置。
二十、MyBatis实现一对多有几种方式操作?
联合查询和嵌套查询
联合查询是几个表联合查询,只查询一次,通过在resultMap里面的Collection节点配置一对多的类就可以完成。
嵌套查询是先查一个表,根据这个表里面的结果的外检id去在另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
二十一、MyBatis的延迟加载 lazyLoadingEnabled=true
延迟加载也叫懒加载,根据需求加载数据,先加载主要显示的数据,如果需要显示关联的数据,再加载关联的数据
1、为什么使用延迟加载?
总结:按需加载数据,避免资源浪费,提升查询速度
1)、假设场景:用户表和订单表,用户表有多个用户,每个用户有多个订单,某些时候,我们需要同时查看用户信息和用户的订单信息,这个时候用户的信息和订单的信息都需要显示。但如果我们只查看订单列表,就没必要把用户信息和用户的订单的数据信息全部查询出来,只显示订单信息就够了。如果想看订单详情,这时候需要显示订单的用户信息,再来加载订单的用户数据。 那这个时候很明显,就需要用到延迟加载。
2)、好处就是:避免资源浪费,提升查询速度
2、如何实现?
1)、在sqlMapConfig配置文件中加入以下节点:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
lazyLoadingEnabled这个是开启懒加载的全局配置 aggressiveLazyLoading这个属性必须改为false,否则不生效。
2)、
<1>、将原始的关联查询代码改为单表查询 原始sql语句
<select id="findUserAndOrderResultMap" resultMap="orderUserMap">
SELECT orders.id,
orders.number,
orders.createtime,
user.id userId,
user.username,
user.birthday,
user.sex,
user.address
FROM orders,USER WHERE orders.user_id = user.id
</select>
修改后的sql语句:
<select id="findUserAndOrderResultMap" resultMap="orderUserMap">
SELECT orders.id,
orders.number,
orders.createtime
FROM orders
</select>
<2>、更改关联查询的resultMap内的映射association节点加入懒加载需要的查询条件 和 sql语句statementId 原始resultMap映射
<resultMap type="orders" id="orderUserMap">
<id column="id" property="id"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<!-- association节点标明唯一关系映射 -->
<association property="user" javaType="user">
<id column="userId" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
更改后的resultMap映射:
<resultMap type="orders" id="orderUserMap">
<id column="id" property="id"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<!-- association节点标明唯一关系映射 -->
<association property="user" javaType="user" column="user_id" select="findUserById">
</association>
</resultMap>
3)、添加根据id查询用户的sql语句statement
<select id="findUserById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>
4)、效果演示 查询订单信息输出
List<Orders> list = userMapper.findOrderAndUser();
for(Orders orders:list){
System.out.println(orders.getNumber());
}
只会看到订单的编号
若在订单内部获取用户,才会又输出一条根据订单的用户id查询user用户的sql语句
List<Orders> list = userMapper.findOrderAndUser();
for(Orders orders:list){
System.out.println(orders.getNumber());
System.out.println(orders.getUser());
}
才会看到user的相关信息。
二十二、MyBatis的缓存机制详解
MyBatis提供了缓存机制减轻数据库压力,提高数据库性能。
MyBatis的缓存分为两级:一级缓存、二级缓存
一级缓存是SqlSession级别的缓存,缓存的数据只在SqlSession内有效
二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的(别的sqlsession可以识别)
1、一级缓存 (声明周期和sqlsession是一致的)
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库时需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存 数据,此HashMap是当前回话对象私有的,别的SqlSession回话对象无法识别。
/*
*@Author Wang
*/
public class PerpetualCache implements Cache{
private String id;
private Map<Object,Object> cache = new HashMap<Object,object>();
}
具体流程:
1、第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
2、第二次执行select会从缓存中查数据,如果select相同且 传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
注意事项:
1、如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么MyBatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中存的数据永远和数据库中一致,避免出现脏读。
2、当一个SqlSession结束后那么他里面的一级缓存也就不存在了,MyBatis默认是开启一级缓存,不需要配置。
3、MyBatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。
2、二级缓存
二级缓存是mapper级别的缓存,也就是同一个namespace的mapper.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域。
二级缓存默认是没有开启的。需要在setting全局参数中配置开启二级缓存
conf.xml:
<settings>
<setting name="cacheEnabled" value="true"/>默认是false:关闭二级缓存
<settings>
在userMapper.xml中配置:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>当前mapper下所有语句开启二级缓存
这里配置了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,而却返回的对象是只读的
若想禁用当前select语句的二级缓存,添加userCache="false"修改如下:
<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">
具体流程:
1、当一个sqlsession执行了一次select后,在关闭此session的时候,会将查询结果存到二级缓存
2、当另一个sqlsession执行select时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能
注意事项:
1、如果sqlsession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前mapper缓存中所有的缓存数据,这样可以保证缓存中存的数据永远和数据库中一致,避免出现脏读。
2、mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,sqlsession的HashMap存储数据时,是使用的[namespace:sql:参数]作为key,查询返回的语句作为value保存的。例如:-1242243203:1146242777:winclpt.bean.userMapper.getUser:0:2147483647:select * from user where id=?:19
二十三、什么是mybatis的接口绑定,有哪些实现方式
接口绑定就是在mybatis中定义任意接口,然后把接口里面的方法和sql语句绑定,我们直接调用接口方法就可以。这样比起sqlsession提供的方法我们可以有更灵活的选择和配置。
接口绑定有两种方式:
1、通过注解绑定,就是在接口的方法上面加上@select、@Update等注解,里面包含sql语句来绑定。
2、通过xml里面写sql来绑定,这时要指定xml映射文件里面的namespace必须为接口的全路径名
二十四、使用MyBatis的mapper的接口调用时有哪些要求
1、Mapper接口的全路径名为Mapper.xml文件中的namespace
2、Mapper接口的方法名和Mapper.xml中定义的sql的id相同
3、Mapper接口中方法的形参类型和Mapper.xml中定义的sql的parameterType类型相同
4、Mapper接口中方法的返回值类型和Mapper.xml中定义的sql的resultType的类型相同
二十五、rowbounds是一次性查询全部成功结果吗?为什么?
Rowbounds并不是一次查询出所有数据,因为mybatis是对jdbc的封装,在jdbc驱动中有一个Fetch Size的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,他会在你执行next()的时候,去查询更多的数据,这样做可以有效的防止内存溢出。
二十六、MyBatis逻辑分页和物理分页的区别是什么?
逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消费大量的内存、有内存溢出的风险、对数据库压力较大。
物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题。
二十七、MyBatis有哪些执行器(Executor)?
MyBatis有三种基本的Executor执行器:
1、SimpleExecutor:每执行一次update或select就开启一个Statement对象,用完立即关闭Statement对象;
2、ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后不关闭Statement对象,而是放置于Map内供下一次使用。简言之,就是重复使用Statement对象;
3、BatchExecutor(批处理执行器):执行update(没有select,jdbc批量处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象都是addBatch()完毕后等待逐一执行executorBatch()批处理,与jdbc批处理相同。
二十八、MyBatis分页插件的实现原理是什么?
分页插件的基本原理是使用mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。