mybatis总结

一,概述:

就是对jdbc数据库操作进行封装,使用户开发者只用关注sql语句。通过xml或者注解的方式,将要执行的各种sql语句配置起来,并通过Java对象和statement中的sql语句映射生成最终的sql语句,最后由mybatis框架执行sql语句,并将结果映射成Java对象返回。

二. 工作原理:

1,分层:

a,接口层:接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理
b,数据处理层:SQL查找、SQL解析、SQL执行和执行结果映射处理
c,基础支撑层:功能支撑,包括连接管理、事务管理、配置加载和缓存处理

2,实现过程:

1.读取配置文件
连数据库的相关信息
2.有了这些信息就能创建SqlSessionFactory
SqlSessionFactory的生命周期是程序级,程序运行的时候建立起来,程序结束的时候消亡
3.SqlSessionFactory建立SqlSession,目的执行sql语句
SqlSession是过程级,一个方法中建立,方法结束应该关闭
4.调用MyBatis的statementHandler提供的Api执行Sql语句
5.SQL语句放在Map配置文件里面
6.执行SQl语句,不同的SQl语句返回不同的结果
1、 mybatis配置文件,包括Mybatis全局配置文件和Mybatis映射文件,其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的 信息。
2、 mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
3、 通过SqlSessionFactory,可以创建SqlSession即会话。Mybatis是通过SqlSession来操作数据库的。
4、 SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器接口来操作数据库的。Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。
5、 Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。该对象包括:SQL语句、输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括java的简单类型、HashMap集合对象、POJO对象类型

Configuration.xml:该配置文件是MyBatis的全局配置文件,在这个文件中可以配置诸多项目,但是一般项目中,并不会配置太多内容,常用的内容是别名设置,拦截器设置等,至于环境设置与Mapper映射文件的注册会转移到Spring配置文件中(SSM整合之后),而其余大部分的配置项都采用默认的配置。
  XMLConfigBuilder:该类是XML配置构建者类,是用来通过XML配置文件来构建Configuration对象实例,构建的过程就是解析Configuration.xml配置文件的过程,期间会将从配置文件中获取到的指定标签的值逐个添加到之前创建好的默认Configuration对象实例中。
  Configuration:该类是MyBatis的配置类,创建这个类的目的就是为了使用其对象作为项目全局配置对象,这样通过配置文件配置的信息可以保存在这个配置对象中,而这个配置对象在创建好之后是保存在JVM的Heap内存中的,方便随时读取。不然每次需要配置信息的时候都要临时从磁盘配置文件中获取,代码复用性差的同时,也不利于开发。
  SqlSessionFactoryBuilder:该类是SqlSessionFactory(会话工厂)的构建者类,之前描述的操作其实全是从这里面开启的,首先就是调用XMLConfigBuilder类的构造器来创建一个XML配置构建器对象,利用这个构建器对象来调用其解析方法parse()来完成Configuration对象的创建,之后以这个配置对象为参数调用会话工厂构建者类中的build(Configuration config)方法来完成会话工厂对象的构建。
  SqlsessionFactory:该接口是会话工厂,是用来生产会话的工厂接口,DefaultSqlSessionFactory是其实现类,是真正生产会话的工厂类,这个类的实例的生命周期是全局的,它只会在首次调用时生成一个实例(单例模式),就一直存在直到服务器关闭。
  openSession():在最后的build(Configuration config)方法中会返回一个DefaultSqlSessionFactory类的实例,这个类是MyBatis提供的默认会话工厂类,而我们使用的也正是这个类中的来openSession()方法来完成SqlSession对象的创建。
  SqlSession:该接口是会话,是项目与数据库之间的会话,类似于客户端与服务器之间的会话(session),这个SqlSession的生命周期是方法级的,因为他是非线程安全的,针对每一次数据库访问都要创建一个SqlSession,获取到返回结果之后,这个SqlSession就会被废弃。这区别于SqlSessionFactory的生命周期。
  Executor:执行器接口,SqlSession会话是面向程序员的,而内部真正执行数据库操作的却是Executor执行器,可以将Executor看作是面向MyBatis执行环境的,SqlSession就是门面货,Executor才是实干家。通过SqlSession产生的数据库操作,全部是通过调用Executor执行器来完成的。
  StatementHandler:该类是Statement处理器,封装了Statement的各种数据库操作方法execute(),可见MyBatis其实就是将操作数据库的JDBC操作封装起来的一个框架,同时还实现了ORM罢了。
  ResultSetHandler:结果集处理器,如果是查询操作,必定会有返回结果,针对返回结果的操作,就要使用ResultSetHandler来进行处理,这个是由StatementHandler来进行调用的。这个处理器的作用就是对返回结果进行处理。

