mybatis-spring:@MapperScan注解的使用

作者:NetWhite 时间:2021-11-22 09:58:33 

mybatis-spring:@MapperScan注解

demo: springboot+mybatis的示例中,dao层接口使用了注解@MapperScan:指定扫描com.xuxd.demo.dao.UserDao所在包路径下的所有接口类。

本文分析下@MapperScan注解做了哪些动作。

@MapperScan源码


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
 /**
  *缺省属性(==basePackages),basePackages的别名
  */
 String[] value() default {};

/**
  * 哪些包路径下的接口被扫描注册(接口至少有一个方法),具体实现类(非接口)忽略
  */
 String[] basePackages() default {};

/**
  * 指定类所在包下所有接口被扫描注册(接口至少有一个方法),具体实现类(非接口)忽略
  */
 Class<?>[] basePackageClasses() default {};

/**
  * 扫描到的满足条件的接口,首先要把它们相关bean定义注册到spring容器中吧,注册bean定义
  * 的时候,需要定义bean名称,这个是用来自定方生成bean名称的策略组件,个人觉得很少用
  */
 Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

/**
  * 这个注解指定的接口也要被扫描
  */
 Class<? extends Annotation> annotationClass() default Annotation.class;

/**
  * 继承这个接口的接口也要被扫描
  */
 Class<?> markerInterface() default Class.class;

/**
  * 多数据源的时候可能用到这个,后面单独说明这个
  */
 String sqlSessionTemplateRef() default "";

/**
  * 多数据源的时候可能用到这个,后面单独说明这个
  */
 String sqlSessionFactoryRef() default "";

/**
  * 多数据源的时候可能用到这个,后面单独说明这个
  */
 Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

这个注解的重点是@Import(MapperScannerRegistrar.class)

使用这个注解导入MapperScannerRegistrar主要完成两件事:

1. 扫描指定接口

2. 注册这些接口的bean定义到spring容器

接下来进入MapperScannerRegistrar类看下是如何完成这两动作:

MapperScannerRegistrar.class


public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

这个类实现了ImportBeanDefinitionRegistrar接口:


public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
  AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

@MapperScan注解类上使用了@Import注解导入了这个接口的实现类(MapperScannerRegistrar.class),因此spring解析MybatisConfig(源码:demo: springboot+mybatis)这个类的时候,解析到这个类上使用了注解@MapperScan,从MapperScan注解类上(注解都是一个接口,java会创建代理类)发现了@Import注解及MapperScannerRegistrar类(因为Import注解是导入配置类的)。

在加载MybatisConfig配置类的bean定义时候,找到了ImportBeanDefinitionRegistrar 的实现类MapperScannerRegistrar,便会回调这个MapperScannerRegistrar的registerBeanDefinitions方法。

总之一句话:

在加载配置类MybatisConfig的bean定义的时候,会调用与之看起来有点关系的MapperScannerRegistrar的registerBeanDefinitions方法。

MapperScannerRegistrar的registerBeanDefinitions方法第一个参数importingClassMetadata指的是MybatisConfig这个类的。

mybatis-spring:@MapperScan注解的使用

可以debug,看这个参数的信息。


 @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"));

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));
   }
   scanner.registerFilters();
   scanner.doScan(StringUtils.toStringArray(basePackages));
 }

看这个方法的源码,主要完成2件事:

1. 解析MapperScan注解的各个字段的值 ,用以初始化类路径扫描器

2. 确定扫描类路径下哪些接口,如指定的包路径、指定的类所在包路径。上面倒数第2行代码,注册过滤器,用来指定包含哪些注解或接口的扫描(@MapperScan的annotationClass的markerInterface属性,如果设置的话)

因此,重点是最后一行代码doScan的调用。

这里不贴源码了,前文提到,MapperScannerRegistrar主要完成两件事,都会在这里完成,解析包路径,扫描指定接口并注册bean定义到spring容器。


definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);

在ClassPathMapperScanner类的processBeanDefinitions方法内看到这里注册的一个spring的工厂bean:


public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
 ...
 @Override
 public T getObject() throws Exception {
   return getSqlSession().getMapper(this.mapperInterface);
 }

@Override
 public Class<T> getObjectType() {
   return this.mapperInterface;
 }

@Override
 public boolean isSingleton() {
   return true;
 }
...
}

大部分代码删除了, 只留下这几个说明。

不了解 spring的FactoryBean的建议查看下相关文档。

这里用直白的话说,就是:

我在service层需要注入这个Dao层接口的bean(比如demo: springboot+mybatis中UserServiceImpl类的UserDao字段的自动注入),依据类型注入。

