MyBatis使用与源码解析

文章目录

一、MyBatis核心概念

基本概念

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

其它持久层解决方案对比:
    JDBC
    DataUtils
    JdbcTemplate
    Hibernate

核心对象的作用域与生命周期

简单示例:
UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tuling.mybatis.dao.UserMapper">
    <select id="selectUser" resultType="com.tuling.mybatis.dao.User">
    select * from User where id = #{id}
  </select>
</mapper>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.0.147/luban"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--<mapper resource="com/tuling/mybatis/dao/xml/UserMapper.xml"/>-->
        <mapper class="com.tuling.mybatis.dao.UserMapper"></mapper>
    </mappers>
</configuration>

示例:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
User result = session.selectOne("com.tuling.mybatis.dao.UserMapper.selectUser", 1);
System.out.println(result.toString());

SqlSessionFactoryBuilder:

用于构建会话工厂,基于 config.xml environment 、props 构建会话工厂,构建完成后即可丢弃。

SqlSessionFactory:

用于生成会话的工厂,作用于整个应用运行期间,一般不需要构造多个工厂对像

SqlSession:

作用于单次会话,如WEB一次请求期间,不能用作于某个对像属性,也不能在多个线程间共享,因为它是线程不安全的。

接口式编程

由于每次调用时都去找对应用 statement 以及拼装参数,使用上不是特别友好,myBatis 引入了接口的机制,将接口与mapper.xml 的namespace 名称绑定,MyBatis就可以根据ASM工具动态构建该接口的实例。
mapper 映射器接口实例
通过 session.getMapper(Class type) 就可以获取mapper 实例,该实例一般作用于方法域。

二、全局的 configuration 配置

属性

properties 元素可以通过 resource 或url 加载外部 properties文件中的属性,也可以直接设置property 属性。然后在xml中就可以通过${属性名}进行引用替换。

<properties resource="app.properties" url="">
        <property name="jdbc.driver" value="com.oracle.jdbc.Driver"/>
</properties>
resource=app.properties #从class path中加载
url=file:///G:/git/tuling-mybatis/src/main/resources/app.properties #基于url加载

引用属性方式:${jdbc.user}
从 MyBatis 3.4.2 开始,位符指定一个默认值。例如:${jdbc.user:root}

环境配置

一个项目经常需要在例如开发坏境、测试环境、预演环境、生产环境中等不同环境中进行部署,每个环境所对应的参数是不一样的,myBatis 中可以通过 environment 来设置不同环境的属性。

   <environments default="${default.environment}">
        <environment id="test">
            <!--type=JDBC|MANAGED-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- type=UNPOOLED|POOLED|JNDI-->
            <dataSource type="UNPOOLED">
                <property name="driver" value="${jdbc.driver}"/>
            </dataSource>
        </environment>
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
            </dataSource>
        </environment>
    </environments>

可通过 SqlSessionFactoryBuilder.build( environment) 来指定初始化哪套环境。

设置

设置MyBatis 全局参数,约定myBatis 的全局行为

<settings>
<!-- 开启二级缓存-->
  <setting name="cacheEnabled" value="true"/>
  <!-- 开启驼峰命名适配-->
  <setting name="mapUnderscoreToCamelCase" value="true"/>
<settings>

示例

驼峰命名开启与关闭:
    尝试开关 mapUnderscoreToCamelCase 属性 来观察Account 数据查询情况。

别名

在myBatis 中经常会用到 java 中类型,如sql 块中中 parameterType 参数引用中 javaType 结果集映射的javaType ,都要使用java 全路径名,可以通过

<typeAliases>
    <typeAlias type="com.tuling.mybatis.dao.Account" alias="account"/>
    <package name="com.tuling.mybatis.dao"  />
</typeAliases>

提示:建议不要设置。因为常用的类 mybatis 已经内置别名,而自定义的类设置别反而不好去找,影响阅读。

类型处理器

持久层框架其中比较重要的工作就是处理数据的映射转换,把java 类型转换成jdbc 类型的参数,又需要把jdbc 类型的结果集转换成java 类型。在mybatis 中是通过 TypeHandler 接口来实现的。