三,三种模式

一对一:一个成绩 一个学生

<association 表示一对多,将查询结果中 一行中的数据 设置到 对应score 对应中属性 student

【总结】:

  • association:为多表一对一的的应映射关系
  • javaType:为所用属性的java类型
<resultMap id="scoreMap1" type="score">
        <id property="scoreid" column="scoreid"></id>
        <result property="coursename" column="coursename"></result>
        <result property="score" column="score"></result>
        <result property="studentid" column="studentid"></result>

        <association property="student" javaType="student">
            <id property="id" column="studentid"></id>
            <result property="name" column="name"></result>
            <result property="age" column="age"></result>
            <result property="sex" column="sex"></result>
            <result property="height" column="height"></result>
        </association>
</resultMap>

    <select id="findAllScoreWithStudent" resultMap="scoreMap1">
            SELECT a.scoreid,a.coursename,a.score,a.studentid,b.id,b.name,b.age,b.sex,b.height FROM score_tb a LEFT JOIN student_tb b ON a.studentid = b.id
    </select>

一对多:一个学生有多个成绩

 <resultMap id="studentWithSocreMap" type="student" >

        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="height" column="height"></result>

        <collection property="scoreList" ofType="score">
            <id property="scoreid" column="scoreid"></id>
            <result property="coursename" column="coursename"></result>
            <result property="score" column="score"></result>
            <result property="studentid" column="id"></result>
        </collection>

    </resultMap>


    <select id="findAllStudentWithScore" resultMap="studentWithSocreMap" >
        SELECT a.id,a.name,a.age,a.sex,a.height,b.scoreid,b.coursename,b.score,b.studentid  FROM student_tb a LEFT JOIN score_tb b ON a.id = b.studentid
    </select>

多对多:多对多其实不存在,实际就是 两个/多个 一对多

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JbYYfQv4-1627901784025)(C:\Users\22564\AppData\Roaming\Typora\typora-user-images\image-20210611125126948.png)]

select * from role_tb r LEFT JOIN student_role_tb sr on r.id = sr.roleid
  LEFT JOIN student_tb s on sr.studentid = s.id
<resultMap id="roleMap1" type="role">
        <id property="id" column="id"></id>
        <result property="rolename" column="rolename"></result>

        <collection property="studentList" ofType="Student">
            <id property="id" column="studentid"></id>
            <result property="name" column="name"></result>
            <result property="age" column="age"></result>
            <result property="sex" column="sex"></result>
            <result property="height" column="height"></result>
        </collection>
</resultMap>

    <select id="findAllRoleWithStudent" resultMap="roleMap1">
        select r.id id,r.rolename rolename,s.name name,s.age age,s.sex sex,s.height height,sr.studentid studentid
            from role_tb r LEFT JOIN student_role_tb sr on r.id = sr.roleid
                LEFT JOIN student_tb s on sr.studentid = s.id
    </select>

(1)事务模块,抽象工厂模式
(2)数据源:数据源接口中定义了两个获取数据库连接Connection的方法,可见数据源的目的所在,不论其使用什么原理实现,其目的就是为了对外提供数据库连接的获取接口。unpooled:非池型数据源。pooled:同步的线程安全的池型数据源。jndi:托管型

