mybatis原理分析学习记录,mybatis动态sql学习记录
以下个人学习笔记,仅供参考,欢迎指正。
MyBatis 是支持定制化 SQL、存储过程以及高级映射的持久层框架,其主要就完成2件事情:
封装JDBC操作
利用反射打通Java类与SQL语句之间的相互转换
MyBatis的主要设计目的就是让我们对执行SQL语句时对输入输出的数据管理更加方便,所以方便地写出SQL和方便地获取SQL的执行结果才是MyBatis的核心竞争力。
MyBatis的配置
spring整合mybatis(druid数据源)
(1)整合思路:把MyBatis框架中使用所涉及的核心组件配置到Spring容器中
(2)步骤:
-->1.添加pom依赖,mybatis-spring,spring-tx,spring-jdbc
-->2.创建实体类(entity)
-->3.创建数据访问接口(dao层的接口)
-->4.配置SQL映射文件(resource下的mapper.xml文件)
-->5.配置mybatis的配置文件(resource下的spring下的mybatis-config.xml)
-->6.凡是使用了注解,都需要配置扫描注解定义的业务Bean: <context:component-scan base-package="com.one.ssm.dao"/>
和<context:annotation-config/>(用于激活那些已经在spring容器里注册过的bean) <?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>
<!--使用jdbc的getGeneratedKeys获取数据库自增主键值-->
<setting name="useGeneratedKeys" value="true"/>
<!--使用列别名替换列名,默认true, eg:select name as title from table-->
<setting name="useColumnLabel" value="true"/>
<!--开启驼峰命名转换 table(create_time)-->entity(createTime)
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration> (3)实现整合(spring-dao.xml操作)
-->1.配置dataSource数据源
jdbc.properties内容:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456 <!--properties文件配置数据源-->
<context:property-placeholder location="classpath:spring/jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!--配置连接池属性-->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean> -->2.配置SqlSessionFactoryBean
<!--SqlSession 包含了所有执行数据库SQL语句的方法。能够直接地通过SqlSession实例执行映射SQL-->
<!--缺少sqlSessionFactory:No bean named 'sqlSessionFactory' available 完成对配置文件的读取-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据库连接池--><!--否则会出现java.lang.IllegalArgumentException: Property 'dataSource' is required-->
<property name="dataSource" ref="dataSource"/> <!--扫描entity包,使用别名,设置这个以后再Mapper配置文件中在parameterType
的值就不用写成全路径名了-->
<property name="typeAliasesPackage" value="com.one.ssm.entity"/> <!--扫描mapper需要的xml文件-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean> <!-- 配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.one.ssm.dao"/>
</bean> (4)注入映射器的两种方式:使用了映射器注入就可以不用写dao层的接口的实现方法
-->1.配置MapperFactoryBean生成映射器并注入到业务组件
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!--mapperInterface属性指定映射器,只能是某一个接口类型-->
<property name="mapperInterface" value="com.one.ssm.dao.UserDao"/>
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean> -->2.配置MapperScannerConfiger生成映射器并注入到业务组件:优先使用MapperScannerConfiger,可以批量生成映射器的实现
<!--mybatis-spring提供了MapperScannerConfigurer,
可以扫描指定包中的接口并将它们直接注册为MapperFactoryBean,为了简化MapperFactoryBean映射器太多而导致多出的配置项-->
<!--mybatis-spring提供了MapperScannerConfigurer,可以扫描指定包中的接口并将它们直接注册为MapperFactoryBean-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.one.ssm.dao"/>
</bean> (5)添加声明式事务(spring-service.xml操作)
-->使用xml配置方法配置声明式事务
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
</bean>
<!--配置事务(事务就是对一系列的数据库操作进行统一的提交或回滚操作)管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
-->使用注解添加声明式事务
<!--声明式事务注解的添加方式-->
<tx:annotation-driven transaction-manager="transactionManager"/>
spring整合mybatis
springboot整合mybatis,需要在yml文件中添加相应的配置信息。
MyBatis的主要成员
Configuration:MyBatis所有的配置信息都保存在Configuration
对象之中,配置文件中的大部分配置都会存储到该类中。
SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能。
Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
StatementHandler:封装了JDBC Statement
操作,负责对JDBC statement
的操作,如设置参数等。
ParameterHandler:负责对用户传递的参数转换成JDBC Statement
所对应的数据类型。
ResultSetHandler:负责将JDBC返回的ResultSet
结果集对象转换成List类型的集合。
TypeHandler:负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换,负责对statement对象设定特定的参数,对statement的返回结果result结果集取出特定的列
MappedStatement:MappedStatement
维护一条<select|update|delete|insert>
节点的封装。
SqlSource:负责根据用户传递的parameterObject
,动态地生成SQL语句,将信息封装到BoundSql
对象中,并返回。
BoundSql:表示动态生成的SQL语句以及相应的参数信息。
MyBatis的层次结构:
1、sqlSession -->excutor--> statementHander-->parameterHander--> typeHander-->(进入jdbc)statement(分为:preparedStatement、simpleStatement、callableStatement)-->(取出结果)resultSet--> typeHander-->resultSetHandler-->statementHandler--->excutor-->sqlSession
MyBatis的初始化(解析配置文件和初始化Configuration
的过程)
String resource = "mybatis.xml";
// 加载mybatis的配置文件(它也加载关联的映射文件)
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
} // 构建sqlSession的工厂
sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
首先会创建SqlSessionFactory
建造者对象,然后由它进行创建SqlSessionFactory
。这里用到的是建造者模式,建造者模式最简单的理解就是不手动new对象,而是由其他类来进行对象的创建。
// SqlSessionFactoryBuilder类
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//XMLConfigBuilder对象会进行XML配置文件的解析,实际为configuration节点的解析操作。
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse()); // 开始进行解析了 :)
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
} private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631 /* 处理environments节点数据 */
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
XMLConfigBuilder类
在configuration
节点下会依次解析properties/settings/.../mappers
等节点配置。在解析environments
节点时,会根据transactionManager
的配置来创建事务管理器,根据dataSource
的配置来创建DataSource
对象,这里面包含了数据库登录的相关信息。在解析mappers
节点时,会读取该节点下所有的mapper
文件,然后进行解析,并将解析后的结果存到configuration
对象中。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) { /* 创建事务管理器 */
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource(); /* 建造者模式 设计模式 */
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
} // 解析单独的mapper文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse(); // 开始解析mapper文件了 :)
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
XMLConfigBuilder类
解析完MyBatis
配置文件后,configuration
就初始化完成了,然后根据configuration
对象来创建SqlSession就初始化完成了
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
MyBatis的SQL查询流程
通过封装JDBC进行操作,然后使用Java反射技术完成JavaBean
对象到数据库参数之间的相互转换,这种映射关系就是有TypeHandler
对象来完成的,在获取数据表对应的元数据时,会保存该表所有列的数据库类型。
sqlSession = sessionFactory.openSession(); User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);
System.out.println(user);
调用selectOne
方法进行SQL查询,selectOne
方法最后调用的是selectList
,在selectList
中,会查询configuration
中存储的MappedStatement
对象,mapper
文件中一个sql语句的配置对应一个MappedStatement
对象,然后调用执行器进行查询操作。
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
} public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
执行器在query
操作中,优先会查询缓存是否命中,命中则直接返回,否则从数据库中查询。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/* 获取关联参数的sql,boundSql */
BoundSql boundSql = ms.getBoundSql(parameterObject);
/* 创建cache key值 */
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
} public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
/* 获取二级缓存实例 */
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
} private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
/**
* 先往localCache中插入一个占位对象,这个地方
*/
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
} /* 往缓存中写入数据,也就是缓存查询结果 */
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
CachingExecutor类
真正的doQuery
操作是由SimplyExecutor
代理来完成的,该方法中有2个子流程,一个是SQL参数的设置,另一个是SQL查询操作和结果集的封装。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); /* 子流程1: SQL查询参数的设置 */
stmt = prepareStatement(handler, ms.getStatementLog()); /* 子流程2: SQL查询操作和结果集封装 */
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
子流程
子流程1 SQL查询参数的设置:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
/* 获取Connection连接 */
Connection connection = getConnection(statementLog); /* 准备Statement */
stmt = handler.prepare(connection, transaction.getTimeout()); /* 设置SQL查询中的参数值 */
handler.parameterize(stmt);
return stmt;
} // DefaultParameterHandler类
public void setParameters(PreparedStatement ps) {
/**
* 设置SQL参数值,从ParameterMapping中读取参数值和类型,然后设置到SQL语句中
*/
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
子流程1 SQL查询参数的设置:
子流程2 SQL查询结果集的封装:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询操作
ps.execute();
// 执行结果集封装
return resultSetHandler.<E> handleResultSets(ps);
} // DefaultReseltSetHandler类
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0;
/**
* 获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等。
* 这些信息都存储在了ResultSetWrapper中了
*/
ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
} String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
} return collapseSingleResultList(multipleResults);
}
SQL查询结果集的封装
ResultSetWrapper
是ResultSet
的包装类,调用getFirstResultSet
方法获取第一个ResultSet
,同时获取数据库的MetaData
数据,包括数据表列名、列的类型、类序号等,这些信息都存储在ResultSetWrapper
类中了。然后调用handleResultSet
方法来来进行结果集的封装。
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
DefaultResultSetHandler类
调用handleRowValues
方法进行结果值的设置
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 封装数据
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
} private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
skipRows(rsw.getResultSet(), rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
} private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// createResultObject为新创建的对象,数据表对应的类
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 这里把数据填充进去,metaObject中包含了resultObject信息
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
}
return rowValue;
} private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (autoMapping.size() > 0) {
// 这里进行for循环调用,因为user表中总共有7列,所以也就调用7次
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 这里将esultSet中查询结果转换为对应的实际类型
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
DefaultResultSetHandler类
mapping.typeHandler.getResult
会获取查询结果值的实际类型,比如我们user表中id字段为int类型,那么它就对应Java中的Integer类型,然后通过调用statement.getInt("id")
来获取其int值,其类型为Integer
。metaObject.setValue
方法会把获取到的Integer
值设置到Java类中的对应字段。
metaValue.setValue
方法最后会调用到Java类中对应数据域的set方法,这样也就完成了SQL查询结果集的Java类封装过程。
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null && prop.getChildren() != null) {
// don't instantiate child path if value is null
return;
} else {
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);
} else {
objectWrapper.set(prop, value);
}
}
MetaObject类
MyBatis缓存
MyBatis提供了一级缓存和二级缓存:
一级缓存是SqlSession
级别的缓存,每个SqlSession
对象都有一个哈希表用于缓存数据,不同SqlSession
对象之间缓存不共享。同一个SqlSession
对象对象执行2遍相同的SQL查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库查询了,直接返回缓存结果即可。MyBatis默认是开启一级缓存的。
二级缓存是mapper
级别的缓存,二级缓存是跨SqlSession
的,多个SqlSession
对象可以共享同一个二级缓存。不同的SqlSession
对象执行两次相同的SQL语句,第一次会将查询结果进行缓存,第二次查询直接返回二级缓存中的结果即可。MyBatis默认是不开启二级缓存的,可以在配置文件中使用如下配置来开启二级缓存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
当SQL语句进行更新操作(删除/添加/更新
)时,会清空对应的缓存,保证缓存中存储的都是最新的数据。
MyBatis动态sql
(1)多条件查询
<select id="getUserList" resultMap="userList">
SELECT u.*,r.roleName FROM smbms_user u,smbms_role r
WHERE u.userName LIKE CONCAT('%','#{userName}','%')
AND u.userRole=#{userRole}
AND u.userRole=r.id
</select> (2)if-where的用法
<select id="getUserList" resultType="User">
SELECT * FROM smbms_user
<where>
<if test="userName!=null AND userName!=">
AND userName LIKE CONCAT('%','#{userName}','%')
</if>
<if test="userRole!=null">
AND userRole=#{userRole}
</if>
</where>
</select>
<where>会自动去掉第一个and。 (3)if-trim
<select id="getUserList" resultType="User">
SELECT * FROM smbms_user
<trim prefix="where" prefixOverrides="and|or">
<if test="userName!=null and userName!=">
AND userName LIKE CONCAT('%','#{userName}','%')
</if>
<if test="userRole!=null">
AND userRole=#{userRole}
</if>
</trim>
</select>
<trim prefix="where" prefixOverrides="and|or">作用为自动添加where或者对and|or的自动忽略 (4)if-set 动态更新,假设没有涉及到的或者不需要更新的就可以不用更新,set标签可以自动剔除逗号(,)
<!--parameterType:属性名,如果是select就写resultMap,是其他的写对应实体对应的路径位置-->
<update id="modifyXxx" parameterType="User">
UPDATE smbms_user
<set>
<if test="userCode!=NULL">
userCode=#{userCode},
</if>
<if test="userName!=null">
userName=#{userName},
</if>
<if test="phone!=null">
phone=#{phone},
</if>
</set>
WHERE id=#{id}
</update> (5)if-set中的trim
<update id="modify" parameterType="User">
UPDATE smbms_user
<trim prefix="set" prefixOverrides="," suffix="where id=#{id}">
</trim>
<if test="userCode!=null">
userCode=#{userCode},
</if>
<if test="userName!=null">
userName=#{userName},
</if>
<if test="phone!=null">
phone=#{phone},
</if>
</update>
<trim suffix="where id=#{id}">在trim内容的后面加上后缀 (6)foreach迭代collection数组类型的入参:对于sql语句中含有in语句,则需要foreach标签来实现sql条件的迭代
eg:SELECT u.* from smbms_user u WHERE userRole in(2,4)
<select id="getUserByRoleId_foreach_array" resultMap="userMapByRole">
SELECT * FROM smbms_user WHERE userRole IN
<foreach collection="array" item="roleIds" open="(" separator="," close=")">
#{roleIds}
</foreach>
</select>
<resultMap id="userMapByRole" type="User">
<id property="id" column="id"/>
<result property="userCode" column="userCode"/>
<result property="userName" column="userName"/>
</resultMap>
-->Dao层接口方法为:LIst<User> getUserByRoleId_foreach_array(Integer[] roleIds)
-->item :集合中进行迭代时的别名,
-->index :指定一个名称,表示在迭代过程中每次迭代到的位置
-->separator:每次进行迭代用什么分隔符号,in条件语句用逗号(,)为分隔符
-->open:表示该语句以什么开始的,in语句以 “(”开始
-->close:表示该语句以什么符号结束 ,in语句以“)”结束
-->collection:如果是入参类型是参数是LIst,则collection属性值为list;是一个数组,则为array,如果为多参数,则需要封装成一个Map进行处理 (7)foreach迭代list类型的入参
-->Dao层接口方法为:LIst<User> getUserByRoleId_foreach_list(List<Integer> roleList);
<select id="getUserByRoleId_foreach_list" resultMap="userMapByRole">
SELECT * FROM smbms_user WHERE userRole IN
<foreach collection="list" item="roleIds" open="(" separator="," close=")">
#{roleIds}
</foreach>
</select>
<resultMap id="userMapByRole" type="User">
<id property="id" column="id"/>
<result property="userCode" column="userCode"/>
<result property="userName" column="userName"/>
</resultMap> (8)foreach迭代Map类型的入参
接口方法:public List<User> getUserByRoleId_foreach_map(Map<String,Object> conditionMap);
@Test
public void getUserListByUserName() throws Exception {
Map<String,Object> conditionMap=new HashMap<String, Object>();
List<Integer> roleList=new ArrayList<Integer>();
roleList.add(2);
//gender是一个限定条件
conditionMap.put("gender",1);
//roleIds 对应collection
conditionMap.put("roleIds",roleList);
System.out.println("----------------------------------");
System.out.println(userDao.getUserByRoleId_foreach_map(conditionMap));
System.out.println("-------------------------------------"); <select id="getUserByRoleId_foreach_map" resultMap="userMapByRole">
SELECT * FROM smbms_user WHERE gender=#{gender} and userRole in
<foreach collection="roleIds" item="m" open="(" separator="," close=")">
#{m}
</foreach>
</select> (9)choose(when-otherwise)
接口方法:public List<User> getUserList_choose(@Param("userName") String userName, @Param("userRole")Integer userRole,
@Param("userCode")String userCode, @Param("creationDate")Date creationDate);
测试类:
@Test
public void getUserList_choose() throws Exception {
List<User> userList =new ArrayList<>();
String userName="张明";
Integer userRole=2;
String userCode="";
Date creationDate=new SimpleDateFormat("yyy-MM-dd").parse("2030-10-26");
userList=userDao.getUserList_choose(userName,userRole,userCode,creationDate);
System.out.println(userList);
mapper:
<select id="getUserList_choose" resultMap="userMapByRole">
SELECT * FROM smbms_user WHERE 1=1
<choose>
<when test="userName!=null and userName!=''">
AND userName LIKE CONCAT('%',#{userName},'%')
</when>
<when test="userCode!=null and userCode!=''">
AND userCode LIKE CONCAT('%',#{userCode},'%')
</when>
<when test="userRole!=null and userRole!=''">
AND userRole=#{userRole}
</when>
<otherwise>
AND YEAR(creationDate)=YEAR(#{creationDate})
</otherwise>
</choose>
</select>
-->when:当满足一个条件时跳出循环,
-->otherwise:当所有的when都不满足的时候,执行otherwise
-->choose:相当于switch
-->where 1=1:可以不需要处理多余的and
mybatis原理分析学习记录,mybatis动态sql学习记录的更多相关文章
- Mybatis 动态 sql 有什么用?执行原理?有哪些动态 sql?
Mybatis 动态 sql 可以在 Xml 映射文件内,以标签的形式编写动态 sql,执行原理 是根据表达式的值 完成逻辑判断并动态拼接 sql 的功能. Mybatis 提供了 9 种动态 sql ...
- Mybatis 系列9-强大的动态sql 语句
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis之关联查询及动态SQL
前言 实际开发项目中,很少是针对单表操作,基本都会联查多表进行操作,尤其是出一些报表的内容.此时,就可以使用Mybatis的关联查询还有动态SQL.前几篇文章已经介绍过了怎么调用及相关内容,因此这里只 ...
- mybatis学习记录五——动态sql
8 动态sql 8.1 什么是动态sql mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接.组装. 8.2 需求 用户信息综合查询列表 ...
- MyBatis(4):动态SQL
什么是动态SQL MyBatis的一个强大特性之一通常是它的动态SQL能力.如果你有使用JDBC或其他相似框架的经验,你就明白条件串联SQL字符串在一起是多么地痛苦,确保不能忘了空格或者在列表的最后的 ...
- Mybatis第三篇【动态SQL】
动态SQL 何为动态SQL??回顾一下我们之前写的SSH项目中,有多条件查询的情况,如下图 我们当时刚开始做的时候,是需要在Controller中判断SQL是否已经有条件了,因为SQL语句需要拼接起来 ...
- MyBatis之基于XML的动态SQL
先说下我的梦想,大学的时候一直想着是能开店卖胡辣汤,到目前依然还是我的梦想,上周一家出版社联系我问我有没有时间可以合作出书,这也是我的梦想之一,想了想还是放弃了,至少觉得目前不行,毕竟工作还不到五年, ...
- 【转载】 mybatis入门系列四之动态SQL
mybatis 详解(五)------动态SQL 目录 1.动态SQL:if 语句 2.动态SQL:if+where 语句 3.动态SQL:if+set 语句 4.动态SQL:choose(when, ...
- spring boot整合mybatis基于注解开发以及动态sql的使用
让我们回忆一下上篇博客中mybatis是怎样发挥它的作用的,主要是三类文件,第一mapper接口,第二xml文件,第三全局配置文件(application.properties),而今天我们就是来简化 ...
随机推荐
- css实现三角形相关
1.css样式面包屑导航条实现矩形和三角箭头拼接 .cssTest { font-family: PingFangSC-Regular; font-size: 16px; color: #333333 ...
- supervisor无法监听uwsgi
➜ ~ sudo supervisorctl data_config FATAL Exited too quickly (process log may have details) data_conf ...
- python迭代-如何使用生成器函数实现可迭代对象
如何使用生成器函数实现可迭代对象 问题举例: 实现一个可迭代对象的类,它能迭代出给定范围内 的所有素数: pn = PrimeNumbers(1, 30) for x in pn: print(x) ...
- LeetCode-7-反转数组-c#
目前看到的网上的版本 都是用到数组的反转函数.对于代码实现的细节没给出 反转函数的想法没给出. 特在此补充. Reverse函数是从c#库源码中抠出的主要部分代码.主要是双端步进.交换,当然是o(1) ...
- UML第三次作业
一.PlantUML类图 语法学习小结 关系上的标识:在关系之间使用标签来说明时, 使用 :后接 标签文字.对元素的说明,可以在每一边使用 "" 来说明. 1 @startuml ...
- Vue.js数据响应基础原理
许多前端JavaScript框架(例如Angular,React和Vue)都有自己的数据相应引擎.通过了解相应性及其工作原理,您可以提高开发技能并更有效地使用JavaScript框架.在视频和下面的文 ...
- cmd中查看MySQL数据库表数据及结构
0. 1 .cmd进入mysql安装的bin目录(C:\Program Files\XXXXXX\MySQL Server 5.6\bin) mysql -hlocalhost -uroot -p 回 ...
- namenode No valid image files
1,角色日志报错 Encountered exception loading fsimage java.io.FileNotFoundException: No valid image files f ...
- IIS部署Angular2
因為Angular無刷新的特性,所以瀏覽器地址欄上的網址其實不會真實映射到磁盤的特定位置,所以我們需要安裝.Rewrite Module, 如下: Web.config 如下: <?xml ve ...
- talend工具中往oracle插数据报ORA-01461: can bind a LONG value only for insert into a LONG colum
今天使用talend往oracle插数据报ORA-01461: can bind a LONG value only for insert into a LONG column 数据源是mysql,开 ...