Mybatis使用及源码分析

1、原生JDBC操作

public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        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(); // 遍历查询结果集

            User user = new User();
            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 (Exception e) {
                    e.printStackTrace();
                }
            }

            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
    }

2、JDBC问题总结:

根据上边的代码,原始jdbc开发存在的问题如下:
1、 资源浪费:每次与数据库进行交互,频繁的创建、释放,会造成系统资源浪费,从而影响系统性能。
2、 耦合度太高:在代码中sql存在硬编码,而在实际的应用中,sql变化较大,每次sql变动需要需要改变 java代码;对结果集解析存在硬编码,sql变化导致解析代码变化,系统极度不易维护

针对以上问题,我们能想到的解决思路是
1、使用数据库连接池初始化连接资源,设置过期时间,每次在过期时间内需要与数据库交互,不必创建新的session了
2、将sql语句抽取到xml配置文件中,动态配置,抽取
3、使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

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

ORM全称Object/Relation Mapping:表示对象-关系映射的缩写
ORM完成面向对象的编程语言到关系数据库的映射。当ORM框架完成映射后,程序员既可以利用面向 对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。ORM把关系数据库包装成面向对 象的模型。ORM框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。采用ORM框架 后,应用程序不再直接访问底层数据库,而是以面向对象的放松来操作持久化对象,而ORM框架则将这 些面向对象的操作转换成底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除 等 操作,转换为对数据库的操作

3、使用mybatis快速入门

开发步骤:

3.1、在pom.xml中添加MyBatis的坐标

<dependencies>
    <!--mybatis坐标-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
    </dependency>
    <!--mysql驱动坐标-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
        <scope>runtime</scope>
    </dependency>
    <!--单元测试坐标-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
    </dependency>
 </dependencies>

3.2、创建user数据表

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

3.3、编写User实体类

@Data
public class User {
    private int id;
    private String username; 
}

3.4、编写映射文件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 = "user">
    <!-- sql唯一标识: 由namespace.id来组成 statementId -->
    <select id = "selectOne" resultType = "com.demo.pojo.User" paramterType = "com.demo.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

    <select id = "selectList" resultType = "com.demo.pojo.User">
        select * from user
    </select>
</mapper>

3.5、编写核心文件SqlMapConfig.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>
    <!-- 数据库配置信息 -->
    <dataSource>
        <property name = "driverClass" value = "com.mysql.jdbc.Driver"></property>
        <property name = "jdbcUrl" value = "jdbc:mysql:///demo"></property>
        <property name = "username" value = "root"></property>
        <property name = "password" value = "123456"></property>
    </dataSource>
    
    <!-- mapper.xml 全路径 -->
    <mapper resource = "UserMapper.xml"></mapper>
</configuration>

3.6、编写测试类

public class UserTest{

    @Test
    public void test() throws Exception {
        //加载解析配置文件
        InputStream resourceAsStream = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

         User selectUser = new User();
        selectUser.setId(1);
        selectUser.setUsername("aaa111");
        User user = sqlSession.selectOne("UserMapper.selectOne", selectUser);
        System.out.println(user.toString());

        System.out.println("=============================");

        List<User> userList = sqlSession.selectList("UserMapper.selectList");
        for (User u: userList) {
            System.out.println(u.toString());
        }

        System.out.println("=============================");

        User updateUser = new User();
        updateUser.setId(3);
        updateUser.setUsername("test");
        int updateResult = sqlSession.update("UserMapper.update", updateUser);
        sqlSession.commit();
        System.out.println(updateResult > 0 ? "update success!": "update fail");

        System.out.println("=============================");

        User addUser = new User();
        addUser.setId(7);
        addUser.setUsername("testAdd");
        int addResult = sqlSession.update("UserMapper.add", addUser);
        sqlSession.commit();
        System.out.println(addResult > 0 ? "add success!": "add fail");

        System.out.println("=============================");

        int deleteResult = sqlSession.delete("UserMapper.delete", 3);
        sqlSession.commit();
        System.out.println(deleteResult > 0 ? "delete success!": "delete fail");

        System.out.println("=============================");
        userList = sqlSession.selectList("UserMapper.selectList");
        for (User u: userList) {
            System.out.println(u.toString());
        }

        sqlSession.close();
    }

3.7、执行结果

在这里插入图片描述

4、核心配置文件

<?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>

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

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

    <!--给实体类的全限定类名给别名-->
    <typeAliases>
        <!--给单独的实体起别名-->
      <!--  <typeAlias type="com.demo.pojo.User" alias="user"></typeAlias>-->
        <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
        <package name="com.demo.pojo"/>
    </typeAliases>

