Mybatis映射器

映射器是MyBatis最弱小的⼯具,也是咱们应用MyBatis时⽤得最多的工具,因而熟
练把握它⼗分必要。MyBatis是针对映射器结构的SQL构建的轻量级框架,并且通过配置
生成对应的JavaBean返回给调用者,⽽这些配置次要便是映射器,在MyBatis中你能够根
据状况定义动静SQL来满足不同场景的须要,它比其余框架灵便得多。MyBatis还反对⾃动绑定JavaBean,
咱们只有让SQL返回的字段名和JavaBean 的属性名保持一致(或者采⽤驼峰式命名),便能够省掉这些繁琐的映射配置

目录:
[toc]

咱们先再回顾下映射器的次要元素

映射器的次要元素

映射器是由Java接口和XML文件(或注解)独特组成的,Java接口次要定义调用者接口,XML文件是配置映射器的外围文件,包含以下元素:


  • select 查问语句,能够自定义参数,返回后果集;
  • insert 插入语句,返回一个整数,示意插入的条数;
  • update 更新语句,返回一个整数,示意更新的条数;
  • delete 删除语句,返回一个整数,示意删除的条数;
  • sql 容许定义一部分SQL,而后再各个中央援用;
  • resultMap 用来形容从数据库后果集中来加载对象,还能够配置关联关系,提供映射规定;
  • cache 给定命名空间的缓存配置

Select元素

select元素帮忙咱们
从数据库中读出数据,组装数据给业务人员。执行select语句前,咱们须要定义参数,它
能够是⼀个简略的参数类型,例如int, float , String,也能够是⼀个简单的参数类型
例如
JavaBean、 Map等,这些都是MyBatis承受的参数类型。

执⾏SQL后,MyBatis也提供了
强⼤的映射规定,主动映射来帮忙咱们把返回的后果集绑定到JavaBean中。

select
元素的配置泛滥,上面简略阐明下:

  • id: id和Mapper的命名空间组成惟一值,提供给Mybatis调用,如果不惟一将会报错
  • paramterType:传入的参数类型,能够是根本类型、map、自定义的java bean;
  • resultType:返回的后果类型,能够是根本类型、自定义的java bean;
  • resultMap:它是最简单的元素,能够配置映射规定、级联、typeHandler等,与ResultType不能同时存在;
  • flushCache:在调用SQL后,是否要求清空之前查问的本地缓存和二级缓存,次要用于更新缓存,默认为false;
  • useCache:启动二级缓存的开关,默认只会启动一级缓存;
  • timeout:设置超时参数,等超时的时候将抛出异样,单位为秒;
  • fetchSize:获取记录的总条数设定;

select实例

需要: 查问名称等于JAVA宝典的用户

在UserDao中定义接口办法:

User findIdByName(String name);

定义UserMapper.xml

<select id="findIdByName" parameterType="string" resultType="User">        select        u.*        from t_user u        where u.name=#{name}</select> 

对操作步骤进行演绎概括:

  • Id标出了了这条SQL
  • parameterType定义参数类型
  • resuitType定义返回值类型

下面的例子只是传入单个参数,多个参数能够应用Map,JavaBean,应用注解形式,等等,上面咱们会独自介绍.


insert元素

insert属性和select大部分都雷同, 说下3个不同的属性:

  • keyProperty:指定哪个列是主键,如果是联结主键能够用逗号隔开;
  • keyColumn:指定第几列是主键,不能和keyProperty共用;
  • useGeneratedKeys:是否应用主动增长,默认为false;当useGeneratedKeys设为true时,在插入的时候,会回填Java Bean的id值,通过返回的对象可获取主键值。

如果想依据一些非凡关系设置主键的值,能够在insert标签内应用selectKey标签

<insert id="insertRole" useGeneratedKeys="true" keyProperty="id" >    <selectKey keyProperty="id" resultType="int" order="before">        select if(max(id) is null,1,max(id)+2) as newId from t_role      </selectKey> </insert>

update和delete 就不独自过多介绍了

sql元素

sql元素的意义,在于咱们能够定义⼀串串SQL语句的组成部分,其余的语句能够通过引⽤来使⽤它。

例如,你有一条SQL须要select⼏⼗个字段映射到JavaBean中去,我的第二
条SQL也是这⼏⼗个字段映射到JavaBean中去,显然这些字段写两遍不太适合。那么咱们
就⽤sql元素来实现

定义:

<sql id="columns">    id,    name,    remark,    tid</sql>

应用:

    <select id="getOne" resultType="com.liangtengyu.entity.Ttest">        select  <include refid="columns"/>  from t_test where id  = #{id}    </select>

resultMap元素

resultMap是MyBatis外面最简单的元素,它的作用是定义映射规定、级联的更新、定制类型转换器等。

