关于java:MyBatis如何解析mapper和Spring中MapperScan原理

55次阅读

共计 10826 个字符,预计需要花费 28 分钟才能阅读完成。

咱们在启动我的项目的时候,spring 就会帮咱们实例化好 bean,那咱们应用 mybatis-spring 的时候,编 写的 maper 是怎么交给 spring 容器的呢?这就是明天要探讨的问题。

一、MyBatis

1.1 MyBatis 简介

MyBatis 是一款优良的长久层框架,它反对自定义 SQL、存储过程以及高级映射。MyBatis 罢黜了简直所有的 JDBC 代码以及设置参数和获取后果集的工作。MyBatis 能够通过简略的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,一般老式 Java 对象)为数据库中的记录。

1.2 MyBatis 应用
1.2.1 装置
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
1.2.2 从 XML 中构建 SqlSessionFactory

​ 每个基于 MyBatis 的利用都是以一个 SqlSessionFactory 的实例为外围的。SqlSessionFactory 的实例能够通过 SqlSessionFactoryBuilder 取得。而 SqlSessionFactoryBuilder 则能够从 XML 配置文件或一个事后配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

​ 从 XML 文件中构建 SqlSessionFactory 的实例非常简单,倡议应用类门路下的资源文件进行配置。但也能够应用任意的输出流(InputStream)实例,比方用文件门路字符串或 file:// URL 结构的输出流。MyBatis 蕴含一个名叫 Resources 的工具类,它蕴含一些实用办法,使得从类门路或其它地位加载资源文件更加容易。