    <!--插件-->
    <plugins>
        <!--<plugin interceptor="com.demo.plugin.MyPlugin">-->
            <!--<property name="name" value="tom"/>-->
        <!--</plugin>-->

        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!--数据库方言配置-->
            <property name="dialect" value="mysql"/>
        </plugin>
        
        <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
            <!--指定当前通用mapper接口使用的是哪一个-->
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
        </plugin>
        
    </plugins>


    <!--environments:运行环境-->
    <environments default="development">
        <environment id="development">
            <!--当前事务交由JDBC进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--当前使用mybatis提供的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射配置文件-->
    <mappers>
       <!-- <mapper class="com.demo.mapper.IUserMapper"></mapper>-->
        <!--<package name="com.demo.mapper"/>-->
        <mapper resource="IUserMapper.xml"></mapper>
    </mappers>
</configuration>

在这里插入图片描述
特别注意 :标签中的标签配置有顺序要求

4.1、environments标签 数据库环境的配置,支持多环境配置

在这里插入图片描述
transactionManager 事务管理器类型,目前有两种:

  - JDBC: 直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
  
  - MANAGED: 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生 命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因 此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

dataSource 数据源类型有三种

  - POOLED:  利用“池”的概念将 JDBC 连接对象组织起来。

  - UNPOOLED: 每次被请求时打开和关闭连接。

  - JNDI:  为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。

4.2、 mapper标签 加载映射的 4种加载方式

 <!--使用相对于类路径的资源引用 -->
 <mapper resource="com/demo/mapper/UserMapper.xml"/> 
 
 <!--使用完全限定资源定位符(URL) -->
 <mapper url="file:///com/demo/mapper/UserMapper.xml"/> 
 
 <!--使用映射器接口实现类的完全限定类名 -->
 <mapper class="com.demo.mapper.UserMapper"/> 
 
 <!--将包内的映射器接口实现全部注册为映射器--> 
 <package name="com.demo.mapper"/>

4.3、 Properties标签:

实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的 properties文件
在这里插入图片描述

4.4、typeAliases标签(别名)

目的是为Java 类型设置一个短的名字

sqlMapConfig.xml

 	<!--给实体类的全限定类名给别名-->
	<typeAliases>
	    <!--给单独的实体起别名-->
	     <!--  <typeAlias type="com.demo.pojo.User" alias="user"></typeAlias>-->
	    <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
	    <package name="com.demo.pojo"/>
	</typeAliases>

UserMapper.xml

 	<select id="selectOne" resultType="user" parameterType="uSer">
        select * from user where id = #{id} and username = #{username}
    </select>

mybatis框架也为我们设置好的一些常用的类型的别名
在这里插入图片描述

4.5、 Mapper配置文件 在这里插入图片描述

这是简单的SQL语句,但是在实际业务中,往往要比这复杂的多,SQL是动态变化的,这时我们需要动态SQL配置,以满足我们业务需求

        <!--抽取sql片段简化编写-->
        <sql id="selectUser"> select * from User</sql>
                <!-- 动态条件查询 -->
        <select id="findByCondition" parameterType="user" resultType="user">
          <include refid="selectUser"></include>
	    <where>
	        <if test="id!=0">
	            and id=#{id}
	        </if>
	        <if test="username!=null">
	            and username=#{username}
	        </if>
	    </where>
	   </select>
  
       <!-- 循环执行sql的拼接操作 IN-->
	   <select id="findByIds" parameterType="list" resultType="user">
	      <include refid="selectUser"></include>
	    <where>
	        <!-- foreach标签 用于遍历集合
	           collection:代表要遍历的集合元素,注意编写时不要写#{} 
	           open:代表语句的开始部分
	           close:代表结束部分 
	           item:代表遍历集合的每个元素,生成的变量名 
	           sperator:代表分隔符
	         -->
	        <foreach collection="list" open="id in(" close=")" item="id" separator=",">
	           #{id}
	        </foreach>
	    </where>
	   </select>

5、执行流程

5.1、加载配置文件

SqlSession工厂构建器SqlSessionFactoryBuilder, 通过加载配置文件, 以输入流的方式构建一个SqlSessionFactory对象存在内存中

String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource); 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
SqlSessionFactory factory = builder.build(inputStream);

5.2、创建SqlSession 实例

SqlSession工厂对象SqlSessionFactory创建SqlSession 实例, 常用的有如下两个:
在这里插入图片描述

5.3、SqlSession会话对象

提供多个执行语句的接口方法

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

5.4、提交事务

void commit()
void rollback()

6、Mybatis的Dao层实现

传统方式是 实现一个Dao接口,同时编写一个该接口的实现类


//Dao接口
public interface UserDao {
    List<User> findAll() throws IOException;
}