在这里插入图片描述

可以看到 typeHandler 就是两个作用 设置参数 与获取结果。
你可以设置自定义处理器

<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"  />
</typeHandlers>
可以通过以下两种方式指定处理的范围
	javaType="long", jdbcType="Date"
	@MappedJdbcTypes( jdbc类型) @MappedTypes  (java类型)

示例:
long 类型时间戳转换成 日期类型

// 添加定义处理类:
@MappedJdbcTypes(JdbcType.TIMESTAMP)
@MappedTypes(Long.class)
public class LongTimeHandler extends BaseTypeHandler<Long> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
        ps.setDate(i, new Date(parameter));
    }

    @Override
    public Long getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getDate(columnName).getTime();
    }

    @Override
    public Long getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getDate(columnIndex).getTime();
    }

    @Override
    public Long getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getDate(columnIndex).getTime();
    }
}

在resultMap中指定 typeHandler:

<resultMap id="account2" type="com.tuling.mybatis.dao.Account">
      <result property="createTimestamp" column="createTimestamp"
              typeHandler="com.tuling.mybatis.dao.LongTimeHandler"/>
  </resultMap>
  <select id="selectById2" resultMap="account2">
  select a.*,a.createTime as createTimestamp from account a where id = #{id}
</select>

mappers映谢器

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper  url="http://www.xxx.com/xml/BlogMapper.xml"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
 <package name="org.mybatis.builder"/>
</mappers>
加载方式:
    resource 基于classPath 加载xml文件
    url:基于资源定位加载xml 文件
    class:基于接口加载
    package :扫描包下所有class 然后进行加载
约定规则:
    mapper 中的 namespace必须与对应的接口名称对应。
    通过 class 或package 中加载时 .xml 文件必须与接口在同一级目录。

三、mapper 文件

sql语句块statement

通过原生JDBC写DAO的年代 ,程序员最怕莫过于 拼接SQL语句,拼接参数与设置返回结果集,Hibernate 将拼接SQL时代成为过去,通过ORM映谢,完全不需要处理任何SQL,但这又带来了新的问题就是。无法编写自定义SQL从而丧失了灵活活及更好的性能。MyBatis 通过 mapper 映射SQL很好解决了这一点。它无需在JAVA代码中拼接SQL,而是将其移至mapper 文件集中处理SQL节约了大量的开发时间。

Mapper中的元素:

cache – 对给定命名空间的缓存配置。
resultMap – 结果集映射。
sql – 可被其他语句引用的可重用语句块。
insert – 插入语句
update – 更新语句
delete –删除语句
select – 查询语句
select 用法及属性

示例:

<select id="selectById" resultType="com.tuling.mybatis.dao.Account">
  select * from account where id = #{id}
</select>

属性:

<select
  id="selectById"        <!-- 语句块的唯一标识 与接口中方法名称对应 -->
  parameterType="User"   <!--参数java类型-->
  resultType="hashmap"   <!--返回结果java类型-->
  resultMap="userResultMap" <!--返回结果映射-->
  flushCache="false"      <!--true 每次调用都会刷新 一二级缓存-->
  useCache="true"         <!--true 是否保存至二级缓存当中去-->
  timeout="10"
  statementType= PREPARED">
insert&update&delete 用法
<insert
  id="addUser"   <!-- 语句块的唯一标识 与接口中方法名称对应 -->
  parameterType="User"   <!--参数java类型-->
  flushCache="true"  <!--true 每次调用都会刷新 一二级缓存-->
  statementType="PREPARED" <执行类型>
  keyProperty=""      <!--主键对应的java 属性,多个用 逗号分割-->
  keyColumn=""        <!--主键列,多个用 逗号分割-->
  useGeneratedKeys=""  <!--插入成功后将 将值回设至 原参数->
  timeout="20">

示例:

<insert id="addUser" keyColumn="id" keyProperty="id" useGeneratedKeys="true"
        parameterType="com.tuling.mybatis.dao.User">
    insert into  user (name,updateTime,createTime) values (#{name},#{updateTime},#{createTime})
</insert>

参数映射

参数映射是最强大功能之一,基可以通过以下方式进行引用
单个简单参数引用 :如果方法中只有一个参数可通过任意名称 进行引用
多个简单参数引用:通过参数下标引用 #{arg0} #{arg1}  或 #{param1} ,#{param2}
对像属性引用: 直接通过对象属性名称引用,嵌套对像通过. 号进行引用
map key值引用:
变量名称引用(需要jdk1.8支持) :通过方法中参数名称引用,需要jdk1.8支持,且在编译时必须加上 -parameters 编译命令

在idea 中添加 编译参数

在maven中添加 编译参数

注:一但可通过变量名称引入不在支持arg0获取!
参数引用 相关属性

javaType=int, #参数java类型
jdbcType=NUMERIC,# jdbc类型
typeHandler=MyTypeHandler#  指定类型处理器

参数拼接${}

基于#的参数引用 其原理是通过 ?占位其通过预处理能获得更好的性能 和安全性(防止SQL注入)但有些需求是通过?占位无法实现的,比如在一些分库分表的场景中我们需要 动态的拼接表结构。比如某系统日志表是按年进行切割的 2018_systemlog,2019_systemlog这时就可以通过
示例:

@Select("SELECT * FROM ${table} WHERE id = #{id}")
User selectByTable(String table, int id);

结果集映射

结果集映射是指 将resultSet 中内容封装转换成java对像,在纯jdbc时代全部都是用调用resultSet的getXXX(columnName) 来获取属性并封装。代码量大,编程效率低尤其当数据模型是1对多,或多对多这种复杂关系,这种封装代码将会变得非常复杂。结果集映射就是为解决这个问题 通过resultMap 集中处理 结果集与JAVA对像的关系。

结果集自动映射

在select 中指定 resultType=“” 后无需要任何配置 myBatis 会基于 resultType中的JAVa类型及属性自动推断生成 一个隐示的resultMap 从而完成结果映谢

resultMap

但有时jdbc 并不是与java Bean 完全贴合这时就需要手动设置resultMap

<resultMap id="account2" type="com.tuling.mybatis.dao.Account">
    <id property="id"/>
    <result property="createTimestamp" column="createTimestamp"
            typeHandler="com.tuling.mybatis.dao.LongTimeHandler"/>
</resultMap>

这时在select元素中用 resultMap =“account2” 即可引用该map映射。

基本元素与属性
    ID:用于结果集中的唯一标识
    result:设置一个某通过字段
        property:
        jdbcType:
        javaType:
        column:
        typeHandler:
嵌套结果映射
关联 association

示例:

<resultMap id="accountAndUser" type="com.tuling.mybatis.dao.Account">
    <id property="id" column="id"/>
    <association property="user" javaType="com.tuling.mybatis.dao.User">
        <id property="id" column="user_id"/>
        <result property="name" column="userName"/>
    </association>
</resultMap>
<select id="selectAccountAndUser" resultMap="accountAndUser">
    SELECT a.*, b.name userName from account a,user b where a.user_id=b.id
</select>
引入外部Select
<!--基于多次查询拼装引入 -->
<resultMap id="accountAndUser2" type="com.tuling.mybatis.dao.Account">
    <id property="id" column="id"/>
    <association property="user" javaType="com.tuling.mybatis.dao.User" select="selectUser"
                 column="user_id">
    </association>
</resultMap>

<select id="selectUser" resultType="com.tuling.mybatis.dao.User">
    select * from user  where id = #{id}
</select>
集合collection

四、1, 2 级缓存处理

1级缓存使用场景

订单表与会员表是存在一对多的关系 为了尽可能减少join 查询,进行了分阶段查询,即先查询出订单表,在根据member_id 字段查询出会员表,最后进行数据整合 。如果订单表中存在重复的member_id,就会出现很多没必要的重复查询。
针对这种情况myBatis 通过1缓存来实现,在同一次查询会话中如果出现相同的语句及参数,就会从缓存中取出不在走数据库查询。1级缓存只能作用于查询会话中 所以也叫做会话缓存。

示例:

LabelMapper mapper = session.getMapper(LabelMapper.class);
Label label = mapper.getById(23);
Label label2 = mapper.getById(23);
Label label3 = sqlSessionFactory.openSession().getMapper(LabelMapper.class).getById(23);
Label label4 = session.getMapper(Label2Mapper.class).getById(23);
System.out.println(label == label2);
System.out.println(label3 == label2);
System.out.println(label4 == label2);

一级缓存的使用条件:

1.必须是相同的SQL和参数
2.必须是相同的会话
3.必须是相同的namespace 即同一个mapper
4.必须是相同的statement 即同一个mapper 接口中的同一个方法
5.查询语句中间没有执行session.clearCache() 方法
6.查询语句中间没有执行 insert update delete 方法(无论变动记录是否与 缓存数据有无关系)

一级缓存源码解析:

缓存获取 :

>mapper.mapper.selectById(23)
 >org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
  >org.apache.ibatis.executor.CachingExecutor#query()
   >org.apache.ibatis.executor.BaseExecutor#query() 142L
    >org.apache.ibatis.cache.impl.PerpetualCache#getObject 55L

缓存的存储:

>mapper.mapper.selectById(23)
 >org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
  >org.apache.ibatis.executor.CachingExecutor#query()
   >org.apache.ibatis.executor.BaseExecutor#query() 142L
    >org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
     >org.apache.ibatis.cache.impl.PerpetualCache#putObject

通过对clearCache 作为入口我们可能追踪到 一级缓存的实现 PerpetualCache

>org.apache.ibatis.session.defaults.DefaultSqlSession#clearCache
  >org.apache.ibatis.executor.CachingExecutor#clearLocalCache
     >org.apache.ibatis.executor.BaseExecutor#clearLocalCache
        >org.apache.ibatis.cache.impl.PerpetualCache#clear

提问:
在查询时另一个会话并发去修改查询的数据,一级缓存是否会生效?如果生效是否就会导致数据不正确?

2级缓存使用场景:

业务系统中存在很多的静态数据如,字典表、菜单表、权限表等,这些数据的特性是不会轻易修改但又是查询的热点数据。一级缓存针对的是同一个会话当中相同SQL,并不适合这情热点数据的缓存场景。为了解决这个问题引入了二级缓存,它脱离于会话之外。

2级缓存示例:

@CacheNamespace()
public interface LabelMapper {
    @Select("select * from t_label where id =#{id}")
    Label getById(Integer id);
}

属性说明:

@CacheNamespace(
        implementation = PerpetualCache.class, //  缓存实现 Cache接口 实现类
        eviction = LruCache.class,// 缓存算法
        flushInterval = 60000, // 刷新间隔时间 毫秒
        size = 1024,   // 最大缓存引用对象
        readWrite = true, // 是否可写
        blocking = false  // 是否阻塞
)

2级缓存使用条件:

1.当会话提交或关闭之后才会填充二级缓存
2.必须是在同一个命名空间之下
3.必须是相同的statement 即同一个mapper 接口中的同一个方法
4.必须是相同的SQL语句和参数
5.如果readWrite=true ,实体对像必须实现Serializable 接口

2级缓存清除条件:

1.xml中配置的update 不能清空 @CacheNamespace 中的缓存数据
2.只有修改会话提交之后 才会执行清空操作
3.任何一种增删改操作 都会清空整个namespace 中的缓存

2级缓存源码解析:

清除缓存!

>org.apache.ibatis.session.defaults.DefaultSqlSession#selectList() 147L
  >org.apache.ibatis.executor.CachingExecutor#query()81L
    >org.apache.ibatis.executor.CachingExecutor#query()95L
     >org.apache.ibatis.executor.CachingExecutor#flushCacheIfRequired() 164L //清除缓存

获取缓存关键源码!

>org.apache.ibatis.cache.TransactionalCacheManager#getObject 
 >org.apache.ibatis.cache.decorators.TransactionalCache#getObject
  >org.apache.ibatis.cache.decorators.SynchronizedCache#getObject
   >org.apache.ibatis.cache.decorators.LoggingCache#getObject
    >org.apache.ibatis.cache.decorators.SerializedCache#getObject
     >org.apache.ibatis.cache.decorators.ScheduledCache#getObject
      >org.apache.ibatis.cache.decorators.LruCache#getObject
       >org.apache.ibatis.cache.impl.PerpetualCache#getObject

保存2级缓存 !

 >org.apache.ibatis.executor.CachingExecutor#close
  >org.apache.ibatis.cache.TransactionalCacheManager#commit
   >org.apache.ibatis.cache.decorators.TransactionalCache#flushPendingEntries
    >org.apache.ibatis.cache.decorators.SynchronizedCache#putObject
     >org.apache.ibatis.cache.decorators.LoggingCache#putObject
      >org.apache.ibatis.cache.decorators.SerializedCache#putObject
       >org.apache.ibatis.cache.decorators.ScheduledCache#putObject
        >org.apache.ibatis.cache.decorators.LruCache#putObject
         >org.apache.ibatis.cache.impl.PerpetualCache#putObject

五、执行流程解析

配置文件解析 configuration:

讲解解析流程之前先回顾一下myBatis 中配置文件的结构:
mybatis-config.xml

<configuration>
  <properties/>
  <settting/>
  <typeHandlers/>
  <..../>
  <mappers/>
</configuration>

mybatis-mapper.xml

<mapper > 
  <cache/>
  <resultMap/>
  <select/>  
  <update/> 
  <delete/> 
  <insert/> 
</mapper>

配置文件的解析流程即是将上述XML描述元素转换成对应的JAVA对像过程,其最终转换对像及其关系如下图:

配置元素解析构建器

>org.apache.ibatis.builder.xml.XMLConfigBuilder
 >org.apache.ibatis.builder.xml.XMLMapperBuilder
  >org.apache.ibatis.builder.xml.XMLStatementBuilder
   >org.apache.ibatis.builder.SqlSourceBuilder
    >org.apache.ibatis.scripting.xmltags.XMLScriptBuilder
 >org.apache.ibatis.builder.annotation.MapperAnnotationBuilder

sql statement 构建流程源码:

>org.apache.ibatis.session.SqlSessionFactoryBuilder#build()
//1.Config.xml 文件解析
 >org.apache.ibatis.builder.xml.XMLConfigBuilder#parse()
  >org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration()
   >org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement()
//2.Mapper.xml 文件解析
    >org.apache.ibatis.builder.xml.XMLMapperBuilder#parse()
>org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement()
>org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext()
//3.Statemen sql块解析
  >org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
>org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement()
//4.动态SQL脚本解析
>org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource()
>org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode()
>org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags()

会话创建 SqlSession

首先我们还是先来了解一下会话对像的组成结构如下图:

会话构建源码解析:

>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(boolean)
>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
>org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory#newTransaction()
 >org.apache.ibatis.session.Configuration#newExecutor()
 >org.apache.ibatis.executor.SimpleExecutor#SimpleExecutor
 >org.apache.ibatis.executor.CachingExecutor#CachingExecutor
  //执行器插件包装
 >org.apache.ibatis.plugin.InterceptorChain#pluginAll(executor)
>org.apache.ibatis.session.defaults.DefaultSqlSession#DefaultSqlSession()

方法执行 StatementHandler

StatementHandler 源码解析

>org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
 >org.apache.ibatis.executor.CachingExecutor#query()
  >org.apache.ibatis.executor.BaseExecutor#query()
   >org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
>org.apache.ibatis.session.Configuration#newStatementHandler
>org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
>org.apache.ibatis.session.Configuration#newParameterHandler
>org.apache.ibatis.plugin.InterceptorChain#pluginAll(parameterHandler)
>org.apache.ibatis.session.Configuration#newResultSetHandler
>org.apache.ibatis.plugin.InterceptorChain#pluginAll(resultSetHandler)
>org.apache.ibatis.plugin.InterceptorChain#pluginAll(statementHandler)
>org.apache.ibatis.executor.BaseExecutor#getConnection
>org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement
>org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
>org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
>org.apache.ibatis.type.BaseTypeHandler#setParameter
>org.apache.ibatis.type.UnknownTypeHandler#setNonNullParameter
>org.apache.ibatis.type.IntegerTypeHandler#setNonNullParameter

Mapper接口实例构建

六、myBatis插件开发

插件的四大扩展点

1.Executor
2.StatementHandler
3.ParameterHandler
4.ResultSetHandler

分页插件实现:

用户在接口中声明Page 对像实现后,由插件实现自动分页。使用示例如下:

public class Page implements java.io.Serializable {
    private int szie; // 每页大
    private int number; // 当前页码
}

page参数声明

@Select("select * from user")
List<User> selectByPage(String name, Page page);

客户端调用

mapper.selectByPage("小明", new Page(3, 2))
select * from user  limit 10,20

实现目标分解:

1.修改 修改SQL 并添加 limit 语句
2.判断方法参数中是否有Page对象
3.取出Page对象 生成limit 语句
4.上述操作必须在PreparedStatement 对像生成前完成

七、spring 集成myBatis

核心使用:

基础集成使用:

1、配置 SqlSessionFactoryBean
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
</bean>
2、配置 MapperFactoryBean
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.tuling.mybatis.dao.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
3、获取mapper 对像执行业务方法
context = new ClassPathXmlApplicationContext("spring.xml");
UserMapper mapper = context.getBean(UserMapper.class);
System.out.println(mapper.selectByid(1));

对象说明:

FactoryBean:

工厂Bean 用于 自定义生成Bean对像,当在ioc 中配置FactoryBean 的实例时,最终通过bean id 对应的是FactoryBean.getObject()实例,而非FactoryBean 实例本身

SqlSessionFactoryBean:

生成SqlSessionFactory 实例,该为单例对像,作用于整个应用生命周期。常用属性如下:

  • dataSource: 数据源(必填)
  • configLocation:指定mybatis-config.xml 的内容,但其设置的 将会失效(选填)
  • mapperLocations:指定mapper.xml 的路径,相当于mybatis-config.xml 中 元素配置,(选填)

MapperFactoryBean:
生成对应的Mapper对像,通常为单例,作用于整个应用生命周期。常用属性如下:

  • mapperInterface:mapper 接口 (必填)
  • sqlSessionFactory:会话工厂实例 引用 (必填)

关于Mapper 单例情况下是否存在线程安全的问题?
在原生的myBatis 使用中mapper 对像的生命期是与SqlSession同步的,不会存在线程安全问题,现在单例的mapper 是如何解决线程安全的问题的呢?

核心流程解析:

SQL session 集成结构:
图片

初始化流程

创建 会话模板 SqlSessionTemplate

>org.mybatis.spring.mapper.MapperFactoryBean#MapperFactoryBean()
> org.mybatis.spring.support.SqlSessionDaoSupport#setSqlSessionFactory
> org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate()
>org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor 

创建接口

org.mybatis.spring.mapper.MapperFactoryBean#getObject
org.mybatis.spring.SqlSessionTemplate#getMapper
org.apache.ibatis.session.Configuration#getMapper

执行查询

>com.tuling.mybatis.dao.UserMapper#selectByid
 >org.apache.ibatis.binding.MapperProxy#invoke
 >org.mybatis.spring.SqlSessionTemplate#selectOne(java.lang.String)
 >org.mybatis.spring.SqlSessionTemplate#sqlSessionProxy#selectOne(java.lang.String)
 >org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
 >org.mybatis.spring.SqlSessionUtils#getSqlSession()
 >org.apache.ibatis.session.SqlSessionFactory#openSession()
>org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne()

每次查询都会创建一个新的 SqlSession 会话,一级缓存还会生效吗?
通过前几次课我们了解到 一级缓存的条件是必须相同的会话,所以缓存通过和spring 集成之后就不会生效了。除非使用spring 事务 这时就不会在重新创建会话。

事务使用 :

spring 事务没有针对myBatis的配置,都是一些常规事务配置:

<!--添加事务配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSource"/>
</bean>
<!--事务注解配置-->
<tx:annotation-driven/>

添加事务注解:

@Transactional()
public User getUser2(Integer id) {
    userMapper.selectByid(id);
    return userMapper.selectByid(id);
}

执行测试发现 当调用getUser2 方法时两次查询不在重复创建 sqlSession。而是共用一个直到getUser2 方法结束。

事务与SqlSession 集成原理:

其原理前面讲查询流程时有所涉及。每次执行SQL操作前都会通过 getSqlSession 来获取会话。其主要逻辑是 如果当前线程存在事务,并且存在相关会话,就从ThreadLocal中取出 。如果没就从创建一个 SqlSession 并存储到ThreadLocal 当中,共下次查询使用。
相关源码:

>org.mybatis.spring.SqlSessionUtils#getSqlSession()
>org.springframework.transaction.support.TransactionSynchronizationManager#getResource
>org.mybatis.spring.SqlSessionUtils#sessionHolder
>org.apache.ibatis.session.SqlSessionFactory#openSession()
>org.mybatis.spring.SqlSessionUtils#registerSessionHolder
>org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive
>org.springframework.transaction.support.TransactionSynchronizationManager#bindResource

简化Mapper 配置

如果每个mapper 接口都配置*MapperFactoryBean *相当麻烦 可以通过 如下配置进行自动扫描

 <mybatis:scan base-package="com.tuling.mybatis.dao"/>

其与 spring bean 注解扫描机制类似,所以得加上注解扫描开关的配置

<context:annotation-config/>

八、动态化SQL

动态命令使用:

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

示例说明:

<trim prefix="where" prefixOverrides="and|or">
    <if test="id != null">
        and id = #{id}
    </if>
    <if test="name != null">
        and name = #{name}
    </if>
</trim>

trim属性说明:

  • prefix=“where” // 前缀
  • prefixOverrides=“and|or” // 前缀要替换的词
  • suffix="" // 添加后缀
  • suffixOverrides="" // 后缀要替换的词

<where>元素说明:
在where 包裹的SQL前会自动添加 where 字符 并去掉首尾多佘的 and|or 字符 相当于下配置:

    <trim prefix="where" prefixOverrides="and|or" suffixOverrides="and|or"> 

<set>元素说明:
在set包裹的SQL前会自动添加 set 字符并去掉首尾多佘的, 字符。

<sql> 元素说明:
在同一个mapper 多个statement 存在多个相同的sql 片段时,可以通过<sql>元素声明,在通过 <include> 元素进行引用
声明sql 段

<sql id="files">
    id ,name ,createTime
</sql>

引用

<include refid="files" />

<bind> 变量使用
有时需要进行一些额外 逻辑运行,通过 声明<bind>元素,并在其value 属性中添加运算脚本,如下示例 自动给likeName加上了% 分号,然后就可以用#{likeName} 来使用带%分号的like 运算。

<bind name="likeName" value="'%'+ _parameter.getName() +'%'"></bind>

内置变量
_databaseid 数据库标识ID
_parameter 当前参数变理

自定义模板解释器:

以上的if trim where 等逻辑符都是 myBatis 自带的XMLLanguageDriver 所提供的解释语言,除此之外 我们还可以使用 MyBatis-Velocity 或 mybatis-freemarker 等外部 解释器来编写动态脚本。

mybatis-freemarker 使用

引入mybatis 包:
<dependency>
    <groupId>org.mybatis.scripting</groupId>
    <artifactId>mybatis-freemarker</artifactId>
    <version>1.1.2</version>
</dependency>
添加sql 语句
<select id="selectByIds"
        resultType="com.tuling.mybatis.dao.User"
        lang="org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver">
    select  * from user
    where  id in(${ids?join(',')})
</select>
添加接口方法
List<User> selectByIds(@Param("ids") List<Integer> ids);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lastinglate

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值