欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢(shì)詳解

 更新時(shí)間:2020年11月27日 10:57:13   作者:小團(tuán)團(tuán)開心鴨  
這篇文章主要介紹了mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢(shì)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧

好久沒編碼了!最近開始編碼遇到一個(gè)問題 !一個(gè)批量修改的問題,就是mybatis foreach 的使用。

當(dāng)時(shí)使用的場(chǎng)景 ,前端 傳逗號(hào)拼接的字符串id, 修改id對(duì)應(yīng)數(shù)據(jù)的數(shù)據(jù)順序 ,順序 就是id 的順序.

就是一個(gè)條件(單個(gè)id值) 修改一個(gè)值(傳入的id的順序) ,

1、 把條件作為Map 的key 修改值是value,用map入?yún)?/p>

2、用List<Object> 或者數(shù)組 ,把條件和值封裝成對(duì)象放進(jìn)list集合或者array數(shù)組

3、代碼使用for循環(huán)調(diào)用mapper方法 穿兩個(gè)參數(shù)。

因?yàn)榭紤]到第二種用法,需要不斷創(chuàng)建對(duì)象 放進(jìn)數(shù)組在 遍歷數(shù)組獲取對(duì)象取值。從虛擬機(jī)的堆內(nèi)存考慮,放棄------------------------

第三種方法,會(huì)循環(huán)多少次就執(zhí)行多少條sql語句,放棄-----------------------

于是使用Map,

可是在mybatis中參數(shù)是map的foreach使用,對(duì)于很久沒編碼的我,實(shí)在是忘記得很干凈。于是百度一堆,一致性 就是報(bào)錯(cuò):

把打印出的sql語句放到navicat 執(zhí)行 可以執(zhí)行不會(huì)報(bào)錯(cuò)。那問題是什么(這里想來我1個(gè)小時(shí)),最后沒辦法 直接看mybatis的官網(wǎng),把sql改成如下,正確執(zhí)行。

下面給出正確的mybatis中foreach的map的姿勢(shì),避免大家以后在這上面浪費(fèi)時(shí)間 ,直接上代碼,涂改部分因公司保密協(xié)議問題(大家都知道是表名):

mapper

一定要加@Param注解

mapper.xml

最后 大家遇到此問題的時(shí)候 看到我的這篇文章 少耽誤時(shí)間!

補(bǔ)充知識(shí):MyBatis3的Plugins功能使用

1、這是什么?

根據(jù)官方介紹,這個(gè)功能可以讓你在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。其實(shí)就是MyBatis給用戶留了幾個(gè)切入點(diǎn),通過這些切入點(diǎn),用戶可以自己實(shí)現(xiàn)的功能。根據(jù)官方介紹,這些切入點(diǎn)(方法)包括以下幾個(gè):

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

這是四個(gè)接口以及接口的方法,這些接口是MyBatis在執(zhí)行sql語句時(shí)會(huì)執(zhí)行的方法。用戶就可以通過這些方法進(jìn)行切入,實(shí)現(xiàn)自己的邏輯。

接下來,我們通過實(shí)現(xiàn)一個(gè)打印sql語句的功能來進(jìn)行演示。

2、通過Plugin實(shí)現(xiàn)打印SQL語句

2.1 創(chuàng)建表并初始化數(shù)據(jù)

CREATE TABLE `t_user` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) DEFAULT NULL,
 `age` int(3) DEFAULT NULL,
 `address` varchar(255) DEFAULT NULL,
 `sex` varchar(10) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

插入測(cè)試數(shù)據(jù)

INSERT INTO `t_user` VALUES (1, '張三', 4, 'OUT', '男');
INSERT INTO `t_user` VALUES (2, '李四', 5, 'OUT', '男');
INSERT INTO `t_user` VALUES (3, '王五', 5, 'OUT', '男');

2.2、構(gòu)建項(xiàng)目

創(chuàng)建一個(gè)maven項(xiàng)目,并引入相關(guān)jar包,pom.xml文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.mybatis.plugins</groupId>
 <artifactId>mybatis-plugins-study</artifactId>
 <version>1.0-SNAPSHOT</version>

 <dependencies>
  <dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.4.6</version>
  </dependency>
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.47</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>8</source>
     <target>8</target>
     <encoding>utf-8</encoding>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

