跳至主要內容

Mybatis框架

holic-x...大约 61 分钟框架持久层框架MyBatis

[JAVA]-Mybatis框架

[TOC]

1.Mybatis框架基础

【1】MyBatis框架的基本概念

mybatis框架概念

MyBatisopen in new window是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 MyBatis消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索。 MyBatis可以使用简单的XML 或注解用于配置和原始映射,将接口和 Java 的 POJO( Plain Old Java Objects,普通的Java 对象)映射成数据库中的记录。

​ MyBatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,可以自由灵活的生成满足条件的sql(半自动化的orm框架)

mybatis框架流程

mybatis核心API

1>Resources

​ org.apache.ibatis.io.Resources:加载资源的工具类

返回值方法名说明
InputStreamgetResourceAsStream(String fileName)通过类加载器返回指定资源的文件输入流
2>SqlSessionFactoryBuilder(构建器)

​ org.apache.ibatis.session.SqlSessionFactoryBuilder:获取 SqlSessionFactory 工厂对象的功能类

返回值方法名说明
SqlSessionFactorybuild(InputStream is)通过指定资源字节输入流获取SqlSession工厂对象
// 参考示例:通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
String resource = "org/mybatis/builder/mybatis-config.xml"; 
InputStream inputStream = Resources.getResourceAsStream(resource); 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
SqlSessionFactory factory = builder.build(inputStream);
3>SqlSessionFactory(工厂对象)

​ org.apache.ibatis.session.SqlSessionFactory:获取 SqlSession 构建者对象的工厂接口

返回值方法名说明
SqlSessionopenSession()获取SqlSession构建者对象,并开启手动提交事务
SqlSessionopenSession(boolean autoCommit)获取SqlSession构建者对象,指定是否自定提交事务
4>SqlSession(会话对象)

​ org.apache.ibatis.session.SqlSession:构建者对象接口。用于执行 SQL、管理事务、接口代理

​ 其核心的API是与DB操作相关的内容

【2】MyBatis入门程序

🔖需求分析

功能说明

利用MyBatis框架相关知识完成下述需求

1)根据用户id(主键) 查询用户信息

2)根据用户名模糊查询用户信息

3)添加用户

4)删除用户

5)更新用户

开发环境

​ JDK1.8+Mysql5.7

📌工程环境搭建

1>创建工程

a.配置POM.XML文件,添加相关jar包

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<mybatis-version>3.4.6</mybatis-version>
		<mysql-version>5.1.6</mysql-version>
		<logging-version>1.2</logging-version>
		<log4j-version>1.2.17</log4j-version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>${mybatis-version}</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql-version}</version>
		</dependency>

		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>${logging-version}</version>
		</dependency>

		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j-version}</version>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.25</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

b.引入记录日志的文件Log4j.properties

  • 记录日志的文件log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=d\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

#log4j.rootLogger=info, stdout , file
log4j.rootLogger=DEBUG,stdout

c.工程目录结构如下所示

2>sqlMapConfig.xml配置

​ 可查阅相关的官方doc文档,内部有详细的配置关于mybatis的运行环境、数据源、事务等配置说明

Mybatis官网open in new windowMybatis中文网址open in new window

基本配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!-- 这是数据源的配置 -->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="root" />
			</dataSource>
		</environment>
	</environments>
</configuration>
3>数据库环境

创建数据表、初始化数据

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `items`
-- ----------------------------
DROP TABLE IF EXISTS `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;

-- ----------------------------
-- Records of items
-- ----------------------------
INSERT INTO `items` VALUES ('1', '台式机', '3000.0', '该电脑质量非常好!!!!', null, '2015-02-03 13:22:53');
INSERT INTO `items` VALUES ('2', '笔记本', '6000.0', '笔记本性能好,质量好!!!!!', null, '2015-02-09 13:22:57');
INSERT INTO `items` VALUES ('3', '背包', '200.0', '名牌背包,容量大质量好!!!!', null, '2015-02-06 13:23:02');

-- ----------------------------
-- Table structure for `orderdetail`
-- ----------------------------
DROP TABLE IF EXISTS `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;

-- ----------------------------
-- Records of orderdetail
-- ----------------------------
INSERT INTO `orderdetail` VALUES ('1', '3', '1', '1');
INSERT INTO `orderdetail` VALUES ('2', '3', '2', '3');
INSERT INTO `orderdetail` VALUES ('3', '4', '3', '4');
INSERT INTO `orderdetail` VALUES ('4', '4', '2', '3');

-- ----------------------------
-- Table structure for `orders`
-- ----------------------------
DROP TABLE IF EXISTS `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;

-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES ('3', '1', '1000010', '2015-02-04 13:22:35', null);
INSERT INTO `orders` VALUES ('4', '1', '1000011', '2015-02-03 13:22:41', null);
INSERT INTO `orders` VALUES ('5', '10', '1000012', '2015-02-12 16:13:23', null);

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `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;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '王五', '2017-08-13', '2', '浙江杭州');
INSERT INTO `user` VALUES ('10', '张三', '2014-07-10', '1', '北京市');
INSERT INTO `user` VALUES ('16', '张小明', '2014-08-26', '1', '河南郑州');
INSERT INTO `user` VALUES ('22', '陈小明', '2017-08-16', '1', '山东济南');
INSERT INTO `user` VALUES ('24', '张三丰', '2017-08-06', '1', '河南郑州');
INSERT INTO `user` VALUES ('25', '陈小明', '2017-08-30', '1', '江苏南京');
INSERT INTO `user` VALUES ('26', '王五', '2017-08-04', '2', '浙江温州');

​ 建表模型如下所示:

按照如下步骤完成程序测试:
1.创建PO类(根据数据库表格实体创建相应的model)
2.编写映射文件(根据实际开发规范编写映射文件,并在核心配置文件中进行加载)
3.编写测试程序进行测试

📌代码编写测试

1>根据用户的id(主键)查询用户信息

创建User类,其属性与数据库列名一一对应

User.java:
public class User {
	private int id;
	private String username;
	private Date birthday;
	private String sex;
	private String address;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public Date getBirthday() {
		return birthday;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", sex=" + sex + ", address="
				+ address + "]";
	}
}

编写映射文件,并在核心配置文件中进行加载

  • user.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:代表命名空间,其作用就是对sql进行分类管理 ,可以理解为对sql进行隔离
   	注意: 目前namespace没有什么含义 ,但是在mapper代理方法开发的过程中 namespace有着特殊的作用
   -->
 <mapper namespace="test">
 	<!-- 在映射文件中通常会配置很多sql内容 -->
 	
 	<!-- 1.测试案例1:通过id查询用户信息 -->
 	<!-- 
 		通过select语句查询数据库
 		id:标识符,用于标识当前的sql 
 		parameterType:是指输入参数的类型(此处根据id查找,id是int类型)
 		resultType:指定输出的映射的java对象的类型 (select 语句执行之后要封装的类型,此处返回为User类型)
 		#{}代表一个占位符
 		#{id} 其中id代表接受输入参数的类型   参数的名字是id      
 			     如果输入参数是简单类型  ${}可以是任意或者value都可以
 	 -->
 	<select id="findUserById" parameterType="int" resultType="com.noob.mybatis.model.User">
 		select * from user where id=#{id}
 	</select>
 </mapper>
  • 主要配置:
<select id="findUserById" parameterType="int" resultType="com.noob.mybatis.model.User">
 		select * from user where id=#{id}
 </select>
  • 把映射文件添加到MyBatis的核心配置文件中进行加载

编写测试文件进行测试

public class UserTest {
	@Test
	public void testFindUserById() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		// c.通过会话工厂得到SqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		/**
		 * d.通过sqlSession操作数据库
		 * 参数1:映射文件中的statement的id,即 namespace+"."+statment的id
		 * 参数2:指定和映射文件中parameterType所匹配的类型
		 * selectOne 就是查询一个对象或者是一个数据
		 */
		User user = sqlSession.selectOne("test.findUserById", 1);
		System.out.println(user);
		// e.释放资源
		sqlSession.close();
	}
}

​ 完成上述测试,则按照相关步骤即可完善下述其余相关的数据库操作,在上述内容配置完成的基础上,只需要完成user.xml文件的配置以及测试编码的编写即可完成相关测试

2>根据用户名进行模糊查询

user.xml配置

		<!-- 2.根据用户名查询用户信息   可能会返回多条数据 -->
  	<!-- 
 		此处的resultType可能不只是一个对象,可能是一个集合
     	${value}:接受参数输入内容     
     	%${value}%:是模糊进行匹配
    -->	
 	<select id="findUserByName" parameterType="java.lang.String" resultType="com.noob.mybatis.model.User">
 		select * from user where username like '%${value}%'
 	</select>

编写测试代码

	// 优化测试,将公共代码提取出来
	SqlSessionFactory sqlSessionFactory;
	@Before  // 在所有测试代码执行之前执行该方法
	public void before() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
				
		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}
	
	@Test
	public void testFindUserByUsername() {	
		// 通过会话工厂获取SqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();
		List<User> list = sqlSession.selectList("test.findUserByName","小明");
		System.out.println(list);
		// 关闭连接
		sqlSession.close();	
	}

测试结果

image-20210503111659205

3>添加用户信息
普通方式控制

user.xml配置

	<!-- 3.添加用户信息 -->
 	<!-- 
 	   insert 代表插入语句的片段
 	   #{} 中获取的是输入参数中的属性  也就是User中的属性
 	   #{username} 就是获取User对象中的username属性的值
 	 -->
 	<insert id="insertUser" parameterType="com.noob.mybatis.model.User">
 		insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
 	</insert>

编写测试代码

public class UserTest {

	// 优化测试,将公共代码提取出来
	SqlSessionFactory sqlSessionFactory;
	@Before  // 在所有测试代码执行之前执行该方法
	public void before() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
				
		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}
	
	@Test
	public void testInsertUser() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 创建要添加的用户信息
		User user = new User();
		user.setSex("2");
		user.setUsername("狄仁杰");
		user.setBirthday(new Date());
		user.setAddress("敌方水晶");
		// 通过sqlSession添加用户信息
		sqlSession.insert("test.insertUser",user);
		// 查阅当前插入用户的id
		System.out.println("当前插入用户id:"+user.getId());
		// 提交事务、关闭连接(数据库事务默认提交方式是关闭的因此需要手动提交数据)
		sqlSession.commit();
		sqlSession.close();
	}
}

测试结果

image-20210503111741211

自增主键的返回

在user.xml中配置如下,只需要在插入数据的时候设置一个属性,随后测试的时候返回该user对象的id值进行验证即可

	<!-- 3.添加用户信息 -->
 	<!-- 
 	   insert 代表插入语句的片段
 	   #{} 中获取的是输入参数中的属性  也就是User中的属性
 	   #{username} 就是获取User对象中的username属性的值
 	 -->
 	<insert id="insertUser" parameterType="com.noob.mybatis.model.User">
 		<!-- 
 			selectKey 将插入数据库的主键返回到指定的对象中
 			keyProperty将查询到的主键设置到parameterType对象的哪个属性中 
 			order  执行insert语句之前还是之后
 			resultType :select LAST_INSERT_ID()这个的返回结果类型
 		 -->
 		<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
 			select LAST_INSERT_ID()
 		</selectKey>
 		insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
 	</insert>

测试代码

测试结果

4>删除用户信息

user.xml配置

	<!-- 4.删除用户信息 -->
 	<delete id="deleteUser" parameterType="java.lang.Integer">
 		delete from user where id=#{id}
 	</delete>

编写测试代码

	@Test
	public void testDeleteUser() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过sqlSession执行删除数据
		sqlSession.update("test.deleteUser", 30);
		// 提交事务、释放连接
		sqlSession.commit();
		sqlSession.close();
	}

测试结果

5>修改用户信息

user.xml配置

	<!-- 5.更新用户信息 -->
 	<update id="updateUser" parameterType="com.noob.mybatis.model.User">
 		update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
 	</update>

编写测试代码

	@Test
	public void testUpdateUser() {
		/**
		 * 需要注意的是此处测试是直接创建一个新的User对象,通过User对象
		 * id进行修改操作,一般情况下是先根据id查找相关用户再进行修改
		 */
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 创建要修改的用户信息(通过指定的用户id修改数据)
		User user = new User();
		user.setId(32);
		user.setUsername("小傻子");
		user.setAddress("王者峡谷");
		// 通过sqlSession修改用户信息
		sqlSession.update("test.updateUser",user);
		// 提交事务、关闭连接
		sqlSession.commit();
		sqlSession.close();
	}

测试结果

【3】MyBatis开发DAO的方法

原始DAO开发

开发步骤:

原始dao开发:需要定义dao接口和相应的dao实现类
在上述入门程序的案例基础上完成dao程序开发(参考上述案例的配置文件)
a.定义dao接口和dao实现类
b.编写测试代码进行测试
c.总结原始dao开发的不足之处

a.定义dao接口和dao实现类

UserDAO.java:
public interface UserDAO {
	public void addUser(User user);
	public void updateUser(User user);
	public void deleteUser(int id);
	public User findUserById(int id);
}
UserDAOImpl.java:
public class UserDAOImpl implements UserDAO{
	
	private SqlSessionFactory sqlSessionFactory;
	
	public UserDAOImpl(SqlSessionFactory sqlSessionFactory) {
		this.sqlSessionFactory = sqlSessionFactory;
	}

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

	@Override
	public void updateUser(User user) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		sqlSession.update("test.updateUser", user);
		sqlSession.commit();
		sqlSession.close();
	}

	@Override
	public void deleteUser(int id) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		sqlSession.update("test.deleteUser", id);
		sqlSession.commit();
		sqlSession.close();
	}

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

b.编写测试代码进行测试

UserDAOTest.java:
public class UserDAOTest {
 	// 定义SqlSessionFactory 	
private SqlSessionFactory sqlSessionFactory;

	@Before // 在所有测试代码执行之前执行该方法
	public void before() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);

		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void test() {
		// 创建UserDAO实现类对象进行测试
		UserDAO userDAO = new UserDAOImpl(sqlSessionFactory);
		User findUser = userDAO.findUserById(25);
		System.out.println(findUser);
	}
}

c.总结原始dao开发的不足之处

  • dao接口实现类方法中存在大量的模板方法 ,如果能把这些模板方法提取出来能够减少程序员的工作量

  • 调用sqlSession方法将Statement的id进行硬编码

Mapper代理方法(重点)

开发步骤:
Mapper代理方法开发:程序员只需要定义mapper接口(相当于dao接口)
程序员需要编写mapper.xml映射文件,且在编写mapper接口(dao接口)的时候需要遵循一些开发规范,mapper可以自动生成mapper接口实现类代理对象
遵循以下定义规范:
	a.在mapper.xml中namespace 等于mapper接口地址
	b.mapper.java接口中的方法名和mapper.xml中的statement的id必须一致
	c.mapper.java接口的方法返回值类型和mapper.xml的resultType的类型一致
	d.mapper.java接口的方法的形参和mapper.xml中的parameterType的类型一致
总结:namespace有特殊含义等价于mapper接口地址,且mapper.java接口中的方法名、返回值类型、形参必须与mapper.xml中定义的内容一一对应
主要步骤分析:
	a.编写mapper.java文件
	b.配置映射文件mapper.xml遵循上述规范
	c.通过sqlMapConfig.xml加载映射配置文件
	d.编写测试代码进行测试

a.编写mapper.java文件

Mapper.java:
public interface Mapper {
	// 此处简单定义两个方法进行测试
	public void addUser(User user);
	public User findUserById(int id);
}

b.配置映射文件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:代表命名空间,此处mapper标识访问地址
   -->
 <mapper namespace="com.noob.mybatis.b_mapper.UserMapper">
 	<!-- 定义添加用户相关的信息 -->
 	<insert id="addUser" parameterType="com.noob.mybatis.model.User">
 		<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
 			select LAST_INSERT_ID()
 		</selectKey>
		insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) 	
 	</insert>
 	
 	<!-- 定义根据id查找用户信息 -->
 	<select id="findUserById" parameterType="java.lang.Integer" resultType="User">
 		select * from user where id=#{id}
 	</select>
 </mapper>

c.通过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>

	<!-- 这是数据源的配置 不需要记 和Spring整合之后 environment都将废弃 -->
	<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://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>
	
	<!-- 加载映射文件的多种方式 -->
	<mappers>
		<!-- 
			方式1:通过resource一次加载一个文件 
			<mapper resource=""/>
		-->
		<mapper resource="sqlmap/user.xml"/>
		<!-- 
			方式2:遵循mapper定义规范
			通过mapper接口加载单个配置文件
			此种方式加载数据的前提是:映射文件与接口同名且必须在同一个包下
			通过指定的包与接口名结合进行加载
			<mapper class=""></mapper>
		 -->
		 <mapper class="com.noob.mybatis.b_mapper.UserMapper"></mapper>
		 <!-- 
		 	方式3:通过package进行批量加载
		 	<package name="指定包名"></package> 
		 	从而实现批量加载hiding包下的所有映射文件
		 -->
		 <!-- <package name="com.noob.mybatis.b_mapper"></package> -->
	</mappers>
	
</configuration>

d.编写测试代码进行测试

UserMapperTest.java:
public class UserMapperTest {

	// 优化测试,将公共代码提取出来
	SqlSessionFactory sqlSessionFactory;

	@Before // 在所有测试代码执行之前执行该方法
	public void before() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);

		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void testAddUser() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		// 创建要添加的用户信息(通过指定的用户id修改数据)
		User user = new User();
		user.setUsername("小傻子");
		user.setAddress("王者峡谷");
		user.setBirthday(new Date());
		user.setSex("2");
		// 通过UserMapper对象执行操作
		userMapper.addUser(user);
		// 提交事务、释放连接
		sqlSession.commit();
		sqlSession.close();
	}

	@Test
	public void testFindUserById() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		// 根据id查找用户信息
		User findUser = userMapper.findUserById(25);
		System.out.println("查找到的用户:"+findUser);
		// 关闭连接
		sqlSession.close();
	}
}

测试结果分析:

添加用户测试结果:

根据id查找用户测试结果:

【4】SqlMapConfig.xml

​ MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置(settings)和属性(properties)信息。文档的顶层结构如下:

​ 可通过官方文档进一步了解相关的配置:http://www.mybatis.org/mybatis-3/zh/configuration.html

	configuration 配置
     -   properties 属性
     -   settings 设置
     -   typeAliases 类型别名
     -   typeHandlers 类型处理器
     -   objectFactory 对象工厂
     -   plugins 插件
     -   environments 环境
            *  environment 环境变量
                >  transactionManager 事务管理器
                >  dataSource 数据源
     -   databaseIdProvider 数据库厂商标识
     -   mappers 映射器
在配置相关属性的时候必须注意属性配置的先后顺序,如果打乱顺序进行配置,则相应的配置文件会报错,需要注意这点

properties属性

​ 如果属性在多处进行了配置,那么 MyBatis 将按照下面的顺序来加载:

a.在 properties元素体内指定的属性首先被读取 
b.然后根据 properties元素中的resource属性读取类路径下属性文件或根据url属性指定的路径读取属性文件,并覆盖已读取的同名属性
c.最后读取作为方法参数传递的属性,并覆盖已读取的同名属性
<configuration>
   <!-- 通过properties加载配置文件 -->
   <properties resource="db.properties">
      <property name="username" value="wkaka"/>
   </properties>
   
   <!-- 这是数据源的配置 不需要记 和Spring整合之后 environment都将废弃 -->
   <environments default="development">
      <environment id="development">
         <transactionManager type="JDBC" />
         <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driver}" />
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
         </dataSource>
      </environment>
   </environments>
</configuration>

​ 分析:在properties元素体内的property属性先被读取,由于加载了配置文件db.properties,相应地通过resource属性读取属性文件中的内容会覆盖掉同名属性定义的内容,虽然用户名显示定义为“wkaka”,但后续被属性文件中读取的内容覆盖,因此可以显示正常的数据信息

settings

​ settings是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。下表描述了设置中各项的意图、默认值等

typeAliases别名

​ 类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:在Mapper.xml定义的很多的statement 输出结果映射类型可以使用别名减少名称的冗余

1>MyBatis默认支持的别名
别名映射类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

举例说明:java.lang.Integer等价于int

	<!-- 根据id查找用户信息 -->
	<select id="findUserById" parameterType="java.lang.Integer" resultType="com.noob.mybatis.model.User">
 		select * from user where id=#{id}
 	</select>

	# 改为如下使用系统支持的别名
	<!-- 根据id查找用户信息 -->
	<select id="findUserById" parameterType="int" resultType="com.noob.mybatis.model.User">
 		select * from user where id=#{id}
 	</select>
2>自定义别名

自定义别名可分为:定义单个别名和批量定义别名,案例列举如下

在SqlMapConfig.xml中配置如下内容:

	<!-- 3.定义别名的两种方式 -->
	<typeAliases>
		<!-- 方式1:定义单个别名 -->
		<!-- <typeAlias type="com.noob.mybatis.model.User" alias="user"/> -->
		<!-- 方式2:批量定义别名 -->
		<package name="com.noob.mybatis.model"/>
		<!-- 批量定义别名:对应的别名即为类名或者类型首字母小写(eg:User、user) -->
	</typeAliases>
  • 原始用法:
	<!-- 根据id查找用户信息 -->
	<select id="findUserById" parameterType="int" resultType="com.noob.mybatis.model.User">
 		select * from user where id=#{id}
 	</select>
  • 使用自定义别名:
	<!-- 根据id查找用户信息:使用别名方式1 -->
	<select id="findUserById" parameterType="int" resultType="user">
 		select * from user where id=#{id}
 	</select>
 	
 	<!-- 根据id查找用户信息:使用别名方式2 -->
	<select id="findUserById" parameterType="int" resultType="User">
 		select * from user where id=#{id}
 	</select>

​ 需要注意的是,如果是定义个别名则需要指定的别名才生效,如果是批量定义别名,别名即为类名或者是类名首字母小写

environments配置环境

​ MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者共享相同 Schema 的多个生产数据库, 想使用相同的 SQL 映射

​ 注意:尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

注意这里的关键点:

  • 默认的环境 ID(比如:default="development")

  • 每个 environment 元素定义的环境 ID(比如:id="development")

  • 事务管理器的配置(比如:type="JDBC")

  • 数据源的配置(比如:type="POOLED")

    默认的环境和环境 ID 是自解释的,因此一目了然。可以对环境随意命名,但一定要保证默认的环境 ID 要匹配其中一个环境 ID

Mapper映射配置

Mapper映射配置由多种方式

a.通过resource加载单个配置文件
b.通过mapper接口加载单个mapper
c.批量加载mapper(推荐)

参考配置

	<!-- 加载映射文件的多种方式 -->
	<mappers>
		<!-- 
			方式1:通过resource一次加载一个文件 
			<mapper resource=""/>
		-->
		<mapper resource="sqlmap/user.xml"/>
		<!-- 
			方式2:遵循mapper定义规范
			通过mapper接口加载单个配置文件
			此种方式加载数据的前提是:映射文件与接口同名且必须在同一个包下
			通过指定的包与接口名结合进行加载
			<mapper class=""></mapper>
		 -->
		<!--  <mapper class="com.noob.mybatis.b_mapper.UserMapper"></mapper> -->
		 <!-- 
		 	方式3:通过package进行批量加载
		 	<package name="指定包名"></package> 
		 	从而实现批量加载hiding包下的所有映射文件
		 -->
		 <package name="com.noob.mybatis.b_mapper"></package>
		 <package name="com.noob.mybatis.c_mapper"></package>
	</mappers>

【5】输入映射和输出映射

传递POJO的包装对象:输入映射

开发步骤:
a.需求分析
b.根据需求定义包装的POJO对象
c.根据需求定义mapper.java接口和相应的mapper.xml配置文件
d.在sqlMapConfig.xml中加载配置文件并编写测试代码进行测试

a.需求分析

​ 完成用户信息的综合查询 需要传入的查询条件比较复杂(可能包含用户信息,或者其他相关的详细信息, 比如与用户相关的商品信息、订单信息等)

b.根据需求定义包装的POJO对象

​ POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通的JavaBeans,是为了避免和EJB混淆所创造的简称

  • 查询到结果后把查询到的复杂数据进行封装
UserCustom.java:
public class UserCustom extends User{
	
	// 拥有User的所有相关属性,亦可定义扩展的用户信息
	
	// eg:定义订单信息
	
	// eg:定义商品信息

}
  • 查询条件比较复杂:对查询条件进行封装
UserQueryVO.java:
public class UserQueryVO {
	
	// 此处定义所需要查询的条件
	private UserCustom userCustom;

	public UserCustom getUserCustom() {
		return userCustom;
	}

	public void setUserCustom(UserCustom userCustom) {
		this.userCustom = userCustom;
	}
	
	// 还可封装其他的查询条件:例如订单信息、商品信息等内容

}

c.根据需求定义mapper.java接口和相应的mapper.xml配置文件

  • UserCustomMapper.java
public interface UserCustomMapper {
	// 定义查询方法
	public List<UserCustom> findUserByUnion(UserQueryVO userQueryVO)throws Exception;
}
  • UserCustomMapper.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:代表命名空间,此处mapper标识访问地址
   -->
 <mapper namespace="com.noob.mybatis.c_mapper.UserCustomMapper">
   <!-- 定义根据综合条件查找用户信息 -->
   <select id="findUserByUnion" parameterType="UserQueryVO" resultType="UserCustom">
      select * from user where user.sex=#{userCustom.sex} and user.username like '%${userCustom.username}%'
    <!-- 分析:#{userCustom.sex}是指获取UserQueryVO中userCustom属性的sex属性值 -->
   </select>
	</mapper>

d.在sqlMapConfig.xml中加载配置文件并编写测试代码进行测试

加载配置文件:
 <package name="com.noob.mybatis.c_mapper"></package>
测试代码:
UserCustomMapperTest.java:
public class UserCustomMapperTest {

	// 优化测试,将公共代码提取出来
	SqlSessionFactory sqlSessionFactory;

	@Before // 在所有测试代码执行之前执行该方法
	public void before() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);

		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void testFindUserByUnion() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserCustomMapper userCustomMapper = sqlSession.getMapper(UserCustomMapper.class);
		// 定义查找条件
		UserQueryVO userQueryVO = new UserQueryVO();
		UserCustom userCustom = new UserCustom();
		userCustom.setSex("1");
		userCustom.setUsername("小明");
		userQueryVO.setUserCustom(userCustom);
		// 查找内容
		List<UserCustom> list = userCustomMapper.findUserByUnion(userQueryVO);
		System.out.println(list);
		// 关闭连接
		sqlSession.close();
	}
}

测试结果:

输出映射

1>ResultType

ResultType就是把查询到的结果封装为指定的对象,然后输出

需要查询的列名和POJO中的属性必须一致,否则封装不成功,从而导致查询不到数据

2>输出简单类型

需求:查询用户的总数(在上述案例的基础上完成)

在UserCustomMapper.xml中配置

	<!-- 查找满足指定条件的用户个数 -->
 	<select id="getUserCountByUnion" parameterType="UserQueryVO" resultType="int">
 		select count(*) from user where user.sex=#{userCustom.sex} and user.username like '%${userCustom.username}%'
 	</select>

在UserCustomMapper.java中添加方法

	// 查询符合指定条件的用户个数
	public int getUserCountByUnion(UserQueryVO userQueryVO);

编写测试代码进行测试

	@Test
	public void testFindUserByUnion() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserCustomMapper userCustomMapper = sqlSession.getMapper(UserCustomMapper.class);
		// 定义查找条件
		UserQueryVO userQueryVO = new UserQueryVO();
		UserCustom userCustom = new UserCustom();
		userCustom.setSex("1");
		userCustom.setUsername("小明");
		userQueryVO.setUserCustom(userCustom);
		// 查找内容
		List<UserCustom> list = userCustomMapper.findUserByUnion(userQueryVO);
		System.out.println(list);
		// 关闭连接
		sqlSession.close();
	}

测试结果:

3>输出POJO对象和POJO列表

需求:查询用户的总数(在上述案例的基础上完成)

在UserCustomMapper.xml中配置

	<!-- 根据用户id查找用户信息 -->
 	<select id="findUserById" parameterType="int" resultType="User">
 		select * from user where id=#{id}
 	</select>
 	
 	<!-- 根据用户姓名关键字查找用户信息(返回的可能是一个列表) -->
 	<select id="findUserByName" parameterType="UserQueryVO" resultType="User">
 		select * from user where user.username like '%${userCustom.username}%'
 	</select>

在UserCustomMapper.java中添加方法

	// 根据用户id查找用户信息 
 	public User findUserById(int id);
 	
 	// 根据用户姓名关键字查找用户信息(返回的可能是一个列表)
 	public List<User> findUserByName(UserQueryVO userQueryVO);

编写测试代码进行测试

	@Test
	public void testFindUserById() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserCustomMapper userCustomMapper = sqlSession.getMapper(UserCustomMapper.class);
		User findUser = userCustomMapper.findUserById(25);
		System.out.println(findUser);
		// 关闭连接
		sqlSession.close();
	}
	
	@Test
	public void testFindUserByName() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserCustomMapper userCustomMapper = sqlSession.getMapper(UserCustomMapper.class);
		// 定义查找条件
		UserQueryVO userQueryVO = new UserQueryVO();
		UserCustom userCustom = new UserCustom();
		userCustom.setUsername("小明");
		userQueryVO.setUserCustom(userCustom);
		// 查找内容
		List<User> list = userCustomMapper.findUserByName(userQueryVO);
		System.out.println(list);
		// 关闭连接
		sqlSession.close();
	}

测试结果:

  • testFindUserById测试结果:

image-20210503114049116

  • testFindUserByName测试结果:

image-20210503114105566

4>ResultMap

​ 问题分析:在实际开发中针对多表查询,为了区分相同列名而导致查询结果的二义性,通常会为查询出来的列名起别名,但如果是通过resultType进行映射无法匹配相应的数据,导致数据查询失败

​ 测试结果:虽然数据能够查询,但结果且无法正确映射到输出结果中,导致查询失败

​ 因此为了解决上述问题,提出解决方案:如果查询出的列名和POJO属性不一致,可以通过定义一个resultMap定义类名和POJO进行映射完成查询。

在UserCustomMapper.xml中配置

	<!-- 配置映射 -->
 	<resultMap type="user" id="userResultMap">
 		<id column="_id" property="id"/>
 		<result column="_username" property="username"/>
 		<result column="_birthday" property="birthday"/>
 		<result column="_sex" property="sex"/>
 		<result column="_address" property="address"/>
 	</resultMap>
 	
 	<!-- 通过resultMap映射查找指定条件的用户信息 -->
 	<select id="findUserListByResultMap" parameterType="UserQueryVO" resultMap="userResultMap">
 		select id _id,username _username,birthday _birthday,sex _sex,address _address
 		from user 
 		where user.username like '%${userCustom.username}%'
 	</select>

在UserCustomMapper.java中添加方法

//通过ResultMap映射根据用户名称关键字查找用户信息
public List<User> findUserListByResultMap(UserQueryVO userQueryVO);

编写测试代码进行测试

	@Test
	public void testFindUserListByResultMap() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserCustomMapper userCustomMapper = sqlSession.getMapper(UserCustomMapper.class);
		// 定义查找条件
		UserQueryVO userQueryVO = new UserQueryVO();
		UserCustom userCustom = new UserCustom();
		userCustom.setUsername("小明");
		userQueryVO.setUserCustom(userCustom);
		// 查找内容
		List<User> list = userCustomMapper.findUserListByResultMap(userQueryVO);
		System.out.println(list);
		// 关闭连接
		sqlSession.close();
	}

测试结果:数据正常显示

【6】动态sql

动态sql基础

1>什么是动态sql?

​ MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

​ 虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形

2>简单案例分析

​ 需求分析:用户信息综合查询列表和用户信息查询列表总数这两个Statement的定义使用动态sql完成

Mapper.xml中定义

Mapper.java中添加方法

	// 定义查询方法
	public List<UserCustom> findUserByUnion(UserQueryVO userQueryVO);
	
	// 查询符合指定条件的用户个数
	public int getUserCountByUnion(UserQueryVO userQueryVO);

在sqlMapConfig.xml中加载配置文件并编写测试代码进行测试

(此处使用的是上述案例中测试的代码,结果正常显示不再赘述)

sql片段

需求分析:针对上方的简单案例分析,可以把公共的代码片段抽离成一个独立的sql片段,从而简化开发

​ Mapper.xml中替换成如下的内容,其余基本的数据保持不变参考上述案例进行测试,结果正常显示,此处不再赘述

	<!-- a.定义sql片段 -->
 	<sql id="query_user_where">
 		<if test="userCustom!=null">
 			<if test="userCustom.sex!=null">
 				and user.sex=#{userCustom.sex}
 			</if>
 			<if test="userCustom.username!=null">
				and user.username like '%${userCustom.username}%'
 			</if>
 		</if>
 	</sql>
 	
 	<!-- b.在指定的位置引用sql片段 -->
 	<!-- 定义根据综合条件查找用户信息(动态sql完成拼接) -->
 	<select id="findUserByUnion" parameterType="UserQueryVO" resultType="UserCustom">
 		select * from user 
 		<where>
			<include refid="query_user_where"></include> 			
 		</where>
 	</select>

foreach遍历

需求分析:查询指定用户的id为 1 、10 、16 的用户信息

查询语句分析如下:

select  * from user where (id=1 or id=10 or id =16)
Select * from user where id in(1,10,16)

UserQueryVO中添加查询条件(即根据id列表进行查找)

public class UserQueryVO {
	
	private List<Integer> ids;
	
	public List<Integer> getIds() {
		return ids;
	}

  // 指定List<Integer> ids参数列表进行查询
	public void setIds(List<Integer> ids) {
		this.ids = ids;
	}
	
	// 还可封装其他的查询条件:例如订单信息、商品信息等内容
}

指定mapper.xml中添加配置如下所示

	<!-- a.定义sql片段 -->
 	<sql id="query_user_foreach">
 		<if test="ids!=null">
 			<!--  
 				说明:
 				foreach表示对collection数据进行迭代
 				collection:要迭代的集合属性
 				item:在迭代遍历的过程中生成的对象名称
 				open:开始遍历id是拼接的字符串
 				close:遍历结束后拼接的字符串
 				separator:每次遍历两个对象中间需要拼接的内容
 			-->
 			<!-- 方式1:and (id=1 or id=10 or id=16) -->
 			<!-- 
	 			<foreach collection="ids" item="user_id" open="and (" close=")" separator="or"> 
	 				id=#{user_id}
	 			</foreach> 
 			-->
 			<!-- 方式2:id in (1,10,16) -->
 			<foreach collection="ids" item="user_id" open="id in (" close=")" separator=",">
 				#{user_id}
 			</foreach>
 		</if>
 	</sql>
 
 	<!-- b.通过指定的sql片段遍历用户id数据 -->
 	<select id="findUserListByIds" parameterType="UserQueryVO" resultType="UserCustom">
 		select * from user 
 		<where>
			<include refid="query_user_foreach"></include> 			
 		</where>
 	</select>

指定mapper.java中添加方法

// 根据提供的用户id列表查找用户信息 
public List<UserCustom> findUserListByIds(UserQueryVO userQueryVO);

在sqlMapConfig.xml中加载配置文件并编写测试代码进行测试

	@Test
	public void testFindUserListByIds() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserCustomMapper userCustomMapper = sqlSession.getMapper(UserCustomMapper.class);
		// 定义查找条件
		UserQueryVO userQueryVO = new UserQueryVO();
		List<Integer> ids = new ArrayList<>();
		ids.add(1);
		ids.add(10);
		ids.add(16);
		userQueryVO.setIds(ids);
		// 查找内容
		List<UserCustom> list = userCustomMapper.findUserListByIds(userQueryVO);
		System.out.println(list);
		// 关闭连接
		sqlSession.close();
	}

测试结果:

【7】MyBatis高级映射

​ MyBatis高级映射通过实现案例进行分析,分为:一对一查询、一对多查询、多对多查询 ,通过现有的数据模型进行分析(新建工程,完成mybatis的基本配置随后进行相关测试即可)

📌一对一查询

1>需求分析

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

Sql语句:
select
  orders.*,
  user.username,
  user.sex,
  user.address
from  
   orders,
   user
where orders.user_id=user.id
2>创建POJO对象

Orders.java:

public class Orders {
	private int id;
	private int user_id;
	private String number;
	private Date createtime;
	private String note;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getUser_id() {
		return user_id;
	}
	public void setUser_id(int user_id) {
		this.user_id = user_id;
	}
	public String getNumber() {
		return number;
	}
	public void setNumber(String number) {
		this.number = number;
	}
	public Date getCreatetime() {
		return createtime;
	}
	public void setCreatetime(Date createtime) {
		this.createtime = createtime;
	}
	public String getNote() {
		return note;
	}
	public void setNote(String note) {
		this.note = note;
	}
}
3>配置mapper.java和mapper.xml
方式1:ResultType

​ 通过创建OrderCustom继承Orders并扩展其他属性实现查询结果映射

创建POJO查询对象

OrderCustom.java:
public class OrderCustom extends Orders{
	/**
	 *  此处创建查询对象,通过继承获取某个对象的所有属性,
	 *  此外还可自定义自己的属性,并且提供相应的Getter、Setter
	 */
	private String username;
	private String sex;
	private String address;
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
}

配置mapper.xml

OrderCustomMapper.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.noob.mybatis.a_one_to_one.OrderCustomMapper">
 	<!-- a.定义通过ResultType实现一对一查询-->
 	<select id="findOrderCustomByResultType" resultType="OrderCustom">
		select
		  orders.*,
		  user.username,
		  user.sex,
		  user.address
		from  
		   orders,
		   user
		where orders.user_id=user.id
 	</select>
 </mapper>

编写mapper.java接口

OrderCustomMapper.java:
public interface OrderCustomMapper {
	// 根据ResultType实现一对一查询
	public List<OrderCustom> findOrderCustomByResultType();
}

通过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>

	<!-- 通过properties加载配置文件 -->
	<properties resource="db.properties"></properties>
	
	<!-- 定义别名减少命名冗余 -->
	<typeAliases>
		<!-- 批量定义别名 -->
		<package name="com.noob.mybatis.model"/>
	</typeAliases>
	
	<!-- 这是数据源的配置 不需要记 和Spring整合之后 environment都将废弃 -->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	
	<!-- 批量加载映射文件 -->
	<mappers>
		<package name="com.noob.mybatis.a_one_to_one"></package>
	</mappers>

</configuration>

编写代码进行测试

OrderCustomerTest.java:
public class OrderCustomerTest {

	// 优化测试,将公共代码提取出来
	SqlSessionFactory sqlSessionFactory;

	@Before // 在所有测试代码执行之前执行该方法
	public void before() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);

		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void testFindOrderCustomByResultType() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
		List<OrderCustom> list = orderCustomMapper.findOrderCustomByResultType();
		list.forEach(System.out::println);
		// 关闭连接
		sqlSession.close();
	}

}

​ 测试结果:利用debug模式进行测试,在指定位置设置断点,右键选择Run As -- Junit Test进行调试分析数据即可

方式2:ResultMap

​ 分析思路:使用resultMap将查询的结果中的订单信息映射到Orders对象中,在Orders类中添加User属性,将用户信息映射到Orders对象中的user属性中(此处为了方便测试,直接在原有代码基础上进行修改)

创建User类,在Orders类中添加User属性

User.java:
public class User {
	private int id;
	private String username;
	private Date birthday;
	private String sex;
	private String address;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public Date getBirthday() {
		return birthday;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
}
Order.java:
public class Orders {
	private int id;
	private int user_id;
	private String number;
	private Date createtime;
	private String note;
	private User user;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getUser_id() {
		return user_id;
	}
	public void setUser_id(int user_id) {
		this.user_id = user_id;
	}
	public String getNumber() {
		return number;
	}
	public void setNumber(String number) {
		this.number = number;
	}
	public Date getCreatetime() {
		return createtime;
	}
	public void setCreatetime(Date createtime) {
		this.createtime = createtime;
	}
	public String getNote() {
		return note;
	}
	public void setNote(String note) {
		this.note = note;
	}
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
}

配置mapper.xml

在配置中添加以下内容:

	<!-- b.定义通过ResultMap实现一对一查询-->
 	<resultMap type="Orders" id="OrderCustomResultMap">
 		<!-- 配置基本属性 -->
 		<id column="id" property="id"/>
 		<result column="user_id" property="user_id"/>
 		<result column="number" property="number"/>
 		<result column="createtime" property="createtime"/>
 		<result column="note" property="note"/>
 		<!-- 配置对象属性(即配置映射的关联的用户信息) -->
 		<!-- 
 			一对一查询配置关联属性使用association标签
 			<association property="" javaType="">
 		 		<id column="" property=""></id>
 		 		<result column="" property=""/>
 		 	</association>
 		 	association:用于映射关系查询,查询单个对象的信息(一对一查询的映射)
 		 	property:此处是指要将关联查询的用户信息映射到Orders的哪个属性(即user)
 		 	javaType:要映射的对象的全名称(即com.noob.mybatis.model.User)
 		 	其余内容的定义则为对应对象的普通属性进行操作即可
 		 	(没有获取到的属性则不需要定义映射)
 		 -->
 		 <association property="user" javaType="com.noob.mybatis.model.User">
 		 	<!-- 此处的id是唯一标识“user”对象的属性 -->
 		 	<id column="user_id" property="id"/>
 		 	<result column="username" property="username"/>
 		 	<result column="sex" property="sex"/>
 		 	<result column="address" property="address"/>
 		 </association>
 	</resultMap>
 	<select id="findOrderCustomByResultMap" resultMap="OrderCustomResultMap">
		select
		  orders.*,
		  user.username,
		  user.sex,
		  user.address
		from  
		   orders,
		   user
		where orders.user_id=user.id
 	</select>

编写mapper.java接口

在接口中添加以下方法:

	// 根据ResultMap实现一对一查询
	public List<Orders> findOrderCustomByResultMap();

通过sqlMapConfig.xml加载映射文件并编写代码进行测试

	@Test
	public void testFindOrderCustomByResultMap() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
		List<Orders> list = orderCustomMapper.findOrderCustomByResultMap();
		list.forEach(System.out::println);
		// 关闭连接
		sqlSession.close();
	}

测试结果:

两种方式结果详细对比

方式1:

方式2:

📌一对多查询

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

数据库查询结果:

​ 分析:如果使用resultType将上方的查询结果直接映射到POJO对象中,其中订单的信息就是重复的。如果要求按照结果分析,本来应该有两个订单,但是查询出有四个结果 即在orders出现重复的记录,为了解决实际的问题,可以再订单中通过resultMap进行映射,也就是在orders类中添加 List<Orderdetail> orderdetails;从而解决问题

​ 在上述测试代码中进行整改,从而进行测试

2>POJO对象的设计

创建OrderDetail类

OrderDetail.java:
public class OrderDetail {
	private int id;
	private int orders_id;
	private int items_id;
	private int items_num;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getOrders_id() {
		return orders_id;
	}
	public void setOrders_id(int orders_id) {
		this.orders_id = orders_id;
	}
	public int getItems_id() {
		return items_id;
	}
	public void setItems_id(int items_id) {
		this.items_id = items_id;
	}
	public int getItems_num() {
		return items_num;
	}
	public void setItems_num(int items_num) {
		this.items_num = items_num;
	}
}

创建Orders类,在Orders类中引入订单明细列表属性

Orders.java:
public class Orders {
	private int id;
	private int user_id;
	private String number;
	private Date createtime;
	private String note;
	private User user;
  // List<OrderDetail>属性
	private List<OrderDetail> orderDetail_list;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getUser_id() {
		return user_id;
	}
	public void setUser_id(int user_id) {
		this.user_id = user_id;
	}
	public String getNumber() {
		return number;
	}
	public void setNumber(String number) {
		this.number = number;
	}
	public Date getCreatetime() {
		return createtime;
	}
	public void setCreatetime(Date createtime) {
		this.createtime = createtime;
	}
	public String getNote() {
		return note;
	}
	public void setNote(String note) {
		this.note = note;
	}
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
	public List<OrderDetail> getOrderDetail_list() {
		return orderDetail_list;
	}
	public void setOrderDetail_list(List<OrderDetail> orderDetail_list) {
		this.orderDetail_list = orderDetail_list;
	}
}
3>配置mapper.xml和mapper.java进行测试

配置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.noob.mybatis.b_one_to_many.OrderDetailMapper">
 
 	<!-- 此处是参考上一个一对一查询的案例中配置的映射 -->
 	<resultMap type="Orders" id="OrderCustomResultMap">
 		<!-- 配置基本属性 -->
 		<id column="id" property="id"/>
 		<result column="user_id" property="user_id"/>
 		<result column="number" property="number"/>
 		<result column="createtime" property="createtime"/>
 		<result column="note" property="note"/>
 		<!-- 配置对象属性(即配置映射的关联的用户信息) -->
 		 <association property="user" javaType="com.noob.mybatis.model.User">
 		 	<!-- 此处的id是唯一标识“user”对象的属性 -->
 		 	<id column="user_id" property="id"/>
 		 	<result column="username" property="username"/>
 		 	<result column="sex" property="sex"/>
 		 	<result column="address" property="address"/>
 		 </association>
 	</resultMap>
 	
 	<!-- 定义通过ResultMap实现一对多查询-->
 	<resultMap type="Orders" id="OrderDetailResultMap" extends="OrderCustomResultMap">
 		<!-- 基本数据的配置(订单信息关联的顾客信息已有现成配置可参考,通过extends继承引用即可)-->
 		<!-- 对订单关联的订单明细进行配置 -->
 		<!-- 
 			<collection property="" ofType="">
	 		 	<id column="" property=""/>
	 		 	<result column="" property=""/>
	 		 </collection>
 			collection:一对多查询使用collection进行映射(此处一条订单对应多个订单明细)
 			property:将关联查询到的多条记录映射到集合对象中(此处为Orders的orderDetail_list属性)
 			ofType:指定映射到list集合属性中的POJO类型
 		 -->
 		 <collection property="orderDetail_list" ofType="com.noob.mybatis.model.OrderDetail">
 		 	<!-- 此处id用以唯一标识当前订单明细的属性 -->
 		 	<id column="orderdetail_id" property="id"/>
 		 	<result column="items_id" property="orders_id"/>
 		 	<result column="items_num" property="items_id"/>
 		 	<result column="orders_id" property="items_num"/>
 		 </collection>
 	</resultMap>
 	<select id="findOrderDetailByResultMap" resultMap="OrderDetailResultMap">
		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>
 </mapper>

编写Mapper.java接口

public interface OrderDetailMapper {	
	// 根据ResultMap实现一对多查询
	public List<Orders> findOrderDetailByResultMap();
}

在sqlMapConfig中加载配置文件并编写测试代码

	<!-- 批量加载映射文件 -->
	<mappers>
		<package name="com.noob.mybatis.a_one_to_one"></package>
		<package name="com.noob.mybatis.b_one_to_many"></package>
	</mappers>
	@Test
	public void testFindOrderCustomByResultMap() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
		List<Orders> list = orderCustomMapper.findOrderCustomByResultMap();
		list.forEach(System.out::println);
		// 关闭连接
		sqlSession.close();
	}

测试结果:

📌多对多查询

1>需求分析

​ 查询用户以及用户购买的商品信息

Sql语句分析:

​ 确定查询的主表:用户表

​ 关联表: 由于用户和商品没有直接的关联,通过订单和订单明细进行关联,关联表是orders,ordersdetail,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

​ 要求:此处查询数据只有一个用户 ,在这个用户中封装了四个商品信息

映射思路:
一个用户有多条订单信息,一条订单信息对应着多条订单明细,而每一条订单明细又对应着相应的商品详情
将所有的信息映射到用户中
在User类中 添加订单列表的属性private List<Orders> orders ;
在Orders类中 添加订单明细属性private List<Orderdetail> orderdetails;
在Orderdetails添加商品信息属性private Items items;
2>POJO对象的设计

​ 此处为了节省篇幅只给出相关属性的定义,而不做具体扩展叙述,需要提供相应的Getter、Setter构造器

创建Items类

public class Items {
	private int id;
	private String name;
	private float price;
	private String detail;
	private String pic;
	private Date createtime;
}

创建Orderdetail类,在Orderdetail添加商品信息属性

public class OrderDetail {
	private int id;
	private int orders_id;
	private int items_id;
	private int items_num;
  // 商品信息属性
	private Items items;
}

创建Orders类,在Orders类中 添加订单明细属性

public class Orders {
	private int id;
	private int user_id;
	private String number;
	private Date createtime;
	private String note;
  // 订单明细
  private List<OrderDetail> orderdetails;
}

创建User类,在User类中添加订单列表的属性

public class User implements Serializable{
	private int id;
	private String username;
	private Date birthday;
	private String sex;
	private String address;
  // 订单列表
	private List<Orders> orders;
}
3>配置mapper.xml和编写mapper.java接口并进行测试

配置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.noob.mybatis.c_many_to_many.UserDetailMapper">
 	
 	<!-- 定义通过ResultMap实现多对多查询-->
 	<resultMap type="User" id="UserDetailResultMap">
 		<!-- 配置User基本属性 -->
 		<id column="user_id" property="id"/>
 		<result column="username" property="username"/>
 		<result column="sex" property="sex"/>
 		<result column="address" property="address"/>
 		<!-- 配置User对应订单信息 -->
 		<collection property="orders" ofType="Orders">
 			<id column="id" property="id"/>
			<result column="user_id" property="user_id"/>
			<result column="number" property="number"/>
			<result column="createtime" property="createtime"/>
			<result column="note" property="note"/>
			<!-- 配置订单信息对应的订单明细 -->			
 			<collection property="orderdetails" ofType="OrderDetail">
 				<!-- 配置订单明细基本属性 -->
				<id column="orderdetail_id" property="id"/>
				<result column="orders_id" property="orders_id"/>
				<result column="items_id" property="items_id"/>
				<result column="items_num" property="items_num"/>
 				<!-- 配置订单明细对应的商品详情 -->
 				<association property="items" javaType="Items">
 					<!-- 配置商品详情 -->
 					<id column="items_id" property="id"/>
 					<result column="items_name" property="name"/>
 					<result column="items_price" property="price"/>
 					<result column="items_detail" property="detail"/>
 					<!-- 没有查询到的属性则不作定义 -->
 				</association>
 			</collection>
 		</collection>
 	</resultMap>
 	
 	<select id="findUserDetailByResultMap" resultMap="UserDetailResultMap">
		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>
 </mapper>

编写mapper.java接口

public interface UserDetailMapper {
	// 根据ResultMap实现多对多查询
	public List<User> findUserDetailByResultMap();
}

在sqlMapConfig.xml中加载配置文件并编写测试代码进行测试

	<!-- 批量加载映射文件 -->
	<mappers>
		<package name="com.noob.mybatis.a_one_to_one"></package>
		<package name="com.noob.mybatis.b_one_to_many"></package>
		<package name="com.noob.mybatis.c_many_to_many"></package>
	</mappers>
	@Test
	public void testFindUserDetailByResultMap() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		UserDetailMapper userDetailMapper = sqlSession.getMapper(UserDetailMapper.class);
		List<User> list = userDetailMapper.findUserDetailByResultMap();
		list.forEach(System.out::println);
		// 关闭连接
		sqlSession.close();
	}

测试结果:

【8】延迟加载

什么是延迟加载

​ ResultMap可以实现高级映射(使用association和collection实现一对一以及一对多映射)

​ 其中association和collection都具备延迟加载功能

需求分析:

​ 如果查询订单并且关联查询用户信息, 如果查询订单就满足需要当我们需要查询用户信息的是在去按需加载,把对用户的信息的查询不是立即查询,而是按需加载

​ 延迟加载:从单表查询 ,需要的时候再从关联表去查询,大大提高数据库的性能,因为查询单表要比查询关联表速度快

使用association延迟加载

开发步骤:
a.需求分析
b.配置mapper.xml和编写mapper.java接口
c.在sqlMapConfig.xml中加载配置文件并编写测试代码进行debug调试

a.需求分析

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

如果查询的订单已经满足条件,但并不需要查询用户,此时用户的信息就是按需加载

public class Orders {
	private int id;
	private int user_id;
	private String number;
	private Date createtime;
	private String note;
	private User user;
}

b.配置mapper.xml和编写mapper.java接口

分析:
1.执行mapper方法findOrderUserLazyLoading  内部调用  只会查询orders信息
2.在程序中遍历上一步查询的List<Orders> 
3.延迟加载  再去调用 相关的用户信息  才会执行findUserById进行延迟加载用户信息
  • 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.noob.mybatis.d_lazyloading.OrderUserMapper">
 	<!-- 查询订单关联查询用户(用户信息延迟加载) -->
 	<select id="findOrderUserLazyLoading" resultMap="OrderUserLazyLoadingResultMap">
		select * from orders
 	</select>
 	
 	<select id="fingUserById"  parameterType="int" resultType="User">
 		select * from user where id=#{value}
 	</select>
 	
 	<resultMap type="Orders" id="OrderUserLazyLoadingResultMap">
 		<!-- 配置基本属性 -->
 		<id column="id" property="id"/>
 		<result column="user_id" property="user_id"/>
 		<result column="number" property="number"/>
 		<result column="createtime" property="createtime"/>
 		<result column="note" property="note"/>
 	
 		 <association property="user" javaType="com.noob.mybatis.model.User" 
 		 select="com.noob.mybatis.d_lazyloading.OrderUserMapper.fingUserById" column="user_id">
 		 	<!-- 
 		 		此处通过select实现用户信息的延迟加载 :加载指定用户信息
 		 		column为延迟加载过程中传递的参数:传递user_id参数
			-->
 		 </association>
 	</resultMap>
 </mapper>
  • mapper.java:
public interface OrderUserMapper {
	// 使用association实现延迟加载
	public List<Orders> findOrderUserLazyLoading();
}

c.在sqlMapConfig.xml中加载配置文件并编写测试代码进行debug调试

	<!-- 配置settings属性实现延迟缓存 -->
	<settings>
		<!-- 打开延迟加载的开关 -->
		<setting name="lazyLoadingEnabled" value="true"/>
		<!-- 将积极加载改为消极加载和按需加载 -->
		<setting name="aggressiveLazyLoading" value="false"/>
		<!-- 开启二级缓存 -->
		<setting name="cacheEnabled" value="true"/>
	</settings>
	
	<mappers>
		<package name="com.noob.mybatis.a_one_to_one"></package>
		<package name="com.noob.mybatis.b_one_to_many"></package>
		<package name="com.noob.mybatis.c_many_to_many"></package>
		<package name="com.noob.mybatis.d_lazyloading"></package>
	</mappers>

测试代码:

	@Test
	public void testFindOrderUserLazyLoading() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 通过SqlSession对象获取UserMapper对象
		OrderUserMapper orderUserMapper = sqlSession.getMapper(OrderUserMapper.class);
		// 此处加载订单信息,设置断点进行测试
		List<Orders> list =orderUserMapper.findOrderUserLazyLoading();
		System.out.println(list);
		// 此处用户信息按需加载,执行查看结果
		for(Orders order:list) {
			// 获取用户信息进行分析
			User user = order.getUser();
			System.out.println(user);
		}
		// 关闭连接
		sqlSession.close();
	}

测试结果分析:

​ 执行到第一个断点时,list集合已查询出结果,但其对应的user为null(按需加载)

​ 当执行到第二个断点,依次获取每个orders中的user数据,一步步执行,数据依次显示,user用户信息按需加载

【9】查询缓存

查询缓存概念

什么是查询缓存

​ MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。

​ 默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环、依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行代码: <cache/>

​ MyBatis提供查询缓存用于减轻数据压力,提供数据库性能

MyBatis提供了两级缓存,一级缓存和二级缓存:

​ 一级缓存是SqlSession级别的缓存,在操作数据库需要构建SqlSession对象。在这个对象中有一个数据结构HashMap,用于存储缓存数据,不同的sqlSession之间的缓存数据是互不影响的

​ 二级缓存是mapper级别的缓存,多个sqlSession之间可以操作通过一个Mapper的sql语句,多个sqlSession可以共用二级缓存,二级缓存是跨SqlSession的

​ 如果缓存中有的数据直接从缓存中操作,不需要从数据库获取,大大提高系统的性能

​ 下述通过id查找用户的案例简单了解一级缓存和二级缓存

​ 此处为相应的mapper.xml、mapper.java内容:

  • 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.noob.mybatis.e_cache.UserMapper">
 	<!-- 定义根据id查找用户信息 -->
 	<select id="findUserById" parameterType="java.lang.Integer" resultType="User">
 		select * from user where id=#{id}
 	</select>
 </mapper>
  • mapper.java:
public interface UserMapper {
	// 此处简单根据id查找用户信息进行测试
	public User findUserById(int id);
}

在sqlMapConfig.xml中配置相关内容,随后编写测试代码进行测试分析

一级缓存

UserMapperTest.java

public class UserMapperTest {
	// 优化测试,将公共代码提取出来
	SqlSessionFactory sqlSessionFactory;

	@Before // 在所有测试代码执行之前执行该方法
	public void before() throws Exception {
		// a.定义mybatis配置文件,并得到配置文件的流
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);

		// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}
}

分析1:

​ 测试结果:

​ 此处只考虑一级缓存,暂时不考虑二级缓存。第一次查找的时候,先查询缓存中的数据,一级缓存中没有相关的数据,需要发送sql从数据库中查找获取相应的用户信息,但完成查找之后会在一级缓存中存入已查找过的用户信息

​ 在第二次查找的时候,从缓存中获取数据,由于查找的是相同的数据,此时一级缓存中已经存在相关的用户信息,因此可以直接获取,而不需要发送sql进行查找,从而显示上述测试结果

分析2:

​ 测试结果:

​ 在查询完数据之后对原有数据进行修改并提交,随后再次进行查询相同的数据,此时一级缓存中查询的数据已被清空,因此在第二次查找会发送sql语句从数据库中查找相关的数据信息,显示结果如下所示

二级缓存

​ 二级缓存是Mapper级别的, 二级缓存和一级缓存的区别是:二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。这个二级缓存区域可以有多个,按照namespace进行区分的

使用二级缓存需要配置如下数据(在一级缓存的案例基础上进行修改)
a.在sqlMapConfig.xml中配置二级缓存
b.在指定的xxxMapper.xml中开启二级缓存
c.调用pojo类实现序列化Serializable
d.编写测试代码进行相关测试并分析

a.在sqlMapConfig.xml中配置二级缓存

	<!-- 配置settings属性实现延迟缓存 -->
	<settings>
		<!-- 打开延迟加载的开关 -->
		<setting name="lazyLoadingEnabled" value="true"/>
		<!-- 将积极加载改为消极加载和按需加载 -->
		<setting name="aggressiveLazyLoading" value="false"/>
		<!-- 开启二级缓存 -->
		<setting name="cacheEnabled" value="true"/>
	</settings>

b.在指定的xxxMapper.xml中开启二级缓存

<mapper namespace="com.noob.mybatis.e_cache.UserMapper">
 	<!-- 在当前mapper.xml中开启二级缓存 -->
 	<cache/>
</mapper>

c.调用pojo类实现序列化Serializable

public class User implements Serializable{
	// 属性定义
}

d.编写测试代码进行相关测试并分析

测试结果:

​ 第一次查找的时候先从一级缓存中获取数据,如果一级缓存中没有指定的数据,再从二级缓存中获取数据,二级缓存中如果没有指定的数据则发送sql从数据库中获取数据,查询完数据之后,随后关闭当前使用的sqlSession,此时会相应地清空一级缓存中的数据

​ 第二次查找相同数据的时候,此时一级缓存中的数据已经被清空,开启新的sqlSession获取数据,此时会从二级缓存中获取数据,二级缓存中存在指定的数据则可直接获取,此时可看到指定的参数Cache Hit Ratio(缓存命中率)

​ 第三次查找相同数据,以此类推进行分析即可

【10】逆向工程

逆向工程概念

​ 结合mybatis基本案例可知,使用mybatis框架还是需要自行编写mapper.java、mapper.xml、po以及sql语句相关的内容,但在企业开发中设计到的表由于较多,可以使用逆向工程直接生成单表的mapper.xml映射文件和mapper.java dao接口和PO类,但对于复杂逻辑的sql语句还需要自行定义

逆向工程的应用

1>创建Java Project完成配置文件的自动生成

a.创建Java Project工程,导入相关jar包

b.根据需求修改核心配置文件的相关内容

​ 根据详细注释进行修改操作,主要是数据库配置、指定生成文件位置、指定数据表的配置

​ generatorConfig.xml:

<?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://localhost:3306/mybatis" userId="root"
			password="root">
		</jdbcConnection>
		<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
			connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:orcl" 
			userId="noob"
			password="noob">
		</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.noob.mybatis.model"
			targetProject=".\src">
			<!-- enableSubPackages:是否让schema作为包的后缀 -->
			<property name="enableSubPackages" value="false" />
			<!-- 从数据库返回的值被清理前后的空格 -->
			<property name="trimStrings" value="true" />
		</javaModelGenerator>
        <!-- targetPackage:mapper映射文件生成的位置   mapper.xml -->
		<sqlMapGenerator targetPackage="com.noob.mybatis.dao.mapper" 
			targetProject=".\src">
			<property name="enableSubPackages" value="false" />
		</sqlMapGenerator>
		<!-- targetPackage:mapper接口的生成位置  mapper.java-->
		<javaClientGenerator type="XMLMAPPER"
			targetPackage="com.noob.mybatis.dao.mapper" 
			targetProject=".\src">
			<property name="enableSubPackages" value="false" />
		</javaClientGenerator>
		
		<!-- 指定数据库表 -->
		<table tableName="items"></table>
		<table tableName="orders"></table>
		<table tableName="orderdetail"></table>
		<table tableName="user"></table>
	</context>
</generatorConfiguration>

c.加载GeneratorSqlmap.java文件并执行,即可自动生成相应的配置文件

public class GeneratorSqlmap {
    public void generator() throws Exception
    {

        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        // 指定 逆向工程配置文件
        File configFile = new File("generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }

    public static void main(String[] args) throws Exception
    {
        try
        {
            GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
            generatorSqlmap.generator();
        } catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}

​ 右键run as -- >java application,等待程序运行结束,刷新工程可看到自动生成相应的数据

2>创建Maven工程进行测试

将自动生成的配置文件加载至新建的maven工程进行相关测试即可

​ 一般额外指定generate生成目录,避免生成的内容冲掉目标项目路径的内容,生成内容确认无误之后再copy到项目指定目录即可

此处以User.xml中的相关内容为例,简单完成用户数据的增删改查

public class MyBatisTest {
	private SqlSessionFactory sqlSessionFactory;
	@Before
	public void before() throws IOException {
		// 定义mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件的流
		InputStream inputStream = Resources.getResourceAsStream(resource);
		// 根据流 创建 会话工厂 传入mybatis配置信息到会话工厂
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void testUserSelectByPrimaryKey() {
		// 根据id查找用户信息
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		// 查找id为1的用户信息
		User user = userMapper.selectByPrimaryKey(1);
		System.out.println(user);
		sqlSession.close();
	}

	@Test
	public void testSelectByExample() {
		// 根据条件查询用户信息
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		/**
		 *  设置查询条件:
		 *  a.创建相应的xxxExample对象
		 *  b.通过相应的Criteria对象构建查询条件(通过createCriteria()方法获取)
		 *  c.通过相应的方法设置查询条件,再将查询条件封装到xxxExample中
		 */
		UserExample userExample = new UserExample();
		// 通过Criteria对象构建查询条件
		Criteria criteria = userExample.createCriteria();
		// 设置相应的查询条件:名字包含小明、性别为2
		criteria.andUsernameEqualTo("张小明");
		criteria.andSexEqualTo("1");
		// 把查询条件封装到UserExample中即可
		userExample.or(criteria);
		// 执行查询
		List<User> list = userMapper.selectByExample(userExample);
		// 关闭连接
		sqlSession.close();
	}

	@Test
	public void testInsert() {
		// 增加用户信息
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		// 创建用户
		User user = new User();
		user.setUsername("花木兰");
		user.setSex("2");
		user.setAddress("王者峡谷");
		user.setBirthday(new Date());
		userMapper.insert(user);
		// 提交事务、关闭连接
		sqlSession.commit();
		sqlSession.close();
	}

	@Test
	public void testUpdateByPrimaryKey() {
		// 根据主键id修改用户信息
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		// 修改id为28的用户信息
		User user = new User();
		user.setId(28);
		user.setSex("1");
		user.setAddress("绝地求生");
		user.setUsername("aomoll");
		userMapper.updateByPrimaryKey(user);
		// 提交事务,关闭连接
		sqlSession.commit();
		sqlSession.close();
	}

	@Test
	public void testDeleteByPrimaryKey() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		userMapper.deleteByPrimaryKey(28);
		sqlSession.commit();
		sqlSession.close();
	}
}

2.MyBatis注解开发

【1】常用注解

​ 通过mybatis注解开发,减少mapper映射文件

注解说明
@Insert新增
@Update更新
@Delete删除
@Select查询
@Result结果集封装
@Results多个结果集封装,可与@Result结合使用
@One一对一结果封装
@Many一对多结果封装

【2】参考案例

​ 此处在Mapper相关案例的基础上进行注解开发测试,在测试的时候需要咋sqlMapper.xml文件中对mapper扫描路径进行配置

🔖单表的CRUD

1)model:User.java

public class User implements Serializable{
	private int id;
	private String username;
	private Date birthday;
	private String sex;
	private String address;
}

2)创建mapper接口(通过注解方式设定配置)

public interface UserMapper {

    //新增操作
    @Insert("INSERT INTO user(id,username,birthday,sex,address) VALUES (#{id},#{username},#{birthday},#{sex},#{address})")
    public abstract Integer insert(User user);

    //修改操作
    @Update("UPDATE user SET username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} WHERE id=#{id}")
    public abstract Integer update(User user);

    //删除操作
    @Delete("DELETE FROM user WHERE id=#{id}")
    public abstract Integer delete(Integer id);

    //查询全部
    @Select("SELECT * FROM user")
    public abstract List<User> selectAll();

}

3)UserMapperTest测试接口

public class UserMapperTest {

    // 优化测试,将公共代码提取出来
    SqlSessionFactory sqlSessionFactory;

    @Before // 在所有测试代码执行之前执行该方法
    public void before() throws Exception {
        // a.定义mybatis配置文件,并得到配置文件的流
        String resource = "SqlMapConfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testInsert() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取UserMapper对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // ------- 业务测试 -------
        User user = new User();
        user.setId(1001);
        user.setUsername("test");
        user.setBirthday(new Date());
        user.setSex("男");
        user.setAddress("拉斯维加斯");
        // 新增用户
        userMapper.insert(user);
        // 提交事务,关闭连接
        sqlSession.commit();
        sqlSession.close();
    }


    @Test
    public void testUpdate() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取UserMapper对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // ------- 业务测试 -------
        User updateUser = new User();
        updateUser.setId(1001);
        updateUser.setUsername("haha");
        updateUser.setAddress("china");
        userMapper.update(updateUser);

        // 提交事务,关闭连接
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testDelete() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取UserMapper对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // ------- 业务测试 -------
        userMapper.delete(1001);
        // 提交事务,关闭连接
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testSelectAll() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取UserMapper对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // ------- 业务测试 -------
        List<User> list = userMapper.selectAll();
        list.forEach(System.out::println);
        // 关闭连接
        sqlSession.close();
    }
}

4)测试说明

​ 通过注解方式一一测试接口,这种方式针对单表的CRUD操作在一定程度上简化了mapper配置文件的定义,直接在对应的dao接口上定义sql实现操作,但针对一些特殊的语法和场景反而没有办法很好地兼顾

🔖多表操作

数据表说明

  • Orders:订单列表
  • OrderDetail:订单详情
  • Items:订单项内容

关系说明

​ 一个订单列表下可包含多个订单,而每个订单下囊括具体的订单项内容,从而构建一对一、一对多、多对多查询

1>一对一查询

基本参数实体类

public class OrderDetail {
	private int id;
	private int orders_id;
	private int items_id;
	private int items_num;
}
public class Items {
	private int id;
	private String name;
	private float price;
	private String detail;
	private String pic;
	private Date createtime;
}

查询结果实体对象定义

public class OrderDetailVO extends OrderDetail {

    // 订单详情关联的订单项
    private Items items;

    public Items getItems() {
        return items;
    }

    public void setItems(Items items) {
        this.items = items;
    }

}

一对一接口定义

public interface OneToOneMapper {

    // 根据item_id查找订单项
    @Select("SELECT * FROM items WHERE id=#{id}")
    public abstract Items selectItemsById(Integer id);

    // 根据id查找订单详情以及关联的订单项内容
    @Select("SELECT * FROM orderdetail")
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "orders_id",property = "orders_id"),
            @Result(
                    property = "items",      // 被包含对象的变量名
                    javaType = Items.class,  // 被包含对象的实际数据类型
                    column = "items_id",     // 根据查询出的orderdetails表的items_id字段来查询items表
                    /*
                        one、@One(一对一)
                        select属性:指定调用哪个接口中的哪个方法
                     */
                    one = @One(select = "com.noob.annotation.mapper.OneToOneMapper.selectItemsById")
            )
    })
    public abstract List<OrderDetailVO> selectAll();

}

测试接口定义

public class OrderTest {

    // 优化测试,将公共代码提取出来
    SqlSessionFactory sqlSessionFactory;

    @Before // 在所有测试代码执行之前执行该方法
    public void before() throws Exception {
        // a.定义mybatis配置文件,并得到配置文件的流
        String resource = "SqlMapConfig-annotation.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testOneToOne() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取UserMapper对象
        OneToOneMapper oneToOneMapper = sqlSession.getMapper(OneToOneMapper.class);

        // ------- 业务测试 -------
        List<OrderDetailVO> list = oneToOneMapper.selectAll();
//        list.forEach(System.out::println);
        for (OrderDetailVO orderDetailVO : list) {
            System.out.print(orderDetailVO.getId()+orderDetailVO.getItems().getDetail()+"\n");
        }

        // 关闭连接
        sqlSession.close();
    }

}
2>一对多查询

基本参数实体类

public class Orders {
	private int id;
	private int user_id;
	private String number;
	private Date createtime;
	private String note;
}
public class OrderDetail {
	private int id;
	private int orders_id;
	private int items_id;
	private int items_num;
}

结果实体定义

public class OrdersVO extends Orders {

    private List<OrderDetail> orderDetailList;

    public List<OrderDetail> getOrderDetailList() {
        return orderDetailList;
    }

    public void setOrderDetailList(List<OrderDetail> orderDetailList) {
        this.orderDetailList = orderDetailList;
    }
}

一对多查询接口定义

public interface OneToManyMapper {

    //根据orders_id查询orderdetail表(一个订单列表对应多个订单详情)
    @Select("SELECT * FROM orderdetail WHERE orders_id = #{ordersId}")
    public abstract List<OrderDetail> selectByOrdersId(Integer ordersId);


    //查询全部
    @Select("SELECT * FROM orders")
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "user_id",property = "user_id"),
            @Result(column = "number",property = "number"),
            @Result(column = "createtime",property = "createtime"),
            @Result(column = "note",property = "note"),
            @Result(
                    property = "orderDetailList",  // 被包含对象的变量名
                    javaType = List.class,  // 被包含对象的实际数据类型
                    column = "id",          // 根据查询出的orders表的id字段来查询orderdetail表
                    /*
                        many、@Many 一对多查询的固定写法
                        select属性:指定调用哪个接口中的哪个查询方法
                     */
                    many = @Many(select = "com.noob.annotation.mapper.OneToManyMapper.selectByOrdersId")
            )
    })
    public abstract List<OrdersVO> selectAll();

}

测试接口定义

		@Test
    public void testOneToMany() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取UserMapper对象
        OneToManyMapper oneToManyMapper = sqlSession.getMapper(OneToManyMapper.class);

        // ------- 业务测试 -------
        List<OrdersVO> list = oneToManyMapper.selectAll();
//        list.forEach(System.out::println);
        for (OrdersVO ordersVO : list) {
            System.out.println(ordersVO.getId()+"订单列表:");
            ordersVO.getOrderDetailList().forEach((od)->{
                System.out.println(od.getOrders_id()+":"+od.getItems_num()+"\t");
            });
        }

        // 关闭连接
        sqlSession.close();
    }
3>多对多查询

基本参数实体类

​ 基本参数实体以Orders.java、OrderDetail、Items为参考

结果实体定义

public class OrderDetailMoreVO extends OrderDetail {

    // 订单详情关联的订单项相关内容
    private float items_price;
    private String items_detail;
    private String items_pic;
    private Date items_createtime;
}
public class OrderMoreVO extends Orders{

    private List<OrderDetailMoreVO> orderDetailMoreVOList;

    public List<OrderDetailMoreVO> getOrderDetailMoreVOList() {
        return orderDetailMoreVOList;
    }

    public void setOrderDetailMoreVOList(List<OrderDetailMoreVO> orderDetailMoreVOList) {
        this.orderDetailMoreVOList = orderDetailMoreVOList;
    }
}

多对多查询接口定义

public interface ManyToManyMapper {

    // 根据orders_id筛选订单列表详情内容及关联的items详情
    @Select("SELECT od.*,it.price 'items_price',it.detail 'items_detail',it.pic 'items_pic',it.createtime 'items_createtime' FROM orderdetail od,items it WHERE od.items_id=it.id AND od.orders_id=#{ordersId}")
    public abstract List<OrderDetailMoreVO> selectOrderDetailMoreByOrdersId(Integer ordersId);


    // 查询全部订单内容
    @Select("SELECT os.*,od.* FROM orders os,orderdetail od WHERE os.id=od.orders_id")
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "user_id",property = "user_id"),
            @Result(column = "number",property = "number"),
            @Result(column = "createtime",property = "createtime"),
            @Result(column = "note",property = "note"),
            @Result(
                    property = "orderDetailMoreVOList",  // 被包含对象的变量名
                    javaType = List.class,  // 被包含对象的实际数据类型
                    column = "id",          // 根据查询出的orders表的id字段来查询orderdetail表
                    /*
                        many、@Many 一对多查询的固定写法
                        select属性:指定调用哪个接口中的哪个查询方法
                     */
                    many = @Many(select = "com.noob.annotation.mapper.ManyToManyMapper.selectOrderDetailMoreByOrdersId")
            )
    })
    public abstract List<OrderMoreVO> selectAll();

}

测试接口定义

		@Test
    public void testManyToMany() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取ManyToManyMapper对象
        ManyToManyMapper manyToManyMapper = sqlSession.getMapper(ManyToManyMapper.class);

        // ------- 业务测试 -------
        List<OrderMoreVO> list = manyToManyMapper.selectAll();
//        list.forEach(System.out::println);
        list.forEach((om)->{
            System.out.println(om.getId()+"订单列表:");
            om.getOrderDetailMoreVOList().forEach((od)->{
                System.out.println(od.getOrders_id()+":"+od.getItems_num()+"\t"
                        +od.getItems_price()+"\t"+od.getItems_detail()+"\t");
            });

        });

        // 关闭连接
        sqlSession.close();
    }
4>总结分析

​ 由上述内容可知,多表联查操作涉及的注解主要说明如下

@Results:封装映射关系的父注解,每个Results下可对应多个Result内容
	Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解
	column 属性:查询出的表中字段名称
	property 属性:对应实体对象中的属性名称
	javaType 属性:被包含对象的数据类型
	one 属性:一对一查询固定属性
@One:一对一查询的注解
	select 属性:指定调用某个接口中的方法
@Many:一对多查询的注解
	select 属性:指定调用某个接口中的方法

​ 而其中可借助不同的组合实现多表联查效果,构建思路参考如下

一对一

1.明确一对一关系,确定主表、从表关系(例如orderdetail包含items,此处orderdetail为主表、items为从表)
2.明确查找的SQL语句:遍历从表数据,附带关联查找主表详情,则此处要定义的SQL语句参考为
	select ....
	from orderdetail od,items it
	where od.items_id = it.id
3.dao接口定义
	- 接口1:根据id查找items
	- 接口2:一对一定义,定义查找orderdetail内容,通过items_id关联查找items
	# 此处返回的结果是一对一的关系,因此最终呈现的数据是一条ordertail记录对应一条items记录

一对多

1.明确一对多关系,确定主表、从表关系(例如orders关联多个orderdetail,此处orders为主表、orderdetail为从表)
2.明确查找的SQL语句:遍历从表数据,附带关联查找主表详情,则此处要定义的SQL语句参考为
	select ....
	from orders os,orderdetail od
	where os.id = od.orders_id
3.dao接口定义
	- 接口1:根据orders_id查找orderdetail,此处返回的结果是orderdetail列表数据
	- 接口2:一对多定义,定义查找orders内容,通过orders_id关联查找orderdetail
	# 此处返回的结果是一对多的关系,因此最终呈现的数据是一条orders记录对应多条orderdetail记录

多对多

1.明确多对多关系,确定主表、从表关系(例如orders关联多个orderdetail,而每个orderdetail又关联对应的items项)
2.明确查找的SQL语句:遍历从表数据,附带关联查找主表详情,则此处要定义的SQL语句参考为
	select ....
	from orders os,orderdetailExtend ode
	where os.id = ode.orders_id
	# 此处的orderdetailExtend不是一个表的概念,而是由一对一关联查找衍生出的一个记录集合(一对一),通过这个新的集合与orders联动,从而把问题又简化为一对多概念,最终呈现的效果为多对多
	
3.dao接口定义
	- 接口1:根据orders_id查找订单详情列表内容,此处返回的结果封装的订单详情和其关联的列表项详细内容
	- 接口2:多对多定义,定义查找orders内容,通过orders_id关联查找orderdetail
	# 此处返回的结果是多对多的关系,因此最终呈现的数据是一条orders记录对应多条详情记录(包括订单详细内容、关联订单项)

​ 与sql执行呈现的效果一致,但需要注意的是此处并不会将相同orders_id的记录做智能的去重,例如一个订单主表id为3下关联两个订单记录,则通过这种方式查找的结果会将订单主表的记录封装为两个相同的内容,其下都有2个订单详情内容。而理想状态则是希望呈现的效果是每一个订单主记录下封装对应的详情内容,而不是以详情的维度封装结果,造成数据重复的情况,这点则需要在实现上进行控制。

【3】SQL构建对象

​ 通过注解开发,相关的SQL语句则是通过在语句中定义。MyBatis提供了org.apache.ibatis.jdbc.SQL 功能类,提供SQL相关注解用于SQL语句构建

📌参考示例:根据ID查找用户信息

1)接口定义

public interface UserMapper {

    // 根据ID查找用户信息
    @SelectProvider(type = BaseUserProvider.class,method = "selectById")
    public abstract User selectById(@Param(value = "id")Integer id);

}

2)接口实现定义(其作用相当于mapper.xml,此处是借助java代码实现SQL配置)

public class BaseUserProvider {

    public String selectById(Map<String,Object> params){
        return new SQL(){
            {
                SELECT("*");
                FROM("user");
                WHERE("id="+params.get("id"));
            }
        }.toString();
    }

}

3)测试

		@Test
    public void testSelectById() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象获取UserMapper对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // ------- 业务测试 -------
        User findUser = userMapper.selectById(1001);
        System.out.println(findUser.getId()+"-"+findUser.getUsername());
        // 关闭连接
        sqlSession.close();
    }

📌构建说明

1)注解、方法说明

注解说明

注解说明
@SelectProvider生成查询用的 SQL 语句注解
@InsertProvider生成新增用的 SQL 语句注解
@UpdateProvider生成修改用的 SQL 语句注解
@DeleteProvider生成删除用的 SQL 语句注解

2)调用参考

  • 接口定义
public interface UserMapper {
    // type属性:指定生成 SQL语句功能类对象;method属性:指定调用方法,其余注解的使用类似  
    @SelectProvider(type = BaseUserProvider.class,method = "selectById")
    public abstract User selectById(@Param(value = "id")Integer id);
}
  • SQL语句功能类对象实现
// 定义SQL语句功能类(对应注解中的type属性)
public class BaseUserProvider {
		// 定义SQL操作方法(对应注解中的method属性)
    public String selectById(Map<String,Object> params){
      	// 根据提供的SQL构建方法封装SQL语句,并返回生成的SQL字符串结果,具体方法调用可参考AbstractSQL
        return new SQL(){
            {
                SELECT("*");
                FROM("user");
                WHERE("id="+params.get("id"));
            }
        }.toString();
    }
}

上述定义等价于

public interface UserMapper {
    @Select("SELECT * FROM user where id = #{id}")
    public abstract User selectById(Integer id);
}
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3