spring在自己的容器里翻呀翻,如果是普通bean,一看和这个接口类型(UserDao)都不匹配就换一个,找到了这个工厂bean,一看是工厂bean,就不能直接做类型匹配了,而是调用getObjectType方法,把返回的类型和需要被注入字段的类型一比较,正好匹配(都是UserDao类型),就调用这个工厂bean的getObject方法返回这个对象,然后通过反射等操作,把这个值注入到这个字段中。而调用getObject方法,其实就是我们平常直接用mybatis的接口返回的一个MapperProxy的代理对象的操作了。

demo: springboot+mybatis

最近因工作原因,需要研究下spring的事务部分和mybatis的多数据源的源码实现,这样才能更容易的在代码层面通过扩展/重写等方式去定制自己的实现。

以前虽然用过几次mybatis,但是却一直没抽出时间认真翻看下源码,趁这次机会,花点时间研究下,顺便做个笔记。

关于看源码,我向来是觉得只有一步步去debug整个流程,查看每一步的数据流向和数据状态,才会有个更清晰的深知。如果只是看的话,有些源码中各种继承、适配、代理、装饰等,会分不清当前使用的到底是哪个类。

于是乎,所谓工欲善其事,必先利其器。先搭建个极简单的mybatis的工程环境,用来调试源码。

这个工程用了spring boot+mybatis。mybatis采用java config的形式(是真心不喜欢xml配置,所以源码研究上也会避开xml的加载)

后面博文关于分析描述就会针对这个工程的配置来说了。

另外,代码中关于spring事务的注解先注释了。

说了这么多,是希望缓解自己又写了篇这么没技术含量的博客的尴尬,哎,最近这段时间写的博客确实有些凑数了。

工程代码

数据库脚本:


CREATE DATABASE `testdb` /*!40100 DEFAULT CHARACTER SET utf8 */

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `age` int(11) NOT NULL,
 `username` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

工程结构

mybatis-spring:@MapperScan注解的使用

按工程结构,列一下文件代码:

User.java


public class User {
   int id;
   int age;
   String username;
   public User() {
   }

public User(int id, int age, String username) {
       this.id = id;
       this.age = age;
       this.username = username;
   }

public int getId() {
       return id;
   }

public void setId(int id) {
       this.id = id;
   }

public int getAge() {
       return age;
   }

public void setAge(int age) {
       this.age = age;
   }

public String getUsername() {
       return username;
   }

public void setUsername(String username) {
       this.username = username;
   }

@Override
   public String toString() {
       return "User{" +
               "id=" + id +
               ", age=" + age +
               ", username='" + username + '\'' +
               '}';
   }
}

MybatisConfig.java


@Configuration
@MapperScan(basePackageClasses = {UserDao.class})
//@EnableTransactionManagement //启用spring事务
public class MybatisConfig {
   @Autowired
   private Environment environment;

// 数据源配置
   @Bean
   public DataSource dataSource() {
       // mybatis自带的一个简易数据库连接池,只是为了debug代码,这个就不关心了
       PooledDataSource pooledDataSource = new PooledDataSource();
       pooledDataSource.setDriver(environment.getProperty("mysql.driver"));
       pooledDataSource.setUsername(environment.getProperty("mysql.username"));
       pooledDataSource.setPassword(environment.getProperty("mysql.passwd"));
       pooledDataSource.setUrl(environment.getProperty("mysql.url"));
       return pooledDataSource;
   }

// spring事务管理的基础bean,事务部分会用到
   @Bean
   public PlatformTransactionManager transactionManager(DataSource dataSource) {
       DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
       transactionManager.setDataSource(dataSource);
       return transactionManager;
   }

@Bean
   public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException {
       SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
       sqlSessionFactoryBean.setDataSource(dataSource);
       ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
       sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath:/mapper/*.xml"));
       return sqlSessionFactoryBean;
   }
}

UserController.java


@RestController
@RequestMapping("/user")
public class UserController {
   @Autowired
   IUserService userService;
   @GetMapping("/save")
   public String saveUser() {
       User user = new User(10, 100, "test user");
       try {
           return userService.saveUser(user) ? "save success" : "save fail";
       } catch (Exception ignore) {
           ignore.printStackTrace();// 不打印日志了,堆栈信息直接打到控制台看
           return "save error: " + ignore.getMessage();
       }
   }

@GetMapping("/list")
   public String getUsers() throws Exception {
       return userService.getUsers().toString();
   }

@GetMapping("/delete")
   public String deleteUser() throws Exception {
       return userService.deleteUser() ? "delete success" : "delete fail";
   }
}

UserDao.java


@Repository
public interface UserDao {
   boolean saveUser(User user);
   List<User> getUsers();
   boolean deleteUser();
}

UserServiceImpl.java


@Service
public class UserServiceImpl implements IUserService {
   @Autowired
   UserDao userDao;
   //@Transactional //指定这个方法的事务属性
   @Override
   public boolean saveUser(User user) throws Exception {
       boolean success = userDao.saveUser(user);
       // spring事务能力测试的时候,使用下面这段代码
       /*if (true) {
           throw new RuntimeException();
       }*/
       return success;
   }

@Override
   public List<User> getUsers() throws Exception {
       return userDao.getUsers();
   }

@Override
   public boolean deleteUser() throws Exception {
       return userDao.deleteUser();
   }
}

