1. mybatis基本构成
知识点:
- SqlSessionFactoryBuilder是SqlSessionFactory的构建器
- SqlSessionFactory用于生成SqlSession
- SqlSession可以直接执行sql返回结果,也可以通过其获取Mapper接口
以下代码均来自mybatis官方示例
示例1-使用SqlSession执行获取数据:
@Test
public void shouldSelectAllAuthors() throws Exception {
SqlSession session = sqlMapper.openSession();
try {
List<Author> authors = session.selectList("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAllAuthors");
assertEquals(2, authors.size());
} finally {
session.close();
}
}
使用session.selectList相关接口属于功能性代码,可以使用Mapper的方式,更加符合面向对象思维,具备更强的代码可读性
示例2-使用Mapper执行获取数据:
@Test
public void shouldSelectAuthorsUsingMapperClass() {
SqlSession session = sqlMapper.openSession();
try {
AuthorMapper mapper = session.getMapper(AuthorMapper.class);
List<Author> authors = mapper.selectAllAuthors();
assertEquals(2, authors.size());
} finally {
session.close();
}
}
2. 配置(configuration)
2.1 别名(typeAlias)
参考:
https://www.cnblogs.com/lxcmyf/p/6444120.html
示例3-别名的使用:
<configuration>
...
<typeAliases>
<typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
</typeAliases>
</configuration>
<mapper namespace="org.apache.ibatis.domain.blog.mappers.AuthorMapper">
<select id="selectAllAuthors" resultType="Author">
select * from author
</select>
</mapper>
2.2 类型处理器(TypeHandler)
TypeHandler也可以使用别名来注册,并在使用时也使用别名
示例4-使用类型处理器别名:
<resultMap id="appInfo" type="xxx.XxxEntity">
<id column="id" property="id" typeHandler="idHandler"/>
<result column="created_at" property="createdAt"/>
<result column="updated_at" property="updatedAt"/>
</resultMap>
当使用多个idHandler的时候,如果该TypeHandler全局未注册时,则会被实例化多次,可以在Configuration中全局注册TypeHandler
示例5-注册类型处理器:
<typeHandlers>
<package name="com.xxx.xxx.typeHandlers"/>
</typeHandlers>
https://blog.csdn.net/chenbaige/article/details/72568959
3. Mapper
3.1 Mapper的使用
- 定义Mapper接口
- 在Config中添加Mapper映射xml
示例6-Mapper的定义:
public interface AuthorMapper {
void selectAuthor(int id, ResultHandler handler);
}
<configuration>
...
<mappers>
<mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
</mappers>
</configuration>
其在解析过程中内部调用了Configuration类中MapperRegistry的addMapper相关方法
3.2 Mapper的生成
Mapper的声明定义是一个接口,所以其生成的示例其实是一个代理对象
见下图执行流程
实际上走了一圈,最终还是回到了示例1的代码
4. mybatis-spring
spring很大一部分工作是帮助简化配置
4.1 SqlSessionFactoryBean用于构建SqlSessionFactory,其主要是开放属性来封装Configuration,最终传递给SqlSessionFactoryBuilder生成SqlSessionFactory
以下是为便利的改进
1.封装了DataSource属性,隐藏了内部细节
2.MapperLocations以扫描目录的形式简化了apper的配置方式
示例7-mapper在xml中配置
<mappers>
<mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/CachedAuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/PostMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/NestedBlogMapper.xml"/>
</mappers>
如实1个项目中有超过20个以上的配置,那就会比较麻烦,SqlSessionFactoryBean刚好简化了该配置
示例8-mapper改进后的配置
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception{
SqlSessionFactoryBean bean= new SqlSessionFactoryBean();
bean.setDataSource(dataSource());
bean.setConfigLocation(new ClassPathResource("mybatis/config.xml"));
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/**/*Mapper.xml");
bean.setMapperLocations(resources);
return bean;
}
4.2 MapperScannerConfigurer
SqlSessionFactoryBean解决了SqlSessionFactory创建的问题,再来看Mapper的使用问题,在实际场景中Mapper一般在Service中使用,如下示例
示例9-mapper在Spring中的实际使用方式
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
public List<Article> getArticleList() {
return articleMapper.getArticleList();
}
}
这里有一个问题ArticleMapper是如何注入到spring中来的,比较简单的方式就是采用以下流程SqlSessionFactory->SqlSession->getMapper的方式
示例10-手动创建Mapper
@Bean
public ArticleMapper createArticleMapper(SqlSessionFactory factory) {
return factory.openSession().getMapper(ArticleMapper.class);
}
@Bean
public MediaMapper createMediaMapper(SqlSessionFactory factory) {
return factory.openSession().getMapper(MediaMapper.class);
}
MapperScannerConfigurer跟SqlSessionFactoryBean一样,解决了自动扫描的问题,如下图
processBeanDefinitions方法是核心处理方法
示例11-processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
最终到了MapperFactoryBean,获取到的对象还是调用了SqlSession的getMapper方法
示例11-MapperScannerConfigurer配置
@Bean("mapperScannerConfigurer")
@DependsOn(value= {"sqlSessionFactory"})
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("xxx.xxx.dal");
configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
return configurer;
}
4.3 MapperScan注解
其效果与MapperScannerConfigurer一样,方式不同,优先推荐使用注解配置的方式
4.4 SqlSessionTemplate
先看下面示例:
示例12-session.close
@Test
public void shouldSelectAllPostsUsingMapperClass() throws Exception {
SqlSession session = sqlMapper.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
List<Map> posts = mapper.selectAllPosts();
assertEquals(5, posts.size());
} finally {
session.close();
}
}
每次使用完毕后都要调用一下session.close.这个时候肯定又要发挥动态代理的作用
示例13-SqlSessionTemplate
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
/**
* {@inheritDoc}
*/
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.<T> selectOne(statement);
}
其实写了这么多,刚开始也只是对于Mapper接口如何生成类比较感兴趣,引起的一连串流程问题
参考:
深入浅出MyBatis技术原理与实战
https://www.cnblogs.com/ChenLLang/p/5307590.html