目录
一. Mybatis 简单示例
什么是mybatis
半自动ORM框架,在传统jdbc模式中每次执行对数据库的操作,都需要获取数据库连接,执行sql语句,关闭数据库连接,获取到的数据手动映射,比较复杂,重复一些无关的代码,通过mybatis,自动获取数据库连接,只关注sql执行,通过动态代理,自动对数据进行映射等
1. 搭建mybatis步骤总结
- 引入依赖
- 创建存有数据库连接信息的properties,配置文件,与log4j日志相关的配置文件(不配置日志可能会报错)
- 创建xml配置文件
加载properties配置文件,读取文件中的数据库用户名密码,url等,配置数据源DataSource注入到容器中
配置SqlSessionFactory,持有数据源扫描对应数据库表的mapper.xml
扫描与mapper.xml进行绑定,通过动态代理生成代理类的mapper接口 - 创建对应数据库中每一张表的mapper.xml
- 创建于mapper.xml进行绑定,通过动态代理生成代理类的mapper接口,接口的全类名是对应mapper.xml的命名空间值
- 代码执行
2. 代码示例
- 引入依赖(此处直接以Spring整合mybatis为例,多引入了spring使用mybatis需要的大部分依赖)
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--ibatis 依赖-->
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-core</artifactId>
<version>${ibatis.version}</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
- 创建properties配置文件
数据库连接信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mysql?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=
配置日志的
#console log
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %m%n
#logger
#log4j.logger.org.springframework=DEBUG,CONSOLE
#log4j.logger.org.hibernate=INFO,CONSOLE
#log4j.logger.org.apache=INFO,CONSOLE
log4j.rootLogger=DEBUG,CONSOLE
- 创建关于mybatis的xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 1.加载属性文件(例如存有数据库连接,登入密码的jdbc.properties,与配置日志相关的 log4j.properties-->
<context:property-placeholder location="classpath:properties/*.properties"/>
<!-- 2.配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 配置url ${jdbc.url}从属性文件获取 -->
<property name="url" value="${jdbc.url}"/>
<!-- 配置用户名 -->
<property name="username" value="${jdbc.username}"/>
<!-- 配置数据库密码 -->
<property name="password" value=""/>
<!-- 使用Druid无需配置driver,会自动的根据url得到driver -->
</bean>
<!-- 3.配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/config.xml"/>
<property name="dataSource" ref="dataSource"/>
<!-- 配置自动扫描mybatis Mapper.java Mapper.xml -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!--扫描对应mapper.xml的映射接口-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描包 -->
<property name="basePackage" value="com.ssm.dao" />
<!-- sqlSessionFactoryBeanName是批factory的名称 此处使用value,而不是ref -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
</beans>
- 创建对应数据库表的mapper.xml
<?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.ssm.dao.HelpCategoryDao">
<!--mapper namespace 当前mapper.xml的命名空间,指向当前xml文件对应的接口全类名-->
<resultMap type="com.ssm.entity.HelpCategory" id="HelpCategoryMap">
<result property="helpCategoryId" column="help_category_id" jdbcType="OTHER"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="parentCategoryId" column="parent_category_id" jdbcType="OTHER"/>
<result property="url" column="url" jdbcType="VARCHAR"/>
</resultMap>
<!--查询单个-->
<select id="queryById" resultMap="HelpCategoryMap">
select
help_category_id, name, parent_category_id, url
from mysql.help_category
where help_category_id = #{helpCategoryId}
</select>
<select id="selectAll" resultMap="HelpCategoryMap">
select
help_category_id, name, parent_category_id, url
from mysql.help_category
</select>
<!--查询指定行数据-->
<select id="queryAllByLimit" resultMap="HelpCategoryMap">
select
help_category_id, name, parent_category_id, url
from mysql.help_category
limit #{offset}, #{limit}
</select>
<!--通过实体作为筛选条件查询-->
<select id="queryAll" resultMap="HelpCategoryMap">
select
help_category_id, name, parent_category_id, url
from mysql.help_category
<where>
<if test="helpCategoryId != null">
and help_category_id = #{helpCategoryId}
</if>
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="parentCategoryId != null">
and parent_category_id = #{parentCategoryId}
</if>
<if test="url != null and url != ''">
and url = #{url}
</if>
</where>
</select>
<!--新增所有列-->
<insert id="insert" keyProperty="helpCategoryId" useGeneratedKeys="true">
insert into mysql.help_category(name, parent_category_id, url)
values (#{name}, #{parentCategoryId}, #{url})
</insert>
<!--通过主键修改数据-->
<update id="update">
update mysql.help_category
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="parentCategoryId != null">
parent_category_id = #{parentCategoryId},
</if>
<if test="url != null and url != ''">
url = #{url},
</if>
</set>
where help_category_id = #{helpCategoryId}
</update>
<!--通过主键删除-->
<delete id="deleteById">
delete from mysql.help_category where help_category_id = #{helpCategoryId}
</delete>
<insert id="saveReturnKey" parameterType="com.ssm.entity.HelpCategory" useGeneratedKeys="true">
insert into help_category value (
<set>
<if test="name != null and name != ''">
#{name},
</if>
<if test="parentCategoryId != null">
#{parentCategoryId},
</if>
<if test="url != null and url != ''">
#{url},
</if>
</set>
)
</insert>
</mapper>
- 与mapper.xml进行绑定的接口
public interface HelpCategoryDao {
/**
* 通过ID查询单条数据
*
* @param helpCategoryId 主键
* @return 实例对象
*/
HelpCategory queryById(Object helpCategoryId);
/**
* 查询指定行数据
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return 对象列表
*/
List<HelpCategory> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);
/**
* 通过实体作为筛选条件查询
*
* @param helpCategory 实例对象
* @return 对象列表
*/
List<HelpCategory> queryAll(HelpCategory helpCategory);
/**
* 新增数据
*
* @param helpCategory 实例对象
* @return 影响行数
*/
int insert(HelpCategory helpCategory);
/**
* 修改数据
*
* @param helpCategory 实例对象
* @return 影响行数
*/
int update(HelpCategory helpCategory);
/**
* 通过主键删除数据
*
* @param helpCategoryId 主键
* @return 影响行数
*/
int deleteById(Object helpCategoryId);
List<HelpCategory> selectAll();
String saveReturnKey(HelpCategory helpCategory);
}
- 创建执行代码,此处通过在容器中获取SqlSessionFactory,手动执行sql,演示sql执行步骤
执行时,通过 SqlSessionFactory 获取到 SqlSession,通过SqlSession,进行数据库的增删改查
@org.junit.Test
public void test3(){
//创建ioc容器
ApplicationContext context = new ClassPathXmlApplicationContext
("classpath:spring/applicationContext-mybatis.xml");
SqlSessionFactory sessionFactory = (SqlSessionFactory) context.getBean("sqlSessionFactory");
//通过SqlSessionFactory获取SqlSession,注意SqlSessin是非线程安全的,
SqlSession sqlSession = sessionFactory.openSession();
//不使用接口的情况下,通过mapper.xml执行sql:
//selectOne()方法的第一个参数,指定mapper.xml的命名空间+要执行的sql语句的id值
//通过这个参数,可以定位到到底是哪个sql执行,第二个参数为执行该sql语句需要的参数
HelpCategory helpCategory = sqlSession.selectOne( "com.ssm.dao.HelpCategoryDao.queryById", 1);
System.out.println(helpCategory);
//使用接口的动态代理,生成代理类,通过代理类执行的方式二:
//该方式需要对应每个mapper.xml 创建接口,mapper.xml的命名空间指向对应的接口的全类名
//接口中提供与mapper.xml中要执行的sql语句对应的抽象方法,入参就是sql执行需要的参数,
//返回值就是sql执行的返回值,方法名,就是对应sql的id
//通过 SqlSession 调用 getMapper() 方法,传递接口的类型,
//获取该接口的全类名,通过接口的全类名找到对应的mapper.xml
//与mapper.xml进行绑定,通过动态代理生成该接口的实体类
//(此处是手动指定的,mybatis中需要配置自动扫描这些接口,与对应的xml,自动绑定)
HelpCategoryDao helpCategoryDao = sqlSession.getMapper(HelpCategoryDao.class);
//通过接口与mapper.xml生成的实体类调用接口中的方法,也就是对应每一条sql的方法
List<HelpCategory> hList = helpCategoryDao.selectAll();
hList.stream().forEach(h -> System.out.println(h));
//关闭sqlSession
sqlSession.close();
}
二. 源码分析
通过代码执行sql操作数据库数据可以发现,是通过SqlSessionFactory,获取到SqlSession,然后通过SqlSession获取到对应数据表的接口对象,通过接口对象执行sql方法,
分为三步了解源码:
- SqlSessionFactoryBean继承了FactoryBean接口,通过重写的该接口的getObject()方法,创建初始化SqlSesionFactory,通过配置的SqlSessionFactoryBean的属性拿到存有全局变量的配置文件与存有sql执行语句对应数据库表的xml文件,基于DOM4J进行解析,最终将每一条sql语句解析为MappedStatement ,将解析后的所有数据存入Configuration中,创建 DefaultSqlSessionFactory 对象
- 通过SqlSessionFactory创建出SqlSession,首先会通过存有解析后数据的Configuration拿到我们配置的执行器类型(默认是Simple),通过执行器类型创建执行器,数据库的增删改查在最顶层是有执行器来完成的,通过执行器与Configuration创建DefaultSqlSession
- 对应数据库表的接口代理类的创建: 通过扫描觉得方式获取到指定接口,根据接口的全类名找到绑定的对应数据库表的xml(xml的命名空间就是接口的全类名),创建MapperProxyFactory代理工厂,通过代理工厂创建代理类MapperProxy
- sql方法的执行 : 实际执行调用的是生成的代理类MapperProxy的invoke(),在执行前通过Configuration获取到当前要执行的方法的的sql详细信息,将当前要执行的sql方法封装为 MapperMethod ,该类中有三个静态内部类: SqlCommand 存有分析方法后得出的sql指令,ParamMap 是一个容器,存有方法执行前后的需要的参数,MethodSignature 该类中主要关注持有的ParamNameResolver参数名解析器,通过该解析器,可以解析保存参数的所有信息,例如参数类型,参数注解等,将执行的sql方法解析封装到MapperMethod 的这三个内部类中,还是通过SqlSession调用方法执行操作数据库
- SqlSession调用方法执行数据库操作继续向下查看会发现,通过前面创建的执行器BaseExecutor来执行,在执行器执行时,首先会创建一个StatementHandler ,通过StatementHandler 创建出Statement,通过Statement执行数据库的操作,在执行的过程中还会创建另外几个Handler,例如处理执行sql参数的ParameterHandler, 处理返回结果参数的ResultHandler等,
1. SqlSessionFactoryBean 初始化 SqlSessionFactory
在搭建Spring+mybatis项目时,需要配置数据源,配置SqlSessionFactory,查看配置文件发现,实际在配置文件中配置注入的是SqlSessionFactoryBean该类继承了 FactoryBean与 ApplicationListener 两个接口,继承FactoryBean接口可以得出这是一个工厂,当向容器中注入一个实现了FactoryBean接口的类时,实际注入的是该类中重写的getObject() 方法返回的 bean(具体查看FactoryBean方式注入),在通过该方法创建DefaultSqlSessionFactory注入时,同时会解析存有全局变量的xml跟存有sql语句,与数据库表进行映射的xml,存入Configuration中
在buildSqlSessionFactory() 方法中判断SqlSessionFactoryBean都是配置了哪些属性,通过这个属性读取配置文件,进行解析,将解析后的数据存入Configuration中,此处我们通过配置SqlSessionFactoryBean 的 mapperLocations 属性扫描对应数据库表的 xml
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//1.创建基于Dom4j的解析器 XMLConfigBuilder
XMLConfigBuilder xmlConfigBuilder = null;
//2.声明为后续存放解析数据的 Configuration
Configuration configuration;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
//3.判断是否配置了configuration 属性值,如果配置了,通过该属性值读取配置文件进行解析(例如此处配置该属性值是开启二级缓存)
} else if (this.configLocation != null) {
//通过解析器解析使用 configLocation 属性设置的的xml
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
String[] typeHandlersPackageArray;
String[] var4;
int var5;
int var6;
String packageToScan;
if (StringUtils.hasLength(this.typeAliasesPackage)) {
typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
var4 = typeHandlersPackageArray;
var5 = typeHandlersPackageArray.length;
for(var6 = 0; var6 < var5; ++var6) {
packageToScan = var4[var6];
configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
int var27;
if (!ObjectUtils.isEmpty(this.typeAliases)) {
Class[] var25 = this.typeAliases;
var27 = var25.length;
for(var5 = 0; var5 < var27; ++var5) {
Class<?> typeAlias = var25[var5];
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!ObjectUtils.isEmpty(this.plugins)) {
Interceptor[] var26 = this.plugins;
var27 = var26.length;
for(var5 = 0; var5 < var27; ++var5) {
Interceptor plugin = var26[var5];
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (StringUtils.hasLength(this.typeHandlersPackage)) {
typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
var4 = typeHandlersPackageArray;
var5 = typeHandlersPackageArray.length;
for(var6 = 0; var6 < var5; ++var6) {
packageToScan = var4[var6];
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
TypeHandler[] var28 = this.typeHandlers;
var27 = var28.length;
for(var5 = 0; var5 < var27; ++var5) {
TypeHandler<?> typeHandler = var28[var5];
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException var24) {
throw new NestedIOException("Failed getting a databaseId", var24);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception var22) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var22);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
//查看我们的配置文件是通过 mapperLocations 属性配置扫描的mapper.xml
//解析对应数据库表的mapper.xml
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] var29 = this.mapperLocations;
var27 = var29.length;
for(var5 = 0; var5 < var27; ++var5) {
Resource mapperLocation = var29[var5];
if (mapperLocation != null) {
try {
//读取mapper.xml配置文件,创建解析器进行解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
//解析,并将解析后的数据存入 configuration 中
xmlMapperBuilder.parse();
} catch (Exception var20) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
}
} else if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
//通过 sqlSessionFactoryBuilder 调用 build() 方法传递解析好数据的 configuration
//创建DefaultSqlSessionFactory对象返回
return this.sqlSessionFactoryBuilder.build(configuration);
}
解析对应数据库表的mapper.xml
XMLMapperBuilder 中的 parse()方法,在调用该方法时已经读取到了对应数据库表的指定xml
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
//读取xml中的mapper标签,也就是当前xml的命名空间(命名空间的值是对应进行绑定的接口的全类名)
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingChacheRefs();
this.parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
//读取当前xml的命名空间(也就是当前xml绑定的接口全类名)
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
//将当前xml的命名空间设置到 builderAssistant 属性中
this.builderAssistant.setCurrentNamespace(namespace);
//解析当前xml中的各种标签
//解析缓存相关标签是由有缓存设置
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
//解析参数集标签
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析自定义的返回结果集标签
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析抽取的可重用sql标签
this.sqlElement(context.evalNodes("/mapper/sql"));
//解析当前xml中的增删改查标签
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
}
}
解析Mapper.xml中的每一条sql语句
最终将每个 sql封装为 MappedStatement 类型,存入 Configuration 中(MappedStatement 可以简单理解为对应sql语句的类,保存了一条增删改查的详细信息)
private void buildStatementFromContext(List<XNode> list) {
if (this.configuration.getDatabaseId() != null) {
//this.configuration.getDatabaseId() 获取到增删改查相关标签中的所有节点
//然后进行解析
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator i$ = list.iterator();
while(i$.hasNext()) {
XNode context = (XNode)i$.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
//解析sql语句标签
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
//读取sql标签的id值
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
//解析sql标签中的所有节点
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultType = this.context.getStringAttribute("resultType");
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultSetType = this.context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
String nodeName = this.context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
String resultSets = this.context.getStringAttribute("resultSets");
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
//解析每一个sql方法封装为 MappedStatement,存入Configuration 中
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
2. 获取SqlSession
(没找到Sqring创建SqlSession此处示例的是通过SqlSessionFactory调用openSession()方法获取SqlSession)
创建SqlSession流程是通过配置爱的执行器类型创建Executor 执行器,执行器中封装了实际的增删改查方法,通过Configuration与执行器,创建DefaultSqlSession
public SqlSession openSession() {
//configuration.getDefaultExecutorType() 获取配置的执行器类型,默认是"SIMPLE"
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
//1.在配置中获取当前环境数据
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//创建一个事物
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//2.根据配置的执行器类型通过configuration创建一个执行器
Executor executor = this.configuration.newExecutor(tx, execType);
//3.使用configuration与创建的executor创建一个DefaultSqlSession对象返回
var8 = new DefaultSqlSession(this.config uration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
3. 执行sql的流程
MapperProxy 代理类的 invoke() 方法
在项目运行时,会通过MapperProxyFactory创建与mapper.xml进行绑定的接口的代理类MapperProxy,调用映射接口中的方法时,执行的是生成的代理类MapperProxy中的invoke()方法,
- 判断当前执行的方法是否是Object中的方法如果是直接放行,例如toString,equals等,不是关于数据库的
- 如果是关于数据库的sql方法,调用cachedMapperMethod()解析方法,将sql方法再次进行封装为 MapperMethod 类型
- 通过 MapperMethod 调用 execute() 执行sql的方法 执行方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.首先判断当前执行的方法是否是Object下的方法(例如toString,equals等)如果是直接放行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
//执行sql相关的方法
//2.将执行的Sql 方法封装为 MapperMethod 类型
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//3.执行sql方法
return mapperMethod.execute(this.sqlSession, args);
}
}
封装sql方法的 MapperMethod
在MapperProxy的invoke()方法中发现将执行的sql方法封装成了MapperMethod ,查看MapperMethod 源码该类中有三个静态内部类 SqlCommand 存有分析方法后得出的sql指令,ParamMap 是一个容器,存有方法执行前后的需要的参数,MethodSignature 该类中主要关注持有的ParamNameResolver参数名解析器,通过该解析器,可以解析保存参数的所有信息,例如参数类型,参数注解等,将执行的sql方法解析封装到MapperMethod 的这三个内部类中
ParamNameResolver 参数的封装过程:
public ParamNameResolver(Configuration config, Method method) {
//1.获取方法执行的参数类型
Class<?>[] paramTypes = method.getParameterTypes();
//2.获取参数注解
Annotation[][] paramAnnotations = method.getParameterAnnotations();
SortedMap<Integer, String> map = new TreeMap();
int paramCount = paramAnnotations.length;
//3.遍历标注参数下标(也就是执行sql是传递的参数要填充的位置)
for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
if (!isSpecialParameter(paramTypes[paramIndex])) {
String name = null;
Annotation[] arr$ = paramAnnotations[paramIndex];
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
Annotation annotation = arr$[i$];
//判断参数是否被@Param注解修饰
if (annotation instanceof Param) {
this.hasParamAnnotation = true;
//如有有name就是@Param注解的value值
name = ((Param)annotation).value();
break;
}
}
if (name == null) {
if (config.isUseActualParamName()) {
name = this.getActualParamName(method, paramIndex);
}
//如果没有被@Param注解修饰,name就是下标
if (name == null) {
name = String.valueOf(map.size());
}
}
//3.存入map集合中(如果有@Param注解,就是注解的值,如果没有就是下标位置的索引)
//这个map中存放的就是sql执行需要的参数,自此将参数填充的位置设置好了
map.put(paramIndex, name);
}
}
this.names = Collections.unmodifiableSortedMap(map);
}
execute() 方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
//1.首先通过switch判断当前要执行的sql是怎删改查什么类型的
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
//2.以select查询为例,首先判断当前执行的sql是否有参数,参数是什么类型(map,list,基本类型等)
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
//3.调用 convertArgsToSqlCommandParam(args)方法,将传递的参数
//转换为sql的可执行参数(可以理解为将java类型的数据转换为sql类型的数据)
param = this.method.convertArgsToSqlCommandParam(args);
//4.还是通过sqlSession调用增删改查来实现的(前面解析xml中的每一条sql封装为
//MappedStatement 类型,存入 Configuration,在sqlSession执行sql语句时也是
//在Configuration中,根据当前要执行的sql语句的id值去Configuration中获取)
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
selectOne()通过查询方法了解实际底层查询流程
通过Configuration拿到存有解析到的sql标签详细信息(在解析时将每一条sql的详细信息封装为MappedStatement 存入了Configuration中)
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
//获取当前要执行的sql标签的详细信息
MappedStatement ms = this.configuration.getMappedStatement(statement);
//通过Executor执行器调用方法进行实际查询
//this.wrapCollection(parameter)根据执行sql时传递的数据类型进行封装
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取BoundSql,sql语句的详细信息,可以理解为再次封装要执行的sql语句
BoundSql boundSql = ms.getBoundSql(parameter);
//创建缓存key(如果使用了缓存,通过缓存key先在缓存中查询数据)
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//首先通过缓存key在缓存中查询数据
Cache cache = ms.getCache();
if (cache != null) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, parameterObject, boundSql);
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
//最终调用BaseExecutor中的 query() 方法进行查询
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor中的 query()
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
//先查询二级缓存
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
//二级缓存中没有,在一级缓存中查
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//一级缓存中也没有,执行sql查询
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator i$ = this.deferredLoads.iterator();
while(i$.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
在该方法中调用SimpleExecutor中的 doQuery() 进行查询
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
//调用doQuery()方法进行查询
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
//将查询出的数据再次放入缓存中
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
SimpleExecutor中的 doQuery()
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
//拿到当前的配置信息
Configuration configuration = ms.getConfiguration();
//创建一个 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//通过StatementHandler 创建 Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());
//通过 Statement 执行sql语句
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}