四、mybatis第三方软件集成源码解析

一、spring集成myBatis


知识点

  1. 核心使用
  2. 核心流程解析
  3. 事物使用
  4. 简化Mapper配置

1、核心使用:

基础集成使用:

  1. 配置dataSource
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;rewriteBatchedStatements=true"></property>
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
</bean>
复制代码
  1. 配置 SqlSessionFactoryBean
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <!--<property name="configLocation" value="classpath:mybatis-config.xml"/>-->
    <!--<property name="mapperLocations" value="classpath:com/cyan/mapper/*.xml"></property>-->
</bean>
复制代码
  1. 配置 MapperFactoryBean
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.cyan.mapper.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
复制代码
  1. 创建mapper接口(UserMapper)
@Select("select * from user where id=#{id}")
User selectUserById1(Integer id);
复制代码
  1. 获取mapper对象执行业务方法
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserMapper userMapper = context.getBean(UserMapper.class);
User user = userMapper.selectUserById1(1);
System.out.println(user);
复制代码

核心对像说明:

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是如何解决线程安全的问题的呢?

2、核心流程解析

SQL session集成结构:

图片

初始化流程

  1. 创建会话模板SqlSessionTemplate
> org.mybatis.spring.mapper.MapperFactoryBean#getObject()
> org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession()
> org.mybatis.spring.support.SqlSessionDaoSupport#setSqlSessionFactory()
> org.mybatis.spring.support.SqlSessionDaoSupport#createSqlSessionTemplate()
> org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate()
> org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor()
复制代码
  1. 创建接口
> org.mybatis.spring.mapper.MapperFactoryBean#getObject()
> org.mybatis.spring.SqlSessionTemplate#getMapper()
> org.apache.ibatis.session.Configuration#getMapper()
> org.apache.ibatis.binding.MapperRegistry#getMapper()
> org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
复制代码
  1. 执行查询
> com.cyan.test#selectUserByid()
> org.apache.ibatis.binding.MapperProxy#invoke()
> org.mybatis.spring.SqlSessionTemplate#selectOne()
> org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#SqlSessionInterceptor()
> 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事物,这时就不会在重新创建会话。

3、事物使用

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

<context:annotation-config/>

<context:component-scan base-package="com.cyan.service.**"/>

<!--添加事物配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--开启事物注解-->
<tx:annotation-driven/>
复制代码

添加事物注解:

@Transactional
public User selectUserById(Integer id) {
    userMapper.selectUserById(id);
    return userMapper.selectUserById(id);
}
复制代码

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

事物与SqlSession集成原理:

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

相关源码:

> org.mybatis.spring.SqlSessionUtils#getSqlSession()
> org.springframework.transaction.support.TransactionSynchronizationManager#getResource()
> org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource()

> 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()
复制代码

4、简化Mapper配置

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

<mybatis:scan base-package="com.cyan.mapper"></mybatis:scan>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.cyan.mapper"></property>
</bean>
复制代码

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

<context:annotation-config/>
复制代码

二、动态化SQL


知识点

  1. 动态命令使用
  2. 自定义模板解释器

1、动态命令使用

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

、示例说明:

<select id="selectByUser" parameterType="com.cyan.pojo.User" resultType="com.cyan.pojo.User">
    select * from user
    <trim prefix="where" prefixOverrides="and|or">
        <if test="id !=null and id != ''">
            and id = #{id}
        </if>
        <if test="name !=null and name != ''">
            and name = #{name}
        </if>
    </trim>
</select>
复制代码

trim属性说明:

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

元素说明:

在where 包裹的SQL前会自动添加where字符 并去掉首尾多佘的and|or字符,相当于以下配置:

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

<select id="selectByUser" parameterType="com.cyan.pojo.User" resultType="com.cyan.pojo.User">
    select * from user
    <where>
        <if test="id !=null and id != ''">
            and id = #{id}
        </if>
        <if test="name !=null and name != ''">
            and name = #{name}
        </if>
    </where>
</select>
复制代码

、、元素说明:

类似于java中的switch语句

<select id="selectUserByNameOrId" resultType="com.cyan.pojo.User">
    select * from user
    <where>
        <choose>
            <when test="id != null">
                and id = #{id}
            </when>
            <when test="name != null and name != ''">
                and name = #{name}
            </when>
            <otherwise>
                and id in (1,2,3)
            </otherwise>
        </choose>
    </where>
</select>
复制代码

元素说明:

在set包裹的SQL前会自动添加set字符并去掉首尾多佘的','字符。

<update id="updateUserById" parameterType="com.cyan.pojo.User">
    update user
    <set>
        <if test="name != null and name != ''">
            name = #{name},
        </if>
        <if test="updateTime != null">
            update_time = #{updateTime}
        </if>
    </set>
    where id = #{id}
</update>
复制代码

元素说明:

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

<select id="selectUserListByIds" resultType="com.cyan.pojo.User">
    select * from user
    <where>
        <if test="ids != null">
            id in
            <foreach collection="ids" item="item" index="index" open="(" close=")" separator=",">
                #{item}
            </foreach>
        </if>
    </where>
</select>
复制代码

、元素说明:

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

<sql id="files">
    id,name,create_time,update_time
</sql>

<select id="selectUserById2" resultType="com.cyan.pojo.User" parameterType="int">
    select
    <include refid="files"></include>
    from user where id = #{id}
</select>
复制代码

变量使用:

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

<select id="selectByUser" parameterType="com.cyan.pojo.User" resultType="com.cyan.pojo.User">
    <bind name="likename" value="'%'+_parameter.getName()+'%'"></bind>
    select * from user
    <trim prefix="where" prefixOverrides="and|or">
        <if test="id !=null and id != ''">
            and id = #{id}
        </if>
        <if test="name !=null and name != ''">
            and name like #{likename}
        </if>
    </trim>
</select>
复制代码

内置变量:

  • _databaseid 数据库标识ID
  • _parameter 当前参数变理

2、自定义模板解释器

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

mybatis-freemarker使用:

引入jar包:

<dependency>
    <groupId>org.mybatis.scripting</groupId>
    <artifactId>mybatis-freemarker</artifactId>
    <version>1.2.0</version>
</dependency>
复制代码

添加sql语句

<select id="selectUserListByIds" resultType="com.cyan.pojo.User"
            lang="org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver">
    select * from user
    where  id in (${ids?join(',')})
</select>
复制代码

添加接口方法

List<User> selectUserListByIds(@Param("ids") List<Integer> ids);
复制代码

猜你喜欢

转载自juejin.im/post/5da30cb4f265da5b5e2d9c5c