IUserService.java


public interface IUserService {
   boolean saveUser(User user) throws Exception;
   List<User> getUsers() throws Exception;
   boolean deleteUser() throws Exception;
}

WebApplication.java


@SpringBootApplication
public class WebApplication {
   public static void main(String[] args) {
       SpringApplication.run(WebApplication.class, args);
   }
}

UserMapper.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuxd.demo.dao.UserDao">

<sql id="user_column">
       id,age,username
   </sql>

<select id="getUsers" resultType="com.xuxd.demo.beans.User">
       select * from user
   </select>

<!--  增加用户 -->
   <insert id="saveUser" parameterType="com.xuxd.demo.beans.User">
       insert into user
       (<include refid="user_column"/>)
       values
       (#{id},#{age},#{username})
   </insert>

<delete id="deleteUser">
       DELETE from USER where id = 10
   </delete>

</mapper>

application.properties


#data source config
mysql.driver=com.mysql.jdbc.Driver
mysql.username=root
mysql.passwd=123456
mysql.url=jdbc:mysql://localhost:3306/testdb?useSSL=false

pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

<groupId>com.xuxd</groupId>
   <artifactId>spring-mybatis.demo</artifactId>
   <version>1.0-SNAPSHOT</version>

<parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>1.5.6.RELEASE</version>
   </parent>

<dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

<dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-jdbc</artifactId>
           <version>4.3.1.RELEASE</version>
       </dependency>

<dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.3.1</version>
       </dependency>

<dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis-spring</artifactId>
           <version>1.2.5</version>
       </dependency>

<dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>5.1.43</version>
       </dependency>
   </dependencies>
</project>

后续就用这个工程debug源码了。

来源:https://blog.csdn.net/x763795151/article/details/99899471

标签:mybatis,spring,@MapperScan,注解
0
投稿

猜你喜欢

  • 解析c#操作excel后关闭excel.exe的方法

    2021-08-14 16:55:22
  • Java利用位运算实现乘法运算详解

    2023-03-19 20:29:24
  • Java之Algorithm_analysis案例详解

    2022-03-07 01:34:50
  • C#利用Random得随机数求均值、方差、正态分布的方法

    2022-08-28 14:26:37
  • Java 面试题基础知识集锦

    2022-05-25 00:01:58
  • Java实现XML文件学生通讯录

    2023-07-23 19:21:49
  • C#中csv文件与DataTable互相导入处理实例解析

    2023-01-16 19:32:35
  • C#实现23种常见的设计模式的示例详解

    2022-10-19 20:28:29
  • WPF基于物理像素绘制图形

    2022-01-06 20:25:18
  • C#绘制飞行棋地图小程序

    2021-11-20 23:19:10
  • edittext + listview 实现搜索listview中的内容方法(推荐)

    2022-03-11 21:41:57
  • 详谈Java中的事件监听机制

    2022-08-05 00:29:30
  • Flutter简洁实用的图片编辑器的实现

    2021-10-31 08:30:44
  • Java concurrency线程池之线程池原理(四)_动力节点Java学院整理

    2023-08-12 21:13:13
  • Android 反射注解与动态代理综合使用详解

    2023-01-13 12:30:56
  • C#多线程学习之Thread、ThreadPool、Task、Parallel四者区别

    2023-08-27 05:32:14
  • Unity调取移动端的麦克风进行录音并播放

    2023-06-04 22:18:05
  • java web项目里ehcache.xml介绍

    2022-02-25 20:46:25
  • Java类的初始化顺序知识点总结

    2021-09-23 23:19:26
  • C#中Linq的入门教程

    2023-12-23 16:20:52
  • asp之家 软件编程 m.aspxhome.com