創(chuàng)建包及類

2.3、類介紹

User類,與數(shù)據(jù)庫表對(duì)應(yīng)的實(shí)體類

package com.mybatis.plugins.study.entity;

/**
 * @author guandezhi
 * @date 2019/7/2 17:40
 */
public class User {

 private int id;
 private String name;
 private int age;
 private String address;
 private String sex;

 @Override
 public String toString() {
 final StringBuffer sb = new StringBuffer("User{");
 sb.append("id=").append(id);
 sb.append(", name='").append(name).append('\'');
 sb.append(", age=").append(age);
 sb.append(", address=").append(address);
 sb.append(", sex=").append(sex);
 sb.append('}');
 return sb.toString();
 }

 public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public String getName() {
 return name;
 }

 public void setName(String name) {
 this.name = name;
 }

 public int getAge() {
 return age;
 }

 public void setAge(int age) {
 this.age = age;
 }

 public String getAddress() {
 return address;
 }

 public void setAddress(String address) {
 this.address = address;
 }

 public String getSex() {
 return sex;
 }

 public void setSex(String sex) {
 this.sex = sex;
 }
}

UserMapper接口,實(shí)現(xiàn)具體的sql語句,這里為了方便,我們使用MyBatis的注解方式

package com.mybatis.plugins.study.mapper;
import com.mybatis.plugins.study.entity.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.type.Alias;
import java.util.List;

/**
 * @author guandezhi
 * @date 2019/7/2 17:49
 */
@Alias(value = "userMapper")
public interface UserMapper {

 /**
 * 查詢方法
 * @param id 用戶id
 * @param name 用戶姓名
 * @return 結(jié)果集合
 */
 @Select("select * from t_user where id = #{param1} and name = #{param2}")
 List<User> select(@Param("idParam") int id, @Param("nameParam") String name);

}

在這里簡(jiǎn)單說明一下注解sql里的參數(shù)問題,一般情況下,如果只有一個(gè)參數(shù),我們不需要寫@Param注解也是可以的,如果有多個(gè)參數(shù),則需要對(duì)每個(gè)參數(shù)使用@Param注解指定參數(shù)名字,或者也可以使用MyBatis提供的默認(rèn)參數(shù)方式,就是示例中使用的方式,按照參數(shù)的順序分別為param1、param2…。

接下來,就是Plugins的核心代碼了,LogInterceptor類,按照官方文檔,如果我們需要使用Plugins的功能,需實(shí)現(xiàn) Interceptor 接口

package com.mybatis.plugins.study.interceptor;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.List;
import java.util.Properties;

/**
 * @author guandezhi
 * @date 2019/7/2 17:46
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class LogInterceptor implements Interceptor {
 @Override
 public Object intercept(Invocation invocation) throws Throwable {
 StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
 BoundSql boundSql = statementHandler.getBoundSql();
 // 原始sql
 String sql = boundSql.getSql();
 System.out.println("打印原始sql===>" + sql);
 // 參數(shù)集合
 MapperMethod.ParamMap parameterObject = (MapperMethod.ParamMap) boundSql.getParameterObject();
 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 for (ParameterMapping parameterMapping : parameterMappings) {
 Object param = parameterObject.get(parameterMapping.getProperty());
 if (param.getClass() == Integer.class) {
 sql = sql.replaceFirst("\\?", param.toString());
 } else if (param.getClass() == String.class) {
 sql = sql.replaceFirst("\\?", "'".concat(param.toString()).concat("'"));
 }
 }
 // 替換占位符的sql
 System.out.println("打印執(zhí)行sql===>" + sql);
 return invocation.proceed();
 }

 @Override
 public Object plugin(Object target) {
 return Plugin.wrap(target, this);
 }

 @Override
 public void setProperties(Properties properties) {

 }
}

實(shí)現(xiàn)接口的三個(gè)方法 intercept(Invocation invocation)、plugin(Object target)、setProperties(Properties properties)。

通過注解Intercepts告訴MyBatis,我們需要在哪個(gè)切入點(diǎn)實(shí)現(xiàn)自己的功能,在這里我們切入的是StatementHandler的prepare方法,即這是我們的切入點(diǎn),當(dāng)MyBatis執(zhí)行一個(gè)sql語句到這個(gè)方法的時(shí)候,會(huì)先執(zhí)行我們自己的邏輯,執(zhí)行哪個(gè)邏輯呢?就是 intercept(Invocation invocation)里面的邏輯。具體原理我們稍后再講。

最后是我們的主體類,這里為了方便,我直接使用一個(gè)main方法。

package com.mybatis.plugins.study;

import com.mybatis.plugins.study.entity.User;
import com.mybatis.plugins.study.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;

/**
 * @author guandezhi
 * @date 2019/7/2 17:45
 */