四,问题:

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

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

2,当实体类中的属性名和表中的字段名不一样 ,怎么办

第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致
第2种: 通过来映射字段名和实体类属性名的一一对应的关系

3,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个标签,都会被解析为一个MappedStatement对象。

Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。

Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

4,Mybatis是如何进行分页的

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

5,在mapper中如何传递多个参数

对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可,

在mybatis 中 如果有多个参数 并且使用 #{参数名} 必须在方法中为参数声明 别名 @Param(“sex”)

也可以使用 arg0 arg1 或者 param1 param2
* 如果是传多个属性(参数)
* 也可以直接传对象
* findStudentBySexAndAge(Student student);
* 或者直接传对象
* findStudentBySexAndAge(Map map);

 findStudentBySexAndAge(@Param("sex") String sex,@Param("age") int age);
6,缓存问题:

缓存:比如执行select * from student 第一次,sqlsesion 没有缓存去数据库找,缓存起来,第二次执行select * from student 直接去缓存找。Mybatis在查询时会采用缓存机制,分为一级缓存和二级缓存,

好处:1.查询效率高(快)2.降低mysql服务器请求查询压力

mybatis 一级二级缓存

一级缓存就是基于sqlsesion 的缓存

二级缓存基于namespace的缓存,二级缓存是 多个 sqlsesion 共享的

一级缓存默认就会开启,二级缓存需要配置才可以使用。无论是一级缓存,还是二级缓存共同的特点:所有的查询都会缓存,但是只要发生增删改,缓存立即清空否则会有脏数据问题。
一级缓存:一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。一旦发生增删改,缓存立即失效。
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。
对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
在缓存的作用域中,如果调用的方法的参数一样,就会使用缓存中查询的结果,而不会再次查询数据库。

二级缓存:基于namespace,有多个Sqlsession共享

二级缓存开启:

1、开启缓存开关

<settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>

        <!-- 开启全局懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>

        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>

</settings>

2、对应的dao.xml开启缓存

<!--
         <cache>  开启二级缓存
            eviction="LRU" 缓存是有大小限制,超过大小要 清除一些缓存
                    - LRU - 最近最少回收,移除最长时间不被使用的对象
                    - FIFO - 先进先出,按照缓存进入的顺序来移除它们
                    - SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
                    - WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
            size="1024"  缓存 最多 1024 条数 sql 对应的数据
            flushInterval="60000"  60s 缓存 刷新一次
            readOnly="true" 是否只读  不能获取缓存中对象的引用,不能直接修改缓存对象
                     "false" 能获取缓存中对象的引用,能直接修改缓存对象
                      
            blocking="true" 在读取缓存时是否阻塞 
    -->
<cache eviction="LRU" size="1024" flushInterval="60000" readOnly="true" blocking="true">
</cache>

3、使用缓存

<!--
        在查选标签中配置 useCache="true" 使用缓存
        默认值就是true 使用缓存    
-->
<select id="findStudentById" resultType="Student" useCache="true">
        select  * from student_tb where id = #{id}
</select>

【注意】:一级缓存和二级缓存同时存在,可能会引入脏数据

【解决方案】:

1、第一种不使用一级缓存

<!-- 将一级缓存修改为STATEMENT表示不使用一级缓存
                  SESSION表示使用一级缓存
-->
<setting name="localCacheScope" value="STATEMENT"/>

2、在spring中使用mybatis,每次调用mapper对应的方法,都使用新的Sqlsession

7、Mybatis基于注解的开发
public interface StudentDao{

    @Select("select * from student_tb where id = #{id}")
    Student findStudentById(int id);

    @Insert("insert into student_tb (name,age,sex,height) values(#{name},#{age},#{sex},#{height})")

