Spring instantiates the bean when we start the project. How do we use mybatis- Spring to deliver the maper to the spring container? That’s the question today.

A, MyBatis

1.1 introduction of MyBatis

MyBatis is an excellent persistence layer framework that supports custom SQL, stored procedures, and advanced mapping. MyBatis eliminates almost all of the JDBC code and the work of setting parameters and fetching result sets. MyBatis can configure and map primitive types, interfaces, and Java POJOs (Plain Old Java Objects) to records in the database via simple XML or annotations.

1.2 use MyBatis
1.2.1 installation
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
Copy the code
1.2.2 Building the SqlSessionFactory from XML

Each MyBatis application is built around an instance of the SqlSessionFactory. An instance of SqlSessionFactory can be obtained from SqlSessionFactoryBuilder. SqlSessionFactoryBuilder, on the other hand, can build an SqlSessionFactory instance from an XML Configuration file or a pre-configured Configuration instance.

Building an instance of SqlSessionFactory from an XML file is straightforward, and it is recommended to use a resource file under the classpath for configuration. But you can also use an arbitrary instance of an InputStream, such as an InputStream constructed from a file path string or a file:// URL. MyBatis includes a utility class called Resources, which contains utility methods that make it easier to load resource files from the classpath or elsewhere.

/ * * *@author wangjun
 * @dateThe 2020-08-01 * /
public class SqlSessionFactoryWithXml {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader();
        if (defaultClassLoader == null) {
            return;
        }
        InputStream inputStream = defaultClassLoader.getResourceAsStream(resource);
        SqlSessionFactory build = newSqlSessionFactoryBuilder().build(inputStream); System.out.println(build); }}Copy the code

The XML configuration file contains the core Settings for the MyBatis system, including the DataSource to get the database connection instance and the TransactionManager to determine the transaction scope and control mode. More on XML configuration files later, but here’s a simple example:


      
<! DOCTYPEconfiguration PUBLIC "- / / mybatis.org//DTD Config / 3.0 / EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/zp"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/NewsMapper.xml"/>
    </mappers>
</configuration>
Copy the code
1.2.3 Build SqlSessionFactory without XML

If you prefer to create configurations directly from Java code rather than XML files, or want to create your own configuration builder, MyBatis also provides complete configuration classes that provide all configuration items equivalent to XML files.

/ * * *@author wangjun
 * @dateThe 2020-08-01 * /
public class SqlSessionFactoryWithoutXml {
    public static void main(String[] args) {
        DataSource dataSource = DataSourceFactory.getDataSource();
        JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();
        Environment development = new Environment("development", jdbcTransactionFactory, dataSource);
        Configuration configuration = new Configuration(development);
        configuration.addMapper(NewsMapper.class);
        SqlSessionFactory build = newSqlSessionFactoryBuilder().build(configuration); System.out.println(build); }}Copy the code

Note that in this example, Configuration adds a Mapper class. Mapper classes are Java classes that contain SQL mapping annotations to avoid dependency on XML files. However, due to the limitations of Java annotations and the complexity of some MyBatis mappings, XML configuration is still required to use most advanced mappings (e.g., nested union mappings). With this in mind, MyBatis will automatically find and load an XML configuration file with the same name if it exists

1.2.4 Obtaining SqlSession from SqlSessionFactory

Now that we have SqlSessionFactory, as the name implies, we can get an instance of SqlSession from it. SqlSession provides all the methods needed to execute SQL commands in the database. You can use the SqlSession instance to execute the mapped SQL statement directly. Such as:

Such as:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}
Copy the code
1.2.5 Exploring mapped SQL Statements

In the example mentioned above, a statement can be defined by either XML or annotations. Taking a look at the way XML defines statements, the fact that all of the features MyBatis offers can be implemented using xmL-based mapping languages has made MyBatis popular over the past few years. If you have used older versions of MyBatis, you should be familiar with this concept. However, compared to the previous version, the new version has many XML configuration improvements, which we will refer to later. Here is an example of an XML-based mapping statement that should satisfy the SqlSession call from the previous example.


      
<! DOCTYPEmapper
        PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.tim.wang.sourcecode.mybatis.spring.mapper.NewsMapper">

    <select id="selectIds" resultType="java.lang.Integer">
        SELECT ID FROM t_news LIMIT 0,1
    </select>