public class MainClass {

 public static void main(String[] args) throws Exception {
 String resource = "mybatis-config.xml";
 InputStream stream = Resources.getResourceAsStream(resource);
 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
 SqlSession session = factory.openSession();
 try {
 UserMapper mapper = session.getMapper(UserMapper.class);
 List<User> userList = mapper.select(1, "張三");
 userList.forEach(System.out::println);
 } finally {
 session.close();
 }
 }
}

基于我們的需求,只做了簡(jiǎn)單的配置,配置文件如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 <properties>
  <property name="driver" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/test"/>
  <property name="username" value="root"/>
  <property name="password" value="baofeng"/>
 </properties>
 <plugins>
  <plugin interceptor="com.mybatis.plugins.study.interceptor.LogInterceptor"/>
 </plugins>
 <environments default="dev">
  <environment id="dev">
   <transactionManager type="JDBC"/>
   <dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
   </dataSource>
  </environment>
 </environments>
 <mappers>
  <mapper class="com.mybatis.plugins.study.mapper.UserMapper" />
 </mappers>
</configuration>

2.4、執(zhí)行效果

執(zhí)行我們的main方法,效果如下:

可以看出,通過一個(gè)切入,我們可以把MyBatis真正執(zhí)行的sql打印出來。

3、詳細(xì)解析

我們從main方法開始一步步的去分析。

public static void main(String[] args) throws Exception {
 String resource = "mybatis-config.xml";
 InputStream stream = Resources.getResourceAsStream(resource);
 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
 SqlSession session = factory.openSession();
 try {
 UserMapper mapper = session.getMapper(UserMapper.class);
 List<User> userList = mapper.select(1, "張三");
 userList.forEach(System.out::println);
 } finally {
 session.close();
 }
 }

我們?cè)谂渲梦募性黾恿薒ogInterceptor的配置,那么在SqlSessionFactory初始化的時(shí)候做了什么呢?

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 try {
  // 根據(jù)配置文件構(gòu)造一個(gè)XMLConfigBuilder對(duì)象
  XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  // 通過parse()方法解析配置文件,構(gòu)造一個(gè)Configuration對(duì)象,然后根據(jù)Configuration對(duì)象構(gòu)造一個(gè)默認(rèn)的DefaultSqlSessionFactory對(duì)象
  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.
  }
 }
 }

跟蹤這個(gè)對(duì)象的parse()方法,找到解析plugins節(jié)點(diǎn)的方法

private void pluginElement(XNode parent) throws Exception {
 // parent就是plugins節(jié)點(diǎn)
 if (parent != null) {
  // 解析每個(gè)plugin節(jié)點(diǎn)
  for (XNode child : parent.getChildren()) {
  // 獲取plugin節(jié)點(diǎn)的interceptor參數(shù)
  String interceptor = child.getStringAttribute("interceptor");
  // 獲取plugin節(jié)點(diǎn)的property參數(shù)(我們的配置文件未配置該參數(shù))
  Properties properties = child.getChildrenAsProperties();
  // 調(diào)用resolveClass方法,加載我們配置的interceptor參數(shù)指定的類并返回這個(gè)類的實(shí)例
  Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
  // 設(shè)置配置的屬性
  interceptorInstance.setProperties(properties);
  // 把實(shí)例添加到配置對(duì)象的私有屬性interceptorChain中,它只有一個(gè)私有屬性,就是所有Interceptor的集合
  configuration.addInterceptor(interceptorInstance);
  }
 }
 }

