SSM框架梳理(三)MyBatis原理分析

一.MyBatis配置解析

今天到了SSM三个框架的最后一个MyBatis,MyBatis是一个针对数据库进行操作的框架,基于jdbc产生的一个便捷的操作数据库的框架,底层是对jdbc的封装,简化对数据库的操作。

主要通过对配置文件的解析来连接数据库,然后通过XML文件得到SQL,有了数据库连接+SQL语句,就可以去操作数据库了。

我们先来看一下MyBatis-config.xml这个配置文件里的基础参数设置:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

	<!-- 全局参数 -->
	<settings>
		<!-- 使全局的映射器启用或禁用缓存。 -->
		<setting name="cacheEnabled" value="true"/>
		
		<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
		<setting name="lazyLoadingEnabled" value="true"/>
		
		<!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
		<setting name="aggressiveLazyLoading" value="true"/>
		
		<!-- 是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true -->
		<setting name="multipleResultSetsEnabled" value="true"/>
		
		<!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
		<setting name="useColumnLabel" value="true"/>
		
		<!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false  -->
		<setting name="useGeneratedKeys" value="false"/>
		
		<!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不映射 PARTIAL:部分  FULL:全部  -->  
		<setting name="autoMappingBehavior" value="PARTIAL"/>
		
		<!-- 这是默认的执行类型  (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新)  -->
		<setting name="defaultExecutorType" value="SIMPLE"/>
		
		<!-- 使用驼峰命名法转换字段。 -->
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		
		<!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
        <setting name="localCacheScope" value="SESSION"/>
		
        <!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
        
        <!-- 打印SQL -->
<!-- 		<setting name="logImpl" value="STDOUT_LOGGING" /> -->
		
	</settings>
	
	
	<!-- 插件配置 -->
	<plugins>
		<plugin interceptor="com.xxx.lrt.frame.core.dao.interceptor.PaginationInterceptor">
			<property name="jdbcType" value="mysql"/>
		</plugin>
    </plugins>
	
</configuration>

我们都知道,在SSM层级里,service实现层调用DAO层方法,其实就等同于在执行SQL,最终就会从数据库里得到数据,那么这个DAO层里的方法,是如何跟XML里的SQL语句对应起来的呢?哪个dao对应哪个映射文件,哪个方法又对应哪个语句。我们再来看一个mapping文件的开头:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.lrt.modules.app.dao.CheckOrderAppDao">

这里的mapper使用项目绝对路径指向了一个java类,这个java类就对应起了这个映射xml文件,这是文件之间的对应,再来看一下sql语句和方法的对应:

<select id="selectUserRoleAndLineId" parameterType="WorkOrderApp" resultType="WorkOrderApp">
SELECT
	b.role_id AS userRole,
	a.line_id AS lineId
FROM
	t_sys_user a,
	t_sys_user_role b,
	t_sys_role_app c
WHERE
	CODE = #{userId}
AND b.ROLE_ID=c.ROLE_ID
AND a.user_id=b.USER_ID
AND c.APP_SYS_CODE=#{sysCode}
AND a.DEL_FLAG=0;
</select>

这是xml文件里的一个sql语句,有一个id叫做selectUserRoleAndLineId,而在对应的dao层文件中方法声明如下:

// 查询用户权限+线路
    WorkOrderApp selectUserRoleAndLineId(WorkOrderApp workorderapp);

方法名、入参形式、返回值形式这三点都需要对应上,这样就形成了文件对文件、方法对语句的映射,我们在service层调用dao层文件+方法时候,就可以准确地找到对应的sql语句执行。

讲过了对应的sql找寻方法,我们来看一下,数据库连接是怎么执行的,毕竟我们需要先打开数据库,才能去执行语句。

在spring的配置文件spring-context.xml中,有着很多我们配置好的bean,其中就有关于mybatis的配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.xxx.lrt"/>
        <property name="typeAliasesSuperType" value="com.xxx.lrt.frame.core.entity.BaseEntity"/>
        <property name="mapperLocations" value="classpath:/mappings/**/**/*.xml"/>
		<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
    </bean>

这是一个名为sqlSessionFactory的bean,里面有五个属性,typeAliases不需要关心,我们重点看一下其余三个,第一个dataSorce,这就是数据源的配置,它ref指向了一个叫做dataSorce的bean,我们看到,每一个property都有对应的指向,有的直接给值value,有个给一个ref指向。注意看这个mapperLocations,他的值指向了一个路径,这个路径是项目里映射文件的路径,而基础配置的加载configLocation则指向了mybatis-config.xml,我们顺便看一下项目整体路径:

大家可以看到映射xml的文件位置,在mappings里面,其余的配置文件跟mappings同级。

那么这个dataSource又是怎么注册的呢:

<!-- 动态配置数据源 -->  
   <bean id="dataSource" class="com.xxx.lrt.frame.core.datasource.DynamicDataSource">  
         <property name="targetDataSources">  
               <map key-type="java.lang.String">  
                     <entry value-ref="defaultDataSource" key="defaultDataSource"></entry>
                     <entry value-ref="dcDataSource" key="dcDataSource"></entry>  
                     <entry value-ref="ztmDataSource" key="ztmDataSource"></entry>     
               </map>  
         </property>  
         <property name="defaultTargetDataSource" ref="defaultDataSource"></property>  
   </bean>

可以看到这是一个多数据源,有defaultDataSource、dcDataSource、ztmDataSource三个数据源,他们都有一个key。再来看key指向什么(以defaultDataSource为例):

<!-- 数据源配置, 使用 druid 数据库连接池 -->
	<bean id="defaultDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
	    <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
	    <property name="driverClassName" value="${jdbc.driver}" />
	    
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		
		<!-- 配置初始化大小、最小、最大 -->
		<property name="initialSize" value="${jdbc.pool.init}" />
		<property name="minIdle" value="${jdbc.pool.minIdle}" /> 
		<property name="maxActive" value="${jdbc.pool.maxActive}" />
		
		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="60000" />
		
		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		
		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="300000" />
		
		<property name="validationQuery" value="${jdbc.testSql}" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		
		<!-- 打开PSCache,并且指定每个连接上PSCache的大小(Oracle使用)-->
		<property name="poolPreparedStatements" value="true" />
		<property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> 
		
		<!-- 配置监控统计拦截的filters,stat,slf4j -->
	    <property name="filters" value="stat,config" /> 
	    <property name="connectionProperties" value="${config.decrypt}" />
	</bean>

大家看到,这里面并没有直接把数据库地址、账号、密码等信息写在spring-context.xml里,而是采用了加载配置文件的方式,这个jdbc是从哪来的呢?spring-context.xml又是怎么获取其他配置文件的信息的呢?因为在spring-context.xml一开始,就有一个属性配置:

<context:property-placeholder ignore-unresolvable="true" location="classpath:config.properties" />

大家看到,context已经对config.properties进行了加载,大家去回看以下我上面截图的项目路径,配置文件里第一个就是config.properties,这里面包含了jdbc、web、redis的一些配置,看下jdbc的部分:

#mysql database setting
jdbc.type=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=${db-url}
jdbc.username=${db-username}
jdbc.password=${db-password}

没想到吧,明文的地址和账号密码还是没写在这里。到底在哪儿呢,这里的{db-url}又是怎么找过去的呢?在config.properties文件里,我没有做任何的声明和指向,按照惯性思维,就类似于spring-context.xml需要用到config.properties的变量一样,需要先在spring-context.xml里面加载一下。现在config.properties我没有去加载其他的文件,那么我又是怎么取到其他文件的变量的呢?首先,这里的EL符指向的是POM.XML,看下pom.xml里对应的变量:

