【02】MyBatis高级知识

(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

MyBatis高级知识

文章目录

0.mybatis基础知识复习

0.1mybatis是什么?

(1)mybatis是一人持久层框架,mybatis是一个不完全的ORM框架。sql语句需要程序员自己去编写,但是mybatis也有映射(输入参数映射、输出结果映射)。

(2)mybatis入门门槛不高,学习成本低,让程序员把精力放在sql语句上,对sql语句优化非常方便,适用与需求变化较多项目,比如互联网项目。

0.2mybatis框架执行过程

(1)配置mybatis的配置文件,SqlMapConfig.xml(名称不固定)
(2)通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂
SqlSessionFactory在实际使用时按单例方式。
(3)通过SqlSessionFactory创建SqlSession
SqlSession是一个面向用户接口(提供操作数据库方法),实现对象是线程不安全的,建议sqlSession应用场合在方法体内。
(4)调用sqlSession的方法去操作数据。
如果需要提交事务,需要执行SqlSession的commit()方法。
(5)释放资源,关闭SqlSession

0.3mybatis开发dao的方法

(1)原始dao 的方法
需要程序员编写dao接口和实现类
需要在dao实现类中注入一个SqlSessionFactory工厂。

(2)mapper代理开发方法(建议使用)
只需要程序员编写mapper接口(就是dao接口)
程序员在编写mapper.xml(映射文件)和mapper.java需要遵循一个开发规范:

  • mapper.xml中namespace就是mapper.java的类全路径。
  • mapper.xml中statement的id和mapper.java中方法名一致。
  • mapper.xml中statement的parameterType指定输入参数的类型和mapper.java的方法输入参数类型一致。
  • mapper.xml中statement的resultType指定输出结果的类型和mapper.java的方法返回值类型一致。

0.4SqlMapConfig.xml配置文件

可以配置properties属性、别名、mapper加载。。。

0.5输入映射:

  • parameterType:指定输入参数类型可以简单类型、pojo、hashmap。。
  • 对于综合查询,建议parameterType使用包装的pojo,有利于系统 扩展。

0.6输出映射:

resultType:
查询到的列名和resultType指定的pojo的属性名一致,才能映射成功。
reusltMap:

  • 可以通过resultMap 完成一些高级映射。
  • 如果查询到的列名和映射的pojo的属性名不一致时,通过resultMap设置列名和属性名之间的对应关系(映射关系)。可以完成映射。

0.7高级映射:

- 将关联查询的列映射到一个pojo属性中。(一对一)
- 将关联查询的列映射到一个List<pojo>中。(一对多)

0.8动态sql:(重点)

  • if判断(掌握)
  • where
  • foreach
  • sql片段(掌握)

0.9课程安排

(1)对订单商品数据模型进行分析。

(2)高级映射:(了解)

  • 实现一对一查询、一对多、多对多查询。
  • 延迟加载

(3)查询缓存

  • 一级缓存

  • 二级缓存(了解mybatis二级缓存使用场景)

(4)mybatis和spirng整合(掌握)

(5)逆向工程(会用)

1.订单商品数据模型

在这里插入图片描述

1.1数据模型分析思路

(1)每张表记录的数据内容

分模块对每张表记录的内容进行熟悉,相当 于你学习系统 需求(功能)的过程。

(2)每张表重要的字段设置

非空字段、外键字段

(3)数据库级别表与表之间的关系

外键关系

(4)表与表之间的业务关系

在分析表与表之间的业务关系时一定要建立在某个业务意义基础上去分析。

1.2数据模型分析

在这里插入图片描述

(1)用户表user:

记录了购买商品的用户信息

(2)订单表:orders

记录了用户所创建的订单(购买商品的订单)

(3)订单明细表:orderdetail:

记录了订单的详细信息即购买商品的信息

(4)商品表:items

记录了商品信息

1.2.1表与表之间的业务关系

(1)在分析表与表之间的业务关系时需要建立在某个业务意义基础上去分析。

(2)先分析数据级别之间有关系的表之间的业务关系:

1.2.1.1user和orders:

(1)user---->orders:一个用户可以创建多个订单,一对多

(2)orders—>user:一个订单只由一个用户创建,一对一

1.2.1.2orders和orderdetail:

(1)orders—> orderdetail:一个订单可以包括 多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系

(2)orderdetail–> orders:一个订单明细只能包括在一个订单中,一对一

1.2.1.3 orderdetail和items:

(1)orderdetail—> items:一个订单明细只对应一个商品信息,一对一

(2)items–> orderdetail:一个商品可以包括在多个订单明细 ,一对多

1.2.2再分析数据库级别没有关系的表之间是否有业务关系:

(1)orders和items:

orders和items之间可以通过orderdetail表建立关系。

2.一对一查询

2.1 需求

查询订单信息,关联查询创建订单的用户信息

2.2resultType

2.2.1sql语句

  • 确定查询的主表:订单表
  • 确定查询的关联表:用户表
  • 关联查询使用内链接?还是外链接?
  • 由于orders表中有一个外键(user_id),通过外键关联查询用户表只能查询出一条记录,可以使用内链接。
SELECT 
  orders.*,
  USER.username,
  USER.sex,
  USER.address 
FROM
  orders,
  USER 
WHERE orders.user_id = user.id

2.2.2创建pojo

  • 将上边sql查询的结果映射到pojo中,pojo中必须包括所有查询列名。

  • 原始的Orders.java不能映射全部字段,需要新创建的pojo。

  • 创建一个pojo继承包括查询字段较多的po类。

2.2.3mapper.xml

<!-- 1.查询订单关联查询用户 -->

<select id="findOrdersUser" resultType="com.gdc.virtualstall.po.OrdersCustom">
	select 
		orders.*,
		user.username,
		user.sex,
		user.address
	from 
		orders,user 
	where orders.user_id = user.id;
</select>

2.2.4mapper.java

import java.util.List;

import com.gdc.virtualstall.po.OrdersCustom;

public interface IOrdersMapper {
	
	/**
	 * 1.查询订单关联查询用户
	 * @return
	 * @throws Exception
	 */
	public List<OrdersCustom> findOrdersUser() throws Exception;
}

2.3.1sql语句

同resultType实现的sql一样

2.3.2使用resultMap映射的思路

使用resultMap将查询结果中的订单信息映射到Orders对象中,在orders类中添加User属性,将关联查询出来的用户信息映射到orders对象中的user属性中。

2.3.3需要Orders类中添加user属性

在这里插入图片描述

2.3.4mapper.xml

2.3.4.1定义resultMap
<!-- 
	3.订单查询关联用户的resultMap
	3.1将整个查询结果映射到 com.gdc.virtualstall.po.Orders中
-->
<resultMap type="com.gdc.virtualstall.po.Orders" id="OrdersUserResultMap">
	<!-- 
		3.2配置映射的订单信息
		3.2.1 id指定查询列中的唯一标识,订单信息中的唯一标识,如果有多个列组成唯一标识,配置多个id
		3.2.1.1column:订单信息的唯一标识列
		3.2.1.2property:订单信息的唯一标识列所映射到Orders哪个属性中。 
	-->
	<id column="id" property="id"/>
	<result column="user_id" property="userId"/>
	<result column="number" property="number"/>
	<result column="createtime" property="createtime"/>
	<result column="note" property="note"/>
	
	<!-- 
		3.3配置映射的关联的用户信息
		3.3.1 association:用于映射关联查询单个对象的信息
		3.3.1.1 property:要将关联查询的用户信息映射到Orders中哪个属性	
	-->
	
	<association property="user" javaType="com.gdc.virtualstall.po.User">
	<!-- 
		3.3.2 id:关联查询的用户的唯一标识 
		3.3.2.1 column:指定唯一标识用户信息的列
		3.3.2.2 property:映射到user的哪个属性
	-->
		<id column="user_id" property="id"/>
		<result column="username" property="username"/>
		<result column="sex" property="sex"/>
		<result column="address" property="address"/>
	</association>
	
</resultMap>
2.3.4.2statement定义
<!-- 2.查询订单关联查询用户,使用resultMap -->

<select id="findOrdersUserResultMap" resultMap="OrdersUserResultMap">
	select 
		orders.*,
		user.username,
		user.sex,
		user.address
	from 
		orders,user 
	where orders.user_id = user.id;
</select>

2.3.5mapper.java

	/**
	 * 2.查询订单关联查询用户,使用resultMap
	 * @return
	 * @throws Exception
	 */
	public List<Orders> findOrdersUserResultMap() throws Exception;

2.4resultType和resultMap实现一对一查询小结

实现一对一查询:

(1)resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。

(2)如果没有查询结果的特殊要求建议使用resultType。

(3)resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的属性中。

(4)resultMap可以实现延迟加载,resultType无法实现延迟加载。

3.一对多查询

3.1需求

查询订单及订单明细的信息。

3.2sql语句

  • 确定主查询表:订单表
  • 确定关联查询表:订单明细表
  • 在一对一查询基础上添加订单明细表关联即可。
SELECT 
  orders.*,
  user.username,
  user.sex,
  user.address,
  orderdetail.id orderdetail_id,
  orderdetail.items_id,
  orderdetail.items_num,
  orderdetail.orders_id
FROM
  orders,
  user,
  orderdetail
WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id;

3.3分析

使用resultType将上边的 查询结果映射到pojo中,订单信息的pojo就是重复。
在这里插入图片描述


要求:
(1)对orders映射不能出现重复记录。

(2)在orders.java类中添加List<orderDetail> orderDetails属性。
(3)最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。

在这里插入图片描述

3.4在orders中添加list订单明细属性

在这里插入图片描述

3.5mapper.xml

<select id="findOrdersAndOrderDetailResultMap" resultMap="OrdersAndOrderDetailResultMap">
	SELECT 
	  orders.*,
	  user.username,
	  user.sex,
	  user.address,
	  orderdetail.id orderdetail_id,
	  orderdetail.items_id,
	  orderdetail.items_num,
	  orderdetail.orders_id
	FROM
	  orders,
	  user,
	  orderdetail
	WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id
</select>

3.6resultMap定义

<!-- 
	5.查询订单及订单明细的OrdersAndOrderDetailResultMap
	5.1使用extends继承,不用在配置订单信息和用户信息的映射
-->
<resultMap type="com.gdc.virtualstall.po.Orders" id="OrdersAndOrderDetailResultMap" extends="OrdersUserResultMap">
	<!-- 5.1配置映射的订单信息-->
	<!-- 5.2用户信息 -->
	<!-- 
		5.3订单明细信息 
		5.3.1一个订单关联查询出了多条明细,要使用cololection进行映射。
		5.3.2collection:对关联查询到的多条记录映射到集合对象中。
		5.3.3:property:将关联查询到多条记录映射到com.gdc.virtualstall.po.Orders中哪个属性
		5.3.4:ofType:指定映射到集合list属性中pojo的类型
	-->
	<collection property="orderdetails" ofType="com.gdc.virtualstall.po.Orderdetail">
		<!-- 
			id:订单明细唯一标识 
			property:要将订单明细的唯一标识映射到com.gdc.virtualstall.po.Orderdetail的哪个属性
		-->
		<id column="orderdetail_id" property="id"/>
		<result column="items_id" property="itemsId"/>
	 	<result column="items_num" property="itemsNum"/>
	 	<result column="orders_id" property="ordersId"/>
	</collection>
</resultMap>

3.7mapper.java

	/**
	 * 3.查询订单(关联用户)及订单明细
	 * @return
	 * @throws Exception
	 */
	public List<Orders> findOrdersAndOrderDetailResultMap() throws Exception;

3.8 小结

(1)mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。

(2)使用resultType实现:
将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。

4.多对多查询

4.1需求

查询用户及用户购买商品信息。

4.2sql语句

查询主表是:用户表
关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:
orders、orderdetail、items

SELECT 
  orders.*,
  user.username,
  user.sex,
  user.address,
  orderdetail.id orderdetail_id,
  orderdetail.items_id,
  orderdetail.items_num,
  orderdetail.orders_id,
  items.name items_name,
  items.detail items_detail,
  items.price items_price
FROM
  orders,
  user,
  orderdetail,
  items
WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id

4.3映射思路

1)将用户信息映射到user中。
(2)在user类中添加订单列表属性List<Orders> orderslist,将用户创建的订单映射到orderslist
(3)在Orders中添加订单明细列表属性List<OrderDetail>orderdetials,将订单的明细映射到orderdetials
(4)在OrderDetail中添加Items属性,将订单明细所对应的商品映射到Items