所以構(gòu)建SqlSessionFactory的時(shí)候,就是為我們配置的interceptor創(chuàng)建了一個(gè)實(shí)例并保存在了一個(gè)集合中。

繼續(xù)回到main方法,構(gòu)建完SqlSessionFactory之后,想要執(zhí)行sql,需要通過SqlSessionFactory返回一個(gè)SqlSession,跟蹤factory.openSession()方法

 /**
 * @param execType 執(zhí)行器,不配置默認(rèn)是SIMPLE
 * @param level 事物隔離級(jí)別,這里為null
 * @param autoCommit 自動(dòng)提交,這里默認(rèn)為false
 */
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 // 數(shù)據(jù)庫連接的包裝類
 Transaction tx = null;
 try {
  // 從配置對(duì)象中獲取環(huán)境信息,構(gòu)建數(shù)據(jù)庫連接
  final Environment environment = configuration.getEnvironment();
  final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  // 調(diào)用newExecutor方法,返回一個(gè)執(zhí)行器
  final Executor executor = configuration.newExecutor(tx, execType);
  // 返回一個(gè)默認(rèn)的SqlSession
  return new DefaultSqlSession(configuration, executor, autoCommit);
 } catch (Exception e) {
  closeTransaction(tx); // may have fetched a connection so lets call close()
  throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
 } finally {
  ErrorContext.instance().reset();
 }
 }

看看如何構(gòu)造一個(gè)執(zhí)行器

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
 executorType = executorType == null ? defaultExecutorType : executorType;
 executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
 Executor executor;
 // 根據(jù)配置的執(zhí)行器類型,返回不同的執(zhí)行器
 if (ExecutorType.BATCH == executorType) {
  executor = new BatchExecutor(this, transaction);
 } else if (ExecutorType.REUSE == executorType) {
  executor = new ReuseExecutor(this, transaction);
 } else {
  executor = new SimpleExecutor(this, transaction);
 }
 // 如果開啟緩存功能,返回一個(gè)CachingExecutor
 if (cacheEnabled) {
  executor = new CachingExecutor(executor);
 }
 // 重點(diǎn)?。?!
 executor = (Executor) interceptorChain.pluginAll(executor);
 return executor;
 }

之前說過,構(gòu)建SqlSessionFactory的時(shí)候,interceptor被初始化到configuration的interceptorChain屬性中,查看代碼,看看這里做了什么

public class InterceptorChain {

 private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

 public Object pluginAll(Object target) {
 for (Interceptor interceptor : interceptors) {
  // 遍歷所有的interceptor,調(diào)用plugin方法,這個(gè)plugin方法就是我們實(shí)現(xiàn)Interceptor接口需要實(shí)現(xiàn)的三個(gè)方法之一
  target = interceptor.plugin(target);
 }
 return target;
 }
 省略其他代碼......
}

看看我們實(shí)現(xiàn)的方法中做了什么

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class LogInterceptor implements Interceptor {
 省略其他代碼......

 @Override
 public Object plugin(Object target) {
  // 因?yàn)槲覀兊倪@個(gè)Interceptor切入的是StatementHandler,所以,如果target是一個(gè)StatementHandler,我們就調(diào)用Plugin的wrap方法返回一個(gè)代理類,否則就直接返回target
  // 因?yàn)槿绻覀儾磺腥脒@個(gè)target,就不需要返回代理類。
 Class<?>[] interfaces = target.getClass().getInterfaces();
 for (Class<?> i : interfaces) {
 if (i == StatementHandler.class) {
 return Plugin.wrap(target, this);
 }
 }
 return target;
 }
 省略其他代碼......
}

從上邊我們知道,剛剛是從的newExecutor調(diào)用的,所以target是Executor不是我們切入的StatementHandler,所以這里其實(shí)什么都沒做,直接又返回了Executor。

到這里,openSession也分析完了。就是構(gòu)建了數(shù)據(jù)庫連接及執(zhí)行器。

然后,分析session.getMapper()方法,這個(gè)方法返回的是一個(gè)代理類,既然是代理類,那么我們就需要知道使用的是什么代理,找到執(zhí)行的入口。