    @SelectKey(resultType = Integer.class,keyProperty = "id",keyColumn = "id",before = false,statement = "select last_insert_id()")
	//获取自增id  statement = "" 获取自增id 的sql
    int addStudent(Student student);

    @Update("update student_tb set name = #{name},age = #{age},sex=#{sex},height=#{height} where id = #{id}")
    int updateStudent(Student student);

    @Delete("delete from student_tb where id = #{id}")
    int deleteStudent(int id);

}

自定义resultMap完成数据库列名和实体类名不一致的问题

@Results

@Select("select * from student_tb")
@Results(id = "studentResult1",value = {
        @Result(id = true,property = "id",column = "id"),
        @Result(property = "name",column = "name"),
        @Result(property = "age",column = "age"),
        @Result(property = "sex",column = "sex"),
        @Result(property = "height",column = "height")
    })// 相当于xml 中 <resultMap> 声明map
    // @Result(id = true,property = "id",column = "id") 相当于 <id property="id" column="id"></id>
    List<Student> findAllStudent();

    @Select("select * from student_tb where sex = #{sex}")
    @ResultMap(value = "studentResult1")// 使用@ResultMap 引用已经在其他方法上声明的 		@Results
    List<Student> findAllStudentBySex(String sex);

基于注解的懒加载

@Select("select * from student_tb")
@Results(id = "studentResult2",value = {
			@Result(id = true,property = "id",column = "id"),
            @Result(property = "name",column = "name"),
            @Result(property = "age",column = "age"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "height",column = "height"),
            @Result(property = "scoreList",column = "id",many = @Many(select = "com.qfedu.dao.ScoreDao.findAllScoreByStudentId",fetchType = FetchType.LAZY))
    })
    List<Student> findAllStudentWithScorelazy();
9,延迟加载:

延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗。

1、开启懒加载:

<settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>

        <!-- 开启全局懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>

    </settings>

2、一对多懒加载:

 <!--

        一对多懒加载
            column="id" 两张表关联的id

            select="com.qfedu.dao.ScoreDao.findAllScoreByStudentId"  调用 更具学生id 查询所有成绩方法

             fetchType="lazy" 懒加载
             fetchType="eager" 立即加载
    -->
    <resultMap id="studentMap1" type="Student">
        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="height" column="height"></result>

        <collection property="scoreList" ofType="Score" column="id" select="com.qfedu.dao.ScoreDao.findAllScoreByStudentId"
                    fetchType="lazy">
        </collection>
    </resultMap>

    <select id="findStudentByLazy" resultMap="studentMap1">
        select  * from  student_tb
    </select>
10、PageHelper分页

pagehelper 是mybatis 的一个插件,帮助我们完成分页查询

1、引入依赖

<!--配置分页-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.1.4</version>
</dependency>

2、在配置文件中引入

<!--
   配置pagehelper 插件注意  必须在 <environments 之前
-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 使用MySQL方言的分页 -->
            <property name="helperDialect" value="mysql"/><!--如果使用mysql,这里value为mysql-->
            <!--  
                如果 pegesize为0 查询所有
            -->
            <property name="pageSizeZero" value="true"/>
        </plugin>
    </plugins>

3、使用

 @Test
    public void pageHelperTest(){

        // PageHelper 帮助 下面的 sql 完成分页
        // 在 select * from student_tb  拼接 limit index,pagesize
//        select * from student_tb limit ?,?
        PageHelper.startPage(1, 3);// 在查选语句之前 调用

        List<Student> allStudentList = studentDao2.findAllStudent();

        // 获取分页信息
        PageInfo<Student> pageInfo = new PageInfo<Student>(allStudentList);

        System.out.println("总数量"+pageInfo.getTotal());
        System.out.println("当前页"+pageInfo.getPageNum());
        System.out.println("每页多少"+pageInfo.getPageSize());
        System.out.println("总页数"+pageInfo.getPages());

        for (Student student : allStudentList) {
            System.out.println("student:"+student);
        }

    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值