MyBatis 学习笔记

只写一些不太记得的东西

简单的查询例子
(1)以前的老版本方法

  		String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //根据xm1配置文件(全局配置文件)创建一 个SqlSess ionFactory对象,有数据源一些运行环境信息
        //sql映射文件;配置了每一个sql,以及sql的封装规则等。
       // 将sql映射文件注册在全局配置文件中
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获得一个SqlSession,用来执行sql语句
        SqlSession session = sqlSessionFactory.openSession();
        Customers customersMapper = (Customers)session.selectOne("CustomersMapper.selectOne", 1);
        //bean.Customers{id=1, name='汪涵', email='wf@126.com', birth=Tue Feb 02 00:00:00 CST 2010}
        System.out.println(customersMapper);

(2)现在的通用写法,接口式编程,写一个Mappper接口和对应Mapper.xml里的方法一致,并获取接口实现类对象,然后去执行sql方法。

	public interface CustomersMapper {
    	public Customers selectOne(Integer id);
	}
	
       @Test
    public void test2() throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        //会得到一个代理对象,去执行CRUD。
        //org.apache.ibatis.binding.MapperProxy@36a5cabc
        CustomersMapper mapper = session.getMapper(CustomersMapper.class);
        Customers customers = mapper.selectOne(2);
        //bean.Customers{id=2, name='王菲', email='wangf@163.com', birth=Mon Dec 26 00:00:00 CST 1988}
        System.out.println(customers);
    }

mybatis全局配置:mybatis-config.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CustomersMapper.xml"/>
    </mappers>
</configuration>

sql映射文件:CustomersMapper.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">
       <!--Mybatis中namespace用于绑定dao接口,dao接口的方法对应mapper中的sql语名。-->
<mapper namespace="CustomersMapper">
    <select id="selectOne" resultType="Customers">
        select * from customers where id = #{id}
    </select>
</mapper>

总结:
1、接口式编程
原生: Dao ====> DaoImpl
mybatis: Mapper ====> xxMapper.xml

2、SqlSession代表和数据库的一次会话;用完必须关闭;
3、SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。
4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。
(将接口和xml进行绑定)
EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
5、两个重要的配置文件:
mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等…系统运行环境信息
sql映射文件:保存了每一个sql语句的映射信息:
将sql抽取出来。


SqlSessionFactory与SqlSession的相关资料详细请看:Mybatis的SqlSession运行原理

首先SqlSessionFactory与SqlSession都是同一个接口

SqlSessionFactory是通过SqlSessionFactoryBuilder的build方法创建的

而build方法创建的是一个SqlSessionFactory的实现类,叫DefaultSqlSessionFactory

然后这个实现类主要用的设计模式是建造者(build)模式,而里面最终要达到的一个目的是为了创建出DefaultSqlSession,这个是SqlSession的实现类.

前面说了SqlSession也是同一个接口,那么SqlSession=SqlSessionFactory.openSession()就相当于SqlSession=DefaultSqlSession这个实现类.

那么这个实现类是干什么的呢?这个实现类可以进行增删查改以及事务操作等.

那么DefaultSqlSession是怎么进行这些操作的呢?答:通过调用Executor执行器.

所以,咱们平时见到的SqlSession.select()等等其实是在操作DefaultSqlSession.select()的方法,而DefaultSqlSession的方法其实也不是就能够实现对数据库进行操作的,而是通过调用Executor执行器来执行

而Executor执行器其实也是一个接口,而在解析配置文件的时候已经解析出来SQL,那么根据一路传过来的SQL以及参数等信息,Executor再调度StatementHandler等对象对数据库进行增删查改操作.

总结:现在你应该明白了吧,其实我们看到的都是表面,你认为的SqlSession能进行数据库操作其实不是的,是它的实现类再调用底层的Executor,底层的Executor再调度相关数据库操作对象才搞定的,而再要深入它的相关对象,那么就深似海了…

MyBatis 的全局配置文件

配置文档的顶层结构如下:xml配置标签一定要按照以下先后顺序写
  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

设置(settings)