// 跟蹤代碼,直到MapperRegistry的getMapper方法
 /**
 *@param type 就是我們getMapper要獲取的那個(gè)Mapper對(duì)象
 *@param sqlSession 就是上一步創(chuàng)建的sqlSession對(duì)象
 */
 @SuppressWarnings("unchecked")
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 // 先從knownMappers中獲取Mapper,這個(gè)knownMappers是在構(gòu)建SqlSessionFactory的時(shí)候,解析的mappers節(jié)點(diǎn)配置的所有Mapper
 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
 if (mapperProxyFactory == null) {
  throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
 }
 try {
  // 創(chuàng)建實(shí)例
  return mapperProxyFactory.newInstance(sqlSession);
 } catch (Exception e) {
  throw new BindingException("Error getting mapper instance. Cause: " + e, e);
 }
 }

查看創(chuàng)建實(shí)例的代碼

 @SuppressWarnings("unchecked")
 protected T newInstance(MapperProxy<T> mapperProxy) {
 // 根據(jù)MapperProxy創(chuàng)建代理類實(shí)例
 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
 }

 public T newInstance(SqlSession sqlSession) {
 // 直接構(gòu)造了一個(gè)MapperProxy的代理類,mapperInterface就是我們需要?jiǎng)?chuàng)建的mapper,這個(gè)是在解析配置文件的時(shí)候,為每個(gè)配置的Mapper創(chuàng)建了一個(gè)MapperProxyFactory,這里保存了對(duì)應(yīng)的mapper
 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
 return newInstance(mapperProxy);
 }

根據(jù)代碼,知道通過session.getMapper(UserMapper.class)返回的Mapper是一個(gè)MapperProxy的代理類,所以,main方法里的userMapper.select()實(shí)際上執(zhí)行的是MapperProxy的invoke方法

@Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 try {
  // 判斷是不是Object聲明的方法
  if (Object.class.equals(method.getDeclaringClass())) {
  return method.invoke(this, args);
  // 判斷是不是public方法或者接口方法
  } else if (isDefaultMethod(method)) {
  return invokeDefaultMethod(proxy, method, args);
  }
 } catch (Throwable t) {
  throw ExceptionUtil.unwrapThrowable(t);
 }
 // 在methodCache中獲取這個(gè)Mapper方法的MapperMethod對(duì)象,如果沒有就構(gòu)造一個(gè)
 final MapperMethod mapperMethod = cachedMapperMethod(method);
 return mapperMethod.execute(sqlSession, args);
 }

cachedMapperMethod方法

 private MapperMethod cachedMapperMethod(Method method) {
 // 先從methodCache中拿,因?yàn)闆]有執(zhí)行過,所以這里是空
 MapperMethod mapperMethod = methodCache.get(method);
 if (mapperMethod == null) {
  // 根據(jù)mapper類和執(zhí)行的方法(select)構(gòu)建一個(gè)MapperMethod對(duì)象,然后放到緩存中
  mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  methodCache.put(method, mapperMethod);
 }
 return mapperMethod;
 }

構(gòu)造MapperMethod

 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
 this.command = new SqlCommand(config, mapperInterface, method);
 this.method = new MethodSignature(config, mapperInterface, method);
 }

構(gòu)造SqlCommond和MethodSignature

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  final String methodName = method.getName();
  final Class<?> declaringClass = method.getDeclaringClass();
  // 根據(jù)Mapper和方法名,構(gòu)造一個(gè)MappedStatement對(duì)象
  MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
   configuration);
  if (ms == null) {
  if (method.getAnnotation(Flush.class) != null) {
   name = null;
   type = SqlCommandType.FLUSH;
  } else {
   throw new BindingException("Invalid bound statement (not found): "
    + mapperInterface.getName() + "." + methodName);
  }
  } else {
  name = ms.getId();
  type = ms.getSqlCommandType();
  if (type == SqlCommandType.UNKNOWN) {
   throw new BindingException("Unknown execution method for: " + name);
  }
  }
 }
 // 方法簽名,執(zhí)行的方法是否有返回值,返回值類型等等信息
 public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
  Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
  if (resolvedReturnType instanceof Class<?>) {
  this.returnType = (Class<?>) resolvedReturnType;
  } else if (resolvedReturnType instanceof ParameterizedType) {
  this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
  } else {
  this.returnType = method.getReturnType();
  }
  this.returnsVoid = void.class.equals(this.returnType);
  this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
  this.returnsCursor = Cursor.class.equals(this.returnType);
  this.mapKey = getMapKey(method);
  this.returnsMap = this.mapKey != null;
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  this.paramNameResolver = new ParamNameResolver(configuration, method);
 }