//Dao接口实现类
public class UserDaoImpl implements UserDao {
    public List<User> findAll() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		SqlSession sqlSession = sqlSessionFactory.openSession(); 
		List<User> userList = sqlSession.selectList("userMapper.findAll"); sqlSession.close();
		return userList;
    } 
}

@Test
public void testDao() throws IOException { 
	UserDao userDao = new UserDaoImpl();
	List<User> all = userDao.findAll(); 
}

但是这种方式,需要我们对每个接口进行实现类编写,每个实现类都是一个sqlsession实例的构建,代码重复,耦合度高,通知会造成资源浪费

所以我们采用 Mybatis 的代理开发方式实现 DAO 层的开发,Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper 接口开发需要遵循以下规范:

  1. Mapper.xml文件中的namespace与mapper接口的全限定名相同
  2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
  3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
  4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
    在这里插入图片描述

上述方式是自定义接口查询,我们也可以使用mybatis框架自身提供的方法来操作

7、Mybatis复杂映射

7.1、一对一查询

一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
sql语句: select * from orders o,user u where o.uid=u.id;在这里插入图片描述
Order和User实体

public class Order {
    private int id;
    private Date ordertime;
    private double total;
    //代表当前订单属于哪一个客户
    private User user;
}

public class User {
    private int id;
    private String username;
}

配置OrderMapper.xml

<mapper namespace="com.lagou.mapper.OrderMapper"> 

   <!-- column 数据库结果集字段; property 自定义的javabean字段 -->
   <!-- 两种方式 -->
	<!--
	    <resultMap id="orderMap" type="com.demo.pojo.Order">
		<result column="uid" property="user.id"></result>
		<result column="username" property="user.username"></result> 
	</resultMap>
	-->

    <resultMap id="orderMap" type="com.demo.pojo.Order">
		<result property="id" column="id"></result>
		<result property="ordertime" column="ordertime"></result> 
		<result property="total" column="total"></result> 
		<association property="user" javaType="com.demo.pojo.User">
	        <result column="uid" property="id"></result>
	        <result column="username" property="username"></result>
	    </association>
	</resultMap>

    <select id="findAll" resultMap="orderMap">
		select * from orders o,user u where o.uid=u.id
    </select>
</mapper>

7.2、一对多查询

用户表和订单表的关系,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
sql语句: select *,o.id as oid from user u left join orders o on u.id=o.uid;
在这里插入图片描述

select * from user u left join orders o on u.id=o.uid;
在这里插入图片描述
这两个SQL语句进行比较,多了一个返回字段 oid
如果不加上这个字段,数据库查询返回结果集中包含两个一样的字段id,无法区分是用户ID还是订单ID
所以添加了一个订单ID字段:oid

Order和User实体

public class User {
    private int id;
    private String username;
    
	//代表当前用户具备哪些订单
	private List<Order> orderList;
}

public class Order {
    private int id;
    private Date ordertime;
    private double total;
    //代表当前订单属于哪一个客户
    private User user;
}

配置UserMapper.xml

<mapper namespace="com.demo.mapper.UserMapper"> 
	<resultMap id="userMap" type="com.demo.pojo.User">
		<result column="id" property="id"></result>
		<result column="username" property="username"></result>
		<collection property="orderList" ofType="com.demo.pojo.Order">
	            <result column="oid" property="id"></result>
	            <result column="ordertime" property="ordertime"></result>
	            <result column="total" property="total"></result>
	        </collection>
	    </resultMap>
	<select id="findAll" resultMap="userMap">
		select *,o.id oid from user u left join orders o on u.id=o.uid
    </select>
</mapper>

7.3、多对多查询

用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色

select u.,r.,r.id as rid
from user u
left join sys_user_role ur on u.id = ur.userid
inner join sys_role r on ur.roleid=r.id;

在这里插入图片描述
Role和User实体

public class User {
    private int id;
    private String username;
    
	//代表当前用户具备哪些订单
	private List<Order> orderList;
    
    //代表当前用户具备哪些角色
    private List<Role> roleList;
}

public class Role {
    private int id;
    private String rolename;
}

配置UserMapper.xml

<resultMap id="userRoleMap" type="com.demo.pojo.User">
	<result column="id" property="id"></result>
	<result column="username" property="username"></result>
	<collection property="roleList" ofType="com.demo.pojo.Role">
        <result column="rid" property="id"></result>
        <result column="rolename" property="rolename"></result>
    </collection>
</resultMap>
<select id="findAllUserAndRole" resultMap="userRoleMap">
	select u.*,r.*,r.id as rid from user u left join sys_user_role ur on u.id=ur.userid
	inner join sys_role r on ur.roleid=r.id
</select>

8、Mybatis注解开发

8.1、MyBatis的常用注解