mapUnderscoreToCamelCase
是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
jdbcTypeForNull
要能让mybatis插入语句可以插入null
	<settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="jdbcTypeForNull" value="NULL" />
    </settings>

更多设置属性去看官方文档:配置

类型别名(typeAliases)

指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
    <typeAliases>
        <package name="com.yhy.bean"/>
    </typeAliases>

比如原来resultType要写全限定名

<mapper namespace="CustomersMapper">
    <select id="selectOne" resultType="com.yhy.bean.Customers">
        select * from customers where id = #{id}
    </select>
</mapper>

现在只要改成 resultType=“Customers”。

环境配置(environments)

可以配置多个环境(多个environment 标签),以便于生产或开发环境中使用,以下就是指定使用test环境
   <environments default="test">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

CRUD编写

插入一条数据,并返回自增的主键的值
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
         useGeneratedKeys="true":使用自增主键获取主键值策略
         keyProperty:指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
<insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into customers(name,email) values (#{name},#{email})
</insert>
        SqlSession session = getSqlSession();
        CustomersMapper mapper = session.getMapper(CustomersMapper.class);
        Customers customers = new Customers();
        customers.setEmail("123@163.com");
        customers.setName("马云");
       	mapper.insert(customers);
       //bean.com.yhy.bean.Customers{id=23, name='马云', email='123@163.com', birth=null}
        System.out.println(customers);

${} 和 #{}

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:
ORDER BY ${columnName}

这样,MyBatis 就不会修改或转义该字符串了。
比如表名不支持占位符,使用${tablename}就行

@Select("select * from${tablename}  where name = #{name}")
User findByName(@Param("tablename") String name);

关联(association)

使用association封装关联的单个对象,作用在多对一的多这一方

一个订单对应一个顾客,所以订单对象里包含着一个顾客信息。
我们接下来就是要根据订单ID查出订单详情。

customers
id	name	  email			birth
1	汪涵	wf@126.com		2010-02-02
2	王菲	wangf@163.com	1988-12-26
3	林志玲	linzl@gmail.com	1984-06-12
4	汤唯	tangw@sina.com	1986-06-13
5	成龙	Jackey@gmai.com	1955-07-14

orders
order_id	order_name	 order_date		c_id
1			AA			 2010-03-04		  1
2			BB			 2000-02-01		  5
4			GG			 1994-06-28		  3

查询订单ID为2的订单详情
select orders.*,customers.`name`,customers.email,customers.birth from orders left join customers on orders.c_id=customers.id where orders.order_id=2

结果
order_id	order_name	order_date	c_id	name	   email				  birth
4				GG		1994-06-28	 3		林志玲	linzl@gmail.com		1984-06-12

bean

public class Customers {
    private int id;
    private String name;
    private String email;
    private Date birth;
}
    public class Orders {
    private int orderId;
    private String orderName;
    private Date orderDate;
    //对应一个顾客
    private Customers customers;
}

OrdersMapper

public interface OrdersMapper {
    public Orders selectOne(Integer orderId);
}

OrdersMapper.xml

<result column="c_id" property="customers.id"></result>
<result column="name" property="customers.name"></result>

把查出的关联对象数据直接赋值,而不用使用上面这种对象里的对象.属性这种方式,下面方式可以使其模块化。

方式一
嵌套在resultMap
column字段对应的应当是数据库查询结果字段,而不是数据库中的字段。(比如别名)
<?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="OrdersMapper">
    <resultMap id="MyMap" type="Orders">
        <!--
        column为查出来的数据库字段,有别名就写别名
        property为对应的bean字段(association中的也是)
        -->
        <id column="order_id" property="orderId"></id>
        <result column="order_name" property="orderName"></result>
        <result column="order_date" property="orderDate"></result>
         <!--
        association可以指定联合的javaBean对象
		property="dept":指定哪个属性是联合的对象,结果都封装给他
		javaType:指定这个属性对象的类型[不能省略]
		-->
        <association property="customers" javaType="Customers">
            <id column="c_id" property="id"></id>
            <result column="name" property="name"></result>
            <result column="email" property="email"></result>
            <result column="birth" property="birth"></result>
        </association>
    </resultMap>

    <select id="selectOne" resultMap="MyMap">
        select orders.*,customers.`name`,customers.email,customers.birth from orders left join customers on orders.c_id=customers.id where orders.order_id=#{orderId}
    </select>
</mapper>
方式二

association中使用select,分步查询。

先查出订单数据,再根据订单数据里的顾客id查出顾客对象(先从数据库简单查出(一对多)多的这一方,再把查出的关联外键当做参数传给association里的select语句(一对多的一的这方),查出关联对象,最后封装进去)。

select语句不能写成方式一的联合查询了,要改成单一查询。

OrdersMapper.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="OrdersMapper">
    <resultMap id="MyMap2" type="Orders">
        <id column="order_id" property="orderId"></id>
        <result column="order_name" property="orderName"></result>
        <result column="order_date" property="orderDate"></result>
        <result column="c_id" property="customers.id"></result>
        <result column="name" property="customers.name"></result>
        <!--
            select:表明当前属性是调用select指定的方法查出的结果
	 		column:指定将上面查询结果哪一列的值传给这个方法(就是写上面的colum就行)
	 		流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
	 		-->
        <association property="customers" column="c_id" select="CustomersMapper.selectOne">
        </association>
    </resultMap>

    <select id="selectOne" resultMap="MyMap2">
        select *
        from orders
        where order_id = #{ordersId}
        </select>
</mapper>

测试

    @Test
    public void test() throws Exception {
        SqlSession session = getSqlSession();
        OrdersMapper mapper = session.getMapper(OrdersMapper.class);
        Orders orders = mapper.selectOne(3);
        //Orders(orderId=4, orderName=GG, orderDate=Tue Jun 28 00:00:00 CST 1994, customers=bean.com.yhy.bean.Customers{id=3, name='林志玲', email='linzl@gmail.com', birth=Tue Jun 12 00:00:00 CST 1984})
        System.out.println(orders);
    }

懒加载(分步查询下使用)

按照上面的查询方式,每次都会把关联对象查出来,而懒加载是当有需要关联对象的时候才执行关联的sql语句查出来,要设置全局配置里的两个属性。
  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true
  • aggressiveLazyLoading:开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。false
  <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

1.没有用到顾客对象的测试

    @Test
    public void test() throws Exception {
        SqlSession session = getSqlSession();
        OrdersMapper mapper = session.getMapper(OrdersMapper.class);
        Orders orders = mapper.selectOne(2);
        System.out.println(orders.getOrderName());
    }
[DEBUG] - org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137) - Opening JDBC Connection
[DEBUG] - org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:434) - Created connection 2143139988.
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==>  Preparing: select * from orders where order_id = ?
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==> Parameters: 2(Integer)
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - <==      Total: 1
BB

