三-框架学习

第一章:MyBatis

第1节:阶段内容

本章节介绍我们框架阶段分为核心框架基础和核心框架高级两部分
核心框架基础:     
    1. Mybatis持久层技术
    2. Spring逻辑控制技术
    3. SpringMVC Web交互技术
    4. SSM整合进行单体项目开发
核心框架高级
    1. zookeeper 注册中心 
    2. Dubbo     分布式技术
    3. SpringBoot Spring框架整合
    4. Mybatis-Plus Mybatis封装工具
    5. Redis&Nginx  非关系型数据库与负载均衡工具 
分布式项目     
 		基于springBoot+Mybatis+Zookeeper+Dubbo 的综合健康项目。

第2节:学习方法

1. 课上认真听讲,重点听取框架的基础语法和设计思想。

   俗话说,内行看门道,外行看热闹。学习也是一样,首先要认真听讲,对于重点内容要聚精汇神,了解框架结构和设计思想,这样才能阔宽自己的视野,领悟知识的精髓。

2. 课下注重实践

    我们都知道理论与实践是相脱节的,也就是说听懂和学会知识是两个概念,它们之间是有一条鸿沟的,想要跨过鸿沟,把理论知识变成自己的知识,唯一的途径就是不断的练习即实践。

3. 不断提升自我学习能力

    在学习过程中不要机械化的学习,对框架来讲其实很简单,首先要看官方文档,从文档入手,根据文档所提示步聚一步一步进行实践,总结和探讨为什么框架要这么做,分析其原理,不断提高自己的学习能力。

4. 不断提升自主解决问题的能力

    在练习过程中,不出错的人是不存在,其实出错是一件好事,因为我们都是从错误中吸取教训和总结,但是,一定要戒骄戒燥,遇到问题要令静分析问题出现的原因,不断的进行总结增加自己的经验,这样解决问题的能力才能有提升。

第3节:框架概述

3.1 框架介绍
		框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。

总结: 
框架把重复利用的单独抽取, 形成项目的半成品, 半成品就是我们所说的框架。
程序员在框架基础之上,进行开发。
提升了开发效率,减少了重复性代码~ 

		我们是由于效率和易用性的考虑使用框架。框架能节省开发时间。框架强制使用公共的约定,因此它能有效地解决一些共有的问题,它能让决定更连贯,避免我们写一大堆自定义模块来实现这些性能,框架节省了我们不少的时间和精力,并且让扩展变得更容易。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TWuLJh5w-1666713220228)(images\毛坯房.png)]

3.2 框架解决的问题
3.2.1 技术整合问题
		框架要解决的最重要的一个问题是技术整合的问题,在 J2EE 的 框架中,有着各种各样的技术,不同的软件企业需要从 J2EE 中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。框架一般处在低层应用平台(如 J2EE )和高层业务逻辑之间的中间层。
3.2.2 三层架构
分层后的优势:
  	 1.避免了表示层直接访问数据访问层,表示层只和业务逻辑层有联系,提高了数据安全性。 
     2.有利于系统的分散开发,每一个层可以由不同的人员来开发,只要遵循接口标准,利用相同的对象模型实体类就可以了,这样就可以           大大提高系统的开发速度。
     3.方便系统的移植,如果要把一个 C/S 的系统变成 B/S 系统,只要修改三层架构的表示层就可以了,业务逻辑层和数据访问层几乎           不用修改就可以轻松的把系统移植到网络上。
     4.项目结构更清楚,分工更明确,有利于后期的维护和升级。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dWHET8A-1666713220230)(images\三层架构图-1.bmp)]

3.3 常见框架
1. 持久化框架
       1) 持久化框架Mybatis
         MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了googlecode,       并且改名为MyBatis201311月迁移到Github。
         iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL MapsData Access ObjectsDAOs)。

       2) 持久化框架Hibernate 
        Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是      一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数库。      Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意      义的是,Hibernate可以在应用EJB的JavaEE架构中取代CMP,完成数据持久化的重任。但是,这个框架因为各种原因目前在国内的流行      程度下降太多,现在公司开发也越来越少使用。目前使用 Spring Data 来实现数据持久化也是一种趋势。

2. Web层MVC框架

     Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的     全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用SpringSpring MVC框架     或集成其他MVC开发框架,如Struts1(现在一般不用)Struts 2(一般老项目使用)等等。

3. 技术整合框架
   SpringJava EE编程领域的一个轻量级开源框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。 Spring是一个开源容器框架,它集成各类型的工具,通过核心的Bean factory实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程。

第4节:Mybatis

4.1 概述
【重点】
		mybatis是一个优秀的基于 java 满足orm思想的的持久层框架,它内部封装了 jdbc,屏蔽了javaapi的访问细节,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

总结: 
优秀的 orm思想  jdbc封装的 持久层框架:  

 Mybatis是一个半自动的orm框架


【重要】
ORM思想: Object Relational Mapping 
object:对象 java 对象
Relational:关系型数据库 mysql  oracle db2 SQLServer
Mapping: java对象和关系型数据库的映射:
java     mysql 
类        表名
属性      字段
对象      记录


mybatis的特点:
(1) 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
(2) 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
(3) 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
(4)提供映射标签,支持对象与数据库的orm字段关系映射
(5) 提供xml标签,支持编写动态sql。
(6) 支持数据缓存和注解开发。

mybatis的官方网址: https://www.mybatis.org/
4.2 准备
4.2.1 创建数据库
create table user(
   id int not null auto_increment,
   name varchar(30),
   gender varchar(6),
   age int,
   birthday date,
    primary key(id)
); 
4.2.2 创建maven Java项目
打开Idear-->选择File-->选择new-->选择project

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpBGiVVT-1666713220231)(images\创建maven-Java工程.png)]

4.2.3 导入依赖
  <dependencies>
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.4.6</version>
     </dependency>
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.47</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.17</version>
     </dependency>
 </dependencies>
4.3 使用
4.3.1 编写实体类
package cn.offcn.entity;
import java.io.Serializable;
import java.util.Date;

public class User  implements Serializable {

  private Integer id;
  private String name;
  private String gender;
  private Integer age;
  private Date birthday;

 public User() {}

 public User(String name, String gender, Integer age, Date birthday) {
     this.name = name;
     this.gender = gender;
     this.age = age;
     this.birthday = birthday;
 }

 public Integer getId() {
     return id;
 }

 public void setId(Integer id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public String getGender() {
     return gender;
 }

 public void setGender(String gender) {
     this.gender = gender;
 }

 public Integer getAge() {
     return age;
 }

 public void setAge(Integer age) {
     this.age = age;
 }

 public Date getBirthday() {
     return birthday;
 }

 public void setBirthday(Date birthday) {
     this.birthday = birthday;
 }

 @Override
 public String toString() {
     return "User{" +
             "id=" + id +
             ", name='" + name + '\'' +
             ", gender='" + gender + '\'' +
             ", age=" + age +
             ", birthday=" + birthday +
             '}';
 }
}
4.3.2 编写持久层接口
public interface UserDao {

 /**
     * 查询所有User对象
     * @return 返回List集合
     */
    public List<User> queryAllUsers();
}

4.3.3 编写映射文件
编写:UserDao.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="cn.offcn.dao.UserDao">

<!--配置查询的sql语句-->
<select id="queryAllUsers" resultType="cn.offcn.entity.User">
   select * from user
</select>

</mapper>
4.3.4 编写主配置文件
编写: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>
<!-- 配置 mybatis的环境 -->
<environments default="development">
  <!-- 配置环境 -->
  <environment id="development">
      <!-- 配置事务的类型 -->
      <transactionManager type="JDBC"></transactionManager>
      <!-- 配置连接数据库的信息:用的是数据源【连接池】-->
      <dataSource type="POOLED">
          <property name="driver" value="com.mysql.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
          <property name="username" value="root"/>
          <property name="password" value="root"/>
      </dataSource>
  </environment>
</environments>
<!--  注册UserDao接品映射文件位置 -->
<mappers>
  <mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>
</configuration>
4.4 注意事项
1. 接口的映射文件需要与接口文件名称对应: 
   UserMapper 接口
   UserMapper.xml  映射文件

2. 接口的映射文件需要与接口文件需要在同一个位置: 

3. 总配置文件中引入映射文件时,如果使用了mapper标签的resource属性,必须使用"/"表示分隔符。
案例: 
  <mapper resource="com/offcn/mapper/UserMapper.xml"/>

4. UserMapper.xml映射文件中mapper标签中namespace属性必须填写UserMapper接口的完全限定名。


5. select标签中的id属性与UserMapper接口中的方法名必须一致。


 通过快速入门示例,我们发现使用 mybatis 是非常容易的一件事情,因为只需要编写 Dao 接口并且按照mybatis 要求编写两个配置文件,就可以实现功能。远比我们之前的 jdbc 方便多了。【我们使用注解之后,将变得更为简单,只需要编写一个 mybatis 接口文件就够了】但是,这里面包含了许多细节,比如为什么会有工厂对象

第5节:入门案例

5.1 编写工具类
利用Mybatis对数据库进行操作时,都需要产生Mybatis总配置文件的输入流对象、构建SqlSessionFactoryBuilder对象、调用build方法进产生SqlSessionFactory工厂对象、调用工厂类openSession()方法创建SqlSession对象,代码冗余量大,步聚颇有繁琐,所以我们对产生SqlSession对象利用工具类进行优化。
package cn.offcn;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream; 

public class MyBatisUtils {

 //定义静态变量sqlSessionFactory
 private static  SqlSessionFactory sqlSessionFactory;

 //创建静态块,当MyBatisUtils类被加载时,自动执行该静态块,始初数据。
 static{
     try{
         //获取mybatis主配置文件SqlMapperConfig.xml的输入流
         InputStream inputStream= Resources.getResourceAsStream("SqlMapperConfig.xml");
         //创建SqlSessionFactoryBuilder构建者对象
         SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
         //调用build方法返回SqlSessionFactory工厂对象
         sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
     }catch (Exception e){
          e.printStackTrace();
         System.out.println("初始化数据失失败"+e.getMessage());
     }
 }

 //创建getSqlSessionFactory方法返回SqlSessionFactory对象
 public static SqlSessionFactory getSqlSessionFactory(){
     return sqlSessionFactory;
 }

 //创建一个SqlSession对象并返回
 public static SqlSession getSession(){
     return sqlSessionFactory.openSession();
 }

 //关闭SqlSession方法
 public static void close(SqlSession session){
     if(session!=null) session.close();
 }
}
5.2 操作数据库
public class UserTest {

 @Test
 public void testQueryAllUsers() throws  Exception{
     //1.获取mybatis主配置文件SqlMapperConfig.xml的输入流
     InputStream inputStream=Resources.getResourceAsStream("SqlMapperConfig.xml");
     //2.创建SqlSessionFactoryBuilder构建者对象
     SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
     //3.调用build方法返回SqlSessionFactory工厂对象
     SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
     //4.调用sqlSessionFactory的openSession方法返回一个Session对象
     SqlSession sqlSession = sqlSessionFactory.openSession();
     //5.调用SqlSession 创建 UserDao接口的代理对象
     UserDao userDao = sqlSession.getMapper(UserDao.class);
     //6.调用代理对象的queryAllUsers方法查询所有User
     List<User> users = userDao.queryAllUsers();
     users.forEach(System.out::print);
     //7.释放资源
     sqlSession.close();
     inputStream.close();
 }

 @Test
 public void testQueryAllUsers2() throws  Exception{
    //调用MyBatisUtils工具创建SqlSession对象
     SqlSession sqlSession = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserDao userDao = sqlSession.getMapper(UserDao.class);
     //调用代理对象的queryAllUsers方法查询所有User
     List<User> users = userDao.queryAllUsers();
     users.forEach(System.out::print);
     //释放资源
     sqlSession.close();
     inputStream.close();
 }
}
5.3 设计模式解析
5.3.1 工厂模式
 		工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BelRb65j-1666713220232)(images\工厂模式图.bmp)]

5.3.2 代理模式
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式的三要素:
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理模式的优点:
1.职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
2.代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
3.高扩展性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yj4JG9Pt-1666713220232)(images\代理模式图.png)]

5.3.3 构建者模式
 		创建者模式是java23种设计模式之一,英文叫Builder Pattern。其核心思想是将一个“复杂对象的构建算法”与它的“部件及组装方式”分离,使得构件算法和组装方式可以独立应对变化;复用同样的构建算法可以创建不同的表示,不同的构建过程可以复用相同的部件组装方式。

 	  在创建者模式中,客户端不再负责对象的创建与组装,而是把这个对象创建的责任交给其具体的创建者类,把组装的责任交给组装类,客户端只负责对象的调用,从而明确了各个类的职责。虽然利用创建者模式可以创建出不同类型的产品,但是如果产品之间的差异巨大,则需要编写多个创建者类才能实现,如果这是结合工厂模式更好。

