MyBatis框架中Mapper映射配置的使用及原理


(Mapper用於映射SQL語句,可以說是MyBatis操作數據庫的核心特性之一,這里我們討論java的MyBatis框架中Mapper映射配置的使用及原理解析,包括對mapper.xml配置文件的讀取流程解讀)

Mapper的內置方法

model層就是實體類,對應數據庫的表。controller層是Servlet,主要是負責業務模塊流程的控制,調用service接口的方法,在struts2就是Action。Service層主要做邏輯判斷,Dao層是數據訪問層(與數據庫進行對接)。至於Mapper是mybatis框架映射用到的,而mapper映射文件在dao層使用。

下面是介紹一下Mapper的內置方法:

1、countByExample => 根據條件查詢數量

int countByExample(UserExample example);
//下面是一個完整的案列
UserExample example = new UserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo("joe");
int count = userDAO.countByExample(example);

相當於:select count(*) from user where username='joe'

2、deleteByExample => 根據條件刪除多條

int deleteByExample(AccountExample example);
//下面是一個完整的案例
UserExample example=new UserExample();
Criteria criteria=example.createCriteria();
criteria.andUsernameEqualTo("joe");
userDao.deleteByExample(example);

相當於:delete from user where username='joe'

3、deleteByPrimaryKey => 根據條件刪除單條

int deleteByPrimaryKey(Integer id);
userDao.deleteByPrimaryKey(101);

相當於:delete from user where id=101

4、insert => 插入數據

int insert(Account record);
//下面是完整的案例
User user=new User();
user.setId(101);
user.setUsername("test");
user.setPassword("123456");
user.setEmail("123@qq.com");
userDao.insert(user);

相當於:insert  into user (id,username,password,email) values (101,'test','123456','1232qq.com')

 

5、selectByExample => 根據條件查詢數據

List<Account> selectByExample(AccountExample example);
//下面是一個完整的案例
UserExample example=new UserExample();
Criteria criteria=example.createCriteria();
criteria.andUsernameEqualTo("joe");
criteria.andUsernameIsNull();
example.setOrderByClause("username asc,email desc");
List<?> list=userDao.selectByExample(example);

相當於:select * from user where username='joe' and username is null order by username asc,email desc

注:在iBator生成的文件UserExample.java中包含一個static的內部類Criteria,在Criteria中有很多方法,主要是定義SQL語句where后的查詢條件

6、updateByExampleSelective => 按條件更新不為null的字段

int updateByExampleSelective(@Param("record")Account record,@Param("example")AccountExample example);
//下面是一個完整的案例
UserExample example = new UserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo("joe");
User user = new User();
user.setPassword("123");
userDAO.updateByPrimaryKeySelective(user,example);

相當於:update user set password='123' where username='joe'

7、updateByPrimaryKeySelective => 按條件更新

int updateByPrimaryKeySelective(Account record);
//下面是完整的案例
User user=new User();
user.setId(101);
user.setPassword("joe");
userDAO.updateByPrimaryKeySelective(user);

相當於:update user set password='joe' where id=101

8、updateByPrimaryKey => 按主鍵更新

int updateByPrimaryKey(Account record);
//下面是一個完整的案例
User user =new User();
user.setId(101);
user.setUsername("joe");
user.setPassword("joe");
user.setEmail("joe@163.com");
userDAO.updateByPrimaryKey(user);

相當於:update user set username='joe',password='joe',email='joe@163.com' where id=101

解析mapper的xml配置文件

我們來看看mybatis是怎么讀取mapper的xml配置文件並解析其中的sql語句。

我們還記得是這樣配置sqlSessionFactory的:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">   
  <property name="dataSource" ref="dataSource" />  
  <property name="configLocation" value="classpath:configuration.xml"></property>   
  <property name="mapperLocations" value="classpath:com/xxx/mybatis/mapper/*.xml"/>   
  <property name="typeAliasesPackage" value="com.mybatis.model" />   
</bean>  

這里配置了一個mapperLocation屬性,它是一個表達式,sqlSessionFactory會根據這個表達式讀取包com.xxx.mybatis.mapper下面的所有xml格式文件,那么具體是怎么根據這個屬性來讀取配置文件的呢?

答案就在SqlSessionFactoryBean類中buildSqlSessionFactory方法中:

if(!isEmpty(this.mapperLocations)){
   for(Resource mapperLocation:this.mapperLocations){
      continue;
  }
   try{
      XMLMapperBuilder xmlMapperBuilder=new XMLMapperBuilder(mapperLocation.getInputStream(),configuration,mapperLocation.toString(),configuration.getSqlFragments());
      xmlMapperBuilder.parse();
   }catch(Exception e){
      throw new NestedIOException("Failed to parse mapping resource:'"+mapperLocation+"'",e);
   }finally{
      ErrorContext.instance().reset();
   }
   if(logger.isDebugEnablec()){
      logger.debug("Parsed mapper file:'"+mapperLocation+"'");
   }
}

mybatis使用XMLMapperBuilder類的實例來解析mapper配置文件。

 

