Mybatis @Flush annotation analysis
While looking at the source code, I found the @flush annotation. I haven’t used it before, and that’s where this article comes in
Note: The executor type here must be BatchExecutor
First example
@Test
public void testShouldSelectClassUsingMapperClass(a){
try(
// Specify BATCH as the executor type for this query. The corresponding type is BatchExecutor
SqlSession session = sqlMapper.openSession(ExecutorType.BATCH)
){
ClassMapper mapper = session.getMapper(ClassMapper.class);
long start = System.currentTimeMillis();
// This query is normal query method
List<ClassDo> classDos = mapper.listClassOwnerStudentByClassId(1.new String[]{"The dog egg"});
System.out.println(classDos);
System.out.println("Start updating");
// Here are the two update operations
System.out.println(mapper.updateClassNameById(1."Accounting Division 1"));
System.out.println(mapper.updateClassNameById(2."Accounting Division 1 2"));
System.out.println(End of update);
// Call flush, which I wrote myself. The return value is List
and there is no CORRESPONDING Xml for this flush method.
// The database is actually updated when this method is called
for (BatchResult flush : mapper.flush()) {
// Update SQL
System.out.println(flush.getSql());
// Updated parameters
System.out.println(flush.getParameterObjects());
// Update the number of rows affected, including getParameterObjects above, which return values are all an array
// This batch is in the statement dimension. Therefore, both of the above queries belong to the same statement, with different parameters
So, getParameterObjects is an array, and getUpdateCounts must also be an array.System.out.println(flush.getUpdateCounts()); }}public interface ClassMapper {
// This does not have, must not have
@Flush
List<BatchResult> flush(a);
// The following two have XMl files
List<ClassDo> listClassOwnerStudentByClassId(Integer orderId,@Param("array") String[] names);
int updateClassNameById(@Param("id") Integer id,@Param("className") String className);
}
Copy the code
Looking at the results
1. The role of annotations
First look at the comments on the source code
/** * The maker annotation that invoke a flush statements via Mapper interface. * * * public interface UserMapper {* & # 064; Flush * List< BatchResult> flush(); * } *
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Flush {
}
Copy the code
This annotation is a tag interface that is invoked to refresh statements through Mapper’s interface
2. How do annotations work?
On method invocation, the Invoke method of MapperProxy is called, where PlainMethodInvoker is built, and MapperMethod is wrapped inside PlainMethodInvoker. SqlCommand and MethodSignature are built in MapperMethod, and the first parse @flush operation builds the SqlCommand object. In this, if the current method is decorated with @flush and there is no Mapper, the query will be sqlCommandType.flush
- First, parse the @flush annotation.
public SqlCommand(Configuration configuration, Class
mapperInterface, Method method) {
// The method name
final String methodName = method.getName();
// The class of method
finalClass<? > declaringClass = method.getDeclaringClass();// Get the MappedStatement from Configuration with the fully qualified type name and method name.
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// if ms is null. To use Flush, do not write a MapperStatement.
if (ms == null) {
if(method.getAnnotation(Flush.class) ! =null) {
// The query type is FLUSH.
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); }}}Copy the code
- use
@Flush
Annotation.
The execute method of MapperMethod uses Type to determine the Type of the operation. The execute method is FLUSH.
public Object execute(SqlSession sqlSession, Object[] args) {
Object result; // It's called strategic mode
// Type is FLUSH
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;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} 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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
// The point is the 'flushStatements' method
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;
}
Copy the code
Continue to see the sqlSession. FlushStatements () went down after the above code has been point, there will be produce polymorphic. One is in the doFlushStatements method of BatchExecutor, and the other is in the doFlushStatements method of SimpleExecutor. The most important thing here is to take the first case. But I’m going to go ahead and look at both cases.
-
BatchExecutor
StatementList is an attribute of BatchExecutor. In this method, the Statement previously added to the statementList is executed, and the updated line number is encapsulated as an attribute of batchResult. This is called batch updating.
How many questions?
Why is statementList a List
Because there can be multiple statements in an update operation, so here’s a List,
Why is batchResult also a List and parameterObjects also a List?
Because multiple statements correspond to multiple results, parameterObjects is because a Statement can be called multiple times with different parameter values.
So where are the statementList and batchResult values added?
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
if (isRollback) {
return Collections.emptyList();
}
/ / traverse the statement
for (int i = 0, n = statementList.size(); i < n; i++) {
// Set the query parameters
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
// Statement executeBatch will be called to execute the previously prepared SQL in batches.
// Where is statement added? Where was batchResult added?
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
// Get the updated operation
List<Object> parameterObjects = batchResult.getParameterObjects();
// If KeyGenerator is used.
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if(! NoKeyGenerator.class.equals(keyGenerator.getClass())) {//issue #141
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter); }}/ / close the Statement
closeStatement(stmt);
} catch (BatchUpdateException e) {
StringBuilder message = new StringBuilder();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append("")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
// Add the result to result.
results.add(batchResult);
}
return results;
} finally {
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null; statementList.clear(); batchResultList.clear(); }}Copy the code
-
SimpleExecutor
// Empty operation. Returns an empty list, which is nothing to look at.
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
return Collections.emptyList();
}
Copy the code
3. WhenstatementList
andbatchResultList
Add value
The easiest way to do this is to click on it, find all the addresses it references, and look for the Add method, because it’s a List
There appears to be only one place where the Add method is called
The built Statement is added to the statementList during the update operation, and the BatchResult is also built and added to it. The PreparedStatement#addBatch() method is finally called. And returns a fixed value.
The BatchResult parameters are MapperStatement, SQL, and parameterObject
The problem?
-
What is currentSql in the following code?
This is when an updated Mapper is called multiple times to produce only one Statement and one BatchResult
CurrentSql is null when the method is called for the first time. CurrentSql and currentStatement are assigned to the current assembly after being added to the first Statement. If the method is called a second time, the currentSql and currentStatement will go to if. The query parameters are added to the parameterObject property of the BatchResult
// BatchExecutor's doUpdate method
// The logic is basically the same as the previous query. Get Connection, process parameters, but instead of doing it, PreparedStatement#addBatch() is called;
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null.null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);// fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); // fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
handler.batch(stmt);
// Return a fixed value of integer.min_value + 1002;
return BATCH_UPDATE_RETURN_VALUE;
}
Copy the code
added
Delete, insert and Update in Mybatis will use doUpdate method, the above is Update example. That is, the remaining three operations perform batch processing except for queries.
conclusion
Mybatis @Flush is prepared for batch operation. The executor type must be set to BatchExecutor (which can be set globally and locally when retrieving sessions), and the @flush method must not have an XML file. The return value is List, as shown below
@Flush
List<BatchResult> flush(a);
Copy the code
When a method is called, it can be updated by calling Mapper’s @flush modified method.