可以看出连接之后只执行了一句sql

2.有用到顾客对象的测试

 @Test
    public void test() throws Exception {
        SqlSession session = getSqlSession();
        OrdersMapper mapper = session.getMapper(OrdersMapper.class);
        Orders orders = mapper.selectOne(2);
        System.out.println(orders.getCustomers());
    }
[DEBUG] - org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137) - Opening JDBC Connection
[DEBUG] - org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:434) - Created connection 2143139988.
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==>  Preparing: select * from orders where order_id = ?
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==> Parameters: 2(Integer)
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - <==      Total: 1
[16:48:01:717] [DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==>  Preparing: select * from customers where id = ?
[16:48:01:718] [DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==> Parameters: 5(Integer)
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - <==      Total: 1
bean.com.yhy.bean.Customers{id=5, name='成龙', email='Jackey@gmai.com', birth=Thu Jul 14 00:00:00 CST 1955}

可以看出,这次测试有用到关联的对象,mybatis才会去执行查询语句。
以上就是是否开启懒加载的区别。

集合 collection

一个顾客有多个订单,把这个顾客的多个订单都封装到顾客对象中,collection作用在一(一对多)的这方。
方式一
嵌套在resultMap

CustomersMapper.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="CustomersMapper">
    <resultMap id="MyMap1" type="Customers">
        <!--这里返回的数据库字段为多的一方关联一的一方的主键c_id,就是订单表里的顾客ID,而不是顾客表原有的id,这样写才会生效,如果写顾客表主键ID上去会报错-->
        <id column="c_id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="email" property="email"></result>
        <result column="birth" property="birth"></result>
        <collection property="orders" ofType="Orders">
         <!--多的这边表啥都不用动,直接用需要啥字段写啥,主键就用ID,其他用result-->
            <id column="order_id" property="orderId"></id>
            <result column="order_name" property="orderName"></result>
            <result column="order_date" property="orderDate"></result>
        </collection>
    </resultMap>

<!--查询时,记得查出的是一的一方只有一个主键,就是结果有多条记录,但是一的这方始终是同一条,即一个顾客多个订单,只允许出现一个顾客,不能出现其他顾客
当然可以试下查多个顾客,返回list应该也是可以的
-->
    <select id="getCustomersById" resultMap="MyMap1">
        SELECT orders.*,
               customers.NAME,
               customers.email,
               customers.birth
        FROM orders
                 LEFT JOIN customers ON orders.c_id = customers.id
        WHERE customers.id = #{id}</select>
</mapper>

getCustomersById:连表查询,结果如下

order_idorder_nameorder_datec_idNAMEemailbirth
4GG28/6/19943林志玲linzl@gmail.com12/6/1984
5DD14/10/20203林志玲linzl@gmail.com12/6/1984

Bean

public class Customers {
    private int id;
    private String name;
    private String email;
    private Date birth;
    private List<Orders> orders;
}

CustomersMapper

public interface CustomersMapper {
    public Customers getCustomersById(Integer id);

}

测试

    @Test
    public void test() throws Exception {
        SqlSession session = getSqlSession();
        CustomersMapper mapper = session.getMapper(CustomersMapper.class);
        Customers customers = mapper.getCustomersById(3);
        //Customers(id=3, name=林志玲, email=linzl@gmail.com, birth=Tue Jun 12 00:00:00 CST 1984, orders=[Orders(orderId=4, orderName=GG, orderDate=Tue Jun 28 00:00:00 CST 1994, customers=null), Orders(orderId=5, orderName=DD, orderDate=Wed Oct 14 00:00:00 CST 2020, customers=null)])
        System.out.println(customers);
    }
方式二
嵌套 Select 查询,通常用来分步查询,同样的,懒加载规则也适用于它

CustomersMapper.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="CustomersMapper">
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into customers(name, email)
                                                                 values (#{name}, #{email})</insert>
    <resultMap id="MyMap2" type="Customers">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="email" property="email"></result>
        <result column="birth" property="birth"></result>
        <collection property="orders"  select="OrdersMapper.getOrdersByCid" column="id" >
        </collection>
    </resultMap>

    <select id="selectOne" resultMap="MyMap2">
        select *
        from customers
        where id = #{id}
    </select>
</mapper>

OrdersMapper.xml

    <select id="getOrdersByCid" resultType="Orders">
        select *
        from orders
        where c_id = #{cId}
    </select>

鉴别器 (discriminator)

这块照抄视频的例子,自己还没用过。
column 指定了 MyBatis 查询被比较值的地方, 而 javaType 用来确保使用正确的相等测试。
鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
		封装Employee:
			如果查出的是女生:就把部门信息查询出来,否则不查询;
			如果是男生,把last_name这一列的值赋值给email;
	 -->
	 <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpDis">
	 	<id column="id" property="id"/>
	 	<result column="last_name" property="lastName"/>
	 	<result column="email" property="email"/>
	 	<result column="gender" property="gender"/>
	 	<!--
	 		column:指定判定的列名
	 		javaType:列值对应的java类型  -->
	 	<discriminator javaType="string" column="gender">
	 		<!--女生  resultType:指定封装的结果类型;不能缺少。/resultMap-->
	 		<case value="0" resultType="com.atguigu.mybatis.bean.Employee">
	 			<association property="dept" 
			 		select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
			 		column="d_id">
		 		</association>
	 		</case>
	 		<!--男生 ;如果是男生,把last_name这一列的值赋值给email; -->
	 		<case value="1" resultType="com.atguigu.mybatis.bean.Employee">
		 		<id column="id" property="id"/>
			 	<result column="last_name" property="lastName"/>
			 	<result column="last_name" property="email"/>
			 	<result column="gender" property="gender"/>
	 		</case>
	 	</discriminator>
	 </resultMap>

SQL 代码片段

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。
还可以动态的传参进去,动态获取值,两张表关联查询时相同字段只需要一个,那就传入这个表的名称即可。
使用include标签引用sql片段,需要传参在里面写个property 标签就行。
 	<sql id="allColumns" >
        ${alias}.id,name,email,birth
    </sql>
 
    <select id="selectOne" resultMap="MyMap2">
        select    
   	 	<include refid="allColumns">
        	<property name="alias" value="customers"/>
    	</include>
        from customers
        where id = #{id}
    </select>

缓存

一级缓存
每个一级缓存都在SqlSession中,多个SqlSession是并排的,不共享,只存在于同一次会话中
        SqlSession session = getSqlSession();
        CustomersMapper mapper = session.getMapper(CustomersMapper.class);
        Customers c1 = mapper.selectOne(3);
        Customers c2 = mapper.selectOne(3);
        System.out.println(c1==c2);
[DEBUG] - org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137) - Opening JDBC Connection
[DEBUG] - org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:434) - Created connection 813823788.
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==>  Preparing: select customers.id,name,email,birth from customers where id = ?
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - ==> Parameters: 3(Integer)
[DEBUG] - org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137) - <==      Total: 1
true

