mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢詳解
好久沒編碼了!最近開始編碼遇到一個問題 !一個批量修改的問題,就是mybatis foreach 的使用。
當時使用的場景,前端 傳逗號拼接的字串id,修改id對應資料的資料順序 ,順序 就是id 的順序.
就是一個條件(單個id值) 修改一個值(傳入的id的順序),
1、 把條件作為Map 的key 修改值是value,用map入參
2、用List<Object> 或者陣列 ,把條件和值封裝成物件放進list集合或者array陣列
3、程式碼使用for迴圈呼叫mapper方法 穿兩個引數。
因為考慮到第二種用法,需要不斷建立物件 放進陣列在 遍歷陣列獲取物件取值。從虛擬機器的堆記憶體考慮,放棄------------------------
第三種方法,會迴圈多少次就執行多少條sql語句,放棄-----------------------
於是使用Map,
可是在mybatis中引數是map的foreach使用,對於很久沒編碼的我,實在是忘記得很乾淨。於是百度一堆,一致性 就是報錯:
把打印出的sql語句放到navicat 執行 可以執行不會報錯。那問題是什麼(這裡想來我1個小時),最後沒辦法 直接看mybatis的官網,把sql改成如下,正確執行。
下面給出正確的mybatis中foreach的map的姿勢,避免大家以後在這上面浪費時間 ,直接上程式碼,塗改部分因公司保密協議問題(大家都知道是表名):
mapper
一定要加@Param註解
mapper.xml
最後 大家遇到此問題的時候 看到我的這篇文章 少耽誤時間!
補充知識:MyBatis3的Plugins功能使用
1、這是什麼?
根據官方介紹,這個功能可以讓你在已對映語句執行過程中的某一點進行攔截呼叫。其實就是MyBatis給使用者留了幾個切入點,通過這些切入點,使用者可以自己實現的功能。根據官方介紹,這些切入點(方法)包括以下幾個:
Executor (update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)
ParameterHandler (getParameterObject,setParameters)
ResultSetHandler (handleResultSets,handleOutputParameters)
StatementHandler (prepare,parameterize,batch,update,query)
這是四個介面以及介面的方法,這些介面是MyBatis在執行sql語句時會執行的方法。使用者就可以通過這些方法進行切入,實現自己的邏輯。
接下來,我們通過實現一個列印sql語句的功能來進行演示。
2、通過Plugin實現列印SQL語句
2.1 建立表並初始化資料
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;
插入測試資料
INSERT INTO `t_user` VALUES (1,'張三',4,'OUT','男'); INSERT INTO `t_user` VALUES (2,'李四',5,'男'); INSERT INTO `t_user` VALUES (3,'王五','男');
2.2、構建專案
建立一個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>
建立包及類
2.3、類介紹
User類,與資料庫表對應的實體類
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介面,實現具體的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裡的引數問題,一般情況下,如果只有一個引數,我們不需要寫@Param註解也是可以的,如果有多個引數,則需要對每個引數使用@Param註解指定引數名字,或者也可以使用MyBatis提供的預設引數方式,就是示例中使用的方式,按照引數的順序分別為param1、param2…。
接下來,就是Plugins的核心程式碼了,LogInterceptor類,按照官方文件,如果我們需要使用Plugins的功能,需實現 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); // 引數集合 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("列印執行sql===>" + sql); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target,this); } @Override public void setProperties(Properties properties) { } }
實現介面的三個方法 intercept(Invocation invocation)、plugin(Object target)、setProperties(Properties properties)。
通過註解Intercepts告訴MyBatis,我們需要在哪個切入點實現自己的功能,在這裡我們切入的是StatementHandler的prepare方法,即這是我們的切入點,當MyBatis執行一個sql語句到這個方法的時候,會先執行我們自己的邏輯,執行哪個邏輯呢?就是 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、執行效果
執行我們的main方法,效果如下:
可以看出,通過一個切入,我們可以把MyBatis真正執行的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 { // 根據配置檔案構造一個XMLConfigBuilder物件 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream,environment,properties); // 通過parse()方法解析配置檔案,構造一個Configuration物件,然後根據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節點的方法
private void pluginElement(XNode parent) throws Exception { // parent就是plugins節點 if (parent != null) { // 解析每個plugin節點 for (XNode child : parent.getChildren()) { // 獲取plugin節點的interceptor引數 String interceptor = child.getStringAttribute("interceptor"); // 獲取plugin節點的property引數(我們的配置檔案未配置該引數) Properties properties = child.getChildrenAsProperties(); // 呼叫resolveClass方法,載入我們配置的interceptor引數指定的類並返回這個類的例項 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); // 設定配置的屬性 interceptorInstance.setProperties(properties); // 把例項新增到配置物件的私有屬性interceptorChain中,它只有一個私有屬性,就是所有Interceptor的集合 configuration.addInterceptor(interceptorInstance); } } }
所以構建SqlSessionFactory的時候,就是為我們配置的interceptor建立了一個例項並儲存在了一個集合中。
繼續回到main方法,構建完SqlSessionFactory之後,想要執行sql,需要通過SqlSessionFactory返回一個SqlSession,跟蹤factory.openSession()方法
/** * @param execType 執行器,不配置預設是SIMPLE * @param level 事物隔離級別,這裡為null * @param autoCommit 自動提交,這裡預設為false */ private SqlSession openSessionFromDataSource(ExecutorType execType,TransactionIsolationLevel level,boolean autoCommit) { // 資料庫連線的包裝類 Transaction tx = null; try { // 從配置物件中獲取環境資訊,構建資料庫連線 final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit); // 呼叫newExecutor方法,返回一個執行器 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(); } }
看看如何構造一個執行器
public Executor newExecutor(Transaction transaction,ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; // 根據配置的執行器型別,返回不同的執行器 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方法就是我們實現Interceptor介面需要實現的三個方法之一 target = interceptor.plugin(target); } return target; } 省略其他程式碼...... }
看看我們實現的方法中做了什麼
@Intercepts({@Signature(type = StatementHandler.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也分析完了。就是構建了資料庫連線及執行器。
然後,分析session.getMapper()方法,這個方法返回的是一個代理類,既然是代理類,那麼我們就需要知道使用的是什麼代理,找到執行的入口。
// 跟蹤程式碼,直到MapperRegistry的getMapper方法 /** *@param type 就是我們getMapper要獲取的那個Mapper物件 *@param sqlSession 就是上一步建立的sqlSession物件 */ @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type,SqlSession sqlSession) { // 先從knownMappers中獲取Mapper,這個knownMappers是在構建SqlSessionFactory的時候,解析的mappers節點配置的所有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 { // 建立例項 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e,e); } }
檢視建立例項的程式碼
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { // 根據MapperProxy建立代理類例項 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[] { mapperInterface },mapperProxy); } public T newInstance(SqlSession sqlSession) { // 直接構造了一個MapperProxy的代理類,mapperInterface就是我們需要建立的mapper,這個是在解析配置檔案的時候,為每個配置的Mapper建立了一個MapperProxyFactory,這裡儲存了對應的mapper final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,mapperInterface,methodCache); return newInstance(mapperProxy); }
根據程式碼,知道通過session.getMapper(UserMapper.class)返回的Mapper是一個MapperProxy的代理類,所以,main方法裡的userMapper.select()實際上執行的是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中拿,因為沒有執行過,所以這裡是空 MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { // 根據mapper類和執行的方法(select)構建一個MapperMethod物件,然後放到快取中 mapperMethod = new MapperMethod(mapperInterface,sqlSession.getConfiguration()); methodCache.put(method,mapperMethod); } return mapperMethod; }
構造MapperMethod
public MapperMethod(Class<?> mapperInterface,Configuration config) { this.command = new SqlCommand(config,method); this.method = new MethodSignature(config,method); }
構造SqlCommond和MethodSignature
public SqlCommand(Configuration configuration,Class<?> mapperInterface,Method method) { final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); // 根據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); } } }
// 方法簽名,執行的方法是否有返回值,返回值型別等等資訊 public MethodSignature(Configuration configuration,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); }
繼續invoke方法,執行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; } // 我們執行的就是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; }
執行的是executeForMany方法,這裡呼叫sqlSession的selectList方法,在selectList中呼叫了執行器(構建SqlSession時的那個執行器)的query方法,因為預設我們沒有配置快取,預設是開啟的,所以會先執行CachingExecutor的query方法,在這裡,會呼叫配置檔案中配置的執行器(我們的沒有配置,預設是SIMPLE型別執行器)的query方法,因為SimpleExecutor沒有覆蓋query方法,所以實際上執行的是BaseExecutor的query方法,在這裡又呼叫了queryFromDatabase方法,然後又呼叫了doQuery方法,這個方法是抽象方法,由子類實現,即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,BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor,mappedStatement,parameterObject,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) { // 建立一個代理類,代理類為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是代理類,所以執行的都是代理類的invoke方法 stmt = handler.prepare(connection,transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
代理類Plugin的invoke方法
@Override public Object invoke(Object proxy,Object[] args) throws Throwable { try { // 從signatureMap中找一下,這個map的key是切入的類,所以呼叫getDeclaringClass()根據當前方法的宣告類查詢對應的方法集合。 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); // 如果集合不為空或者包含當前方法, 也就是說我們切入這個類的這個方法了 if (methods != null && methods.contains(method)) { // 就會呼叫interceptor的interceptor方法了。注意,這裡是return,也就是說不會執行預設的後續流程了。如果要執行後續流程,可以再interceptor方法的最後呼叫invocation的proceed()方法。 return interceptor.intercept(new Invocation(target,args)); } return method.invoke(target,args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }
這時候,輪到我們真正切入的邏輯了,這裡為了演示,我們只是簡單的做了替換。實現效果。
@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); // 引數集合 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("\\?","'".concat(param.toString()).concat("'")); } } // 替換佔位符的sql System.out.println("列印執行sql===>" + sql); // 最後呼叫一下這個方法,這個方法其實就是預設的後續方法。當然,按照實際業務,如果不需要的話,也可以不呼叫。 return invocation.proceed(); }
之後的流程就是預設的流程了。至此,其實整個的plugin攔截就分析完畢了。
其實,就是通過jdk的動態代理,為需要攔截的點,建立一個代理類,當執行指定攔截的方法時,通過代理類執行自己的業務邏輯,而且可以選擇是否執行預設的後續流程,可以選擇自己執行後續流程。
以上這篇mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢詳解就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。