Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

The Mybatis interface does not implement why classes can perform additions, deletions, modifications and queries.

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

Mybatis interface does not implement why classes can be added, deleted, changed and checked, many novices are not very clear about this, in order to help you solve this problem, the following editor will explain in detail for you, people with this need can come to learn, I hope you can gain something.

Introduction to the preface

MyBatis is a very excellent persistence layer framework, which is much more refined than IBatis. At the same time, it also provides many extension points, such as the most commonly used plug-ins; language drivers, executors, object factories, object wrapper factories, and so on. So, if you want to be a deep man (programmer), you should learn the source code of this open source framework so that you can better understand the essence of design patterns (interview?) . In fact, it is possible that ordinary business development does not delve into the source code of each framework, and it is often heard that code can be developed even if it does not. But! Everyone's goals are different, like; the code is good and the salary is low (how can you see your job without bug!) Oh, good! In order to change the world, start the analysis!

Before the analysis, let's ask a question to see if you are suitable for the source code.

@ Test public void test () {BB = new B (); b.scan (); / / what is my output? } static class A {public void scan () {doScan ();} protected void doScan () {System.out.println ("A.doScan");}} static class B extends A {@ Override protected void doScan () {System.out.println ("B.doScan");}}

In fact, whether your answer is right or wrong, it does not affect your analysis of the source code. However, there are often a lot of design patterns and development skills in some frameworks, and if the above code is hardly used in your usual development, then you may be developing more CRUD functions for the time being (don't panic, I also wrote PHP).

Next, first analyze the execution process of the source code when Mybatis is used alone, and then analyze the Mybatis+Spring integration source code, good! Start.

II. Case project

For better analysis, we create a case project of Mybaits, which includes the use of Mybatis alone and Mybatis+Spring integration.

Itstack-demo-mybatis └── src ├── main │ ├── java │ │ └── org.itstack.demo │ │ ├── dao │ ├── ISchool.java │ └── IUserDao.java │ interfaces │ │ ├── School.java │ │ └── User.java │ ├── resources │ │ ├── mapper │ ├── School_Mapper.xml │ └── User_Mapper.xml │ │ ├── props │ └── jdbc.properties │ │ ├── spring │ ├── mybatis-config-datasource.xml │ └── spring-config-datasource.xml │ │ ├── logback.xml │ │ ├── mybatis-config.xml │ │ └── spring-config .xml │ └── webapp │ └── WEB-INF └── test └── java └── org.itstack.demo.test ├── MybatisApiTest.java └── SpringApiTest.java

III. Environmental configuration

JDK1.8

IDEA 2019.3.1

Mybatis 3.4.6 {slightly different source code of different versions and bug fix}

Mybatis-spring 1.3.2 {the following source code analysis will say the line number, note that there may be differences between different versions}

Fourth, (mybatis) source code analysis

Org.mybatis mybatis 3.4.6

The whole source code of Mybatis is still very large, the following mainly collates and analyzes some of the core contents, so as to facilitate the subsequent analysis of the source code of the integration of Mybatis and Spring. It briefly includes container initialization, configuration file parsing, Mapper loading and dynamic proxy.

1. Start with a simple case.

The best way to learn the Mybatis source code must be to enter from a simple point, rather than starting with Spring integration. SqlSessionFactory is the core instance object of the whole Mybatis, and the instance of SqlSessionFactory object is obtained through SqlSessionFactoryBuilder object. The SqlSessionFactoryBuilder object can load configuration information from the XML configuration file and then create a SqlSessionFactory. Examples are as follows:

MybatisApiTest.java

Public class MybatisApiTest {@ Test public void test_queryUserInfoById () {String resource = "spring/mybatis-config-datasource.xml"; Reader reader; try {reader = Resources.getResourceAsReader (resource); SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder () .build (reader); SqlSession session = sqlMapper.openSession () Try {User user = session.selectOne ("org.itstack.demo.dao.IUserDao.queryUserInfoById", 1L); System.out.println (JSON.toJSONString (user));} finally {session.close (); reader.close () }} catch (IOException e) {e.printStackTrace ();}

Dao/IUserDao.java

Public interface IUserDao {User queryUserInfoById (Long id);}

Spring/mybatis-config-datasource.xml

If all goes well, the result will be as follows:

{"age": 18, "createTime": 1571376957000, "id": 1, "name": "Huahua", "updateTime": 1571376957000}

As you can see from the code block above, the core code; SqlSessionFactoryBuilder (). Build (reader), is responsible for loading, parsing, building, and so on, the Mybatis configuration file, until finally it can be executed through SqlSession and the result is returned.

two。 Container initialization

As you can see from the above code, SqlSessionFactory is created through the SqlSessionFactoryBuilder factory class, rather than using the constructor directly. The configuration file loading and initialization process for the container is as follows:

Process core class

SqlSessionFactoryBuilder

XMLConfigBuilder

XPathParser

Configuration

SqlSessionFactoryBuilder.java

Public class SqlSessionFactoryBuilder {public SqlSessionFactory build (Reader reader) {return build (reader, null, null);} public SqlSessionFactory build (Reader reader, String environment) {return build (reader, environment, null);} public SqlSessionFactory build (Reader reader, Properties properties) {return build (reader, null, properties);} public SqlSessionFactory build (Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder (reader, environment, properties) Return build (parser.parse ());} catch (Exception e) {throw ExceptionFactory.wrapException ("Error building SqlSession.", e);} finally {ErrorContext.instance (). Reset (); try {reader.close ();} catch (IOException e) {/ / Intentionally ignore. Prefer previous error. }} public SqlSessionFactory build (InputStream inputStream) {return build (inputStream, null, null);} public SqlSessionFactory build (InputStream inputStream, String environment) {return build (inputStream, environment, null);} public SqlSessionFactory build (InputStream inputStream, Properties properties) {return build (inputStream, null, properties) } public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder (inputStream, environment, properties); return build (parser.parse ());} catch (Exception e) {throw ExceptionFactory.wrapException ("Error building SqlSession.", e);} finally {ErrorContext.instance (). Reset (); try {inputStream.close () } catch (IOException e) {/ / Intentionally ignore. Prefer previous error. Public SqlSessionFactory build (Configuration config) {return new DefaultSqlSessionFactory (config);}}

As you can see from the source code above, SqlSessionFactory provides three ways for build to build objects

Byte stream: java.io.InputStream

Character stream: java.io.Reader

Configuration class: org.apache.ibatis.session.Configuration

Then, both the byte stream and the character stream will create the profile parsing class: XMLConfigBuilder, generate Configuration through parser.parse (), and finally call the configuration class build method to generate SqlSessionFactory.

XMLConfigBuilder.java

Public class XMLConfigBuilder extends BaseBuilder {private boolean parsed; private final XPathParser parser; private String environment; private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory ();... Public XMLConfigBuilder (Reader reader, String environment, Properties props) {this (new XPathParser (reader, true, props, new XMLMapperEntityResolver ()), environment, props);}.

XMLConfigBuilder entrusts XPathParser to load and parse XML files, and finally uses javax.xml included in JDK for XML parsing (XPath).

XPathParser (Reader reader, boolean validation, Properties variables, EntityResolver entityResolver)

1. Reader: use character streams to create new input sources for reading XML files

2. Validation: whether to perform DTD verification

3. Variables: attribute configuration information

4. EntityResolver:Mybatis hard-coded new XMLMapperEntityResolver () provides XML default parser

XMLMapperEntityResolver.java

Public class XMLMapperEntityResolver implements EntityResolver {private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd"; private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd"; private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd"; private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd"; private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd" Private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd"; / * Converts a public DTD into a local one * * @ param publicId The public id that is what comes after "PUBLIC" * @ param systemId The system id that is what comes after the public id. * @ return The InputSource for the DTD * * @ throws org.xml.sax.SAXException If anything goes wrong * / @ Override public InputSource resolveEntity (String publicId, String systemId) throws SAXException {try {if (systemId! = null) {String lowerCaseSystemId = systemId.toLowerCase (Locale.ENGLISH) If (lowerCaseSystemId.contains (MYBATIS_CONFIG_SYSTEM) | | lowerCaseSystemId.contains (IBATIS_CONFIG_SYSTEM)) {return getInputSource (MYBATIS_CONFIG_DTD, publicId, systemId);} else if (lowerCaseSystemId.contains (MYBATIS_MAPPER_SYSTEM) | | lowerCaseSystemId.contains (IBATIS_MAPPER_SYSTEM)) {return getInputSource (MYBATIS_MAPPER_DTD, publicId, systemId);} return null } catch (Exception e) {throw new SAXException (e.toString ());}} private InputSource getInputSource (String path, String publicId, String systemId) {InputSource source = null; if (path! = null) {try {InputStream in = Resources.getResourceAsStream (path); source = new InputSource (in); source.setPublicId (publicId); source.setSystemId (systemId) } catch (IOException e) {/ / ignore, null is ok}} return source;}}

Mybatis relies on dtd files for parsing, in which ibatis-3-config.dtd is mainly used for compatibility purposes

The call to getInputSource (String path, String publicId, String systemId) has two parameters, publicId (public identifier) and systemId (system identifier).

XPathParser.java

Public XPathParser (Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {commonConstructor (validation, variables, entityResolver); this.document = createDocument (new InputSource (reader));} private void commonConstructor (boolean validation, Properties variables, EntityResolver entityResolver) {this.validation = validation; this.entityResolver = entityResolver; this.variables = variables; XPathFactory factory = XPathFactory.newInstance (); this.xpath = factory.newXPath () } private Document createDocument (InputSource inputSource) {/ / important: this must only be called AFTER common constructor try {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance (); factory.setValidating (validation); factory.setNamespaceAware (false); factory.setIgnoringComments (true); factory.setIgnoringElementContentWhitespace (false); factory.setCoalescing (false); factory.setExpandEntityReferences (true); DocumentBuilder builder = factory.newDocumentBuilder (); builder.setEntityResolver (entityResolver) Builder.setErrorHandler (new ErrorHandler () {@ Override public void error (SAXParseException exception) throws SAXException {throw exception;} @ Override public void fatalError (SAXParseException exception) throws SAXException {throw exception;} @ Override public void warning (SAXParseException exception) throws SAXException {}}); return builder.parse (inputSource) } catch (Exception e) {throw new BuilderException ("Error creating document instance. Cause: "+ e, e);}}

From top to bottom, you can see that the main purpose is to create a Mybatis document parser, and finally return Document according to builder.parse (inputSource)

After you get the XPathParser instance, then call the method: this (reader, true, props, new XMLMapperEntityResolver ()), environment, props)

XMLConfigBuilder.this (new XPathParser (reader, true, props, new XMLMapperEntityResolver (), environment, props); private XMLConfigBuilder (XPathParser parser, String environment, Properties props) {super (new Configuration ()); ErrorContext.instance (). Resource ("SQL Mapper Configuration"); this.configuration.setVariables (props); this.parsed = false; this.environment = environment; this.parser = parser;}

3. Where the constructor of the parent class is called

Public abstract class BaseBuilder {protected final Configuration configuration; protected final TypeAliasRegistry typeAliasRegistry; protected final TypeHandlerRegistry typeHandlerRegistry; public BaseBuilder (Configuration configuration) {this.configuration = configuration; thisthis.typeAliasRegistry = this.configuration.getTypeAliasRegistry (); thisthis.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry ();}}

4. After the XMLConfigBuilder is created, sqlSessionFactoryBuild calls parser.parse () to create the Configuration

Public class XMLConfigBuilder extends BaseBuilder {public Configuration parse () {if (parsed) {throw new BuilderException ("Each XMLConfigBuilder can only be used once.");} parsed = true; parseConfiguration (parser.evalNode ("/ configuration"); return configuration;}}

3. Configuration file parsing

This section is the core of the parsing and loading of the entire XML file, including

Attribute parsing propertiesElement

Load settings Node settingsAsProperties

Loading Custom VFS loadCustomVfs

Resolve type alias typeAliasesElement

Load plug-in pluginElement

Load object Factory objectFactoryElement

Create an object wrapper factory objectWrapperFactoryElement

Load reflection Factory reflectorFactoryElement

Element setting settingsElement

Load environment configuration environmentsElement

Database Vendor ID load databaseIdProviderElement

Load type processor typeHandlerElement

(core) load mapper file mapperElement

ParseConfiguration (parser.evalNode ("/ configuration")); private void parseConfiguration (XNode root) {try {/ / issue # 117 read properties first / / attribute parsing propertiesElement propertiesElement (root.evalNode ("properties")); / / load settings node settingsAsProperties Properties settings = settingsAsProperties (root.evalNode ("settings")); / / load custom VFS loadCustomVfs loadCustomVfs (settings) / / parse type alias typeAliasesElement typeAliasesElement (root.evalNode ("typeAliases")); / / load plug-in pluginElement pluginElement (root.evalNode ("plugins")); / / load object factory objectFactoryElement objectFactoryElement (root.evalNode ("objectFactory")); / / create object wrapper factory objectWrapperFactoryElement objectWrapperFactoryElement (root.evalNode ("objectWrapperFactory")) / / load reflection factory reflectorFactoryElement reflectorFactoryElement (root.evalNode ("reflectorFactory")); / / element setting settingsElement (settings); / / read it after objectFactory and objectWrapperFactory issue # 631 / / load environment configuration environmentsElement environmentsElement (root.evalNode ("environments")); / / Database vendor ID load databaseIdProviderElement databaseIdProviderElement (root.evalNode ("databaseIdProvider")) / / load type processor typeHandlerElement typeHandlerElement (root.evalNode ("typeHandlers")); / / load mapper file mapperElement mapperElement (root.evalNode ("mappers"));} catch (Exception e) {throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: "+ e, e);}}

All the bottom layers of root.evalNode () call the XML DOM method: Object evaluate (String expression, Object item, QName returnType), expression parameter expression, and return the final node content through XObject resultObject = eval (expression, item). You can refer to http://mybatis.org/dtd/mybati..., as follows

There are 11 configuration files in the mybatis-3-config.dtd definition file, as follows

Properties?

Settings?

TypeAliases?

TypeHandlers?

ObjectFactory?

ObjectWrapperFactory?

ReflectorFactory?

Plugins?

Environments?

DatabaseIdProvider?

Mappers?

Each of the above configurations is optional. The final configuration content will be saved to org.apache.ibatis.session.Configuration, as follows

Public class Configuration {protected Environment environment; / / allows paging (RowBounds) in nested statements. Set to false if allowed. The default is false protected boolean safeRowBoundsEnabled; / / to allow paging (ResultHandler) in nested statements. Set to false if allowed. Protected boolean safeResultHandlerEnabled = true; / / whether automatic hump naming rule (camel case) mapping is enabled, that is, a similar mapping from the classic database column name A_COLUMN to the classic Java attribute name aColumn. Default false protected boolean mapUnderscoreToCamelCase; / / when on, any method call will load all the properties of the object. Otherwise, each property is loaded on demand. The default value false (true in ≤ 3.4.1) protected boolean aggressiveLazyLoading; / / allows multiple result sets to be returned by a single statement (compatible driver is required). Protected boolean multipleResultSetsEnabled = true; / / allows JDBC to support automatic generation of primary keys, which requires driver compatibility. This is the switch that gets the mysql self-incrementing primary key / oracle sequence when insert. Note: generally speaking, this is the desired result, and the default value should be true. Protected boolean useGeneratedKeys; / / use column tags instead of column names. Generally speaking, this is the desired result: protected boolean useColumnLabel = true; / / whether caching is enabled or not {default is on, maybe this is your interview question} protected boolean cacheEnabled = true / / specifies whether to call the setter method of the mapping object (put when the map object is map) when the result set value is null, which is useful when there is a Map.keySet () dependency or null value initialization. Protected boolean callSettersOnNulls; / / allows you to use the name in the method signature as the statement parameter name. In order to use this feature, your project must be compiled in Java 8 with the-parameters option. (starting from 3.4.1) protected boolean useActualParamName = true; / / when all columns of the returned row are empty, MyBatis returns null by default. When this setting is enabled, MyBatis returns an empty instance. Note that it also applies to nested result sets i.e. Collectioin and association Note: this should be split into two parameters, one for the result set and one for a single record. Generally speaking, we would want the result set not to be null, and the single record would still be the prefix that null protected boolean returnInstanceForEmptyRow; / / specifies MyBatis to add to the log name. Protected String logPrefix; / / specifies the specific implementation of the log used by MyBatis. If it is not specified, it will be found automatically. It is generally recommended to specify slf4j or log4j protected Class mapperInterface = Resources.classForName (mapperClass); configuration.addMapper (mapperInterface);} else {throw new BuilderException ("A mapper element may only specify a url, resource or class, but not more than one.");}}

Mybatis provides two methods to configure Mapper. The first is to use package automatic search mode, so that all interfaces under the specified package will be registered as mapper, which is also a common way in Spring, such as:

The other is to specify Mapper explicitly, which can be subdivided by resource, url, or class, such as

4. Mapper loading and dynamic proxy

Automatically search and load through package to generate the corresponding mapper proxy classes, code blocks and processes, as follows

Private void mapperElement (XNode parent) throws Exception {if (parent! = null) {for (XNode child: parent.getChildren ()) {if ("package" .equals (child.getName () {String mapperPackage = child.getStringAttribute ("name"); configuration.addMappers (mapperPackage);} else {.}

Mapper is loaded into the process of generating proxy objects, and the main core classes include

XMLConfigBuilder

Configuration

MapperRegistry

MapperAnnotationBuilder

MapperProxyFactory

MapperRegistry.java

Parsing loading Mapper

The search classpath provided by the public void addMappers (String packageName, Class superType) {/ / mybatis framework specifies the package and the classes in the sub-package that meet the criteria (annotated or inherited from a class / interface). The loader returned by Thread.currentThread () .getContextClassLoader () is used by default, which is the same as the utility class of spring. ResolverUtil > (); / / unconditionally load all classes because the caller passes Object.class as the parent class, which also leaves room for the later specified mapper interface resolverUtil.find (new ResolverUtil.IsA (superType), packageName); / / all matching calss are stored in the ResolverUtil.matches field Set, MapperProxyFactory > knownMappers = new HashMap > ()

MapperProxyFactory.java

Public class MapperProxyFactory {private final Class mapperInterface; private final Map methodCache = new ConcurrentHashMap (); public MapperProxyFactory (Class mapperInterface) {this.mapperInterface = mapperInterface;} public Class getMapperInterface () {return mapperInterface;} public Map getMethodCache () {return methodCache;} @ SuppressWarnings ("unchecked") protected T newInstance (MapperProxy mapperProxy) {return (T) Proxy.newProxyInstance (mapperInterface.getClassLoader (), new Class [] {mapperInterface}, mapperProxy) } public T newInstance (SqlSession sqlSession) {final MapperProxy mapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance (mapperProxy);}}

As above is the proxy class project of Mapper, the mapperInterface in the constructor is the corresponding interface class. When instantiated, you will get a specific MapperProxy proxy, which mainly contains SqlSession.

Fifth, (mybatis-spring) source code analysis

Org.mybatis mybatis-spring 1.3.2

As an easy-to-use ORM framework, it must be Lori's face (simple), Royal Sister's heart (powerful), made bed (shielding to deal directly with JDBC), and warm room (good speed performance)! In view of these advantages, almost most of the domestic Internet development frameworks will use Mybatis, especially in some high-performance scenarios that need to optimize sql, so handwritten sql must be in xml. So, are you ready? Start to analyze its source code.

1. Start with a simple case.

Just like analyzing the mybatis source code, do a simple case first; define dao, write configuration files, and junit unit tests

SpringApiTest.java

@ RunWith (SpringJUnit4ClassRunner.class) @ ContextConfiguration ("classpath:spring-config.xml") public class SpringApiTest {private Logger logger = LoggerFactory.getLogger (SpringApiTest.class); @ Resource private ISchoolDao schoolDao; @ Resource private IUserDao userDao; @ Test public void test_queryRuleTreeByTreeId () {School ruleTree = schoolDao.querySchoolInfoById (1L); logger.info (JSON.toJSONString (ruleTree)); User user = userDao.queryUserInfoById (1L) Logger.info (JSON.toJSONString (user));}}

Spring-config-datasource.xml

If all goes well, the result will be as follows:

{"address": "5 Yiheyuan Road, Haidian District, Beijing", "createTime": 1571376957000, "id": 1, "name": "Peking University", "updateTime": 1571376957000} {"age": 18, "createTime": 1571376957000, "id": 1, "name": "Huahua", "updateTime": 1571376957000}

As you can see from the above unit test code, two annotations without a method body miraculously execute the configuration statements in our xml and output the results. In fact, it mainly benefits from the following two categories

Org.mybatis.spring.SqlSessionFactoryBean

Org.mybatis.spring.mapper.MapperScannerConfigurer

two。 Scan Assembly Registration (MapperScannerConfigurer)

MapperScannerConfigurer generates dynamic proxy class registrations for the entire Dao interface layer, booting to the core role. This class implements the following interface to process the scanned Mapper:

BeanDefinitionRegistryPostProcessor

InitializingBean

ApplicationContextAware

BeanNameAware

The overall class diagram is as follows

The execution process is as follows

The above class diagram + flow chart, in fact, has clearly described the MapperScannerConfigurer initialization process, but for the first time to see the new person is still too difficult for me, good to continue!

MapperScannerConfigurer.java & partial interception

@ Override public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders ();} ClassPathMapperScanner scanner = new ClassPathMapperScanner (registry); scanner.setAddToConfig (this.addToConfig); scanner.setAnnotationClass (this.annotationClass); scanner.setMarkerInterface (this.markerInterface); scanner.setSqlSessionFactory (this.sqlSessionFactory); scanner.setSqlSessionTemplate (this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName (this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName (this.sqlSessionTemplateBeanName); scanner.setResourceLoader (this.applicationContext) Scanner.setBeanNameGenerator (this.nameGenerator); scanner.registerFilters (); scanner.scan (StringUtils.tokenizeToStringArray (this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}

BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry is implemented to register Bean into the Spring container

Line 306: new ClassPathMapperScanner (registry); hard-coded classpath scanner for parsing Mybatis's Mapper file

Line 317: scanner.scan scans the Mapper. This includes a call to the inheritance class implementation relationship, which is the test question at the beginning of this article.

ClassPathMapperScanner.java & partial interception

@ Override public Set doScan (String... BasePackages) {Set beanDefinitions = super.doScan (basePackages); if (beanDefinitions.isEmpty ()) {logger.warn ("No MyBatis mapper was found in'" + Arrays.toString (basePackages) + "'package. Please check your configuration.");} else {processBeanDefinitions (beanDefinitions);} return beanDefinitions;}

Call super.doScan (basePackages) of the parent class first; register the Bean information

ClassPathBeanDefinitionScanner.java & partial interception

Protected Set doScan (String... BasePackages) {Assert.notEmpty (basePackages, "At least one base package must be specified"); Set beanDefinitions = new LinkedHashSet (); for (String basePackage: basePackages) {Set candidates = findCandidateComponents (basePackage); for (BeanDefinition candidate: candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata (candidate); candidate.setScope (scopeMetadata.getScopeName ()) String beanName = this.beanNameGenerator.generateBeanName (candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition ((AbstractBeanDefinition) candidate, beanName) } if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations ((AnnotatedBeanDefinition) candidate)} if (checkCandidate (beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder (candidate, beanName) DefinitionHolder = AnnotationConfigUtils.applyScopedProxyMode (scopeMetadata, definitionHolder, this.regi beanDefinitions.add (definitionHolder); registerBeanDefinition (definitionHolder, this.registry);} return beanDefinitions;}

Priority is given to calling the doScan method of the parent class for Mapper scanning and Bean definition as well as registration to DefaultListableBeanFactory. {DefaultListableBeanFactory is the ancestor of the IOC container in Spring. All classes that need to be instantiated need to be registered, and then initialized}

Line 272: findCandidateComponents (basePackage), scan the package package path, there is another way for annotated classes, more or less the same

Line 288: registerBeanDefinition (definitionHolder, this.registry); the process of registering Bean information will eventually call: org.springframework.beans.factory.support.DefaultListableBeanFactory

ClassPathMapperScanner.java & partial interception

* * processBeanDefinitions (beanDefinitions); * * private void processBeanDefinitions (Set beanDefinitions) {GenericBeanDefinition definition; for (BeanDefinitionHolder holder: beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition (); if (logger.isDebugEnabled ()) {logger.debug ("Creating MapperFactoryBean with name'" + holder.getBeanName () + "and"+ definition.getBeanClassName () +" mapperInterface ") } / / the mapper interface is the original class of the bean / / but, the actual class of the bean is MapperFactoryBean definition.getConstructorArgumentValues (). AddGenericArgumentValue (definition.getBeanClassName ()); / / issue # 59 definition.setBeanClass (this.mapperFactoryBean.getClass ()); definition.getPropertyValues () .add ("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false If (StringUtils.hasText (this.sqlSessionFactoryBeanName)) {definition.getPropertyValues () .add ("sqlSessionFactory", new RuntimeBeanReference (this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true;} else if (this.sqlSessionFactory! = null) {definition.getPropertyValues () .add ("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true } if (StringUtils.hasText (this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {logger.warn ("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. SqlSessionFactory is ignored. ");} definition.getPropertyValues (). Add (" sqlSessionTemplate ", new RuntimeBeanReference (this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true;} else if (this.sqlSessionTemplate! = null) {if (explicitFactoryUsed) {logger.warn (" Cannot use both: sqlSessionTemplate and sqlSessionFactory together. SqlSessionFactory is ignored. ");} definition.getPropertyValues (). Add (" sqlSessionTemplate ", this.sqlSessionTemplate); explicitFactoryUsed = true;} if (! explicitFactoryUsed) {if (logger.isDebugEnabled ()) {logger.debug (" Enabling autowire by type for MapperFactoryBean with name'"+ holder.getBeanName () +". ");} definition.setAutowireMode (AbstractBeanDefinition.AUTOWIRE_BY_TYPE) }}}

Line 163: super.doScan (basePackages);, after the parent class method is called, the inner method is executed: processBeanDefinitions (beanDefinitions)

Line: definition.getConstructorArgumentValues () .addGenericArgumentValue (definition.getBeanClassName ()); set the BeanName parameter, that is, our: ISchoolDao, IUserDao

Line 187: definition.setBeanClass (this.mapperFactoryBean.getClass ()); set BeanClass. The interface itself has no class, so set the MapperFactoryBean class here, and finally all the dao layer interface classes are this MapperFactoryBean.

MapperFactoryBean.java & partial interception

This class has both inheritance and interface implementation. It is best to take a look at the overall class diagram first, as follows

This class is very important, and eventually all sql message execution will get getObject () through this class, that is, the proxy class for SqlSession to get mapper: MapperProxyFactory- > MapperProxy.

Public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {private Class mapperInterface; private boolean addToConfig = true; public MapperFactoryBean () {/ / intentionally empty} public MapperFactoryBean (Class mapperInterface) {this.mapperInterface = mapperInterface } / * checkDaoConfig () is called when the SpringBean container is initialized, which is the abstract method in the inheritance class * {@ inheritDoc} * / @ Override protected void checkDaoConfig () {super.checkDaoConfig (); notNull (this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession (). GetConfiguration () If (this.addToConfig & &! configuration.hasMapper (this.mapperInterface)) {try {configuration.addMapper (this.mapperInterface);} catch (Exception e) {logger.error ("Error while adding the mapper'" + this.mapperInterface + "'to configuration.", e); throw new IllegalArgumentException (e);} finally {ErrorContext.instance (). Reset () } / * * {@ inheritDoc} * / @ Override public T getObject () throws Exception {return getSqlSession () .getMapper (this.mapperInterface);}.}

Line 72: checkDaoConfig (), which is called to checkDaoConfig () when the SpringBean container is initialized, which is an abstract method in the inheritance class

Line 95: getSqlSession () .getMapper (this.mapperInterface);, and get the Mapper (proxy class) through the API. The calling procedure is as follows

DefaultSqlSession.getMapper (Class type), get Mapper

Configuration.getMapper (Class type, SqlSession sqlSession), obtained from the configuration

MapperRegistry.getMapper (Class type, SqlSession sqlSession), obtained from the registry to instantiate the generation

Public T getMapper (Class type, SqlSession sqlSession) {final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get (type); if (mapperProxyFactory = = null) {throw new BindingException ("Type" + type + "is not known to the MapperRegistry.");} try {return mapperProxyFactory.newInstance (sqlSession);} catch (Exception e) {throw new BindingException ("Error getting mapper instance. Cause: "+ e, e);}}

MapperProxyFactory.newInstance (sqlSession);, generate MapperProxy through reflection engineering

SuppressWarnings ("unchecked") protected T newInstance (MapperProxy mapperProxy) {return (T) Proxy.newProxyInstance (mapperInterface.getClassLoader (), new Class [] {mapperInterface}, mapperProxy);} public T newInstance (SqlSession sqlSession) {final MapperProxy mapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance (mapperProxy);}

MapperProxy.java & partial interception

Public class MapperProxy implements InvocationHandler, Serializable {private static final long serialVersionUID =-6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; public MapperProxy (SqlSession sqlSession, Class mapperInterface, Map methodCache) {this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache } @ Override public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {try {if (Object.class.equals (method.getDeclaringClass () {return method.invoke (this, args);} else if (isDefaultMethod (method)) {return invokeDefaultMethod (proxy, method, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable (t) } final MapperMethod mapperMethod = cachedMapperMethod (method); return mapperMethod.execute (sqlSession, args);} private MapperMethod cachedMapperMethod (Method method) {MapperMethod mapperMethod = methodCache.get (method); if (mapperMethod = = null) {mapperMethod = new MapperMethod (mapperInterface, method, sqlSession.getConfiguration ()); methodCache.put (method, mapperMethod);} return mapperMethod } @ UsesJava7 private Object invokeDefaultMethod (Object proxy, Method method, Object [] args) throws Throwable {final Constructor constructor = MethodHandles.Lookup.class .getDeclaredConstructor (Class.class, int.class); if (! constructor.isAccessible ()) {constructor.setAccessible (true);} final Class declaringClass = method.getDeclaringClass () Return constructor .newInstance (declaringClass, MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC) .unreflectSpecial (method, declaringClass) .bindTo (proxy) .invokeWithArguments (args);}.}

Line 58: final MapperMethod mapperMethod = cachedMapperMethod (method);, get MapperMethod from the cache

Line 59: mapperMethod.execute (sqlSession, args);, execute the SQL statement and return the result (this is where the query gets the result to the bone (dry) layer; INSERT, UPDATE, DELETE, SELECT

Public Object execute (SqlSession sqlSession, Object [] args) {Object result; 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);} break; case FLUSH: 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;}

The above analysis is done for the MapperScannerConfigurer layer, from scanning definition injection to preparing Bean information for the Spring container, proxy, reflection, SQL execution, basically including all the core content, followed by the analysis of SqlSessionFactoryBean

3. SqlSession Container Factory initialization (SqlSessionFactoryBean)

Some of its own content needs to be handled during SqlSessionFactoryBean initialization, so you also need to implement the following interfaces

FactoryBean

InitializingBean-> void afterPropertiesSet () throws Exception

ApplicationListener

In fact, the above process has clearly described the entire core process, but there will also be obstacles for beginners on the road, so! All right, go on!

SqlSessionFactoryBean.java & partial interception

Public void afterPropertiesSet () throws Exception {notNull (dataSource, "Property 'dataSource' is required"); notNull (sqlSessionFactoryBuilder, "Property' sqlSessionFactoryBuilder' is required"); state ((configuration = = null & & configLocation = = null) | | (configuration! = null & & configLocation! = null), "Property 'configuration' and' configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory ();}

AfterPropertiesSet (), the InitializingBean interface provides a way for bean to initialize the method, which only includes the afterPropertiesSet method, which is executed when the bean is initialized by any class that inherits the interface.

Line 380: buildSqlSessionFactory (); the internal method is built, and the core functions move on.

SqlSessionFactoryBean.java & partial interception

Protected SqlSessionFactory buildSqlSessionFactory () throws IOException {Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null;... 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);}

Line 513: for (Resource mapperLocation: this.mapperLocations) loop parses Mapper content

Line 519: XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder (...) Parsing XMLMapperBuilder

Line 521: xmlMapperBuilder.parse () performs parsing, as follows

XMLMapperBuilder.java & partial interception

Public class XMLMapperBuilder extends BaseBuilder {private final XPathParser parser; private final MapperBuilderAssistant builderAssistant; private final Map sqlFragments; private final String resource; private void bindMapperForNamespace () {String namespace = builderAssistant.getCurrentNamespace (); if (namespace! = null) {Class boundType = null; try {boundType = Resources.classForName (namespace) } catch (ClassNotFoundException e) {/ / ignore Bound type is not required} if (boundType! = null) {if (! configuration.hasMapper (boundType)) {/ / Spring may not know the real resource name so we set a flag / / to prevent loading again this resource from the mapper interface / / look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource ("namespace:" + namespace) Configuration.addMapper (boundType);}}

Line 413 here is very important, configuration.addMapper (boundType); it's really time to add Mapper to the configuration center.

MapperRegistry.java & partial interception

Public class MapperRegistry {public void addMapper (Class type) {if (type.isInterface ()) {if (hasMapper (type)) {throw new BindingException ("Type" + type + "is already known to the MapperRegistry.");} boolean loadCompleted = false; try {knownMappers.put (type, new MapperProxyFactory (type)) / / It's important that the type is added before the parser is run / / otherwise the binding may automatically be attempted by the / / mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder (config, type); parser.parse (); loadCompleted = true;} finally {if (! loadCompleted) {knownMappers.remove (type);}

Line 67: create the agent project knownMappers.put (type, new MapperProxyFactory (type))

At this point, MapperScannerConfigurer and SqlSessionFactoryBean merge what the two classes do.

The first one is used to scan Dao interface settings proxy class is registered in IOC, which is used to generate Bean entity class, MapperFactoryBean, and can obtain Mapper from Configuration through mapperInterface.

The other is used to generate SqlSession factory initialization, parse the XML configuration in Mapper and dynamically proxy the Mapper injected into Configuration by MapperProxyFactory- > MapperProxy

Finally, the method injection is carried out with the help of the annotation class, and the dynamic proxy object can be obtained when the operation is performed, so as to perform the corresponding CRUD operation.

@ Resource private ISchoolDao schoolDao; schoolDao.querySchoolInfoById (1L)

VI. Summary

The analysis process is long and large, and you may not be able to understand the whole process in one day, but when you have the patience to do a little research, you can still get a lot of harvest. In the future, when you encounter this kind of anomaly, you can easily solve it, and it is also helpful to interview and recruitment.

The reason for analyzing Mybatis is to add custom annotations to Dao, and it is found that the aspect can not be intercepted. Think that this is a class that is dynamically proxied, and then often go down layer by layer to MapperProxy.invoke! Of course, Mybatis provides custom plug-in development.

The above source code analysis is only part of the core content analysis, if you want to know all can refer to materials; MyBatis 3 source code depth analysis, and debug the code. IDEA is still very convenient to look at the source code, including the ability to view class diagrams, call sequence, and so on.

In fact, the most important thing in mybatis and mybatis-spring is to map the Mapper configuration file parsing and interface classes into proxy classes, so as to facilitate the CRUD operation of the database. After analyzing the source code, you can gain more programming experience (routines).

Is it helpful for you to read the above content? If you want to know more about the relevant knowledge or read more related articles, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report