-
Mybatis是什么?ORM是什么?
mybatis是一款优秀的持久层框架,可以支持定制化SQL、存储过程以及高级映射。通过简单的XML或注解配置,整合Spring就很方便操作数据库,映射结果集等等。
ORM(对象关系映射),是一种解决关系型数据库数据与简单Java对象PO的映射关系的技术。简单来说就是将对象自动化持久到关系型数据库或者从数据库查询记录,自动映射上java对象的技术。
-
Mybatis与Hibernate区别?
Mybatis它不完全是一个ORM框架,需要手动编写sql,而Hibernate属于全自动ORM映射工具,不过Mybatis可以通过XML或注解方式灵活配置运行的sql语句。Mybatis学习成本比较低,对于那种软件需求变动频繁的,有需求就要迅速输出的,如我们现在的互联网软件,管理项目等。Hibernate关系映射能力强,对于固定定制的软件比较好,但是学习门槛高,而且要有一些经验设计好O/R映射,需要很强的经验和能力才行。
-
JDBC存在问题?mybatis如果解决?mybatis优点?
- 数据库连接和释放频繁造成系统资源浪费。在mybatis-config.xml中配置数据连接池,管理对数据库的连接。
- sql语句存在硬编码,代码没有统一不好维护。在mapper.xml中与java代码分离。
- 动态查不方便,结果集需要手动一个个取出来映射。mybatis提供很多标签,而且根据resultType等能将结果集自动映射成java对象。
优点:mybatis对于sql的操作很灵活,也把xml文件跟java文件分开能统一管理,也能自动映射上,mybatis还能跟很多数据库映射上(sqlServer,oracle),也能跟spring很好的集成。
缺点:sql语句编写还是很大的,特别有些多表查询,对开发人员sql语句功底还是有一定要求,而且sql语句依赖数据库的,导致数据库移植性差,不能随意更换数据库。
-
mybatis编程步骤是什么样的?(六步)
// 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.(1)解析了配置文件,封装configuration对象
// (2)创建了DefaultSqlSessionFactory工厂对象 解析完了mapper文件,加入到了mappedStatement中
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//产生代理类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用代理类的方法执行
User myUser = userMapper.findByCondition(1);
//关闭
sqlSession.close()
5. ### 说说mybatis的工作流程
1)首先会把mybatis的配置文件先进行加载,这个配置文件包含一些数据源配置(environment,jdbc事务,连接池等),还可以配置一些设置settings,插件plugins,最重要的是mapper,执行mapper的路径。然后按指定的类加载器(classLoaderWrapper)去加载配置文件成一个input输入流。
2)然后根据这个流去构建sqlSessionFactory,由sqlSessionFactoryBuilder的build方法,把刚刚的字节流传入,方法中把根据inputSteam流去把inputStream流解析成document对象,封装在XPathParser中,XMLConfigBuilder中主要持有parser,还有去创建Configuration对象,初始化一些(事务工厂名,数据源名,缓存策略别名,日志别名,代理工厂),因为XMLConfigBuilder继承BaseBuilder,configuration是BaseBuilder中的一个属性。然后由parser调用parse方法开始解析配置文件。parser.parse()方法首先会解析configuration根标签,然后开始解析configuration中的所有标签,从properties开始,settings标签,plugins标签,mappers标签等。解析mappers标签时,会遍历child节点,看是package还是mapper子标签,由XMLMapperBuilder去调用parse方法解析mapper映射文件,去将一个个标签如(cache,parameterMap,resultMap,sql片段,select|insert|update|delete标签)去封装到configuration中,key是命名空间+id,将select等标签里面的封装到由builderAssistant构建出来的mappedStatement对象中,mappedStatement也是放到configuration的一个hashMap中,最后由sqlSessionFactoryBuilder执行build方法,根据configuration创建一个DefaultSqlSessionFactory工厂。
3)工厂创建好了,下来要开始创建一个会话SqlSession,执行openSession()方法,主要是给Environment对象赋值,Environment对象包含事务transactionFactory,还有数据源DataSource,还要根据事务工厂,去创建执行器Executor(BATCH批量执行器,REUSE可重用执行器,SIMPLE简单执行器),如果有缓存的话,会创建出一层CachingExecutor包装住executor。最后创建出DefaultSqlSession(configuration,executor,autoCommit)。
4)由sqlSession去创建mapper代理,这里主要是jdk动态代理,前面解析mapper文件的时候,把mapper接口反射出来了,存到了一个knownMappers的hashmap中,这里就是取出来,然后mapperProxy去重写invoke方法,下次调用方法就先走这个invoke方法。
5)① 由mapper调用方法,会走刚刚的代理方法先,方法内会判断调用的是哪种类型(select|update)的方法,去执行不同的方法。如果是调用select,并且查询单条记录时,会去真正调用selectOne方法。selectOne方法也是去调用selectList方法,只不过是取一条,多了报错。然后会根据调用的方法名,去在刚刚那个mappedStatement Map中获取这个方法对应的mappedStatement,因为这个mappedStatement已经把sql语句,参数类型,返回类型什么的我们再xml写一个sql的所有都封装起来了,再加上我们调方法的时候传的参数,就接下来就可以真正的去查询出来了。所以由executor执行query(ms,wrapCollection(parameter),rowBounds,handler)方法。如果我们开启了缓存的话,会先生成缓存的key,由id,分页参数,sql,参数的值,环境变量组成,先去根据这个key查询二级缓存有没有,如果没有的话,去交给BaseExecutor查询会话的一级缓存,如果一级缓存还没有,那就得真正的去查询数据库。会先准备一个预编译的PreparedStatementHandler,然后获取数据库connection(这里的connection也是代理的,目的是close的时候,拦截然后做的是归还操作而不是关闭操作),根据connection获取Statement,并且会将预编译的sql语句中?号的值设值进去,至此所有的已经准备好了。调用query方法,由刚刚的preparedStatement真正的去执行execute方法。② 接下来是对查询出来的结果集进行处理,会反射创建对象,然后看是否是自动映射上,也是反射开始赋值,最后封装到resultList中,最后统一加到multipleResultList中。将查询出来的结果先存储到一级缓存,再存到二级缓存,至此方法走完。
-
mybatis插件的运行原理,以及如何编写一个插件?
mybatis的插件运行原理:
myabtis插件的运行原理核心在于实现Interceptor接口,这个接口包含三个方法,分别是intercept、plugin和setProperties,其实plugin还有setProperties在接口中都有default修饰,所以不实现也没有关系。intercept方法是插件的核心方法,拦截执行的处理;plugin会生成一个代理类Plugin,setProperties主要一开始初始化的时候就会掉这个方法。
1)mybatis在解析配置文件的时候,会对plugin标签进行处理,如果指定有拦截器的话,会根据类名把类给反射出来,加到一个interceptorChain中的一个list集合中。
2)mybatis只会对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四种接口进行拦截。执行到对应的方法的时候,mybatis会去调用pluginAll方法,去取出一个个interceptor去执行plugin方法,plugin方法去匹配当前的target是不是就是我们要拦截的类(主要看我们的插件类上@Signature注解是不是匹配当前target的某个方法),然后由jdk动态代理生成代理类Plugin。
3)真正执行方法时比如executor.update方法时,会进代理类Plugin中invoke方法,该方法中会去真正走我们插件的intercept方法去,如果是对整个类代理,如果不是匹配上方法签名的话会走原本方法
4)执行我们写的intercept方法,参数我们能拿到很多有用的东西,比如我们的statement,还有执行的参数,我们可以做一些分页处理,或者给一些createTime,公共字段附上值等等,最后调用invocation.proceed方法走mybatis原来的方法
总的来说,myabtis插件的工作原理是由jdk动态代理和责任链模式来实现的。
如何编写一个插件:
1)首先我们可以先写一个类,去继承intercept接口,实现intercept,plugin,setProperties方法,intercept中写我们主要的插件扩展方法。还有一个很重要的是我们写的这个类到底要对哪个接口进行增强,所以要在类上标上Intercepts注解,注解中还要指明Signature的type对哪个类,method对类中的哪个方法,args可能方法重载,指定参数表明到底哪个方法具体点的。
2)要在配置文件sqlMapConfig中配置上plugin标签,指明我们刚刚写的插件的全限定类名,plugin标签中还可以有properties属性,初始化的时候有用。
-
mybatis缓存机制?
首先,mybatis是默认开启一级缓存的,不需要进行任何配置。mybatis的一级缓存是SQLSession级别的,不同的SqlSession之间缓存数据区域是不同的。需要注意的是:如果SqlSession执行了DML操作,并执行了commit()操作,mybatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存数据中存储的是最新的信息,避免出现脏读现象。一级缓存在执行update、commit、rollback、close都会被销毁。一级缓存的缺点就是在分布式环境下,可能会存在脏数据的问题,这种问题解决要将一级缓存级别设置为Statement级别。
二级缓存要在config配置中开启cacheEnabled=true,并且要在mapper文件中加上标签。二级缓存作用域是同一个namespace,是跨SqlSession的(因为在加在mapper文件的时候,会加载cache标签,如果标签存在的话会构建出一系列包装的cache对象,key就是当前的namespace,会给每一个mappedStatement设置cache为当前currentCache)。使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,相比一级缓存SqlSession,二级缓存的范围更大。只要执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句。当会话结束或者提交之后,才会往二级缓存提交数据,因为没有提交的数据可能是脏数据,是不能用的。并且实体类要实现序列化接口,保证每次拿到的是序列化的对象,而不是和一级缓存一样,直接拿引用。二级缓存也是有时间的,默认是一小时,每当存数据的时候,都检查一下cache的生命时间,时间到了就整个清空。
clearOnCommit在update的时候,会修改为true,同时会将entriesToAddOnCommit中的数据清除掉。如果是查询的话,会先加入到entriesToAddOnCommit中。在commit的时候,如果clearOnCommit为true的话,就会清空掉二级缓存,而且在为true的时候,另一个线程来查二级缓存,能查到数据页要返回null,这样就是要避免脏数据。最后会将entriesToAddOnCommit真正存到二级缓存中。
二级缓存在多表查询的时候,也会出现缓存不一致的问题,而且开启二级缓存,缓存结果比较多,也会占用大量的内存空间。
FifoCache:是用Deque实现的链表,会先淘汰队头的元素;
LruCache:是用LinkedHashMap,因为在源码中体现到在每次放map中put和putAll会调用是否要删除最老的条目的方法
-
缓存cachekey由多少部分组成?
-1004424763:-935161847 // hashcode checksum
:com.lyric.mapper.UserMapper.findByCondition // statement中的id
:0:2147483647 //分页参数
:SELECT id,username FROM user WHERE id = ? //sql语句
:1 //入参
:development //当前环境
缓存的key是一个对象,而不是简单的字符串(注意:好像是没有出参类型的)
-
为什么需要预编译?
预编译阶段可以优化SQL的执行,预编译后的SQL多数情况下可以直接执行,DBMS不需要再次编译,越复杂的SQL编译的复杂度将越大。同样预编译语句可以重复使用,把一个SQL预编译产生的PreparedStatement对象缓存下来,下次对于同一个SQL,可以直接用这个缓存的PreparedStatement对象。而且还能防止SQL注入。
-
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
- SimpleExecutor简单执行器,根据对应的sql直接执行即可,不会做一些额外操作。(默认执行器)
- BatchExecutor批量执行器,通过批量操作来优化性能,通常是批量更新操作,用完要调用flushStatement来清除缓存
- ReuseExecutor重用执行器,重用的对象是statement,也就是说该执行器会缓存同一个sql的statement,省去重新创建,优化性能。
-
mybatis是否支持延迟加载?它的实现原理?
用户跟订单,有些时候查用户信息的时候是需要订单信息的,如果不查又不行,需要时没有,查了的话,又不是每次都要,进退两难,这时候就可以用到我们延迟加载的技术。
分一对一association,和一对多collection延迟加载,要在config配置文件中开始懒加载,积极懒加载设置为false,还有一个懒加载触发器方法要设置为true。如果更严谨,要在associate标签设置fetchType属性为true;
总结:延迟加载通过Javassist动态代理创建目标对象的代理对象。当调用代理对象的延迟加载属性的getting方法时,进入拦截方法,如果发现需要延迟加载时,那么就会单独发送事先保存好的查询关联B对象的SQL,把B查询上来,调用a.setB(b)方法。
-
#{}和${}的区别
{}是字符串替换,#{}是预处理;使用#{}可以防止SQL注入,提高系统安全性,mybatis处理{}时,就直接替换成变量的值。而Mybatis在处理#{}时,会对sql语句进行预处理,将sql中的#{}替换成?号,调用PreparedStatement的set方法来赋值;
从源码的角度来看,sql中包含${},会封装到DynamicSqlSource中,如果包含#{},则是会封装成StaticSqlSource。
-
模糊查询like语句该怎么写
可以直接用 like "%"#{name}"%",也可以像若依 like concat('%',#{name},'%'),还有一个bind标签,再者也能java代码处理。
-
在mapper中如果传递多个参数
- 使用map接口传递参数(用得不多,没有加泛型)
- 用@Param注解,可以指定对应#{name}中的内容
- java对象传递(用得比较多,扩展性好)
- 混合使用,要params.name的方式指定才行,以免冲突
-
mybatis如何执行批量操作
foreach,基本都是,我们查询根据id在集合中呀,或者插入的批量插入呀,删除id在集合中的都是用foreach,主要有item,index,collection,open,separator,close属性。
-
如何获取生成的主键
在insert标签后面,加上useGeneratedKeys="true",keyProperty="id"
-
当实体类中的属性名和表中的字段名不一样怎么办?
在select查询的字段后加as定义别名,或者用resultMap指定
-
mybatis接口绑定的方式有哪些?
1)使用注解,在mapper文件上可以直接在方法上写上@Select等注解写上sql语句。比较简单的时候可以用。
2)通过映射文件xml方式进行绑定,xml中namespace对应的接口的全路径名。一般用这个比较多。
-
mybatis的mapper接口调用时有哪些要求?
mapper接口方法名和xml中定义sql的id要一样,而且入参类型parameterType,返回类型resultType都得一样,命名空间namespace还得是mapper接口的全限定路径
-
简述mybatis的xml映射文件和内部数据结构的关系?
mybatis将所有xml配置都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,parameterMap标签会被解析成ParameterMap对象,其每个子元素会被解析成ParameterMapping对象。resultMap标签会解析为ResultMap对象,其每个子标签会被解析成ResultMapping对象。每个select、insert、update、delete标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。
-
myabtis是如何将sql执行结果封装为目标对象并返回?
1)使用resultMap标签,逐一定义列名和对象之间的映射关系
2)使用sql列的别名功能。不过最后都是通过反射给对象的属性逐一赋值的,找不到就无法完成赋值。
-
谈谈你对mybatis整体理解?
mybatis是一个开源的持久层框架,它实现了数据层与业务层的分离,通过映射文件来实现对象关系映射(ORM),使得开发者开源将精力和时间集中在业务逻辑的实现上。mybatis的配置很简单,我们只需要严格按照格式就可以了,而且在mapper文件中,提供了大量的映射,解决jdbc无法自动映射的缺点。mybatis和spring一样,有很优秀的扩展性,mybatis的插件机制可以很方便的对代码进行扩展。mybatis还对sql的控制能力很好,提供了一些动态标签,也不是硬编码的方式。再者mybatis的性能也是很优秀的,有一级缓存,二级缓存,延迟加载等等,能有效地减少对数据库访问次数,再说了,mybatis还有连接池呢。
-
谈谈你对SQLSessionFactory的理解?
SqlSessionFactory是由SqlSessionFactoryBuilder执行build方法创建出来的,这个build方法主要是要把配置文件config里面的配置,包括mapper全部解析出来放到configuration里面,所以SqlSessionFactory默认实现类DefaultSqlSessionFactory是持有这个configuration的。从SqlSession的角度来看,SqlSessionFactory负责对SqlSession对象的创建(执行openSession方法),既然都创建会话了,那么对数据库连接池,获取连接,事务管理,事务隔离级别,是否自动提交,还有多种环境的支持(管理environment)。
总的来说,SqlSessionFactory是管理生产SqlSession,体现的是一种工厂模式,持有多配置文件解析好的configuration,还去管理对于数据库的连接,事务,环境的支持。
-
谈谈你对SQLSession的理解?
SqlSession是mybatis与数据库交互的一个核心的类,提供了很多与数据库进行交互的接口,比如说selectOne,selectList等,还有对于书屋的begin,commit,rollback等操作。
它主要是持有executor执行器,它还对mapper接口生成代理对象,代理对象委派给执行器去执行相应的方法,接下来的一些结果查询,结果映射,都是在SqlSession中。mybatis的缓存机制也是在SqlSession中实现。
总之SqlSession就是提供了对于数据库操作的方法,一些处理,缓存等等都是在方法中体现。
-
mybatis中的设计模式
- 建造者模式(builder)
例如SQLSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
构建者模式的定义是将一个复杂对象的构建与它的表示分离开,使得同样的构建过程可以创建不同的表示。一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式和构建者模式。
在mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取mybatis配置,构建mybatis运行的核心对象configuration对象,然后将该configuration对象作为参数构建一个SQLSessionFactory对象。其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。
在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XPathParser解析、语法解析等步骤,这么多工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。
- 工厂模式
在mybatis中比如SqlSessionFactory使用的是工厂模式,是一个简单工厂模式。
简单工厂模式又称静态工厂方法,它属于创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
SqlSession可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行SQL语句,获取Mapper,管理事务等。SqlSessionFactory中的openSession方法重载很多,分别支持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession。在DefaultSqlSessionFactory的实现中,直接通过new DefaultSqlSession的方式生产出SqlSessionFactory产品
- 单例模式
单例模式:单例模式确保某一个类只有一个实例,向整个系统提供这个实例。单例模式是一种对象创建型模式。
在mybatis中有两个地方用到单例模式,ErrorContext和LogFactory,其中ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而LogFactory则是提供给整个mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。
我看的mybatis源码3.5的,它是构造函数是私有的,如果想创建是不能的,唯一的就是调用instance方法,去ThreadLocal修饰的一个变量中取,如果map中没有的话,会覆盖线程的init方法创建一个ErrorContext对象,后面就直接从ThreadLocal的map中取。
- 代理模式
代理模式可以认为是mybatis的核心使用的模式,正是由于这个模式,我们只需要编写mapper接口的时候,不需要实现,由mybatis后台帮我们完成具体SQL执行。
代理模式:给一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫Proxy,它是一种对象结构型模式
在mybatis中,对于mapper接口,缓存cache记录日志的,还有插件功能的实现,都是通过代理,实现一定的增强。拿mapper接口来说,我们调用mapper中xxx方法的时候,其实这个mapper已经是代理对象了,调用方法时会走到代理对象的invoke方法,然后委派给exector看是哪一种类型是select还是update去执行真正的方法,从而就间接的实现了接口有实现类这样的方式。插件核心的代理类是那个Plugin,获取connection连接也是返回代理对象。
- 组合模式
组合模式组合多个对象形成树形结构以表示“整体-部分”的层次结构
mybatis中sql语句中的各种动态标签就是体现组合模式,各个标签比如foreach会封装成一个ForEachSqlNode叶子节点,别的也是这样。叶子对象和组合对象实现相同的接口。
- 模板方法模式
模板类定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可以重定义该算法的某些特定步骤。
在mybatis中,sqlSession的SQL执行,都是委派给Executor实现的,Executor包含Batch、Simple、Reuse实现,BaseExecutor中的doQuery、doUpdate方法都是一些抽象的方法,就是留来给子类实现的,父类抽象类只是定义一些公共的行为,不同的子类去实现不同的策略。
- 装饰者模式
装饰者模式:动态给一个对象增加一些额外的职责,就增加对象功能来说,装饰器模式比生成子类实现更为灵活。(油漆工模式)
在mybatis中,比较突出的就是如果开启二级缓存的话,会给executor包装多一层CachingExecutor,在查询的时候,先生成查询key,到二级缓存先查一下。而且我们的Cache接口的实现类也包装的很厉害,二级缓存的模式实现是perpetualCache,但是mybatis会根据配置追加一系列的装饰器。
SynchronizedCache -> LoggingCache -> SerializedCache -> ScheduledCache -> LruCache -> PerpetualCache
其中SynchronizedCache线程同步,LoggingCache会记录缓存命中率,SerializedCache实现序列化,ScheduledCache过期清理,LruCache为指定了淘汰策略,PerpetualCache真正缓存。