可以看出只执行了一条语句,直接从缓存中拿取。
但是执行了 insert、update 和 delete 语句会刷新缓存,比如在上面两个查询语句价格插入语句,缓存就会被刷新(没掉),就得重新发送sql语句查询。

二级缓存
会话间(SqlSession)共享的缓存,一个关闭了,另一个也能查到关闭的那个的缓存 要启用全局的二级缓存,只需要在你的 mapper文件中添加一行:
<cache/>

每次增删改,一二级缓存都会失效。

mybatis是如何实现预编译的

最新了解的预编译,估计是假的 ,看了这篇文章

mysql预编译
得知预编译并没有真正执行,都是假的,框架层的预编译应该叫预准备

我们要明白mysql执行一个sql语句的过程。查了一些资料后,我得知,mysql执行脚本的大致过程如下:prepare(准备)-> optimize(优化)-> exec(物理执行),其中,prepare也就是我们所说的编译。开篇时已经说过,对于同一个sql模板,如果能将prepare的结果缓存,以后如果再执行相同模板而参数不同的sql,就可以节省掉prepare(准备)的环节,从而节省sql执行的成本。

PreparedStatement: PreparedStatement创建时就传过去一个sql语句,开始预编译的话,会返回语句ID,下次传语句ID和参数过去,就少了一次编译过程,这句话引用【JDBC系列】从源码角度理解JDBC和Mysql的预编译特性