<profile>
            <id>dev</id>
            <properties>
                <db-url><![CDATA[jdbc:mysql://aaaaaaa:3306/db_drds?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true]]></db-url>
               <db-username>aaaaaa</db-username>
               <db-password>aaaaaa</db-password>     
               
               <db-url1><![CDATA[jdbc:mysql://aaaaaaaaaa:3308/db_dc?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true]]></db-url1>
               <db-username1>aaaaaa</db-username1>
               <db-password1>aaaaaa</db-password1>
               
               <db-url2><![CDATA[jdbc:mysql://aaaaaa:3306/db_psd_bs?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true]]></db-url2>
               <db-username2>aaaaaa</db-username2>
               <db-password2>aaaaaa</db-password2>
               <ds-decrypt>config.decrypt=false</ds-decrypt>
               	
                <!-- redis -->
				<redis.host>aaaaaa</redis.host>
				
                <!-- log4j -->
                <logs.basedir>${catalina.base}</logs.basedir>
                <!-- logback level -->
                <log.level>INFO</log.level>                
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
                <jdk>${jdk.version}</jdk>
            </activation>
        </profile>

为了保密起见,里面的url和账号密码我都用a代替,端口号和数据库名称就无所谓了。

可以看到,明文的SQL连接、账号密码确实就写在这里了,那么config.properties又是怎么取到这里的内容的呢,因为在pom.xml里有以下代码:

<build>
        <resources>
            <resource>
                 <directory>src/main/resources</directory>
                 <filtering>true</filtering>
            </resource>
        </resources>
</build>

大家往上翻一下,看一下项目的路径,就可以发现config.properties的路径就位于 src/main/resources这个路径下,这几行代码的意思就是,在编译的时候,pom.xml允许此路径下所有文件对pom.xml配置变量进行访问,当然也就包括了config.properties,有些写法是精准指向config.properties文件,意思就它一个可以访问,但是由于我这类路径下诸如spring-context、spring-mvc此类的文件都需要访问pom文件,所以比较粗暴的写法就是精确到路径,大家都能取。这就是config.properties与maven的pom.xml文件之间的取值。

至此,整个DAO层方法对mapping映射的关系、mybaties的配置、sql配置的读取都通过实际项目配置方式讲完了。接下来讲讲mybatis是怎么运行的。

二.Mybatis在SSM中的运行原理

前文提过了,其实mybatis很早就装载了名为“SqlSessionFactory”的bean,这个类是mybatis的核心类,其最主要的功能就是提供创建mybatis核心接口:SqlSession。至于怎么给参数来创建SqlSessionFactory,我们前面已经讲过,也给出了mybatis-config.xml配置文件的内容,我们讲一下SqlSession的运行过程。

SqlSession的作用相当于jdbc的一个connection对象,代表着一个连接资源。具体来说,他有三大功能:①获取mapper接口②发送sql语句给数据库③控制数据库事务

先看一下,mybatis是怎么获取到SqlSession的,很简单,通过SqlSessionFactory,前文提到了sqlSessionFacoty的bean装载,看下sqlSessionFacoty结构:

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

这样我们就得到了一个SqlSession,SqlSession是通过获取mapper接口,然后发送sql的,mapper接口并不能单独直接运行,mybatis运用了动态代理技术使得mapper接口得以运行,mybatis为mapper生成一个代理对象,由这个代理对象去处理相关的逻辑。那么mybatis是如何实现动态代理的呢?

sqlSession的有四大对象Executor、StatementHandler、ParameterHandler、ResultSetHandler:

Executor代表执行器,由它调度StatementHandler、ParameterHandler、ResultSetHandler等来执行对应的SQL。其中StatementHandler是最重要的。

StatementHandler的作用是使用数据库的Statement执行操作。

ParameterHandler是用来处理SQL参数的。

ResultSetHandler是进行数据集ResultSet的封装返回处理的,它相当复杂,好在我们不常用它。

运行过程是通过执行器Executor调度StatementHandler,而StatementHandler分为三个步骤:

①prepared预编译②parameterize设置参数③query/update执行sql

其中parameterize是调用parameterHandler的方法来进行设置,参数则根据类型处理器typeHandler处理。query/update方法通过ResultSetHandler进行处理结果的封装,如果是update语句,就返回整数,以下举个实际update语句代码例子:

<update id="distributeCheckPlan" parameterType="WorkOrderApp" >
UPDATE t_ms_check_plan
SET is_revisable = 0,
 p_state = #{pState},
 p_team_id = #{teamId},
 oper_id=#{userId},
 oper_time=now()
WHERE
	id = #{planId}
</update>

结合之前的sql语句代码,会发现,我这个update语句缺了一个东西,就是resultType,如果是个查询语句,则resultType必须要有,里面是数据返回的类型,但是update就可以不写,再看下这个sql对应的DAO层方法:

// 派发任务
    int distributeCheckPlan(WorkOrderApp workorderapp);

这个方法,我用的是int来定义的,这就是因为update语句执行过后,mybatis会返回回来整数。

如果不是update语句,sqlSession就会通过typeHandler取处理结果类型,也就是我给的resultType参数,然后用objectFactory提供的规则组装类型,最终返回给调用者。

以上就粗略得介绍完了mybatis的运行原理,没有太过深入地讲,一是因为我尚且有些东西没弄清,摊开来讲也过于复杂,例如为了提高效率mybatis的缓存机制之类的。二是在我们实际使用中,其实不需要了解底层的原理(好像被我之前说的《工作内容梳理》打脸了),等有时间了,单独开篇讲一下mybatis的底层实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值