public XMLMapperBuilder(Reader reader,configuration configuration,String resource,Map<String,XNode> sqlFragments){
    this(new XPathParser(reader,true,configuration.getVariables(),new XMLMapperEntityResolver()),configuration,resource,sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { 
  super(configuration); 
  this.builderAssistant = new MapperBuilderAssistant(configuration, resource); 
  this.parser = parser; 
  this.sqlFragments = sqlFragments; 
  this.resource = resource; 
} 

接着系統調用xmlMapperBuilder的parse方法解析mapper

public void parse(){
   //如果configuration對象還沒加載xml配置文件(避免重復加載,實際上是確認是否解析了mapper節點的屬性以及內容,
   //為解析它的子節點如cache、sql、select、resultMap、parameterMap等做准備),
   //則從輸入流中解析mapper節點,然后再將resource的狀態置為已加載
   if(!configuration.isResourceloaded(resource)){
      configurationElement(parser.evalNode("/mapper"));
      configuration.addCloadedResource(respurce);
      bindMapperForNamespace();
   }
   //解析在configurationElement函數中處理resultMap時其extends屬性指向的父對象還沒被處理的<resultMap>節點 
  parsePendingResultMaps(); 
  //解析在configurationElement函數中處理cache-ref時其指向的對象不存在的<cache>節點(如果cache-ref先於其指向的cache節點加載就會出現這種情況) 
  parsePendingChacheRefs(); 
  //同上,如果cache沒加載的話處理statement時也會拋出異常 
  parsePendingStatements(); 
}

mybatis解析mapper的xml文件的過程已經很明顯了,接下來我們看看它是怎么解析mapper的:

private void configurationElement(XNode context) { 
try{
  //獲取mapper節點的namespace屬性
  String namespace=context.getStringAttribute("namespace");
   if(namespace.equals("")){
       throw new BuilderException("Mapper's namespace cannot be empty");
   //設置當前的namespace
   builderAssistant.setCuttentNamespace(namespace);
   //解析mapper的<cache-ref>節點
   cacheRefElement(context.evalNode("cache-ref"));
    //解析mapper的<cache>節點 
   cacheElement(context.evalNode("cache")); 
   //解析mapper的<parameterMap>節點 
   parameterMapElement(context.evalNodes("/mapper/parameterMap")); 
   //解析mapper的<resultMap>節點 
   resultMapElements(context.evalNodes("/mapper/resultMap")); 
   //解析mapper的<sql>節點 
   sqlElement(context.evalNodes("/mapper/sql")); 
   //使用XMLStatementBuilder的對象解析mapper的<select>、<insert>、<update>、<delete>節點, 
   //mybaits會使用MappedStatement.Builder類build一個MappedStatement對象, 
   //所以mybaits中一個sql對應一個MappedStatement 
   buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 
   }
}catch(Exception e){
   throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); 
}
}

configurationElement函數幾乎解析了mapper節點下所有子節點,至此mybatis解析了mapper中的所有節點,並將其加入到了Configuration對象中提供sqlSessionFactory對象隨時使用。這里我們需要補充講一下mybatis是怎么使用XMLStatementBuilder類的對象的parseStatementNode函數借用MapperBuilderAssistant類對象builderAssistant的addMappedStatement解析MappedStatement並將其關聯到Configuration類對象的

public void parseStatementNode() { 
  //ID屬性 
  String id = context.getStringAttribute("id"); 
  //databaseId屬性 
  String databaseId = context.getStringAttribute("databaseId"); 
  
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { 
   return; 
  } 
  //fetchSize屬性 
  Integer fetchSize = context.getIntAttribute("fetchSize"); 
  //timeout屬性 
  Integer timeout = context.getIntAttribute("timeout"); 
  //parameterMap屬性 
  String parameterMap = context.getStringAttribute("parameterMap"); 
  //parameterType屬性 
  String parameterType = context.getStringAttribute("parameterType"); 
  Class<?> parameterTypeClass = resolveClass(parameterType); 
  //resultMap屬性 
  String resultMap = context.getStringAttribute("resultMap"); 
  //resultType屬性 
  String resultType = context.getStringAttribute("resultType"); 
  //lang屬性 
  String lang = context.getStringAttribute("lang"); 
  LanguageDriver langDriver = getLanguageDriver(lang); 
  
  Class<?> resultTypeClass = resolveClass(resultType); 
  //resultSetType屬性 
  String resultSetType = context.getStringAttribute("resultSetType"); 
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); 
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 
  
  String nodeName = context.getNode().getNodeName(); 
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); 
  //是否是<select>節點 
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 
  //flushCache屬性 
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); 
  //useCache屬性 
  boolean useCache = context.getBooleanAttribute("useCache", isSelect); 
  //resultOrdered屬性 
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); 
  
  // Include Fragments before parsing 
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); 
  includeParser.applyIncludes(context.getNode()); 
  
  // Parse selectKey after includes and remove them. 
  processSelectKeyNodes(id, parameterTypeClass, langDriver); 
    
  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 
  //resultSets屬性 
  String resultSets = context.getStringAttribute("resultSets"); 
  //keyProperty屬性 
  String keyProperty = context.getStringAttribute("keyProperty"); 
  //keyColumn屬性 
  String keyColumn = context.getStringAttribute("keyColumn"); 
  KeyGenerator keyGenerator; 
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; 
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); 
  if (configuration.hasKeyGenerator(keyStatementId)) { 
   keyGenerator = configuration.getKeyGenerator(keyStatementId); 
  } else { 
   //useGeneratedKeys屬性 
   keyGenerator = context.getBooleanAttribute("useGeneratedKeys", 
     configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) 
     ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); 
  } 
  
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, 
    fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, 
    resultSetTypeEnum, flushCache, useCache, resultOrdered,  
    keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); 
 }

由以上代碼可以看出mybatis使用XPath解析mapper的配置文件后將其中的resultMap、parameterMap、cache、statement等節點使用關聯的builder創建並將得到的對象關聯到configuration對象中,而這個configuration對象可以從sqlSession中獲取的,這就解釋了我們在使用sqlSession對數據庫進行操作時mybatis怎么獲取到mapper並執行其中的sql語句的問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM