MyBatis基础回顾及高级应用

一、Mybatis相关概念

1.1 对象/关系数据库映射(ORM)

  ORM全称Object/Relation Mapping:表示对象-关系映射的缩写

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

1.2 Mybatis简介

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

1.3 Mybatis历史

  原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11月迁移到Github。

  iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

1.4 Mybatis优势

  Mybatis是一个半自动化的持久层框架,对开发人员开说,核心sql还是需要自己进行优化,sql和java编码进行分离,功能边界清晰,一个专注业务,一个专注数据。

分析图示如下:
在这里插入图片描述

二、Mybatis基本应用

2.1 快速入门

  MyBatis官网地址:http://www.mybatis.org/mybatis-3/

2.1.1 开发步骤:

  1. 添加MyBatis的坐标
  2. 创建user数据表
  3. 编写User实体类
  4. 编写映射文件UserMapper.xml
  5. 编写核心文件SqlMapConfig.xml
  6. 编写测试类
(1)导入MyBatis的坐标和其他相关坐标
<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
	<java.version>1.8</java.version>
	<maven.compiler.source>1.8</maven.compiler.source>
	<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<!--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>
	<scope>test</scope>
</dependency>

<!--日志坐标-->
<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.12</version>
</dependency>
(2)创建user数据表
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(3)编写User实体
public class User {

	private int id;
	private String username;
	private String password;
	
	//省略get个set方法
	
}
(4)编写UserMapper映射文件
<?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="userMapper">
	<select id="findAll" resultType="com.cyd.domain.User">
		select * from User
	</select>
</mapper>
(5)编写MyBatis核心文件
<!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.jdbc.Driver"/>
				<property name="url" value="jdbc:mysql:///test"/>
				<property name="username" value="root"/>
				<property name="password" value="root"/>
			</dataSource>
		</environment>
	</environments>
	
	<mappers>
		<mapper resource="com/lagou/mapper/UserMapper.xml"/>
	</mappers>
	
</configuration>
(6)编写测试代码
	//加载核心配置文件
	InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
	//获得sqlSession工厂对象
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	//获得sqlSession对象
	SqlSession sqlSession = sqlSessionFactory.openSession();
	//执行sql语句
	List<User> userList = sqlSession.selectList("userMapper.findAll");
	//打印结果
	System.out.println(userList);
	//释放资源
	sqlSession.close();

2.2 MyBatis的增删改查操作

2.2.1 MyBatis的插入数据操作

(1)编写UserMapper映射文件

<mapper namespace="userMapper">
	<insert id="add" parameterType="com.cyd.domain.User">
		insert into user values(#{id},#{username},#{password})
	</insert>
</mapper>

(2)编写插入实体User的代码

InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int insert = sqlSession.insert("userMapper.add", user);
System.out.println(insert);
//提交事务
sqlSession.commit();
sqlSession.close();

(3)插入操作注意问题

  • 插入语句使用insert标签
  • 在映射文件中使用parameterType属性指定要插入的数据类型
  • Sql语句中使用#{实体属性名}方式引用实体中的属性值
  • 插入操作使用的API是sqlSession.insert(“命名空间.id”,实体对象);
  • 插入操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

2.2.2 MyBatis的修改数据操作

(1)编写UserMapper映射文件

<mapper namespace="userMapper">
	<update id="update" parameterType="com.cyd.domain.User">
		update user set username=#{username},password=#{password} where id=#{id}
	</update>
</mapper>

(2)编写修改实体User的代码

InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int update = sqlSession.update("userMapper.update", user);
System.out.println(update);
sqlSession.commit();
sqlSession.close();

(3)修改操作注意问题

  • 修改语句使用update标签
  • 修改操作使用的API是sqlSession.update(“命名空间.id”,实体对象);

2.2.3 MyBatis的删除数据操作

(1)编写UserMapper映射文件

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

(2)编写删除数据的代码

InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int delete = sqlSession.delete("userMapper.delete",3);
System.out.println(delete);
sqlSession.commit();
sqlSession.close();

(3)删除操作注意问题

  • 删除语句使用delete标签
  • Sql语句中使用#{任意字符串}方式引用传递的单个参数
  • 删除操作使用的API是sqlSession.delete(“命名空间.id”,Object);

2.3 Mybatis的Dao层实现

2.3.1 传统开发方式

(1)编写UserDao接口

public interface UserDao {
	List<User> findAll() throws IOException;
}

(2)编写UserDaoImpl实现

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;
	}
	
}

(3)测试传统方式

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

2.3.2 代理开发方式

代理开发方式介绍

  • 采用 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的类型相同

编写UserMapper接口
在这里插入图片描述

测试代理方式

@Test
public void testProxyDao() throws IOException {
	InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	SqlSession sqlSession = sqlSessionFactory.openSession();
	//获得MyBatis框架生成的UserMapper接口的实现类
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	User user = userMapper.findById(1);
	System.out.println(user);
	sqlSession.close();
}

三、Mybatis配置文件深入

3.1 核心配置文件SqlMapConfig.xml

3.1.1 MyBatis核心配置文件层级关系

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
在这里插入图片描述

3.1.2 MyBatis常用配置解析

3.1.2.1 environments标签

数据库环境的配置,支持多环境配置
在这里插入图片描述

其中,事务管理器(transactionManager)类型有两种:

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

其中,数据源(dataSource)类型有三种:

  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
  • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
  • JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
3.1.2.2 mapper标签

该标签的作用是加载映射的,加载方式有如下几种:
在这里插入图片描述

3.1.2.3 Properties标签

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

3.1.2.4 typeAliases标签

  类型别名是为Java 类型设置一个短的名字。原来的类型名称配置如下

在这里插入图片描述

  配置typeAliases,为com.lagou.domain.User定义别名为user

在这里插入图片描述

  上面我们是自定义的别名,mybatis框架已经为我们设置好的一些常用的类型的别名

在这里插入图片描述

3.2 映射配置文件mapper.xml

3.2.1 动态sql语句

动态sql语句概述

  Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

(1)动态SQL之 if 标签

  我们根据实体类的不同取值,使用不同的 SQL语句来进行查询。比如在 id如果不为空时可以根据id查询,如果username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

<select id="findByCondition" parameterType="user" resultType="user">
	select * from User
	<where>
		<if test="id!=0">
			and id=#{id}
		</if>
		<if test="username!=null">
			and username=#{username}
		</if>
	</where>
</select>

当查询条件id和username都存在时,控制台打印的sql语句如下:

	//获得MyBatis框架生成的UserMapper接口的实现类
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	User condition = new User();
	condition.setId(1);
	condition.setUsername("lucy");
	User user = userMapper.findByCondition(condition);

在这里插入图片描述

当查询条件只有id存在时,控制台打印的sql语句如下:

	//获得MyBatis框架生成的UserMapper接口的实现类
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	User condition = new User();
	condition.setId(1);
	User user = userMapper.findByCondition(condition);

在这里插入图片描述

(2)动态SQL之 foreach 标签

  循环执行sql的拼接操作,例如:SELECT * FROM User WHERE id IN (1,2,5)。

<select id="findByIds" parameterType="list" resultType="user">
	select * from User
	<where>
		<foreach collection="list" open="id in(" close=")" item="id" separator=",">
			#{id}
		</foreach>
	</where>
</select>

测试代码片段如下:

	//获得MyBatis框架生成的UserMapper接口的实现类
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	int[] ids = new int[]{2,5};
	List<User> userList = userMapper.findByIds(ids);
	System.out.println(userList);

在这里插入图片描述

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

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句的开始部分
  • close:代表结束部分
  • item:代表遍历集合的每个元素,生成的变量名
  • sperator:代表分隔符

(3)SQL片段抽取

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

<!--抽取sql片段简化编写-->
<sql id="selectUser"> 
	select * from User
</sql>

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

<select id="findByIds" parameterType="list" resultType="user">
	<include refid="selectUser"/>
	<where>
		<foreach collection="array" open="id in(" close=")" item="id" separator=",">
			#{id}
		</foreach>
	</where>
</select>

四、Mybatis复杂映射开发

4.1 一对一查询

4.1.1 一对一查询的模型

  用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
  一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户

4.1.2一对一查询的语句

  对应的sql语句:select * from orders o,user u where o.uid=u.id;
  查询的结果如下:
在这里插入图片描述

4.1.3 创建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;
	private String password;
	private Date birthday;
}

4.1.4 创建OrderMapper接口

public interface OrderMapper {
	List<Order> findAll();
}

4.1.5 配置OrderMapper.xml

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

	<resultMap id="orderMap" type="com.cyd.domain.Order">
		<result column="uid" property="user.id"></result>
		<result column="username" property="user.username"></result>
		<result column="password" property="user.password"></result>
		<result column="birthday" property="user.birthday"></result>
	</resultMap>
	
	<select id="findAll" resultMap="orderMap">
		select * from orders o,user u where o.uid=u.id
	</select>
	
</mapper>

其中还可以配置如下(一般采用该种方式):

<resultMap id="orderMap" type="com.cyd.domain.Order">
	<result property="id" column="id"></result>
	<result property="ordertime" column="ordertime"></result>
	<result property="total" column="total"></result>
	<association property="user" javaType="com.cyd.domain.User">
		<result column="uid" property="id"></result>
		<result column="username" property="username"></result>
		<result column="password" property="password"></result>
		<result column="birthday" property="birthday"></result>
	</association>
</resultMap>

4.1.6 测试结果

	OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
	List<Order> all = mapper.findAll();
	for(Order order : all){
		System.out.println(order);
	}

在这里插入图片描述

4.2 一对多查询

4.2.1 一对多查询的模型

  用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
  一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
在这里插入图片描述

4.2.2 一对多查询的语句

对应的sql语句:select *,o.id oid from user u left join orders o on u.id=o.uid;
查询的结果如下:
在这里插入图片描述

4.2.3 修改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;
	private String password;
	private Date birthday;
	
	//代表当前用户具备哪些订单
	private List<Order> orderList;
	
}

4.2.4 创建UserMapper接口

public interface UserMapper {
	List<User> findAll();
}

4.2.5 配置UserMapper.xml

<mapper namespace="com.cyd.mapper.UserMapper">

	<resultMap id="userMap" type="com.cyd.domain.User">
		<result column="id" property="id"></result>
		<result column="username" property="username"></result>
		<result column="password" property="password"></result>
		<result column="birthday" property="birthday"></result>
		
		<collection property="orderList" ofType="com.cyd.domain.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>

4.2.6 测试结果

	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
	List<User> all = mapper.findAll();
	for(User user : all){
		System.out.println(user.getUsername());
		List<Order> orderList = user.getOrderList();
		for(Order order : orderList){
			System.out.println(order);
		}
		System.out.println("----------------------------------");
	}

在这里插入图片描述

4.3 多对多查询

4.3.1 多对多查询的模型

用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色
在这里插入图片描述

4.3.2 多对多查询的语句

  对应的sql语句:select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id;

查询的结果如下:
在这里插入图片描述

4.3.3 创建Role实体,修改User实体

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

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

4.3.4 添加UserMapper接口方法

List<User> findAllUserAndRole();

4.3.5 配置UserMapper.xml

<resultMap id="userRoleMap" type="com.cyd.domain.User">
	<result column="id" property="id"></result>
	<result column="username" property="username"></result>
	<result column="password" property="password"></result>
	<result column="birthday" property="birthday"></result>
	
	<collection property="roleList" ofType="com.cyd.domain.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 rid from user u left join user_role ur on u.id=ur.user_id
	inner join role r on ur.role_id=r.id
</select>

4.3.6 测试结果

	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
	List<User> all = mapper.findAllUserAndRole();
	for(User user : all){
		System.out.println(user.getUsername());
		List<Role> roleList = user.getRoleList();
		for(Role role : roleList){
			System.out.println(role);
		}
		System.out.println("----------------------------------");
	}

在这里插入图片描述

五、Mybatis注解开发

5.1 MyBatis的常用注解

  这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。我们先围绕一些基本的CRUD来学习,再学习复杂映射多表操作。

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

5.2 MyBatis的增删改查

我们完成简单的user表的增删改查的操作

private UserMapper userMapper;

@Before
public void before() throws IOException {
	InputStream resourceAsStream =
	Resources.getResourceAsStream("SqlMapConfig.xml");
	SqlSessionFactory sqlSessionFactory = new
	SqlSessionFactoryBuilder().build(resourceAsStream);
	SqlSession sqlSession = sqlSessionFactory.openSession(true);
	userMapper = sqlSession.getMapper(UserMapper.class);
}

@Test
public void testAdd() {
	User user = new User();
	user.setUsername("测试数据");
	user.setPassword("123");
	user.setBirthday(new Date());
	userMapper.add(user);
}

@Test
public void testUpdate() throws IOException {
	User user = new User();
	user.setId(16);
	user.setUsername("测试数据修改");
	user.setPassword("abc");
	user.setBirthday(new Date());
	userMapper.update(user);
}

@Test
public void testDelete() throws IOException {
	userMapper.delete(16);
}

@Test
public void testFindById() throws IOException {
	User user = userMapper.findById(1);
	System.out.println(user);
}

@Test
public void testFindAll() throws IOException {
	List<User> all = userMapper.findAll();
	for(User user : all){
		System.out.println(user);
	}
}

  修改MyBatis的核心配置文件,我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的 Mapper接口即可。

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

或者指定扫描包含映射关系的接口所在的包也可以

<mappers>
	<!--扫描使用注解的类所在的包-->
	<package name="com.lagou.mapper"></package>
</mappers>

5.3 MyBatis的注解实现复杂映射开发

  实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置。
在这里插入图片描述
在这里插入图片描述

5.4 一对一查询

5.4.1 一对一查询的模型

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
在这里插入图片描述

5.4.2 一对一查询的语句

对应的sql语句:

select * from orders;
select * from user where id=查询出订单的uid;

查询的结果如下:
在这里插入图片描述

5.4.3 创建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;
	private String password;
	private Date birthday;
}

5.4.4 创建OrderMapper接口

public interface OrderMapper {
	List<Order> findAll();
}

5.4.5 使用注解配置Mapper

public interface OrderMapper {

	@Select("select * from orders")
	@Results({
		@Result(id=true,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.lagou.mapper.UserMapper.findById"))
	})
	List<Order> findAll();
}
public interface UserMapper {
	@Select("select * from user where id=#{id}")
	User findById(int id);
}

5.4.6 测试结果

@Test
public void testSelectOrderAndUser() {
	List<Order> all = orderMapper.findAll();
	for(Order order : all){
		System.out.println(order);
	}
}

在这里插入图片描述

5.5 一对多查询

5.5.1 一对多查询的模型

  用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

  一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
在这里插入图片描述

5.5.2 一对多查询的语句

select * from user;
select * from orders where uid=查询出用户的id;

查询的结果如下:
在这里插入图片描述

5.5.3 修改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;
	private String password;
	private Date birthday;
	//代表当前用户具备哪些订单
	private List<Order> orderList;
	
}

5.5.4 创建UserMapper接口

List<User> findAllUserAndOrder();

5.5.5 使用注解配置Mapper

public interface UserMapper {
	@Select("select * from user")
	@Results({
		@Result(id = true,property = "id",column = "id"),
		@Result(property = "username",column = "username"),
		@Result(property = "password",column = "password"),
		@Result(property = "birthday",column = "birthday"),
		@Result(property = "orderList",column = "id",
			javaType = List.class,
			many = @Many(select ="com.lagou.mapper.OrderMapper.findByUid"))
	})
	List<User> findAllUserAndOrder();
}

public interface OrderMapper {
	@Select("select * from orders where uid=#{uid}")
	List<Order> findByUid(int uid);
}

5.5.6 测试结果

	List<User> all = userMapper.findAllUserAndOrder();
	for(User user : all){
		System.out.println(user.getUsername());
		List<Order> orderList = user.getOrderList();
		for(Order order : orderList){
			System.out.println(order);
		}
		System.out.println("-----------------------------");
	}

在这里插入图片描述

5.6 多对多查询

5.6.1 多对多查询的模型

  用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用

  多对多查询的需求:查询用户同时查询出该用户的所有角色
在这里插入图片描述

5.6.2 多对多查询的语句

select * from user;
select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=用户的id

查询的结果如下:
在这里插入图片描述

5.6.3 创建Role实体,修改User实体

public class User {

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

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

5.6.4 添加UserMapper接口方法

List<User> findAllUserAndRole();

5.6.5 使用注解配置Mapper

public interface UserMapper {
	@Select("select * from user")
	@Results({
		@Result(id = true,property = "id",column = "id"),
		@Result(property = "username",column = "username"),
		@Result(property = "password",column = "password"),
		@Result(property = "birthday",column = "birthday"),
		@Result(property = "roleList",column = "id",
			javaType = List.class, many = @Many(select = "com.lagou.mapper.RoleMapper.findByUid"))
	})
	List<User> findAllUserAndRole();
}

public interface RoleMapper {
	@Select("select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=#{uid}")
	List<Role> findByUid(int uid);
}

5.6.6 测试结果

	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
	List<User> all = mapper.findAllUserAndRole();
	for(User user : all){
		System.out.println(user.getUsername());
		List<Role> roleList = user.getRoleList();
		for(Role role : roleList){
			System.out.println(role);
		}
		System.out.println("----------------------------------");
	}

在这里插入图片描述

六、Mybatis缓存

6.1 一级缓存

1、在一个sqlSession中,对User表根据id进行两次查询,查看他们发出sql语句的情况

@Test
public void test1(){
	// 根据 sqlSessionFactory 产生 session
	SqlSession sqlSession = sessionFactory.openSession();
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	
	//第一次查询,发出sql语句,并将查询出来的结果放进缓存中
	User u1 = userMapper.selectUserByUserId(1);
	System.out.println(u1);
	
	//第二次查询,由于是同一个sqlSession,会在缓存中查询结果
	//如果有,则直接从缓存中取出来,不和数据库进行交互
	User u2 = userMapper.selectUserByUserId(1);
	System.out.println(u2);
	
	sqlSession.close();
}

查看控制台打印情况:第一次查询时发出了sql语句,然后第二次查询直接打印用户对象,没有发出查询sql语句。

在这里插入图片描述
2、同样是对user表进行两次查询,只不过两次查询之间进行了一次update操作。

@Test
public void test2(){
	//根据 sqlSessionFactory 产生 session
	SqlSession sqlSession = sessionFactory.openSession();
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	
	//第一次查询,发出sql语句,并将查询的结果放入缓存中
	User u1 = userMapper.selectUserByUserId( 1 );
	System.out.println(u1);
	
	//第二步进行了一次更新操作,sqlSession.commit()
	u1.setSex("女");
	userMapper.updateUserByUserId(u1);
	sqlSession.commit();
	
	//第二次查询,由于是同一个sqlSession.commit(),会清空缓存信息
	//则此次查询也会发出sql语句
	User u2 = userMapper.selectUserByUserId(1);
	System.out.println(u2);
	
	sqlSession.close();
}

查看控制台打印情况:
在这里插入图片描述
3、总结

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

在这里插入图片描述
一级缓存原理探究与源码分析

  一级缓存到底是什么?一级缓存什么时候被创建、一级缓存的工作流程是怎样的?相信你现在应该会有这几个疑问,那么我们本节就来研究一下一级缓存的本质。

  大家可以这样想,上面我们一直提到一级缓存,那么提到一级缓存就绕不开SqlSession,所以索性我们就直接从SqlSession,看看有没有创建缓存或者与缓存有关的属性或者方法。

在这里插入图片描述
  调研了一圈,发现上述所有方法中,好像只有clearCache()和缓存沾点关系,那么就直接从这个方法入手吧,分析源码时,我们要看它(此类)是谁,它的父类和子类分别又是谁,对如上关系了解了,你才会对这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图:
在这里插入图片描述
  再深入分析,流程走到Perpetualcache中的clear()方法之后,会调用其cache.clear()方法,那么这个cache是什么东西呢?点进去发现,cache其实就是private Map cache = new HashMap();也就是一个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是本地存放的一个map对象,每一个SqISession都会存放一个map对象的引用,那么这个cache是何时创建的呢?

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

在这里插入图片描述
  创建缓存key会经过一系列的update方法,udate方法由一个CacheKey这个对象来执行的,这个update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能理解这五个值都是什么了。
在这里插入图片描述
  这里需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是定义在 mybatis-config.xml中的标签,见如下。
在这里插入图片描述
  那么我们回归正题,那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会的,经过我们对一级缓存的探究之后,我们发现一级缓存更多是用于查询操作,毕竟一级缓存也叫做查询缓存吧,为什么叫查询缓存我们一会儿说。我们先来看一下这个缓存到底用在哪了,我们跟踪到query方法如下:

在这里插入图片描述
  如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。 localcache对象的put方法最终交给Map进行存放

在这里插入图片描述

6.2 二级缓存

6.2.1 原理

  二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。

6.2.2 如何使用二级缓存

1、开启二级缓存

  和一级缓存默认开启不一样,二级缓存需要我们手动开启

  首先在全局配置文件sqlMapConfig.xml文件中加入如下代码:

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

  其次在UserMapper.xml文件中开启缓存

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

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

在这里插入图片描述
  从PerpetualCache类中我们可以看到二级缓存底层还是HashMap结构

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

2、测试

(1)测试二级缓存和sqlSession无关

@Test
public void testTwoCache(){
	// 根据 sqlSessionFactory 产生 session
	SqlSession sqlSession1 = sessionFactory.openSession();
	SqlSession sqlSession2 = sessionFactory.openSession();
	
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class );
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class );
	
	// 第一次查询,发出sql语句,并将查询的结果放入缓存中
	User u1 = userMapper1.selectUserByUserId(1);
	System.out.println(u1);
	sqlSession1.close(); // 第一次查询完后关闭 sqlSession
	
	// 第二次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句
	User u2 = userMapper2.selectUserByUserId(1);
	System.out.println(u2);
	sqlSession2.close();
}

(2)测试执行commit()操作,二级缓存数据清空

@Test
public void testTwoCache(){
	// 根据 sqlSessionFactory 产生 session
	SqlSession sqlSession1 = sessionFactory.openSession();
	SqlSession sqlSession2 = sessionFactory.openSession();
	SqlSession sqlSession3 = sessionFactory.openSession();
	
	String statement = "com.cyd.pojo.UserMapper.selectUserByUserld" ;
	
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class );
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class );
	UserMapper userMapper3 = sqlSession2.getMapper(UserMapper. class );
	
	// 第一次查询,发出sql语句,并将查询的结果放入缓存中
	User u1 = userMapperl.selectUserByUserId( 1 );
	System.out.println(u1);
	sqlSessionl .close(); // 第一次查询完后关闭sqlSession
	
	// 执行更新操作,commit()
	u1.setUsername( "aaa" );
	userMapper3.updateUserByUserId(u1);
	sqlSession3.commit(); 
	
	// 第二次查询,由于上次更新操作,缓存数据已经清空(防止数据脏读),这里必须再次发出sql语
	User u2 = userMapper2.selectUserByUserId( 1 );
	System.out.println(u2);
	sqlSession2.close();
}

查看控制台情况:

在这里插入图片描述
3、useCache和flushCache

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

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

  这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数据库中获取。

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

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

<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.cyd.pojo.User" parameterType="int">
	select * from user where id=#{id}
</select>

  一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可。

6.3 二级缓存整合redis

  上面我们介绍了 mybatis自带的二级缓存,但是这个缓存是单服务器工作,无法实现分布式缓存。 那么什么是分布式缓存呢?假设现在有两个服务器1和2,用户访问的时候访问了1服务器,查询后的缓存就会放在1服务器上,假设现在有个用户访问的是2服务器,那么他在2服务器上就无法获取刚刚那个缓存,如下图所示:
在这里插入图片描述
  为了解决这个问题,就得找一个分布式的缓存,专门用来存储缓存数据的,这样不同的服务器要缓存数据都往它那里存,取缓存数据也从它那里取,如下图所示:
在这里插入图片描述
  如上图所示,在几个不同的服务器之间,我们使用第三方缓存框架,将缓存都放在这个第三方框架中,然后无论有多少台服务器,我们都能从缓存中获取数据。

6.3.1 mybatis与redis的整合。

  刚刚提到过,mybatis提供了一个eache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。

  mybatis本身默认实现了一个,但是这个缓存的实现无法实现分布式缓存,所以我们要自己来实现。

  redis分布式缓存就可以,mybatis提供了一个针对cache接口的redis实现类,该类存在mybatis-redis包中实现:

1、pom文件

<dependency>
	<groupId>org.mybatis.caches</groupId>
	<artifactId>mybatis-redis</artifactId>
	<version>1.0.0-beta2</version>
</dependency>

2、配置文件 Mapper.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="com.cyd.mapper.IUserMapper">

	<cache type="org.mybatis.caches.redis.RedisCache" />
	
	<select id="findAll" resultType="com.cyd.pojo.User" useCache="true">
		select * from user
	</select>
	
</mapper>

3、redis.properties

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

4、测试

@Test
public void SecondLevelCache(){
	SqlSession sqlSession1 = sqlSessionFactory.openSession();
	SqlSession sqlSession2 = sqlSessionFactory.openSession();
	SqlSession sqlSession3 = sqlSessionFactory.openSession();
	
	IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
	lUserMapper mapper2 = sqlSession2.getMapper(lUserMapper.class);
	lUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);
	
	User user1 = mapper1.findUserById(1);
	sqlSession1.close(); //清空一级缓存
	
	User user = new User();
	user.setId(1);
	user.setUsername("lisi");
	mapper3.updateUser(user);
	sqlSession3.commit();
	
	User user2 = mapper2.findUserById(1);
	System.out.println(user1==user2);
}

6.3.2 源码分析:

  RedisCache和大家普遍实现Mybatis的缓存方案大同小异,无非是实现Cache接口,并使用jedis操作缓存;不过该项目在设计细节上有一些区别;

在这里插入图片描述
  RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的方式很简单,就是调用RedisCache的带有String参数的构造方法,即RedisCache(String id);而在RedisCache的构造方法中,调用了 RedisConfigurationBuilder 来创建 RedisConfig 对象,并使用 RedisConfig 来创建JedisPool。

在这里插入图片描述
  RedisConfig类继承了 JedisPoolConfig,并提供了 host、port等属性的包装,简单看一下RedisConfig的属性:

在这里插入图片描述
  RedisConfig对象是由RedisConfigurationBuilder创建的,看下这个类:

在这里插入图片描述
  核心的方法就是parseConfiguration方法,该方法从classpath中读取一个redis.properties文件:并将该配置文件中的内容设置到RedisConfig对象中,并返回;

host=localhost
port=6379
connectionTimeout=5000
soTimeout=5000
password= database=0 clientName=

  接下来,就是RedisCache使用,RedisConfig类创建完成jedisPool;在RedisCache中实现了一个简单的模板方法,用来操作Redis:
在这里插入图片描述
  模板接口为RedisCallback,这个接口中就只需要实现了一个doWithRedis方法而已:

在这里插入图片描述
  接下来看看Cache中最重要的两个方法:putObject和getObject,通过这两个方法来查看mybatis-redis 储存数据的格式:

在这里插入图片描述
  可以很清楚的看到,mybatis-redis在存储数据的时候,是使用的hash结构,把cache的id作为这个hash的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash 的 field,需要缓存的内容直接使用SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责对象的序列化和反序列化;

七、Mybatis插件

7.1 插件简介

  一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作。以MyBatis为例,我们可基于MyBatis插件机制实现分页、分表,监控等功能。由于插件和业务无关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能。

7.2 Mybatis插件介绍

  Mybatis作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(ExecutorStatementHandlerParameterHandlerResultSetHandler)处提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象。

在这里插入图片描述
MyBatis所允许拦截的方法如下:

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

7.3 Mybatis插件原理

在四大对象创建的时候

  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(type = Executor.class, method = "query", 
		args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
	)
})
public class ExeunplePlugin implements Interceptor {
	//省略逻辑
}

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

<plugins>
	<plugin interceptor="com.cyd.plugin.ExamplePlugin"/>
</plugins>

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

  以上就是MyBatis插件机制的基本原理。

7.4 自定义插件

7.4.1 插件接口

Mybatis 插件接口-Interceptor

  • Intercept方法,插件的核心方法
  • plugin方法,生成target的代理对象
  • setProperties方法,传递插件所需参数

7.4.2自定义插件

设计实现一个自定义插件

@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();
    }

    /**
     * 主要是为了把这个拦截器生成一个代理放到拦截器链中
     *
     * @Description 包装目标对象 为目标对象创建代理对象
     * @Param target为要拦截的对象
     * @Return 代理对象
     */
    @Override
    public Object plugin(Object target) {
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }

    /**
     * 获取配置文件的属性
     * 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件的参数是:" + properties);
    }
}

sqlMapConfig.xml

<plugins>
	<plugin interceptor="com.cyd.plugin.MySqlPagingPlugin">
		<!--配置参数-->
		<property name="name" value="Bob"/>
	</plugin>
</plugins>

mapper接口

public interface UserMapper {
	List<User> selectUser();
}

mapper.xml

<mapper namespace="com.cyd.mapper.UserMapper">
	<select id="selectUser" resultType="com.cyd.pojo.User">
		SELECT id,username FROM user
	</select>
</mapper>

测试类

public class PluginTest {

	@Test
	public void test() throws IOException {
		InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		List<User> byPaging = userMapper.selectUser();
		for (User user : byPaging) {
			System.out.println(user);
		}
	}
	
}

7.5 pageHelper分页插件

  MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

开发步骤:

  1. 导入通用PageHelper的坐标
  2. 在mybatis核心配置文件中配置PageHelper插件
  3. 测试分页数据获取
  4. 导入通用PageHelper坐标

1、导入通用PageHelper坐标

<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>3.7.5</version>
</dependency>

<dependency>
	<groupId>com.github.jsqlparser</groupId>
	<artifactId>jsqlparser</artifactId>
	<version>0.9.1</version>
</dependency>

2、在mybatis核心配置文件中配置PageHelper插件

<!--注意:分页助手的插件 配置在通用馆mapper之前*-->*
<plugin interceptor="com.github.pagehelper.PageHelper">
	<!-- 指定方言 -->
	<property name="dialect" value="mysql"/>
</plugin>

3、测试分页代码实现

@Test
public void testPageHelper() {
	// 设置分页参数
	PageHelper.startPage(1, 2);
	List<User> select = userMapper2.select(null);
	for (User user : select) {
		System.out.println(user);
	}
}

获得分页相关的其他参数

// 其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo. getPages ());
System.out.println("当前页:"+pageInfo. getPageNum());
System.out.println("每页显万长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());

7.6 通用 mapper

7.6.1 什么是通用Mapper

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

如何使用

1、首先在maven项目,在pom.xml中引入mapper的依赖

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

2、 Mybatis配置文件中完成配置

<plugins>
	<!--分页插件:如果有分页插件,要排在通用mapper之前-->
	<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>

3、实体类设置主键

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

4、定义通用mapper

import com.cyd.domain.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {

}

5、测试

public class UserTest {
	@Test
	public void test1() throws IOException {
		Inputstream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
		SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
		SqlSession sqlSession = build.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		
		User user = new User();
		user.setId(4);
		
		// (1)mapper基础接口
		
		// select 接口
		// 根据实体中的属性进行查询,只能有一个返回值
		User user1 = userMapper.selectOne(user); 
		 // 查询全部结果
		List<User> users = userMapper.select(null);
		// 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
		userMapper.selectByPrimaryKey(1); 
		// 根据实体中的属性查询总数,查询条件使用等号
		userMapper.selectCount(user); 
		
		// insert 接口
		// 保存一个实体,null值也会保存,不会使用数据库默认值
		int insert = userMapper.insert(user); 
		// 保存实体,null的属性不会保存,会使用数据库默认值
		int i = userMapper.insertSelective(user); 
		
		// update 接口
		// 根据主键更新实体全部字段,null值会被更新
		int i1 = 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);
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值