mybatis-spring原理简析+tkmybatis简单使用

2021-02-04  本文已影响0人  灿烂的GL

一、mybatis

mybatis的使用大家应该熟悉,但是对于怎么实现sql的调用和结果返回一直带有疑问,以我们项目探索如下:
配置文件配置数据库信息如下:

spring.datasource.url=jdbc:mysql://localhost:3306/sqldatabase?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=XXX
spring.datasource.password=XXX
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

启动类上为了扫描到mapper会加上@MapperScan的注解,使spring扫描到你的mapper,或者可以在每个mapper上添加@Mapper注解

@SpringBootApplication
//basePackages里是所有mapper文件的相对路径
@MapperScan(basePackages = {"com.cmcc.web.mapper"})
@EnableCreateCacheAnnotation
@Slf4j
public class Application {
        do something
}

@MapperScan注解如下,

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(tk.mybatis.spring.annotation.MapperScannerRegistrar.class)
public @interface MapperScan {
}

再看看@Import导入的

//EnvironmentAware主要解析配置文件,ResourceLoaderAware主要获取资源加载器,可以获得外部资源文件,ImportBeanDefinitionRegistrar注册bean
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
...something
//主要是装载ClassPathMapperScanner实体类,重写ImportBeanDefinitionRegistrar的registerBeanDefinitions类
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        // this check is needed in Spring 3.1
        if (resourceLoader != null) {
            scanner.setResourceLoader(resourceLoader);
        }

        Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            scanner.setAnnotationClass(annotationClass);
        }

        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            scanner.setMarkerInterface(markerInterface);
        }

        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
            scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
        }

        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
            scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
        }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
        scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
//获取value属性放入basePackages ,方便之后doScan扫描
        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        //优先级 mapperHelperRef > properties > springboot
        String mapperHelperRef = annoAttrs.getString("mapperHelperRef");
        String[] properties = annoAttrs.getStringArray("properties");
        if (StringUtils.hasText(mapperHelperRef)) {
            scanner.setMapperHelperBeanName(mapperHelperRef);
        } else if (properties != null && properties.length > 0) {
            scanner.setMapperProperties(properties);
        } else {
            try {
                scanner.setMapperProperties(this.environment);
            } catch (Exception e) {
                ...something
            }
        }
     //注册filter
        scanner.registerFilters();
     //扫描包路径并注册bd
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }

doScan方法会扫描包路径并注册bd,内方法processBeanDefinitions处理注册后的bd(包括:映射接口类名、sqlSessionFactory、sqlSessionTemplate、mapperFactoryBean、设置属性按类型注入等),spring在进行实例化db的时候会按类型注入的方式找到类型为SqlSessionFactoryBean的进行注入

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

简单总结如下:
在spring中使用mybatis需要SqlSessionFactoryBean和至少一个映射器,SqlSessionFactoryBean会生成SqlSessionFactory,SqlSessionFactory会注入到spring中,里面包含db的环境信息和mybatis的配置文件路径等,我们在使用过程中通过@MapperScan来实现这一步;我们在调用mapper的时候会生成反向代理,mapper的bean即MapperFactoryBean,MapperFactoryBean会生成SqlSession来实现sql的执行和关闭,异常被转化成spring异常以DataAccessException形式抛出。

参考(阅读顺序如下):

1、mybatis-spring中间件逻辑-详细
2、官网

二、 tkmybatis

tkmybatis是在mybatis上做了一层封装的jar包,主要用于单表的增删改查,不能做连表查询,好处是避免使用xml文件。

基本配置:

pom文件:

   <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.4</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.persistence</groupId>
                    <artifactId>persistence-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

实体类---需要加上JPA注解如@Table(name="表名") @Column列名,column里也有一些参数,比如可以插入数据可以为空设置(@Column(name = "size" , nullable = false)),其他的参数可以自己参照方法
dao-----可以继承tkmybatis里mapper的方法


可以继承的mapper.png

以idsmapper为例里面涉及到一些封装方法


image.png
    @DeleteProvider(type = IdsProvider.class, method = "dynamicSQL")
    int deleteByIds(String ids);

IdsProvider里做了sql的拼接,sql调用实现底层和mybatis一样

    public String deleteByIds(MappedStatement ms) {
        final Class<?> entityClass = getEntityClass(ms);
        StringBuilder sql = new StringBuilder();
        sql.append(SqlHelper.deleteFromTable(entityClass, tableName(entityClass)));
        Set<EntityColumn> columnList = EntityHelper.getPKColumns(entityClass);
        if (columnList.size() == 1) {
            EntityColumn column = columnList.iterator().next();
            sql.append(" where ");
            sql.append(column.getColumn());
            sql.append(" in (${_parameter})");
        } else {
            throw new MapperException("继承 deleteByIds 方法的实体类[" + entityClass.getCanonicalName() + "]中必须只有一个带有 @Id 注解的字段");
        }
        return sql.toString();
    }

上边的方法可以用于一些常用增删改查操作,对于复杂查询等操作tkmybatis还提供了Example和Condition 类可以做多条件查询,然后调用ExampleMapper中的方法

@tk.mybatis.mapper.annotation.RegisterMapper
public interface ExampleMapper<T> extends
        SelectByExampleMapper<T>,
        SelectOneByExampleMapper<T>,
        SelectCountByExampleMapper<T>,
        DeleteByExampleMapper<T>,
        UpdateByExampleMapper<T>,
        UpdateByExampleSelectiveMapper<T> {

}

详见Example常见方法
总结:
1、Tkmybatis场景一常用的查询mapper中继承的mapper里的方法(需要注意的是如:IdsMapper<这里是数据库表对应的实体类>)
2、复杂查询等操作借助Example或者Condition

上一篇下一篇

猜你喜欢

热点阅读