</mapper>
Copy the code
1.2.6 A few additions to namespaces

In previous versions of MyBatis, ** Namespaces ** were not useful and were optional. But now, as namespaces become more important, you have to specify namespaces.

Namespaces serve two purposes. One is to use longer fully qualified names to separate statements from each other, while also implementing the interface binding you saw above. Even if you don’t think interface binding is necessary for the time being, you should follow the rules here in case you change your mind. In the long run, your code will be cleaner and it will be easier to use MyBatis if you just put the namespace in the proper Java package namespace.

** Name resolution: ** To reduce input, MyBatis uses the following name resolution rules for all configuration elements with names (including statements, result maps, caches, etc.).

  • Fully qualified name (such as “com. Mypackage. MyMapper. SelectAllThings) will be used to find and use directly.
  • Short names (such as “selectAllThings”) can also be used as a separate reference if they are globally unique. If there are two or more of the same names (such as “com.foo.selectallThings” and “com.bar.selectallThings”) that are not unique, then the “short name is not unique” error is generated, in which case fully qualified names must be used.
1.3 Scope and life cycle

Dependency injection frameworks can create thread-safe, transaction-based SQLSessions and mappers and inject them directly into your beans, so their life cycles can be ignored.

1.3.1 the SqlSessionFactoryBuilder is

This class can be instantiated, used, and discarded, and is no longer needed once the SqlSessionFactory has been created. So the best scope for SqlSessionFactoryBuilder instances is the method scope (that is, local method variables). You can reuse SqlSessionFactoryBuilder to create multiple instances of SqlSessionFactory, but it’s best not to keep it around all the time to ensure that all XML parsing resources can be freed up for more important things.

1.3.2 SqlSessionFactory

Once created, the SqlSessionFactory should persist for the duration of the application, and there is no reason to discard it or recreate another instance. The best practice with SqlSessionFactory is not to create the SqlSessionFactory more than once while the application is running. Rebuilding SqlSessionFactory more than once is considered a code “bad habit.” So the best scope for SqlSessionFactory is the application scope. There are many ways to do this, the easiest is to use singleton or static singleton.

1.3.3 SqlSession

Each thread should have its own INSTANCE of SqlSession. An instance of SqlSession is not thread-safe and therefore cannot be shared, so its best scope is the request or method scope. A reference to an SqlSession instance must never be placed in a static domain of a class, or even an instance variable of a class. A reference to an SqlSession instance should also never be placed in any type of managed scope, such as HttpSession in the Servlet framework. If you are currently using a Web framework, consider putting SqlSession in a scope similar to HTTP requests. In other words, each time an HTTP request is received, an SqlSession can be opened, and when a response is returned, it can be closed. The close operation is important, and to ensure that the close operation is performed every time, you should put the close operation ina finally block. The following example is a standard mode to ensure that SqlSession is closed:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // Your application logic code
}
Copy the code
1.3.4 Mapper instance

Mappers are interfaces that bind mapping statements. The instance of the mapper interface is obtained from SqlSession. Although technically speaking, the maximum scope of any mapper instance is the same as the SqlSession from which they are requested. But the method scope is the most appropriate scope for the mapper instance. That is, mapper instances should be retrieved in the methods that call them and then discarded after use. The mapper instance does not need to be explicitly closed. Although keeping the mapper instance in the entire request scope is fine, you will soon find that managing too many resources like SqlSession in this scope can get you too busy. Therefore, it is best to place the mapper in the method scope. As in the following example:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // Your application logic code
}
Copy the code
1.4 MyBatis – Spring
1.4.1 MyBatis – Spring applications

We will add this annotation to the corresponding configuration class when we access Mybatis – Spring

@MapperScan(basePackages = "com.test.**.mapper")
Copy the code

MyBatis-Spring will integrate MyBatis into Spring. The versions I use here, Spring 5.2.1, MyBatis 3.5.3, MyBatis-Spring 2.0.3, meet the official requirements and use annotations instead of XML