好处:使用注解开发方式,可以减少编写Mapper 映射文件

@Insert:实现新增

@Update:实现更新

@Delete:实现删除

@Select:实现查询

@Result:实现结果集封装

@Results:可以与@Result 一起使用,封装多个结果集

@One:实现一对一结果集封装

@Many:实现一对多结果集封装
在这里插入图片描述
Mapper接口

   public interface IUserMapper {

    //查询所有用户、同时查询每个用户关联的订单信息
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "orderList",column = "id", javaType = List.class,
                many=@Many(select = "com.demo.mapper.IOrderMapper.findOrderByUid"))
    })
    public List<User> findAll();

    //查询所有用户、同时查询每个用户关联的角色信息
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "roleList",column = "id", javaType = List.class,
             many = @Many(select = "com.demo.mapper.IRoleMapper.findRoleByUid"))
    })
    public List<User> findAllUserAndRole();

    //添加用户
    @Insert("insert into user values(#{id},#{username})")
    public void addUser(User user);

    //更新用户
    @Update("update user set username = #{username} where id = #{id}")
    public void updateUser(User user);

    //查询用户
    @Select("select * from user")
    public List<User> selectUser();

    //删除用户
    @Delete("delete from user where id = #{id}")
    public void deleteUser(Integer id);

    //根据id查询用户
    @Select({"select * from user where id = #{id}"})
    public User findUserById(Integer id);
}

public interface IOrderMapper {
    //查询订单的同时还查询该订单所属的用户
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "orderTime",column = "orderTime"),
            @Result(property = "total",column = "total"),
            @Result(property = "user",column = "uid",javaType = User.class,
                    one=@One(select = "com.demo.mapper.IUserMapper.findUserById"))
    })
    @Select("select * from orders")
    public List<Order> findOrderAndUser();

    @Select("select * from orders where uid = #{uid}")
    public List<Order> findOrderByUid(Integer uid);
}

public interface IRoleMapper {
    @Select("select * from sys_role r,sys_user_role ur where r.id = ur.roleid and ur.userid = #{uid}")
    public List<Role> findRoleByUid(Integer uid);
}

=============================================================================================

9、Mybatis缓存

默认是将数据库中查询结果存储到内存中,避免频繁与数据库交互,提高响应速度,节省系统资源

Mybatis 提供了对缓存的支持,分为一级缓存和二级缓存,这两级缓存实际都是以 HashMap数据结构形式存储在内存中(二级缓存也有可能是在磁盘中存储)在这里插入图片描述

9.1、一级缓存

SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的

    @Test
    public void firstLevelCache(){
        // 第一次查询id为1的用户
        User user1 = userMapper.findUserById(1);
        // 第二次查询id为1的用户
        User user2 = userMapper.findUserById(1);

        System.out.println(user1==user2);
    }

在这里插入图片描述
根据执行结果分析,第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息,说明第二次查询是从缓存中获取的User对象的引用

    @Test
    public void firstLevelCache(){
        // 第一次查询id为1的用户
        User user1 = userMapper.findUserById(1);
        // 第二次查询id为1的用户
        User user2 = userMapper.findUserById(1);

        //更新用户
        User user = new User();
        user.setId(1);
        user.setUsername("haha");
        userMapper.updateUser(user);
        sqlSession.commit();
        
        System.out.println(user1==user2);
    }

在这里插入图片描述
同样是对user表进行两次查询,只不过两次查询之间进行了一次update操作。
根据结果分析,如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一级 缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

在这里插入图片描述
一级缓存原理探究与源码分析
1、一级缓存到底是什么?
2、一级缓存什么时候被创建?
3、一级缓存的工作流程是怎样的?

在这里插入图片描述
深入分析,流程走到Perpetualcache中的clear()方法之后,会调用其cache.clear()方法,点进去发现,cache其实就是private Map cache = new HashMap(); 也就是一个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是本地存放的一个map对象,每一个SqISession都会存放一个map对象的引用,

最有可能创建缓存的地方是哪里呢? 我觉得是Executor,为什么这么认为? 因为Executor是执行器,用来执行SQL请求,而且清除缓存的方法也在Executor中执行,所以很可能缓存的创建也很有可能在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法很像是创建缓存的方法,跟进去看看,会发现createCacheKey方法是由BaseExecutor执行的,代码如下
在这里插入图片描述

一级缓存更多是用于查询操作,(毕竟一级缓存也叫做查询缓存)
BaseExecutor在执行query查询时,会先去查询缓存Map中是否存在,如果存在直接返回,不存在,再查询数据库,查询出结果会先放入缓存Map中,再返回
在这里插入图片描述
在这里插入图片描述

9.2、二级缓存

mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,共用二级缓存,二级缓存是跨SqlSession的
需要注意的是如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中
在这里插入图片描述
开启二级缓存方式

首先在全局配置文件sqlMapConfig.xml中添加

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

1、在Mapper.xml文件中开启缓存
mapper.xml文件中就这么一个空标签,其实这里可以配置属性
PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现Cache接口来自定义缓存。

<!--开启二级缓存--> 
<cache></cache>

2、注解

@CacheNamespace(implementation = RedisCache.class)//开启二级缓存
public interface IUserMapper {
}

在这里插入图片描述

开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作。因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口

在执行commit()操作,二级缓存数据也会被清空,这样可以避免数据库脏读

Mapper.xml文件中
mybatis中还可以配置 userCache 和 flushCache 等配置项
userCache:是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出 sql去查询,默认情况是true, 即该sql使用二级缓存

<select id="selectUserByUserId" useCache="false" resultType="User" parameterType="int">
    select * from user where id=#{id}
</select>

flushCache:针对 SELECT 标签 默认是 false,不刷新缓存,UPDATE、DELETE、INSERT默认是true,刷新缓存
在这里插入图片描述
二级缓存构建在一级缓存之上,在收到查询请求时,MyBatis 首先会查询二级缓存,若二级缓存未命
中,再去查询一级缓存,一级缓存没有,再查询数据库。

使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

一级缓存和二级缓存的生命周期分别是?
一级缓存的生命周期是会话级别,因为一级缓存是存在Sqlsession的成员变量Executor的成员变量localCache中的。而二级缓存的生命周期是整个应用级别,因为二级缓存是存在Configuration对象中,而这个对象在应用启动后一直存在

同时配置一级缓存和二级缓存后,先查询哪个缓存?
当然是先查询二级缓存再查询一级缓存啊,因为一级缓存的实现在BaseExecutor,而二级缓存的实现在CachingExecutor,CachingExecutor是BaseExecutor的装饰器

10、Mybatis插件

四大组件
1、Executor
2、StatementHandler
3、ParameterHandler
4、ResultSetHandler
Mybatis提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦 截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的 动态 代理实现的,换句话说,MyBatis中的四大对象都是代理对象
在这里插入图片描述
MyBatis所允许拦截的方法如下:

  • 执行器Executor (update、query、commit、rollback等方法);
  • SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
  • 参数处理器ParameterHandler (getParameterObject、setParameters方法);
  • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);

10.1、插件原理

在四大对象创建的时候
1、每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
2、获取到所有的Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返 回 target 包装后的对象
3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP (面向切面)我们的插件可 以 为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行;

插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说

public ParameterHandler newParameterHandler(MappedStatement mappedStatement,Object object, BoundSql sql, InterceptorChain interceptorChain){
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
   return parameterHandler;
}

public Object pluginAll(Object target) {
	for (Interceptor interceptor : interceptors) { 
		target = interceptor.plugin(target);
	}
    return target;
}

interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调用拦截器链 中的拦截器依次的对目标进行拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis 中的四大对象。返回的target是被重重代理后的对象

如果我们想要拦截Executor的query方法,那么可以这样定义插件

@Intercepts({//大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
        @Signature(type= StatementHandler.class, //这是指拦截哪个接口
                  method = "prepare",  //这个接口内的哪个方法名,不要拼错了
                  args = {Connection.class,Integer.class}) 
                  // 这是拦截的方法的入参,按顺序写到,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
})
public class MyPlugin implements Interceptor {

    /*
     * 拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强....");
        return invocation.proceed(); //原方法执行
    }

    /*
     * 主要为了把当前的拦截器生成代理存到拦截器链中
     */
    @Override
    public Object plugin(Object target) {
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }

    /*
     *  获取配置文件的参数
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件的参数是:"+properties);
    }
}

除此之外,我们还需将插件配置到sqlMapConfig.xm l中

<plugins>
	<plugin interceptor="com.demo.plugin.MyPlugin"> 
        <!--配置参数-->
        <property name="name" value="test"/>
    </plugin>
</plugins>

这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待 准备工作做完后,MyBatis处于就绪状态。我们在执行SQL时,需要先通过DefaultSqlSessionFactory 创 建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后, MyBatis会通过JDK动态代理为实例生成代理类。这样插件逻辑即可在 Executor相关方法被调用前执行。

11、通用 mapper

通用Mapper就是为了解决单表增删改查,基于Mybatis的插件机制。开发人员不需要编写SQL,不需要 在DAO中增加方法,只要写好实体类,就能支持相应的增删改查方法

在pom.xml中引入mapper的依赖

<dependency> 
	<groupId>tk.mybatis</groupId> 
	<artifactId>mapper</artifactId> 
	<version>3.1.2</version>
</dependency>

sqlMapConfig.xml配置文件

<plugins>
    <!-- 通用Mapper接口,多个通用接口用逗号隔开 -->
	<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"> 
		<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
    </plugin>
</plugins>

实体类设置主键

如果实体类名称和表名称不一致,可以在实体类上添加注解@Table(name=“指定数据库表名”)

@Table(name = "t_user")
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY) 
	private Integer id;
	
	private String username;
}

//Mapper接口
public interface UserMapper  extends Mapper<User> {
}

//测试
@Test
public void mapperTest() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setId(1);
    
    //(1)mapper基础接口
    
	//select 接口
	//根据实体中的属性进行查询,只能有—个返回值
	User user1 = userMapper.selectOne(user); 
	
	//根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
	userMapper.selectByPrimaryKey(1);
	 
	//查询全部结果 
	List<User> users = userMapper.select(null); 
	
	 //根据实体中的属性查询总数,查询条件使用等号
	userMapper.selectCount(user);
	
	// insert 接口
	//保存一个实体,null值也会保存,不会使用数据库默认值
	int insert = userMapper.insert(user); 
	
    //保存实体,null的属性不会保存,会使用数据库默认值
	int insertSelective = userMapper.insertSelective(user); 
	
	// update 接口
	//根据主键更新实体全部字段,null值会被更新
	int updateByPrimaryKey = userMapper.updateByPrimaryKey(user);
	
	// delete 接口
	//根据实体属性作为条件进行删除,查询条件使用等号
	int delete = userMapper.delete(user); 
	
	//根据主键字段进行删除,方法参数必须包含完整的主键属性
	userMapper.deleteByPrimaryKey(1); 
	
	//(2)example方法
	Example example = new Example(User.class); 
	example.createCriteria().andEqualTo("id", 1); 	
	example.createCriteria().andLike("val", "1");
	//自定义查询
	List<User> users1 = userMapper.selectByExample(example);
}

执行结果
在这里插入图片描述

12、Mybatis架构原理在这里插入图片描述

我们把Mybatis的功能架构分为三层:
(1) API接口层:提供给外部使用的接口 API,开发人员通过这些本地API来操纵数据库。接口层一接收到
调用请求就会调用数据处理层来完成具体的数据处理。

MyBatis和数据库的交互有两种方式:
a. 使用传统的MyBati s提供的API ;
b. 使用Mapper代理的方式

(2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根 据调用的请求完成一次数据库操作。

(3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑

主要构件及其相互关系
在这里插入图片描述
在这里插入图片描述

12.1、总体流程

(1) 加载配置并初始化
触发条件:加载配置文件
配置来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml); —个是java代码中的注解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement 对象,存储在内存之中

(2) 接收调用请求
触发条件:调用Mybatis提供的API 传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。

(3) 处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象

处理过程:
A) 根据SQL的ID查找对应的MappedStatement对象。
B) 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
C) 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
D) 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理 结果。
E) 释放连接资源。

(4) 返回处理结果
将最终的处理结果返回。

Mybatis 延迟加载
在开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。

例子

  • 在一对多中,当我们有一个用户,它有个100个订单 在查询用户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的用户查出来?
  • 回答 在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。 在查询订单时,订单所属的用户信息应该是随着订单一起查询出来。

12.2、延迟加载

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

  • 优点: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表
    速度要快。
  • 缺点: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时
    间,所以可能造成用户等待时间变长,造成用户体验下降。
  • 在多表中: 一对多,多对多:通常情况下采用延迟加载 一对一(多对一):通常情况下采用立即加载
  • 注意: 延迟加载是基于嵌套查询来实现的

在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。
fetchType=“lazy” 懒加载策略
fetchType=“eager” 立即加载策略

在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。

<settings>
<!--开启全局延迟加载功能-->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

12.3、延迟加载原理实现

它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属性的getting 方法时,进入拦截器方法。比如调用 a.getB().getName() 方法,进入拦截器的invoke(…) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对 象的 SQL ,把 B 查询上来,然后调用 a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() 方法的调用。这就是延迟加载的基本原理 总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。
在这里插入图片描述

Setting 配置加载

public class Configuration { 

	/** aggressiveLazyLoading:
	 * 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考 lazyLoadTriggerMethods).
	 * 默认为false
	 */
    protected boolean aggressiveLazyLoading;
  
  /**
	* 延迟加载触发方法
	*/
	protected Set<String> lazyLoadTriggerMethods = new HashSet<String> (Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));

	/** 是否开启延迟加载 */
	protected boolean lazyLoadingEnabled = false;
	
	/**
	* 默认使用Javassist代理工厂 
	* @param proxyFactory
	*/
    public void setProxyFactory(ProxyFactory proxyFactory) {
	    if (proxyFactory == null) {
	      proxyFactory = new JavassistProxyFactory();
	    }
	this.proxyFactory = proxyFactory; 
	}