mybatis:默认情况下,将对所有的 sql 进行预编译。mybatis底层使用PreparedStatement,过程是先将带有占位符(即”?”)的sql模板发送至mysql服务器,由服务器对此无参数的sql进行编译后,将编译结果缓存。

核心是通过#{} 实现的

在预编译之前,#{} 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符?。

//sqlMap 中如下的 sql 语句
select * from user where name = #{name};
//解析成为预编译语句
select * from user where name = ?;

如果${},SQL 解析阶段将会进行变量替换。不能实现预编译。

select * from user where name = '${name}'
//传递的参数为 "ruhua" 时,解析为如下,然后发送数据库服务器进行编译。
select * from user where name = "ruhua";

原理

不管是SqlSessionFactory,还是SqlSession,都包含了一个重要的类--->Configuration 配置类,它包含了我们mybatis的全局配置和mapper文件,把他加载成一个个类。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
四大对象:
Executor:它是一个执行器,使用StatementHandler来执行语句
StatementHandler:处理sql语句预编译,设置参数等相关工作
ParameterHandler:设置预编译参数用的
ResultHandler:处理结果集

BaseStatementHandler: 是 StatementHandler 接口的另一个实现类.本身是一个抽象类.用于简化StatementHandler 接口实现的难度,属于适配器设计模式体现,它主要有三个实现类

  • SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句
  • PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句,
  • CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程

