mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢詳解
好久沒編碼了!最近開始編碼遇到一個問題 !一個批量修改的問題,就是mybatis foreach 的使用。
當時使用的場景 ,前端 傳逗號拼接的字符串id, 修改id對應數(shù)據(jù)的數(shù)據(jù)順序 ,順序 就是id 的順序.
就是一個條件(單個id值) 修改一個值(傳入的id的順序) ,
1、 把條件作為Map 的key 修改值是value,用map入?yún)?/p>
2、用List<Object> 或者數(shù)組 ,把條件和值封裝成對象放進list集合或者array數(shù)組
3、代碼使用for循環(huán)調用mapper方法 穿兩個參數(shù)。
因為考慮到第二種用法,需要不斷創(chuàng)建對象 放進數(shù)組在 遍歷數(shù)組獲取對象取值。從虛擬機的堆內存考慮,放棄------------------------
第三種方法,會循環(huán)多少次就執(zhí)行多少條sql語句,放棄-----------------------
于是使用Map,
可是在mybatis中參數(shù)是map的foreach使用,對于很久沒編碼的我,實在是忘記得很干凈。于是百度一堆,一致性 就是報錯:


把打印出的sql語句放到navicat 執(zhí)行 可以執(zhí)行不會報錯。那問題是什么(這里想來我1個小時),最后沒辦法 直接看mybatis的官網(wǎng),把sql改成如下,正確執(zhí)行。
下面給出正確的mybatis中foreach的map的姿勢,避免大家以后在這上面浪費時間 ,直接上代碼,涂改部分因公司保密協(xié)議問題(大家都知道是表名):
mapper

一定要加@Param注解
mapper.xml

最后 大家遇到此問題的時候 看到我的這篇文章 少耽誤時間!
補充知識:MyBatis3的Plugins功能使用
1、這是什么?
根據(jù)官方介紹,這個功能可以讓你在已映射語句執(zhí)行過程中的某一點進行攔截調用。其實就是MyBatis給用戶留了幾個切入點,通過這些切入點,用戶可以自己實現(xiàn)的功能。根據(jù)官方介紹,這些切入點(方法)包括以下幾個:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
這是四個接口以及接口的方法,這些接口是MyBatis在執(zhí)行sql語句時會執(zhí)行的方法。用戶就可以通過這些方法進行切入,實現(xiàn)自己的邏輯。
接下來,我們通過實現(xiàn)一個打印sql語句的功能來進行演示。
2、通過Plugin實現(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;
插入測試數(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、構建項目
創(chuàng)建一個maven項目,并引入相關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ù)庫表對應的實體類
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接口,實現(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 結果集合
*/
@Select("select * from t_user where id = #{param1} and name = #{param2}")
List<User> select(@Param("idParam") int id, @Param("nameParam") String name);
}
在這里簡單說明一下注解sql里的參數(shù)問題,一般情況下,如果只有一個參數(shù),我們不需要寫@Param注解也是可以的,如果有多個參數(shù),則需要對每個參數(shù)使用@Param注解指定參數(shù)名字,或者也可以使用MyBatis提供的默認參數(shù)方式,就是示例中使用的方式,按照參數(shù)的順序分別為param1、param2…。
接下來,就是Plugins的核心代碼了,LogInterceptor類,按照官方文檔,如果我們需要使用Plugins的功能,需實現(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) {
}
}
實現(xiàn)接口的三個方法 intercept(Invocation invocation)、plugin(Object target)、setProperties(Properties properties)。
通過注解Intercepts告訴MyBatis,我們需要在哪個切入點實現(xiàn)自己的功能,在這里我們切入的是StatementHandler的prepare方法,即這是我們的切入點,當MyBatis執(zhí)行一個sql語句到這個方法的時候,會先執(zhí)行我們自己的邏輯,執(zhí)行哪個邏輯呢?就是 intercept(Invocation invocation)里面的邏輯。具體原理我們稍后再講。
最后是我們的主體類,這里為了方便,我直接使用一個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();
}
}
}
基于我們的需求,只做了簡單的配置,配置文件如下
<?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方法,效果如下:

