Parse the databaseIdProvider element and configure the database type unique flag generator
An interface named DatabaseIdProvider is defined in mybatis. The function of this interface is to obtain the unique flag of different data sources in mybatis.
DatabaseIdProvider defines two methods, setProperties() method is used to configure custom properties and getDatabaseId() method is used to obtain the databaseId corresponding to the specified data source.
/** * When you need to use the multi database feature, you can implement this interface to build your own DatabaseIdProvider * <p> * @author Eduardo Macarron */ public interface DatabaseIdProvider { // Configure custom properties void setProperties(Properties p); /** * Gets the databaseId of the specified data source * * @param dataSource data source */ String getDatabaseId(DataSource dataSource) throws SQLException; }
In general, the setProperties() method is called before the getDatabaseId() method.
With the databaseId attribute configured in the DatabaseIdProvider and mapping statement, mybatis can execute different SQL statements at runtime according to different data sources.
The filtering logic of mybatis for effective statements is as follows:
MyBatis loads statements with the databaseId property matching the current database and all statements without the databaseId property. If you find the databaseId and the same statement without databaseId are discarded.
With this filtering logic in mind, we have a unit test to get a deeper understanding of DatabaseIdProvider.
In the unit test package of mybatis, there is a test class named MultiDbTest, which is located in org.apache.ibatis.submitted.multidb Under the bag.
> org.apache.ibatis . submitted.multidb The classes and files under the package are mainly used to test the functions of multiple data sources.
A setUp() method is defined in the MultiDbTest. This method will be executed before the unit test runs. It is mainly responsible for initializing SqlSessionFactory objects and initializing database information.
protected static SqlSessionFactory sqlSessionFactory; @BeforeAll public static void setUp() throws Exception { try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/multidb/MultiDbConfig.xml")) { // Initialize sqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } // Initialize database BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), "org/apache/ibatis/submitted/multidb/CreateDB.sql"); }
For initializing SqlSessionFactory objects MultiDbConfig.xml The configuration file is simple:
<configuration> <!-- Configure data source environment--> <environments default="development"> <environment id="development"> <transactionmanager type="JDBC"> <property name="" value="" /> </transactionmanager> <datasource type="UNPOOLED"> <property name="driver" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:mem:multidb" /> <property name="username" value="sa" /> </datasource> </environment> </environments> <!-- to configure DatabaseIdProvider example--> <databaseidprovider type="DB_VENDOR"> <property name="HSQL Database Engine" value="hsql" /> </databaseidprovider> <!-- introduce mappers--> <mappers> <mapper resource="org/apache/ibatis/submitted/multidb/MultiDbMapper.xml" /> </mappers> </configuration>
He configures a data source environment named development, specifies the DatabaseIdProvider instance used to get databaseId, and introduces the mapper file corresponding to the multidbmapper object—— MultiDbMapper.xml .
The initialization script corresponding to the development data source is CreateDB.sql , the script creates two tables, common and hsql, and inserts a piece of data with the same name with id 1 into each table.
create table common ( id int, name varchar(20) ); create table hsql ( id int, name varchar(20) ); insert into common (id, name) values(1, 'common'); insert into hsql (id, name) values(1, 'hsql');
We are going to see the unit test method named shouldExecuteHsqlQuery() in MultiDbTest this time:
@Test public void shouldExecuteHsqlQuery() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // Get the proxy object of MultiDbMapper MultiDbMapper mapper = sqlSession.getMapper(MultiDbMapper.class); // Perform a simple query String answer = mapper.select1(1); assertEquals("hsql", answer); } }
This method is relatively simple, calling the select1() method of MultiDbMapper for a simple query operation.
stay MultiDbMapper.xml There are two definitions of the select1 method in the file:
<mapper namespace="org.apache.ibatis.submitted.multidb.MultiDbMapper"> <!-- Unspecified databaseId,from common Query data in table --> <select id="select1" resulttype="string" parametertype="int"> select name from common where id=#{value} </select> <!-- Specified databaseId The value of is hsql,from hsql Query data in table--> <select id="select1" databaseid="hsql" resulttype="string" parametertype="int"> select name from hsql where id=#{value} </select> </mapper>
In the unit test, we can see that the return value of select1() method of MultiDbMapper is hsql, which means the declaration statement configured with databaseId="hsql" is effective.
According to the logic of mybatis filtering valid declaration statements we have learned before, we can infer that the databaseId corresponding to the current data source environment is hsql.
This hsql uses the alias DB_ Obtained by DatabaseIdProvider instance of vendor.
Mybaits registered alias dB in Configuration's constructor_ Vendor, which refers to the VendorDatabaseIdProvider type.
public Configuration() { ... // Register the provider that processes the database ID typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); ... }
VendorDatabaseIdProvider is the only instance of DatabaseIdProvider registered by default in mybatis. This instance obtains the database product name corresponding to the specified data source based on the getDatabaseProductName() method of the database link metadata DatabaseMetaData object.
Generally speaking, the database product name is a very long string, and the database product names obtained by different versions of the same database may also be inconsistent. Therefore, in order to provide a better user experience, enhance code compatibility and reduce code volume,
The VendorDatabaseIdProvider allows the user to provide a Properties object containing the corresponding relationship between the database product name and databaseId, and infer the appropriate databaseId from this.
private String getDatabaseName(DataSource dataSource) throws SQLException { // Get the name of the current database through a connection String productName = getDatabaseProductName(dataSource); if (this.properties != null) { for (Map.Entry<object, object> property : properties.entrySet()) { // Mainly including the name of the configuration, even if it matches if (productName.contains((String) property.getKey())) { return (String) property.getValue(); } } // no match, return null return null; } // If the user does not transfer properties, the database product name will be used as the current databaseId directly return productName; }
In the current unit test, the databaseId corresponding to the data source containing the string HSQL Database Engine in the database product name is specified as hsql.
<!-- to configure DatabaseIdProvider example--> <databaseidprovider type="DB_VENDOR"> <property name="HSQL Database Engine" value="hsql" /> </databaseidprovider>
We are currently using a database driver that is org.hsqldb.jdbcDriver , so the instance corresponding to DatabaseMetaData is org.hsqldb.jdbc.JDBCDatabaseMetaData.
The getDatabaseProductName() method of this instance is defined as follows:
public String getDatabaseProductName() throws SQLException { return "HSQL Database Engine"; }
The return value of the method just matches the name attribute defined in the property element, so the value value hsql of the property element is the final databaseId.
After understanding the definition and implementation of databaseidprovider, let's take a look at the DTD definition of databaseidprovider element:
<!--ELEMENT databaseIdProvider (property*)--> <!--ATTLIST databaseIdProvider type CDATA #REQUIRED -->
Databaseidprovider has a required attribute type, which specifies the implementation class of databaseidprovider. This parameter can use the alias configured in Mybatis.
Databaseidprovider also allows to define zero or more property subelements, which will be converted into property objects, and further processing will be completed by passing the setProperties() method of databaseidprovider to the implementation class of databaseidprovider.
For example, the corresponding relationship between the database product name and databaseId specified by the user in the VendorDatabaseIdProvider is realized through the property sub element of the databaseIdProvider element:
<databaseidprovider type="DB_VENDOR"> <property name="Apache Derby" value="derby" /> <property name="SQL Server" value="sqlserver" /> <property name="DB2" value="db2" /> <property name="Oracle" value="oracle" /> </databaseidprovider>
The parsing code of the databaseIdProvider element is also relatively simple:
/** * Parsing databaseIdProvider node * * @param context databaseIdProvider node */ private void databaseIdProviderElement(XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null; if (context != null) { String type = context.getStringAttribute("type"); // awful patch to keep backward compatibility if ("VENDOR".equals(type)) { type = "DB_VENDOR"; } // Get the configuration of user-defined database type and databaseId Properties properties = context.getChildrenAsProperties(); // Get databaseIdProvider instance databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance(); // Configure the correspondence between database type and databaseId databaseIdProvider.setProperties(properties); } // Get Environment container Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { // Get the databaseId of the current environment String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); // Synchronize the value of configuration? Databaseid configuration.setDatabaseId(databaseId); } }
First, all property sub elements are parsed to obtain the corresponding Properties object, then the databaseidprovider instance type corresponding to the databaseidprovider type attribute is parsed, and then the corresponding databaseidprovider instance is obtained by reflection operation.
After getting the DatabaseIdProvider instance, call the setProperties() method of the instance to complete the subsequent processing operation of the Properties object.
At this point, the initialization of DatabaseIdProvider instance is completed.
Then obtain the data source in the currently effective Environment object (Configuration Environment) of Mybatis, analyze the corresponding databaseId of the data source, and assign it to the databaseId property of Configuration.
So far, the databaseIdProvider element has been parsed.
Pay attention to me and learn more together
</object,></p>