欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

mybatis原理分析学习记录,mybatis动态sql学习记录

程序员文章站 2023-04-05 22:16:23
以下个人学习笔记,仅供参考,欢迎指正。 MyBatis 是支持定制化 SQL、存储过程以及高级映射的持久层框架,其主要就完成2件事情: 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转换 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转换 MyBatis的主要设计目的 ......

以下个人学习笔记,仅供参考,欢迎指正。

mybatis 是支持定制化 sql、存储过程以及高级映射的持久层框架,其主要就完成2件事情:

  • 封装jdbc操作

  • 利用反射打通java类与sql语句之间的相互转换

mybatis的主要设计目的就是让我们对执行sql语句时对输入输出的数据管理更加方便,所以方便地写出sql和方便地获取sql的执行结果才是mybatis的核心竞争力。

mybatis的配置

 spring整合mybatis(druid数据源)

mybatis原理分析学习记录,mybatis动态sql学习记录
  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"/>
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.
        }
    }
}
mybatis原理分析学习记录,mybatis动态sql学习记录
 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 }
xmlconfigbuilder类

 在configuration节点下会依次解析properties/settings/.../mappers等节点配置。在解析environments节点时,会根据transactionmanager的配置来创建事务管理器,根据datasource的配置来创建datasource对象,这里面包含了数据库登录的相关信息。在解析mappers节点时,会读取该节点下所有的mapper文件,然后进行解析,并将解析后的结果存到configuration对象中。

mybatis原理分析学习记录,mybatis动态sql学习记录
 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   }
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操作中,优先会查询缓存是否命中,命中则直接返回,否则从数据库中查询。

mybatis原理分析学习记录,mybatis动态sql学习记录
 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;
cachingexecutor类

真正的doquery操作是由simplyexecutor代理来完成的,该方法中有2个子流程,一个是sql参数的设置,另一个是sql查询操作和结果集的封装。

mybatis原理分析学习记录,mybatis动态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查询参数的设置:

mybatis原理分析学习记录,mybatis动态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 }
子流程1 sql查询参数的设置:

子流程2 sql查询结果集的封装:

mybatis原理分析学习记录,mybatis动态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   }
sql查询结果集的封装

resultsetwrapperresultset的包装类,调用getfirstresultset方法获取第一个resultset,同时获取数据库的metadata数据,包括数据表列名、列的类型、类序号等,这些信息都存储在resultsetwrapper类中了。然后调用handleresultset方法来来进行结果集的封装。

mybatis原理分析学习记录,mybatis动态sql学习记录
 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 }
defaultresultsethandler类

调用handlerowvalues方法进行结果值的设置

mybatis原理分析学习记录,mybatis动态sql学习记录
 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 }
defaultresultsethandler类

mapping.typehandler.getresult会获取查询结果值的实际类型,比如我们user表中id字段为int类型,那么它就对应java中的integer类型,然后通过调用statement.getint("id")来获取其int值,其类型为integermetaobject.setvalue方法会把获取到的integer值设置到java类中的对应字段。

metavalue.setvalue方法最后会调用到java类中对应数据域的set方法,这样也就完成了sql查询结果集的java类封装过程。

mybatis原理分析学习记录,mybatis动态sql学习记录
 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 }
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