mybatis详细笔记_从入门到与spring整合

mybatis是一个持久层的框架,让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写)满足需要sql语句。
mybatis可以将向preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象(输出映射)。
执行流程:
在这里插入图片描述
使用的项目为maven项目,导入的依赖:
在这里插入图片描述

一、入门操作

数据库有以下这个简单的表:
在这里插入图片描述
id为自动递增。

对应java的实体类:
在这里插入图片描述
编写mybatis全局配置文件SqlMapConfig.xml
在这里插入图片描述
这个文件暂时只配置了数据源(通过加载属性文件db.properties获取driver等内容)以及加载映射文件(User.xml放在了sqlmap包下,本文接下来会有配置内容)。
db.properties文件内容如下:
在这里插入图片描述
记录了连接数据库的信息,在SqlMapConfig.xml里通过${jdbc.driver}获取到值,这样做的好处就是方便管理,因为这个文件里只有数据库连接信息,数据库发生变化时可直接改这个db.properties即可。
编写映射文件User.xml
在这里插入图片描述
注意:当传入参数的类型即parameterType为自己定义的对象时,比如我这里的插入语句就是传入一个User对象,那么下面sql语句中#{}里面的内容一定要与User对象中的属性名相同,因为它执行过程中是通过映射去自动获取传入的属性去替换掉#{}
由于是比较好理解的,各项配置注释已经解释得比较清楚了,就不多加解释。
编写代码测试:

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import com.sj.bean.User;

public class MybatisFirst {
	// 根据id查询用户信息,得到一条记录结果
	@Test
	public void findUserById() throws IOException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件流
		InputStream resourceAsStream = Resources.getResourceAsStream(resource);
		// 创建会话工厂
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		// 通过工厂得到SqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession操作数据库
		// 第一个参数,映射文件中statement的id,等于namespace+"."+statement的id
		// 第二个参数,指定和映射文件中所匹配的paramenterType类型的参数
		// sqlSession.selectOne()结果是与映射文件中所匹配的resultType类型的对象
		User user = sqlSession.selectOne("test.findUserById", 1);

		System.out.println(user);

		// 释放资源
		sqlSession.close();
	}
}

大致流程就是:告诉mybatis你的配置文件SqlMapConfig.xml,然后它通过配置文件中的数据源信息连接数据库,又通过mappers标签找到映射文件,告诉它namespace+"."+statement的id(如“test.findUserById”)找到要执行的sql,然后操作数据库得到事先配置好的返回对象(即User.xml中select标签内的resultType属性)。
代码的解释尽在注释中。
运行结果:
在这里插入图片描述
通过日志信息及输出可知通过id为1查找出了张三三这个人的信息。
另外几种测试:

// 根据用户名称来模糊查询用户列表
	@Test
	public void findUserByName() throws IOException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件流
		InputStream resourceAsStream = Resources.getResourceAsStream(resource);
		// 创建会话工厂
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		// 通过工厂得到SqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();

		List<User> list = sqlSession.selectList("test.findUserByName", "张");

		System.out.println(list);

		sqlSession.close();
	}

	// 添加用户
	@Test
	public void insertUser() throws IOException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件流
		InputStream resourceAsStream = Resources.getResourceAsStream(resource);
		// 创建会话工厂
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		// 通过工厂得到sqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();

		User user = new User();
		user.setUsername("啦啦");
		user.setBirthday(new Date());
		user.setSex("男");
		user.setAddress("abc");

		sqlSession.insert("test.insertUser", user);

		System.out.println(user.getId() + "===============");

		sqlSession.commit();
		sqlSession.close();
	}

	// 删除用户
	@Test
	public void deleteUserById() throws IOException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件流
		InputStream resourceAsStream = Resources.getResourceAsStream(resource);
		// 创建会话工厂
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		// 通过工厂得到sqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();

		sqlSession.delete("test.deleteUserById", 4);
		sqlSession.commit();

		sqlSession.close();
	}

	// 更新用户
	@Test
	public void updateUserById() throws IOException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件流
		InputStream resourceAsStream = Resources.getResourceAsStream(resource);
		// 创建会话工厂
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		// 通过工厂得到sqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();

		User user = new User();
		user.setId(1);
		user.setUsername("张三三");

		sqlSession.delete("test.updateUserById", user);
		sqlSession.commit();

		sqlSession.close();
	}

