折腾了老久,原来是一个@MapperScan注解放的位置不对,导致对于Mapper接口实例化十分疑惑,记录一下过程
MyBatis和Hibernate不太一样,SQL是需要自行配置,而不像JPA里都已经封装好了,如果SQL比较简单,直接用封装好的接口即可,但是如果十分复杂的SQL,还需要审核,校验,可以通过MyBatis框架来实现持久层
图来自:https://oscimg.oschina.net/oscnet/cefa901a81228be243c28cd1af13c039a00.jpg
用MyBatis-Spring会将MyBatis整合到Spring当中,我这里用的版本Spring 5.2.1,MyBatis 3.5.3,MyBatis-Spring 2.0.3,满足官方的要求,用的注解而不是XML
具体的使用,需要配置两样东西,一个是SqlSessionFactory,用来创建SqlSession的Factory,SqlSession用来对接数据库;另一个是数据映射器Mapper接口
1、在MyBatis-Spring中,可以通过SqlSessionFactoryBean来创建SqlSessionFactory,这里还需要一个DataSource,和JDBC等其它DataSource一样即可
@Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); return factoryBean.getObject(); }
大致流程SqlSessionFactoryBean->getObject()->afterPropertiesSet()->buildSqlSessionFactory()->Configuration->SqlSessionFactory
2、映射器是一个接口,定义一个Mapper接口,一个简单的查询方法,@Select指定具体的SQL
package com.lihuia.mybatis.mapper; import com.lihuia.mybatis.model.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; /** * Copyright (C), 2018-2019 * FileName: UserMapper * Author: lihui * Date: 2019/11/15 */ public interface UserMapper { @Select("select * from user_info where id = #{id}") User getUserById(@Param("id") int id); }
3、对应ORM实体类
package com.lihuia.mybatis.model; import lombok.Builder; /** * Copyright (C), 2018-2019 * FileName: User * Author: lihui * Date: 2019/11/10 */ @Builder public class User { private int id; private String name; private String phone; private int age; @Override public String toString() { return "id=" + id + ", name=" + name + ", phone=" + phone + ", age=" + age; } }
4、映射器的注册,通过MapperFactoryBean可以将映射器注册到Spring
package com.lihuia.mybatis.bean; import com.lihuia.mybatis.mapper.UserMapper; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperFactoryBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; /** * Copyright (C), 2018-2019 * FileName: MyBatisConfig * Author: lihui * Date: 2019/11/15 */ @PropertySource(value = {"classpath:config/db.properties"}) @Configuration public class MyBatisConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DriverManagerDataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); return factoryBean.getObject(); } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); } @Bean public MapperFactoryBean<UserMapper> userMapper() throws Exception { MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class); factoryBean.setSqlSessionFactory(sqlSessionFactory()); return factoryBean; } }
5、测试类
package com.lihuia.mybatis; import javax.annotation.Resource; import com.lihuia.mybatis.bean.MyBatisConfig; import com.lihuia.mybatis.mapper.UserMapper; import lombok.extern.slf4j.Slf4j; import org.testng.annotations.Test; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; /** * Copyright (C), 2018-2019 * FileName: UserTest * Author: lihui * Date: 2019/11/15 */ @ContextConfiguration(classes = {MyBatisConfig.class}) @Slf4j public class UserTest extends AbstractTestNGSpringContextTests { @Resource private JdbcTemplate jdbcTemplate; @Resource private UserMapper userMapper; @Test(description = "测试JDBC") public void jdbcTest() { String sql = "select * from user_info"; System.out.println(jdbcTemplate.queryForList(sql)); } @Test(description = "测试MyBatis") public void myBatisTest() { log.info(userMapper.getUserById(1).toString()); } }
如果是直接通过注入Bean的方式注入UserMapper,那么假如有一大堆的映射器,一个一个的注册注入十分麻烦,因此就和@ComponentScan注解一样有一个扫描映射器的注解@MapperScan,大致如下
@PropertySource(value = {"classpath:config/db.properties"}) @Configuration @MapperScan("com.lihuia.mybatis.mapper") public class MyBatisConfig {
@MapperScan指定Mapper接口类的路径放在配置类上即可,如果还有其它Mapper接口,也能够一并扫描到;但是我之前将这个扫描注解放在了测试类UserTest上,结果程序就会有误,弄了挺久的
首先看下@MapperScan注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan {
里面的@Import注解,可以导入一个类,定义如下
/** * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration. * * @author Michael Lanyon * @author Eduardo Macarron * @author Putthiphong Boonphong * * @see MapperFactoryBean * @see ClassPathMapperScanner * @since 1.2.0 */ public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
根据注释里可以看到ClassPathMapperScanner,继承了ClassPathBeanDefinitionScanner类是Spring提供的一个用于扫描Bean定义配置的基础类,这里覆盖了基类的doScan()方法
/** * Calls the parent search that will search and register all the candidates. Then the registered objects are post * processed to set them as MapperFactoryBeans */ @Override 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; }
DEBUG一下,可以看到这个就是扫描Mapper接口的方法,返回@MapperScan注解的value
具体流程看的迷糊,不太懂,@MapperScan注解扫描必须定义在配置类上面
有空再学习学习,最终Mapper接口的实例化是通过Java动态代理来实现的
参考链接:
https://cofcool.github.io/tech/2018/06/20/mybatis-sourcecode-1#21-%E9%85%8D%E7%BD%AE%E7%B1%BB