持久层框架源码解析

问题

1 原理
    @Autowrite
    private IUserDao dao;
    dao.selectList();

2 . Mybatis 涉及到的设计模式

自定义持久层框架

JDBC分析

jdbc代码

try {
    
    
    // 加载数据库驱动
    Class.forName("com.mysql.jdbc.Driver");
    // 通过驱动管理类获取数据库链接
    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8", "root", "root");
    // 定义sql语句?表示占位符
    String sql = "select * from user where username = ?";
    // 获取预处理statement
    preparedStatement = connection.prepareStatement(sql);
    // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
    preparedStatement.setString(1, "tom");
    // 向数据库发出sql执⾏查询,查询出结果集
    resultSet = preparedStatement.executeQuery();
    // 遍历查询结果集
    while (resultSet.next()) {
    
    
        int id = resultSet.getInt("id");
        String username = resultSet.getString("username");
        // 封 装 User user.setId(id);
        user.setUsername(username);
    }
    System.out.println(user);
}
} catch (Exception e) {
    
     e.printStackTrace();
                      } finally {
    
    
    // 释放资源
    if (resultSet != null) {
    
     try {
    
    
        resultSet.close();
    } catch (SQLException e) {
    
    
        e.printStackTrace()

存在问题

实际工作中,需求发生改变时候,sql也会改变。如果是硬编码,那么就要频繁改动代码

  1. 数据库连接频繁创建释放
  2. sql语句中存在硬编码,造成代码不宜维护。实际工作中sql经常需要变动
  3. 使用preparedStatement占有符进行参数传递,实际工作中参数不一定
  4. 查询结果时,存在硬编码问题(取列名),并且麻烦。

解决

①使⽤数据库连接池初始化连接资源

②将sql语句抽取到xml配置⽂件中

③使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射

自定义框架

使用端

1.提供两个配置文件

	sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml 

	引⼊mapper.xml 是因为只需要加载一次文件(sqlMapConfig.xml ),这样可以减少io操作和解耦

	mapper.xml Mapper.xml : sql语句的配置⽂件信息

框架端

1.读取配置文件:根据配置文件路径加载成字节输入流存储在内存当中
	加载Resource类:InputSteam getReSource(String path)
2. 创建javaBean保存加载信息

	(1)  Configuration(核心配置类) : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "."

+ id

	(2)  MappedStatement(映射配置类):sql语句、statement类型、输⼊参数java类型、输出参数java类型
  1. 解析配置文件 dom4j

    创建sqlSessionFactoryBuilder类:

    ⽅法:sqlSessionFactory build():

    第⼀:使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中

    第⼆:创建SqlSessionFactory的实现类DefaultSqlSession(工厂模式)

  2. 创建SqlSessionFactory接口及实现类DefaultSqlSession

    ⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象

  3. 创建sqlSession接⼝及实现类:主要封装crud⽅法

    ⽅法:

    selectList(String statementId,Object param):查询所有selectOne(String statementId,Object param):查询单个

    具体实现:封装JDBC完成对数据库表的查询操作

  4. 创建Executor接口及实现类

    query():执行的就是JDBC代码

涉及到的设计模式:

Builder构建者设计模式、⼯⼚模式、代理模式

https://www.processon.com/diagraming/617fd7d7e0b34d1a7ea51228

在这里插入图片描述

Mybatis配置文件解析

SqlMapConfig层级关系

层级关系必须按照顺序来

在这里插入图片描述

<configuration > 
    <properties></properties>				为下面的文件引入properties文件,取值方式为${}   
    <settings></settings>  
    <typeAliases></typeAliases>				 给全限定类名设置别名   
    <typeHandlers></typeHandlers> 
    <objectFactory></objectFactory>  
    <plugins></plugins>  
    <environments default="">     
        <environment id="">    
            <transactionManager type=""></transactionManager> 
            <dataSource type=""></dataSource> 
        </environment>  
    </environments>
    <databaseIdProvider ></databaseIdProvider>  
    <mappers></mappers>
</configuration>

environments标签

事务管理器(transactionManager)类型

•JDBC:这个配置就是直接使⽤了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作

⽤域。

•MANAGED:这个配置⼏乎没做什么。它从来不提交或回滚⼀个连接,⽽是让容器来管理事务的整个⽣ 命周期(⽐如 JEE 应⽤服务器的上下⽂)。 默认情况下它会关闭连接,然⽽⼀些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻⽌它默认的关闭⾏为。

主要使用jdbc的方式

数据源(dataSource)类型

•UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。

•POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。

•JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配置数据源,然后放置⼀个 JNDI 上下⽂的引⽤。

主要使用pooled的方式

mapper标签

•使⽤相对于类路径的资源引⽤,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
•使⽤完全限定资源定位符(URL),例如: <mapper class="org.mybatis.builder.AuthorMapper"/>
•将包内的映射器接⼝实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>

如果同时使用,package要放在最后面

Properties标签

实际开发中,习惯将数据源的配置信息单独抽取成⼀个properties⽂件,该标签可以加载额外配置的properties⽂件

相当于把数据存放到jdbc.properties文件中,再配置文件中用${}取值

<!--加载外部的properties文件-->    <properties resource="jdbc.properties"></properties>

在这里插入图片描述

typeAliases

设置别名有两种方式

1 单独设置

<!--给实体类的全限定类名给别名--> <typeAliases>        <!--给单独的实体起别名-->       <typeAlias type="com.lagou.pojo.User" alias="user"></typeAlias> </typeAliases>

2 设置包名

已经设置了一部分别名,如包装类,Stirng等

<!--给实体类的全限定类名给别名-->    <typeAliases>        <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->       <package name="com.lagou.pojo"/>     </typeAliases>       为所有pojo下面的包设置别名,如com.lagou.pojo.user 别名为User 或许user USEr 等,不设置大小写

Mapper

resultMap

用来对应数据库列名和实体名称

id 唯一表示,其他《select resultTpye="id"》type 封装实体全类名<resultMap id="orderMap" type="com.lagou.domain.Order">     <result column="uid" property="user.id"></result>       <result  column="username" property="user.username"></result>      <result  column="password" property="user.password"></result>     <result  column="birthday" property="user.birthday"></result></resultMap>

association

中文为联合的意思,用于多表查询,共有两种方式

1.使用select

column :数据库列名,用于select作为参数  property:resultMap标签中需要映射的实体类字段名称 select:方法id   <association column="user_id" property="user" select="com.ljy.inspector.dao.UserDao.findById">        </association>

具体使用

<resultMap id="projectMap" type="com.ljy.inspector.entity.Project">    <id column="id" property="id"/>    //使用自定义类型转换器    <result column="state" property="state" typeHandler="com.ljy.inspector.type_handler.ItemStateHandler"/>    <association column="user_id" property="user" select="com.ljy.inspector.dao.UserDao.findById">    </association></resultMap><select id="findById" resultType="com.ljy.inspector.entity.User">    SELECT * FROM user AS u    <where>        u.id = #{id}    </where></select>

2.直接sql映射

<resultMap id="projectMap" type="com.ljy.inspector.entity.Project">    <id column="id" property="id"/>    <result column="state" property="state" typeHandler="com.ljy.inspector.type_handler.ItemStateHandler"/>    <association property="user" javaType="com.ljy.inspector.entity.User">        <id column="u_id" property="id"/>        <result column="username" property="username"/>    </association></resultMap>

3 两种方式的区别

1.如果使用了select属性,则本质上要进行两次查询,得到两张结果表,因为是从不同的表中查询(相同也无所谓)且每次只查询一个表,所以二者互不干涉,所以不存在字段歧义的问题,自动映射会开启。

2.如果使用resultMap属性,或者association本身当做一个resultMap,则本质是对多张表的一次联合查询,只产生一张结果表。然后对这张结果表一次进行映射。可能存在字段歧义问题,自动映射会关闭(如果没有指明映射关系的字段会得到空值)。此时要把产生歧义的字段起别名,并且把所有需要映射的字段都显式写出来。

collection

1 使用select

<!-- column="{qid=id,sort=sort} 前面的 qid/sort是定义的变量名, 后面的 id/sort是主表的字段id/sort,先查出主表的结果, 然后主表记录数是几 就执行几次 collection 的select,javaType 集合ofType 每一个元素的类型select的值: 对应xml的namespace + 对应xml中的代码片段的id,column作为select语句的参数传入,如果只传一个参数id可以简写: column="id" --><collection property="options" javaType="java.util.ArrayList" ofType="com.xxx.modules.xxx.entity.QuestionOption"            select="com.xxx.modules.xxx.mapper.QuestionOptionMapper.selectList" column="{qid=id,sort=sort}" />

2.直接sql映射

property :实体熟悉名字<collection property="options" javaType="java.util.ArrayList" ofType="com.xxx.modules.data.entity.QuestionOption">    <id column="oid" property="id" jdbcType="VARCHAR" />    <result column="ocontent" property="content" jdbcType="VARCHAR" />    <result column="osort" property="sort" jdbcType="INTEGER" /></collection>

3 区别

直接sql映射结果集分页顺序会错乱

原理太复杂,学不会

动态ql语句

if

数字类型<if test="id != null"></if><if test='id != null and id > 28'></if>字符串类型<if test="username != null"></if><if test="username != null and '' != username"></if><if test="username != null and username.indexOf('ji') == 0"> </if><if test="username != null and username.indexOf('ji') >= 0"> </if><if test="username != null and username.lastIndexOf('ji') > 0"></if>

where

我不说,你难道不懂?

foreach

我不说,你也应该懂?

<foreach collection="array" open="id in(" close=")" item="id" separator=",">#{id}</foreach>

foreach标签的属性含义如下: 标签⽤于遍历集合,它的属性:

•collection:代表要遍历的集合元素,注意编写时不要写#{}

•open:代表语句的开始部分

•close:代表结束部分

•item:代表遍历集合的每个元素,⽣成的变量名

•sperator:代表分隔符

SQL⽚段抽取

Sql 中可将重复的 sql 提取出来,使⽤时⽤ include 引⽤即可,最终达到 sql 重⽤的⽬的

<sql id="selectUser" select * from User</sql><select id="findById" parameterType="int" resultType="user">	    <include refid="selectUser"></include>    where id=#{id}</select>

Mybatis复杂映射开发

一对一

使用association标签进行对应

一对多

使用collection 标签进行对应

多对多

写法与一对多一致,只是数据库表现房室不一样(sql不一样)

注解

基本注解

@Insert:实现新增@Update:实现更新@Delete:实现删除@Select:实现查询@Result:实现结果集封装@Results:可以与@Result ⼀起使⽤,封装多个结果集@One: 实 现 ⼀ 对 ⼀ 结 果 集 封 装     @Many:实现⼀对多结果集封装

基本使用

1.修改sqlMapConfig.xml

<mappers>    <!--扫描使⽤注解的类-->    <mapper class="com.lagou.mapper.UserMapper"></mapper></mappers>

缓存

sqlSession(一级缓存)

  1. 在sqlSession中存在HashMap存放数据,不同的sqlSeesion的缓存是不同的。

  2. 第⼀次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,如果没有,从 数据库查询⽤户信息。得到⽤户信息,将⽤户信息存储到⼀级缓存中。

  3. 如果中间sqlSession去执⾏commit操作(执⾏插⼊、更新、删除),则会清空SqlSession中的 ⼀级缓存,这样做的⽬的为了让缓存中存储的是最新的信息,避免脏读。

  4. 第⼆次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,缓存中有,直 接从缓存中获取⽤户信息

源码

在这里插入图片描述

缓存的数据结构是一个map,key值是由MapperStartMentId,params,sql,分页参数组成
在这里插入图片描述

Mapper(二级缓存)

与一级类似,带更新ing

代码附录

自定义持久层框架

使⽤端

sqlMapConfig.xml


mapper.xml


User实体


框架段

Configuration


MappedStatement


Resources


SqlSessionFactoryBuilder


XMLConfigerBuilder


猜你喜欢

转载自blog.csdn.net/xxf_is_girl_gad/article/details/121154013