SqlSessionFactory is used to create the SqlSessionFactory, which is used to connect to the database. The other is the data Mapper interface

In MyBatis, you can create the SqlSessionFactory by using the SqlSessionFactoryBean

@Bean
public SqlSessionFactory sqlSessionFactory(a) throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
}
Copy the code

SqlSessionFactoryBean->getObject()->afterPropertiesSet()->buildSqlSessionFactory()->Configuration->SqlSessionFactory

2, Mapper is an interface, define a Mapper interface, a simple query method, @select specify specific SQL

public interface UserMapper {
    
    @Select("select * from user_info where id = #{id}")
    User getUserById(@Param("id") int id);
}
Copy the code

3. Corresponding ORM entity class

public class User {
    
    private int id;
    private String name;
    private String phone;
    private int age;
    
    @Override
    public String toString(a) {
        return "id=" + id + ", name=" + name + ", phone=" + phone + ", age="+ age; }}Copy the code

MapperFactoryBean is used to register MapperFactoryBean with Spring

import com.lihuia.mybatis.mapper.UserMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

/** * Copyright (C), 2018-2019 * FileName: MyBatisConfig * Author: lihui * Date: 2019/11/15 */

@PropertySource(value = {"classpath:config/db.properties"})
@Configuration
public class MyBatisConfig {
  	@Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    
    @Bean
    public DriverManagerDataSource dataSource(a) {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(a) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }
    
  	@Bean
    public JdbcTemplate jdbcTemplate(a) {
        return new JdbcTemplate(dataSource());
    }
    
    @Bean
    public MapperFactoryBean<UserMapper> userMapper(a) throws Exception {
        MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory());
        returnfactoryBean; }}Copy the code

5. Test classes

@ContextConfiguration(classes = {MyBatisConfig.class})
@Slf4j
public class UserTest extends AbstractTestNGSpringContextTests {

    @Resource
    private JdbcTemplate jdbcTemplate;
    
    @Resource
    private UserMapper userMapper;
    
    
    @test (description = "Test JDBC")
    public void jdbcTest(a) {
        String sql = "select * from user_info";
        System.out.println(jdbcTemplate.queryForList(sql));
    }
    
    @test (description = "Test MyBatis")
    public void myBatisTest(a) {
        log.info(userMapper.getUserById(1).toString()); }}Copy the code

If you inject UserMapper directly through an injection Bean, you have a bunch of mappers that are cumbersome to register and inject one by one, so you have a scan MapperScan annotation like @ComponentScan annotation, which looks something like this

@PropertySource(value = {"classpath:config/db.properties"})
@Configuration
@MapperScan("com.lihuia.mybatis.mapper")
public class MyBatisConfig {
Copy the code
1.4.2 @ MapperScan annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
Copy the code

The @import annotation inside allows you to Import a class, defined as follows

/**
 * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using
 * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing
 * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
 *
 * @author Michael Lanyon
 * @author Eduardo Macarron
 * @author Putthiphong Boonphong
 *
 * @see MapperFactoryBean
 * @see ClassPathMapperScanner
 * @since1.2.0 * /
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware {
Copy the code

Can see ClassPathMapperScanner, according to the comments inherited is Spring ClassPathBeanDefinitionScanner class provides a base class used to scan the Bean definition configuration, here covers doScan of the base class () method

/** * Calls the parent search that will search and register all the candidates. Then the registered objects are post * processed to set them as MapperFactoryBeans */
@Override
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;
}
Copy the code

If you DEBUG this, you can see that this is the way to scan the Mapper interface and return the value of the @mapperscan annotation

2. Reference links

Juejin. Cn/post / 684490…

Mybatis.org/mybatis-3/z…

lihuia.com/mybatis/

Juejin. Cn/post / 684490…

My.oschina.net/xiaolyuh/bl…

Www1350. Making. IO/hexo/post / 4…

Cofcool. Making. IO/tech / 2018/0…

Objcoding.com/2018/06/12/…

[www.songshuiyang.com/2018/12/18/ www.songshuiyang.com/2018/12/18/… Mybatis annotation-based configuration principle of integrated parsing /)