MyBatis entry series (3)-MyBatis global configuration file detailed 2

7. Object Factory (objectFactory)

Official description : Every time MyBatis creates a new instance of the result object, it will use an ObjectFactory instance to complete the instantiation work. What the default object factory needs to do is to instantiate the target class, either through the default no-parameter construction method, or through the existing parameter mapping to call the construction method with parameters. If you want to override the default behavior of the object factory, you can do so by creating your own object factory.

When creating a result set, MyBatis will use an object factory to complete the creation of this result set instance. By default, MyBatis will use its defined object factory DefaultObjectFactory (org.apache.ibatis.reflection.factory.DefaultObjectFactory) to complete the corresponding work.

Custom object factory case :

  1. Inherit the DefaultObjectFactory to create a custom object factory
public class ExampleObjectFactory extends DefaultObjectFactory {

    // 处理默认构造方法
    @Override
    public <T> T create(Class<T> type) {
        return super.create(type);
    }

    // 处理有参构造方法
    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        return super.create(type, constructorArgTypes, constructorArgs);
    }

    // 判断集合类型参数
    @Override
    public <T> boolean isCollection(Class<T> type) {
        return super.isCollection(type);
    }

    /**
     * mybatis核心配置文件中自配置<objectFactory><property></property></objectFactory>
     * 中的property标签的内容,会在加载配置文件后,设置到Properties对象中
     */
    @Override
    public void setProperties(Properties properties) {
        super.setProperties(properties);
        System.out.println(properties.getProperty("userName"));
    }
}
  1. The global configuration adds an object factory, and its subtag property will be initialized to MyObjectFactory through the setProperties method when the global configuration file is loaded, and used as a global parameter of this class.
    <!--对象工厂-->
    <objectFactory type="org.pearl.mybatis.demo.handler.ExampleObjectFactory">
        <property name="userName" value="zhangsansan"/>
    </objectFactory>
  1. The test found that the properties set by the ObjectFactory were obtained.
Insert picture description here

8. Plugins

Mybatis plug-in is also called interceptor. Mybatis adopts the chain of responsibility model to organize multiple plug-ins (interceptors) through dynamic agents, through which the default behavior of Mybatis can be changed. MyBatis allows you to intercept calls at a certain point during the execution of the mapped statement. By default, MyBatis allows the use of plug-ins to intercept method calls including:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) method of intercepting the executor;
  • ParameterHandler (getParameterObject, setParameters) intercept parameter processing;
  • ResultSetHandler (handleResultSets, handleOutputParameters) intercept the processing of the result set;
  • StatementHandler (prepare, parameterize, batch, update, query) intercepts the processing of Sql syntax construction;

Through the powerful mechanism provided by MyBatis, it is very simple to use the plug-in. You only need to implement the Interceptor interface and specify the signature of the method you want to intercept.

Add interceptor case to query operation :

  1. Write interceptor
@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {


    private Properties properties = new Properties();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget(); //被代理对象
        Method method = invocation.getMethod(); //代理方法
        Object[] args = invocation.getArgs(); //方法参数
        // do something ...... 方法拦截前执行代码块
        Object result = invocation.proceed();
        // do something .......方法拦截后执行代码块
        return result;
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}
  1. Register interceptor
    <!--插件-->
    <plugins>
        <plugin interceptor="org.pearl.mybatis.demo.plugins.ExamplePlugin"></plugin>
    </plugins>

The above plug-in will intercept all "query" method calls in the Executor instance, where Executor is the internal object responsible for executing the underlying mapping statement.

9. Environment configuration (environments)

In MyBatis, the main function of the operating environment is to configure database information. It can configure multiple databases. Generally speaking, only one of them needs to be configured.

It is divided into two configurable elements: transaction manager (transactionManager) and data source (dataSource).

In actual work, in most cases, Spring will be used to manage data source and database transactions.

MyBatis can be configured to adapt to a variety of environments. This mechanism helps to apply SQL mapping to a variety of databases. In reality, there are many reasons to do this. For example, development, testing, and production environments require different configurations; or you want to use the same SQL mapping in multiple production databases with the same Schema. There are many similar usage scenarios.

But remember: Although multiple environments can be configured, only one environment can be selected for each SqlSessionFactory instance.

Therefore, if you want to connect to two databases, you need to create two SqlSessionFactory instances, one for each database. If there are three databases, three instances are needed, and so on, it's easy to remember:

  • Each database corresponds to a SqlSessionFactory instance