4.4mapper.xml

<!-- 6.查询用户及购买商品的信息,使用resultMap -->

<select id="findUsersAndItemsResultMap" resultMap="UsersAndItemsResultMap">
	SELECT 
	  orders.*,
	  user.username,
	  user.sex,
	  user.address,
	  orderdetail.id orderdetail_id,
	  orderdetail.items_id,
	  orderdetail.items_num,
	  orderdetail.orders_id,
	  items.name items_name,
	  items.detail items_detail,
	  items.price items_price
	FROM
	  orders,
	  user,
	  orderdetail,
	  items
	WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id
</select>

4.5resultMap的定义

<!-- 
	7.查询用户及购买的商品
	7.1用户是一个主查询,
 -->
<resultMap type="com.gdc.virtualstall.po.User" id="UsersAndItemsResultMap">

	<!-- 7.1.2用户属性 -->
	<id column="user_id" property="id"/>
	<result column="username" property="username"/>
	<result column="sex" property="sex"/>
	<result column="address" property="address"/>
	
	<!-- 
		7.1.4订单信息
		一个用户对应多个订单,使用collection映射 
	 -->
	 <collection property="ordersList" ofType="com.gdc.virtualstall.po.Orders">
	 	<id column="id" property="id"/>
	 	<result column="user_id" property="userId"/>
		<result column="number" property="number"/>
		<result column="createtime" property="createtime"/>
		<result column="note" property="note"/>
		
		<!-- 
			 7.1.5订单明细
			 一个订单包括 多个明细
		 -->
	  	<collection property="orderdetails" ofType="com.gdc.virtualstall.po.Orderdetail">
	  			<id column="orderdetail_id" property="id"/>
			 	<result column="items_id" property="itemsId"/>
			 	<result column="items_num" property="itemsNum"/>
			 	<result column="orders_id" property="ordersId"/>
			 	
			<!-- 
			7.1.6商品信息
		  	一个订单明细对应一个商品
		  	 -->
	  	 	<association property="items" javaType="com.gdc.virtualstall.po.Items">
	  	 		<id column="items_id" property="id"/>
	  	 		<result column="items_name" property="name"/>
	  	 		<result column="items_detail" property="detail"/>
	  	 		<result column="items_price" property="price"/>
	  	 	</association>
			 	
	  	</collection>
	 </collection>
	