//这个类是整个代理机制的核心类,对Sqlsession当中的操作进行了封装
public class MapperMethod {
  //一个内部封 封装了SQL标签的类型 insert update delete select
  private final SqlCommand command;
  //一个内部类 封装了方法的参数信息 返回类型信息等
  private final MethodSignature method;
}

DefaultSqlSession部分方法:selectOne selectOne selectMap selectMap selectMap
Executor部分方法:update query

//获取的是代理对象
CustomersMapper mapper = session.getMapper(CustomersMapper.class);
Customers c1 = mapper.selectOne(3);

上面例子查询流程总结
MapperProxy(mapper代理对象)调用CRUD方法(mapper.selectOne(3))实际是MapperMethod调用DefaultSqlSession的CRUD方法,接着DefaultSqlSession调用Executor增删改查,Executor在调用StatementHandler执行处理sql语句预编译,设置参数等(其中ParameterHandler来给sql设置参数),然后发送sql到数据库,返回结果使用ResultHandler处理。而ParameterHandler和ResultHandler都是用的TypeHandler进行处理。

在这里插入图片描述

Mapper代理类生成过程

以下面这个为例,看看CustomersMapper 代理类如何生成的。
SqlSession session = getSqlSession();
CustomersMapper mapper = session.getMapper(CustomersMapper.class);
Customers customers = mapper.selectOne(2);

我们需要知道以下两点:

  1. 首先需要知道的是MapperProxy<T>实现了InvocationHandler,我们知道实现了InvocationHandler接口的类就是要对被代理类CustomersMapper方法的增强。
  2. MapperProxy里面有些属性,是我们在重写invoke对原先方法增强时需要用到的,所以在我们实例化MapperProxy时,传一些参数给MapperProxy构造器,进行实例化。

开始分析过程

1 . 直接从session.getMapper(CustomersMapper.class)生成代理类方法开始,点进去发现跳到下面这个方法。

public class DefaultSqlSession implements SqlSession {
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
}

2 . 把 CustomersMapper 类和 DefaultSqlSession 传给 Configuration 的 getMapper

public class Configuration {
	public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    	return mapperRegistry.getMapper(type, sqlSession);
  	}
}

3 . 再传给 MapperRegistry 的 getMapper;
     MapperRegistry 在初始化时就会被创建,且调用addMapper对knownMappers 初始化,缓存所有mapper接口。

public class MapperRegistry {

 // knownMappers<Mapper接口类型, Mapper代理的工场对象>
 // 初始化时,会把这些东西缓存起来。为后续生成动态代理做准备。
 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
 
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 	//取到我们需要的代理的工厂对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
      //调用代理对象工厂生成实例
      return mapperProxyFactory.newInstance(sqlSession);
  }
  
  //把mapper接口都添加到knownMappers
 public <T> void addMapper(Class<T> type) {
 		......
        knownMappers.put(type, new MapperProxyFactory<>(type));
        ......
	}
}

4 . 上一步调用mapperProxyFactory.newInstance(sqlSession)把 sqlSession 传给 MapperProxyFactory 工厂类生成InvocationHandler的实现类MapperProxy实例。

public class MapperProxyFactory<T> {
  
  //需要生成代理类的接口
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //真正生产代理实例
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
  	//此时才生成InvocationHandler的实现类MapperProxy,传一些增强时需要的用到的属性
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

5 . 生成完 MapperProxy 实例后,再调用 newInstance(mapperProxy) 才算真正完成代理类的实例化。
通过工具类生成代理类文件,反编译查看,跟动态代理一模一样,它的所有方法都是调用MapperProxy类的invoke方法。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值