由以下元素形成:

<resultMap>    <constructor> <!-- 配置构造方法 -->        <idArg/>        <arg/>    </constructor>    <id/> <!--指明哪一列是主键-->    <result/> <!--配置映射规定-->    <association/> <!--一对一-->    <collection/> <!--一对多-->    <discriminator> <!--鉴别器级联-->        <case/>    </discriminator></resultMap>

有的实体不存在没有参数的构造方法,须要应用constructor配置有参数的构造方法:

<resultMap id="role" type="com.liangtengyu.entity.Role">    <constructor>        <idArg column="id" javaType="int"/>        <arg column="role_name" javaType="string"/>    </constructor></resultMap>

id指明主键列,result配置数据库字段和POJO属性的映射规定:

<resultMap id="role" type="com.liangtengyu.entity.Role">

<id property="id" column="id" /><result property="roleName" column="role_name" /><result property="note" column="note" />

</resultMap>
association、collection用于配置级联关系的,别离为一对一和一对多,理论中,多对多关系的利用不多,因为比较复杂,会用一对多的关系把它合成为双向关系。

discriminator用于这样一种场景:比方咱们去体检,男和女的体检我的项目不同,如果让男生去查看妇科我的项目,是不合理的, 通过discriminator能够依据性别,返回不同的对象。

级联关系的配置比拟多,就不在此演示了,可查看文档进行理解。

cache元素

在没有显示配置缓存时,只开启一级缓存,一级缓存是绝对于同一个SqlSession而言的,在参数和SQL齐全一样的状况下,应用同一个SqlSession对象调用同一个Mapper的办法,只会执行一次SQL。如果是不同的SqlSession对象,因为不同SqlSession是互相隔离的,即应用雷同的Mapper、参数和办法,还是会再次发送SQL到数据库去执行。

二级缓存是SqlSessionFactory层面上的,须要进行显示配置

这样很多设置是默认的,有如下属性能够配置:

  • eviction:代表缓存回收策略,可选值有LRU起码应用、FIFO先进先出、SOFT软援用,WEAK弱援用;
  • flushInterval:刷新间隔时间,单位为毫秒,如果不配置,当SQL被执行时才会刷新缓存;
  • size:援用数目,代表缓存最多能够存储多少对象,不宜设置过大,设置过大会导致内存溢出;
  • readOnly:只读,意味着缓存数据只能读取不能批改;
在大型服务器上,可能会应用专用的缓存服务器,比方Redis缓存,能够通过实现org.apache.ibatis.cache.Cache接口很不便的实现:
public interface Cache {        String getId(); //缓存编号    void putObject(Object var1, Object var2); //保留对象    Object getObject(Object var1); //获取对象    Object removeObject(Object var1); //移除对象    void clear(); //清空缓存    int getSize(); //获取缓存对象大小    ReadWriteLock getReadWriteLock(); //获取缓存的读写锁    }

映射器的外部组成

一般而言,一个映射器是由3个局部组成:

关上Mybatis源码,在mapping包中能够找到他们

  • MappedStatement,它保留映射器的一个节点(select|insert|delete|update)并且包含许多咱们配置的sql,sql的id、缓存信息,resultMap,parameterType、resultType、languageDriver等重要的配置内容。
  • SqlSource,它是提供BoundSql对象的中央,它是MappedStatement的一个属性。它的次要作用是依据参数和其余规定组装sql。
  • BoundSql,它是建设SQL和参数的中央,他有3个罕用的属性:SQLparameterObjectparameterMappings 这3个等会介绍.

idea生成的依赖图

MappedStatement创立过程

首先咱们介绍一下MappedStatement

在SqlSessionFactoryBuilder.build()办法,它会帮咱们创立一个SqlSessionFactory,它的build办法:

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {    try {      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);      return build(parser.parse());//此处应用解析XML返回的数据构建 SqlSessionFactory 跟进去..    } 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.      }    }  }

进入到parser.parse()办法看看:

 public Configuration parse() {    if (parsed) { //如果一个xml文件解析两次 就会报错 否则才去解析      throw new BuilderException("Each XMLConfigBuilder can only be used once.");    }    parsed = true;    parseConfiguration(parser.evalNode("/configuration"));//解析节点configuration,跟进去...    return configuration;  }

跟进代码parseConfiguration(parser.evalNode("/configuration"));

 private void parseConfiguration(XNode root) {    try {      propertiesElement(root.evalNode("properties"));      Properties settings = settingsAsProperties(root.evalNode("settings"));      loadCustomVfs(settings);      loadCustomLogImpl(settings);      typeAliasesElement(root.evalNode("typeAliases"));      pluginElement(root.evalNode("plugins"));      objectFactoryElement(root.evalNode("objectFactory"));      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));      reflectorFactoryElement(root.evalNode("reflectorFactory"));      settingsElement(settings);      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);    }  }
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");//获取三种形式指定的门路 进行判断 通知MyBatis 到哪里去找映射文件          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();//调用 mapperParser.parse();解析xml  跟进去...          } 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();//调用 mapperParser.parse();解析xml          } 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.");          }        }      }    }  }