/**
 * @author wangjun
 * @date 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 = new SqlSessionFactoryBuilder().build(inputStream);
        System.out.println(build);
    }
}

XML 配置文件中蕴含了对 MyBatis 零碎的外围设置,包含获取数据库连贯实例的数据源(DataSource)以及决定事务作用域和管制形式的事务管理器(TransactionManager)。前面会再探讨 XML 配置文件的具体内容,这里先给出一个简略的示例:

<?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>
    <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>
1.2.3 不应用 XML 构建 SqlSessionFactory

如果你更违心间接从 Java 代码而不是 XML 文件中创立配置,或者想要创立你本人的配置建造器,MyBatis 也提供了残缺的配置类,提供了所有与 XML 文件等价的配置项。

/**
 * @author wangjun
 * @date 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 = new SqlSessionFactoryBuilder().build(configuration);
        System.out.println(build);
    }
}

留神该例中,configuration 增加了一个映射器类(mapper class)。映射器类是 Java 类,它们蕴含 SQL 映射注解从而防止依赖 XML 文件。不过,因为 Java 注解的一些限度以及某些 MyBatis 映射的复杂性,要应用大多数高级映射(比方:嵌套联结映射),依然须要应用 XML 配置。有鉴于此,如果存在一个同名 XML 配置文件,MyBatis 会主动查找并加载它

1.2.4 从 SqlSessionFactory 中获取 SqlSession

既然有了 SqlSessionFactory,顾名思义,咱们能够从中取得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有办法。你能够通过 SqlSession 实例来间接执行已映射的 SQL 语句。例如:

例如:

try (SqlSession session = sqlSessionFactory.openSession()) {BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}
1.2.5 探索已映射的 SQL 语句

在下面提到的例子中,一个语句既能够通过 XML 定义,也能够通过注解定义。咱们先看看 XML 定义语句的形式,事实上 MyBatis 提供的所有个性都能够利用基于 XML 的映射语言来实现,这使得 MyBatis 在过来的数年间得以风行。如果你用过旧版本的 MyBatis,你应该对这个概念比拟相熟。但相比于之前的版本,新版本改良了许多 XML 的配置,前面咱们会提到这些改良。这里给出一个基于 XML 映射语句的示例,它应该能够满足上个示例中 SqlSession 的调用。

<?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="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>
1.2.6 对命名空间的一点补充

在之前版本的 MyBatis 中, 命名空间(Namespaces) 的作用并不大,是可选的。但当初,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你下面见到的接口绑定。就算你感觉临时用不到接口绑定,你也应该遵循这里的规定,以防哪天你扭转了主见。久远来看,只有将命名空间置于适合的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更不便地应用 MyBatis。

命名解析: 为了缩小输入量,MyBatis 对所有具备名称的配置元素(包含语句,后果映射,缓存等)应用了如下的命名解析规定。

  • 全限定名(比方“com.mypackage.MyMapper.selectAllThings)将被间接用于查找及应用。
  • 短名称(比方“selectAllThings”)如果全局惟一也能够作为一个独自的援用。如果不惟一,有两个或两个以上的雷同名称(比方“com.foo.selectAllThings”和“com.bar.selectAllThings”),那么应用时就会产生“短名称不惟一”的谬误,这种状况下就必须应用全限定名。
1.3 作用域(Scope)和生命周期

依赖注入框架能够创立线程平安的、基于事务的 SqlSession 和映射器,并将它们间接注入到你的 bean 中,因而能够间接疏忽它们的生命周期。

1.3.1 SqlSessionFactoryBuilder

这个类能够被实例化、应用和抛弃,一旦创立了 SqlSessionFactory,就不再须要它了。因而 SqlSessionFactoryBuilder 实例的最佳作用域是办法作用域(也就是部分办法变量)。你能够重用 SqlSessionFactoryBuilder 来创立多个 SqlSessionFactory 实例,但最好还是不要始终保留着它,以保障所有的 XML 解析资源能够被开释给更重要的事件。

1.3.2 SqlSessionFactory

SqlSessionFactory 一旦被创立就应该在利用的运行期间始终存在,没有任何理由抛弃它或从新创立另一个实例。应用 SqlSessionFactory 的最佳实际是在利用运行期间不要反复创立屡次,屡次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因而 SqlSessionFactory 的最佳作用域是利用作用域。有很多办法能够做到,最简略的就是应用单例模式或者动态单例模式。

1.3.3 SqlSession

每个线程都应该有它本人的 SqlSession 实例。SqlSession 的实例不是线程平安的,因而是不能被共享的,所以它的最佳的作用域是申请或办法作用域。相对不能将 SqlSession 实例的援用放在一个类的动态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的援用放在任何类型的托管作用域中,比方 Servlet 框架中的 HttpSession。如果你当初正在应用一种 Web 框架,思考将 SqlSession 放在一个和 HTTP 申请类似的作用域中。换句话说,每次收到 HTTP 申请,就能够关上一个 SqlSession,返回一个响应后,就敞开它。这个敞开操作很重要,为了确保每次都能执行敞开操作,你应该把这个敞开操作放到 finally 块中。上面的示例就是一个确保 SqlSession 敞开的规范模式:

try (SqlSession session = sqlSessionFactory.openSession()) {// 你的应用逻辑代码}
1.3.4 映射器实例

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中取得的。尽管从技术层面上来讲,任何映射器实例的最大作用域与申请它们的 SqlSession 雷同。但办法作用域才是映射器实例的最合适的作用域。也就是说,映射器实例应该在调用它们的办法中被获取,应用结束之后即可抛弃。映射器实例并不需要被显式地敞开。只管在整个申请作用域保留映射器实例不会有什么问题,然而你很快会发现,在这个作用域上治理太多像 SqlSession 的资源会让你忙不过来。因而,最好将映射器放在办法作用域内。就像上面的例子一样:

try (SqlSession session = sqlSessionFactory.openSession()) {BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的应用逻辑代码
}
1.4 MyBatis-Spring
1.4.1 MyBatis-Spring 利用

咱们在接入 mybatis-spring 的时候会在相应的配置类减少这样的注解

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

用 MyBatis-Spring 会将 MyBatis 整合到 Spring 当中,我这里用的版本 Spring 5.2.1,MyBatis 3.5.3,MyBatis-Spring 2.0.3,满足官网的要求,用的注解而不是 XML

具体的应用,须要配置两样货色,一个是 SqlSessionFactory,用来创立 SqlSession 的 Factory,SqlSession 用来对接数据库;另一个是数据映射器 Mapper 接口

1、在 MyBatis-Spring 中,能够通过 SqlSessionFactoryBean 来创立 SqlSessionFactory,这里还须要一个 DataSource,和 JDBC 等其它 DataSource 一样即可

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

大抵流程 SqlSessionFactoryBean->getObject()->afterPropertiesSet()->buildSqlSessionFactory()->Configuration->SqlSessionFactory

2、映射器是一个接口,定义一个 Mapper 接口,一个简略的查询方法,@Select 指定具体的 SQL

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

3、对应 ORM 实体类

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

4、映射器的注册,通过 MapperFactoryBean 能够将映射器注册到 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() {DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

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

}

5、测试类

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

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

如果是间接通过注入 Bean 的形式注入 UserMapper,那么如果有一大堆的映射器,一个一个的注册注入非常麻烦,因而就和 @ComponentScan 注解一样有一个扫描映射器的注解 @MapperScan,大抵如下

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

外面的 @Import 注解,能够导入一个类,定义如下

/**
 * 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
 * @since 1.2.0
 */
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

依据正文里能够看到 ClassPathMapperScanner,继承了 ClassPathBeanDefinitionScanner 类是 Spring 提供的一个用于扫描 Bean 定义配置的根底类,这里笼罩了基类的 doScan() 办法

/**
 * 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;
}

DEBUG 一下,能够看到这个就是扫描 Mapper 接口的办法,返回 @MapperScan 注解的 value

二、参考链接

https://juejin.im/post/5d8e06…

https://mybatis.org/mybatis-3…

Spring:MyBatis 框架 @MapperScan 注解流程和疑惑

https://juejin.im/post/5db3bc…

https://my.oschina.net/xiaoly…

https://www1350.github.io/hex…

https://cofcool.github.io/tech/2018/06/20/mybatis-sourcecode-1#21-%E9%85%8D%E7%BD%AE%E7%B1%BB

https://objcoding.com/2018/06…

http://www.songshuiyang.com/2018/12/18/backend/framework/mybatis/sourceCodeAnalysis/Mybatis%E6%BA%90%E7%A0%81(%E5%8D%81%E4%B9%9D)Spring%20Mybatis%E9%9B%86%E6%88%90%E4%B9%8B%E5%9F%BA%E4%BA%8E%E6%B3%A8%E8%A7%A3%E7%9A%84%E9%85%8D%E7%BD%AE%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/Spring Mybatis 集成之基于注解的配置原理解析 /)

正文完
 0