繼續(xù)invoke方法,執(zhí)行MapperMethod.execute()方法

public Object execute(SqlSession sqlSession, Object[] args) {
 Object result;
 switch (command.getType()) {
  case INSERT: {
  Object param = method.convertArgsToSqlCommandParam(args);
  result = rowCountResult(sqlSession.insert(command.getName(), param));
  break;
  }
  case UPDATE: {
  Object param = method.convertArgsToSqlCommandParam(args);
  result = rowCountResult(sqlSession.update(command.getName(), param));
  break;
  }
  case DELETE: {
  Object param = method.convertArgsToSqlCommandParam(args);
  result = rowCountResult(sqlSession.delete(command.getName(), param));
  break;
  }
  // 我們執(zhí)行的就是select方法
  case SELECT:
  if (method.returnsVoid() && method.hasResultHandler()) {
   executeWithResultHandler(sqlSession, args);
   result = null;
  // 并且返回的是個(gè)List
  } else if (method.returnsMany()) {
   result = executeForMany(sqlSession, args);
  } else if (method.returnsMap()) {
   result = executeForMap(sqlSession, args);
  } else if (method.returnsCursor()) {
   result = executeForCursor(sqlSession, args);
  } else {
   Object param = method.convertArgsToSqlCommandParam(args);
   result = sqlSession.selectOne(command.getName(), param);
  }
  break;
  case FLUSH:
  result = sqlSession.flushStatements();
  break;
  default:
  throw new BindingException("Unknown execution method for: " + command.getName());
 }
 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  throw new BindingException("Mapper method '" + command.getName() 
   + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
 }
 return result;
 }

執(zhí)行的是executeForMany方法,這里調(diào)用sqlSession的selectList方法,在selectList中調(diào)用了執(zhí)行器(構(gòu)建SqlSession時(shí)的那個(gè)執(zhí)行器)的query方法,因?yàn)槟J(rèn)我們沒有配置緩存,默認(rèn)是開啟的,所以會(huì)先執(zhí)行CachingExecutor的query方法,在這里,會(huì)調(diào)用配置文件中配置的執(zhí)行器(我們的沒有配置,默認(rèn)是SIMPLE類型執(zhí)行器)的query方法,因?yàn)镾impleExecutor沒有覆蓋query方法,所以實(shí)際上執(zhí)行的是BaseExecutor的query方法,在這里又調(diào)用了queryFromDatabase方法,然后又調(diào)用了doQuery方法,這個(gè)方法是抽象方法,由子類實(shí)現(xiàn),即SimpleExecutor的doQuery方法

 @Override
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 Statement stmt = null;
 try {
  Configuration configuration = ms.getConfiguration();
  // 這個(gè)方法,類似之前提過的newExecutor方法
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.<E>query(stmt, resultHandler);
 } finally {
  closeStatement(stmt);
 }
 }

newStatementHandler方法

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
 // 在這里,又調(diào)用了pluginAll方法, 之前講過,這個(gè)方法其實(shí)就是遍歷每個(gè)Interceptor,調(diào)用它們的plugin方法
 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
 return statementHandler;
 }

我們的LogInterceptor的plugin方法

 @Override
 public Object plugin(Object target) {
 Class<?>[] interfaces = target.getClass().getInterfaces();
 for (Class<?> i : interfaces) {
 if (i == StatementHandler.class) {
 return Plugin.wrap(target, this);
 }
 }
 return target;
 }

其實(shí)就是調(diào)用了一下Plugin的wrap方法。首先看一下Plugin的聲明,他就是一個(gè)InvocationHandler。