能够看到在解析mapper标签

  public void parse() {    if (!configuration.isResourceLoaded(resource)) {      configurationElement(parser.evalNode("/mapper"));//解析xml的mapper标签,并返回调用configurationElement()办法      configuration.addLoadedResource(resource);      bindMapperForNamespace();    }    parsePendingResultMaps();//短少资源的再尝试一下.具体逻辑咱们不深刻走这里了,有趣味的能够看看源码.    parsePendingCacheRefs();    parsePendingStatements();  }

configurationElement()办法

  private void configurationElement(XNode context) {    try {      String namespace = context.getStringAttribute("namespace");      if (namespace == null || namespace.isEmpty()) {        throw new BuilderException("Mapper's namespace cannot be empty");      }      builderAssistant.setCurrentNamespace(namespace);      cacheRefElement(context.evalNode("cache-ref"));      cacheElement(context.evalNode("cache"));      parameterMapElement(context.evalNodes("/mapper/parameterMap"));      resultMapElements(context.evalNodes("/mapper/resultMap"));      sqlElement(context.evalNodes("/mapper/sql"));      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));    } catch (Exception e) {      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);    }  }

buildStatementFromContext()办法:

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {    for (XNode context : list) {      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);      try {        statementParser.parseStatementNode();//会调用此处,持续跟进      } catch (IncompleteElementException e) {        configuration.addIncompleteStatement(statementParser);      }    }  }

statementParser.parseStatementNode();

  public void parseStatementNode() {    String id = context.getStringAttribute("id");    String databaseId = context.getStringAttribute("databaseId");    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {      return;    }    String nodeName = context.getNode().getNodeName();    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);    boolean useCache = context.getBooleanAttribute("useCache", isSelect);    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);    // Include Fragments before parsing    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);    includeParser.applyIncludes(context.getNode());    String parameterType = context.getStringAttribute("parameterType");    Class<?> parameterTypeClass = resolveClass(parameterType);    String lang = context.getStringAttribute("lang");    LanguageDriver langDriver = getLanguageDriver(lang);    // Parse selectKey after includes and remove them.    processSelectKeyNodes(id, parameterTypeClass, langDriver);    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)    KeyGenerator keyGenerator;    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);    if (configuration.hasKeyGenerator(keyStatementId)) {      keyGenerator = configuration.getKeyGenerator(keyStatementId);    } else {      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;    }    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);    //SqlSource是在这里创立的 记住这里等会咱们再来剖析她.先持续走完流程.    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));    Integer fetchSize = context.getIntAttribute("fetchSize");    Integer timeout = context.getIntAttribute("timeout");    String parameterMap = context.getStringAttribute("parameterMap");    String resultType = context.getStringAttribute("resultType");    Class<?> resultTypeClass = resolveClass(resultType);    String resultMap = context.getStringAttribute("resultMap");    String resultSetType = context.getStringAttribute("resultSetType");    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);    if (resultSetTypeEnum == null) {      resultSetTypeEnum = configuration.getDefaultResultSetType();    }    String keyProperty = context.getStringAttribute("keyProperty");    String keyColumn = context.getStringAttribute("keyColumn");    String resultSets = context.getStringAttribute("resultSets");    //对各种属性进行了解析并且调用addMappedStatement办法.    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,         fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,        resultSetTypeEnum, flushCache, useCache, resultOrdered,        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);  }

addMappedStatement()办法:

public MappedStatement addMappedStatement(      String id,      SqlSource sqlSource,      StatementType statementType,      SqlCommandType sqlCommandType,      Integer fetchSize,      Integer timeout,      String parameterMap,      Class<?> parameterType,      String resultMap,      Class<?> resultType,      ResultSetType resultSetType,      boolean flushCache,      boolean useCache,      boolean resultOrdered,      KeyGenerator keyGenerator,      String keyProperty,      String keyColumn,      String databaseId,      LanguageDriver lang,      String resultSets) {    if (unresolvedCacheRef) {      throw new IncompleteElementException("Cache-ref not yet resolved");    }    id = applyCurrentNamespace(id, false);    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)        .resource(resource)        .fetchSize(fetchSize)        .timeout(timeout)        .statementType(statementType)        .keyGenerator(keyGenerator)        .keyProperty(keyProperty)        .keyColumn(keyColumn)        .databaseId(databaseId)        .lang(lang)        .resultOrdered(resultOrdered)        .resultSets(resultSets)        .resultMaps(getStatementResultMaps(resultMap, resultType, id))        .resultSetType(resultSetType)        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))        .useCache(valueOrDefault(useCache, isSelect))        .cache(currentCache);    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);    if (statementParameterMap != null) {      statementBuilder.parameterMap(statementParameterMap);    }    MappedStatement statement = statementBuilder.build();    configuration.addMappedStatement(statement);//configuration外部放入了一个Map<String, MappedStatement>    return statement;//最终返回MappedStatement  }

