千家信息网

mybatis-spring下@MapperScan注解的使用方法

发表于:2025-01-27 作者:千家信息网编辑
千家信息网最后更新 2025年01月27日,这篇文章主要介绍"mybatis-spring下@MapperScan注解的使用方法",在日常操作中,相信很多人在mybatis-spring下@MapperScan注解的使用方法问题上存在疑惑,小编
千家信息网最后更新 2025年01月27日mybatis-spring下@MapperScan注解的使用方法

这篇文章主要介绍"mybatis-spring下@MapperScan注解的使用方法",在日常操作中,相信很多人在mybatis-spring下@MapperScan注解的使用方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"mybatis-spring下@MapperScan注解的使用方法"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

目录
  • mybatis-spring:@MapperScan注解

    • @MapperScan源码

    • MapperScannerRegistrar.class

  • demo: springboot+mybatis

    • 工程代码

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 nameGenerator() default BeanNameGenerator.class;   /**   * 这个注解指定的接口也要被扫描   */  Class annotationClass() default Annotation.class;   /**   * 继承这个接口的接口也要被扫描   */  Class markerInterface() default Class.class;   /**   * 多数据源的时候可能用到这个,后面单独说明这个   */  String sqlSessionTemplateRef() default "";   /**   * 多数据源的时候可能用到这个,后面单独说明这个   */  String sqlSessionFactoryRef() default "";   /**   * 多数据源的时候可能用到这个,后面单独说明这个   */  Class 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这个类的。

可以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 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 generatorClass = annoAttrs.getClass("nameGenerator");    if (!BeanNameGenerator.class.equals(generatorClass)) {      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));    }     Class 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 basePackages = new ArrayList();    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 #59definition.setBeanClass(this.mapperFactoryBean.getClass());definition.getPropertyValues().add("addToConfig", this.addToConfig);

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

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {   ...  @Override  public T getObject() throws Exception {    return getSqlSession().getMapper(this.mapperInterface);  }   @Override  public Class 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;

工程结构

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

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

@Repositorypublic interface UserDao {     boolean saveUser(User user);     List getUsers();     boolean deleteUser(); }

UserServiceImpl.java

@Servicepublic 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 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 getUsers() throws Exception;     boolean deleteUser() throws Exception; }

WebApplication.java

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

UserMapper.xml

             id,age,username                          insert into user        ()        values        (#{id},#{age},#{username})                 DELETE from USER where id = 10     

application.properties

#data source configmysql.driver=com.mysql.jdbc.Drivermysql.username=rootmysql.passwd=123456mysql.url=jdbc:mysql://localhost:3306/testdb?useSSL=false

pom.xml

    4.0.0     com.xuxd    spring-mybatis.demo    1.0-SNAPSHOT             org.springframework.boot        spring-boot-starter-parent        1.5.6.RELEASE                             org.springframework.boot            spring-boot-starter-web                             org.springframework            spring-jdbc            4.3.1.RELEASE                             org.mybatis            mybatis            3.3.1                             org.mybatis            mybatis-spring            1.2.5                             mysql            mysql-connector-java            5.1.43            

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

到此,关于"mybatis-spring下@MapperScan注解的使用方法"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

0