Multi-environment switching case demonstration :

  1. Add configuration files, configure multiple environments
    <!--多环境配置-->
    <!--default默认使用的环境ID,此处表示默认使用开发环境配置-->
    <environments default="development">
        <!--开发环境配置-->
        <!--id:指定当前环境的唯一标识-->
        <environment id="development">
            <!--事务管理器的配置(比如:type="JDBC")-->
            <transactionManager type="JDBC"/>
            <!--数据源的配置(比如:type="POOLED")-->
            <dataSource type="POOLED">
                <!--驱动名-->
                <property name="driver" value="${db.driver:com.mysql.cj.jdbc.Driver}"/>
                <!--数据库地址-->
                <property name="url"
                          value="${db.url:jdbc:mysql://127.0.0.1:3306/angel_admin?serverTimezone=Asia/Shanghai}"/>
                <!--用户名-->
                <property name="username" value="${db.username:root}"/>
                <!--密码-->
                <property name="password" value="${db.password:123456}"/>
            </dataSource>
        </environment>
        <!--测试环境配置-->
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver:com.mysql.cj.jdbc.Driver}"/>
                <property name="url"
                          value="${db.url:jdbc:mysql://192.168.17.1:3306/angel_admin?serverTimezone=Asia/Shanghai}"/>
                <property name="username" value="${db.username:root}"/>
                <property name="password" value="${db.password:123456}"/>
            </dataSource>
        </environment>
  1. Create SqlSessionFactory according to different environments and execute SQL.
public class Test002 {
    public static void main(String[] args) throws IOException {
        // 开发环境
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"development");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectOneById(1L);
        System.out.println(user);


        // 测试环境
        String resourceTest = "mybatis-config.xml";
        InputStream inputStreamTest = Resources.getResourceAsStream(resourceTest);
        SqlSessionFactory sqlSessionFactoryTest = new SqlSessionFactoryBuilder().build(inputStreamTest,"test");
        SqlSession sqlSessionTest = sqlSessionFactoryTest.openSession();
        UserMapper mapperTest = sqlSessionTest.getMapper(UserMapper.class);
        User userTest = mapperTest.selectOneById(1L);
        System.out.println(userTest);
    }
}

Transaction Manager (transactionManager)

There are two types of transaction managers in MyBatis (that is, type="[JDBC|MANAGED]"):

  • JDBC-This configuration directly uses the commit and rollback facilities of JDBC, which relies on the connection obtained from the data source to manage the scope of the transaction.
  • MANAGED-This configuration does almost nothing. It never commits or rolls back a connection, but lets the container manage the entire life cycle of the transaction (such as the context of a JEE application server). By default it will close the connection. However, some containers do not want the connection to be closed, so you need to set the closeConnection property to false to prevent the default closing behavior.

If you are using Spring + MyBatis, there is no need to configure the transaction manager, because the Spring module will use its own manager to override the previous configuration.

Neither transaction manager type requires any properties to be set. They are actually type aliases. In other words, you can replace them with the fully qualified name or type alias of the TransactionFactory interface implementation class.

public interface TransactionFactory {
  default void setProperties(Properties props) { // 从 3.5.2 开始,该方法为默认方法
    // 空实现
  }
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

After the transaction manager is instantiated, all the properties configured in XML will be passed to the setProperties() method. Your implementation also needs to create an implementation class of the Transaction interface. This interface is also very simple. Using these two interfaces, you can completely customize MyBatis's handling of transactions.

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

Data Source (dataSource)

The dataSource element uses the standard JDBC data source interface to configure the resources of the JDBC connection object.

  • Most MyBatis applications will configure the data source according to the example in the example. Although the data source configuration is optional, if you want to enable the lazy loading feature, you must configure the data source.

There are three built-in data source types (that is, type="[UNPOOLED|POOLED|JNDI]"):

UNPOOLED -The implementation of this data source opens and closes the connection every time it is requested. Although a bit slow, it is a good choice for simple applications that do not require high database connection availability. Performance depends on the database used. For some databases, the use of connection pools is not important, and this configuration is very suitable for this situation. The data source of UNPOOLED type only needs to configure the following 5 attributes:

  • driver – This is the fully qualified name of the Java class of the JDBC driver (not the data source class that may be included in the JDBC driver).
  • url-This is the JDBC URL address of the database.
  • username-the username for logging in to the database.
  • password-the password for logging in to the database.
  • defaultTransactionIsolationLevel-The default connection transaction isolation level.
  • defaultNetworkTimeout-the default network timeout (unit: milliseconds) to wait for the database operation to complete. Check the API documentation of java.sql.Connection#setNetworkTimeout() for more information.

As an option, you can also pass attributes to the database driver. Just add the "driver." prefix to the attribute name, for example:

  • driver.encoding=UTF8

This will pass the encoding property whose value is UTF8 to the database driver through the DriverManager.getConnection(url, driverProperties) method.

POOLED -The realization of this data source uses the concept of "pool" to organize JDBC connection objects, avoiding the initialization and authentication time necessary to create a new connection instance. This processing method is very popular and enables concurrent Web applications to respond quickly to requests.

In addition to the attributes under UNPOOLED mentioned above, there are more attributes to configure the data source of POOLED:

  • poolMaximumActiveConnections-the number of active (in use) connections that can exist at any time, default value: 10
  • poolMaximumIdleConnections-the number of idle connections that may exist at any time.
  • poolMaximumCheckoutTime-before being forced to return, the pool connection is checked out (checked out) time, default value: 20000 milliseconds (ie 20 seconds)
    poolTimeToWait-this is a low-level setting, if it takes a long time to get the connection, the connection pool It will print the status log and try to obtain a connection again (to avoid continuous failure and not print the log in case of misconfiguration), the default value: 20000 milliseconds (ie 20 seconds).
  • poolMaximumLocalBadConnectionTolerance-This is a low-level setting about the tolerance of bad connections, which acts on every thread that tries to get a connection from the buffer pool. If this thread gets a bad connection, then this data source allows this thread to try to get a new connection again, but the number of retry attempts should not exceed
  • The sum of poolMaximumIdleConnections and poolMaximumLocalBadConnectionTolerance. Default value: 3 (added in 3.4.5)
    poolPingQuery-a detection query sent to the database to check whether the connection is working properly and is ready to accept requests. The default is "NO PING QUERY SET", which will cause most database drivers to return appropriate error messages when they fail.
  • poolPingEnabled-Whether to enable detection query. If enabled, you need to set the poolPingQuery property to an executable SQL statement (preferably a very fast SQL statement), the default value: false.
  • poolPingConnectionsNotUsedFor-Configure the frequency of poolPingQuery. It can be set to be the same as the database connection timeout period to avoid unnecessary detection. Default value: 0 (that is, all connections are detected every moment-of course only applicable when-poolPingEnabled is true).

JNDI -This data source is implemented in order to be used in containers such as EJB or application servers. The container can configure the data source centrally or externally, and then place a JNDI context data source reference. This data source configuration requires only two attributes:

  • initial_context – This attribute is used to find the context in the InitialContext (ie, initialContext.lookup(initial_context)). This is an optional attribute. If omitted, the data_source attribute will be directly searched from the InitialContext.
  • data_source – This is the context path that refers to the location of the data source instance. When the initial_context configuration is provided, it will be searched in the context returned by it, and when it is not provided, it will be searched directly in the InitialContext.

Similar to other data source configuration, attributes can be passed directly to InitialContext by adding the prefix "env.". such as:

  • env.encoding=UTF8

This will pass the encoding property of UTF8 to its constructor when the InitialContext is instantiated.

The third-party data source can be implemented by implementing the interface org.apache.ibatis.datasource.DataSourceFactory:

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory can be used as a parent class to build a new data source adapter. For example, the following code is necessary to insert a C3P0 data source:

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

  public C3P0DataSourceFactory() {
    this.dataSource = new ComboPooledDataSource();
  }
}

10. Database vendor identification (databaseIdProvider)

There are many types of databases. Although most of them are based on the SQL standard, each database has its own dialect or function.

Mybatis also supports multiple databases. You only need to tell the framework what database is used. MyBatis can execute different statements according to different database vendors.

Adapt to Mysql and Oracle database cases :

  1. Add configuration
    <!--数据库厂商标识-->
    <!--DB_VENDOR: 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义-->
    <databaseIdProvider type="DB_VENDOR">
        <!--添加两个数据库厂商别名-->
        <!--name:数据库厂商标识-->
        <!--value:为标识起一个别名,方便SQL语句使用databaseId属性引用-->
        <property name="Oracle" value="oracle"/>
        <property name="MySQL" value="mysql"/>
    </databaseIdProvider>

  1. Specify the databaseId in the xml as the database of the response
<mapper namespace="org.pearl.mybatis.demo.dao.UserMapper">
    <select id="selectOneById" resultType="org.pearl.mybatis.demo.pojo.entity.User" databaseId="mysql">
    select * from base_user where user_id = #{id}
  </select>
</mapper>
  1. Test, check the current database vendor
public class Test001 {
    public static void main(String[] args) throws IOException {
        // 根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        String databaseId = sqlSessionFactory.getConfiguration().getDatabaseId();
        System.out.println(databaseId+"数据库");
        }
}
Insert picture description here

Matching rules :

  • If the databaseIdProvider tag is not configured, then databaseId=null
  • If the databaseIdProvider tag is configured, use the name of the tag configuration to match the database information, and set databaseId= the value specified by the configuration on the match, otherwise it will still be null
  • If the databaseId is not null, he will only find the SQL statement that configures the databaseId
  • MyBatis will load all statements without the databaseId attribute and with the databaseId attribute matching the current database. If the same statement with databaseId and without databaseId is found at the same time, the latter will be discarded.

11. Mappers (mappers)

Now that the behavior of MyBatis has been configured by the above elements, we are now going to define the SQL mapping statement. But first, we need to tell MyBatis where to find these sentences. In terms of automatically finding resources, Java does not provide a good solution, so the best way is to directly tell MyBatis where to find the mapping file. You can use resource references relative to the classpath, or fully qualified resource locators (including URLs in the form of file:///), or class names and package names. E.g:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>