mybatis整合spring 当前spring是最热门的框架之一,很多框架都需要和spring整合,将组件交给spring的IOC容器来管理,下面来带大家来简单地配置一下mybatis和spring的整合
添加依赖 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 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.5</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.48</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.3</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 4.3.29.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-core</artifactId > <version > 4.3.29.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 4.3.29.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 4.3.29.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-beans</artifactId > <version > 4.3.29.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 4.3.29.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-tx</artifactId > <version > 4.3.29.RELEASE</version > </dependency >
mybatis配置文件 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 <?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 > <settings > <setting name ="cacheEnabled" value ="true" /> <setting name ="logImpl" value ="STDOUT_LOGGING" /> <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" /> </settings > <typeAliases > <typeAlias type ="com.zhanghe.study.mybatis.model.User" alias ="User" /> <package name ="com.zhanghe.study.mybatis.model" /> </typeAliases > <databaseIdProvider type ="DB_VENDOR" > <property name ="MySQL" value ="mysql" /> <property name ="Oracle" value ="oracle" /> </databaseIdProvider > </configuration >
spring配置文件 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 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:mybatis ="http://mybatis.org/schema/mybatis-spring" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <context:component-scan base-package ="com.zhanghe.study.mybatis" /> <context:property-placeholder location ="jdbc.properties" /> <bean id ="datasource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="${jdbc.driver}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="datasource" /> </bean > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="datasource" /> <property name ="configLocation" value ="mybatis-config1.xml" /> <property name ="mapperLocations" value ="classpath*:mapper/**/*.xml" /> </bean > <bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="com.zhanghe.study.mybatis.mapper" /> <property name ="sqlSessionFactoryBeanName" value ="sqlSessionFactory" /> </bean > <tx:annotation-driven transaction-manager ="transactionManager" /> </beans >
单测结果 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 public class MybatisSpringTest { private ApplicationContext context; private SqlSessionFactory sqlSessionFactory; @Before public void beanFactory () { context = new ClassPathXmlApplicationContext("applicationContext.xml" ); sqlSessionFactory = context.getBean(SqlSessionFactory.class); } @Test public void testbeanAutoWired () { UserMapper userMapper = context.getBean(UserMapper.class); System.out.println(userMapper); UserService userService = context.getBean(UserService.class); userService.print(); } @Test public void testTwoLevelCache () { SqlSession session = sqlSessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = userMapper.selectUser(8 ); System.out.println(user); userMapper.updateUser(user); session.close(); SqlSession session1 = sqlSessionFactory.openSession(); UserMapper userMapper1 = session1.getMapper(UserMapper.class); User user1 = userMapper1.selectUser(8 ); System.out.println(user1); System.out.println(user == user1); session1.close(); } }
参考地址http://mybatis.org/spring/zh/getting-started.html
分析 SqlSessionFactoryBean创建SqlSessionFactory 先来看一下SqlSessionFactoryBean是怎么创建SqlSessionFactory的,看这个命名像是一个FactoryBean,看一下实现,果然实现了FactoryBean和InitializingBean
1 public class SqlSessionFactoryBean implements FactoryBean <SqlSessionFactory >, InitializingBean , ApplicationListener <ApplicationEvent >
那么接下来就是看getObject()方法和afterPropertiesSet()方法了
getObject() 发现getObject()方法也是调用afterPropertiesSet()方法,那就只需要看afterPropertiesSet()方法了
1 2 3 4 5 6 7 public SqlSessionFactory getObject () throws Exception { if (this .sqlSessionFactory == null ) { afterPropertiesSet(); } return this .sqlSessionFactory; }
afterPropertiesSet() 忽略掉组织Configuration对象,其实最后就是调用了mybatis的sqlSessionFactoryBuilder.build(configuration),OK,生成了SqlSessionFactory对象
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 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); }
其实除了MapperScannerConfigurer之外,spring还提供了一个MapperFactoryBean,但是每一个Mapper都需要配置,太麻烦了,这里就直接分析MapperScannerConfigurer,先看一下结构,实现了BeanDefinitionRegistryPostProcessor、InitializingBean、ApplicationContextAware、BeanNameAware
1 public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor , InitializingBean , ApplicationContextAware , BeanNameAware
那接下来就是分析BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry和InitializingBean#afterPropertiesSet
afterPropertiesSet 1 2 3 4 public void afterPropertiesSet () throws Exception { notNull(this .basePackage, "Property 'basePackage' is required" ); }
postProcessBeanDefinitionRegistry 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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)); }
扫描
1 2 3 4 5 6 7 8 9 10 11 12 public int scan (String... basePackages) { int beanCountAtScanStart = this .registry.getBeanDefinitionCount(); doScan(basePackages); if (this .includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this .registry); } return (this .registry.getBeanDefinitionCount() - beanCountAtScanStart); }
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 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 Set<BeanDefinitionHolder> doScan (String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified" ); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); for (String basePackage : basePackages) { Set<BeanDefinition> 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 .registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this .registry); } } } return beanDefinitions; } 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); } } }