当前位置: 首页>编程语言>正文

mybatis 为什么要禁止使用if test mybatis为什么只有接口


文章目录

  • 一、问题引入
  • 二、传统开发 VS 代理开发
  • 2.1 传统开发模式
  • 2.2 代理开发模式
  • 三、Dao接口和XML文件里的SQL是如何建立关系的?
  • 3.1 初始化SqlSessionFactoryBean
  • 3.1.1 SqlSessionFactoryBean简介
  • 3.1.2 SqlSessionFactoryBean
  • 3.1.3 解析mapper.xml 文件的过程
  • 3.1.3.1 创建SqlSource
  • 3.1.3.2 创建MappedStatement
  • 3.2 Dao接口代理
  • 3.2.1 配置需要扫描的基本包路径
  • 3.2.2 扫描并注册bean
  • 3.3 使用bean
  • 四、参考文档


一、问题引入

mybatis在写dao层的时候只是写了个接口,并没有具体实现,如何正常工作的?

其实最初开发web的时候是需要写dao接口的实现,只是后面mybatis简化了我们的开发模式,将“dao层的实现”这部分重复代码给我们自动生成了,不需要手动写了。

我们先回顾下“需要写dao实现的传统开发模式” 和 “不需要写dao实现的代理开发模式”,再看看mybatis是如何做到这点的。

二、传统开发 VS 代理开发

为了方便比价,这里将“带dao实现”的开发方式称作“传统开发”模式,将“不带dao实现”的开发模式称作“代理开发”模式。

2.1 传统开发模式

step1: 定义UserDao接口

public interface UserDao {
    // 1、 根据用户ID查询用户信息
    public User findUserById(int id) throws IOException;

    // 2、 根据用户ID和用户名称查询用户信息
    public User findByUserIdAndName(User user) throws IOException;
    
	// 3、 返回所有符合条件的用户信息
    public List<User> getUserList(User user) throws IOException;
}

step2: 定义 UserDao 接口的实现类 UserDaoImpl

public class UserDaoImpl implements UserDao {
    /**  依赖注入,将工程在外面创建 */
    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
        //将外面创建的工厂传递进来(以后spring)
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public User findUserById(int id) throws IOException {
        // 创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 调用SqlSession的增删改查方法
        User user = sqlSession.selectOne("userMapper.findByUserId", id);
        System.out.println(user);
        // 关闭资源
        sqlSession.close();
        return user;
    }

    @Override
    public User findByUserIdAndName(User user)  throws IOException {
        // 创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 调用SqlSession的增删改查方法
        User result = sqlSession.selectOne("userMapper.findByIdAndName", user);
        System.out.println(result);
        // 关闭资源
        sqlSession.close();
        return result;
    }

    @Override
    public List<User> getUserList(User user) throws IOException {
        // 创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 调用SqlSession的增删改查方法
        List<User> userList= sqlSession.selectList("userMapper.getUserList", user);
        System.out.println(user);
        // 关闭资源
        sqlSession.close();
        return userList;
    }
}

step3: 对应的 UserMapper.xml 文件

<?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.demo.dao.impl.UserDaoImpl" >

    <select id="findUserById" parameterType="int" resultType="User">
        select *from user
        where id = #{id}
    </select>

    <select id="findByUserIdAndName" parameterType="User"  resultType="User">
        select *from user
        where id = #{id} and name = #{name}
    </select>

    <select id="getUserList" parameterType="User"  resultType="User">
        select *from user
        where age = #{age}
    </select>
</mapper>

step4: 模拟业务层调用

public static void main(String [] args) throws Exception {
   InputStream inputStream = Resources.getResourceAsStream(resource);
   // 创建SqlSessionFactory
   SqlSessionFactory  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
   UserDao dao = new UserDaoImpl(sqlSessionFactory);
   User user = dao.findUserById(1);
   System.out.println(user);
}

2.2 代理开发模式

