Cabbage Java self study room covers core knowledge
The path of Java engineer (Mybatis)
1. Mybatis SqlSession details
The primary Java interface to use MyBatis is SqlSession. You can use this interface to execute commands, get mapper samples, and manage transactions. Before introducing the SqlSession interface, let’s look at how to get an INSTANCE of SqlSession. SqlSessions are created by the SqlSessionFactory instance. The SqlSessionFactory object contains various methods for creating an instance of SqlSession. The SqlSessionFactory itself is created by SqlSessionFactoryBuilder, which can create the SqlSessionFactory from XML, annotations, or Java configuration code.
When Mybatis is used with some dependency injection framework (such as Spring or Guice), SqlSession will be created and injected by the dependency injection framework. So you don’t need to use SqlSessionFactoryBuilder or SqlSessionFactory, you can read SqlSession directly. Please refer to Mybatis-Spring or Mybatis-Guice manual for more information.
1.1. The SqlSessionFactoryBuilder is
SqlSessionFactoryBuilder has five build() methods, each of which allows you to create an instance of SqlSessionFactory from a different resource.
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
Copy the code
The first method, the most common, takes an InputStream instance pointing to an XML file (that is, the mybatis-config.xml file discussed earlier). The optional parameters are environment and properties. Environment determines which environment to load, including the data source and transaction manager. Such as:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
...
<dataSource type="POOLED">
...
</environment>
<environment id="production">
<transactionManager type="MANAGED">
...
<dataSource type="JNDI">
...
</environment>
</environments>
Copy the code
If you call the build method with the environment parameter, MyBatis will use the environment configuration. Of course, if you specify an invalid environment, you will receive an error. If you call the build method without the environment parameter, the default environment configuration is used (in the above example, the default environment is specified with default=”development”).
If you call the method that accepts the properties instance, MyBatis will load these properties and provide them for use in the configuration. In most cases, these configuration values can be referenced as ${propName}.
If a property exists in more than one of the following locations, MyBatis will load them in the following order:
- First, read the properties specified in the properties element body;
- Second, it reads the properties specified in the classpath resource or URL of the properties element and overwrites the duplicate properties already specified.
- Finally, the properties passed as method parameters are read, overwriting duplicate properties that have already been loaded from the properties element body and resource or URL properties.
Therefore, properties passed through method parameters have the highest priority, those specified by resource or URL have medium priority, and those specified in the properties element body have the lowest priority.
To summarize, the first four methods are largely the same, but offer different override options that allow you to optionally specify environment and/or properties. Here is an example of creating a SqlSessionFactory from the mybatis-config. XML file:
String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
Copy the code
Note that here we use the Resources utility class, which is in the org.apache.ibatis. IO package. The Resources class, as its name suggests, helps you load resource files from the classpath, file system, or a Web URL. After skimming the source code for the class or viewing the class information in your IDE, you’ll find a set of fairly useful methods. Here is a brief list:
URL getResourceURL(String resource)
URL getResourceURL(ClassLoader loader, String resource)
InputStream getResourceAsStream(String resource)
InputStream getResourceAsStream(ClassLoader loader, String resource)
Properties getResourceAsProperties(String resource)
Properties getResourceAsProperties(ClassLoader loader, String resource)
Reader getResourceAsReader(String resource)
Reader getResourceAsReader(ClassLoader loader, String resource)
File getResourceAsFile(String resource)
File getResourceAsFile(ClassLoader loader, String resource)
InputStream getUrlAsStream(String urlString)
Reader getUrlAsReader(String urlString)
Properties getUrlAsProperties(String urlString)
Class classForName(String className)
Copy the code
The last build method accepts a Configuration instance. The Configuration class contains everything you might care about for an instance of SqlSessionFactory. The Configuration class is useful when checking the Configuration, allowing you to find and manipulate SQL maps (but not recommended when your application starts receiving requests). All of the Configuration switches you’ve learned about are in the Configuration class, but they are exposed as Java apis. Here is a simple example of how to manually configure a Configuration instance and then pass it to the build() method to create an SqlSessionFactory.
DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);
Copy the code
Now you have an SqlSessionFactory that you can use to create an instance of SqlSession.
1.2. SqlSessionFactory
SqlSessionFactory has six methods for creating SqlSession instances. Generally speaking, when choosing one of these methods, you need to consider the following:
- Transaction processing: Do you want to use transaction scope in session scope or auto-commit? (Equivalent to turning off transaction support for many database and/or JDBC drivers)
- Database Connections: Do you want MyBatis to help you get a connection from a configured data source, or do you want to use your own connection?
- Statement execution: Do you want MyBatis to reuse PreparedStatements and/or bulk update statements (including insert and delete statements)?
Based on the above requirements, there are the following overloaded openSession() methods available.
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
Copy the code
The default openSession() method takes no arguments and creates an SqlSession with the following features:
- Transaction scope will be turned on (that is, not commit automatically).
- The Connection object will be retrieved from the DataSource instance configured by the current environment.
- The transaction isolation level will use the default Settings for the driver or data source.
- Preprocessed statements are not reused and updates are not processed in batches.
You already know the difference between these methods from the method signature. Passing true to the optional parameter autoCommit enables automatic commit. To use your own Connection instance, pass a Connection instance to the Connection parameter. Note that we do not provide a way to set Connection and autoCommit at the same time, because MyBatis determines whether to enable autoCommit based on the incoming Connection. For the transaction isolation level, MyBatis USES a Java enum wrapper, called TransactionIsolationLevel, The transaction isolation level supports the five JDBC isolation levels (NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, and SERIALIZABLE) and is consistent with expected behavior.
You may be unfamiliar with the ExecutorType parameter. This enumerated type defines three values:
- Executortype. SIMPLE: This type of actuator has no specific behavior. It creates a new preprocessed statement for each statement execution.
- Executortype. REUSE: This type of executor reuses pre-processed statements.
- Executortype. BATCH: This type of executor executes all update statements in batches. If a SELECT is executed between multiple updates, it separates the updates for easy comprehension if necessary.
There’s another method in SqlSessionFactory that we didn’t mention, and that’s getConfiguration(). This method returns a Configuration instance that you can use at run time to check the Configuration of MyBatis.
1.3. SqlSession
As mentioned earlier, SqlSession is a very powerful class in MyBatis. It contains all methods for executing statements, committing or rolling back transactions, and getting mapper instances.
There are more than 20 methods in the SqlSession class, which are grouped for ease of understanding.
1.3.1. Statement execution method
These methods are used to execute SELECT, INSERT, UPDATE, and DELETE statements defined in THE SQL mapping XML file. You can get a quick idea of what they do by name. Each method takes the ID of a statement and a parameter object, which can be a primitive type (supporting auto-boxing or wrapping classes), JavaBean, POJO, or Map.
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
Copy the code
SelectOne differs from selectList only in that selectOne must return an object or null value. If more than one value is returned, an exception is thrown. If you don’t know how many objects will be returned, use selectList. If you need to see if an object exists, the best way is to query for a count value (0 or 1). SelectMap is slightly special in that it returns one of the object’s properties as a key value and the object as a value value, thereby converting multiple result sets to maptype values. Since not all statements require parameters, these methods have an overloaded form that requires no parameters.
A Cursor returns the same result as a List, except that the Cursor uses iterators to lazily load data.
try (Cursor<MyEntity> entities = session.selectCursor(statement, Param)) {for (MyEntity entity:entities) {// Handle entity}}Copy the code
The values returned by the INSERT, UPDATE, and DELETE methods represent the number of rows affected by the statement.
<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<T> Cursor<T> selectCursor(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)
Copy the code
Finally, there are three advanced versions of the SELECT method, which allow you to limit the number of rows returned or provide custom result processing logic, usually for very large data sets.
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
Copy the code
The RowBounds parameter tells MyBatis to skip a specified number of records and limit the number of results returned. The values of offset and limit of the RowBounds class are passed in only during constructor time and cannot be changed otherwise.
int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);
Copy the code
Database drivers determine query efficiency when records are skipped. For best performance, it is recommended that the ResultSet type be set to SCROLL_SENSITIVE or SCROLL_INSENSITIVE (in other words: do not use FORWARD_ONLY).
The ResultHandler parameter allows you to customize the processing of each row of results. You can add it to a List, create maps and sets, or even discard every returned value, keeping only the calculated statistics. You can do a lot of things with ResultHandler, and this is actually MyBatis’ internal implementation of building the result list.
Starting with version 3.4.6, a ResultHandler passes the CALLABLE statement used in the REFCURSOR output parameter of the stored procedure.
Its interface is simple:
package org.apache.ibatis.session;
public interface ResultHandler<T> {
void handleResult(ResultContext<? extends T> context);
}
Copy the code
The ResultContext parameter allows you to access the result object and the number of objects that have been created. It also provides a stop method that returns a Boolean. You can use this stop method to stop MyBatis from loading more results.
Note the following two limitations when using a ResultHandler:
- When using a method with a ResultHandler parameter, the received data is not cached.
- When using an advanced resultMap, It is likely that MyBatis will need several rows of results to construct an object. If you use a ResultHandler, you may receive objects from an association or collection that have not been fully populated.
1.3.2. Immediate batch update method
BATCH When you set ExecutorType to executorType. BATCH, you can use this method to clear (execute) BATCH update statements cached in JDBC driver classes.
List<BatchResult> flushStatements()
Copy the code
1.3.3. Transaction control method
There are four methods for controlling transaction scope. Of course, these methods won’t work if you’ve set up automatic commit or if you use an external transaction manager. However, if you are using a JDBC transaction manager controlled by a Connection instance, these four methods will come in handy:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
Copy the code
By default, MyBatis does not commit transactions automatically unless it detects that the database has been changed by calling the insert, update, or delete methods. If you do not commit your changes using these methods, you can pass the true value in the COMMIT and rollback method arguments to ensure that the transaction is committed properly. (Note that in auto-commit mode or with an external transaction manager, setting the force value does not apply to the session.) In most cases you don’t need to call rollback() because MyBatis will do the rollback for you if you don’t call commit. However, when you have detailed control over transactions in a session that can be committed or rolled back multiple times, rollback operations come in handy.
MyBatis-Spring and MyBatis-Guice provide declarative transactions, so if you are using Spring or Guice in conjunction with MyBatis, please refer to their manuals for more information.Copy the code
1.3.4. Local caching
Mybatis uses two types of caches: local cache and second level cache.
Whenever a new session is created, MyBatis creates a local cache associated with it. The results of any queries executed in the session are stored in the local cache, so when the same query is executed again with the same parameters, there is no need to actually query the database. The local cache is cleared when changes are made, a transaction commits or rolls back, and a session is closed.
By default, the lifetime of locally cached data is equal to the lifetime of the entire session. Because caching is used to solve circular reference problems and speed up repeated nested queries, it cannot be completely disabled. However, you can use caching only for STATEMENT execution by setting localCacheScope=STATEMENT.
Note that if the localCacheScope is set to SESSION, MyBatis will return a reference to the only object in the local cache for an object. Any changes made to the returned objects (such as list) will affect the contents of the local cache, which in turn will affect the values returned from the cache during this session. Therefore, do not make changes to the objects returned by MyBatis to prevent future problems.
You can always call the following methods to clear the local cache:
void clearCache()
Copy the code
1.3.5. Make sure SqlSession is closed
void close()
Copy the code
For any sessions you open, it is important that you make sure they are closed properly. The best code pattern to ensure proper shutdown looks like this:
SqlSession session = sqlSessionFactory.openSession(); Try (SqlSession session. = sqlSessionFactory openSession ()) {/ / assume that the following three lines of code are you the business logic of the session. The insert (...). ; session.update(...) ; session.delete(...) ; session.commit(); }Copy the code
As with SqlSessionFactory, you can call the getConfiguration method of the currently used SqlSession to get the Configuration instance.
Configuration getConfiguration()
Copy the code
1.3.6. Using a mapper
<T> T getMapper(Class<T> type)
Copy the code
The insert, UPDATE, DELETE, and SELECT methods above are powerful, but also cumbersome, not type-safe, and not very friendly to your IDE and unit tests. Therefore, it is more common to use mapper classes to execute mapping statements.
A mapper class is an interface that simply declares methods that match the SqlSession methods. The following example shows some method signatures and how they are mapped to SqlSession.
public interface AuthorMapper { // (Author) selectOne("selectAuthor",5); Author selectAuthor(int id); // (List<Author>) selectList(" selectAuthors ") List<Author> selectAuthors(); // (Map<Integer,Author>) selectMap("selectAuthors", "id") @MapKey("id") Map<Integer, Author> selectAuthors(); // insert("insertAuthor", author) int insertAuthor(Author author); // updateAuthor("updateAuthor", author) int updateAuthor(Author author); // delete("deleteAuthor",5) int deleteAuthor(int id); }Copy the code
In summary, each mapper method signature should match the associated SqlSession method, and the string parameter ID does not need to match. Instead, the method name matches the ID of the mapping statement.
In addition, the return type must match the expected result type. For a single value, the return type should be the class that returns the value, for multiple values, an array or collection class, or a Cursor. All common types are supported, including primitive types, Maps, POJos, and Javabeans.
- The mapper interface does not need to implement any interface or inherit from any class. As long as the method signature can be used to uniquely identify the corresponding mapping statement, this is ok.
- Mapper interfaces can inherit from other interfaces. When using XML to bind the mapper interface, make sure the statement is in the appropriate namespace. The only limitation is that you cannot have the same method signature in two interfaces that have inherited relationships (this is potentially dangerous and undesirable).
You can pass multiple arguments to a mapper method. In the case of multiple parameters, by default they will be named after param plus their position in the parameter list, for example: #{param1}, #{param2}, etc. If you want to customize the name of the parameter (when there are multiple parameters), you can annotate the parameter with @param (“paramName”).
You can also pass a RowBounds instance to the method to limit the query results.
#{} and ${} in MyBatis
- #{} treats all incoming data as if it were a string, with double quotes around automatically passed data.
- ${} displays the incoming data directly in SQL.
- The #{} approach largely prevents SQL injection.
- ${} does not prevent Sql injection.
- The ${} method is typically used to pass in database objects, such as table names.
- Don’t use ${} if you can use #{}.
3. Two-level caching principle of Mybatis
3.1. Level 1 cache
Mybatis supports caching, but by default it only enables level 1 caching, relative to the same SqlSession, without configuration. Therefore, in the case that the parameters and SQL are exactly the same, we use the same SqlSession object to call a Mapper method, usually only execute SQL once, because after using SelSession for the first query, MyBatis will put it in the cache, if there is no statement to refresh the query in the future, If the cache does not time out, THE SqlSession will fetch the cached data and will not send the SQL to the database again.
3.1.1. Life cycle of level 1 cache
- When MyBatis starts a database session, it creates a new SqlSession object, which contains a new Executor object. A new PerpetualCache object is held in the Executor object; When the session ends, the SqlSession object and its internal Executor and PerpetualCache objects are also released.
- If the SqlSession calls the close() method, the Tier 1 cache PerpetualCache object is freed and the tier 1 cache is not available.
- If a SqlSession calls clearCache(), the data in the PerpetualCache object is cleared, but the object is still available.
- Any UPDATE operation performed in SqlSession (update(), delete(), INSERT ()) clears data from the PerpetualCache object, but the object can continue to be used.
3.1.2. Determine that two queries are exactly the same
Mybatis considers two queries to be identical if the following conditions are exactly the same.
- The statementId passed in.
- The range of results in the result set required when querying.
- Eventually produced by the queries to pass to the JDBC Java, SQL, the SQL Preparedstatement string boundSql. GetSql ().
- Pass to java.sql.Statement the value of the parameter to be set.
3.2. Level 2 cache
MyBatis level 2 cache is the Application level cache, it can improve the efficiency of database query, to improve the performance of the Application. Level 2 cache at SqlSessionFactory level is disabled by default. The open seat of level 2 cache needs to be configured. To implement level 2 cache, MyBatis requires that poJOs returned by MyBatis must be Serializable, that is, Serializable interface.
3.2.1. Configuration and customization of level 2 cache
MyBatis has a powerful built-in transactional query caching mechanism that can be easily configured and customized. To make it more powerful and easy to configure, we have made a number of improvements to the caching implementation in MyBatis 3.
By default, only local session caching is enabled, which only caches data in one session. To enable level 2 caching globally, just add a line to your SQL mapping file:
<cache/>
Copy the code
If we configure a level 2 cache that means:
- All select statements in the mapping statement file will be cached.
- Any INSERT, UPDATE, and DELETE statements in the mapping statement file flush the cache.
- The cache will be retrieved using the default Least Recently Used (LRU) algorithm.
- According to a schedule, such as No Flush Interval (CNFI has No Flush Interval), the cache is not flushed in any chronological order.
- Will the cache store 1024 references to list collections or objects (whatever the query method returns)
- The cache is treated as a read/write cache, meaning that object retrieval is not shared and can be safely modified by the caller without interfering with potential changes made by other callers or threads.
The cache only works on statements in the mapping file where the cache label resides. If you use a mix of Java APIS and XML mapping files, statements in the common interface will not be cached by default. You need to specify the cache scope using the @Cachenamespaceref annotation.
3.2.2. Refresh interval and clearance strategy of level 2 cache
These attributes can be modified through the attributes of the cache element. Such as:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
Copy the code
This more advanced configuration creates a FIFO cache, refreshed every 60 seconds, that can store up to 512 references to result objects or lists, and the returned objects are considered read-only, so modifying them could cause conflicts among callers in different threads.
The cleanup policies available are:
- LRU – Least recently used: Removes the object that has not been used for the longest time.
- FIFO – First in, first out: Objects are removed in the order in which they enter the cache.
- SOFT – SOFT reference: Objects are removed based on garbage collector status and SOFT reference rules.
- WEAK – WEAK references: Objects are removed more aggressively based on garbage collector state and WEAK reference rules.
The default cleanup policy is LRU.
The flushInterval property can be set to any positive integer, and the value should be a reasonable amount of time in milliseconds. The default is no, that is, there is no refresh interval, and the cache is flushed only when the statement is called.
The size attribute can be set to any positive integer, taking into account the size of the object to be cached and the memory resources available in the runtime environment. The default value is 1024.
The readOnly property can be set to true or false. A read-only cache returns the same instance of the cache object to all callers. Therefore, these objects cannot be modified. This provides a significant performance boost. A read-write cache returns (through serialization) a copy of the cached object. It’s slower, but safer, so the default is false.
Level 2 caching is transactional. This means that the cache is updated when the SqlSession completes and commits, or when the INSERT/DELETE /update statement from flushCache=true is completed and rolled back.
4. Mybatis batch processing mechanism details
4.1. Actuator type of Mybatis
There are three built-in executortypes for Mybatis:
- SIMPLE: This type of actuator has no specific behavior. It creates a new preprocessed statement for each statement execution.
- REUSE: An executor of this type reuses preprocessed statements.
- BATCH: This type of executor executes all update statements in batches. If the SELECT is executed in the middle of multiple updates, it separates the multiple update statements for easier understanding if necessary.
Mybatis has three basic Executor actuators:
- SimpleExecutor: Every update or SELECT execution, a Statement object is opened and closed immediately.
- ReuseExecutor: Execute update or select to find the Statement object using the SQL as its key. If the Statement object exists, it will be used. If the Statement object does not exist, it will be created.
- BatchExecutor: Execute update (no SELECT, JDBC batch does not support SELECT), add all SQL to the batch (addBatch()), and wait for uniform execution (executorBatch()), which caches multiple Statement objects, Each Statement object is addBatch() and waits for executorBatch() batch processing to be executed one by one. Same as JDBC.
There are three execution modes of Mybatis:
- In SIMPLE mode, it creates a new preprocessed statement for each statement execution, submitting a single SQL;
- BATCH mode reuses preprocessed statements and executes all updated statements in batches. Obviously, BATCH performance is better.
- BATCH mode also has its own problems. For example, during Insert operations, there is no way to get the incremented ID before the transaction is committed, which is not suitable for the business in some cases.
4.2. Mybatis selects a better batch processing scheme
Insert and Update:
pulic boolean bathInsert(String statementId, List<Map> params) {
SqlSession sqlSession = null;
try {
sqlSession = SqlsessionUtil.getSqlSession();
for (Map param : params) {
sqlSession.insert(statementId, param);
}
sqlSession.commit();
return true;
} catch (Exception e) {
sqlSession.rollback();
e.printStackTrace();
} finally {
SqlsessionUtil.closeSession(sqlSession);
}
return false;
}
Copy the code
Insert and Update:
<insert id="batchInsert">
INSERT INTO table
(
business_id,
element_id,
business_value
)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.business_id, jdbcType=VARCHAR},
#{item.element_id, jdbcType=VARCHAR},
#{item.business_value, jdbcType=VARCHAR})
</foreach>
</insert>
Copy the code
Comparison conclusion:
When the number of set data is more, the efficiency of scheme 2 is significantly improved!
way | Article 50. | Article 100. | Article 500. | Article 1000. |
---|---|---|---|---|
Plan a | 178ms | 266ms | 841ms | 1863ms |
Scheme 2 | 156ms | 211ms | 395ms | 456ms |
Cause analysis:
The main reason for the high execution efficiency is that the log volume (MySQL binlog and InnoDB transaction log) is reduced after the merge, which reduces the amount of data and frequency of the log flush, thus improving the efficiency. By merging SQL statements, you can reduce the number of SQL statement parsing and IO transmission over the network.
Matters needing attention:
- The length of SQL statements is limited. When data is merged in the same SQL, the length must not exceed the limit. You can modify the length by configuring max_allowed_packet.
- Transactions need to be controlled for size, and transactions that are too large may affect the efficiency of their execution. MySQL has an innodb_log_buffer_size configuration item. Exceeding this value will flush InnoDB data to disk, which is inefficient. Therefore, it is better to commit the transaction before the data reaches this value.
The path of Java engineer (Mybatis)