至此,MappedStatement创立结束


SqlSource的创立过程

回到parseStatementNode()办法找到:

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

createSqlSource办法是LanguageDriver接口的一个办法 她有4个实现


咱们看一下xml实现

public class XMLLanguageDriver implements LanguageDriver {  @Override  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);  }  @Override  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);    return builder.parseScriptNode();//进行解析,咱们跟进..  }  ....}
  public SqlSource parseScriptNode() {    MixedSqlNode rootSqlNode = parseDynamicTags(context);//对动静标签的解决    SqlSource sqlSource;//初始化Null    if (isDynamic) {//判断是否动静,来调用不同的SqlSource实例.      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);    } else {//跟进RawSqlSource()      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);    }    return sqlSource;  }
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);    Class<?> clazz = parameterType == null ? Object.class : parameterType;    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());  }

sqlSourceParser.parse()办法要对sql进行解决,这里咱们打个断点,能够看到原始参数sql为

insert into t_test set context = #{context}

parse办法处理完毕返回:insert into t_test set context = ?并且调用了StaticSqlSource的构造方法

StaticSqlSource类继实现了SqlSource接口,

public class StaticSqlSource implements SqlSource {  private final String sql;  private final List<ParameterMapping> parameterMappings;  private final Configuration configuration;  public StaticSqlSource(Configuration configuration, String sql) {    this(configuration, sql, null);  }  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {    this.sql = sql;    this.parameterMappings = parameterMappings;    this.configuration = configuration;  }  @Override  public BoundSql getBoundSql(Object parameterObject) {//这个办法是不是眼生,BoundSql的构建    return new BoundSql(configuration, sql, parameterMappings, parameterObject);  }}

至此,SqlSource创立结束.


BoundSql

public class BoundSql {  private final String sql;  private final List<ParameterMapping> parameterMappings;  private final Object parameterObject;  private final Map<String, Object> additionalParameters;  private final MetaObject metaParameters;........}

创立SqlSource时,就把参数放入BoundSql类中来构建一个new BoundSql

public BoundSql getBoundSql(Object parameterObject) {    return new BoundSql(configuration, sql, parameterMappings, parameterObject);  }

configuration, sql, parameterMappings, parameterObject这些参数是十分重要的咱们一一剖析下

configuration

这个不必多说了吧 如果不意识她,那间接打回新手村

sql

这个属性保留的是咱们在映射文件xml中写的sql语句

例子就是刚刚解析进去的Sql语句:insert into t_test set context = ?

parameterMappings

List<ParameterMapping> parameterMappings;

它是一个list,每一个元素都是parameterMapping对象,这个对象会形容包含属性、名称、表达式、javaType、jdbcType、typeHandler等重要信息。

ParameterMapping
public class ParameterMapping {  private Configuration configuration;  private String property;  private ParameterMode mode;  private Class<?> javaType = Object.class;  private JdbcType jdbcType;  private Integer numericScale;  private TypeHandler<?> typeHandler;  private String resultMapId;  private String jdbcTypeName;  private String expression;  ......  }
parameterObject

parameterObject是参数,能够传递简略对象,pojo、map或者@param注解的参数

传入简略对象 (包含int、 float、 double等),⽐如当咱们传递int类型时,MyBatis
会把参数包装成为Integer对象传递,相似的long、 float、 double也是如此.

传入pojo、map那么这个parameterObject参数就是你传入的POJO或者Map不变

传入多个参数,不带@Param注解那么MyBatis就会把
parameterObject变为一个Map<String, Object>对象,其键值的关系是按程序来布局
的,相似于这样的模式{"1":Obj1,"2":Obj2,"3":Obj3…}所以在编写的时候咱们都能够应用#{param 1}或者#{1}去引⽤第⼀个参数

传入多个参数,带@Param注解如果咱们使⽤@Param注解,那么MyBatis就会把parameterObject变为一个Map<String, Object>对象相似于没有@Param注解,只是把其数字的键值对应置换为了@Param注解的键值。

⽐如咱们注解
(@Param{"name"} String var1,@Param{"age"} int var2)数字的键值对应置换为了name和age

关注公众号:java宝典