创建者模式主要由五个部分构成:组装类,抽象创建者类,实现抽象创建者类的具体创建者类,抽象产品和实现抽象产品的具体产品类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTfPI7G0-1666713220233)(images\创建者模式.bmp)]

从图中我们可以看出,创建者模式由四部分组成。

	 抽象创建者角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体创建者角色。具体创建者必须实现这个接口的两种方法:一是建造方法,比如图中的 buildPart1 和 buildPart2 方法;另一种是结果返回方法,即图中的 getProduct 方法。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少零件,就有多少相应的建造方法。
	具体创建者角色:他们在应用程序中负责创建产品的实例。这个角色要完成的任务包括:
		1、实现抽象创建者所声明的抽象方法,给出一步一步的完成产品创建实例的操作。
		2、在创建完成后,提供产品的实例。
	导演者角色:这个类调用具体创建者角色以创建产品对象。但是导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体创建者角色。
	产品角色:产品便是建造中的复杂对象。一般说来一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的接口,而完全可以使不相关的。

第6节:原理分析

6.1 配置文件分析
1. 核心配置文件

<?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>
    <!-- 配置 mybatis 的环境 -->
    <environments default="development">
        <!-- 配置的环境 -->
        <environment id="development">
            <!-- 配置事务的类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源【连接池)-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--  注册UserDao接品映射文件位置 -->
    <mappers>
        <mapper resource="cn/offcn/dao/UserDao.xml"/>
    </mappers>
</configuration>

核心配置文件参数详解: 
    (1)environments标签中的id属性值必须和environments标签中的default属性一致。
    (2)事务管理器:
    第一种采用JDBC事务类型,直接使用了 JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
    第二种采用MANAGED事务类型,它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如: 
    <transactionManager type="MANAGED">
       <property name="closeConnection" value="false"/>
    </transactionManager>
    (3) 数据源(dataSource)
    dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源。大多数 MyBatis 应用程序会按示例中的例子来配置数据源。     虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
    有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")

    UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

    POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

    JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的数据源引用。

    (4) mapper中的resource属性用于指定映射文件的位置。mapper中有多个属性可以描述映射文件,分别为:
        resource=“cn/offcn/dao/UserDao.xml” 文件在多级目录中要用分隔符“/”隔开。
        class="cn.offcn.dao.UserDao"  指定UserDao接口文件位置,但此时UserDao.xml必须和接口处在同一个包中并且文件名要         相同。

2. 映射文件
<mapper namespace="cn.offcn.dao.UserDao">
<!--配置查询的sql语句-->
<select id="queryAllUsers" resultType="cn.offcn.entity.User">
         select * from user
</select>
</mapper>

映射文件参数详解: 
(1) namespace必须为接口的完全限定名(即包名+类名的格式)。
(2) select标签中的id必须和接口中声明的方法同名。
(3) 如果接口中方法有返回值,resyultType必须跟方法返回值一致并采用返回值的完全限定名来表示。

6.2 底层源码分析
1.利用Resources的getResourceAsStream方法读取mybatis核心配置文件,该配置文件中注册数据源【dataSource】和映射文件的位置【mappers标签中的mapper子标签的resource属性】。
<dataSource type="POOLED">
             <property name="driver" value="com.mysql.jdbc.Driver"/>
             <property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
             <property name="username" value="root"/>
             <property name="password" value="root"/>
</dataSource>
<mappers>
<mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>

2.展开映射文件【UserDao.xml】中定义了查询方法、查询时所封装结果类型和所要执行的sql语句。
<select id="queryAllUsers" resultType="cn.offcn.entity.User">
      select * from user
</select>

3.使用构建者模式SqlSessionFactoryBuilder类的build方法创建SqlSessionFactory对象,在build方法中从字节输入流中解析数据。
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);

//SqlSessionFactoryBuilder源码:
//SqlSessionFactoryBuilder的build方法
public SqlSessionFactory build(InputStream inputStream) {
     return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

public SqlSessionFactory build(InputStream inputStream) {
     return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
     SqlSessionFactory var5;
     try {
         XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
         var5 = this.build(parser.parse());
     } catch (Exception var14) {
         throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
     } finally {
         ErrorContext.instance().reset();

         try {
             inputStream.close();
         } catch (IOException var13) {
             ;
         }

     }

     return var5;
 }

public SqlSessionFactory build(Configuration config) {
     return new DefaultSqlSessionFactory(config);
}



//XMLConfigBuilder类的构造方法
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment,props);
}

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
     this.commonConstructor(validation, variables, entityResolver);
     this.document = this.createDocument(new InputSource(inputStream));
 }


上述代码中我们可以看到创建了一个XMLConfigBuilder对象用来解析XML,把配置文件中所有标签及标签中属性值放封装到Configuration对象中并返回SqlSessionFactory对象。

4.调用SqlSessionFactory对象的openSession方法返回一个SqlSession对象,SqlSessionFactory是一个接口,我们找到它的实现类
DefaultSqlSessionFactory类,点击它的openSession方法,打开openSessionFromDataSource方法。

 //DefaultSqlSessionFactory源码:
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,      boolean autoCommit) {
     Transaction tx = null;

     DefaultSqlSession var8;
     try {
         Environment environment = this.configuration.getEnvironment();
         TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
         //创建一个新事务,从环境中找到数据源。
         tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
         Executor executor = this.configuration.newExecutor(tx, execType);
         //创建一个DefaultSqlSession对象
         var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
     } catch (Exception var12) {
         this.closeTransaction(tx);
         throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
     } finally {
         ErrorContext.instance().reset();
     }

     return var8;
 }

5.调用sqlSession.getMapper(UserDao.class)方法返回一个UserDao接口的代理类对象。

//DefaultSqlSession源码:
public <T> T getMapper(Class<T> type) {
 return configuration.<T>getMapper(type, this);
}

//Configuration类源码:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 return mapperRegistry.getMapper(type, sqlSession);
}

//MapperRegistry类源码:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
 if (mapperProxyFactory == null) {
   throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
 }
 try {
   return mapperProxyFactory.newInstance(sqlSession);
 } catch (Exception e) {
   throw new BindingException("Error getting mapper instance. Cause: " + e, e);
 }
}

//MapperProxyFactory类源码:
public T newInstance(SqlSession sqlSession) {
 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
 return newInstance(mapperProxy);
}
//通过Proxy的newProxyInstance方法创建代理对象
protected T newInstance(MapperProxy<T> mapperProxy) {
 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

//MapperProxy类源码:
//invoke回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 try {
   if (Object.class.equals(method.getDeclaringClass())) {
     return method.invoke(this, args);
   } else if (isDefaultMethod(method)) {
     return invokeDefaultMethod(proxy, method, args);
   }
 } catch (Throwable t) {
   throw ExceptionUtil.unwrapThrowable(t);
 }
 final MapperMethod mapperMethod = cachedMapperMethod(method);
 //调用执行器excute方法
 return mapperMethod.execute(sqlSession, args);
}


//MapperMethod类源码:
public Object execute(SqlSession sqlSession, Object[] args) {
 Object result;
 switch (command.getType()) {
   case INSERT: {
   Object param = method.convertArgsToSqlCommandParam(args);
     result = rowCountResult(sqlSession.insert(command.getName(), param));
     break;
   }
   case UPDATE: {
     Object param = method.convertArgsToSqlCommandParam(args);
     result = rowCountResult(sqlSession.update(command.getName(), param));
     break;
   }
   case DELETE: {
     Object param = method.convertArgsToSqlCommandParam(args);
     result = rowCountResult(sqlSession.delete(command.getName(), param));
     break;
   }
   case SELECT:
     if (method.returnsVoid() && method.hasResultHandler()) {
       executeWithResultHandler(sqlSession, args);
       result = null;
     } else if (method.returnsMany()) {
       result = executeForMany(sqlSession, args);
     } else if (method.returnsMap()) {
       result = executeForMap(sqlSession, args);
     } else if (method.returnsCursor()) {
       result = executeForCursor(sqlSession, args);
     } else {
       Object param = method.convertArgsToSqlCommandParam(args);
       result = sqlSession.selectOne(command.getName(), param);
     }
     break;
   case FLUSH:
     result = sqlSession.flushStatements();
     break;
   default:
     throw new BindingException("Unknown execution method for: " + command.getName());
 }
 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
   throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

6.3 执行原理图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4Ri7if5-1666713220234)(images\mybatis执行原理图.bmp)]

第7节:实现CRUD操作

7.1 数据保存
7.1.1 定义接口方法
public class UserDao{
 /**
      新增添加用户方法
 */
 public void addUser(User user);
}
7.1.2 配置对应的sql
<insert id="addUser" parameterType="cn.offcn.entity.User">
  insert into user(name,gender,age,birthday) values (#{name},#{gender},#{age},#{birthday})
</insert>

#{}为mybatis的占位符,如果方法中传递的参数为实体类类型,#{实体类属性}
7.1.3 添加测试方法
@Test
 public void testAddUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserDao userDao = session.getMapper(UserDao.class);
     //创建User对象
     User user=new User("何晓","男",22,new Date());
     //调用addUser方法进行保存
     userDao.addUser(user);
     //关闭连接
     MyBatisUtils.close(session);
 }
7.2 数据更新
7.2.1 定义接口方法
/**
     * 更新user对象
     * @param user
     */
    public void updateUser(User user);
7.2.2 配置对应的sql
<update id="updateUser" parameterType="cn.offcn.entity.User">
      update user set name=#{name},gender=#{gender},age=#{age},birthday=#{birthday}  where id=#{id}
</update>
7.2.3 添加测试方法
 @Test
 public void testUpdateUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserDao userDao = session.getMapper(UserDao.class);
     //创建User对象
     User user=new User("何晓飞","女",21,new Date());
     //指定要更新id
     user.setId(1);
     //调用updateUser方法进行修改
     userDao.updateUser(user);
     //关闭连接
     MyBatisUtils.close(session);
 }
7.3 数据删除
7.3.1 定义接口方法
/**
     * 根据id删除指定user
     * @param id
     */
    public void deleteUser(Integer id);
7.3.2 配置对应的sql
<delete id="deleteUser" parameterType="int">
     delete from user where id=#{id}
</delete>
7.3.3 添加测试方法
 @Test
 public void testDeleteUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserDao userDao = session.getMapper(UserDao.class);
     //调用deleteUser方法进行删除
     userDao.deleteUser(2);
     //关闭连接
     MyBatisUtils.close(session);
 }
7.4 #{}和${}的区别
1.#{}是预编译处理,${}是字符串替换。
2.Mybatis在处理${}时,就是把${}替换成变量的值。
3.Mybatis在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值。
4.使用#{}可以有效的防止 SQL 注入,提高系统安全性。
7.5 模糊查询
7.5.1 添加接口方法
/**
     * 根据name模糊查询
     * @param name
     * @return
     */
    public List<User> likeSearcherUsers(String name);
7.5.2 配置对应sql
1)配置占位符方式#{}
<select id="likeSearcherUsers" parameterType="String" resultType="cn.offcn.entity.User">
     select * from user where name like #{name}
</select>
2)配置拼接字符串方式${}
<select id="likeSearcherUsers" parameterType="String" resultType="cn.offcn.entity.User">
     select * from user where name like '%${value}%'
</select>

我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字。
3)配置mysql函数方式concat
<select id="likeSearcherUsers" parameterType="String" resultType="cn.offcn.entity.User">
     select * from user where name like concat('%',#{name},'%')
</select>
7.5.3 模糊查询测试
 @Test
 public void testSearcherUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserDao userDao = session.getMapper(UserDao.class);
     //调用likeSearcherUsersr方法进行模糊查询
     List<User> userList = userDao.likeSearcherUsers("张");
     //遍历输出
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }

第8节:参数处理

8.1 parameterType 配置参数
8.1.1 参数的使用说明
	上一章节中已经介绍了SQL语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。
8.1.2 参数配置的注意事项
	基本类型和String可以直接写类型名称也可以使用包名.类名的方式,例如:java.lang.String。
	实体类类型,目前我们只能使用全限定类名。
	究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名。
mybatis 的官方文档的说明请参考下面表格数据。
别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listListg
arraylistArrayList
collectionCollection
iteratorIterator
这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。可以参考 TypeAliasRegistery.class 的源码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sy0IqNX6-1666713220234)(images\TypeAliasRegistry类源码.png)]

8.2 传递 pojo 包装对象
 		开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数,Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
8.2.1 编写QueryVo
package cn.offcn.entity;
public class QueryVo {
 private User user;
 public User getUser() {
     return user;
 }
 public void setUser(User user) {
     this.user = user;
 }
}
8.2.2 编写持久层接口UserMapper
public interface UserMapper {

 public List<User> getUserByLikeName(QueryVo queryVo);
}
8.2.3 配置接口方法对应的sql文件
<select id="getUserByLikeName" parameterType="cn.offcn.entity.QueryVo" resultType="cn.offcn.entity.User">
     select * from user where name like #{user.name}
</select>
8.2.4 测试QueryVo对象作为参数
 @Test
 public void testGetUserByLikeName() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User();
     user.setName("%李%");
     QueryVo queryVo=new QueryVo();
     queryVo.setUser(user);
     //调用deleteUser方法进行模糊查询
     List<User> userList = userMapper.getUserByLikeName(queryVo);
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }
8.3 map作为参数的处理方式
8.3.1 添加接口方法参数使用map集合
public List<User> getUserByGenderAndAge(Map<String,Object> map);
8.3.2 配置接口对应的sql配置
<!--#{}中参数必须和Map集合中的key保存一致,表示取Map集合中指定key的值。-->
<select id="getUserByGenderAndAge" parameterType="java.util.Map" resultType="cn.offcn.entity.User">
      select * from user where gender=#{sex} and age=#{age}
</select>
8.3.3 测试map集合作为参数
 @Test
 public void testGetUserByGenderAndAge() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     Map<String,Object> map=new HashMap<String,Object>();
     map.put("sex","男");
     map.put("age",22);
     List<User> userList = userMapper.getUserByGenderAndAge(map);
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }
8.4 @Param方式解决多参数处理
8.4.1 @Param注解的介绍
		@Param注解用于给方法内的参数取别名,当方法中拥有多个参数时,我们无法一次性将这些参数进行传递,尤其多个参数具有不同的数据类型时无法传递,所以我们利用@Param给每个方法中的参数取一个别名,在映射文件中使用别名进行取值。
8.4.2 添加接口方法参数使用map集合
public List<User> getUserByGenderAndBirthday(@Param("gen") String gender,Param("birth") Date birthday);
8.4.3 配置接口对应的sql配置
<select id="getUserByGenderAndBirthday" resultType="cn.offcn.entity.User">
     select * from user where gender=#{gen} and age=#{birth}
</select>
8.4.4 测试注解方法
 @Test
 public void testGetUserByGenderAndBithday() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     Calendar calendar = Calendar.getInstance();
     calendar.set(Calendar.YEAR,1997);
     calendar.set(Calendar.DAY_OF_MONTH,10);
     calendar.set(Calendar.DAY_OF_MONTH,12);
     Date birthday=calendar.getTime();
     List<User> userList = userMapper.getUserByGenderAndBirthday("女",birthday);
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }

第9节:查询结果封装

9.1 resultType结果类型
9.1.1 resultType属性介绍
  	resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。我们在前面的 CRUD 案例中已经对此属性进行过应用了。需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
9.1.2 resultType属性的使用
[1]基本类型
编写dao接口方法getTotalRecords参数是基本类型
/**
     * 统计所有记录数
     * @return
     */
    public int getTotalRecords();
配置接口方法对应的sql语句
<select id="getTotalRecords" resultType="int">
      select count(*) from user
</select>
测试查询结果
 @Test
 public void testGetTotalRecords() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //调用getTotalRecords统计记录数
     int totalRecords= userMapper.getTotalRecords();
     //打印totalRecords
     System.out.println("总记录数:"+totalRecords);
     //关闭连接
     MyBatisUtils.close(session);
 }
[2] 实体类型
编写dao接口方法getAllInfo参数是对象类型
public List<User> getUsers();
配置接口方法对应的sql语句
<select id="getUsers" resultType="cn.offcn.entity.User">
     select * from user
</select>
测试查询结果
 @Test
 public void testGetUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //调用getUsers查询所有记录
     List<User> userList = userMapper.getUsers();
    //遍历结果
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }
9.1.3 特殊情况
如果个修改了实体类User中的id属性值,比如修改成了userId,此时查询出的结果没有把表中的id值映射到userId属性中,因为属性和表中的列名不一致,内部无法用反射技术进行映射,所以为空。
public class User  implements Serializable {

  private Integer userId;
  private String userName;
  private String gender;
  private Integer age;
  private Date userBirthday;

 public User() {}

 public User(String userName, String gender, Integer age, Date userBirthday) {
     this.userName = userName;
     this.gender = gender;
     this.age = age;
     this.birthday = userBirthday;
 }

 public Integer getUserId() {
     return userId;
 }

 public void setUserId(Integer userId) {
     this.userId = userId;
 }

 public String getUserName() {
     return userName;
 }

 public void setUserName(String userName) {
     this.userName = userName;
 }

 public String getGender() {
     return gender;
 }

 public void setGender(String gender) {
     this.gender = gender;
 }

 public Integer getAge() {
     return age;
 }

 public void setAge(Integer age) {
     this.age = age;
 }

 public Date getUserBirthday() {
     return userBirthday;
 }

 public void setUserBirthday(Date userBirthday) {
     this.userBirthday = userBirthday;
 }
}

@Test
 public void testGetUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //调用getUsers查询所有记录
     List<User> userList = userMapper.getUsers();
    //遍历结果
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }


经过上述测试结果发现userId属性为null,没有被赋上值。
解决方案:修改映射配置,采用别名设置,让结果集中的列与实体类中的属性对应。
<select id="getUsers" resultType="cn.offcn.entity.User">
     select id userId,name userName,gender gender,age age,birthday userBirthday from user
</select>
9.2 resultMap自定义结果类型
9.2.1 resultMap标签介绍
		resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。     
9.2.2 定义接口方法
/**
     * 根据id查询指定User对象
     * @param id
     * @return
     */
    public User getUserById(int id);
9.2.3 配置文件中定义resultMap
<select id="getUserById" parameterType="int" resultMap="UserResultMap">
        select * from user where userId=#{id}
</select>

<resultMap id="UserResultMap" type="cn.offcn.entity.User">
</resultMap>

此处我们使用resultMap而不是resultType, resultType是直接写结果类型,resultMap是映射结果集与类中属性对应关系。
resultMap标签中的id表示一个唯一标记是resultMap的名称。
type: 表示该resultMap返回的类型。
9.2.4 使用resultMap查询
<resultMap id="UserResultMap" type="cn.offcn.entity.User">
 <id column="id" property="userId"></id>
 <result column="name" property="userName"></result>
 <result column="gender" property="gender"></result>
 <result column="age" property="age"></result>
 <result column="birthday" property="userBirthday"></result>
</resultMap>

id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称
9.2.5 测试查询结果
 @Test
 public void testGetUserById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //查询user
     User user = userMapper.getUserById(1);
     //打印user
     System.out.println(user);
     //关闭连接
     MyBatisUtils.close(session);
 }

第10节:主配置文件解析

  在主配置SqlMapConfig.xml中,定义了很多标签,我们现在只是使用了一部分标签 ,主配置文件中可以出现的标签 用dtd文 件进行约束。下面介绍其它标签的使用含义。
10.1 dtd规范文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--

    Copyright 2009-2016 the original author or authors.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

-->
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>

<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>

<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>

<!ELEMENT settings (setting+)>

<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>

<!ELEMENT typeAliases (typeAlias*,package*)>

<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>

<!ELEMENT typeHandlers (typeHandler*,package*)>

<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>

<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>

<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>

<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>

<!ELEMENT plugins (plugin+)>

<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>

<!ELEMENT environments (environment+)>
<!ATTLIST environments
default CDATA #REQUIRED
>

<!ELEMENT environment (transactionManager,dataSource)>
<!ATTLIST environment
id CDATA #REQUIRED
>

<!ELEMENT transactionManager (property*)>
<!ATTLIST transactionManager
type CDATA #REQUIRED
>

<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>

<!ELEMENT mappers (mapper*,package*)>

<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>

<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
10.2 properties标签详解
 	 在使用 properties 标签配置时,在xml中可以引用properties属性文件中key的值,日后修改properties属性文 件中key的值时,不用修改xml文件,从而提高了效率。
10.2.1 新建属性文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis001
jdbc.username=root
jdbc.password=root
10.2.2 引用属性文件
<properties resource="dbconfig.properties"></properties>
<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>
10.3 typeAliases标签详解
在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。
10.3.1 定义别名
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="cn.offcn.entity.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="cn.offcn.entity"/>
<package name=" 其它包 "/>
</typeAliases>
10.3.2 使用别名
 <select id="getUsers" resultType="user">
     select * from user
 </select>

 <select id="getUserById" parameterType="int" resultMap="UserResultMap">
        select * from user where userId=#{id}
 </select>
 <resultMap id="UserResultMap" type="user">
     <id column="id" property="userId"></id>
     <result column="name" property="userName"></result>
     <result column="gender" property="gender"></result>
     <result column="age" property="age"></result>
     <result column="birthday" property="userBirthday"></result>
 </resultMap>
10.4 mappers标签详解
mappers映射器用于指定映射文件的位置。
<!--使用相对于类路径的资源-->
<mappers>
 <mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>

<!--使用mapper接口类路径-->
如:<mapper class="cn.offcn.dao.UserDao"/>

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

第11节:自动返回主键值

11.1 应用场景
		对于自增主键在某些业务中保存一个对象后,需要使用到这个主键完成后续的业务逻辑,比如:要保存订单后,还要保存订单项信息,订单项相信需要用到订单主键。所以应用场合很多,下面我们来应用一下返回主键值操作。
11.2 定义接口方法
/**
     * 保存user对象
     * @param user
     */
    public void saveUser(User user);
11.3 配置文件对应SQL
<select id="saveUser" parameterType="cn.offcn.entity.User">
      insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</select>
11.4 添加配置
第一种方式:
<insert id="saveUser" parameterType="cn.offcn.entity.User" useGeneratedKeys="true" keyProperty="id">
      insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</insert>
useGeneratedKeys: 表示开启获取自增主键值。
keyProperty: 表示从表中取到主键值后赋给User类中的哪个属性。

第二种方式:使用selectKey标签和mysql内置函数
<insert id="saveUser" parameterType="cn.offcn.entity.User">
      <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
           select last_insert_id()
      </selectKey>
      insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</insert>
keyColumn:指定取数据库中哪一列的值(通常指主键列)。
keyProperty: 表示取出主键值后赋值User对象的哪个属性。
resultType:  表示对象的属性类型
order:表示完后sql语句之前还是之后把主键值赋给实体类对应属性。
11.5 测试
 @Test
 public void testsaveUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User("刘备","男",30,new Date());
     //保存User对象
     userMapper.saveUser(user);
     //打印user的id属性值
     System.out.println(user.getId());
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }

第12节:SQL片段

12.1 配置文件中SQL冗余
 		在开发中,SQL的拼接很常见,有很多对拼接的sql具有重复性高的特点,有sql冗余,不仅不美观还导致映射文件配置臃肿,这时最好把重复的sql抽取出来,作为公用的sql片段,尤其在动态sql中应用中更加显著,提高可重用性。
12.2 定义SQL片段
<!--使用sql标签定义一个sql片段-->
<sql id="baseColumn ">
	id,name,gender,age
</sql>
12.3 使用SQL片段
<select id="getUsers" resultType="cn.offcn.entity.User">
     select <include refid= "baseColumn"></include> from user
</select>

第13节:连接池及事务详解

本节重点分析接池的分类、数据配置、mybatis事务的处理方式。在mybatis核心配置文件中可以引入UNPOOLED、POOLED、JNDI数据源,分别介绍三种数据源使用场景及性能。并对UNPOOLED和POOLED数所源获取链接的方式进行源码分析。讲解mybatis事务处理方式默认为手动提交,还可设置为自动提交方式需要在调用openSession(true)方法传入true,默认为false,分析其原理是使用jdbc事务,底层将调用jdbc的setAutoCommit方法进行设置。
13.1连接池技术
13.1.1 介绍
我们在前面的 WEB 课程中也学习过类似的连接池技术,使用连接池可以避免使用时每次创建连接的情况,减少获取连接的时间,每次访问可以复用连接池中的连接提高效率,而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 MybatisSqlMapConfig.xml 配置文件中,通过<dataSourcetype="pooled">来实现 Mybatis 中连接池的配置。
13.1.2 分类
Mybatis中我们将它的数据源 dataSource 分为以下几类:
UNPOOLED 不使用连接池的数据源
POOLED 使用连接池的数据源
JNDI使用JNDI 实现的数据源

MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSourcePooledDataSource 类来表示UNPOOLED、POOLED 类型的数据源,在这三种数据源中,通常使用的是POOLED数据源。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q99HZPgH-1666713220235)(images\连接池的分类.png)]

实现了DataSource数据源的结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sC9mR5Q4-1666713220236)(images\实现datasource的数据源结构.png)]

13.1.3 数据源的配置
我们的数据源配置就是在 SqlMapConfig.xml 文件中,具体配置如下:
<!-- 配置数据源(连接池)信息 -->
<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>
MyBatis 在初始化时,根据<dataSource>的 type 属性来创建相应类型的的数据源DataSource,即:
type=”POOLED”:MyBatis 会创建 PooledDataSource 实例
type=”UNPOOLED” : MyBatis 会创建UnpooledDataSource实例
type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用。
13.1.4 POOLED池获取连接分析
1.PooledDataSource类,类中有一个getConnection方法
public Connection getConnection(String username, String password) throws SQLException {
 return popConnection(username, password).getProxyConnection();
}
2.在该方法中调用了popConnection方法,我们展开该方法。
private PooledConnection popConnection(String username, String password) throws SQLException {
 boolean countedWait = false;
 PooledConnection conn = null;
 long t = System.currentTimeMillis();
 int localBadConnectionCount = 0;

 while (conn == null) {
     //同步块代码
   synchronized (state) {
       //当空闲池不为空时,也就是空闲池中有连接
     if (!state.idleConnections.isEmpty()) {
       // Pool has available connection  直接取出一个连接返回
       conn = state.idleConnections.remove(0);
       if (log.isDebugEnabled()) {
         log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
       }
     } else {
       // Pool does not have available connection
         //如果活动连接数小于设定的大数量
       if (state.activeConnections.size() < poolMaximumActiveConnections) {
         // Can create new connection
          //创建连接放到池中
         conn = new PooledConnection(dataSource.getConnection(), this);
         if (log.isDebugEnabled()) {
           log.debug("Created connection " + conn.getRealHashCode() + ".");
         }
       } else {
         // Cannot create new connection
         PooledConnection oldestActiveConnection = state.activeConnections.get(0);
         long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
         if (longestCheckoutTime > poolMaximumCheckoutTime) {
           // Can claim overdue connection
           state.claimedOverdueConnectionCount++;
           state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
           state.accumulatedCheckoutTime += longestCheckoutTime;
           state.activeConnections.remove(oldestActiveConnection);
           if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
             try {
               oldestActiveConnection.getRealConnection().rollback();
             } catch (SQLException e) {
               /*
                  Just log a message for debug and continue to execute the following
                  statement like nothing happend.
                  Wrap the bad connection with a new PooledConnection, this will help
                  to not intterupt current executing thread and give current thread a
                  chance to join the next competion for another valid/good database
                  connection. At the end of this loop, bad {@link @conn} will be set as null.
                */
               log.debug("Bad connection. Could not roll back");
             }  
           }
           conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
           conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
           conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
           oldestActiveConnection.invalidate();
           if (log.isDebugEnabled()) {
             log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
           }
         } else {
           // Must wait
           try {
             if (!countedWait) {
               state.hadToWaitCount++;
               countedWait = true;
             }
             if (log.isDebugEnabled()) {
               log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
             }
             long wt = System.currentTimeMillis();
             state.wait(poolTimeToWait);
             state.accumulatedWaitTime += System.currentTimeMillis() - wt;
           } catch (InterruptedException e) {
             break;
           }
         }
       }
     }
     if (conn != null) {
       // ping to server and check the connection is valid or not
       if (conn.isValid()) {
         if (!conn.getRealConnection().getAutoCommit()) {
           conn.getRealConnection().rollback();
         }
         conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
         conn.setCheckoutTimestamp(System.currentTimeMillis());
         conn.setLastUsedTimestamp(System.currentTimeMillis());
         state.activeConnections.add(conn);
         state.requestCount++;
         state.accumulatedRequestTime += System.currentTimeMillis() - t;
       } else {
         if (log.isDebugEnabled()) {
           log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
         }
         state.badConnectionCount++;
         localBadConnectionCount++;
         conn = null;
         if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
           if (log.isDebugEnabled()) {
             log.debug("PooledDataSource: Could not get a good connection to the database.");
           }
           throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
         }
       }
     }
   }

 }    
13.1.5 UNPOOLED池获取连接分析
源码分析:
    1. 打开UnpooledDataSource类,类中有一个getConnection方法
 public Connection getConnection() throws SQLException {
      return doGetConnection(username, password);
 }
    2. 打开doGetConnection(username, password)方法
 private Connection doGetConnection(String username, String password) throws SQLException {
     Properties props = new Properties();
     if (driverProperties != null) {
       props.putAll(driverProperties);
     }
     if (username != null) {
       props.setProperty("user", username);
     }
     if (password != null) {
       props.setProperty("password", password);
     }
     return doGetConnection(props);
 }
3.打开doGetConnection(props)方法
private Connection doGetConnection(Properties properties) throws SQLException {
     initializeDrive
     r();
     Connection connection = DriverManager.getConnection(url, properties);
     configureConnection(connection);
     return connection;
}
在doGetConnection方法中可以见到Connection connection = DriverManager.getConnection(url, properties);这句代码,该句代码就是我们在jdbc中直截获取连接的方式,也就是说没有从池中取连接,是自己创建的新连接。
13.2 事务详解
13.2.1 事务的概述
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。事务分为手动提交和自动提交两种方式。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
13.2.2 事务的使用
Mybatis 中事务的提交方式,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。
@Test
public void testSaveUser() throws Exception {
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUsername("张三");
//执行保存操作
int result = userMapper.saveUser(user);
//提交事务
session.commit();
//关闭session
MyBatisUtils.close(session);
System.out.println(result);
System.out.println(user.getId());
}

控制台输出的结果:
------------------------>代补充结果

 这是我们的 Connection 的整个变化过程,通过分析我们能够发现之前的 CUD 操作过程中,我们都要手动进行事务的提交,原因是 setAutoCommit()方法,在执行时它的值被设置为 false 了,所以我们在CUD操作中,必须通过 sqlSession.commit()方法来执行提交操作。

13.2.4 事务自动提交的配置
 通过上面的研究和分析,现在我们一起思考,为什么 CUD 过程中必须使用 sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。
现在一起尝试不进行手动提交,一样实现 CUD 操作。
工具类:
public class MyBatisUtils {

 //定义静态变量sqlSessionFactory
 private static  SqlSessionFactory sqlSessionFactory;

 //创建静态块,当MyBatisUtils类被加载时,自动执行该静态块,始初数据。
 static{
     try{
         //获取mybatis主配置文件SqlMapperConfig.xml的输入流
         InputStream inputStream= Resources.getResourceAsStream("SqlMapperConfig.xml");
         //创建SqlSessionFactoryBuilder构建者对象
         SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
         //调用build方法返回SqlSessionFactory工厂对象
         sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
     }catch (Exception e){
          e.printStackTrace();
         System.out.println("初始化数据失失败"+e.getMessage());
     }
 }

 //创建getSqlSessionFactory方法返回SqlSessionFactory对象
 public static SqlSessionFactory getSqlSessionFactory(){
     return sqlSessionFactory;
 }

 //创建一个SqlSession对象并返回
 public static SqlSession getSession(){
     return sqlSessionFactory.openSession(true);
 }

 //关闭SqlSession方法
 public static void close(SqlSession session){
     if(session!=null) session.close();
 }
}

注意:一定要把openSession时传入true,表示自动提交事务。
@Test
public void testSaveUser() throws Exception {
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUsername("李四");
//执行保存操作
int result = userMapper.saveUser(user);
//提交事务
session.commit();
//关闭session
MyBatisUtils.close(session);
System.out.println(result);
System.out.println(user.getId());
}

我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为 false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。

第14节:动态SQL

提出二阶段时进行sql组拼是在java程序中进行的,对mybatis来讲程序和sql进行了分离,是在xml中进行定义的,这时如果要进行时常变化条件查询只能使用动态sql进行sql语句拼接。重点讲解where标签、if标签 、set标签、trim标签、foreach标签的场景和使用。
14.1 介绍
 		顾名思义,SQL 是动态拼接成的,根据传入的变量值进行逻辑操作,并动态拼接,方便实现多条件下的数据库操作。 在业务逻辑复杂,即简单 SQL 无法完成时,需要拼接时就要使用动态 SQL。
		动态sql主要解决根据条件判断附加条动态sql主要解决多条件变化查询,实现自动判断记录字段是否需要更新,根据条件判断附加条sql条件,实现批量添加数据、批量修改数据、批量修删除数据等,优化sql语句,提高执行效率。
14.2 构建测试环境
14.1 创建maven项目
添加依赖
 <dependencies>
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.4.6</version>
     </dependency>
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.47</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.17</version>
     </dependency>
 </dependencies>

创建bean

public class User  implements Serializable {

  private Integer id;
  private String name;
  private String gender;
  private Integer age;
  private Date birthday;

 public User() {}

 public User(String name, String gender, Integer age, Date birthday) {
     this.name = name;
     this.gender = gender;
     this.age = age;
     this.birthday = birthday;
 }

 public Integer getId() {
     return id;
 }

 public void setId(Integer id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public String getGender() {
     return gender;
 }

 public void setGender(String gender) {
     this.gender = gender;
 }

 public Integer getAge() {
     return age;
 }

 public void setAge(Integer age) {
     this.age = age;
 }

 public Date getBirthday() {
     return birthday;
 }

 public void setBirthday(Date birthday) {
     this.birthday = birthday;
 }

 @Override
 public String toString() {
     return "User{" +
             "id=" + id +
             ", name='" + name + '\'' +
             ", gender='" + gender + '\'' +
             ", age=" + age +
             ", birthday=" + birthday +
             '}';
 }
}

创建接口

public interface UserMapper {
}
14.2 主配置文件和映射文件
编写框架配置文件
<?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>
 <!-- 配置 mybatis的环境 -->
 <environments default="development">
     <!-- 配置环境 -->
     <environment id="development">
         <!-- 配置事务的类型 -->
         <transactionManager type="JDBC"></transactionManager>
         <!-- 配置连接数据库的信息:用的是数据源【连接池】-->
         <dataSource type="POOLED">
             <property name="driver" value="com.mysql.jdbc.Driver"/>
             <property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
             <property name="username" value="root"/>
             <property name="password" value="root"/>
         </dataSource>
     </environment>
 </environments>
 <!--  注册UserDao接品映射文件位置 -->
 <mappers>
     <mapper resource="cn/offcn/mapper/UserMapper.xml"/>
 </mappers>
</configuration>x
sql映射文件
<?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="cn.offcn.mapper.UserMapper">
</mapper>
14.3 where标签
14.3.1 where标签简介
where标签用于代替sql中的where关键字,可以根据条件判断是否附加where关键字。如果where标签中有条件成立就会附加where关键字,如果没有成立的条件就不会附加where关键字. 可以去掉离他最近一个无关的and 或or关键字.where标签的书写格式为<where>添写附加条件</where>
14.3.2 where标签使用
1)编写接口方法
/**
     * 根据User中的字段进行查询
     * @return
     */
    public List<User> findByUser(User user);
2)使用where标签
<select id="findByUser" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
 select * from user
 <where>
     and name=#{name} and age=#{age}
 </where>
</select>
3)测试
 @Test
 public void testFindByUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User();
     user.setName("张三");
     user.setAge(22);
     List<User> userList = userMapper.findByUser(user);
     //遍历
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }

14.4 if标签
14.4.1 if标签简介
	if标签表示逻辑条件判断,如果条件成立就附加<if></if>之间的sql语句,如果条件不成立就不附加<if></if>之间的sql语句。
书写格式为:<if test="表达式">sql语句</if>
14.4.2 if标签使用
1)编写接口方法
/**
     * 根据条件查询
     * @param user
     * @return
     */
    public List<User> findUsersByCondition(User user);
2)使用if标签
<select id="findUsersByCondition" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
     select * from user
     <where>
         <if test="name!=null">
             name=#{name}
         </if>
         <if test="age!=null">
             age=#{age}
         </if>
         <if test="name!=null">
             gender=#{gender}
         </if>
     </where>
 </select>
3)测试
 @Test
 public void testFindUsersByCondition() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User();
     user.setName("王五");
     user.setAge(20);
     user.setGender("男");
     List<User> userList = userMapper.findUsersByCondition(user);
     //遍历
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }

通过产生的sql语句可以看出,当if标签中test属性表达式为true时,就会附加if标签之间的条件。
注意:<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
14.5 set标签
14.5.1 set标签简介
set标签用于更新语句中,代替set关键字,可以有效对指定字段进行更新,提升sql的执行效率。,当set标签中有条件成立时就会附加set标签,set标签会去除无关的逗号。set标签中一般嵌套if标签进行使用其格式为
<set>
<if test="name">
   name=#{name},
</if>
<if test="age">
   age=#{age},
</if>
......
</set>
14.5.2 set标签使用
1)编写接口方法
/**
     * 更新user
     * @param user
     */
    public void updateUser(User user);
2)使用set标签
<update id="updateUser" parameterType="cn.offcn.entity.User">
     update user 
     <set>
         <if test="name!=null">
             name=#{name}
         </if>
         <if test="gender!=null">
             gender=#{gender}
         </if>
         <if test="age!=null">
             age=#{age}
         </if>
         <if test="birthday!=null">
             birthday=#{birthday}
         </if>
      </set>
         where id=#{id}
 </update>
3)测试
 @Test
 public void testUpdateUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User();
     user.setName("王五");
     user.setAge(20);
     //更新user
     userMapper.updateUser(user);
     //提交事务
     MyBatisUtils.close(session);
     //关闭连接
     MyBatisUtils.close(session);
 }

通过产生的sql语句可以看出,当set标签中有条件成立时就会附加set关键字,字段为null时该列不会被更新。set可以忽略与sql无关的逗号。
14.6 trim标签
14.6.1 trim标签简介
trim标签为万能标签,可用于set或where等。prefix表示要附加的前缀关键字,suffix表示要附加的后缀关键字,prefixOverrides表示要忽略前置字符,suffixOverrides表示要忽略后置字符。
格式:
<trim prefix="where" prefixOverrides=",">
         <if test="name!=null">
             name=#{name}
         </if>
         <if test="age!=null">
             age=#{age}
         </if>
         <if test="name!=null">
             gender=#{gender}
         </if>
</trim>
14.6.2 trim标签使用
1)修改
1)应用于where
<select id="findUsersByCondition" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
     select * from user
     <trim prefix="where" prefixOverrides=",">
         <if test="name!=null">
             name=#{name}
         </if>
         <if test="age!=null">
             age=#{age}
         </if>
         <if test="name!=null">
             gender=#{gender}
         </if>
     </trim>
 </select>

2)用于set标签 
<update id="updateUser" parameterType="cn.offcn.entity.User">
     update user
     <trim prefix="set" suffixOverrides=",">
         <if test="name!=null">
             name=#{name}
         </if>
         <if test="gender!=null">
             gender=#{gender}
         </if>
         <if test="age!=null">
             age=#{age}
         </if>
         <if test="birthday!=null">
             birthday=#{birthday}
         </if>
     </trim>
         where id=#{id}
 </update>
2)测试
@Test
 public void testFindUsersByCondition() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User();
     user.setName("王五");
     user.setAge(20);
     user.setGender("男");
     List<User> userList = userMapper.findUsersByCondition(user);
     //遍历
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }

 @Test
 public void testUpdateUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User();
     user.setName("王五");
     user.setAge(20);
     //更新user
     userMapper.updateUser(user);
     //提交事务
     MyBatisUtils.close(session);
     //关闭连接
     MyBatisUtils.close(session);
 }
14.7 choose标签
14.7.1 choose标签简介
choose标签作用条件判断来拼接指定的条件,它和if不太相同,choose似类于java中的switch语句用法,直要有条件成立,其它判断将得不到执行,如果所有条件都不成立则执行otherwise标签中的内容。
格式:
<choose>
  <when test=条件1>
    执行的代码;
  </when>
<when test=条件2>
    执行的代码;
  </when>
  ......
  <otherwise>
      执行的代码;
  </when>
  </otherwise>
</choose>
14.7.2 choose标签使用
1)编写接口方法
/**
     * 查询符合条件的所有user对象 
     * @param user
     * @return
     */
    public List<User> getInfoByUser(User user);
2)使用choose标签
<select id="getInfoByUser" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
     select * from user
     <where>
         <choose>
             <when test="name!=null">
                 name=#{name}
             </when>
             <when test="age!=null">
                 age=#{age}
             </when>
             <when test="gender!=null">
                 gender=#{gender}
             </when>
             <otherwise>
                 birthday='1991-10-10'
             </otherwise>
         </choose>
     </where>
 </select>
3)测试
 @Test
 public void testGetInfoByUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     //创建User对象
     User user=new User();
     user.setName("张三");
     user.setAge(22);
     List<User> userList = userMapper.getInfoByUser(user);
     //遍历
     userList.forEach(System.out::print);
     //关闭连接
     MyBatisUtils.close(session);
 }

14.8 foreach标签
14.8.1 foreach标签简介
	foreach标签表示循环,对sql中有重复的部分可以使用此循环来动态拼接sql语句。可以实现批量添加、批量删除、批量更新操作。
foreach标签中有很多的属性,请参考下面的Foreach标签属性表。
Foreach标签属性表
属性名称含义
collection指定你要使用的集合类型
item集合中每个元素。
open在起始时,需要附加字符串,只附加一次。
close在结束时,需要附加字符,只附加一次。
separator在每个循环结时需要附加的字符串。
index每个循环的索引值。
14.8.2 foreach标签使用
1)编写接口方法
/**
     * 批量添加
     * @param userList
     */
    public void addBatchUser(List<User> userList);

    /**
     * 批量更新
     * @param userList
     */
    public void updateBatchUser(List<User> userList);

   /**
     * 批量删除
     * @param ids
     */
    public void deleteBatchUser(List<Integer> ids);
2)使用forcach标签
<!--批量添加-->
 <insert id="addBatchUser">
     insert into user (name,gender,age,birthday) values
     <foreach collection="list" item="user" separator=",">
         (#{user.name},#{user.gender},#{user.age},#{user.birthday})
     </foreach>
 </insert>

 <!--批量更新-->
 <update id="updateBatchUser">
     <foreach collection="list" item="user" separator=";">
         update set user name=#{user.name},gender=#{user.gender},age=#{user.age},birthday=#{birthday}
         where id=#{id}
     </foreach>
 </update>

 <!--批量删除-->
 <delete id="deleteBatchUser">
       delete from user where id in
       <foreach collection="list" item="userId" separator="," open="(" close=")">
             #{userId}
       </foreach>
 </delete>
3)测试
 //批量添加测试
 @Test
 public void testAddBatchUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     List<User> userList=new ArrayList<>();
     userList.add(new User("赵丽","女",22,new Date()));
     userList.add(new User("李宁","女",25,new Date()));
     userList.add(new User("王海涛","男",20,new Date()));
     userMapper.addBatchUser(userList);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }

 //批量更新测试
 @Test
 public void testUpdateBatchUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     List<User> userList=new ArrayList<>();
     userList.add(new User(1,"赵刚","男",24,new Date()));
     userList.add(new User(2,"白雪","女",25,new Date()));
     userList.add(new User(3,"王海燕","女",20,new Date()));
     userMapper.updateBatchUser(userList);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }

 //批量删除测试
 @Test
 public void testDeleteBatchUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     List<Integer> ids=Arrays.asList(1,2,3);
     userMapper.deleteBatchUser(ids);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }

注意:mysql本身不支持批量更新,如果需要批量更新时在url中附加allowMultiQueries=true
<property name="url" value="jdbc:mysql:///mybatis002?allowMultiQueries=true"/>

第15节:多表联合查询

		多表联合查询是企业中常用技术,对于mybatis多分为一对一、一对多、多对一和多对多关系。分别介绍并演示每种关系的两种方式,分别为嵌套结果查询和嵌套查询两种方式的优缺点,并进行分析。突出嵌套结果查询是多查询方式,嵌套查询是分表查询方式,嵌套查询将可以应用延时加载技术,为讲解延时加载埋下伏笔。重点讲解association标签和collection标签及其属性的含义和用法。
15.1概述
		在开发过程中单表查询不能满足项目需求分析功能,对于复杂业务来讲,关联的表有几张,甚至几十张并且表与表之间的关系相当复杂。为了能够实业复杂功能业务,就必须进行多表查询,在mybatis中提供了多表查询的结果时映射标签,可以实现表之间的一对一、一对多、多对一、多对多关系映射。
15.2 一对一查询
15.2.1 构建数据库表
#person表
CREATE TABLE person(
 p_id INT NOT NULL AUTO_INCREMENT,
 p_name VARCHAR(30),
 PRIMARY KEY(p_id)
);

#IdCard表
CREATE TABLE idcard(
 c_id INT NOT NULL AUTO_INCREMENT,
 c_cardno VARCHAR(18),
 c_uselife DATE,
 c_person_id INT NOT NULL,
 PRIMARY KEY(c_id),
 FOREIGN KEY(c_person_id) REFERENCES person(p_id),
 UNIQUE KEY(c_cardno)

);

INSERT INTO person(p_name) VALUES('张三'),('李四');

INSERT INTO idcard(c_cardno,c_uselife,c_person_id)
VALUES('110112199012127821','2029-10-10',1);
INSERT INTO idcard(c_cardno,c_uselife,c_person_id)
VALUES('120114199911103491','2030-12-01',2);

15.2.2 准备项目环境
 构建maven项目,添加依赖
<dependencies>
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
  </dependency>
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</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.17</version>
  </dependency>
</dependencies>
编写框架配置文件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>
<!-- 配置 mybatis的环境 -->
<environments default="development">
  <!-- 配置环境 -->
  <environment id="development">
      <!-- 配置事务的类型 -->
      <transactionManager type="JDBC"></transactionManager>
      <!-- 配置连接数据库的信息:用的是数据源【连接池】-->
      <dataSource type="POOLED">
          <property name="driver" value="com.mysql.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/mybatis002"/>
          <property name="username" value="root"/>
          <property name="password" value="root"/>
      </dataSource>
  </environment>
</environments>
<mappers>
   <package name="cn.offcn.dao"></package>
</mappers
</configuration>
15.2.3 方式1:嵌套结果方式
1)创建数据模型
package cn.offcn.entity;

public class Person {

 private int id;
 private String name;


 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

}
public class IdCard {

 private int id;
 private String cardno;
 private Date useLife;


 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getCardno() {
     return cardno;
 }

 public void setCardno(String cardno) {
     this.cardno = cardno;
 }

 public Date getUseLife() {
     return useLife;
 }

 public void setUseLife(Date useLife) {
     this.useLife = useLife;
 }

}


2)编写sql语句
实现查询个人信息时,也要查询个人所对应的身份证信息。
select p.*,c.* from 
person p,
idcard c
where p.p_id=c.c_person_id and p.p_id=1;

3)编写类
public class PersonIdCard extends Person{

 private String cardno;
 private Date useLife;
 public String getCardno() {
     return cardno;
 }

 public void setCardno(String cardno) {
     this.cardno = cardno;
 }

 public Date getUseLife() {
     return useLife;
 }

 public void setUseLife(Date useLife) {
     this.useLife = useLife;
 }
}
4)定义持久层接口
public interface PersonDao{

 /**
    * 根据id查询person对象
    */
    public PersonIdCard getPersonById(int id);
}
5)定义映射文件
<?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="cn.offcn.dao.PersonDao">
<!-- 配置查询操作-->
<select id="getPersonById" resultMap="PersonResultMap">
 select p.*,c.* from 
 person p,
 idcard c
 where p.p_id=c.c_person_id and p.p_id=#{id};
</select>
<resultMap id="PersonResultMap" type="cn.offcn.entity.PersonIdCard">
 <id column="p_id" property="id"></id>
 <result column="p_id" property="id"></result>
 <result column="p_name" property="name"></result>
 <result column="c_cardno" property="cardno"></result>
 <result column="c_uselife" property="useLife"></result>
</resultMap>
</mapper>
6)创建测试类
@Test
public void testGetPersonById() throws  Exception{
  //获取SqlSession对象
  SqlSession session = MyBatisUtils.getSession();
  //调用SqlSession 创建 UserDao接口的代理对象
  PersonDao personDao = session.getMapper(PersonDao.class);
  PersonIdCard personIdCard=personDao.getPersonById(1);
   //打印
  System.out.println(personIdCard);
  //提交事务
  session.commit();
  //关闭连接
  MyBatisUtils.close(session);
}
15.2.4 方式2:嵌套查询方式
1)修改Person类:添加idCard属性
public class Person {

 private int id;
 private String name;
 private IdCard idCard;

 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }
 public IdCard getIdCard() {
     return idCard;
 }

 public void setIdCard(IdCard idCard) {
     this.idCard = idCard;
 }

}
2)修改接口中的方法
public interface PersonDao{

 /**
    * 根据id查询person对象
    */
    public Person getPersonById(int id);
}
3)定义持久层接口
public interface IdCardDao{
    
    /**
    * 根据c_person_id查询IdCard对象
    */
    public Person getIdCardByPersonId(int id);
}
4)定义映射文件
<?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="cn.offcn.dao.IdCard">
<!-- 配置查询操作-->
<select id="getIdCardByPersonId" resultMap="IdCardResultMap">
 select * from 
 idcard c
 where c_person_id=#{id};
</select>
<resultMap id="IdCardResultMap" type="cn.offcn.entity.IdCard">  
     <id column="c_id" property="id"></id>
     <result column="c_cardno" property="cardno"></result>
     <result column="c_uselife" property="useLife"></result>
</resultMap>
</mapper>
5)修改PersonDao.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="cn.offcn.dao.PersonDao">
<!-- 配置查询操作-->
<select id="getPersonById" resultMap="PersonResultMap">
 select * from 
 person p,
 where p_id=#{id};
</select>
<resultMap id="PersonResultMap" type="cn.offcn.entity.Person">
 <id column="p_id" property="id"></id>
 <result column="p_id" property="id"></result>
 <result column="p_name" property="name"></result>
 <!--映射Person类中的复杂字段idCard对象属性-->
 <association property="idCard" javaType="IdCard" column="p_id" 
              select="cn.offcn.dao.IdCard.getIdCardByPersonId">
 </association>
</resultMap>
</mapper>

column:表示取上次查询出来的指定列的值,做为select属性所指定的查询的输入值。
select:表示指定的查询.
6)加入测试方法
@Test
public void testGetPersonById() throws  Exception{
  //获取SqlSession对象
  SqlSession session = MyBatisUtils.getSession();
  //调用SqlSession 创建 UserDao接口的代理对象
  PersonDao personDao = session.getMapper(PersonDao.class);
  Person person=personDao.getPersonById(1);
  //打印
  System.out.println(person);
  //提交事务
  session.commit();
  //关闭连接
  MyBatisUtils.close(session);
}
15.3 一对多查询
15.3.1 创建数据库表
CREATE TABLE department(
 d_id INT NOT NULL AUTO_INCREMENT,
 d_name VARCHAR(100),
 PRIMARY KEY(d_id)
);

CREATE TABLE employee(
 e_id INT NOT NULL AUTO_INCREMENT,
 e_name VARCHAR(30),
 e_gender VARCHAR(6),
 e_age INT,
 e_depart_id INT,
 PRIMARY KEY(e_id),
 FOREIGN KEY(e_depart_id) REFERENCES department(d_id)
);
15.3.2 方式1:嵌套结果方式
1)创建数据模型
public class Department {

 private int id;
 private String name;
 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }


}

public class Employee {

 private int id;
 private String name;
 private String gender;
 private Integer age;
 public Employee(){}

 public Employee(int id,String name, String gender, int age) {
     this.id=id;
     this.name = name;
     this.gender = gender;
     this.age = age;
 }
 public Employee(String name, String gender, int age) {
     this.name = name;
     this.gender = gender;
     this.age = age;
 }

 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public String getGender() {
     return gender;
 }

 public void setGender(String gender) {
     this.gender = gender;
 }

 public Integer getAge() {
     return age;
 }

 public void setAge(Integer age) {
     this.age = age;
 }
}

2)编写查询语句
SELECT d.*,e.* 
FROM 
department d, employee e 
WHERE
e.e_depart_id=d.d_id AND d.d_id=#{id}
3)加入List属性
private List<Employee> emps;
public List<Employee> getEmps() {
     return emps;
}

public void setEmps(List<Employee> emps) {
     this.emps = emps;
}
4)加入查询方法
import cn.offcn.entity.Department;

public interface DepartmentMapper {
 public Department getDepartmentById(int id);
} 
5)映射文件配置
<?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="cn.offcn.mapper.DepartmentDao">
    <select id="getDepartmentById" resultMap="DepartemntResultMap2">
        SELECT d.*,e.* FROM department d, employee e WHERE
        e.e_depart_id=d.d_id AND d.d_id=#{id}
    </select>
 <resultMap id="DepartemntResultMap" type="Department">
       <id column="d_id" property="id"></id>
       <result column="d_name" property="name"></result>
       <collection property="emps" ofType="Employee">
            <id column="e_id" property="id"></id>
            <result column="e_name" property="name"></result>
            <result column="e_gender" property="gender"></result>
            <result column="e_age" property="age"></result>
       </collection>
 </resultMap>
 collection:当属性为集合时,使用collection标签进行映射。
6)测试方法
 @Test
 public void testGetDepartentById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
     Department dept=departmentDao.getDepartentById(1);
     //打印
     System.out.println(dept);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }
15.3.3 方式2:嵌套查询的方式
1)定义持久层接口
public interface EmployeeDao {
 public List<Employee> getEmployeeByDepartId(int id);
}
2)定义查询配置信息
<select id="getEmployeeByDepartId" resultType="Employee">
       select e_id id,e_name name,e_gender gender,e_age age
       from employee where e_depart_id=#{id}
</select>
3)修改配置文件
<select id="getDepartmentById" resultMap="DepartemntResultMap">
       select * from department where d_id=#{id}
    </select>
 <resultMap id="DepartemntResultMap" type="Department">
        <id column="d_id" property="id"></id>
        <result column="d_name" property="name"></result>
        <collection property="emps" ofType="Employee" column="d_id"
                 select="cn.offcn.mapper.EmployeeMapper.getEmployeeByDepartId">
        </collection>
 </resultMap>

此处变为单表查询,使分表查询方式进行查询。
4)测试方法
@Test
public void testGetDepartentById() throws  Exception{
  //获取SqlSession对象
  SqlSession session = MyBatisUtils.getSession();
  //调用SqlSession 创建 UserDao接口的代理对象
  DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
  Department dept=departmentDao.getDepartentById(1);
  //打印
  System.out.println(dept);
  //提交事务
  session.commit();
  //关闭连接
  MyBatisUtils.close(session);
}
15.4 多对多查询
15.4.1 创建数据库表
CREATE TABLE student(
 sid INT NOT NULL AUTO_INCREMENT,
 sname VARCHAR(30),
 PRIMARY KEY(sid)
);

CREATE TABLE teacher(
 tid INT NOT NULL AUTO_INCREMENT,
 tname VARCHAR(30),
 PRIMARY KEY(tid)
);

CREATE TABLE student_teacher(
 s_id INT NOT NULL,
 t_id INT NOT NULL,
 PRIMARY KEY(s_id,t_id),
 FOREIGN KEY(s_id) REFERENCES student(sid),
 FOREIGN KEY(t_id) REFERENCES teacher(tid)
);
INSERT INTO student(sname) VALUES('张三'),('李四');
INSERT INTO teacher (tname) VALUES('刘老师'),('李老师');
INSERT INTO student_teacher(s_id,t_id) 
VALUES(1,1),(1,2),(2,1)
15.4.2 方式1:嵌套结果方式
1)创建数据模型
public class Student {

 private int id;
 private String name;
 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }
}

public class Teacher {

 private int id;
 private String name;
 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }
}

public class StudentTeacher {
 private int sid;
 private int tid;
 public int getSid() {
     return sid;
 }

 public void setSid(int sid) {
     this.sid = sid;
 }

 public int getTid() {
     return tid;
 }

 public void setTid(int tid) {
     this.tid = tid;
 }

2)编写sql语句
SELECT s.*,ts.*,t.* 
FROM 
student s,student_teacher st,teacher t
WHERE 
s.sid=st.s_id AND st.t_id=t.tid AND s.sid=1
3)加入List属性
private List<StudentTeacher> studentTeacherList;
public List<StudentTeacher> getStudentTeacherList() {
     return studentTeacherList;
}
public void setStudentTeacherList(List<StudentTeacher> studentTeacherList) {
     this.studentTeacherList = studentTeacherList;
}
4)加入Teacher属性
private Teacher teacher;
public Student getStudent() {
     return student;
}
public void setStudent(Student student) {
     this.student = student;
}
5)加入查询方法
import cn.offcn.entity.Student;
public interface StudentDao {
 public Student getStudentById(int id);
}
6)映射文件配置
<select id="getStudentById" resultMap="StudentResultMap">
     SELECT s.*,ts.*,t.* FROM student s,teacher_student ts,teacher t
     WHERE s.sid=ts.s_id AND ts.t_id=t.tid AND s.sid=#{id}
 </select>
 <resultMap id="StudentResultMap" type="Student">
      <id column="sid" property="id"></id>
      <result column="sname" property="name"/>
      <collection property="studentTeacherList" ofType="StudentTeacher">
           <result column="s_id" property="sid"></result>
           <result column="t_id" property="tid"></result>
           <association property="teacher" javaType="Teacher">
               <id column="tid" property="id"></id>
               <result column="tname" property="name"></result>
           </association>
      </collection>
 </resultMap>
7)测试方法
@Test
public void testGetStudentById() throws  Exception{
  //获取SqlSession对象
  SqlSession session = MyBatisUtils.getSession();
  //调用SqlSession 创建 StudentDao接口的代理对象
  StudentDao studentDao = session.getMapper(StudentDao.class);
  Student student= studentDao.getStudentById(1);
  //打印
  System.out.println(tudent);
  //提交事务
  session.commit();
  //关闭连接
  MyBatisUtils.close(session);
}
15.4.3 方式2:嵌套查询方式
1)定义中间表的持久层接口
public interface StudentTeacherDao {
 public List<StudentTeacher> getStudentTeacherBySid(int sid);
}
2)定义查询配置信息
<!DOCTYPE mapper
     PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.offcn.dao.StudentTeacherDao">

 <select id="getStudentTeacherBySid" resultMap="StudentTeacherResultMap">
      select * from teacher_student where s_id=#{sid}
 </select>
 <resultMap id="StudentTeacherResultMap" type="StudentTeacher">
        <result column="s_id" property="sid"></result>
        <result column="t_id" property="tid"></result>
        <association property="teacher" javaType="Teacher"
          column="t_id" select="cn.offcn.dao.TeacherMapper.getTeacherByTid">              </association>
 </resultMap>
</mapper>
3)定义持久层的接口
public interface TeacherDao {

 public Teacher getTeacherByTid(int tid);
}
4)定义映射文件配置
<!DOCTYPE mapper
     PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.offcn.dao.TeacherDao">
 <select id="getTeacherByTid" resultType="Teacher">
         select tid id,tname name from teacher where tid=#{tid}
 </select>
</mapper>
5)修改持久层的方法
public interface StudentMapper {
 public Student getStudentById(int id);
}
6)修改查询配置信息
<select id="getStudentById" resultMap="StudentResultMap">
       select * from student where sid=#{id}
 </select>
 <resultMap id="StudentResultMap" type="Student">
     <id column="sid" property="id"></id>
     <result column="sname" property="name"/>
     <collection property="studentTeacherList" ofType="StudentTeacher"
     column="sid"
     select="cn.offcn.mapper.StudentTeacherMapper.getStudentTeacherBySid">
     </collection>
 </resultMap>
7)测试方法
@Test
public void testGetStudentById() throws  Exception{
  //获取SqlSession对象
  SqlSession session = MyBatisUtils.getSession();
  //调用SqlSession 创建 StudentDao接口的代理对象
  StudentDao studentDao = session.getMapper(StudentDao.class);
  Student student= studentDao.getStudentById(1);
  //打印
  System.out.println(tudent);
  //提交事务
  session.commit();
  //关闭连接
  MyBatisUtils.close(session);
}

第16节:延迟加载策略

首先说明什么是延迟加载及应用场景。特别要说明mybatis应用延迟加载的前得条件必须是嵌套方式才可以。其次解释使用延迟加载可减少访问数据库的频率,减少系统资源的消耗从而提高mybatis查询性能。分别介绍局部延迟加载和全局延迟加载的配置。
16.1 延迟加载的简介
1.什么是延迟加载?
延迟加载(lazy load)(也称为懒加载)关联关系对象默认的加载方式,延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
延迟加载,可以简单理解为,只有在使用的时候,才会发出sql语句进行查询。

2.为什么要使用延迟加载?
减少访问数据库的频率,我们要访问的数据量过大时,明显用缓存不太合适,因为内存容量有限为了减少并发量,减少系统资源的消耗。
16.2 配置延迟加载
在mybatis中使用resultMap来实现一对一,一对多,多对多关系的操作。主要是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能。
16.2.1 局部延时加载
1)配置文件
<select id="getDepartmentById" resultMap="DepartemntResultMap">
       select * from department where d_id=#{id}
    </select>
 <resultMap id="DepartemntResultMap" type="Department">
        <id column="d_id" property="id"></id>
        <result column="d_name" property="name"></result>
        <collection property="emps" ofType="Employee" column="d_id"
                 select="cn.offcn.mapper.EmployeeMapper.getEmployeeByDepartId"
                    fetchType=”lazy”>
        </collection>
 </resultMap>
相关联的查询标签上加 fetchType=”lazy” 
fetchType默认值为eager 立即加载,Lazy为延时加载。
2)测试类
@Test
public void testGetDepartentById() throws  Exception{
  //获取SqlSession对象
  SqlSession session = MyBatisUtils.getSession();
  //调用SqlSession 创建 UserDao接口的代理对象
  DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
  Department dept=departmentDao.getDepartentById(1);
  //打印
  System.out.println(dept.getName);
  //提交事务
  session.commit();
  //关闭连接
  MyBatisUtils.close(session);
}
16.2.2 全局延时加载
如果希望所有关联都需要延时加载,可以在mybatis的核心配置文件中进行配置,不用在collection或association中指定。默认全局开启。
<settings>
<!--开启延时加载开关-->
 <setting name="lazyLoadingEnabled" value="true"/>
 <!--关闭立即加载,实施按需加载-->
 <setting name="aggressiveLazyLoading" value="false"/>
</settings>

第17节:逆向工程

首先解我们企业开发中是先进行数据建模即建库建表并设定表和表之间的关系。可以举方活中的列子,如盖楼先有图纸,才能盖楼,而不是先盖楼再画图纸。mybatis考虑到对单表进行CRUD时,对于映射文件来讲只是标签和sql语句的不同,所以提供了逆向工程,可以通过表直接生成实体类、接口、接口的映射文件,来减轻开人员工作量,提高开发效率。接着可据官方文档搭建项目环境,编写逆向工程的配置文件、最后进行测试。
17.1 介绍
MyBatis的一个主要的特点就是需要程序员自己编写sql,那么如果表太多的话,难免会很麻烦,所以mybatis官方提供了一个逆向工程,可以针对单表自动生成mybatis执行所需要的代码(包括mapper.xml、mapper.java、pojo)。一般在开发中,常用的逆向工程方式是通过数据库的表生成代码。
17.2 使用
17.2.1 构建maven工程导入依赖
<dependencies>
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.4.6</version>
     </dependency>
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.47</version>
     </dependency>
     <dependency>
         <groupId>org.mybatis.generator</groupId>
         <artifactId>mybatis-generator-core</artifactId>
         <version>1.3.7</version>
     </dependency>
     <dependency>
         <groupId>log4j</groupId>
         <artifactId>log4j</artifactId>
         <version>1.2.16</version>
     </dependency>
</dependencies>
17.2.2 编写配置框架配置文件
?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 resource="db.properties"></properties>
  <settings>
      <setting name="lazyLoadingEnabled" value="true"/>
      <!--开启二级缓存-->
      <setting name="cacheEnabled" value="true"/>
  </settings>
  <typeAliases>
      <package name="cn.offcn.entity"></package>
  </typeAliases>
 <plugins>
     <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
 </plugins>

<environments default="development">
 <environment id="development">
     <transactionManager type="JDBC"></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>
<!--在核心配置文件中注册mapper-->
<mappers>
   <package name="cn.offcn.mapper"></package>
</mappers>
</configuration>
17.2.3 编写配置文件generator.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/mybatis828_002"
						userId="root" password="root">
   <!-- 只逆向当前库中的表 -->
   <property name="nullCatalogMeansCurrent" value="true"/>
		</jdbcConnection>


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

		<!-- targetProject:生成实体类的位置 -->
		<javaModelGenerator targetPackage="cn.offcn.entity"
			targetProject="项目名\src\main\java">
			<!-- enableSubPackages:是否让schema作为包的后缀 -->
			<property name="enableSubPackages" value="false" />
			<!-- 从数据库返回的值被清理前后的空格 -->
			<property name="trimStrings" value="true" />
		</javaModelGenerator>

		<!-- targetProject:mapper映射文件生成的位置 -->
		<sqlMapGenerator targetPackage="cn.offcn.mapper"
						 targetProject="项目名\src\main\java">
			<!-- enableSubPackages:是否让schema作为包的后缀 -->
			<property name="enableSubPackages" value="false" />
		</sqlMapGenerator>
		<!-- targetPackage:mapper接口生成的位置 -->
		<javaClientGenerator type="XMLMAPPER"
							 targetPackage="cn.offcn.mapper"
							 targetProject="项目名\src\main\java">
			<!-- enableSubPackages:是否让schema作为包的后缀 -->
			<property name="enableSubPackages" value="false" />
		</javaClientGenerator>
		<!-- 指定数据库表 -->
		<table tableName="department"></table>
		<table tableName="employee"></table>
		<table tableName="person"></table>
		<table tableName="idcard"></table>
		<table tableName="student"></table>
		<table tableName="teacher_student"></table>
		<table tableName="teacher"></table>
	</context>
</generatorConfiguration>
17.2.4 调用官方api实现逆向工程
package cn.test;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class GeneratorTest {

 public static void main(String[] args) throws Exception{
     List<String> warnings = new ArrayList<String>();
     boolean overwrite = true;
     //指定 逆向工程配置文件
     File configFile = new File("F:\\828class\\workspace\\mybatis\\mybatis004_reflect\\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);
 }

17.2.5 使用逆向工程完成数据CRUD
//添加员工
public static void addEmployee(){

     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     Employee employee=new Employee();
     employee.seteName("马大力");
     employee.seteGender("男");
     employee.seteAge(23);
     employee.seteDepartId(1);
     employeeMapper.insert(employee);
     session.commit();
     MyBatisUtils.close(session);

 }

 //根据id查询员工
 public static void queryEmployeeById(){

     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     Employee emp=employeeMapper.selectByPrimaryKey(2);
     System.out.println(emp);
     session.commit();
     MyBatisUtils.close(session);

 }

 //根据年龄查询员工
 public static void queryEmployeeByAge(){
     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     EmployeeExample employeeExample=new EmployeeExample();
     EmployeeExample.Criteria criteria=employeeExample.createCriteria();
     criteria.andEAgeGreaterThan(20);
     List<Employee> employeeList=employeeMapper.selectByExample(employeeExample);
     for (Employee employee : employeeList) {
         System.out.println(employee);
     }
     session.commit();
     MyBatisUtils.close(session);

 }

//根据年龄和性别查询员工
 public static void queryEmployeeByAgeAndGender(){
         SqlSession session = MyBatisUtils.getSession();
         EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
         EmployeeExample employeeExample=new EmployeeExample();
         EmployeeExample.Criteria criteria=employeeExample.createCriteria();
         criteria.andEAgeGreaterThan(20);
         criteria.andEGenderEqualTo("男");
         List<Employee> employeeList=employeeMapper.selectByExample(employeeExample);
         for (Employee employee : employeeList) {
             System.out.println(employee);
         }
         session.commit();
         MyBatisUtils.close(session);
 }

//根据id修改员工
 public static void updateEmployeeById(){
     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     Employee employee= employeeMapper.selectByPrimaryKey(1);
     employee.seteGender("女");
     employee.seteDepartId(2);
     employeeMapper.updateByPrimaryKey(employee);
     session.commit();
     MyBatisUtils.close(session);
 }

//根据id删除员工
 public static void deleteEmployeeById(){
     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     employeeMapper.deleteByPrimaryKey(10);
     session.commit();
     MyBatisUtils.close(session);
 }
//根据姓名更新员工
 public static void updateEmployeeByName(){
     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     Employee employee= employeeMapper.selectByPrimaryKey(1);
     employee.seteGender("男");
     employee.seteAge(24);
     EmployeeExample employeeExample=new EmployeeExample();
     EmployeeExample.Criteria criteria = employeeExample.createCriteria();
     criteria.andENameEqualTo(employee.geteName());
     employeeMapper.updateByExample(employee,employeeExample);
     session.commit();
     MyBatisUtils.close(session);
 }

 //分页查询员工
 public static void queryEmployees(int currentPage,int pageSize){

     PageHelper.startPage(currentPage,pageSize);
     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     EmployeeExample employeeExample=new EmployeeExample();
     List<Employee> employeeList=employeeMapper.selectByExample(employeeExample);
     PageInfo<Employee> pageInfo=new PageInfo<>(employeeList);
     List<Employee> empList=pageInfo.getList();  //取当前页的所有记录
     long totalRecored=pageInfo.getTotal();//总记录数
     int  totalPages=pageInfo.getPages(); //总页数
     System.out.println("总记录数:"+totalRecored);
     System.out.println("总页数:"+totalPages);
     for (Employee employee : empList) {
         System.out.println(employee);
     }
     session.commit();
     MyBatisUtils.close(session);
 }


 //根据性别和部门查询员工
 public static void queryEmployeeByGenderNum(){
     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     EmployeeExample employeeExample=new EmployeeExample();
     EmployeeExample.Criteria criteria = employeeExample.createCriteria();
     criteria.andEGenderEqualTo("男");
     criteria.andEDepartIdEqualTo(1);
     long count= employeeMapper.countByExample(employeeExample);
     System.out.println(count);
     session.commit();
     MyBatisUtils.close(session);
 }

//根据部门查询员工
 public static Employee queryEmployeeDepartmentById(int eid){
     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     Employee employee=employeeMapper.selectByPrimaryKey(eid);
     //查部门
     DepartmentMapper departmentMapper=session.getMapper(DepartmentMapper.class);
     Department department=departmentMapper.selectByPrimaryKey(employee.geteDepartId());
     employee.setDepart(department);
     session.commit();
     MyBatisUtils.close(session);
     return employee;
 }


 public static void main(String[] args) {
     addEmployee();
     queryEmployeeById();
     queryEmployeeByAge();

     queryEmployeeByAgeAndGender();

     updateEmployeeById();
     updateEmployeeByName();
     deleteEmployeeById();

     queryEmployees();
     queryEmployeeByGenderNum();
     Employee employee=queryEmployeeDepartmentById(1);
        System.out.println(employee.geteName()+"\t"
                           +employee.getDepart().getdName());
     queryEmployees(1,2);
 }

第18节:缓存

		本章首先介绍什么是缓存,为什么要使用缓存即提高mybatis查询性能。分别介绍一级缓存和二级缓存的应用场景并对数据存储进行分析。对于缓存数据存储要以画图的方式进行描述。
18.1 缓存简介
缓存是存在于内存中的临时数据,使用缓存的目的是减少和数据库的交互次数,提高执行效率。像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能,Mybatis 中缓存分为一级缓存,二级缓存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZqVboUJ-1666713220237)(images/mybatis缓存.png)]

18.2 一级缓存
18.2.1 介绍
mybatis一级缓存一种是SESSION级别的,针对同一个会话SqlSession中,执行多次条件完全相同的同一个sql,那么会共享这一缓存。
18.2.2 一级缓存结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D5M6EuHm-1666713220237)(images/一级缓存结构图.png)]

18.2.3 编写持久层接口
public interface EmployeeDao {

    public Employee getEmployeeById(int id);
}
18.2.4 编写映射文件
 <select id="getEmployeeById" resultMap="EmployeeResultMap">
       SELECT * FROM employee WHERE e_id=#{id}
 </select>
 <resultMap id="EmployeeResultMap" type="Employee">
     <id column="e_id" property="id"></id>
     <result column="e_name" property="name"></result>
     <result column="e_gender" property="gender"></result>
     <result column="e_age" property="age"></result>
 </resultMap>
18.2.5 编写测试方法
public static void firstCahce(){

     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     //第一次查询
     Employee employee1=employeeMapper.getEmployeeById(1);
     System.out.println(employee1.getName());

     //第二次查询
     Employee employee2=employeeMapper.getEmployeeById(1);
     System.out.println(employee2.getName());
     MyBatisUtils.close(session);

 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-caPTiBPl-1666713220238)(images/一级缓存.png)]

18.2.6 一级缓存的分析
从上面的代码可以出,我们写了两次查询操作,但在访问数据时,只有一次。第一次先从一级缓存中获取,因为session是新创建的,一级缓存中没有数据,于是就查询数据获取数据,然后把查询的数据放到一级缓存中,此时一定要注意的是,一级缓存是一个Map集合,map的key是你的查询条件字符串,值就是查询出来的对象。等第二次查询时,先从一缓存中获取,因为上一次查询后已经放到一级缓存中了,所以从一级缓存中获取到了,就不用访问数据库了,减少和数据次的一次交互,提高了执行效率。
18.2.7 测试一级缓存的清空
当我们在两次查询之间做增、删、改操作都会把一级缓存清空,因为不清空就不能保证缓存中的数据与数据库中数据的一致性,可能会读取不正确的数据。
public static void firstCahce(){

     SqlSession session = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
     //第一次查询
     Employee employee1=employeeMapper.getEmployeeById(1);
     System.out.println(employee1.getName());
      //新增员工
     employeeMapper.addEmployee(new Employee("张小志","男",18));
     //第二次查询
     Employee employee2=employeeMapper.getEmployeeById(1);
     System.out.println(employee2.getName());
     MyBatisUtils.close(session);
 }
18.3 二级缓存
18.3.1 二级缓存简介
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个
SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
18.3.2 二级缓存结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tynq62Fc-1666713220239)(images/二级缓存结构图.png)]

18.3.3 二级缓存的开启与关闭
1)主配置文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
2)配置相关的 Mapper 映射文件
<!DOCTYPE mapper
     PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.offcn.dao.EmployeeDao">
   <!-- 开启二级缓存的支持 -->
   <cache></cache>
</mapper>
3)配置 statement 上面的 useCache 属性
<select id="getEmployeeById" resultMap="EmployeeResultMap" useCache="true">
       SELECT * FROM employee WHERE e_id=#{id}
 </select>
 <resultMap id="EmployeeResultMap" type="Employee">
     <id column="e_id" property="id"></id>
     <result column="e_name" property="name"></result>
     <result column="e_gender" property="gender"></result>
     <result column="e_age" property="age"></result>
 </resultMap>
将 EmployeeDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
4)二级缓存测试
public static void secondCahce(){

     SqlSession session1 = MyBatisUtils.getSession();
     SqlSession session2 = MyBatisUtils.getSession();
     EmployeeMapper employeeMapper1=session1.getMapper(EmployeeMapper.class);
     //第一次查询
     Employee employee1=employeeMapper1.getEmployeeById(1);
     System.out.println(employee1.getName());
     session1.commit();
     MyBatisUtils.close(session1);
     //第二次查询
     EmployeeMapper employeeMapper2=session2.getMapper(EmployeeMapper.class);
     Employee employee2=employeeMapper2.getEmployeeById(1);
     System.out.println(employee2.getName());
     session2.commit();
     MyBatisUtils.close(session2);
}
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二
次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5q9XBJwb-1666713220239)(images/二级缓存查询结果.png)]

第19节:分页插件

本节讲解分页插件的使用步聚,首先进行分页插件介绍,引入分页配置,对分页的理原进行分析,强调该拦截器在执行sql之前会附加limit关键字,推带分页配置并把分页信息封装到PageInfo对象中。
19.1 分页插件的介绍
分页是一种将所有数据分段展示给用户的技术.用户每次看到的不是全部数据,而是其中的一部分,如果在其中没有找到自己想要的内容,用户可以通过制定页码或是翻页的方式转换可见内容,直到找到自己想要的内容为止。
分页的的好处:
1.提高性能,一次查20个,比一次查20000个性能肯定更好;另外如果数据量很大,一次性将内容都查询出来,查询出来的结果是放在内存存里面的,会增加cpu的开销造成内存的浪费,效率极低。
2.展现层面的考虑:如果一次展现太多的数据,不管是排版,还是美观上都不好。

19.2 分页插件的引入和配置
1.在pom.xml中引入插件依赖
<dependency>
 <groupId>com.github.pagehelper</groupId>
 <artifactId>pagehelper</artifactId>
 <version>5.1.10</version>
</dependency>
2.在mybatis核心配置文件中进行配置
<plugins>
 <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

19.3 定义接口方法
public class EmployeeDao{
 public void getInfo();
}
19.4 配置接口方法
<select id="getInfo" resultType="cn.offcn.entity.Employee">
       SELECT * FROM employee
</select>
19.5 测试分页插件
@Test
public void queryEmployees(){
     //在查询前设置当前页与每页显示的记录数
     PageHelper.startPage(1,5);
     SqlSession session = MyBatisUtils.getSession();
     EmployeeDao employeeDao=session.getMapper(EmployeeDao.class);
     //查询
     List<Employee> employeeList=employeeDao.getInfo();
     //创建一个PageInfo对象,把查询出的结果集合当做参数传入
     PageInfo<Employee> pageInfo=new PageInfo<>(employeeList);
     List<Employee> empList=pageInfo.getList();  //取当前页的所有记录
     long totalRecored=pageInfo.getTotal();//总记录数
     int  totalPages=pageInfo.getPages(); //总页数
     System.out.println("总记录数:"+totalRecored);
     System.out.println("总页数:"+totalPages);
     for (Employee employee : empList) {
         System.out.println(employee);
     }
     session.commit();
     MyBatisUtils.close(session);
 }

第20节:注解开发

本节主要介绍注解开发的应用背景及注解开发的流程,首先对注解进行介绍,其次对单表接口进行注解配置并演示运行结果,在讲解一对多和一对一关系注解映射时,要对比xml配置方进行解决,方便学员理解。
20.1 注解开发的背景介绍
注解提供了一种简单的方式来实现简单映射语句,而不会引入大量的开销。能够读懂别人写的代码,特别是框架相关的代码。本来可能需要很多配置文件,需要很多逻辑才能实现的内容,就可以使用一个或者多个注解来替代,这样就使得编程更加简洁,代码更加清晰。
20.2 常用注解介绍
这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。本次我们先围绕一些基本的 CRUD 来学习,再学习复杂映射关系及延迟加载。
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
20.3 使用
20.3.1 构建maven工程导入依赖
<dependencies>
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
  </dependency>
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</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.17</version>
  </dependency>
</dependencies>
20.3.2 注解实现基本 CRUD
1)编写实体类
public class Worker {
 private Integer id;
 private String name;
 private String gender;
 private Integer age;
 public Worker(){}
 public Worker(String name, String gender, Integer age) {
     this.name = name;
     this.gender = gender;
     this.age = age;
 }

 public Integer getId() {
     return id;
 }

 public void setId(Integer id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public String getGender() {
     return gender;
 }

 public void setGender(String gender) {
     this.gender = gender;
 }

 public Integer getAge() {
     return age;
 }

 public void setAge(Integer age) {
     this.age = age;
 }

 @Override
 public String toString() {
     return "Worker{" +
             "id=" + id +
             ", name='" + name + '\'' +
             ", gender='" + gender + '\'' +
             ", age=" + age +
             '}';
 }
}
2)持久层接口
public interface WorkerMapper {

 @Insert("insert into worker (w_name,w_gender,w_age) " +
         "values(#{name},#{gender},#{age})")
 public void saveWorker(Worker worker);

 @Update("update worker set w_name=#{name},w_gender=#{gender},w_age=#{age} where w_id=#{id}")
 public void updateWorker(Worker worker);

 @Delete("delete from worker where w_id=#{id}")
 public void deleteWorkerById(Integer id);

 @Select("select * from worker where w_id=#{id}")
 @Results({
         @Result(column = "w_id",property="id"),
         @Result(column = "w_name",property="name"),
         @Result(column = "w_gender",property="gender"),
         @Result(column = "w_age",property="age")
 })
 public Worker getWorkerById(Integer id);

 @Select("select * from worker")
 @Results({
         @Result(column = "w_id",property="id"),
         @Result(column = "w_name",property="name"),
         @Result(column = "w_gender",property="gender"),
         @Result(column = "w_age",property="age")
 })
 public List<Worker> getWorkers();
}

3)编写主配置文件
<?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>
<!-- 配置 mybatis的环境 -->
<environments default="development">
  <!-- 配置环境 -->
  <environment id="development">
      <!-- 配置事务的类型 -->
      <transactionManager type="JDBC"></transactionManager>
      <!-- 配置连接数据库的信息:用的是数据源【连接池】-->
      <dataSource type="POOLED">
          <property name="driver" value="com.mysql.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/mybatis002"/>
          <property name="username" value="root"/>
          <property name="password" value="root"/>
      </dataSource>
  </environment>
</environments>
<mappers>
   <package name="cn.offcn.dao"></package>
</mappers>
</configuration>
4)编写测试方法
import cn.offcn.dao.WorkerMapper;
import cn.offcn.entity.Worker;
import cn.offcn.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class WorkerTest {

 @Test
 public void testSaveWorker(){

     SqlSession session = MyBatisUtils.getSession();
     WorkerMapper workerMapper = session.getMapper(WorkerMapper.class);
     Worker worker=new Worker("白晓云","女",20);
     workerMapper.saveWorker(worker);
     session.commit();
     MyBatisUtils.close(session);
 }

 @Test
 public void testUpdateWorker(){
     SqlSession session = MyBatisUtils.getSession();
     WorkerMapper workerMapper = session.getMapper(WorkerMapper.class);
     Worker worker=new Worker(1,"白晓云","男",22);
     workerMapper.updateWorker(worker);
     session.commit();
     MyBatisUtils.close(session);
 }

 @Test
 public void testDeleteWorkerById(){
     SqlSession session = MyBatisUtils.getSession();
     WorkerMapper workerMapper = session.getMapper(WorkerMapper.class);
     workerMapper.deleteWorkerById(1);
     session.commit();
     MyBatisUtils.close(session);
 }

 @Test
 public void testGetWorkerById(){
     SqlSession session = MyBatisUtils.getSession();
     WorkerMapper workerMapper = session.getMapper(WorkerMapper.class);
     Worker worker= workerMapper.getWorkerById(2);
     System.out.println(worker);
     MyBatisUtils.close(session);
 }

 @Test
 public void testGetWorkers(){
     SqlSession session = MyBatisUtils.getSession();
     WorkerMapper workerMapper = session.getMapper(WorkerMapper.class);
     List<Worker> workerList=workerMapper.getWorkers();
     workerList.forEach(System.out::println);
     MyBatisUtils.close(session);
 }
}

20.4 注解实现复杂关系映射
20.4.1 复杂关系映射的注解介绍
@Results  注解
代替的是标签<resultMap>
该注解中可以使用单个@Result 注解,也可以使用@Result 集合
@Results{@Result(),@Result()})或@Results@Result())
@Resutl 注解
代替了 <id> 标签和<result> 标签
@Result  中  属性介绍:
id 是否是主键字段
column 数据库的列名
property 需要装配的属性名
one 需要使用的@One 注解(@Result(one=@One)()))
many 需要使用的@Many 注解(@Result(many=@many)()))
@One  注解(一对一)
代替了<assocation> 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One  注解属性介绍:
select 指定用的 来多表查询的 sqlmapper
fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。
使用格式:
@Result(column=" ",property="",one=@One(select=""))
@Many  注解(多对一)
代替了<Collection> 标签, 是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType
(一般为 ArrayList)但是注解中可以不定义;
使用格式:
@Result(property="",column="",many=@Many(select=""))
20.4.2 一对一复杂关系映射及延迟加载
1)添加 实体类
Person实体类:
public class Person {

 private int id;
 private String name;
 private IdCard idCard;


 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public IdCard getIdCard() {
     return idCard;
 }

 public void setIdCard(IdCard idCard) {
     this.idCard = idCard;
 }
}
IdCard实体类:
public class IdCard {

 private int id;
 private String cardno;
 private Date useLife;

 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getCardno() {
     return cardno;
 }

 public void setCardno(String cardno) {
     this.cardno = cardno;
 }

 public Date getUseLife() {
     return useLife;
 }

 public void setUseLife(Date useLife) {
     this.useLife = useLife;
 }

}

2)持久层接口并使用注解配置
public interface IdCardMapper {

@Select("select * from idcard where c_id=#{id}")
 @Results({
         @Result(column = "c_id",property = "id"),
         @Result(column = "c_cardno",property = "cardno"),
         @Result(column = "e_uselife",property = "useLife")
 })
 public IdCard getIdCardByPersonId(int id);
}
3)持久层接口并使用注解配置
public interface PersonMapper {

 @Select(select * from person where  p_id=#{id})
 @Results({
         @Result(column = "p_id",property = "id"),
         @Result(column = "p_name",property = "name"),
         @Result(property = "idCard",
                 javaType = IdCard.class,
                 column = "p_id",
                 one = @One(select = "cn.offcn.dao.IdCardDao.getIdCardByPersonId",fetchType = FetchType.LAZY)
         )
 })
 public Person getPersonById(int id);
}
4)测试一对一关联及延迟加载
@Test
public void testGetPersonById(){

     SqlSession session=MyBatisUtils.getSession();
     PersonMapper personMapper=session.getMapper(PersonMapper.class);
     Person person=personMapper.getPersonById(1);
     System.out.println(person)
     MyBatisUtils.close(session);

 }
20.4.3 一对多复杂关系映射
1)构建实体类
Department实体类:
public class Department {

 private int id;
 private String name;
 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

}
Employee实体类:
public class Employee {

 private int id;
 private String name;
 private String gender;
 private Integer age;


 public Employee(){}

 public Employee(int id,String name, String gender, int age) {
     this.id=id;
     this.name = name;
     this.gender = gender;
     this.age = age;
 }
 public Employee(String name, String gender, int age) {
     this.name = name;
     this.gender = gender;
     this.age = age;
 }

 public int getId() {
     return id;
 }

 public void setId(int id) {
     this.id = id;
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public String getGender() {
     return gender;
 }

 public void setGender(String gender) {
     this.gender = gender;
 }

 public Integer getAge() {
     return age;
 }

 public void setAge(Integer age) {
     this.age = age;
 }
}

2)Department实体类加入 List
private List<Employee> emps;
public List<Employee> getEmps() {
     return emps;
}

public void setEmps(List<Employee> emps) {
     this.emps = emps;
}
3) 持久层接口并使用注解配置
public interface EmployeeMapper{

 @Select("select * from employee where e_depart_id=#{id}")
 @Results({
         @Result(column = "e_id",property = "id"),
         @Result(column = "e_name",property = "name"),
         @Result(column = "e_gender",property = "gender"),
         @Result(column = "e_age",property = "age")
 })
 public List<Employee> getEmployeeByDepartId(int id);


}
4)持久层接口并使用注解配置
public interface DepartmentDao {

 @Select("select d_id id,d_name name from department where d_id=#{id}")
 public Department getDepartmentById(int id);

 @Select("select * from department where d_id=#{id}")
 @Results({
         @Result(column = "d_id",property = "id"),
         @Result(column = "d_name",property = "name"),
         @Result(property = "emps",
                 column = "d_id",
                 many = @Many(select = "cn.offcn.dao.EmployeeMapper.getEmployeeByDepartId",fetchType = FetchType.LAZY))
 })
 public Department getDepartmentById(int id);
}
5)添加测试方法
public void  testGetDepartmentById(){

     SqlSession session=MyBatisUtils.getSession();
     DepartmentMapper departmentMapper=session.getMapper(DepartmentMapper.class);
     Department department=departmentMapper.getDepartmentById(1);
     System.out.println(department)
     MyBatisUtils.close(session);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在我的身边

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值