可以看出,通過一個切入,我們可以把MyBatis真正執(zhí)行的sql打印出來。
3、詳細解析
我們從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();
}
}
我們在配置文件中增加了LogInterceptor的配置,那么在SqlSessionFactory初始化的時候做了什么呢?
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 根據(jù)配置文件構造一個XMLConfigBuilder對象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 通過parse()方法解析配置文件,構造一個Configuration對象,然后根據(jù)Configuration對象構造一個默認的DefaultSqlSessionFactory對象
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.
}
}
}
跟蹤這個對象的parse()方法,找到解析plugins節(jié)點的方法
private void pluginElement(XNode parent) throws Exception {
// parent就是plugins節(jié)點
if (parent != null) {
// 解析每個plugin節(jié)點
for (XNode child : parent.getChildren()) {
// 獲取plugin節(jié)點的interceptor參數(shù)
String interceptor = child.getStringAttribute("interceptor");
// 獲取plugin節(jié)點的property參數(shù)(我們的配置文件未配置該參數(shù))
Properties properties = child.getChildrenAsProperties();
// 調用resolveClass方法,加載我們配置的interceptor參數(shù)指定的類并返回這個類的實例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 設置配置的屬性
interceptorInstance.setProperties(properties);
// 把實例添加到配置對象的私有屬性interceptorChain中,它只有一個私有屬性,就是所有Interceptor的集合
configuration.addInterceptor(interceptorInstance);
}
}
}
所以構建SqlSessionFactory的時候,就是為我們配置的interceptor創(chuàng)建了一個實例并保存在了一個集合中。
繼續(xù)回到main方法,構建完SqlSessionFactory之后,想要執(zhí)行sql,需要通過SqlSessionFactory返回一個SqlSession,跟蹤factory.openSession()方法
/**
* @param execType 執(zhí)行器,不配置默認是SIMPLE
* @param level 事物隔離級別,這里為null
* @param autoCommit 自動提交,這里默認為false
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
// 數(shù)據(jù)庫連接的包裝類
Transaction tx = null;
try {
// 從配置對象中獲取環(huán)境信息,構建數(shù)據(jù)庫連接
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 調用newExecutor方法,返回一個執(zhí)行器
final Executor executor = configuration.newExecutor(tx, execType);
// 返回一個默認的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();
}
}
看看如何構造一個執(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);
}
// 如果開啟緩存功能,返回一個CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 重點?。?!
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
之前說過,構建SqlSessionFactory的時候,interceptor被初始化到configuration的interceptorChain屬性中,查看代碼,看看這里做了什么
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 遍歷所有的interceptor,調用plugin方法,這個plugin方法就是我們實現(xiàn)Interceptor接口需要實現(xiàn)的三個方法之一
target = interceptor.plugin(target);
}
return target;
}
省略其他代碼......
}
看看我們實現(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) {
// 因為我們的這個Interceptor切入的是StatementHandler,所以,如果target是一個StatementHandler,我們就調用Plugin的wrap方法返回一個代理類,否則就直接返回target
// 因為如果我們不切入這個target,就不需要返回代理類。
Class<?>[] interfaces = target.getClass().getInterfaces();
for (Class<?> i : interfaces) {
if (i == StatementHandler.class) {
return Plugin.wrap(target, this);
}
}
return target;
}
省略其他代碼......
}
從上邊我們知道,剛剛是從的newExecutor調用的,所以target是Executor不是我們切入的StatementHandler,所以這里其實什么都沒做,直接又返回了Executor。
到這里,openSession也分析完了。就是構建了數(shù)據(jù)庫連接及執(zhí)行器。
然后,分析session.getMapper()方法,這個方法返回的是一個代理類,既然是代理類,那么我們就需要知道使用的是什么代理,找到執(zhí)行的入口。
// 跟蹤代碼,直到MapperRegistry的getMapper方法
/**
*@param type 就是我們getMapper要獲取的那個Mapper對象
*@param sqlSession 就是上一步創(chuàng)建的sqlSession對象
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 先從knownMappers中獲取Mapper,這個knownMappers是在構建SqlSessionFactory的時候,解析的mappers節(jié)點配置的所有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)建實例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
查看創(chuàng)建實例的代碼
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 根據(jù)MapperProxy創(chuàng)建代理類實例
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// 直接構造了一個MapperProxy的代理類,mapperInterface就是我們需要創(chuàng)建的mapper,這個是在解析配置文件的時候,為每個配置的Mapper創(chuàng)建了一個MapperProxyFactory,這里保存了對應的mapper
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
根據(jù)代碼,知道通過session.getMapper(UserMapper.class)返回的Mapper是一個MapperProxy的代理類,所以,main方法里的userMapper.select()實際上執(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中獲取這個Mapper方法的MapperMethod對象,如果沒有就構造一個
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
cachedMapperMethod方法
private MapperMethod cachedMapperMethod(Method method) {
// 先從methodCache中拿,因為沒有執(zhí)行過,所以這里是空
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
// 根據(jù)mapper類和執(zhí)行的方法(select)構建一個MapperMethod對象,然后放到緩存中
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
構造MapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
構造SqlCommond和MethodSignature
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 根據(jù)Mapper和方法名,構造一個MappedStatement對象
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;
// 并且返回的是個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方法,這里調用sqlSession的selectList方法,在selectList中調用了執(zhí)行器(構建SqlSession時的那個執(zhí)行器)的query方法,因為默認我們沒有配置緩存,默認是開啟的,所以會先執(zhí)行CachingExecutor的query方法,在這里,會調用配置文件中配置的執(zhí)行器(我們的沒有配置,默認是SIMPLE類型執(zhí)行器)的query方法,因為SimpleExecutor沒有覆蓋query方法,所以實際上執(zhí)行的是BaseExecutor的query方法,在這里又調用了queryFromDatabase方法,然后又調用了doQuery方法,這個方法是抽象方法,由子類實現(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();
// 這個方法,類似之前提過的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);
// 在這里,又調用了pluginAll方法, 之前講過,這個方法其實就是遍歷每個Interceptor,調用它們的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;
}
其實就是調用了一下Plugin的wrap方法。首先看一下Plugin的聲明,他就是一個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();
// 如果我們的注解包含這個type,就把type的接口返回,在這里會返回StatementHandler。
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 創(chuàng)建一個代理類,代理類為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;
}
// 省略部分代碼......
}
也就是說,因為我們的切入,在SimpleExecutor的doQuery方法中調用newStatementHandler返回的是一個StatementHandler的代理類。接下來就調用了內部方法prepareStatement方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 重點!??!因為handler是代理類,所以執(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中找一下,這個map的key是切入的類,所以調用getDeclaringClass()根據(jù)當前方法的聲明類查找對應的方法集合。
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 如果集合不為空或者包含當前方法, 也就是說我們切入這個類的這個方法了
if (methods != null && methods.contains(method)) {
// 就會調用interceptor的interceptor方法了。注意,這里是return,也就是說不會執(zhí)行默認的后續(xù)流程了。如果要執(zhí)行后續(xù)流程,可以再interceptor方法的最后調用invocation的proceed()方法。
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
這時候,輪到我們真正切入的邏輯了,這里為了演示,我們只是簡單的做了替換。實現(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);
// 最后調用一下這個方法,這個方法其實就是默認的后續(xù)方法。當然,按照實際業(yè)務,如果不需要的話,也可以不調用。
return invocation.proceed();
}
之后的流程就是默認的流程了。至此,其實整個的plugin攔截就分析完畢了。
其實,就是通過jdk的動態(tài)代理,為需要攔截的點,創(chuàng)建一個代理類,當執(zhí)行指定攔截的方法時,通過代理類執(zhí)行自己的業(yè)務邏輯,而且可以選擇是否執(zhí)行默認的后續(xù)流程,可以選擇自己執(zhí)行后續(xù)流程。
以上這篇mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢詳解就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
skywalking源碼解析javaAgent工具ByteBuddy應用
這篇文章主要為大家介紹了skywalking源碼解析javaAgent工具ByteBuddy應用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-03-03
如何為?Spring?Boot?項目配置?Logback?日志
由于?Spring?Boot?的默認日志框架選用的?Logback,再加上?Log4j2?之前爆過嚴重的漏洞,所以我們這次就只關注?Logback,本文重點給大家介紹如何為?Spring?Boot?項目配置?Logback?日志,感興趣的朋友跟隨小編一起看看吧2024-07-07

