mybatis原理分析学习记录,mybatis动态sql学习记录
以下个人学习笔记,仅供参考,欢迎指正。
mybatis 是支持定制化 sql、存储过程以及高级映射的持久层框架,其主要就完成2件事情:
-
封装jdbc操作
-
利用反射打通java类与sql语句之间的相互转换
mybatis的主要设计目的就是让我们对执行sql语句时对输入输出的数据管理更加方便,所以方便地写出sql和方便地获取sql的执行结果才是mybatis的核心竞争力。
mybatis的配置
spring整合mybatis(druid数据源)
1 (1)整合思路:把mybatis框架中使用所涉及的核心组件配置到spring容器中 2 (2)步骤: 3 -->1.添加pom依赖,mybatis-spring,spring-tx,spring-jdbc 4 -->2.创建实体类(entity) 5 -->3.创建数据访问接口(dao层的接口) 6 -->4.配置sql映射文件(resource下的mapper.xml文件) 7 -->5.配置mybatis的配置文件(resource下的spring下的mybatis-config.xml) 8 -->6.凡是使用了注解,都需要配置扫描注解定义的业务bean: <context:component-scan base-package="com.one.ssm.dao"/> 9 和<context:annotation-config/>(用于激活那些已经在spring容器里注册过的bean) 10 11 12 <?xml version="1.0" encoding="utf-8" ?> 13 <!doctype configuration 14 public "-//mybatis.org//dtd config 3.0//en" 15 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 16 <configuration> 17 <!--配置全局属性--> 18 <settings> 19 <!--使用jdbc的getgeneratedkeys获取数据库自增主键值--> 20 <setting name="usegeneratedkeys" value="true"/> 21 <!--使用列别名替换列名,默认true, eg:select name as title from table--> 22 <setting name="usecolumnlabel" value="true"/> 23 <!--开启驼峰命名转换 table(create_time)-->entity(createtime) 24 <setting name="mapunderscoretocamelcase" value="true"/> 25 </settings> 26 </configuration> 27 28 (3)实现整合(spring-dao.xml操作) 29 -->1.配置datasource数据源 30 jdbc.properties内容: 31 jdbc.driver=com.mysql.jdbc.driver 32 jdbc.url=jdbc:mysql://localhost:3306/smbms?useunicode=true&characterencoding=utf-8 33 jdbc.username=root 34 jdbc.password=123456 35 36 <!--properties文件配置数据源--> 37 <context:property-placeholder location="classpath:spring/jdbc.properties"/> 38 <bean id="datasource" class="com.alibaba.druid.pool.druiddatasource" destroy-method="close"> 39 <!--配置连接池属性--> 40 <property name="driverclassname" value="${jdbc.driver}"/> 41 <property name="url" value="${jdbc.url}"/> 42 <property name="username" value="${jdbc.username}"/> 43 <property name="password" value="${jdbc.password}"/> 44 </bean> 45 46 -->2.配置sqlsessionfactorybean 47 <!--sqlsession 包含了所有执行数据库sql语句的方法。能够直接地通过sqlsession实例执行映射sql--> 48 <!--缺少sqlsessionfactory:no bean named 'sqlsessionfactory' available 完成对配置文件的读取--> 49 <bean id="sqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean"> 50 <!--注入数据库连接池--><!--否则会出现java.lang.illegalargumentexception: property 'datasource' is required--> 51 <property name="datasource" ref="datasource"/> 52 53 <!--扫描entity包,使用别名,设置这个以后再mapper配置文件中在parametertype 54 的值就不用写成全路径名了--> 55 <property name="typealiasespackage" value="com.one.ssm.entity"/> 56 57 <!--扫描mapper需要的xml文件--> 58 <property name="mapperlocations" value="classpath:mapper/*.xml"/> 59 </bean> 60 61 <!-- 配置扫描dao接口包,动态实现dao接口,注入到spring容器中 --> 62 <bean class="org.mybatis.spring.mapper.mapperscannerconfigurer"> 63 <!-- 注入sqlsessionfactory --> 64 <property name="sqlsessionfactorybeanname" value="sqlsessionfactory"/> 65 <!-- 给出需要扫描dao接口包 --> 66 <property name="basepackage" value="com.one.ssm.dao"/> 67 </bean> 68 69 (4)注入映射器的两种方式:使用了映射器注入就可以不用写dao层的接口的实现方法 70 -->1.配置mapperfactorybean生成映射器并注入到业务组件 71 <bean id="usermapper" class="org.mybatis.spring.mapper.mapperfactorybean"> 72 <!--mapperinterface属性指定映射器,只能是某一个接口类型--> 73 <property name="mapperinterface" value="com.one.ssm.dao.userdao"/> 74 <!-- 注入sqlsessionfactory --> 75 <property name="sqlsessionfactory" ref="sqlsessionfactory"/> 76 </bean> 77 78 -->2.配置mapperscannerconfiger生成映射器并注入到业务组件:优先使用mapperscannerconfiger,可以批量生成映射器的实现 79 <!--mybatis-spring提供了mapperscannerconfigurer, 80 可以扫描指定包中的接口并将它们直接注册为mapperfactorybean,为了简化mapperfactorybean映射器太多而导致多出的配置项--> 81 <!--mybatis-spring提供了mapperscannerconfigurer,可以扫描指定包中的接口并将它们直接注册为mapperfactorybean--> 82 <bean class="org.mybatis.spring.mapper.mapperscannerconfigurer"> 83 <!-- 注入sqlsessionfactory --> 84 <property name="sqlsessionfactorybeanname" value="sqlsessionfactory"/> 85 <!-- 给出需要扫描dao接口包 --> 86 <property name="basepackage" value="com.one.ssm.dao"/> 87 </bean> 88 89 (5)添加声明式事务(spring-service.xml操作) 90 -->使用xml配置方法配置声明式事务 91 <bean id="datasource" class="com.alibaba.druid.pool.druiddatasource" destroy-method="close"> 92 <property name="url" value="${jdbc.url}" /> 93 <property name="username" value="${jdbc.username}" /> 94 <property name="password" value="${jdbc.password}" /> 95 <property name="driverclassname" value="${jdbc.driver}" /> 96 </bean> 97 <!--配置事务(事务就是对一系列的数据库操作进行统一的提交或回滚操作)管理器--> 98 <bean id="transactionmanager" 99 class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> 100 <property name="datasource" ref="datasource"/> 101 </bean> 102 -->使用注解添加声明式事务 103 <!--声明式事务注解的添加方式--> 104 <tx:annotation-driven transaction-manager="transactionmanager"/>
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.
}
}
}
1 public configuration parse() { 2 if (parsed) { 3 throw new builderexception("each xmlconfigbuilder can only be used once."); 4 } 5 parsed = true; 6 parseconfiguration(parser.evalnode("/configuration")); 7 return configuration; 8 } 9 10 private void parseconfiguration(xnode root) { 11 try { 12 //issue #117 read properties first 13 propertieselement(root.evalnode("properties")); 14 properties settings = settingsasproperties(root.evalnode("settings")); 15 loadcustomvfs(settings); 16 typealiaseselement(root.evalnode("typealiases")); 17 pluginelement(root.evalnode("plugins")); 18 objectfactoryelement(root.evalnode("objectfactory")); 19 objectwrapperfactoryelement(root.evalnode("objectwrapperfactory")); 20 reflectorfactoryelement(root.evalnode("reflectorfactory")); 21 settingselement(settings); 22 // read it after objectfactory and objectwrapperfactory issue #631 23 24 /* 处理environments节点数据 */ 25 environmentselement(root.evalnode("environments")); 26 databaseidproviderelement(root.evalnode("databaseidprovider")); 27 typehandlerelement(root.evalnode("typehandlers")); 28 mapperelement(root.evalnode("mappers")); 29 } catch (exception e) { 30 throw new builderexception("error parsing sql mapper configuration. cause: " + e, e); 31 } 32 }
在configuration
节点下会依次解析properties/settings/.../mappers
等节点配置。在解析environments
节点时,会根据transactionmanager
的配置来创建事务管理器,根据datasource
的配置来创建datasource
对象,这里面包含了数据库登录的相关信息。在解析mappers
节点时,会读取该节点下所有的mapper
文件,然后进行解析,并将解析后的结果存到configuration
对象中。
1 private void environmentselement(xnode context) throws exception { 2 if (context != null) { 3 if (environment == null) { 4 environment = context.getstringattribute("default"); 5 } 6 for (xnode child : context.getchildren()) { 7 string id = child.getstringattribute("id"); 8 if (isspecifiedenvironment(id)) { 9 10 /* 创建事务管理器 */ 11 transactionfactory txfactory = transactionmanagerelement(child.evalnode("transactionmanager")); 12 datasourcefactory dsfactory = datasourceelement(child.evalnode("datasource")); 13 datasource datasource = dsfactory.getdatasource(); 14 15 /* 建造者模式 设计模式 */ 16 environment.builder environmentbuilder = new environment.builder(id) 17 .transactionfactory(txfactory) 18 .datasource(datasource); 19 configuration.setenvironment(environmentbuilder.build()); 20 } 21 } 22 } 23 } 24 25 // 解析单独的mapper文件 26 private void mapperelement(xnode parent) throws exception { 27 if (parent != null) { 28 for (xnode child : parent.getchildren()) { 29 if ("package".equals(child.getname())) { 30 string mapperpackage = child.getstringattribute("name"); 31 configuration.addmappers(mapperpackage); 32 } else { 33 string resource = child.getstringattribute("resource"); 34 string url = child.getstringattribute("url"); 35 string mapperclass = child.getstringattribute("class"); 36 if (resource != null && url == null && mapperclass == null) { 37 errorcontext.instance().resource(resource); 38 inputstream inputstream = resources.getresourceasstream(resource); 39 xmlmapperbuilder mapperparser = new xmlmapperbuilder(inputstream, configuration, resource, configuration.getsqlfragments()); 40 mapperparser.parse(); // 开始解析mapper文件了 :) 41 } else if (resource == null && url != null && mapperclass == null) { 42 errorcontext.instance().resource(url); 43 inputstream inputstream = resources.geturlasstream(url); 44 xmlmapperbuilder mapperparser = new xmlmapperbuilder(inputstream, configuration, url, configuration.getsqlfragments()); 45 mapperparser.parse(); 46 } else if (resource == null && url == null && mapperclass != null) { 47 class<?> mapperinterface = resources.classforname(mapperclass); 48 configuration.addmapper(mapperinterface); 49 } else { 50 throw new builderexception("a mapper element may only specify a url, resource or class, but not more than one."); 51 } 52 } 53 } 54 } 55 }
解析完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
操作中,优先会查询缓存是否命中,命中则直接返回,否则从数据库中查询。
1 public <e> list<e> query(mappedstatement ms, object parameterobject, rowbounds rowbounds, resulthandler resulthandler) throws sqlexception { 2 /* 获取关联参数的sql,boundsql */ 3 boundsql boundsql = ms.getboundsql(parameterobject); 4 /* 创建cache key值 */ 5 cachekey key = createcachekey(ms, parameterobject, rowbounds, boundsql); 6 return query(ms, parameterobject, rowbounds, resulthandler, key, boundsql); 7 } 8 9 public <e> list<e> query(mappedstatement ms, object parameterobject, rowbounds rowbounds, resulthandler resulthandler, cachekey key, boundsql boundsql) 10 throws sqlexception { 11 /* 获取二级缓存实例 */ 12 cache cache = ms.getcache(); 13 if (cache != null) { 14 flushcacheifrequired(ms); 15 if (ms.isusecache() && resulthandler == null) { 16 ensurenooutparams(ms, parameterobject, boundsql); 17 @suppresswarnings("unchecked") 18 list<e> list = (list<e>) tcm.getobject(cache, key); 19 if (list == null) { 20 list = delegate.<e> query(ms, parameterobject, rowbounds, resulthandler, key, boundsql); 21 tcm.putobject(cache, key, list); // issue #578 and #116 22 } 23 return list; 24 } 25 } 26 return delegate.<e> query(ms, parameterobject, rowbounds, resulthandler, key, boundsql); 27 } 28 29 private <e> list<e> queryfromdatabase(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler, cachekey key, boundsql boundsql) throws sqlexception { 30 list<e> list; 31 /** 32 * 先往localcache中插入一个占位对象,这个地方 33 */ 34 localcache.putobject(key, execution_placeholder); 35 try { 36 list = doquery(ms, parameter, rowbounds, resulthandler, boundsql); 37 } finally { 38 localcache.removeobject(key); 39 } 40 41 /* 往缓存中写入数据,也就是缓存查询结果 */ 42 localcache.putobject(key, list); 43 if (ms.getstatementtype() == statementtype.callable) { 44 localoutputparametercache.putobject(key, parameter); 45 } 46 return list;
真正的doquery
操作是由simplyexecutor
代理来完成的,该方法中有2个子流程,一个是sql参数的设置,另一个是sql查询操作和结果集的封装。
1 public <e> list<e> doquery(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler, boundsql boundsql) throws sqlexception { 2 statement stmt = null; 3 try { 4 configuration configuration = ms.getconfiguration(); 5 statementhandler handler = configuration.newstatementhandler(wrapper, ms, parameter, rowbounds, resulthandler, boundsql); 6 7 /* 子流程1: sql查询参数的设置 */ 8 stmt = preparestatement(handler, ms.getstatementlog()); 9 10 /* 子流程2: sql查询操作和结果集封装 */ 11 return handler.<e>query(stmt, resulthandler); 12 } finally { 13 closestatement(stmt); 14 } 15 }
子流程1 sql查询参数的设置:
1 private statement preparestatement(statementhandler handler, log statementlog) throws sqlexception { 2 statement stmt; 3 /* 获取connection连接 */ 4 connection connection = getconnection(statementlog); 5 6 /* 准备statement */ 7 stmt = handler.prepare(connection, transaction.gettimeout()); 8 9 /* 设置sql查询中的参数值 */ 10 handler.parameterize(stmt); 11 return stmt; 12 } 13 14 // defaultparameterhandler类 15 public void setparameters(preparedstatement ps) { 16 /** 17 * 设置sql参数值,从parametermapping中读取参数值和类型,然后设置到sql语句中 18 */ 19 errorcontext.instance().activity("setting parameters").object(mappedstatement.getparametermap().getid()); 20 list<parametermapping> parametermappings = boundsql.getparametermappings(); 21 if (parametermappings != null) { 22 for (int i = 0; i < parametermappings.size(); i++) { 23 parametermapping parametermapping = parametermappings.get(i); 24 if (parametermapping.getmode() != parametermode.out) { 25 object value; 26 string propertyname = parametermapping.getproperty(); 27 if (boundsql.hasadditionalparameter(propertyname)) { // issue #448 ask first for additional params 28 value = boundsql.getadditionalparameter(propertyname); 29 } else if (parameterobject == null) { 30 value = null; 31 } else if (typehandlerregistry.hastypehandler(parameterobject.getclass())) { 32 value = parameterobject; 33 } else { 34 metaobject metaobject = configuration.newmetaobject(parameterobject); 35 value = metaobject.getvalue(propertyname); 36 } 37 typehandler typehandler = parametermapping.gettypehandler(); 38 jdbctype jdbctype = parametermapping.getjdbctype(); 39 if (value == null && jdbctype == null) { 40 jdbctype = configuration.getjdbctypefornull(); 41 } 42 try { 43 typehandler.setparameter(ps, i + 1, value, jdbctype); 44 } catch (typeexception e) { 45 throw new typeexception("could not set parameters for mapping: " + parametermapping + ". cause: " + e, e); 46 } catch (sqlexception e) { 47 throw new typeexception("could not set parameters for mapping: " + parametermapping + ". cause: " + e, e); 48 } 49 } 50 } 51 } 52 }
子流程2 sql查询结果集的封装:
1 public <e> list<e> query(statement statement, resulthandler resulthandler) throws sqlexception { 2 preparedstatement ps = (preparedstatement) statement; 3 // 执行查询操作 4 ps.execute(); 5 // 执行结果集封装 6 return resultsethandler.<e> handleresultsets(ps); 7 } 8 9 // defaultreseltsethandler类 10 public list<object> handleresultsets(statement stmt) throws sqlexception { 11 errorcontext.instance().activity("handling results").object(mappedstatement.getid()); 12 13 final list<object> multipleresults = new arraylist<object>(); 14 15 int resultsetcount = 0; 16 /** 17 * 获取第一个resultset,同时获取数据库的metadata数据,包括数据表列名、列的类型、类序号等。 18 * 这些信息都存储在了resultsetwrapper中了 19 */ 20 resultsetwrapper rsw = getfirstresultset(stmt); 21 22 list<resultmap> resultmaps = mappedstatement.getresultmaps(); 23 int resultmapcount = resultmaps.size(); 24 validateresultmapscount(rsw, resultmapcount); 25 while (rsw != null && resultmapcount > resultsetcount) { 26 resultmap resultmap = resultmaps.get(resultsetcount); 27 handleresultset(rsw, resultmap, multipleresults, null); 28 rsw = getnextresultset(stmt); 29 cleanupafterhandlingresultset(); 30 resultsetcount++; 31 } 32 33 string[] resultsets = mappedstatement.getresultsets(); 34 if (resultsets != null) { 35 while (rsw != null && resultsetcount < resultsets.length) { 36 resultmapping parentmapping = nextresultmaps.get(resultsets[resultsetcount]); 37 if (parentmapping != null) { 38 string nestedresultmapid = parentmapping.getnestedresultmapid(); 39 resultmap resultmap = configuration.getresultmap(nestedresultmapid); 40 handleresultset(rsw, resultmap, null, parentmapping); 41 } 42 rsw = getnextresultset(stmt); 43 cleanupafterhandlingresultset(); 44 resultsetcount++; 45 } 46 } 47 48 return collapsesingleresultlist(multipleresults); 49 }
resultsetwrapper
是resultset
的包装类,调用getfirstresultset
方法获取第一个resultset
,同时获取数据库的metadata
数据,包括数据表列名、列的类型、类序号等,这些信息都存储在resultsetwrapper
类中了。然后调用handleresultset
方法来来进行结果集的封装。
1 private void handleresultset(resultsetwrapper rsw, resultmap resultmap, list<object> multipleresults, resultmapping parentmapping) throws sqlexception { 2 try { 3 if (parentmapping != null) { 4 handlerowvalues(rsw, resultmap, null, rowbounds.default, parentmapping); 5 } else { 6 if (resulthandler == null) { 7 defaultresulthandler defaultresulthandler = new defaultresulthandler(objectfactory); 8 handlerowvalues(rsw, resultmap, defaultresulthandler, rowbounds, null); 9 multipleresults.add(defaultresulthandler.getresultlist()); 10 } else { 11 handlerowvalues(rsw, resultmap, resulthandler, rowbounds, null); 12 } 13 } 14 } finally { 15 // issue #228 (close resultsets) 16 closeresultset(rsw.getresultset()); 17 } 18 }
调用handlerowvalues
方法进行结果值的设置
1 public void handlerowvalues(resultsetwrapper rsw, resultmap resultmap, resulthandler<?> resulthandler, rowbounds rowbounds, resultmapping parentmapping) throws sqlexception { 2 if (resultmap.hasnestedresultmaps()) { 3 ensurenorowbounds(); 4 checkresulthandler(); 5 handlerowvaluesfornestedresultmap(rsw, resultmap, resulthandler, rowbounds, parentmapping); 6 } else { 7 // 封装数据 8 handlerowvaluesforsimpleresultmap(rsw, resultmap, resulthandler, rowbounds, parentmapping); 9 } 10 } 11 12 private void handlerowvaluesforsimpleresultmap(resultsetwrapper rsw, resultmap resultmap, resulthandler<?> resulthandler, rowbounds rowbounds, resultmapping parentmapping) 13 throws sqlexception { 14 defaultresultcontext<object> resultcontext = new defaultresultcontext<object>(); 15 skiprows(rsw.getresultset(), rowbounds); 16 while (shouldprocessmorerows(resultcontext, rowbounds) && rsw.getresultset().next()) { 17 resultmap discriminatedresultmap = resolvediscriminatedresultmap(rsw.getresultset(), resultmap, null); 18 object rowvalue = getrowvalue(rsw, discriminatedresultmap); 19 storeobject(resulthandler, resultcontext, rowvalue, parentmapping, rsw.getresultset()); 20 } 21 } 22 23 private object getrowvalue(resultsetwrapper rsw, resultmap resultmap) throws sqlexception { 24 final resultloadermap lazyloader = new resultloadermap(); 25 // createresultobject为新创建的对象,数据表对应的类 26 object rowvalue = createresultobject(rsw, resultmap, lazyloader, null); 27 if (rowvalue != null && !hastypehandlerforresultobject(rsw, resultmap.gettype())) { 28 final metaobject metaobject = configuration.newmetaobject(rowvalue); 29 boolean foundvalues = this.useconstructormappings; 30 if (shouldapplyautomaticmappings(resultmap, false)) { 31 // 这里把数据填充进去,metaobject中包含了resultobject信息 32 foundvalues = applyautomaticmappings(rsw, resultmap, metaobject, null) || foundvalues; 33 } 34 foundvalues = applypropertymappings(rsw, resultmap, metaobject, lazyloader, null) || foundvalues; 35 foundvalues = lazyloader.size() > 0 || foundvalues; 36 rowvalue = (foundvalues || configuration.isreturninstanceforemptyrow()) ? rowvalue : null; 37 } 38 return rowvalue; 39 } 40 41 private boolean applyautomaticmappings(resultsetwrapper rsw, resultmap resultmap, metaobject metaobject, string columnprefix) throws sqlexception { 42 list<unmappedcolumnautomapping> automapping = createautomaticmappings(rsw, resultmap, metaobject, columnprefix); 43 boolean foundvalues = false; 44 if (automapping.size() > 0) { 45 // 这里进行for循环调用,因为user表中总共有7列,所以也就调用7次 46 for (unmappedcolumnautomapping mapping : automapping) { 47 // 这里将esultset中查询结果转换为对应的实际类型 48 final object value = mapping.typehandler.getresult(rsw.getresultset(), mapping.column); 49 if (value != null) { 50 foundvalues = true; 51 } 52 if (value != null || (configuration.iscallsettersonnulls() && !mapping.primitive)) { 53 // gcode issue #377, call setter on nulls (value is not 'found') 54 metaobject.setvalue(mapping.property, value); 55 } 56 } 57 } 58 return foundvalues; 59 }
mapping.typehandler.getresult
会获取查询结果值的实际类型,比如我们user表中id字段为int类型,那么它就对应java中的integer类型,然后通过调用statement.getint("id")
来获取其int值,其类型为integer
。metaobject.setvalue
方法会把获取到的integer
值设置到java类中的对应字段。
metavalue.setvalue
方法最后会调用到java类中对应数据域的set方法,这样也就完成了sql查询结果集的java类封装过程。
1 public void setvalue(string name, object value) { 2 propertytokenizer prop = new propertytokenizer(name); 3 if (prop.hasnext()) { 4 metaobject metavalue = metaobjectforproperty(prop.getindexedname()); 5 if (metavalue == systemmetaobject.null_meta_object) { 6 if (value == null && prop.getchildren() != null) { 7 // don't instantiate child path if value is null 8 return; 9 } else { 10 metavalue = objectwrapper.instantiatepropertyvalue(name, prop, objectfactory); 11 } 12 } 13 metavalue.setvalue(prop.getchildren(), value); 14 } else { 15 objectwrapper.set(prop, value); 16 } 17 }
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
上一篇: golang 杂思