Coverage Report - org.mybatis.spring.SqlSessionFactoryBean
 
Classes in this File Line Coverage Branch Coverage Complexity
SqlSessionFactoryBean
87%
108/123
79%
54/68
2.667
 
 1  
 /**
 2  
  *    Copyright 2010-2015 the original author or authors.
 3  
  *
 4  
  *    Licensed under the Apache License, Version 2.0 (the "License");
 5  
  *    you may not use this file except in compliance with the License.
 6  
  *    You may obtain a copy of the License at
 7  
  *
 8  
  *       http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  *    Unless required by applicable law or agreed to in writing, software
 11  
  *    distributed under the License is distributed on an "AS IS" BASIS,
 12  
  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  *    See the License for the specific language governing permissions and
 14  
  *    limitations under the License.
 15  
  */
 16  
 package org.mybatis.spring;
 17  
 
 18  
 import static org.springframework.util.Assert.notNull;
 19  
 import static org.springframework.util.ObjectUtils.isEmpty;
 20  
 import static org.springframework.util.StringUtils.hasLength;
 21  
 import static org.springframework.util.StringUtils.tokenizeToStringArray;
 22  
 
 23  
 import java.io.IOException;
 24  
 import java.sql.SQLException;
 25  
 import java.util.Properties;
 26  
 
 27  
 import javax.sql.DataSource;
 28  
 
 29  
 import org.apache.ibatis.builder.xml.XMLConfigBuilder;
 30  
 import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 31  
 import org.apache.ibatis.executor.ErrorContext;
 32  
 import org.apache.ibatis.logging.Log;
 33  
 import org.apache.ibatis.logging.LogFactory;
 34  
 import org.apache.ibatis.mapping.DatabaseIdProvider;
 35  
 import org.apache.ibatis.mapping.Environment;
 36  
 import org.apache.ibatis.plugin.Interceptor;
 37  
 import org.apache.ibatis.reflection.factory.ObjectFactory;
 38  
 import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
 39  
 import org.apache.ibatis.session.Configuration;
 40  
 import org.apache.ibatis.session.SqlSessionFactory;
 41  
 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 42  
 import org.apache.ibatis.transaction.TransactionFactory;
 43  
 import org.apache.ibatis.type.TypeHandler;
 44  
 import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
 45  
 import org.springframework.beans.factory.FactoryBean;
 46  
 import org.springframework.beans.factory.InitializingBean;
 47  
 import org.springframework.context.ApplicationEvent;
 48  
 import org.springframework.context.ApplicationListener;
 49  
 import org.springframework.context.ConfigurableApplicationContext;
 50  
 import org.springframework.context.event.ContextRefreshedEvent;
 51  
 import org.springframework.core.NestedIOException;
 52  
 import org.springframework.core.io.Resource;
 53  
 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
 54  
 
 55  
 /**
 56  
  * {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}.
 57  
  * This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context;
 58  
  * the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection.
 59  
  *
 60  
  * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction
 61  
  * demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions
 62  
  * which span multiple databases or when container managed transactions (CMT) are being used.
 63  
  *
 64  
  * @author Putthibong Boonbong
 65  
  * @author Hunter Presnall
 66  
  * @author Eduardo Macarron
 67  
  * 
 68  
  * @see #setConfigLocation
 69  
  * @see #setDataSource
 70  
  * @version $Id$
 71  
  */
 72  109
 public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
 73  
 
 74  1
   private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
 75  
 
 76  
   private Resource configLocation;
 77  
 
 78  
   private Resource[] mapperLocations;
 79  
 
 80  
   private DataSource dataSource;
 81  
 
 82  
   private TransactionFactory transactionFactory;
 83  
 
 84  
   private Properties configurationProperties;
 85  
 
 86  69
   private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
 87  
 
 88  
   private SqlSessionFactory sqlSessionFactory;
 89  
 
 90  
   //EnvironmentAware requires spring 3.1
 91  69
   private String environment = SqlSessionFactoryBean.class.getSimpleName();
 92  
 
 93  
   private boolean failFast;
 94  
 
 95  
   private Interceptor[] plugins;
 96  
 
 97  
   private TypeHandler<?>[] typeHandlers;
 98  
 
 99  
   private String typeHandlersPackage;
 100  
 
 101  
   private Class<?>[] typeAliases;
 102  
 
 103  
   private String typeAliasesPackage;
 104  
 
 105  
   private Class<?> typeAliasesSuperType;
 106  
 
 107  
   //issue #19. No default provider.
 108  
   private DatabaseIdProvider databaseIdProvider;
 109  
 
 110  
   private ObjectFactory objectFactory;
 111  
 
 112  
   private ObjectWrapperFactory objectWrapperFactory;
 113  
 
 114  
   /**
 115  
    * Sets the ObjectFactory.
 116  
    * 
 117  
    * @since 1.1.2
 118  
    * @param objectFactory
 119  
    */
 120  
   public void setObjectFactory(ObjectFactory objectFactory) {
 121  1
     this.objectFactory = objectFactory;
 122  1
   }
 123  
 
 124  
   /**
 125  
    * Sets the ObjectWrapperFactory.
 126  
    * 
 127  
    * @since 1.1.2
 128  
    * @param objectWrapperFactory
 129  
    */
 130  
   public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
 131  1
     this.objectWrapperFactory = objectWrapperFactory;
 132  1
   }
 133  
 
 134  
   /**
 135  
    * Gets the DatabaseIdProvider
 136  
    *
 137  
    * @since 1.1.0
 138  
    * @return
 139  
    */
 140  
   public DatabaseIdProvider getDatabaseIdProvider() {
 141  0
     return databaseIdProvider;
 142  
   }
 143  
 
 144  
   /**
 145  
    * Sets the DatabaseIdProvider.
 146  
    * As of version 1.2.2 this variable is not initialized by default. 
 147  
    *
 148  
    * @since 1.1.0
 149  
    * @param databaseIdProvider
 150  
    */
 151  
   public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
 152  3
     this.databaseIdProvider = databaseIdProvider;
 153  3
   }
 154  
 
 155  
   /**
 156  
    * Mybatis plugin list.
 157  
    *
 158  
    * @since 1.0.1
 159  
    *
 160  
    * @param plugins list of plugins
 161  
    *
 162  
    */
 163  
   public void setPlugins(Interceptor[] plugins) {
 164  7
     this.plugins = plugins;
 165  7
   }
 166  
 
 167  
   /**
 168  
    * Packages to search for type aliases.
 169  
    *
 170  
    * @since 1.0.1
 171  
    *
 172  
    * @param typeAliasesPackage package to scan for domain objects
 173  
    *
 174  
    */
 175  
   public void setTypeAliasesPackage(String typeAliasesPackage) {
 176  4
     this.typeAliasesPackage = typeAliasesPackage;
 177  4
   }
 178  
 
 179  
   /**
 180  
    * Super class which domain objects have to extend to have a type alias created.
 181  
    * No effect if there is no package to scan configured.
 182  
    *
 183  
    * @since 1.1.2
 184  
    *
 185  
    * @param typeAliasesSuperType super class for domain objects
 186  
    *
 187  
    */
 188  
   public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
 189  1
     this.typeAliasesSuperType = typeAliasesSuperType;
 190  1
   }
 191  
 
 192  
   /**
 193  
    * Packages to search for type handlers.
 194  
    *
 195  
    * @since 1.0.1
 196  
    *
 197  
    * @param typeHandlersPackage package to scan for type handlers
 198  
    *
 199  
    */
 200  
   public void setTypeHandlersPackage(String typeHandlersPackage) {
 201  1
     this.typeHandlersPackage = typeHandlersPackage;
 202  1
   }
 203  
 
 204  
   /**
 205  
    * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes}
 206  
    *
 207  
    * @since 1.0.1
 208  
    *
 209  
    * @param typeHandlers Type handler list
 210  
    */
 211  
   public void setTypeHandlers(TypeHandler<?>[] typeHandlers) {
 212  1
     this.typeHandlers = typeHandlers;
 213  1
   }
 214  
 
 215  
   /**
 216  
    * List of type aliases to register. They can be annotated with {@code Alias}
 217  
    *
 218  
    * @since 1.0.1
 219  
    *
 220  
    * @param typeAliases Type aliases list
 221  
    */
 222  
   public void setTypeAliases(Class<?>[] typeAliases) {
 223  1
     this.typeAliases = typeAliases;
 224  1
   }
 225  
 
 226  
   /**
 227  
    * If true, a final check is done on Configuration to assure that all mapped
 228  
    * statements are fully loaded and there is no one still pending to resolve
 229  
    * includes. Defaults to false.
 230  
    *
 231  
    * @since 1.0.1
 232  
    *
 233  
    * @param failFast enable failFast
 234  
    */
 235  
   public void setFailFast(boolean failFast) {
 236  0
     this.failFast = failFast;
 237  0
   }
 238  
 
 239  
   /**
 240  
    * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is
 241  
    * "WEB-INF/mybatis-configuration.xml".
 242  
    */
 243  
   public void setConfigLocation(Resource configLocation) {
 244  3
     this.configLocation = configLocation;
 245  3
   }
 246  
 
 247  
   /**
 248  
    * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory}
 249  
    * configuration at runtime.
 250  
    *
 251  
    * This is an alternative to specifying "&lt;sqlmapper&gt;" entries in an MyBatis config file.
 252  
    * This property being based on Spring's resource abstraction also allows for specifying
 253  
    * resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml".
 254  
    */
 255  
   public void setMapperLocations(Resource[] mapperLocations) {
 256  18
     this.mapperLocations = mapperLocations;
 257  18
   }
 258  
 
 259  
   /**
 260  
    * Set optional properties to be passed into the SqlSession configuration, as alternative to a
 261  
    * {@code &lt;properties&gt;} tag in the configuration xml file. This will be used to
 262  
    * resolve placeholders in the config file.
 263  
    */
 264  
   public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
 265  0
     this.configurationProperties = sqlSessionFactoryProperties;
 266  0
   }
 267  
 
 268  
   /**
 269  
    * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource}
 270  
    * should match the one used by the {@code SqlSessionFactory}: for example, you could specify the same
 271  
    * JNDI DataSource for both.
 272  
    *
 273  
    * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code
 274  
    * accessing this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}.
 275  
    *
 276  
    * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not
 277  
    * a {@code TransactionAwareDataSourceProxy}. Only data access code may work with
 278  
    * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the
 279  
    * underlying target {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy}
 280  
    * passed in, it will be unwrapped to extract its target {@code DataSource}.
 281  
    *
 282  
    */
 283  
   public void setDataSource(DataSource dataSource) {
 284  68
     if (dataSource instanceof TransactionAwareDataSourceProxy) {
 285  
       // If we got a TransactionAwareDataSourceProxy, we need to perform
 286  
       // transactions for its underlying target DataSource, else data
 287  
       // access code won't see properly exposed transactions (i.e.
 288  
       // transactions for the target DataSource).
 289  0
       this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
 290  
     } else {
 291  68
       this.dataSource = dataSource;
 292  
     }
 293  68
   }
 294  
 
 295  
   /**
 296  
    * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}.
 297  
    *
 298  
    * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By
 299  
    * default, {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances.
 300  
    *
 301  
    */
 302  
   public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
 303  1
     this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
 304  1
   }
 305  
 
 306  
   /**
 307  
    * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory}
 308  
    *
 309  
    * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases:
 310  
    * be it Spring transaction management, EJB CMT or plain JTA. If there is no active transaction,
 311  
    * SqlSession operations will execute SQL statements non-transactionally.
 312  
    *
 313  
    * <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> If not used, any
 314  
    * attempt at getting an SqlSession through Spring's MyBatis framework will throw an exception if
 315  
    * a transaction is active.
 316  
    *
 317  
    * @see SpringManagedTransactionFactory
 318  
    * @param transactionFactory the MyBatis TransactionFactory
 319  
    */
 320  
   public void setTransactionFactory(TransactionFactory transactionFactory) {
 321  2
     this.transactionFactory = transactionFactory;
 322  2
   }
 323  
 
 324  
   /**
 325  
    * <b>NOTE:</b> This class <em>overrides</em> any {@code Environment} you have set in the MyBatis
 326  
    * config file. This is used only as a placeholder name. The default value is
 327  
    * {@code SqlSessionFactoryBean.class.getSimpleName()}.
 328  
    *
 329  
    * @param environment the environment name
 330  
    */
 331  
   public void setEnvironment(String environment) {
 332  1
     this.environment = environment;
 333  1
   }
 334  
 
 335  
   /**
 336  
    * {@inheritDoc}
 337  
    */
 338  
   @Override
 339  
   public void afterPropertiesSet() throws Exception {
 340  69
     notNull(dataSource, "Property 'dataSource' is required");
 341  67
     notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
 342  
 
 343  66
     this.sqlSessionFactory = buildSqlSessionFactory();
 344  66
   }
 345  
 
 346  
   /**
 347  
    * Build a {@code SqlSessionFactory} instance.
 348  
    *
 349  
    * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
 350  
    * {@code SqlSessionFactory} instance based on an Reader.
 351  
    *
 352  
    * @return SqlSessionFactory
 353  
    * @throws IOException if loading the config file failed
 354  
    */
 355  
   protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 356  
 
 357  
     Configuration configuration;
 358  
 
 359  66
     XMLConfigBuilder xmlConfigBuilder = null;
 360  66
     if (this.configLocation != null) {
 361  2
       xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
 362  2
       configuration = xmlConfigBuilder.getConfiguration();
 363  
     } else {
 364  64
       if (LOGGER.isDebugEnabled()) {
 365  64
         LOGGER.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
 366  
       }
 367  64
       configuration = new Configuration();
 368  64
       configuration.setVariables(this.configurationProperties);
 369  
     }
 370  
 
 371  66
     if (this.objectFactory != null) {
 372  1
       configuration.setObjectFactory(this.objectFactory);
 373  
     }
 374  
 
 375  66
     if (this.objectWrapperFactory != null) {
 376  1
       configuration.setObjectWrapperFactory(this.objectWrapperFactory);
 377  
     }
 378  
 
 379  66
     if (hasLength(this.typeAliasesPackage)) {
 380  4
       String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
 381  
           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
 382  8
       for (String packageToScan : typeAliasPackageArray) {
 383  4
         configuration.getTypeAliasRegistry().registerAliases(packageToScan,
 384  
                 typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
 385  4
         if (LOGGER.isDebugEnabled()) {
 386  4
           LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
 387  
         }
 388  
       }
 389  
     }
 390  
 
 391  66
     if (!isEmpty(this.typeAliases)) {
 392  2
       for (Class<?> typeAlias : this.typeAliases) {
 393  1
         configuration.getTypeAliasRegistry().registerAlias(typeAlias);
 394  1
         if (LOGGER.isDebugEnabled()) {
 395  1
           LOGGER.debug("Registered type alias: '" + typeAlias + "'");
 396  
         }
 397  
       }
 398  
     }
 399  
 
 400  66
     if (!isEmpty(this.plugins)) {
 401  14
       for (Interceptor plugin : this.plugins) {
 402  7
         configuration.addInterceptor(plugin);
 403  7
         if (LOGGER.isDebugEnabled()) {
 404  7
           LOGGER.debug("Registered plugin: '" + plugin + "'");
 405  
         }
 406  
       }
 407  
     }
 408  
 
 409  66
     if (hasLength(this.typeHandlersPackage)) {
 410  1
       String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
 411  
           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
 412  2
       for (String packageToScan : typeHandlersPackageArray) {
 413  1
         configuration.getTypeHandlerRegistry().register(packageToScan);
 414  1
         if (LOGGER.isDebugEnabled()) {
 415  1
           LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
 416  
         }
 417  
       }
 418  
     }
 419  
 
 420  66
     if (!isEmpty(this.typeHandlers)) {
 421  2
       for (TypeHandler<?> typeHandler : this.typeHandlers) {
 422  1
         configuration.getTypeHandlerRegistry().register(typeHandler);
 423  1
         if (LOGGER.isDebugEnabled()) {
 424  1
           LOGGER.debug("Registered type handler: '" + typeHandler + "'");
 425  
         }
 426  
       }
 427  
     }
 428  
 
 429  66
     if (xmlConfigBuilder != null) {
 430  
       try {
 431  2
         xmlConfigBuilder.parse();
 432  
 
 433  2
         if (LOGGER.isDebugEnabled()) {
 434  2
           LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
 435  
         }
 436  0
       } catch (Exception ex) {
 437  0
         throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
 438  
       } finally {
 439  2
         ErrorContext.instance().reset();
 440  2
       }
 441  
     }
 442  
 
 443  66
     if (this.transactionFactory == null) {
 444  65
       this.transactionFactory = new SpringManagedTransactionFactory();
 445  
     }
 446  
 
 447  66
     configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
 448  
 
 449  66
     if (this.databaseIdProvider != null) {
 450  
       try {
 451  0
         configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
 452  0
       } catch (SQLException e) {
 453  0
         throw new NestedIOException("Failed getting a databaseId", e);
 454  0
       }
 455  
     }
 456  
 
 457  66
     if (!isEmpty(this.mapperLocations)) {
 458  32
       for (Resource mapperLocation : this.mapperLocations) {
 459  16
         if (mapperLocation == null) {
 460  1
           continue;
 461  
         }
 462  
 
 463  
         try {
 464  15
           XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
 465  
               configuration, mapperLocation.toString(), configuration.getSqlFragments());
 466  15
           xmlMapperBuilder.parse();
 467  0
         } catch (Exception e) {
 468  0
           throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
 469  
         } finally {
 470  15
           ErrorContext.instance().reset();
 471  15
         }
 472  
 
 473  15
         if (LOGGER.isDebugEnabled()) {
 474  15
           LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
 475  
         }
 476  
       }
 477  
     } else {
 478  50
       if (LOGGER.isDebugEnabled()) {
 479  50
         LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
 480  
       }
 481  
     }
 482  
 
 483  66
     return this.sqlSessionFactoryBuilder.build(configuration);
 484  
   }
 485  
 
 486  
   /**
 487  
    * {@inheritDoc}
 488  
    */
 489  
   @Override
 490  
   public SqlSessionFactory getObject() throws Exception {
 491  69
     if (this.sqlSessionFactory == null) {
 492  29
       afterPropertiesSet();
 493  
     }
 494  
 
 495  66
     return this.sqlSessionFactory;
 496  
   }
 497  
 
 498  
   /**
 499  
    * {@inheritDoc}
 500  
    */
 501  
   @Override
 502  
   public Class<? extends SqlSessionFactory> getObjectType() {
 503  206
     return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
 504  
   }
 505  
 
 506  
   /**
 507  
    * {@inheritDoc}
 508  
    */
 509  
   @Override
 510  
   public boolean isSingleton() {
 511  133
     return true;
 512  
   }
 513  
 
 514  
   /**
 515  
    * {@inheritDoc}
 516  
    */
 517  
   @Override
 518  
   public void onApplicationEvent(ApplicationEvent event) {
 519  121
     if (failFast && event instanceof ContextRefreshedEvent) {
 520  
       // fail-fast -> check all statements are completed
 521  0
       this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
 522  
     }
 523  121
   }
 524  
 
 525  
 }