//省略... 
}

延迟加载代理对象创建
Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。ResultSetHandler 接口只有一个实现,DefaultResultSetHandler,接下来看下延迟加载相关的一个核心的方法

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap,ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
	 this.useConstructorMappings = false; 
	 
	 final List<Class<>> constructorArgTypes = newArray List<Class<>>();
	 
	 final List<Object> constructorArgs = new ArrayList<Object>();
	 
	Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
	
	if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
		final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
		for (ResultMapping propertyMapping : propertyMappings) { 
		// 判断属性有没配置嵌套查询,如果有就创建代理对象
			if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { 
			    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, 
			    configuration, objectFactory, constructorArgTypes, constructorArgs);
				break;
			 }
		} 
	}
	this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); 
    return resultObject;
  }

默认采用javassistProxy进行代理对象的创建
在这里插入图片描述

JavasisstProxyFactory实现

 public class JavassistProxyFactory implements ProxyFactory {
    public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        return JavassistProxyFactory.EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
    }

    /**
      * 代理对象实现,核心逻辑执行 
      */
  private static class EnhancedResultObjectProxyImpl implements MethodHandler {
       static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        org.apache.ibatis.javassist.util.proxy.ProxyFactory enhancer = new org.apache.ibatis.javassist.util.proxy.ProxyFactory();
        enhancer.setSuperclass(type);

        try {
            //通过获取对象方法,判断是否存在该方法
            type.getDeclaredMethod("writeReplace");
            if (log.isDebugEnabled()) {
                log.debug("writeReplace method was found on bean " + type + ", make sure it returns this");
            }
        } catch (NoSuchMethodException var10) {
            enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
        } catch (SecurityException var11) {
        }

        Class<?>[] typesArray = (Class[])constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
        Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);

        Object enhanced;
        try {
            //创建新的代理对象
            enhanced = enhancer.create(typesArray, valuesArray);
        } catch (Exception var9) {
            throw new ExecutorException("Error creating lazy proxy.  Cause: " + var9, var9);
        }

        //设置代理执行器
        ((Proxy)enhanced).setHandler(callback);
        return enhanced;
    }
    
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
            String methodName = method.getName();

            try {
                synchronized(this.lazyLoader) {
                    if ("writeReplace".equals(methodName)) {
                        Object original;
                        if (this.constructorArgTypes.isEmpty()) {
                            original = this.objectFactory.create(this.type);
                        } else {
                            original = this.objectFactory.create(this.type, this.constructorArgTypes, this.constructorArgs);
                        }

                        PropertyCopier.copyBeanProperties(this.type, enhanced, original);
                        if (this.lazyLoader.size() > 0) {
                            return new JavassistSerialStateHolder(original, this.lazyLoader.getProperties(), this.objectFactory, this.constructorArgTypes, this.constructorArgs);
                        }

                        return original;
                    }

                    if (this.lazyLoader.size() > 0 && !"finalize".equals(methodName)) {
                        if (!this.aggressive && !this.lazyLoadTriggerMethods.contains(methodName)) {
                            String property;
                            if (PropertyNamer.isSetter(methodName)) {
                                property = PropertyNamer.methodToProperty(methodName);
                                this.lazyLoader.remove(property);
                            } else if (PropertyNamer.isGetter(methodName)) {
                                property = PropertyNamer.methodToProperty(methodName);
                                if (this.lazyLoader.hasLoader(property)) {
                                    this.lazyLoader.load(property);
                                }
                            }
                        } else {
                            this.lazyLoader.loadAll();
                        }
                    }
                }

                return methodProxy.invoke(enhanced, args);
            } catch (Throwable var10) {
                throw ExceptionUtil.unwrapThrowable(var10);
            }
        }
  }

}

IDEA调试问题 当配置aggressiveLazyLoading=true,在使用IDEA进行调试的时候,如果断点打到 代理执行逻辑当中,你会发现延迟加载的代码永远都不能进入,总是会被提前执行。 主要产生的原 因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对 象的方法。

14、Mybatis 设计模式

在这里插入图片描述

14.1、Builder构建者模式

Builder模式的定义是"将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表 示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范 围,就可以使用工厂模式和Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加 复杂的对象的构建,甚至只会构建产品的一个部分,直白来说,就是使用多个简单的对象一步一步构建 成一个复杂的对象

Mybatis中的体现:SqlSessionFactory 的构建过程
Mybatis的初始化工作非常复杂,不是只用一个构造函数就能搞定的。所以使用了建造者模式,使用了 大 量的Builder,进行分层构造,核心对象Configuration使用了 XmlConfigBuilder来进行构造
在这里插入图片描述
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的 MybatisMapConfig.xml 和所有的 *Mapper.xml 文件,构建 Mybatis 运行的核心对象 Configuration 对 象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

