Generally speaking, modifying the source code of the framework is extremely risky, and do not modify it unless it is absolutely necessary. But today I carefully reconstructed the SqlSessionFactoryBean class officially provided by Mybatis, which is integrated with Spring. First, it has a trial and error mentality, and second, it does have realistic needs.
Let me explain two points first:
Generally speaking, refactoring refers to optimizing the code without changing the function, but the refactoring mentioned in this article also includes adding functions.
The main jar packages (versions) used in this article: spring-*-4.3.3.RELEASE.jar, mybatis-3.4.1.jar, mybatis-spring-1.3.0.jar
Let’s start with the integration of Mybatis and Spring.
1. Integrate Mybatis and Spring
<bean id="sqlSessionFactory" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis/mybatis-config.xml"><property name="mapperLocations"><array><value>classpath*:**/*.sqlmapper.xml</value></array></property></bean>
The key class of integration is org.mybatis.spring.SqlSessionFactoryBean, which is a factory bean used to generate Mybatis global session factory SqlSessionFactory (that is, the factory bean that generates the session factory), and SqlSessionFactory is used to generate session SqlSession object (SqlSessionFactory is equivalent to DataSource, SqlSession is equivalent to Connection).
Where properties (configured using p namespace or property child element):
dataSource is a data source, which can be configured using DBCP, C3P0, Druid, jndi-lookup and other methods.
configLocation is a global configuration of the Mybatis engine, used to modify the behavior of Mybatis.
mapperLocations is the SqlMapper script configuration file (mode) that Mybatis needs to load.
Of course, there are many other attributes, so I won’t give an example here.
2. Why reconstruct
1. Source code optimization
The function of SqlSessionFactoryBean is to generate SqlSessionFactory. Let's take a look at this method (SqlSessionFactoryBean.java line 384-538): /*** Build a {@code SqlSessionFactory} instance.** The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a* {@code SqlSessionFactory} instance based on an Reader.* Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).** @return SqlSessionFactory* @throws IOException if loading the config file failed*/protected SqlSessionFactory buildSqlSessionFactory() throws IOException {Configuration configuration;XMLConfigBuilder xmlConfigBuilder = null;if (this.configuration != null) {configuration = this.configuration;if (configuration.getVariables() == null) {configuration.setVariables(this.configurationProperties);} else if (this.configurationProperties != null) {configuration.getVariables().putAll(this.configRegulation);}} else if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);configuration = xmlConfigBuilder.getConfiguration();} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");}configuration = new Configuration();configuration.setVariables(this.configurationProperties);}if (this.objectFactory != null) {configuration.setObjectFactory(this.objectFactory);}if (this.objectWrapperFactory != null) {configuration.setObjectWrapperFactory(this.objectWrapperFactory);}if (this.vfs != null) {configuration.setVfsImpl(this.vfs);}if (hasLength(this.typeAliasesPackage)) {String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) {configuration.getTypeAliasRegistry().registerAliases(packageToScan,typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");}}}if (!isEmpty(this.typeAliases)) {for (Class<?> typeAlias: this.typeAliases) {configuration.getTypeAliasRegistry().registerAlias(typeAlias);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered type alias: '" + typeAlias + "'");}}}if (!isEmpty(this.plugins)) {for (Interceptor plugin : this.plugins) {configuration.addInterceptor(plugin);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered plugin: '" + plugin + "'");}}}if (hasLength(this.typeHandlersPackage)) {String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) {configuration.getTypeHandlerRegistry().register(packageToScan);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");}}}}if (!isEmpty(this.typeHandlers)) {for (TypeHandler<?> typeHandler : this.typeHandlers) {configuration.getTypeHandlerRegistry().register(typeHandler);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered type handler: '" + typeHandler + "'");}}}if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmlstry {configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException e) {throw new NestedIOException("Failed getting a databaseId", e);}}if (this.cache != null) {configuration.addCache(this.cache);}if (xmlConfigBuilder != null) {try {xmlConfigBuilder.parse();if (LOGGER.isDebugEnabled()) {LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");}} catch (Exception ex) {throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);} finally {ErrorContext.instance().reset();}}if (this.transactionFactory == null) {this.transactionFactory = new SpringManagedTransactionFactory();}configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));if (!isEmpty(this.mapperLocations)) {for (Resource mapperLocation: this.mapperLocations) {if (mapperLocation == null) {continue;}try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),configuration, mapperLocation.toString(), configuration.getSqlFragments());xmlMapperBuilder.parse();} catch (Exception e) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);} finally {ErrorContext.instance().reset();}if (LOGGER.isDebugEnabled()) {LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");}}} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");}} return this.sqlSessionFactoryBuilder.build(configuration);}Although Mybatis is an excellent persistence layer framework, to be honest, this code is indeed not very good and has a lot of room for reconstruction and optimization.
2. Functional expansion
(1) Use Schema to verify SqlMapper
<!-- DTD mode--><?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="org.dysd.dao.mybatis.config.IExampleDao"></mapper><!-- SCHEMA mode--><?xml version="1.0" encoding="UTF-8" ?><mapper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://dysd.org/schema/sqlmapper"xsi:schemaLocation="http://dysd.org/schema/sqlmapper http://dysd.org/schema/sqlmapper.xsd"namespace="org.dysd.dao.mybatis.config.IExampleDao"></mapper>
At first glance, using Schema is more complicated, but if combined with the IDE, the automatic prompts of using Schema are more friendly and the verification information is clearer. At the same time, it also opens a window for other developers to allow them to customize the namespace based on the existing namespace, such as introducing the <ognl> tag, using OGNL expressions to configure SQL statements, etc.
(2) Customize configuration. SqlSessionFactoryBean has provided more parameters for custom configuration, but it is still possible to require more personalized settings, such as:
A. Set the default result type. For <select> elements that do not set resultType and resultMap, you can set the default return type to Map after parsing, thereby simplifying the configuration of SqlMapper.
<!--Before simplified--><select id="select" resultType="map">SELECT * FROM TABLE_NAME WHERE FIELD1 = #{field1, jdbcType=VARCHAR} </select><!--After simplified--><select id="select">SELECT * FROM TABLE_NAME WHERE FIELD1 = #{field1, jdbcType=VARCHAR} </select>B. Extend Mybatis' original parameter analysis. The native parsing implementation is DefaultParameterHandler. This implementation can be inherited and extended. For example, for the property expression prefixed by spel:, use SpEL to evaluate the value.
(3) For other extensions, please refer to the author's previous blog about Mybatis extension
3. Feasibility of reconstruction
(1) In terms of the scope of the code influence
Below is the inheritance structure of SqlSessionFactoryBean
From this we can see that the SqlSessionFactoryBean inheritance system is not complicated, and it does not inherit other parent classes. It only implements three interfaces in Spring (the EventListener in JDK is just a logo). In addition, SqlSessionFactoryBean is aimed at the end development user, with no subclasses and no other classes calling it, so it is very small in terms of the scope of the code impact.
(2) In the reconstruction implementation, you can create a new SchemaSqlSessionFactoryBean, and then the code completely copy the SqlSessionFactoryBean at the beginning, modify the package name and class name, and then use this as the basis for reconstruction. This is relatively simple.
(3) In integrated applications, you only need to modify the class attributes in the integrated configuration and spring.
The above is the refactoring of the SqlSessionFactoryBean integrated with Mybatis and Spring introduced to you. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support to Wulin.com website!