public class Plugin implements InvocationHandler {
 // 省略部分代碼......
 /**
 * @param target 在這里就是StatementHandler
 * @param interceptor 在這里就是我們的LogInterceptor
 * /
 public static Object wrap(Object target, Interceptor interceptor) {
 // 解析注解配置,也就是我們要切入的類及方法
 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
 Class<?> type = target.getClass();
 // 如果我們的注解包含這個(gè)type,就把type的接口返回,在這里會(huì)返回StatementHandler。
 Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
 if (interfaces.length > 0) {
  // 創(chuàng)建一個(gè)代理類,代理類為Plugin
  return Proxy.newProxyInstance(
   type.getClassLoader(),
   interfaces,
   new Plugin(target, interceptor, signatureMap));
 }
 return target;
 }
 
 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
 // 注解配置
 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
 // issue #251
 if (interceptsAnnotation == null) {
  throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());  
 }
 Signature[] sigs = interceptsAnnotation.value();
 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
 for (Signature sig : sigs) {
  Set<Method> methods = signatureMap.get(sig.type());
  if (methods == null) {
  methods = new HashSet<Method>();
  signatureMap.put(sig.type(), methods);
  }
  try {
  Method method = sig.type().getMethod(sig.method(), sig.args());
  methods.add(method);
  } catch (NoSuchMethodException e) {
  throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
  }
 }
 return signatureMap;
 }
 // 省略部分代碼......
}

也就是說,因?yàn)槲覀兊那腥?,在SimpleExecutor的doQuery方法中調(diào)用newStatementHandler返回的是一個(gè)StatementHandler的代理類。接下來就調(diào)用了內(nèi)部方法prepareStatement方法

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 Statement stmt;
 Connection connection = getConnection(statementLog);
 // 重點(diǎn)!!!因?yàn)閔andler是代理類,所以執(zhí)行的都是代理類的invoke方法
 stmt = handler.prepare(connection, transaction.getTimeout());
 handler.parameterize(stmt);
 return stmt;
 }

代理類Plugin的invoke方法

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 try {
  // 從signatureMap中找一下,這個(gè)map的key是切入的類,所以調(diào)用getDeclaringClass()根據(jù)當(dāng)前方法的聲明類查找對(duì)應(yīng)的方法集合。
  Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  // 如果集合不為空或者包含當(dāng)前方法, 也就是說我們切入這個(gè)類的這個(gè)方法了
  if (methods != null && methods.contains(method)) {
  // 就會(huì)調(diào)用interceptor的interceptor方法了。注意,這里是return,也就是說不會(huì)執(zhí)行默認(rèn)的后續(xù)流程了。如果要執(zhí)行后續(xù)流程,可以再interceptor方法的最后調(diào)用invocation的proceed()方法。
  return interceptor.intercept(new Invocation(target, method, args));
  }
  return method.invoke(target, args);
 } catch (Exception e) {
  throw ExceptionUtil.unwrapThrowable(e);
 }
 }

這時(shí)候,輪到我們真正切入的邏輯了,這里為了演示,我們只是簡(jiǎn)單的做了替換。實(shí)現(xiàn)效果。

 @Override
 public Object intercept(Invocation invocation) throws Throwable {
 StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
 BoundSql boundSql = statementHandler.getBoundSql();
 // 原始sql
 String sql = boundSql.getSql();
 System.out.println("打印原始sql===>" + sql);
 // 參數(shù)集合
 MapperMethod.ParamMap parameterObject = (MapperMethod.ParamMap) boundSql.getParameterObject();
 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 for (ParameterMapping parameterMapping : parameterMappings) {
 Object param = parameterObject.get(parameterMapping.getProperty());
 if (param.getClass() == Integer.class) {
 sql = sql.replaceFirst("\\?", param.toString());
 } else if (param.getClass() == String.class) {
 sql = sql.replaceFirst("\\?", "'".concat(param.toString()).concat("'"));
 }
 }
 // 替換占位符的sql
 System.out.println("打印執(zhí)行sql===>" + sql);
 // 最后調(diào)用一下這個(gè)方法,這個(gè)方法其實(shí)就是默認(rèn)的后續(xù)方法。當(dāng)然,按照實(shí)際業(yè)務(wù),如果不需要的話,也可以不調(diào)用。
 return invocation.proceed();
 }

之后的流程就是默認(rèn)的流程了。至此,其實(shí)整個(gè)的plugin攔截就分析完畢了。

其實(shí),就是通過jdk的動(dòng)態(tài)代理,為需要攔截的點(diǎn),創(chuàng)建一個(gè)代理類,當(dāng)執(zhí)行指定攔截的方法時(shí),通過代理類執(zhí)行自己的業(yè)務(wù)邏輯,而且可以選擇是否執(zhí)行默認(rèn)的后續(xù)流程,可以選擇自己執(zhí)行后續(xù)流程。

以上這篇mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢(shì)詳解就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • skywalking源碼解析javaAgent工具ByteBuddy應(yīng)用

    skywalking源碼解析javaAgent工具ByteBuddy應(yīng)用

    這篇文章主要為大家介紹了skywalking源碼解析javaAgent工具ByteBuddy應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • Java List接口的集合使用詳解

    Java List接口的集合使用詳解

    這篇文章主要介紹了Java集合操作之List接口及其實(shí)現(xiàn)方法,詳細(xì)分析了Java集合操作中List接口原理、功能、用法及操作注意事項(xiàng),需要的朋友可以參考下
    2021-08-08
  • 如何為?Spring?Boot?項(xiàng)目配置?Logback?日志

    如何為?Spring?Boot?項(xiàng)目配置?Logback?日志

    由于?Spring?Boot?的默認(rèn)日志框架選用的?Logback,再加上?Log4j2?之前爆過嚴(yán)重的漏洞,所以我們這次就只關(guān)注?Logback,本文重點(diǎn)給大家介紹如何為?Spring?Boot?項(xiàng)目配置?Logback?日志,感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • Java使用poi操作excel實(shí)例解析

    Java使用poi操作excel實(shí)例解析

    這篇文章主要為大家詳細(xì)介紹了Java使用poi操作excel的簡(jiǎn)單實(shí)例,感興趣的小伙伴們可以參考一下
    2016-05-05
  • spring實(shí)例化javabean的三種方式分享

    spring實(shí)例化javabean的三種方式分享

    這篇文章介紹了spring實(shí)例化javabean的三種方式,有需要的朋友可以參考一下
    2013-10-10
  • Java為實(shí)體類動(dòng)態(tài)添加屬性的方法詳解

    Java為實(shí)體類動(dòng)態(tài)添加屬性的方法詳解

    這篇文章主要介紹了Java如何給已有實(shí)體類動(dòng)態(tài)的添加字段并返回新的實(shí)體對(duì)象且不影響原來的實(shí)體對(duì)象結(jié)構(gòu)。文中的方法講解詳細(xì),需要的可以參考一下
    2022-06-06
  • 徹底搞懂Java多線程(五)

    徹底搞懂Java多線程(五)

    這篇文章主要給大家介紹了關(guān)于Java面試題之多線程和高并發(fā)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • 詳解Java中Thread 和Runnable區(qū)別

    詳解Java中Thread 和Runnable區(qū)別

    這篇文章主要介紹了Java中Thread 和Runnable的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Java的常見熱門ORM框架優(yōu)缺點(diǎn)區(qū)別

    Java的常見熱門ORM框架優(yōu)缺點(diǎn)區(qū)別

    Java?ORM框架是一種用于將Java對(duì)象映射到關(guān)系型數(shù)據(jù)庫中的工具,使得開發(fā)人員能夠通過對(duì)象操作數(shù)據(jù)庫而不必直接使用SQL查詢,Java開發(fā)變得更加高效和易于維護(hù),選擇適合你的ORM框架是根據(jù)你的需求決定的,比如你的應(yīng)用場(chǎng)景,數(shù)據(jù)結(jié)構(gòu)和技術(shù)水平等
    2024-02-02
  • 關(guān)于Spring Cloud 本地屬性覆蓋的問題

    關(guān)于Spring Cloud 本地屬性覆蓋的問題

    這篇文章主要介紹了關(guān)于Spring Cloud 本地屬性覆蓋的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評(píng)論