private void parseConfiguration(XNode root) {
	try {
		//解析<properties />标签 
		propertiesElement(root.evalNode("properties")); 
		
		// 解析 <settings /> 标签
		Properties settings = settingsAsProperties(root.evalNode("settings"));
		
		 //加载自定义的VFS实现类
		loadCustomVfs(settings);
		
		// 解析 <typeAliases /> 标签 
		typeAliasesElement(root.evalNode("typeAliases"));
		
		//解析<plugins />标签
		pluginElement(root.evalNode("plugins"));
		
		// 解析 <objectFactory /> 标签
		objectFactoryElement(root.evaINode("obj ectFactory"));
		
		// 解析 <objectWrapper Factory /> 标签
		objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 
		
		// 解析 <reflectorFactory /> 标签 
		reflectorFactoryElement(root.evalNode("reflectorFactory"));
		
		// 赋值 <settings /> 到 Configuration 属性
		settingsElement(settings);
		// read it after objectFactory and objectWrapperFactory issue #631
		
		// 解析 <environments /> 标签
		 environmentsElement(root.evalNode("environments"));
		 
		// 解析 <databaseIdProvider /> 标签 
		databaseldProviderElement(root.evalNode("databaseldProvider"));
		
		//解析<mappers />标签 
		mapperElement(root.evalNode("mappers"));
	}
}

在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser 解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所 能包括的,因此大量采用了 Builder模式来解决
在这里插入图片描述
SqlSessionFactoryBuilder类根据不同的输入参数来构建SqlSessionFactory这个工厂对象

14.2、工厂模式

在Mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创 建型模式。
在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建 其他类的实例,被创建的实例通常都具有共同的父类

Mybatis中执行Sql语句、获取Mappers、管理事务的核心接口SqlSession的创建过程使用到了工厂模式。
有一个 SqlSessionFactory 来负责 SqlSession 的创建

14.3、代理模式

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy,它是一种对象结构型模式,代理模式分为静态代理和动态代理

Mybatis中实现: 代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接口,不需要实现,由Mybatis后台帮我们完成具体SQL的执行。 当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又 会调用 mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    public MapperProxyFactory(Class<T> mapperInterface) {
          this.mapperInterface = mapperInterface;
   }
   public Class<T> getMapperInterface() {
       return mapperInterface;
   }
   public Map<Method, MapperMethod> getMethodCache() {
		return methodCache;
		@SuppressWarnings("unchecked")
		protected T newInstance(MapperProxy<T> mapperProxy) {
			return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
				new Class[] { mapperInterface },
		        mapperProxy);
		}
		public T newInstance(SqlSession sqlSession) {
	            final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
	            return newInstance(mapperProxy);
        }
  }
}

在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用T newInstance(MapperProxy mapperProxy)生成代理对象然后返回。而查看MapperProxy的代码,可 以看到如下内容:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		try {
			if (Object.class.equals(method.getDeclaringClass())) {
				return method.invoke(this, args); 
			} else if (isDefaultMethod(method)) {
               return invokeDefaultMethod(proxy, method, args);
            }
		} catch (Throwable t) {
			throw ExceptionUtil.unwrapThrowable(t);
		}
		final MapperMethod mapperMethod = cachedMapperMethod(method); 
		return mapperMethod.execute(sqlSession, args);
	}
}

非常典型的,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。通 过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给 MapperProxy.invoke方法,而该方法则会调用后续的 sqlSession.cud>executor.execute>prepareStatement 等一系列方法,完成 SQL 的执行和返回

15、Mybatis-Plus

官网: https://mybatis.plus/ 或 https://mp.baomidou.com/
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简 化开发、提高效率而生。

Mybatis-Plus是由baomidou(苞米豆)组织开发并且开源的,目前该组织大概有30人左右。
码云地址: https://gitee.com/organizations/baomidou

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配 置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大 的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同 于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、 Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢 查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误 操作
<!-- Spring Boot -->
<dependency>
	<groupId>com.baomidou</groupId> 
	<artifactId>mybatis-plus-boot-starter</artifactId> 
	<version>3.4.0</version>
</dependency>

<!-- Spring MVC mybatis-plus插件依赖 -->
<dependency> 
	<groupId>com.baomidou</groupId> 
	<artifactId>mybatis-plus</artifactId> 
	<version>3.4.0</version>
</dependency>

在这里插入图片描述
对于Mybatis整合MP有常常有三种用法,
Mybatis+MP
Spring+Mybatis+MP
Spring Boot+Mybatis+MP。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值