为了简化开发,Mybatis 提供了一种代理开发的方式,这种方式是项目开发中主流,由Mybatis 实现 Dao 层的接口。

代理开发方式只需要程序员编写Dao 接口 和 对应的Mapper.xml文件,然后由 Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。 **也就是说,不用写dao层的实现类了。**相应的开发流程如下:

step1: 定义UserDao接口

内容同2.1节

step2: 对应的 UserMapper.xml 文件

内容同2.1节
将mapper标签中的 nameSpace值由 UserDaoImpl 改为 UserDao 即可

step3:模拟业务层调用

public static void main(String [] args) throws Exception {
   InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
   // 创建SqlSessionFactory
   SqlSessionFactory  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
   // 创建SqlSession
   SqlSession sqlSession = sqlSessionFactory.openSession();
   UserDao dao = sqlSession.getMapper(UserDao.class);
   User user = dao.findByUserId(1);
   System.out.println(user);
}

代理开发方式使用的是动态代理的 JDK 代码实现的,使用该代理方式需要遵循下面4个规范:

规范1. 映射文件mapper.xml中的mapper标签的namespace属性与 dao 接口的全限定名相同

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_User,第1张

规范2. 映射文件mapper.xml中的每条映射语句中id的属性值与 dao 接口中方法名相同

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_java_02,第2张

规范3. 映射文件mapper.xml中的每条映射语句的parameterType属性与 dao 接口中方法的形参相同

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_java_03,第3张

规范4. 映射文件mapper.xml中的每条映射语句的resultType属性与 dao 接口中方法的返回值类型相同

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_java_04,第4张

如果 Mapper 接口已经遵循上述规范,那么不需要创建 Dao 层的实现类了,可以直接进行使用。

三、Dao接口和XML文件里的SQL是如何建立关系的?

3.1 初始化SqlSessionFactoryBean

3.1.1 SqlSessionFactoryBean简介

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,但SqlSessionFactory是一个接口,它里面其实就两个方法:

  • openSession方法 是为了获取一个SqlSession对象,完成必要数据库增删改查功能;
  • getConfiguration方法 可以返回一个Configuration类,可来配置mapper映射文件、SQL参数、返回值类型、缓存等属性;Configuration类可以看成是一个配置管家,MyBatis所有的配置信息都维持在Configuration对象之中,基本每个对象都会持有它的引用。
public interface SqlSessionFactory {
  SqlSession openSession();
  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();
}

日常开发中都是将Mybatis与Spring一起使用的,所以把实例化工作交给Spring处理。通常用org.mybatis.spring.SqlSessionFactoryBean来获取SqlSessionFactory实例。

3.1.2 SqlSessionFactoryBean

SqlSessionFactoryBean的类图如下:

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_spring_05,第5张

它实现了InitializingBean接口,该接口内只有一个afterPropertiesSet()方法。这说明,SqlSessionFactoryBean类被实例化之后会调用到该方法。

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

SqlSessionFactoryBean类对应的实现:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

	······
	
  @Override
  public void afterPropertiesSet() throws Exception {
   	······
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 	1、从配置文件的property属性中加载各种组件,解析配置到configuration中
	2、加载mapper文件,解析SQL语句,封装成MappedStatement对象,配置到configuration中。
  }
}

SqlSessionFactoryBean.afterPropertiesSet()方法只有一个动作,就是buildSqlSessionFactory,这个动作的作用有两个:

  1. 从配置文件的property属性中加载各种组件,解析配置到configuration中
  2. 加载mapper.xml文件,解析SQL语句,封装成MappedStatement对象,配置到configuration中。

3.1.3 解析mapper.xml 文件的过程

从mapperLocations路径去解析里面所有的XML文件,获取SQL内容,可以分位两部分。

3.1.3.1 创建SqlSource