</resultMap>

4.6mapper.java

// s8.查询用户购买的商品信息
	public User findUsersAndItemsResultMap(int id) throws Exception;

4.7多对多查询总结

(1)将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)

(2)针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。

(3)一对多是多对多的特例,如下需求:

(4)查询用户购买的商品信息,用户和商品的关系是多对多关系。
需求1:

查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)

企业开发中常见明细列表,用户购买商品明细列表,

使用resultType将上边查询列映射到pojo输出。

(5)需求2:
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。

(6)总结:

使用resultMap是针对那些对查询结果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多个list。

5.resultMap总结

5.1resultType:

(1)作用:

将查询结果按照sql列名pojo属性名一致性映射到pojo中。

(2)场合:

常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。

5.2resultMap:

使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。

5.3association:

(1)作用:

将关联查询信息映射到一个pojo对象中。

(2)场合:

为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。

使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。

5.4collection:

(1)作用:

将关联查询信息映射到一个list集合中。

(2)场合:

为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。

如果使用resultType无法将查询结果映射到list集合中。

6.延迟加载

6.1什么是延迟加载

(1)resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。

(2)需求:

如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。

(3)延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高 数据库性能,因为查询单表要比关联查询多张表速度要快。

6.2 使用association实现延迟加载

6.2.1需求

查询订单并且关联查询用户信息

6.2.2mapper.xml

需要定义两个mapper的方法对应的statement。

(1)只查询订单信息

SELECT * FROM orders

在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)

<!-- 
	8.查询订单,用户信息需要延迟加载
	select * from orders
 -->
<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoading">
	select * from orders
</select>

(2)关联查询用户信息

通过上边查询到的订单信息中user_id去关联查询用户信息

使用UserMapper.xml中的findUserById

<select id="findUserById" parameterType="int" resultType="User">
	SELECT * FROM user WHERE id = #{value}
</select>

上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,中间的关联通过resultMap的定义将延迟加载执行配置起来。

6.2.3延迟加载resultMap

使用association中的select指定延迟加载去执行的statement的id

<!-- 9.延迟加载的resultMap -->
<resultMap type="com.gdc.virtualstall.po.Orders" id="OrdersUserLazyLoading">
	<!-- 9.1对订单信息进行映射配置-->
	<id column="id" property="id"/>
 	<result column="user_id" property="userId"/>
	<result column="number" property="number"/>
	<result column="createtime" property="createtime"/>
	<result column="note" property="note"/>
	
	<!-- 
		9.2实现对用户信息进行延迟加载
		9.3指定延迟加载需要执行的statement的id,(是根据user_id查询用户信息的statement) 
			SELECT orders.*,
				(SELECT username FROM user where orders.user_id = user.id) username,
				(SELECT sex FROM user where orders.user_id = user.id) sex
			FROM orders;
		9.4要使用IUserMapper.xml中findUserById完成根据用户id(user_id)查询用户信息,如果findUserById不在本mapper文件中,
			则需要加namespace
	-->
	<association property="user" javaType="com.gdc.virtualstall.po.User" select="com.gdc.virtualstall.mapper.IUserMapper.findUserById" column="user_id">
		<id column="user_id" property="id"/>
		<result column="username" property="username"/>
		<result column="sex" property="sex"/>
		<result column="address" property="address"/>
	</association>
</resultMap>

6.2.5测试

6.2.5.1测试思路:

1)执行上边mapper方法(findOrdersUserLazyLoading),内部去调用com.gdc.virtualstall.mapper.IUserMapper中的findOrdersUserLazyLoading只查询orders信息(单表)。

(2)在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载。

(3)延迟加载,去调用UserMapper.xml中findUserbyId这个方法获取用户信息。

6.2.5.2延迟加载配置

(1)mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。

(2)在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading

设置项描述允许值默认值
lazyLoadingEnabled全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。true与falsefalse
aggressiveLazyLoading当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。true和falsetrue
	<!-- s2.全局配置参数 需要时再设置-->
	<settings>
		<!-- s2.1 打开延迟加载的开关 -->
		<setting name="lazyLoadingEnabled" value="true"/>
		<!-- s2.2将积级加载改为消极加载,即按需加载 -->
		<setting name="aggressiveLazyLoading" value="false"/>
	</settings>

6.2.5.3测试代码

	/**
	 * 查询订单,用户信息延迟加载
	 * @throws Exception
	 */
	@Test
	public void testFindOrdersUserLazyLoading() throws Exception {
		// s1.通过会话工厂得到会话
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		// s2.创建IOrdersMapper对象,mybatis自动生成mapper代理对象
		IOrdersMapper ordersMapper = sqlSession.getMapper(IOrdersMapper.class);
		
		// s3.查询订单信息(单表)
		List<Orders> list = ordersMapper.findOrdersUserLazyLoading();
		
		//s4.遍历订单列表
		for(Orders order : list) {
			//s5.执行getUser()去查询用户信息,这里实现按需加载
			User user = order.getUser();
			System.out.println(user);
		}
		
		sqlSession.close();
	}

6.2.6延迟加载思考

(1)不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??

(2)实现方法如下:

(3)定义两个mapper方法:

  • 查询订单列表
  • 根据用户id查询用户信息

(4)实现思路:

  • 先去查询第一个mapper方法,获取订单信息列表
  • 在程序中(service),按需去调用第二个mapper方法去查询用户信息。

(5)总之:
使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。

7.查询缓存

7.1什么是查询缓存?

(1)mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。

(2)mybaits提供一级缓存,和二级缓存。

在这里插入图片描述

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

(4)二级缓存是mapper级别的缓存。
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

(5)为什么要用缓存?
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。

7.2一级缓存

7.2.1一级缓存工作原理

在这里插入图片描述

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

(2)如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

(3)第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

7.2.2一级缓存测试

(1)mybatis默认支持一级缓存,不需要在配置文件去配置。

(2)按照上边一级缓存原理步骤去测试。

	/**
	 * 一级缓存测试
	 * @throws Exception 
	 */
	@Test
	public void testCache1() throws Exception{
		// s1.通过会话工厂得到会话
		SqlSession sqlSession = sqlSessionFactory.openSession();

		// s2.创建IUserMapper对象,mybatis自动生成mapper代理对象
		IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
		
		/*
		 * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第一次发请求,查询id为1的用户
		 */
		User user1 = userMapper.findUserById(1);
		System.out.println(user1);
		
		/*
		 * s3.1如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,
		 * 这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
		 * 
		 * s3.2更新user1的信息,去清空缓存
		 */
		user1.setUsername("测试用户22");
		userMapper.updateUser(user1);
		
		// s3.3执行commit操作去清空缓存
		sqlSession.commit();
		
		
		//s4.第二次发请求,查询id为1的用户
		User user2 = userMapper.findUserById(1);
		System.out.println(user2);
		
		sqlSession.close();
	}

7.2.3一级缓存应用

(1)正式开发,是将mybatis和spring进行整合开发,事务控制在service中。

(2)一个service方法中包括 很多mapper方法调用。

service{

	//开始执行时,开启事务,创建SqlSession对象
	//第一次调用mapper的方法findUserById(1)
	
	//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
	//方法结束,sqlSession关闭
}

(3)如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为sqlSession方法结束,sqlSession就关闭,一级缓存就清空。

7.3二级缓存

7.3.1原理

在这里插入图片描述

(1)前提:首先开启mybatis的二级缓存。

(2)sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。

(3)如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。

(4)sqlSession2去查询用户id为1的用户信息, 去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

(5)二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。

(6)UserMapper有一个二级缓存区域(缓存区域按namespace分) ,其它mapper也有自己的二级缓存区域(缓存区域按namespace分)。

(7)每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。

7.3.2开启二级缓存

(1)mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。

(2)在核心配置文件SqlMapConfig.xml中加入

<setting name="cacheEnabled" value="true"/>
属性名称描述允许值默认值
cacheEnabled对在此配置文件下的所有cache 进行全局性开/关设置。true falsetrue

(3)在UserMapper.xml中开启二缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)。

<mapper namespace="com.gdc.virtualstall.mapper.IUserMapper">

<!-- s0:开启本mapper的namespace下的二级缓存 -->
<cache/>

7.3.3调用pojo类实现序列化接口

public class User implements Serializable{
	// 注意:属性名要与数据库的字段对应
	private int id;
	private String username;// 用户姓名
	private String sex;// 性别
	private Date birthday;// 生日
	private String address;// 地址
	private List<Orders> ordersList;

为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。

7.3.4测试方法

(1)二级缓存测试代码1

	/**
	 * 二级缓存测试
	 * @throws Exception 
	 */
	@Test
	public void testCache2() throws Exception{
		// s1.通过会话工厂得到会话
		SqlSession sqlSession1 = sqlSessionFactory.openSession();
		SqlSession sqlSession2 = sqlSessionFactory.openSession();
		SqlSession sqlSession3 = sqlSessionFactory.openSession();
		
		// s2.创建IUserMapper对象,mybatis自动生成mapper代理对象
		IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class);
		/*
		 * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第一次发请求,查询id为1的用户
		 */
		User user1 = userMapper1.findUserById(1);
		System.out.println(user1);
		
		//s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域
		sqlSession1.close();
		
		//s3.2使用sqlSession3执行commit()操作
		
		IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class);
		/*
		 * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第二次发请求,查询id为1的用户
		 */
		User user2 = userMapper2.findUserById(1);
		System.out.println(user2);
		sqlSession2.close();
		
	}

(2)二级缓存测试代码2(存在清空二级缓存的操作)

/**
	 * 二级缓存测试
	 * @throws Exception 
	 */
	@Test
	public void testCache2() throws Exception{
		// s1.通过会话工厂得到会话
		SqlSession sqlSession1 = sqlSessionFactory.openSession();
		SqlSession sqlSession2 = sqlSessionFactory.openSession();
		SqlSession sqlSession3 = sqlSessionFactory.openSession();
		
		// s2.创建IUserMapper对象,mybatis自动生成mapper代理对象
		IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class);
		/*
		 * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第一次发请求,查询id为1的用户
		 */
		User user1 = userMapper1.findUserById(1);
		System.out.println(user1);
		
		//s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域
		sqlSession1.close();
		
		//s3.2使用sqlSession3执行commit()操作
		IUserMapper userMapper3 = sqlSession3.getMapper(IUserMapper.class);
		User user3 = userMapper3.findUserById(1);
		user3.setUsername("李丽华");
		userMapper3.updateUser(user3);
		//s3.3执行提交,清空IUserMapper下的二级缓存
		sqlSession3.commit();
		sqlSession3.close();
		
		IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class);
		/*
		 * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第二次发请求,查询id为1的用户
		 */
		User user2 = userMapper2.findUserById(1);
		System.out.println(user2);
		sqlSession2.close();
		
	}

7.3.5useCache配置

(1)在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。

<select id="findUserById" parameterType="int" resultType="User" useCache="false">
	SELECT * FROM user WHERE id = #{value}
</select>

(2)总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。

(3)测试代码

	/**
	 * 二级缓存测试
	 * @throws Exception 
	 */
	@Test
	public void testCache2() throws Exception{
		// s1.通过会话工厂得到会话
		SqlSession sqlSession1 = sqlSessionFactory.openSession();
		SqlSession sqlSession2 = sqlSessionFactory.openSession();
		SqlSession sqlSession3 = sqlSessionFactory.openSession();
		
		// s2.创建IUserMapper对象,mybatis自动生成mapper代理对象
		IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class);
		/*
		 * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第一次发请求,查询id为1的用户
		 */
		User user1 = userMapper1.findUserById(1);
		System.out.println(user1);
		
		//s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域
		sqlSession1.close();
		
		//s3.2使用sqlSession3执行commit()操作
		/*IUserMapper userMapper3 = sqlSession3.getMapper(IUserMapper.class);
		User user3 = userMapper3.findUserById(1);
		user3.setUsername("李丽华");
		userMapper3.updateUser(user3);
		//s3.3执行提交,清空IUserMapper下的二级缓存
		sqlSession3.commit();
		sqlSession3.close();*/
		
		IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class);
		/*
		 * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第二次发请求,查询id为1的用户
		 */
		User user2 = userMapper2.findUserById(1);
		System.out.println(user2);
		sqlSession2.close();
		
	}

7.3.6刷新缓存(就是清空缓存)

(1)在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

(2)设置statement配置中的flushCache=“true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">

(3)总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。

7.3.7Mybatis Cache参数

(1)flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

(2)size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。

(3)readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。

如下例子:

<cache  eviction="FIFO"  flushInterval="60000"  size="512"  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:

  • LRU – 最近最少使用的:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

7.4mybatis整合ehcache

(1)ehcache是一个分布式缓存框架。

(2)为什么要使用ehcache缓存,而不使用mybatis自己的缓存?
原因见7.4.1

7.4.1分布式缓存

(1)我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)

在这里插入图片描述

(2)不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理。

(3)mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。

7.4.2整合方法(掌握)

(1)mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。

(2)mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类。

(3)mybatis jar包位置

在这里插入图片描述

public interface Cache {

  /**
   * @return The identifier of this cache
   */
  String getId();

  /**
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  void putObject(Object key, Object value);

  /**
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**
   * Optional. It is not called by the core.
   * 
   * @param key The key
   * @return The object that was removed
   */
  Object removeObject(Object key);

  /**
   * Clears this cache instance
   */  
  void clear();

(4)mybatis默认实现cache类是:

可以ctrl + t Cache接口看实现类,查看mybatis实现了哪些缓存接口,即支持哪些缓存接口。

public class PerpetualCache implements Cache {

  private String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  public String getId() {
    return id;
  }

  public int getSize() {
    return cache.size();
  }

  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  public Object getObject(Object key) {
    return cache.get(key);
  }

7.4.3加入ehcache包

在这里插入图片描述

7.4.4整合ehcache

(1)配置mapper中cache中的type为ehcache对cache接口的实现类型。

<!-- 
s0:开启本mapper的namespace下的二级缓存 
s0.1指定Cache(缓存)接口的实现类的类型,mybtis默认使用org.apache.ibatis.cache.impl.PerpetualCache
s0.2要和ehcache整合,需要配置type为ehcache实现Cache接口的类型
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

7.4.5加入ehcache的配置文件

(1)在classpath下配置ehcache.xml

(2)内容如下:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
	<diskStore path="F:\develop\ehcache" />
	<defaultCache 
		maxElementsInMemory="1000" 
		maxElementsOnDisk="10000000"
		eternal="false" 
		overflowToDisk="false" 
		timeToIdleSeconds="120"
		timeToLiveSeconds="120" 
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>

(3)属性说明:

  • diskStore:指定数据在磁盘中的存储位置。
  • defaultCache:当借助CacheManager.add(“demoCache”)创建Cache时,EhCache便会采用
<defalutCache/>指定的的管理策略

以下属性是必须的:

  • maxElementsInMemory - 在内存中缓存的element的最大数目
  • maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
  • eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
  • overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
    以下属性是可选的:
  • timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
  • timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
  • diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
  • diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
  • diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
  • memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

(4)测试

(1)直接执行测试程序即可,查看输出日志缓存命中率是否有变化
Cache Hit Ratio [com.gdc.virtualstall.mapper.IUserMapper]: 0.5

(2)测试代码

/**
	 * 二级缓存测试
	 * @throws Exception 
	 */
	@Test
	public void testCache2() throws Exception{
		// s1.通过会话工厂得到会话
		SqlSession sqlSession1 = sqlSessionFactory.openSession();
		SqlSession sqlSession2 = sqlSessionFactory.openSession();
		SqlSession sqlSession3 = sqlSessionFactory.openSession();
		
		// s2.创建IUserMapper对象,mybatis自动生成mapper代理对象
		IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class);
		/*
		 * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第一次发请求,查询id为1的用户
		 */
		User user1 = userMapper1.findUserById(1);
		System.out.println(user1);
		
		//s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域
		sqlSession1.close();
		
		//s3.2使用sqlSession3执行commit()操作
		/*IUserMapper userMapper3 = sqlSession3.getMapper(IUserMapper.class);
		User user3 = userMapper3.findUserById(1);
		user3.setUsername("李丽华");
		userMapper3.updateUser(user3);
		//s3.3执行提交,清空IUserMapper下的二级缓存
		sqlSession3.commit();
		sqlSession3.close();*/
		
		IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class);
		/*
		 * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession)
		 * 第二次发请求,查询id为1的用户
		 */
		User user2 = userMapper2.findUserById(1);
		System.out.println(user2);
		sqlSession2.close();
		
	}

7.5二级应用场景

(1)对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。

(2)实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。

7.6二级缓存局限性

(1)mybatis二级缓存对细粒度的数据级别的缓存实现不好

(2)比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。

8spring和mybatis整合

8.1 整合思路

(1)需要spring通过单例方式管理SqlSessionFactory。

(2)spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(spring和mybatis整合自动完成)

(3)持久层的mapper都需要由spring进行管理。

8.2 整合环境

(1)创建一个新的java工程(接近实际开发的工程结构)

(2)jar包:

  • mybatis3.2.7的jar包
  • spring3.2.0的jar包
  • mybatis和spring的整合包:早期ibatis和spring整合是由spring官方提供,mybatis和spring整合由mybatis提供。

在这里插入图片描述

全部jar包

见资料压缩包:mybatis与spring整合全部jar包(包括springmvc).zip文件

在这里插入图片描述

8.3sqlSessionFactory

(1)在applicationContext.xml配置sqlSessionFactory和数据源

(2)sqlSessionFactory在mybatis和spring的整合包下。

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-4.2.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-4.2.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
		
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
       <property name="driverClassName" value="${jdbc.driver}"/>
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
		<property name="maxActive" value="10"/>
		<property name="maxIdle" value="5"/>
</bean>	

<!-- mapper配置 -->
	<!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 数据库连接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 加载mybatis的全局配置文件 -->
		<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
	</bean>

</beans>

8.4 原始dao开发(和spring整合后)

8.4.1User.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">
<!-- 
1.namespace命名空间,作用就是对sql进行分类化的管理,理解为sql的一个隔离 
2.注意:使用mapper代理的方法开发,这个namespace就有特殊的重要作用。
-->
<mapper namespace="test">

<!-- 
1.需求:根据用户id(主键)查询用户信息

2.在映射文件中需要配置很多的sql语句

2.1通过select来进行查询

2.2id是用于标识映射文件中的sql,这个id可以称之为sql的id,但有一个比较专业的说法是statement的id.

2.3将来sql语句会封装到mappedStatement对象中,所以将id称为statement的id。

2.4#{}表示一个占位符号

2.5parameterType:用于指定参数的类型,这里指定int型,是因为数据库里的id字段的数据类型为int型

2.6#{id}其中的id表示接收输入的参数,参数的名称就是id,如果输入类型是简单类型,#{}中的参数名称可以任意。
可以是value或者是其他的名称

2.7resultType:用于指定sql输出结果所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象。

-->

<select id="findUserById" parameterType="int" resultType="com.gdc.ssm.po.User">
	SELECT * FROM user WHERE id = #{value}
</select>

<!-- 
2.根据用户名称模糊查询用户信息
2.1.可能返回多条
2.2.resultType:指定的就是单条记录所映射的java对象类型
2.3.${}:表示拼接字符串,将接收到的参数的内容不加任何修饰拼接在sql中
2.4.使用${}来拼字sql,可能会引起sql注入
2.5.${value}:表示接收输入参数的内容,如果传入类型是简单类型,${}中只能使用value,
 -->
<select id="findUserByName" parameterType="java.lang.String" resultType="com.gdc.ssm.po.User">
SELECT * FROM user WHERE username LIKE '%${value}%'
</select>


<!-- 
3.添加用户
3.1parameterType:指定输入参数类型是pojo(包括用户信息)
3.2#{}中指定pojo的属性名称,接收到pojo对象的属性值,mybatis通过OGNL获取对象的属性值.
 -->
 
 <insert id="insertUser" parameterType="com.gdc.virtualstall.po.User">
 	insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})
 </insert>


<!-- 
4.删除用户
4.1根据id删除用户,需要输入id值
-->

<delete id="deleteUser" parameterType="java.lang.Integer">
	delete from user where id=#{id}
</delete>

<!-- 
5.更新用户 
5.1根据id更新用户,需要传入用户id,用户的更新信息
5.2parameterType来指定User对象,包括id和更新信息,注意id是必须存在
5.3#{id}:从输入的User对象中获取id的属性值
-->
<update id="updateUser" parameterType="com.gdc.ssm.po.User">
	update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} 
</update>


</mapper>

(1)在SqlMapconfig.xml中去加载User.xml

	<!-- s5.加载映射文件 -->
	<mappers>
		<mapper resource="sqlmap/User.xml"/>

(2)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>
	
	<!-- s1.statement语句别名定义 -->
	<typeAliases>
		<!-- 
		s1.批量别名的定义
		指定包名,mybatis会自动的扫描包中的po类,自动定义别名,别名是什么呢?别名就是类名,(首字母大小或小写都可以)
		 -->
		 <package name="com.gdc.ssm.po"/>
		
	</typeAliases>
	
	<!-- s2.加载映射文件 -->
	<mappers>
		<mapper resource="sqlmap/User.xml"/>
		
		<!-- 
			s1.通过mapper接口来加载映射文件
			s1.1需要遵循一些规范:
			(1)需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录。
			(2)前提是使用的是mapper代理的方法
		 -->
		<!-- <mapper class="com.gdc.virtualstall.mapper.IUserMapper"/> -->
		
		<!-- 
			s2.批量加载mapper映射文件
			s2.1指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载
			s2.2需要遵循一些规范:
			(1)需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录。
			(2)前提是使用的是mapper代理的方法
		 -->
		<package name="com.gdc.ssm.mapper"/>
		
	</mappers>
	
</configuration>

8.4.2dao(实现类继承SqlSessionDaoSupport)

(1)dao接口实现类需要注入SqlSessoinFactory,通过spring进行注入。

(2)这里spring声明配置方式,配置dao的bean:

(3)让UserDaoImpl实现类继承SqlSessionDaoSupport

package com.gdc.ssm.dao.daoimpl;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import com.gdc.ssm.dao.IUserDao;
import com.gdc.ssm.po.User;

/**
 * s1.需要在实现类中注入SqlSessionFactory s2 .此处通过构造参数注入
 */
public class UserDaoImpl extends SqlSessionDaoSupport implements IUserDao {

	@Override
	public User findUserById(int id) throws Exception {
		//s0.继承SqlSessionDaoSupport,通过this.getSqlSession()得到SqlSession
		// s1.为了保证线程安全,将SqlSession的创建放在方法中
		SqlSession sqlSession = this.getSqlSession();
		User user = sqlSession.selectOne("test.findUserById", id);
		return user;
	}

	@Override
	public List<User> findUserByName(String name) throws Exception {
		// s1.为了保证线程安全,将SqlSession的创建放在方法中
		SqlSession sqlSession = this.getSqlSession();
		List<User> userList = sqlSession.selectList("test.findUserByName", name);
		return userList;
	}

	@Override
	public void insertUser(User user) throws Exception {
		// s1.为了保证线程安全,将SqlSession的创建放在方法中
		SqlSession sqlSession = this.getSqlSession();
		// s2.插入用户对象
		int ret = sqlSession.insert("test.insertUser", user);
		System.out.println(ret);
		// s3.提交事务
		sqlSession.commit();
	}

	@Override
	public void deleteUser(int id) throws Exception {
		// s1.为了保证线程安全,将SqlSession的创建放在方法中
		SqlSession sqlSession = this.getSqlSession();
		// s2.删除用户对象
		int ret = sqlSession.delete("test.deleteUser", 27);
		System.out.println(ret);
		// s3.提交事务
		sqlSession.commit();
	}

}

8.4.3配置dao

(1)在applicationContext.xml中配置dao。

	<!-- s4.原始dao接口 -->
	<bean id="userDao" class="com.gdc.ssm.dao.daoimpl.UserDaoImpl">
		<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
	</bean>

8.4.4测试程序

package com.gdc.ssm.dao.daoimpl;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.gdc.ssm.dao.IUserDao;
import com.gdc.ssm.po.User;

public class UserDaoImplTest extends UserDaoImpl {
	
	private ApplicationContext applicationContext;

	/**
	 * 在setUp这个方法得到spring的容器
	 * @throws Exception
	 */
	@Before
	public void setUp() throws Exception {
		applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml"); 
	}

	@Test
	public void testFindUserById() throws Exception {
		IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");
		//s1.调用userDao的方法
		User user = userDao.findUserById(1);
		
		System.out.println(user);
	}

}

8.5mapper代理开发

8.5.1mapper.xml和mapper.java

在这里插入图片描述

8.5.2通过MapperFactoryBean创建代理对象

	<!-- 
		s5.spring管理mapper的配置(mapper方式开发的配置) 
		s5.1MapperFactoryBean:根据mapper接口生成代理对象
	-->
	<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
		<!-- mapperInterface指定mapper接口 -->
		<property name="mapperInterface" value="com.gdc.ssm.mapper.IUserMapper"/>
		<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
	</bean>

此方法问题:
需要针对每个mapper进行配置,麻烦。

8.5.3通过MapperScannerConfigurer进行mapper扫描(建议使用)

	<!-- 
	s6.mapper的批量扫描
	s6.1从mapper包中扫描出mapper接口,自动创建代理对象并且在spring容器中注册
	s6.2需要遵循一些规范:
	(1)需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录。
	s6.3自动扫描出来的mapper的bean的id为mapper类名(首字母小写)
	-->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 
		s6.3.1指定扫描的包名
		s6.3.1.1如果扫描多个包,每个包中间使用半角逗号分隔
		-->
		<property name="basePackage" value="com.gdc.ssm.mapper"/>
		<!-- 使用sqlSessionFactoryBeanName是为了保证先创建数据源 -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
	</bean>

8.5.4测试代码

public class IUserMapperTest {

	private ApplicationContext applicationContext;

	@Before
	public void setUp() throws Exception {
		applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
	}

	@Test
	public void testFindUserById() throws Exception {
		IUserMapper userMapper = (IUserMapper) applicationContext.getBean("userMapper");
		User user = userMapper.findUserById(1);

		System.out.println(user);
	}

}

9逆向工程

9.1什么是逆向工程

(1)mybaits需要程序员自己编写sql语句,mybatis官方提供逆向工程 可以针对单表自动生成mybatis执行所需要的代码(mapper.java,mapper.xml、po…)

(2)企业实际开发中,常用的逆向工程方式:

由数据库的表生成java代码。

9.2下载逆向工程

在这里插入图片描述

9.3使用方法(会用)

9.3.1运行逆向工程

在这里插入图片描述

建议使用java程序方式,不依赖开发工具。

9.3.2生成代码配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
	<context id="testTables" targetRuntime="MyBatis3">
		<commentGenerator>
			<!-- 是否去除自动生成的注释 true:是 : false:否 -->
			<property name="suppressAllComments" value="true" />
		</commentGenerator>
		<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
		<jdbcConnection driverClass="com.mysql.jdbc.Driver"
			connectionURL="jdbc:mysql://47.104.10.229:3306/mybatis" userId="xiongjie"
			password="x_iongjie_sz_88">
		</jdbcConnection>
		<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
			connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" 
			userId="yycg"
			password="yycg">
		</jdbcConnection> -->

		<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和 
			NUMERIC 类型解析为java.math.BigDecimal -->
		<javaTypeResolver>
			<property name="forceBigDecimals" value="false" />
		</javaTypeResolver>

		<!-- targetProject:生成PO类的位置 -->
		<javaModelGenerator targetPackage="com.gdc.ssm.po1"
			targetProject=".\src">
			<!-- enableSubPackages:是否让schema作为包的后缀 -->
			<property name="enableSubPackages" value="false" />
			<!-- 从数据库返回的值被清理前后的空格 -->
			<property name="trimStrings" value="true" />
		</javaModelGenerator>
        <!-- targetProject:mapper映射文件生成的位置 -->
		<sqlMapGenerator targetPackage="com.gdc.ssm.mapper1" 
			targetProject=".\src">
			<!-- enableSubPackages:是否让schema作为包的后缀 -->
			<property name="enableSubPackages" value="false" />
		</sqlMapGenerator>
		<!-- targetPackage:mapper接口生成的位置 -->
		<javaClientGenerator type="XMLMAPPER"
			targetPackage="com.gdc.ssm.mapper1" 
			targetProject=".\src">
			<!-- enableSubPackages:是否让schema作为包的后缀 -->
			<property name="enableSubPackages" value="false" />
		</javaClientGenerator>
		<!-- 指定数据库表 -->
		<table tableName="items"></table>
		<table tableName="orders"></table>
		<table tableName="orderdetail"></table>
		<table tableName="user"></table>
	</context>
</generatorConfiguration>

9.3.3执行生成程序

在这里插入图片描述

9.3.4使用生成的代码

(1)需要将生成工程中所生成的代码拷贝到自己的工程中。

(2)测试ItemsMapper中的方法

package com.gdc.ssm.mapper;


import java.util.Date;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.gdc.ssm.po.Items;
import com.gdc.ssm.po.ItemsExample;

public class ItemsMapperTest {

	private ApplicationContext applicationContext;
	private ItemsMapper itemsMapper;

	@Before
	public void setUp() throws Exception {
		applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
		itemsMapper = (ItemsMapper) applicationContext.getBean("itemsMapper");
	}
	
	@Test
	public void testDeleteByPrimaryKey() {
		
	}

	@Test
	public void testInsert() {
		//s1.构造items对象
		Items items = new Items();
		items.setName("手机");
		items.setPrice(2300F);
		items.setCreatetime(new Date());
		int ret = itemsMapper.insert(items);
		System.out.println(ret);
	}

	/**
	 * 自定义条件查询
	 */
	@Test
	public void testSelectByExample() {
		ItemsExample itemsExample = new ItemsExample();
		//通过criteria构造查询条件
		ItemsExample.Criteria criteria = itemsExample.createCriteria();
		criteria.andNameEqualTo("手机");
		//可能返回多条记录
		List<Items> list = itemsMapper.selectByExample(itemsExample);
		
		System.out.println(list);
	}

	/**
	 * 根据主键查询
	 */
	@Test
	public void testSelectByPrimaryKey() {
		Items items = itemsMapper.selectByPrimaryKey(1);
		System.out.println(items);
	}

	@Test
	public void testUpdateByPrimaryKey() {
		//s1.对所有字段进行更新,需要先查询出来再更新
		Items items = itemsMapper.selectByPrimaryKey(1);
		items.setName("水杯");
		int ret = itemsMapper.updateByPrimaryKey(items);
		System.out.println(ret);
		
		
		//s2.如果传入的字段不为空才更新,在批量更新中使用此方法,不需要先查询再更新
		//itemsMapper.updateByPrimaryKeySelective(record);
	}

}

©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页