Mybatis

58 阅读4分钟

什么是 Mybatis?

  Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发只需要关注 SQL 语句本身,不用去处理加载驱动、创建连接、创建Statement等繁杂的过程。

Mybatis 动态 sql 是做什么的?

1)Mybatis动态sql可以让我们在Xml映射文件中,以标签形式编写动态sql,完成逻辑判断和动态拼接sql的功能

2)Mybatis提供了9中动态sql标签:
 trim、where、set、foreach、if、choose、when、outherwise、bind

#{}和${}的区别是什么?

#{} 是预编译处理,${} 是字符串替换。Mybatis 处理 #{} 时,会将 sql 中的 #{} 替换为 ?号,调用PreparedStatement 的 set 方法来赋值,使用#{}可以有效的防止sql注入,提高系统安全性

sql 注入:sql 注入即使指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在 web 应用程序中事先定义好的查询语句的结尾添加额外的 sql 语句,在管理员不知情的情况下实现非法操作

工作流程:

  1. 在 SqlSessionFactoryBuilder().build() 的时候会根据mybatis.xml文件生成一个 Configuration 对象,所有的配置都会在此对象里面,相当于一个全局变量,然后把 Configutation 对象当做参数构造出一个DefaultSqlSessionFactory 对象
    在生成 Configuration 对象时会根据标签里的属性来进行解析指定的mapper.xml 文件,解析之后的sql语句都会生成一个 mappedStatement 对象。
    mappedStatement 是 sql 方法的封装,不管是 xml 里的标签形式的sql语句,还是注解形式的 sql 语句,都会被翻译成mappedStatement 对象,该对象有个ID属性,接口的全路径加上方法的名字就是 ID,所有的mappedStatement 对象都会被put到一个叫 mappedStatements 的 map 里,以 ID 为 key,对象自身为value,该 map 继承了 hashmap,重写了 put 方法,如果 key 存在就会直接抛出异常,所以方法不能重载。
  2. 然后是 sqlSessionFactory.openSession(),会根据 Configuration 对象获取 DefaultSqlSession
  3. 然后 sqlsession 调用 getMapper 方法,利用JDK动态代理生成代理
  4. 最后执行方法时,sqlSession 会根据 id 选择合适的 sql 语句,id 就是映射文件 sql 语句的 id,就是方法名来执行。是走的 MapperProxy,就是生成的代理对象的 invoke 方法,如果是查询方法,会先查二级缓存,也没有,再查一级缓存,都没有才会查询数据库,放到一级缓存中

摘自blog.csdn.net/qq_38270106…

缓存

一级缓存

1、一级缓存的生命周期有多长?

  1. MyBatis 在开启一个数据库会话时,会 创建一个新的 SqlSession 对象,SqlSession 对象中会有一个新的Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象;当会话结束时,SqlSession 对象及其内部的 Executor 对象还有 PerpetualCache 对象也一并释放掉。
  2. 如果 SqlSession 调用了close()方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用。
  3. 如果 SqlSession 调用了clearCache(),会清空 PerpetualCache 对象中的数据,但是该对象仍可使用。
  4. SqlSession 中执行了任何一个 update 操作 (update()、delete()、insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用

  mybatis 一级缓存默认是开启的,但与 spring 整合后,每次查询都是新的 SQLSession,所以一级缓存会失效。 而在方法上加 @Transactional 将原本的 DefaultSqlSession 替换成了 SqlSessionTemplate,并且在 SqlSessionTemplate 将 sqlSession替换成了代理对象,当我们执行 sqlSession.selectList 方法的时候会调用到 SqlSessionInterceptor的invoke方法, 在 invoke 方法的fianlly中调用了 SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory) 将我们的 session 关闭了。原生的 mybatis 之所以没有关闭session 是因为它把 session 暴露给我们了,而和 spring 结合使用的时候并没有提供暴露 session 的方法,所以只能在这里关,而一旦 session 关闭了,那一级缓存自然也就失效了。

2、怎么判断某两次查询是完全相同的查询?

  mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

  • 相同的sql和参数
  • 会话级别缓存,必须是同一个会话sqlSession
  • 相同的方法
  • 必须是相同的命名空间

二级缓存

  1. 命中条件
    1.1 必须设置@CacheNamespace
    1.2 会话关闭,才会填充到二级缓存

存储过程

  insert语句的 statementType 默认为 PREPARED,要用 ${} 可以把 statementType 改为 statement,也就是原生的sql,当用存储过程时 需改为 CALLBLE  例:

<insert id="addUser" statementType="CALLABLE">
        {
            call add_user(
            #{username,mode=IN,jdbcType=VARCHAR},
            #{password,mode=IN,jdbcType=VARCHAR},
            #{salt,mode=IN,jdbcType=VARCHAR}
            )
        }
</insert>

循环插入

<insert id="addOptions" parameterType="java.util.List">
        insert into options(oname,count,sid) values
        <foreach collection="optionName" item="option" index="index" separator=",">
            (
            #{option},0,#{subjectId}
            )
        </foreach>
</insert>

配置文件:

mybatis.configuration.map-underscore-to-camel-case  //下划线转成驼峰命名

pagehelper 分页插件使用

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
@Bean
public PageHelper getPageHelper() {
    PageHelper pageHelper = new PageHelper();
    Properties properties = new Properties();
    properties.setProperty("helperDialect", "mysql");
    properties.setProperty("reasonable", "true");
    properties.setProperty("supportMethodsArguments", "true");
    properties.setProperty("params", "count=countSql");
    pageHelper.setProperties(properties);
    return pageHelper;
}
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.yuanqiao.dao.UserDao;
import com.yuanqiao.entity.User;
import com.yuanqiao.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public PageInfo<User> getAllUsers(int pageNum,int pageSize) {
        //当前页,每页展示的数据
        PageHelper.startPage(pageNum,pageSize);
        List<User> allUsers = userDao.getAllUsers();
        PageInfo<User> pageInfo=new PageInfo<>(allUsers);
        return pageInfo;
    }
}

递归查询子节点(只是提供这种写法,但是一般不用)

<mapper namespace="com.wzl.interview.dao.RegionDao">

    <!-- 这里的id的值作为下面的查询返回结果resultMap的值 -->
    <!-- collection中的column属性可以为多个值,这里只有一个,它作为下面递归查询传递进去的参数 -->
    <!-- ofType和javaType属性正好联合构成了数据Bean类Category中的childrenList属性的数据类型 -->
    <!-- select的值为下面递归查询的select标签的id值 -->
    <resultMap id="regionMapping" type="com.wzl.interview.pojo.Region">
        <id column="id" property="value"/>
        <result column="name" property="label"/>
        <collection property="children" ofType="com.wzl.interview.pojo.Region" select="selectChildren" column="id"/>
    </resultMap>
    <!-- 先查询菜单根级目录 -->
    <!-- 这里的返回结果必须为resultMap,并且值为上面构建的resultMap的id的值 -->
    <select id="selectRegion" resultMap="regionMapping">
        select * from region where pid is null
    </select>
    <!-- 再利用上次查询结果colliection中column的值cid做递归查询,查出所有子菜单 -->
    <!-- 这里的返回结果必须为resultMap,并且值为上面构建的resultMap的id的值 -->
    <select id="selectChildren" resultMap="regionMapping">
        select * from region where pid = #{id};
    </select>

</mapper>