Spring的jdbc模块中处理除了提供了JdbcTemplate
等封装工具,还提供了对各种第三方持久层框架的封装,比如Mybatis,Hibernate,帮助开发者可以更高效率的进行业务开发,这里主要分析下spring中如何对mybatis实现的封装。关于Mybatis,可以参考相关笔记:[Mybatis ]
示例 pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-core</artifactId > <version > 5.1.3.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-beans</artifactId > <version > 5.1.3.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.1.3.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.1.3.RELEASE</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.2</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.4.6</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.13</version > </dependency >
User.java 1 2 3 4 5 6 7 8 9 10 11 12 13 public class User { private long id; private String name; public User (long id, String name) { this .id = id; this .name = name; } }
UserMapper.java 1 2 3 4 5 6 public interface UserMapper { void save (User user) ; List<User> query () ; }
bean.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://192.168.141.13:3306/demo" /> <property name ="username" value ="shanhm" /> <property name ="password" value ="shanhm" /> </bean > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="dataSource" /> <property name ="configLocation" value ="mybatis-config.xml" /> </bean > <bean id ="sqlSessionTemplate" class ="org.mybatis.spring.SqlSessionTemplate" > <constructor-arg name ="sqlSessionFactory" ref ="sqlSessionFactory" /> </bean > <bean id ="userMapper" class ="org.mybatis.spring.mapper.MapperFactoryBean" > <property name ="mapperInterface" value ="test.sm.UserMapper" /> <property name ="sqlSessionFactory" ref ="sqlSessionFactory" /> </bean > </beans >
mybatis-config.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <typeAliases > <typeAlias alias ="User" type ="test.sm.User" /> </typeAliases > <mappers > <mapper resource ="test/sm/UserMapper.xml" /> </mappers > </configuration >
UserMapper.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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 ="test.sm.UserMapper" > <select id ="save" resultType ="User" > select id, name from user </select > <insert id ="query" parameterType ="User" > insert into user(id, name) values (#{name}, #{age}) </insert > </mapper >
spring对于Mybatis的两种使用方式都提供了支持,一种是直接使用模板SqlSessionTemplate
,其中帮我们代理了SqlSession
的创建和关闭,并考虑了事务问题,另一种是直接将接口对应的代理注册到容器之中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class App { public static void main ( String[] args ) { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml" ); User user = new User(1 , "shanhm" ); SqlSessionTemplate sqlSessionTemplate = context.getBean(SqlSessionTemplate.class ) ; sqlSessionTemplate.insert("test.sm.UserMapper.save" , user); sqlSessionTemplate.selectList("test.sm.UserMapper.query" ); UserMapper userMapper = context.getBean(UserMapper.class ) ; userMapper.save(new User(1 , "shanhm" )); List<User> userList = userMapper.query(); } }
实现 SqlSessionFactoryBean SqlSessionFactory
是Mybatis的基础,所以封装了SqlSessionFactory
初始化的SqlSessionFactoryBean
也就是spring代理Mybatis的基础,其实它是通过实现对应的事件接口将SqlSessionFactory
的初始化加入到了spring容器的初始化过程中,,具体可以从其实现的接口进行分析
实现InitializingBean
的bean,在初始化过程中,当设置完属性时会调用其afterPropertiesSet
方法,这里的实现中首先检查了下dataSource
有没有设置,然后初始化一个SqlSessionFactory
,另外对Mybatis的Configuration
尽量做了一些支持,就是说可以直接将mybatis-config.xml中的配置配成SqlSessionFactory
的属性,然后在初始化时它会尝试获取和填充;
实现FactoryBean
就是将上面构造的SqlSessionFactory
实例注册到spring容器中
实现ApplicationListener
是为在spring启动时触发对Mapper做一些初始化,其实就是提前检查,如果有错误可以及时失败,而不用等到真正调用时才发现;
org.mybatis.spring.SqlSessionFactoryBean.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 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(); } 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 .configurationProperties); } } 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(); if (this .configurationProperties != null ) { 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 ) { try { 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); } public SqlSessionFactory getObject () throws Exception { if (this .sqlSessionFactory == null ) { afterPropertiesSet(); } return this .sqlSessionFactory; } public void onApplicationEvent (ApplicationEvent event) { if (failFast && event instanceof ContextRefreshedEvent) { this .sqlSessionFactory.getConfiguration().getMappedStatementNames(); } }
SqlSessionTemplate SqlSessionTemplate
作为SqlSession
的使用模板,其作用类似于之前的JdbcTemplate
,帮我们封装了SqlSeesion
的创建与关闭并考虑了事务问题,使我们可以更方便简洁的使用SqlSession
。
其实它是创建了一个对自身的代理sqlSessionProxy
,但是在回调中对于操作并没有调用自己,而是对真正创建出来的SqlSession
的调用(不然会出现调用死循环),另外就是对调用做了一些前后处理。
org.mybatis.spring.SqlSessionTemplate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public SqlSessionTemplate (SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required" ); notNull(executorType, "Property 'executorType' is required" ); this .sqlSessionFactory = sqlSessionFactory; this .executorType = executorType; this .exceptionTranslator = exceptionTranslator; this .sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class .getClassLoader (), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this .sqlSessionFactory, SqlSessionTemplate.this .executorType, SqlSessionTemplate.this .exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this .sqlSessionFactory)) { sqlSession.commit(true ); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this .exceptionTranslator != null && unwrapped instanceof PersistenceException) { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); sqlSession = null ; Throwable translated = SqlSessionTemplate.this .exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null ) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null ) { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); } } } }
MapperFactoryBean Mybatis还有一种使用方式,就是直接创建目标接口的代理实现,spring则提供了MapperFactoryBean
用来帮忙将其注册到容器中,具体是通过实现FactoryBean
实现的,另外它还实现了InitializingBean
,主要是为了在初始化时检查SqlSession
有没有创建,以及利用addMapper
检查接口对应的Mapper有没有问题。
另外,这里会发现MapperFactoryBean
在注册属性时其实初始化的也是SqlSessionTemplate
,就是说也可以跳过接口代理,直接获取容器中的MapperFactoryBean
实例,然后获取它的SqlSessionTemplate
直接进行sql查询,像上面一样
org.springframework.dao.support.DaoSupport 1 2 3 4 5 6 7 8 9 10 public final void afterPropertiesSet () throws IllegalArgumentException, BeanInitializationException { checkDaoConfig(); try { initDao(); }catch (Exception ex) { throw new BeanInitializationException("Initialization of DAO failed" , ex); } }
org.mybatis.spring.mapper.MapperFactoryBean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void setSqlSessionFactory (SqlSessionFactory sqlSessionFactory) { if (!this .externalSqlSession) { this .sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } 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(); } } } public T getObject () throws Exception { return getSqlSession().getMapper(this .mapperInterface); }
上面的方式都有一个缺点,就是当需要配置的映射器比较多时会显得麻烦,因此spring提供了MapperScannerConfigurer
来帮我们实现自动扫描指定包下的接口,比如可以将示例中配置的Mapper换成:
bean.xml 1 2 3 <bean id ="mapperScannerConfigurer" class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="test.sm" /> </bean >
其主要是实现了接口BeanDefinitionRegistryPostProcessor
,所以当BeanDefinition
注册时会触发postProcessBeanDefinitionRegistry
,然后在实现中构造了一个ClassPathMapperScanner
,并将扫描和注册Mapper映射器的任务委托给它,至于扫描时的过滤规则在构造时确定,默认是所有接口,如下面代码所示
org.mybatis.spring.mapper.MapperScannerConfigurer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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)); }
ClassPathMapperScanner ClassPathMapperScanner
继承自ClassPathBeanDefinitionScanner
,ClassPathBeanDefinitionScanner
前面已经介绍过,参考笔记:[spring. 应用上下文(ApplicationContext) ],这里主要是覆盖了isCandidateComponent
,改成了只要是接口就通过,另外在doScan
之后加入了processBeanDefinitions
操作,其实就是注册一个MapperFactoryBean
的BeanDefinition
,并且可以指定构建时使用的sqlSessionFactory
,如果没有指定则自动注入,但是当应用中涉及到多个数据源时,还是要手动指定的,不然spring无法知道应该注入哪一个
org.mybatis.spring.mapper.ClassPathMapperScanner 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 public void registerFilters () { boolean acceptAllInterfaces = true ; if (this .annotationClass != null ) { addIncludeFilter(new AnnotationTypeFilter(this .annotationClass)); acceptAllInterfaces = false ; } if (this .markerInterface != null ) { addIncludeFilter(new AssignableTypeFilter(this .markerInterface) { @Override protected boolean matchClassName (String className) { return false ; } }); acceptAllInterfaces = false ; } if (acceptAllInterfaces) { addIncludeFilter(new TypeFilter() { @Override public boolean match (MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true ; } }); } addExcludeFilter(new TypeFilter() { @Override public boolean match (MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info" ); } }); } public Set<BeanDefinitionHolder> doScan (String... basePackages) { Set<BeanDefinitionHolder> 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; } protected boolean isCandidateComponent (AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); } private void processBeanDefinitions (Set<BeanDefinitionHolder> 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" ); } definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 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); } } }
MapperScannerRegistrar MapperScannerRegistrar
也是用来自动扫描目标路径下的接口的,不过相对于上面通过xml配置的方式,它是通过注解实现的。具体则是实现了ImportBeanDefinitionRegistrar
,然后定义@MapperScan
,并用@Import
进行标识,同时指定了引入配置类为MapperScannerRegistrar
,这样只要将@MapperScan
标识到任何一个被spring扫描的bean上就可以执行MapperScannerRegistrar
中的逻辑了,事实也是委托给了ClassPathMapperScanner
,与上面一样。
对于@MapperScan
的实现,即如何能在启动时就知道要扫描哪些目录下的哪些bean,并进行注册,前面有过详细分析,可以参考笔记:[spring. 应用上下文(ApplicationContext) ]
参考:
《spring源码深度解析》 郝佳