Mybatis会把每个SQL标签封装成SqlSource对象,然后根据SQL语句的不同,又分为动态SQL和静态SQL。其中,静态SQL包含一段String类型的sql语句;而动态SQL则是由一个个SqlNode组成。

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_User_06,第6张

假如我们有这样一个SQL:

<select id="getUserById" resultType="user">
    select * from user 
    <where>
        <if test="uid!=null">
            and uid=#{uid}
        </if>
    </where>
</select>

它对应的SqlSource对象看起来应该是这样的:

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_mybatis_07,第7张

3.1.3.2 创建MappedStatement

XML文件中的每一个SQL标签就对应一个MappedStatement对象,这里面有两个属性很重要:

  • id:全限定类名+方法名组成的ID。
  • sqlSource:当前SQL标签对应的SqlSource对象。

创建完MappedStatement对象,将它缓存到Configuration#mappedStatements中。

Configuration对象就是Mybatis中的大管家,基本所有的配置信息都维护在这里。把所有的XML都解析完成之后,Configuration就包含了所有的SQL信息。

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_User_08,第8张

到目前为止,XML就解析完成了。当我们执行Mybatis方法的时候,就通过全限定类名+方法名找到MappedStatement对象,然后解析里面的SQL内容,执行即可。

3.2 Dao接口代理

3.2.1 配置需要扫描的基本包路径

通过注解的方式配置:

@MapperScan({"com.xxxx.UserDao"})

或者xml的方式配置:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.xxx.UserDao" />
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

3.2.2 扫描并注册bean

它们的作用是一样的。这个过程是通过 org.mybatis.spring.mapper.MapperScannerConfigurer这个类发挥作用的。

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_java_09,第9张

可以看到它实现了几个接口。其中的重点是BeanDefinitionRegistryPostProcessor。它可以动态的注册Bean信息,方法为postProcessBeanDefinitionRegistry()。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
	······
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
// 创建ClassPath扫描器,设置属性
    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中的scan方法最终会调用本类重写的doScan方法:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    ……
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }
    ……
}

经过上面这些步骤,此时已经扫描到了所有的Mapper接口,并将其注册为BeanDefinition对象。

最终的效果是:将包路径下的所有dao类注册到Spring Bean中,并且将它们的beanClass设置为MapperFactoryBean。

这就相当于使用UserDao注册bean时:当前的dao接口在Spring容器中,beanName是userDao,beanClass是MapperFactoryBean.class。故在Spring的IOC初始化的时候,实例化的对象就是MapperFactoryBean对象。

3.3 使用bean

MapperFactoryBean实现了FactoryBean接口,俗称工厂Bean。那么,当我们通过@Autowired注入这个Dao接口的时候,返回的对象就是MapperFactoryBean这个工厂Bean中的getObject()方法对象。

这个getObject()方法干了些什么呢?
它就是通过JDK动态代理,返回了一个Dao接口的代理对象,这个代理对象的处理器是MapperProxy对象。所有,我们通过@Autowired注入Dao接口的时候,注入的就是这个代理对象,我们调用到Dao接口的方法时,则会调用到MapperProxy对象的invoke方法

使用bean:

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    UserDao userDao1;

    public List<User> getUserList(Map<String,Object> map) {
        return userDao1.getUserList(map);
    }
}

我们调用Dao接口方法的时候,实际调用到代理对象的invoke方法。

public class MapperProxy<T> implements InvocationHandler, Serializable {
  ……
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  ……
}

在这里,实际上调用的就是SqlSession里面的东西了。

public class DefaultSqlSession implements SqlSession {

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, 
                wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        }
    }
}

通过statement全限定类型+方法名拿到MappedStatement 对象,然后通过执行器Executor去执行具体SQL并返回:

mybatis 为什么要禁止使用if test mybatis为什么只有接口,mybatis 为什么要禁止使用if test mybatis为什么只有接口_mybatis_10,第10张


https://www.xamrdz.com/lan/5v51957344.html

相关文章: