mybatis-spring原理简析+tkmybatis简单使用
一、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形式抛出。
参考(阅读顺序如下):
二、 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