自动返回新插入数据的id:
注意查看上面插入数据的测试,id列是自增的,所以插入的时候并没有指定userid,但是我在插入一个新的user数据后,能得到userid,因为在user.xml中配置了selectKey,解释如下:
在这里插入图片描述

二、原始dao开发方法

程序员写dao接口和dao实现类。
需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession。
dao层编写接口类UserDao.java:

public interface UserDao {
	public User findUserById(int id);
	
	public void insertUser(User user);
	
	public void deleteUserById(int id);
}

再编写接口实现类:

public class UserDaoImpl implements UserDao {

	private SqlSessionFactory sqlSessionFactory;
	
	public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
		super();
		this.sqlSessionFactory = sqlSessionFactory;
	}

	public User findUserById(int id) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		User user = sqlSession.selectOne("test.findUserById", id);
		sqlSession.close();
		return user;
	}

	public void insertUser(User user) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		sqlSession.insert("test.insertUser", user);
		sqlSession.commit();
		sqlSession.close();
	}

	public void deleteUserById(int id) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		sqlSession.delete("test.deleteUserById", id);
		sqlSession.commit();
		sqlSession.close();
	}
	
}

测试:

public class UserDaoImplTest {
	private static SqlSessionFactory sqlSessionFactory;
	static {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件流
		InputStream resourceAsStream = null;
		try {
			resourceAsStream = Resources.getResourceAsStream(resource);
		} catch (IOException e) {
			e.printStackTrace();
		}
		// 创建会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	}
	@Test
	public void findUserById() {
		UserDao userDao = new UserDaoImpl(sqlSessionFactory);
		User user = userDao.findUserById(1);
		System.out.println(user);
	}
}

在上面入门的基础上,这一步就十分好理解了,无非就是改写成dao开发方法而已。
原始dao开发问题总结:
1、dao的接口实现类方法中存在大量的模板方法,比如openSession()、sqlSession.commit()、sqlSession.close(),设想能否将这些代码提取出来,减少程序员的工作量;
2、调用sqlsession方法时,将statement的id硬编码了,如 “test.findUserById”;
3、调用sqlsession方法传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不会报错,不利于程序员开发。

三、mapper代理方法

程序员只需要写一个mapper接口,相当于dao接口。
1、为了区别开传统的方法,重新编写一个映射文件UserMapper.xml:
在这里插入图片描述
注意mapper标签的namespace属性的更改,使用mapper代理方法开发,namespace有特殊重要的作用,这里的namespace的值即为将要编写的UserMapper.java所在位置,其余内容与User.xml相同。
同时在SqlMapConfig.xml中把UserMapper.xml添加映射:(我的UserMapper.xml文件放在了mapper包下)
在这里插入图片描述
2、编写UserMapper类(类似于dao接口):
mybatis可以自动生成mapper接口实现类的代理对象,但是有个前提,程序员编写mapper接口需要遵循一些规范:
1、在mapper.xml中namespace等于mapper接口地址;
2、mapper.java接口中的方法名和mapper.xml中statement的id一致,如:在这里插入图片描述
3、mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致;
4、mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致。
通过上面四个规范,可写出以下mapper.java:
在这里插入图片描述
为何要这样做?
这样做的话,我们只需要直接调用mapper接口中的方法,就能让mybatis直接代理了相应的操作,因为方法名和mapper.xml中statement的id相同,它通过方法名就能直接找到mapper.xml中的标签,知道是做select操作还是insert操作,方法名中指定了返回类型以及传参类型,所以如果故意把这两个类型写错,与mapper.xml中给的类型不一致,则会报错。
我们现在在SqlMapConfig.xml里面添加映射文件都是通过把mapper.xml所在位置写到mapper标签的resource属性中,但是使用mapper代理方法后,我们还可以通过mapper接口来加载映射文件(注意用的是class属性):
在这里插入图片描述
通过mapper接口加载映射文件,遵循一些规范:需要将mapper接口类名和mapper.xml映射文件保持一致且在一个目录中,且命令相同:
在这里插入图片描述
上边规范的前提是:使用的是mapper代理方法。
如果有多个mapper要加载要怎么办呢,这里还提供了批量加载mapper的方法(推荐使用,因为方便):
在这里插入图片描述
批量加载mapper 指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载,同样要遵循上面那个规范。

四、动态sql

1、if判断

在很多时候我们需要用不同的条件组合去进行查询,那在这里mybatis还提供了动态sql语句拼接的功能。
例如,我想通过用户名或姓别去查询数量,那么在mapper.xml里可以使用if标签和where标签:
在这里插入图片描述
很好理解,if标签中的test属性即表明了满足判断后才拼接下面的sql,比如当用户名不为空时,拼接上and username = #{username}。写在where标签里面的好处就是,where会自动去掉第一个拼接上来的and。
在接口类中添加相应的方法:
在这里插入图片描述
来看测试代码:
在这里插入图片描述
只指定性别时,看下面日志显示的sql语句就只把性别作为条件;
再看指定姓名和性别两个条件时的效果:
在这里插入图片描述
甚至一个条件都不传的话,它的sql语句就为:select count(*) from user,特别地灵活。

2、sql片段

为了提高这个动态sql的重用性,我们可以定义一个sql片段,在需要的地方引用即可,避免了复制粘贴:
在这里插入图片描述
注意where标签不要写在sql片段里,因为如果要引用多个sql片段,则那一条sql语句里会产生多个where。

以上内容为mybatis的基础,
接下来,是mybatis的高级部分。

------------------------------------------------------分割线-------------------------------------------------------
在此之前,需要在数据库中新建四个表并插入数据(把上面用到的user表删了,这里会重建并给上数据):
建表:

/*Table structure for table `items` */

CREATE TABLE `items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL COMMENT '商品名称',
  `price` float(10,1) NOT NULL COMMENT '商品定价',
  `detail` text COMMENT '商品描述',
  `pic` varchar(64) DEFAULT NULL COMMENT '商品图片',
  `createtime` datetime NOT NULL COMMENT '生产日期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Table structure for table `orderdetail` */

CREATE TABLE `orderdetail` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `orders_id` int(11) NOT NULL COMMENT '订单id',
  `items_id` int(11) NOT NULL COMMENT '商品id',
  `items_num` int(11) DEFAULT NULL COMMENT '商品购买数量',
  PRIMARY KEY (`id`),
  KEY `FK_orderdetail_1` (`orders_id`),
  KEY `FK_orderdetail_2` (`items_id`),
  CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

/*Table structure for table `orders` */

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '下单用户id',
  `number` varchar(32) NOT NULL COMMENT '订单号',
  `createtime` datetime NOT NULL COMMENT '创建订单时间',
  `note` varchar(100) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `FK_orders_1` (`user_id`),
  CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

/*Table structure for table `user` */

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `sex` char(1) DEFAULT NULL COMMENT '性别',
  `address` varchar(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;

插入数据:

/*Data for the table `items` */

insert  into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,'台式机',3000.0,'该电脑质量非常好!!!!',NULL,'2015-02-03 13:22:53'),(2,'笔记本',6000.0,'笔记本性能好,质量好!!!!!',NULL,'2015-02-09 13:22:57'),(3,'背包',200.0,'名牌背包,容量大质量好!!!!',NULL,'2015-02-06 13:23:02');

/*Data for the table `orderdetail` */

insert  into `orderdetail`(`id`,`orders_id`,`items_id`,`items_num`) values (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);

/*Data for the table `orders` */

insert  into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,'1000010','2015-02-04 13:22:35',NULL),(4,1,'1000011','2015-02-03 13:22:41',NULL),(5,10,'1000012','2015-02-12 16:13:23',NULL);

/*Data for the table `user` */

insert  into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,'王五',NULL,'2',NULL),(10,'张三','2014-07-10','1','北京市'),(16,'张小明',NULL,'1','河南郑州'),(22,'陈小明',NULL,'1','河南郑州'),(24,'张三丰',NULL,'1','河南郑州'),(25,'陈小明',NULL,'1','河南郑州'),(26,'王五',NULL,NULL,NULL);

数据模型分析:
在这里插入图片描述
用户表user:
记录了购买商品的用户信息
订单表:orders
记录了用户所创建的订单(购买商品的订单)
订单明细表:orderdetail:
记录了订单的详细信息即购买商品的信息
商品表:items
记录了商品信息

表与表之间的业务关系:
在分析表与表之间的业务关系时需要建立 在某个业务意义基础上去分析。
先分析数据级别之间有关系的表之间的业务关系:
usre和orders:
user---->orders:一个用户可以创建多个订单,一对多
orders—>user:一个订单只由一个用户创建,一对一

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

orderdetail和itesm:
orderdetail—>itesms:一个订单明细只对应一个商品信息,一对一
items–> orderdetail:一个商品可以包括在多个订单明细 ,一对多

再分析数据库级别没有关系的表之间是否有业务关系:
orders和items:
orders和items之间可以通过orderdetail表建立 关系。

五、高级映射_一对一查询

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

5.1、使用resultType

5.1.1 sql语句

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

5.1.2 创建pojo

将上边sql查询的结果映射到pojo中,pojo中必须包括所有查询列名。
原始的Orders.java不能映射全部字段,需要新创建的pojo。
创建一个pojo继承包括查询字段较多的po类。

先创建原始的Orders.java:
在这里插入图片描述
再创建Orders的扩展类:
在这里插入图片描述

5.1.3 创建mapper.xml和mapper.java

在这里插入图片描述
在这里插入图片描述

5.1.4 测试及结果

在这里插入图片描述
在这里我们为了能够映射一对一查询结果而专门创建了一个Order扩展类,那能不能不创建扩展类而就只使用Order.java呢?

5.2、使用resultMap

5.2.1 使用resultMap映射的思路

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

5.2.2 编写mapper.xml和mapper.java

在这里插入图片描述
这里再对resultMap的配置进行解释:

<!--column不做限制,可以为任意表的字段,而property须为type 定义的pojo属性-->
<resultMap id="唯一的标识" type="映射的pojo对象">
  <id column="表的主键字段,或者可以为查询语句中的别名字段" jdbcType="字段类型" property="映射pojo对象的主键属性" />
  <result column="表的一个字段(可以为任意表的一个字段)" jdbcType="字段类型" property="映射到pojo对象的一个属性(须为type定义的pojo对象中的一个属性)"/>
  <association property="pojo的一个对象属性" javaType="pojo关联的pojo对象">
    <id column="关联pojo对象对应表的主键字段" jdbcType="字段类型" property="关联pojo对象的主键属性"/>
    <result  column="任意表的字段" jdbcType="字段类型" property="关联pojo对象的属性"/>
  </association>
  <!-- 集合中的property须为oftype定义的pojo对象的属性-->
  <collection property="pojo的集合属性" ofType="集合中的pojo对象">
    <id column="集合中pojo对象对应的表的主键字段" jdbcType="字段类型" property="集合中pojo对象的主键属性" />
    <result column="可以为任意表的字段" jdbcType="字段类型" property="集合中的pojo对象的属性" />  
  </collection>
</resultMap>

在这里插入图片描述

5.2.3测试及结果

在这里插入图片描述

5.3 resultType和resultMap一对一查询小结

resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
如果没有查询结果的特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的属性中。
resultMap可以实现延迟加载,resultType无法实现延迟加载。

六、一对多查询

需求:查询订单及订单明细的信息。

6.1 sql语句

确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可。
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

6.2 分析

使用resultType将上边的 查询结果映射到pojo中,订单信息的就是重复。
在这里插入图片描述
可以看到实际上只有两条订单,每条订单对应了两条订单详情,所以要求:
对orders映射不能出现重复记录。
在orders.java类中添加orderDetails属性。
最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。
映射成的orders记录数为两条(orders信息不重复)
每个orders中的orderDetails属性存储了该订单所对应的订单明细。
在这里插入图片描述
OrderDetail的类如下:
在这里插入图片描述

6.3 编写mapper.xml和mapper.java

在这里插入图片描述
在这里插入图片描述

6.4 测试及结果

在这里插入图片描述
展开这个list可以看到,共有两条订单,每条订单下有两条订单详情:
在这里插入图片描述

6.5 小结

mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
使用resultType实现:
将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。

七、多对多查询

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

7.1 sql语句

查询主表是:用户表
关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:
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
查询结果:
在这里插入图片描述

7.2 映射思路

将用户信息映射到user中。
在User类中添加订单列表属性 orderslist,将用户创建的订单映射到orderslist,
在Orders中添加订单明细列表属性orderdetials,将订单的明细映射到orderdetials
在OrderDetail中添加Items属性,将订单明细所对应的商品映射到Items

根据这个思路,先创建Items类:
在这里插入图片描述
User类:
在这里插入图片描述
Orders类:
在这里插入图片描述
OrderDetail类:
在这里插入图片描述

7.3 编写mapper.xml和mapper.java

在这里插入图片描述
resultMap定义:
在这里插入图片描述
根据上述的思路,一层一层地编写,容易理解。
最后是mapper.java:
在这里插入图片描述

7.4 测试及结果

在这里插入图片描述
来看这个list:
在这里插入图片描述
信息层次展示得很清楚,list内有一个user,这个user内存着两个orders,每个orders又有两个orderdetail,每个orderdetail存着对应的items,正是我们想要的结果。

7.5 多对多查询总结

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

八、resultType和resultMap总结

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

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

association:
作用:
将关联查询信息映射到一个pojo对象中。
场合:
为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。

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

九、查询缓存

mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
mybaits提供一级缓存,和二级缓存。
在这里插入图片描述
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
为什么要用缓存?
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。

9.1 一级缓存

9.1.1 原理:

在这里插入图片描述
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免读脏数据。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

9.1.2 测试

mybatis默认支持一级缓存,不需要在配置文件去配置。
在这里插入图片描述
通过日志及输出信息可以看到,第一次查询执行了sql请求,而第二次查询并没有经过sql请求,直接输出了user对象。
那如果在两次查询中间更新了数据呢?看下面这个测试:
在这里插入图片描述
在这里插入图片描述
通过日志信息可以看到,由于中间有过commit操作,第二次执行查询操作的时候,也执行了sql请求。

9.1.3 一级缓存应用

正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。

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

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

9.2 二级缓存

9.2.1 原理

在这里插入图片描述
二级缓存默认是没有开启的。
sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。
sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按namespace分) ,其它mapper也有自己的二级缓存区域(按namespace分)。
每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。

9.2.2 开启二级缓存

mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关(这个总开关是默认开启的),还要在具体的mapper.xml中开启二级缓存。
首先在SqlMapConfig.xml中设置:
在这里插入图片描述
在这里插入图片描述
在UserMapper.xml中开启二缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap):
在这里插入图片描述
调用pojo类实现序列化接口:
在这里插入图片描述
这样做是为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。

9.2.3 测试

在这里插入图片描述
在这里插入图片描述
在两次查询之间更改了数据,再来测试一次:
在这里插入图片描述
在这里插入图片描述
观察日志信息即可见第二次查询是执行了sql请求的,且命中率为0.0,因为第二次请求缓存区是没有数据的。

9.2.4 useCache配置

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

<select id="findUserById" parameterType="int" resultMap="com.sj.po.User" useCache="false">

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

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

在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache=“true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:

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

9.2.6 二级缓存应用场景

对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。

9.2.7 二级缓存局限性

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

十、mybatis与spring整合

10.1 整合思路:

需要spring通过单例方式管理SqlSessionFactory。
spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(spring和mybatis整合自动完成)
持久层的mapper都需要由spring进行管理。

10.2 整合环境:

这里我们新建一个Maven工程,结构如下:
在这里插入图片描述
pom.xml中的依赖:

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.6</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.46</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.1.5.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>5.1.5.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>

UserMapper.java:
在这里插入图片描述
UserMapper.xml:
在这里插入图片描述
mybatis配置文件SqlMapConfig.xml:
在这里插入图片描述
可以看到较比之前的SqlMapConfig.xml少了数据源等配置,和spring整合后environments配置就被废除掉了。

spring的配置文件applicationContext.xml:
在这里插入图片描述

10.3 测试

在这里插入图片描述

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

如果有多个mapper,那在上述的applicationContext.xml中,需要对每个mapper都进行配置,这样做显然比较麻烦。
所以这里就要在applicationContext.xml中配置通过MapperScannerConfigurer进行mapper扫描:
在这里插入图片描述
注意两个地方:
1. 被注释的两部分代码
当使用mapper批量扫描后,spring全自动通过给的包名找到mapper接口,自动创建代理对象并且在spring容器中注册,所以已经不需要再加载mybatis的配置文件SqlMapConfig.xml了。
2. 为什么使用使用name=“sqlSessionFactoryBeanName” value="sqlSessionFactory"

注意看这个org.mybatis.spring.mapper.MapperScannerConfigurer类:
在这里插入图片描述
有个sqlSessionFactory还有一个sqlSessionFactoryBeanName。
那为什么不用之前那个sqlSessionFactory,用ref="sqlSessionFactory"来引用多好呢,如果这样做的话,那会引出一个问题:
加载db.properties就不起作用了。为什么?因为这个批量扫描会先执行,加载db.properties和创建数据源会后执行,这样的话,执行了批量扫描后需要的数据源却没有创建,而按照我们的思路,肯定是要先创建数据源的。
所以这里并不是注入sqlSessionFactory,而是注入sqlSessionFactoryBeanName,值为“sqlSessionFactory”。

再次进行测试:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值