SSM框架

1.Spring

1.定义

  • 1.不同的语境中,Spring 所代表的含义是不同的
  • 2.Spring包含广义狭义两个语境

1.广义

  • 1.广义上的 Spring 泛指以Spring Framework为核心的Spring技术栈
  • 2.Spring 不是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目组成的成熟技术栈
    • 1.Spring Framework
    • 2.Spring MVC
    • 3.SpringBoot
      • 1.Spring团队提供的全新框架,为Spring以及第三方库提供开箱即用的配置,可以简化Spring应用的搭建及开发过程
    • 4.Spring Cloud
      • 1.基于Spring Boot实现的微服务框架,并不是某一门技术而是一系列微服务解决方案或框架的有序集合
      • 2.将市面上成熟的、经过验证的微服务框架整合起来,并通过Spring Boot进行再封装,屏蔽调其中复杂的配置和实现原理,提供了一套简单易懂、易部署和易维护的分布式系统开发工具包
    • 5.Spring Data
      • 1.Spring技术栈提供的数据访问模块,对JDBCORM提供了很好的支持
    • 6.Spring Security
      • 1.身份验证和访问控制框架

2.狭义

  • 1.狭义的Spring特指Spring Framework,通常称为Spring框架,一般作为存放bean对象的容器
  • 2.Spring框架是一个分层的、面向切面的Java应用程序的一站式轻量级解决方案,它是Spring技术栈的核心和基础
  • 3.Spring有两个核心部分: IOCAOP
    • 1.IOC:控制反转(Inverse of Control) ,指把创建对象过程交给Spring进行管理
    • 2.AOP:面向切面编(Aspect Oriented Programming),指封装类的公共行为,将那些与业务无关却被业务模块共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度,同时AOP还能解决日志、事务、权限等问题

2.架构图

1.Spring4.0.x版本架构图

在这里插入图片描述

  • 1.Test:简化Spring项目的测试
  • 2.Core Container:核心容器,Spring作为工厂的实现
  • 3.AOP+Aspects:面向切面编程,是面向对象编程有利的补充,可以非侵入(不需要改写原有代码)的为代码添加额外功能
  • 4.Instrumentation+Messaging:辅助功能
  • 5.Data Access/Integration:提供数据库访问
  • 6.WebSpringJavaWeb开发的支持

2.Spring5.0.x版本架构图

在这里插入图片描述

  • 1.Core Container:核心容器,由以下四个模块组成
    • 1.spring-beansspring-core模块:Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)
    • 2.spring-context模块:构架于核心模块之上,扩展了BeanFactory并为其添加Bean生命周期
    • 3.spring-expression模块:统一表达式语言EL的扩展模块,可以查询、管理运行中的对象,同时也可以调用对象方法、操作数组、集合等
  • 2.AOP:面向切面编程
    • 1.spring-aop模块:Spring的另一个核心模块,是AOP主要的实现模块,提供了面向切面编程实现
    • 2.spring-aspects模块:集成自AspectJ框架,主要是为AOP提供多种实现方法
    • 3.spring-instrument模块:基于JAVA SE中的java.lang.instrument进行设计,算是AOP的一个支援模块,主要作用是在JVM启用时生成一个代理类,开发者通过代理类在运行时修改类的字节从而改变一个类的功能,实现AOP的功能
    • 4.spring-messaging模块:报文发送模块,主要职责是为Spring框架集成一些基础的报文传送
  • 3.Data Access:数据访问集成,由以下五个模块组成
    • 1.spring-jdbc模块:Spring提供的JDBC抽象框架的主要实现模块,用于简化Spring JDBC
    • 2.spring-tx模块:Spring JDBC事务控制实现模块,对事务进行封装从而通过AOP配置可以将事务灵活的配置在任何一层
    • 3.spring-orm模块:ORM框架支持模块,用于资源管理、数据访问对象(DAO)的实现
    • 4.spring-jms模块:指Java消息服务,提供一套 消息生产者、消息消费者模板,JMS用于在两个应用程序之间或分布式系统中发送消息,进行异步通信
    • 5.spring-oxm模块:提供一个抽象层以支撑 OXMObject-To-XML-Mapping),用于将Java对象映射成XML数据或将XML数据映射成Java对象
  • 4.WebJavaWeb编程模块,由以下四个模块组成
    • 1.spring-web模块:为Spring提供基础Web支持,主要建立于核心容器之上通过 ServletListeners 来初始化 IOC 容器
    • 2.spring-webmvc模块: 实现Spring MVC(model-view-Controller)的Web应用
    • 3.spring-websocket模块:提供了简单的接口,只要实现响应的接口就可以快速的搭建WebSocket Server从而实现双向通讯
    • 4.spring-webflux模块:非堵塞函数式Reactive Web框架,用来建立异步的,非阻塞,事件驱动的服务
  • 5.Test:即spring-test模块,主要为测试提供支持,Spring支持Junit测试框架
  • 6.Spirng各模块之间的依赖关系
    在这里插入图片描述

3.使用步骤

1.搭建开发环境

1.新建Maven项目
  • 1.参考ShardingSphere文章的创建Maven项目
2.导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

在这里插入图片描述

  • 1.pom.xml文件中导入需要用到的Spring核心依赖
  • 2.根据上述Spring模块关系依赖图可知导入spring-context模块即可,其包含Spring常用核心依赖
3.创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
  • 1.创建applicationContext.xml配置文件,一般放在resources根目录下
    在这里插入图片描述

2.创建实体类

package domains;

import lombok.Data;

@Data
public class Person {
   private String name;
   private Integer age;
   private String sex;
   private Double weight;
   private Double height;
   private String phone;

   private Animal cat;
}
package domains;

import lombok.Data;

@Data
public class Animal {
   private String name;
}

3.将对象交给Spring工厂管理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        通过bean标签将对象交给Spring工厂管理
        id: 唯一标识
        class: 类的全限定名(包名+类名)
    -->
    <bean id="person" class="domains.Person">
        <property name="name" value="李四"></property>
        <property name="age" value="18"></property>
        <property name="sex" value=""></property>
        <property name="height" value="175"></property>
        <property name="weight" value="62"></property>
        <property name="phone" value="13465034256"></property>

<!--        <property name="cat" ref="animal"></property>-->
        <property name="cat">
            <ref bean="animal"/>
        </property>
    </bean>

	<bean id="animal" class="domains.Animal">
        <property name="name" value="喵喵"/>
    </bean>
</beans>
  • 1.配置文件中通过bean标签将对象交给Spring工厂管理
  • 2.声明的同时可以注入属性值,不同类型的属性有不同的注入方式,参考下述注入方式

4.使用Spring工厂获取对象

package controller;

import domains.Person;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
   public static void main(String[] args) {
	    // 从类路径中获取上下文定义文件(独立的XML应用程序上下文)
       ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       Person person = (Person) context.getBean("person");
       System.out.println(person);
   }
}

在这里插入图片描述

  • 1.通过配置文件applicationContext.xml获取到ClassPathXmlApplicationContext 对象
  • 2.通过该对象的getBean()方法获取到相应对象
  • 3.注意:applicationContext.xml是独立的XML应用程序上下文,而ClassPathXmlApplicationContext 通过类路径获取到该上下文定义文件

4.注入方式

1.Set注入

  • 1.配置文件的bean标签中定义property子标签,并在property子标签中设置属性的默认值
  • 2.该注入方式本质通过无参构造创建对象,并调用属性的set()方法进行赋值
  • 3.注意:使用该方式注入的类中必须要有无参构造,属性必须要有set()方法,否则配置文件的对应属性会爆红出错
    在这里插入图片描述
    在这里插入图片描述
1.属性是基本类型+String类型

在这里插入图片描述

<bean id="person" class="domains.Person" scope="prototype">
       <property name="name"><value>李四</value></property>
       <property name="age" value="18"/>
       <property name="sex" value=""></property>
</bean>
  • 1.直接使用property子标签即可,使用name指定属性名,使用value指定属性值,并且有以上三种不同的写法
2.属性是对象(自定义类型属性)

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

<bean id="person" class="domains.Person" scope="prototype">
       <property name="name"><value>李四</value></property>
       <property name="age" value="18"/>
       <property name="sex" value=""></property>
       
<!--    <property name="cat" ref="animal"></property>-->
       <property name="cat">
           <ref bean="animal"/>
       </property>
   </bean>

   <bean id="animal" class="domains.Animal">
       <property name="name" value="喵喵"/>
   </bean>
  • 1.首先需要先声明引用的对象,然后通过property 子标签即可,有以上两种方式
    • 1.只使用property 子标签,通过name指定属性名,通过ref指定声明对象的唯一标记id
    • 2.先使用property 子标签,通过name指定属性名,再使用ref标签,通过bean指定声明对象的唯一标记id
3.属性是数组,List或Set集合

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

   <bean id="testArray" class="domains.TestArray">
       <property name="ids">
           <array>
               <value>1</value>
               <value>2</value>
               <value>3</value>
               <value>3</value>
           </array>
       </property>
   </bean>

   <bean id="testList" class="domains.TestList">
       <property name="ids">
           <list>
               <value>1</value>
               <value>2</value>
               <value>3</value>
               <value>3</value>
           </list>
       </property>
   </bean>

   <bean id="testSet" class="domains.TestSet">
       <property name="ids">
           <set>
               <value>1</value>
               <value>2</value>
               <value>3</value>
               <value>3</value>
           </set>
       </property>
   </bean>

   <bean id="testArrayObject" class="domains.TestArrayObject">
       <property name="persons">
           <array>
               <ref bean="person" />
               <ref bean="person" />
           </array>
       </property>
   </bean>

   <bean id="testListObject" class="domains.TestListObject">
       <property name="persons">
           <list>
               <ref bean="person"/>
               <ref bean="person"/>
           </list>
       </property>
   </bean>

   <bean id="testSetObject" class="domains.TestSetObject">
       <property name="persons">
           <set>
               <ref bean="person"/>
               <ref bean="person"/>
           </set>
       </property>
   </bean>

在这里插入图片描述

  • 1.以上三种属性都是在property子标签中嵌套不同的子标签
    • 1.数组使用array子标签
    • 2.List集合使用list子标签
    • 3.Set集合使用set子标签
  • 2.如果属性是基本类型或String类型,可以在相应子标签中使用value子标签进行赋值
  • 3.如果属性是引用类型,则先创建引用类型对象的声明,然后通过ref子标签的bean属性指定声明对象的唯一标记id
  • 4.注意:arraylistset子标签可以互换,但set子标签会对结果进行去重操作,所以一般见名知意,各自使用各自的对应的标签
4.属性是Map集合

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

	<bean id="testMap" class="domains.TestMap">
       <property name="ids">
           <map>
               <entry key="1" value="李四"/>
               <entry key="1" value="王五"/>
               <entry key="2" value="赵六"/>
           </map>
       </property>
       <property name="personMap">
           <map>
               <entry key-ref="person" value-ref="person"/>
               <entry key-ref="person" value-ref="person"/>
           </map>
       </property>
   </bean>

   <bean id="testProperties" class="domains.TestProperties">
       <property name="ids">
           <props>
               <prop key="1">李四</prop>
               <prop key="1">王五</prop>
               <prop key="2">赵六</prop>
               <prop key="3">骑士</prop>
           </props>
       </property>
   </bean>

在这里插入图片描述

  • 1.普通的Map集合使用的是map子标签,properties使用的是props子标签
  • 2.map子标签中嵌套的entry子标签
    • 1.如果Map集合中存放的是基本数据类型和String类型,则直接使用key指定键,使用value指定值
    • 2.如果Map集合中存放的是引用数据类型,则需要先创建引用类型对象的声明,然后通过key-ref属性指定声明对象的唯一标记id作为键,通过value-ref属性指定声明对象的唯一标记id作为值
  • 3.Properties是特殊的Map集合需要使用特殊的propsprop搭配使用
  • 4.注意
    • 1.Map中的键如果重复则前者的值会被后者的值覆盖
    • 2.Properties的输出结果与bean赋值的顺序相反但其余同Map一致

2.构造注入

在这里插入图片描述

	<bean id="user" class="domains.User">
       <!--一个constructor-arg代表构造函数中的一个参数,可以使用name指定名称-->
       <constructor-arg name="name" value="李四"/>
       <constructor-arg name="age" value="18"/>
       <constructor-arg value="" type="java.lang.String"/>
       <constructor-arg ref="animal" index="3"/>
   </bean>

在这里插入图片描述

  • 1.配置文件的bean标签中定义constructor-arg子标签,并在constructor-arg子标签中设置属性的默认值
  • 2.一个constructor-arg子标签代表构造函数中的一个参数
    • 1.使用name指定参数名
    • 2.使用value赋值
    • 3.使用tepe指定类型
    • 4.使用index指定在参数中的位置,注意不存在参数类型相同,顺序相同的两个方法,因为这是同一个方法
    • 5.使用ref指定声明对象的唯一标记id
  • 3.该注入方式本质通过调用不同的构造方法创建对象,并通过该构造方法注入默认值
  • 4.注意:使用该方式注入的类中必须要有对应的构造方法,否则配置文件的对应属性会爆红出错
    在这里插入图片描述
    在这里插入图片描述
1.赋值为null
   <bean id="user" class="domains.User">
       <!--一个constructor-arg代表构造函数中的一个参数,可以使用name制定名称-->
       <constructor-arg name="name"><null></null></constructor-arg>
       <constructor-arg name="age" ><null></null></constructor-arg>
       <constructor-arg value="" type="java.lang.String"/>
       <constructor-arg ref="animal" index="3"/>
   </bean>
  • 1.构造注入注入空值需要使用null子标签,不能直接使用value=null
2.内部bean
  <bean id="person" class="domains.Person" scope="prototype">
       <property name="name"><value>李四</value></property>
       <property name="age" value="18"/>
       <property name="sex" value=""></property>

       <property name="cat">
           <bean class="domains.Animal">
               <property name="name" value="喵喵"/>
           </bean>
       </property>
   </bean>
  • 1.当要求一个bean对象不能被其他类获取,只能被当前bean使用时,可以注入为内部bean
  • 2.内部bean不需要id,因为其只能跟随外部bean一起获取,不能被其他bean获取,其他和正常的bean一致

5.代理

1.代理模式

  • 产出者:只完成根本需求
  • 使用者:需要使用根本需求+额外服务
  • 代理类:只有额外服务
  • 代理类完成额外的功能,根本功能还是交给基本类,使用类使用代理类,代理类一般使用proxy

1.静态代理

  • 代理类必须跟原始类实现同一个接口或者继承同一个父类
  • 代理类中只提供额外功能,核心功能在代理中以调用原始类的原始方式的形式出现
  • 调用者在使用方法时,要创建代理类的对象,调用者只和代理类打交道
package service;

public interface PersonService {
    void insert();
}
package service.impl;

import service.PersonService;

public class PersonServiceImpl implements PersonService {
    public void insert() {
        System.out.println("调用了insert方法");
    }
}
package service.proxy;

import service.PersonService;
import service.impl.PersonServiceImpl;

public class PersonServiceProxy implements PersonService {

    private PersonServiceImpl ps = new PersonServiceImpl();
    public void insert() {
        System.out.println("设置手动提交事务");
        try {
            ps.insert();
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            throw new RuntimeException();
        }
    }
}

2.动态代理

  • 静态代理问题:
  • 1.随着代理的增多,代理类也会逐步增加,代理类代理过于冗长,不利于代理的维护
  • 2.多个原始类会使用同一个代理类的增强,静态代理中,原始类每有一个方法,代理类就得写一个方法,无形中也会出现代码冗余的情况
  • 解决方法:Spring的动态代理
  • 动态代理:代理类不需要手动编写了,当你提供原始类,以及增强代码之后,Spring会自动生成代理类
  • 好处:提高开发效率,降低了冗余代理
1.开发步骤
  • 1.导入依赖
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
  • 2.编写原始类,交给Spring工厂管理
package service;

public interface UserService {

    void selectAll();
}

package service.impl;

import service.UserService;

public class UserServiceImpl implements UserService {
    public void selectAll() {
        System.out.println("selectAll方法被调用");
    }
}

  • 3.写增强类,交给Spring工厂管理
package advice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class MyBeforeAdvice implements MethodBeforeAdvice {
    //在原始方法之前执行的增强

    /**
     *
     * @param method 被增强的原始方法对象
     * @param args 原始方法的参数
     * @param target 返回值
     * @throws Throwable
     */
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("myBeforeAdvice do ....");
        System.out.println(method);
        System.out.println(args);
        System.out.println(target);
    }
}
  • 4.配置切入点
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--原始类-->
    <bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>

    <!--增强类-->
    <bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>

    <aop:config>
        <!--编织切入点
        id:自定义
        expression:指明原始类需要增强的方法是谁
    -->
        <aop:pointcut id="userServiceAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <!--组装:将增强功能与被增强方法组装
            advice-ref:增强功能的类的bean的id
            pointcut-ref:被增强的切入点id
        -->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceAdvice" />
    </aop:config>
</beans>
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@7d68ef40
service.impl.UserServiceImpl@5b0abc94
selectAll方法被调用
  • 注意context中包含aop组件,所以不需要再导入,不过需要导入aspectj,用于实现AOP做切入点表达式、aop相关注解。而且使用aop在xml中需要导入aop的命名空间,否则会出现没有aop:config的声明错误,xmlns:aop=“http://www.springframework.org/schema/aop”,http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
  • 且使用getClass获取对象的类对象后发现该类为代理类class com.sun.proxy.$Proxy3,本质和静态代理一样,只不过是由spring帮我们生成
1.通知(advice)
  • 通知(功能增强):为目标方法添加额外功能
  • 根据通知在原始方法添加的位置:前置通知,后置通知,异常通知,以及环绕通知
1.前置通知
  • 通知会在原始方法之前执行,实现类MethodBeforeAdvice
2.后置通知
  • 通知会在原始方法之后执行,实现类AfterReturningAdvice
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
        userServiceImpl.selectAll();
        System.out.println(userServiceImpl.getClass());
 /**
 myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25b485ba
service.impl.UserServiceImpl@2b546384
selectAll方法被调用
MyAfterAdvice do ....
null
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25b485ba
service.impl.UserServiceImpl@2b546384
class com.sun.proxy.$Proxy3
*/
<!--原始类-->
    <bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>

    <!--前置增强类-->
    <bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>

    <!--后置增强类-->
    <bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>

    <aop:config>
        <!--编织切入点
        id:自定义
        expression:指明原始类需要增强的方法是谁
    -->
        <aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <!--组装:将增强功能与被增强方法组装
            advice-ref:增强功能的类的bean的id
            pointcut-ref:被增强的切入点id
        -->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
        <aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
    </aop:config>
3.异常通知
  • 会在原始方法抛出异常时执行,实现接口ThrowsAdvice
package service.impl;

import service.UserService;

import java.util.Random;

public class UserServiceImpl implements UserService {
    public void selectAll() {
        Random random = new Random();
        int i = random.nextInt();
        if (i % 2 == 0) {
            throw new RuntimeException("随机数异常");
        }
        System.out.println("selectAll方法被调用");
    }
}

package advice;

import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] objects, Object o, Exception e){
        System.out.println(method);
        System.out.println(objects);
        System.out.println(o);
        System.out.println(e);
        System.out.println("MyThrowsAdvice do ...");
    }
}
 <!--原始类-->
    <bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>

    <!--前置增强类-->
    <bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>

    <!--后置增强类-->
    <bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>

    <!--异常增强类-->
    <bean id="myThrowsAdvice" class="advice.MyThrowsAdvice"/>

    <aop:config>
        <!--编织切入点
        id:自定义
        expression:指明原始类需要增强的方法是谁
    -->
        <aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <aop:pointcut id="userServiceThrowsAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <!--组装:将增强功能与被增强方法组装
            advice-ref:增强功能的类的bean的id
            pointcut-ref:被增强的切入点id
        -->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
        <aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
        <aop:advisor advice-ref="myThrowsAdvice" pointcut-ref="userServiceThrowsAdvice"/>
    </aop:config>
E:\JDK\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2020.2.1\lib\idea_rt.jar=61207:E:\IDEA\IntelliJ IDEA 2020.2.1\bin" -Dfile.encoding=UTF-8 -classpath E:\JDK\jre\lib\charsets.jar;E:\JDK\jre\lib\deploy.jar;E:\JDK\jre\lib\ext\access-bridge-64.jar;E:\JDK\jre\lib\ext\cldrdata.jar;E:\JDK\jre\lib\ext\dnsns.jar;E:\JDK\jre\lib\ext\jaccess.jar;E:\JDK\jre\lib\ext\jfxrt.jar;E:\JDK\jre\lib\ext\localedata.jar;E:\JDK\jre\lib\ext\nashorn.jar;E:\JDK\jre\lib\ext\sunec.jar;E:\JDK\jre\lib\ext\sunjce_provider.jar;E:\JDK\jre\lib\ext\sunmscapi.jar;E:\JDK\jre\lib\ext\sunpkcs11.jar;E:\JDK\jre\lib\ext\zipfs.jar;E:\JDK\jre\lib\javaws.jar;E:\JDK\jre\lib\jce.jar;E:\JDK\jre\lib\jfr.jar;E:\JDK\jre\lib\jfxswt.jar;E:\JDK\jre\lib\jsse.jar;E:\JDK\jre\lib\management-agent.jar;E:\JDK\jre\lib\plugin.jar;E:\JDK\jre\lib\resources.jar;E:\JDK\jre\lib\rt.jar;E:\MySelfProject\Spring\target\classes;C:\Users\32929\.m2\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar;C:\Users\32929\.m2\repository\org\springframework\spring-context\5.2.8.RELEASE\spring-context-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-aop\5.2.8.RELEASE\spring-aop-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-beans\5.2.8.RELEASE\spring-beans-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-core\5.2.8.RELEASE\spring-core-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-jcl\5.2.8.RELEASE\spring-jcl-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-expression\5.2.8.RELEASE\spring-expression-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar controller.SpringTest
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@12d3a4e9
service.impl.UserServiceImpl@240237d2
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@12d3a4e9
service.impl.UserServiceImpl@240237d2
java.lang.RuntimeException: 随机数异常
MyThrowsAdvice do ...
Exception in thread "main" java.lang.RuntimeException: 随机数异常
4.环绕增强
  • 会在原始方法的前,后以及发生异常时执行,实现接口MethodInterceptor
package advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAroundAdvice implements MethodInterceptor {
    //invocation :方法调用者,用户调用原始方法的动作
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object proceed = null;
        try{
            //前置
            System.out.println("这是环绕增强的前置增强");

            //执行原始方法,返回值为原始方法的返回值
            proceed = invocation.proceed();

            //后置
            System.out.println("这是环绕增强的后置增强");
        } catch (Throwable throwable) {
            System.out.println("这是环绕增强的异常增强");
        } finally {
            System.out.println("这是环绕增强的最终增强");
        }

        return proceed;
    }
}
 <!--原始类-->
    <bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>

    <!--前置增强类-->
    <bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>

    <!--后置增强类-->
    <bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>

    <!--异常增强类-->
    <bean id="myThrowsAdvice" class="advice.MyThrowsAdvice"/>

    <!--环绕增强类-->
    <bean id="myAroundAdvice" class="advice.MyAroundAdvice"/>

    <aop:config>
        <!--编织切入点
        id:自定义
        expression:指明原始类需要增强的方法是谁
    -->
        <aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <aop:pointcut id="userServiceThrowsAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
        <aop:pointcut id="userServiceAroundAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>

        <!--组装:将增强功能与被增强方法组装
            advice-ref:增强功能的类的bean的id
            pointcut-ref:被增强的切入点id
        -->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
        <aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
        <aop:advisor advice-ref="myThrowsAdvice" pointcut-ref="userServiceThrowsAdvice"/>
        <aop:advisor advice-ref="myAroundAdvice" pointcut-ref="userServiceAroundAdvice"/>
    </aop:config>
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25a65b77
service.impl.UserServiceImpl@2ed0fbae
这是环绕增强的前置增强
selectAll方法被调用
这是环绕增强的后置增强
这是环绕增强的最终增强
MyAfterAdvice do ....
null
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25a65b77
service.impl.UserServiceImpl@2ed0fbae
class com.sun.proxy.$Proxy3
  • 注意:不论是否发生异常,不会报错,因为会被环绕增强捕获然后输出环绕增强的异常
5.切入点表达式(execution)
  • 切入点:需要添加额外功能的方法的位置,通过aop:pointcut标签定义
  • 切入点表达式一共有4种写法
1.execution表达式
execution(返回值类型 包名.类名.方法名(参数类型))
  • 返回值类型:有返回值时需要指定具体类型,java.util.String。没有返回值时用void,任意返回值时使用*号表示
  • 包名,类名,方法名可以用*代替,表示任意
  • 参数类型需要指定时可以使用具体类型例Java.util.Integer指定,如果一个任意类型参数可以用*,多个任意类型参数可以用多个星号,任意类型任意个数可以用两个点
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(String service.impl.UserServiceImpl.*(..))"/>
2.args表达式
  • 根据形参进行匹配
  • args中根据参数类型和顺序进行匹配,如果能匹配上的方法都会被增强
    <aop:pointcut id="userServiceBeforeAdvice" expression="args(java.lang.String)"/>
    
3.within表达式
  • 根据全类名进行匹配
 <aop:pointcut id="userServiceBeforeAdvice" expression="within(service.impl.UserServiceImpl)"/>
4.@annotion表达式
  • 根据自定义注解进行匹配
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)//限定注解的使用位置,该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME)//限定注解有效时机,在被注解代理运行时有效
public @interface MyAnnotation {
}
<aop:pointcut id="userServiceBeforeAdvice" expression="@annotation(annotation.MyAnnotation)"/>
public class UserServiceImpl implements UserService {
    @MyAnnotation
    public void selectAll(String a) {
        System.out.println("selectAll方法被调用");
    }
}
表达式可以使用逻辑运算符
  • 非!
  • 和&&
  • 或 ||

6.事务控制

  • 事务控制的方式:编码式,声明式

1.编码式,将事务控制的代理直接写入service中

JDBC
    conn.setAutoCommit(false);   开启手动控制事务
    conn.commit()  提交事务
    conn.rollback()  回滚
Mybatis
    //默认手动提交事务
    
    sqlSession.commit();
	sqlSession.rollback();

2.声明式事务控制

  • 实现方式:借助Spring的AOP,将事务控制的代码定义成通知,通过AOP编辑进入service的每个方法中
1.导入依赖
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
2.定义业务类
  • 定义原始的service实现类对象,在实现类中的方法里写核心代码(调用mapper,功能实现的逻辑)
public class UserServiceImpl implements UserService {
    private UserMapper userMapper;

    public UserMapper getUserMapper() {
        return userMapper;
    }

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public List<User> selectAll() {
        return userMapper.selectAll();
    }
}
<!--将userService交给spring-->
    <bean id="userService" class="com.baizhi.service.impl.UserServiceImpl">
        <property name="userMapper" ref="userMapper"/>
    </bean>
3.定义增强类
<!-- 事务增强的bean   -->
    <bean id="txAdvice" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 填写之前配置的连接池id -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

这个类存在于框架中,只需要配置bean,不要手动创建该类
4.配置事务增强通知
<!-- 事务增强的通知
        id:给该事务增强起名字
        transaction-manager:将事务增强的bean交给通知管理
    -->
    <tx:advice id="txMyAdvice" transaction-manager="txAdvice">
        <!--进一步配置事务增强的细节-->
        <tx:attributes>
            <!-- 所有查询方法添加只读事务
                  read-only="true" 开启只读事务
             -->
            <tx:method name="select*" read-only="true"/>
            <!-- 增删改方法开启正常事务 -->
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
5.编织切点
  <aop:config>
        <aop:pointcut id="servicePointcut" expression="execution(* com.baizhi.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="txMyAdvice" pointcut-ref="servicePointcut"/>
    </aop:config>

3.事务详解

  • read-only:表示只读事务(查询时的事务):极大提高查询的效率,只能使用在查询中
  • timeout:超时机制,事务提交或者回滚时间超过制定的时长,事务自动提交失败,自动回滚
  • 默认时间:属性值设置为-1,跟数据库默认的超时时间一致
  • rollback-for和no-rollbacck-for
  • rollback-for:当service发生指定的异常时,执行回滚事务
  • no-rollback-for:当service发生指定异常时,不回滚
  • 如果事务设置为rollback-for,Spring中默认:
  • RunTimeException及其子类:执行回滚
  • Exception及其子类:不回滚
  • 如果日后你想自定义一个异常
  • 需要回滚的异常:继承RuntimeException
  • 不需要回滚的异常:继承Exception

4.事务的传播机制

  • 在企业开发中,会出现一个service方法调用另一个service方法的情况,传播机制定义了,当一个service方法调用另一个service方法时的事务如何处理
  • REQUIRED:如果在执行该service之前,已经开启一个事务,那么本service方法就加入该事务,同用同一个事务. 如果在执行该service之前,没有事务开启,那么就开启一个新的事务
  • SUPPORTS:如果在执行该service之前,已经开启一个事务么本service方法就加入该事务,同用同一个事务, 如果在执行该service之前,没有事务开启,那么就以无事务状态运行
  • REQUIRES_NEW:无论外部是否有事务,都会创建新的事务,挂起旧的事务

5.Spring基于注解的事务开发

 <!-- 事务增强的bean   -->
    <bean id="txAdvice" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 填写之前配置的连接池id -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="txAdvice"/>
@Override
    @Transactional(readOnly = true,timeout = 3,propagation = Propagation.REQUIRED)
    public List<User> selectAll() {
        return userMapper.selectAll();
    }
  • 传统开发中,注解控制事务使用很少,多数还是使用xml控制事务,但是在SpringBoot中只提供了注解控制事务的方式

7.注解开发

好处:可以简化配置

1.开启Spring注解的包扫描

<!--开启包扫描-->
    <context:component-scan base-package="com.baizhi.service,com.baizhi.dao"/>
目前我们的mapper是由mybatis生成的,mapper文件是不需要spring扫描的
如果日后使用的不是mybatis,需要将com.baizhi.dao也被spring扫描

2.为目标类和类的属性添加注解

@Component("abc")
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    public UserMapper getUserMapper() {
        return userMapper;
    }

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public List<User> selectAll() {
        return userMapper.selectAll();
    }

}

3.注解开发的优化

  • @Component替换bean,将类交给Spring工厂管理
  • 将@Component细化为三个标签
  • @Controller 用在控制器类
  • @Service 用在service类
  • @Repository 用在dao类上
  • 将注解添加在类上之后,会自动生成id
  • 注意:
  • 1.controller service repository 作用与Component一样,更具有识别性
  • 2.Mybiats会帮我们自动实现dao,dao不需要Spring实现的,目前Repository注解不需要使用的
  • 3.注解在使用时可以不添加参数,不用手动设置id值,id值默认为类名首字母小写UserServiceImpl ==> getBean(“userServiceImpl”)
  • @Autowired 完成属性值的注入的
  • 注意:
  • 1.@Autowired 根据属性的类型完成注入的,默认从Spring中找与属性类型一直或为其子类的对象
  • 2.使用反射注入,不需要属性提供set方法
  • @Quaifier(“bean的id值”)
  • 与@Autowired一起使用,当有多个相同类型的对象时,帮助@Autowired选择正确的对象
  • @Scope 是否单例
  • 在不添加此注解时,默认为单例模式
  • 如果希望对象为多例 @Scope(“prototype”)

4.Spring实现原理

在这里插入图片描述

  • 1.按照上述3.使用步骤完成项目的搭建后,通过xml配置文件读取其中的Bean定义信息,然后将其封装为BeanDefinition对象
  • 2.为了方便后续对不同类型定义信息文件进行扩展,定义了一个读取定义信息的接口规范BeanDefinitionReader,其有以下两个主要子类
    • 1.XmlBeanDefinitionReader:读取xml配置文件
    • 2.PropertiesBeanDefinitionReader:读取properties配置文件
  • 3.通过BeanFactoryPostProcessor接口可以对BeanFactory的功能进行增强,完成一些额外的功能
    • 1.PlaceholderConfigurerSupport:对xml文件中的占位符进行替换
    • 2.ConfigurationClassPostProcessor:对注解进行解析
  • 4.获取BeanDefinition对象后可以通过反射对其进行实例化操作,然后进行初始化操作,这里涉及Bean的生命周期
    • 1.实例化
    • 2.填充属性
    • 3.设置实现Aware接口的属性
    • 4.通过BeanPostProcessor接口对Bean进行前置增强
    • 5.执行init-method方法
    • 6.通过BeanPostProcessor接口对Bean进行后置增强
    • 7.获取Bean的完整对象并使用该对象
    • 8.销毁该Bean对象

1.BeanDefinitionReader

在这里插入图片描述

  • 1.xml注解等文件存放bean的定义信息,用于告知Spring容器如何生成Bean对象
  • 2.通过相应方式读取到xml注解等文件中存放的bean的定义信息后生成对应的BeanDefinition对象然后存放到Spring容器中,这些BeanDefinition对象存放在BeanDefinitionMap类型Map集合中
  • 3.BeanDefinition是一个接口,定义了一系列对象信息
  • 4.不同的文件有不同的读取方式,为了方便扩展,定义了一个BeanDefinitionReader的接口,用于加载不同的文件中的定义信息

2.BeanFactory

  • 1.BeanFactory是一个访问Spring Bean容器的根接口,该接口可以操作Spring容器中的内容,相当于Spring容器的入口

1.BeanFactory和FactoryBean

  • 1.此处的BeanFactory并不是指根接口,而是Bean对象创建的整体流程,Bean的生命周期是一个完整的标准化流程,相对比很麻烦,可以通过FactoryBean接口简单创建一个对象
  • 2.FactoryBean是一个接口,用来创建Bean对象而不通过BeanFactory的标准流程,FactoryBean为创建对象提供了三个抽象方法,可以根据需要创建出具体对象
  • 3.BeanFactory类似一个流水线操作,而FactoryBean类似定制操作
1.自定义Bean对象
  • 1.通过FactoryBean接口可以直接创建复杂对象,不需要经过工厂模板的方式
    • 1.自定义一个类实现FactoryBean接口并实现其中的方法,其中通过getObject()方法返回需要创建的对象
    • 2.配置xml文件,然后通过Spring工厂可以获取该对象
package domains;

import org.springframework.beans.factory.FactoryBean;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Collection;

public class MyConnectionFactoryBean implements FactoryBean {
   //获取复杂对象的方式
   public Object getObject() throws Exception {
       Class.forName("com.mysql.jdbc.Driver");
       Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql?useSSL=false", "root", "root");
       return connection;
   }

   //返回复杂对象的类型
   public Class<?> getObjectType() {
       return Collection.class;
   }

   //返回该对象是否是单例对象,复杂对象不能直接在xml文件中用scope属性设置,需要在这里配置
   public boolean isSingleton() {
       return false;
   }
}
<bean id="connection" class="domains.MyConnectionFactoryBean"/>

在这里插入图片描述

2.BeanFactory和ApplicationContext

  • 1.BeanFactory是一个访问Spring Bean容器的根接口
    • 1.Spring容器采用懒加载lazy-load机制,容器在加载配置文件时并不会立刻创建Java对象,只有程序中获取该对象时才会创建
    • 2.默认情况下Spring容器创建的对象是单例的,该类型可以通过bean标签的scope属性设置
      • 1.singleton:默认单例,Spring创建工厂时直接创建
      • 2.prototype:多例,Spring创建工厂时不会创建对象只有在调用getBean方法时才开始创建对象(lazy-load)
      <bean id="person" class="domains.Person" scope="prototype">
       <property name="name" value="李四"></property>
       <property name="age" value="18"></property>
       <property name="sex" value="男"></property>
       <property name="height" value="175"></property>
       <property name="weight" value="62"></property>
       <property name="phone" value="13465034256"></property>
       </bean>
      public static void main(String[] args) {
       ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       Object person1 = context.getBean("person");
       Object person2 = context.getBean("person");
       System.out.println(person1);
       System.out.println(person2);
       /**
        * domains.Person@210366b4
        * domains.Person@eec5a4a
        */
      }
      
  • 2.ApplicationContext接口继承于BeanFactor根接口,是对BeanFactory的扩展,提供更加完善的内容
    在这里插入图片描述
    • 1.ApplicationContext 接口常用的实现类
      • 1.ClassPathXmlApplicationContext:加载类路径下指定的XML配置文件
      • 2.FileSystemXmlApplicationContext:加载完整的文件系统路径下指定的 XML配置文件

3.PostProcessor

  • 1.PostProcessor类型的接口用于扩展现有的功能,该类型的接口主要有以下两种
    • 1.BeanFactoryPostProcessor:对BeanFactory进行扩展
    • 2.BeanPostProcessor:对Bean进行扩展
      在这里插入图片描述
  • 2.接口和抽象类的区别
    • 1.接口:自上向下,不需要考虑子类,实现类需要去实现接口的内容
    • 2.抽象类:自下向上,是对子类共性的抽取
    • 3.具体可参考JAVA SE文章中的接口和抽象类的区别
  • 3.除了接口可以扩展,模板方法设计模式也可以扩展
    • 1.AbstractApplicationContext类的refresh中的postProcessBeanFactoryonRefresh方法是空的模板方法
      在这里插入图片描述
    • 2.其中onRefresh方法在SpringBoot中进行了扩展,创建了WebServer从而可以启动Web项目
      在这里插入图片描述

1.BeanFactoryPostProcessor

在这里插入图片描述

  • 1.BeanFactoryPostProcessor是对BeanFactory进行扩展的接口,用于在标准初始化之前修改应用程序上下文的内部Bean工厂
  • 2.此时所有的Bean定义都已加载,但还没有实例化任何Bean,该接口允许覆盖或添加属性或抛出异常
  • 3.该接口是一个函数式接口,其中只有一个抽象方法postProcessBeanFactory
1.xml文件中占位符替换

在这里插入图片描述

  • 1.xml文件中可能会有其他文件(db.properties)属性值的引用,因此需要在实例化对象之前进行参数的替换
  • 2.BeanFactory是访问Spring容器的根接口,可以对容器中的对象进行操作,因此此时可以使用BeanFactoryPostProcesor接口的实现类PlaceholderConfigurerSupport完成占位符的替换操作
    在这里插入图片描述
  • 3.占位符替换流程
    在这里插入图片描述
    在这里插入图片描述
    • 1.执行AbstractApplicationContextrefresh方法
    • 2.refresh方法中的invokeBeanFactoryPostProcessors是调用各种BeanFactoryPostProcessors处理器
    • 3.Debug未执行invokeBeanFactoryPostProcessors方法前,DefaultListableBeanFactory中的所有的Bean定义对象都被封装成GenericBeanDefinition对象然后存放在ConcurrentHashMap类型的BeanDefinitionMap中,此时从Debug中可看到xml文件中定义的各种对象,且占位符未被替换
      在这里插入图片描述
    • 4.Debug执行完invokeBeanFactoryPostProcessors方法后,占位符被db.properties中的实际值替换,因此说明替换占位符的功能是由BeanFactoryPostProcessor扩展接口的实现类PlaceholderConfigurerSupport完成
      在这里插入图片描述
2.BeanDefinitionRegistryPostProcessor

在这里插入图片描述

  • 1.BeanFactoryPostProcessor接口有一个子接口BeanDefinitionRegistryPostProcessor,该接口用于对注解进行解析
  • 2.因为先有xml文件后有注解,注解是在xml标准解析处理流程上进行扩展
    在这里插入图片描述
  • 3.ConfigurationClassPostProcessorBeanDefinitionRegistryPostProcessor接口的实现类,其中processConfigBeanDefinitions方法的parse方法对注解进行处理
    在这里插入图片描述
3.自定义BeanFactoryPostProcessor扩展类
  • 1.创建一个类实现BeanFactoryPostProcessor接口,并且实现postProcessBeanFactory方法,该方法中编写完扩展内容后将该类通过xml文件的bean标签交给Spring工厂管理即可
  • 2.执行时会通过refresh方法中的invokeBeanFactoryPostProcessors自动调用该扩展类的postProcessBeanFactory方法
  • 3.可以在自定义扩展类上加@Order注解指定加载顺序,该功能一般用于二次开发
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.BeanPostProcessor

  • 1.BeanPostProcessor是对Bean进行扩展的接口,用于自定义修改Bean对象,一般用于检查标记接口或用代理包装Bean
    • 1.通过标记接口或类似的方式填充Bean的处理器将实现postProcessBeforeInitialization方法
    • 2.通过代理包装Bean的处理器将实现postProcessAfterInitialization
  • 3.ApplicationContext可以在其Bean定义中自动检测BeanPostProcessor ,并将这些处理程序应用于随后创建的任何Bean
1.BeanPostProcessor实现AOP动态代理
  • 1.Bean的生命周期中当完成对象的创建和属性的赋值时,理论上可以直接使用,但是Spring需要考虑扩展性
  • 2.因此使用BeanPostProcessor接口对Bean进行扩展,该接口中有两个抽象方法
    • 1.前置方法postProcessBeforeInitialization
    • 2.后置方法postProcessAfterInitialization,该方法是AOP的入口
  • 3.AOP是通过代理对象实现,动态代理有两种实现方式
    • 1.JDK动态代理
    • 2.cglib
  • 4.BeanPostProcessor接口的实现子类中有一个重要抽象类AbstractAutoProxyCreator,用来创建对应的代理对象,其中代理对象是在后置增强方法中实现
  • 5.从源码中可以看出AOPIOC整体流程中的一个扩展点
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

4.Bean的生命周期

在这里插入图片描述
请添加图片描述

  • 1.生命周期:从对象的创建使用到销毁的过程
    • 1.实例化:堆空间中开辟空间,此时对象的属性值一般是默认值,通过反射创建对象,实例化实际调用方法是createBeanInstance
    • 2.初始化
      • 1.属性赋值:实例化完成后给属性赋值,属性赋值实际调用方式是populateBean,通过调用属性的set()方法完成赋值操作
      • 2.设置实现Aware接口的属性Aware是一个标记接口,没有任何内容
        在这里插入图片描述
      • 3.调用BeanPostProcessor中的前置处理方法postProcessBeforeInitializationBean对象进行扩展
      • 4.执行初始化方法init-method
      • 5.调用BeanPostProcessor中的后置处理方法postProcessBeforeInitializationBean对象进行扩展
      • 6.通过ClassPathXmlApplicationContext对象的getBean方法获取完整对象并使用
      • 7.通过ClassPathXmlApplicationContext对象的close方法销毁对象

1.Spring中的Bean对象类型

  • 1.Spring容器中Bean对象为两类
    • 1.自定义对象
    • 2.容器对象(BeanFactoryApplicationContext等由容器创建的对象)
  • 2.自定义类中可以注入容器对象,这些容器对象由Spring容器在统一的地方进行统一的注入,因此自定义类需要满足相应标识,即直接或间接实现Aware接口
  • 3.例:如果想注入BeanFactory容器对象则需要满足BeanFactoryAware标识,然后在对应的set()方法中进行赋值
    在这里插入图片描述
  • 4.因此当一个类需要注入容器属性值时可以实现相应的接口,然后通过invokeAwareMethods统一调用进行赋值
    在这里插入图片描述
    在这里插入图片描述
  • 5.Bean生命周期中初始化的前两步可以解释为以下内容,方便理解
    在这里插入图片描述

2.Bean对象的初始化方法

  • 1.此处的初始化方法并非指整个Bean对象的初始化,而是特指init-method方法
  • 2.Spring创建对象并为对象填充属性后,可以调用init-metho的方法进行一些自定义操作
1.修改对象属性的两种方法
  • 1.实现InitializingBean接口,并重写afterPropertiesSet方法
  • 2.xml配置中在bean标签的init-method属性指定初始化方法的名称
  • 3.一般两者不会同时出现,如果同时出现则先执行接口的方法后执行init-method属性的方法
package domains;

import org.springframework.beans.factory.InitializingBean;

public class Teacher implements InitializingBean {
   private String id;
   private String name;
   private String password;

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

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

   public void setPassword(String password) {
       this.password = password;
   }

   @Override
   public String toString() {
       return "Teacher{" +
               "id='" + id + '\'' +
               ", name='" + name + '\'' +
               ", password='" + password + '\'' +
               '}';
   }

   public void afterPropertiesSet() throws Exception {
       //检验对象属性
       if(this.name.equals("lisi")){
           this.name = "张三";
       }
       System.out.println("这是初始化阶段实现接口的操作");
   }

   public void myInitMethod(){
       if(this.name.equals("lisi")){
           this.name = "哈哈";
       }
       System.out.println("这是自定义的初始化方法");
   }
}
<bean id="teacher" class="domains.Teacher" init-method="myInitMethod">
       <property name="id" value="1"/>
       <property name="name" value="lisi"/>
       <property name="password" value="123456"/>
</bean>
这是初始化阶段实现接口的操作
这是自定义的初始化方法
Teacher{id='1', name='张三', password='123456'}

3.Bean对象的销毁方法

  • 1.使用完对象关闭工厂context.closed()时会执行Bean对象的销毁方法,此时可以自定义一些销毁操作
    • 1.实现DisposableBean接口,并重写destroy方法
    • 2.xml配置中在bean标签的destroy-method属性指定销毁方法的名称
  • 3.一般两者不会同时出现,如果同时出现则先执行接口的方法后执行destroy-method属性的方法
package domains;

import org.springframework.beans.factory.DisposableBean;

public class Teacher implements DisposableBean {
   private String id;
   private String name;
   private String password;

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

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

   public void setPassword(String password) {
       this.password = password;
   }

   @Override
   public String toString() {
       return "Teacher{" +
               "id='" + id + '\'' +
               ", name='" + name + '\'' +
               ", password='" + password + '\'' +
               '}';
   }

   public void destroy() throws Exception {
       System.out.println("实现接口的销毁阶段");
   }

   public void myDestroyMethod(){
       System.out.println("自定义的销毁阶段");
   }
}
<bean id="teacher" class="domains.Teacher" destroy-method="myDestroyMethod">
       <property name="id" value="1"/>
       <property name="name" value="lisi"/>
       <property name="password" value="123456"/>
   </bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = (Teacher) context.getBean("teacher");
System.out.println(teacher);
context.close();

在这里插入图片描述

4.BeanPostProcoress扩展实例

package domains;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class Teacher implements InitializingBean , DisposableBean {
   private String id;
   private String name;
   private String password;

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

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

   public void setPassword(String password) {
       this.password = password;
   }

   @Override
   public String toString() {
       return "Teacher{" +
               "id='" + id + '\'' +
               ", name='" + name + '\'' +
               ", password='" + password + '\'' +
               '}';
   }

   public void afterPropertiesSet() throws Exception {
       //检验对象属性
       if(this.name.equals("lisi")){
           this.name = "张三";
       }
       System.out.println("这是初始化阶段实现接口的操作");
   }

   public void myInitMethod(){
       if(this.name.equals("lisi")){
           this.name = "哈哈";
       }
       System.out.println("这是自定义的初始化方法");
   }

   public void destroy() throws Exception {
       System.out.println("实现接口的销毁阶段");
   }

   public void myDestroyMethod(){
       System.out.println("自定义的销毁阶段");
   }
}
package domains;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
   //在初始化之前执行
   //参数含义
   //bean:用户要获取的对象
   //beanName:对象对应的bean标签的id
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
       System.out.println("BeanPostProcessor前置方法+对象:" + bean + "  id:" + beanName);
       return null;
   }

   //初始化之后执行
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
       System.out.println("BeanPostProcessor后置方法+对象:" + bean + "  id:" + beanName);
       return null;
   }
}
   <bean id="teacher" class="domains.Teacher" destroy-method="myDestroyMethod" init-method="myInitMethod">
       <property name="id" value="1"/>
       <property name="name" value="lisi"/>
       <property name="password" value="123456"/>
   </bean>
   
   <bean class="domains.MyBeanPostProcessor"/>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = (Teacher) context.getBean("teacher");
System.out.println(teacher);
context.close();
/** 结果
* BeanPostProcessor前置方法+对象:Teacher{id='1', name='lisi', password='123456'}  id:teacher
* 这是初始化阶段实现接口的操作
* 这是自定义的初始化方法
* BeanPostProcessor后置方法+对象:Teacher{id='1', name='张三', password='123456'}  id:teacher
* Teacher{id='1', name='张三', password='123456'}
* 实现接口的销毁阶段
* 自定义的销毁阶段
*/

5.IOC 控制反转

  • 1.IOC(Inversion of Controller):控制反转,把对象创建和对象之间的调用过程交给Spring进行管理,降低耦合度

1.IOC底层原理

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

  • 1.调用AbstractAutowireCapableBeanFactory类中doCreateBean方法中createBeanInstance方法
  • 2.然后再调用其中的instantiateBean方法的instantiate实例化Bean
  • 3.本质是通过反射调用对象的默认无参构造函数getDeclaredConstructor,然后通过newInstance实例化对象
    在这里插入图片描述
    在这里插入图片描述

2.DI 依赖注入

  • 1.DI(Denpendency Injection):依赖注入,本质是Bean对象属性注入,该注入方式有两种,可参考上述注入方式,源码中是在populateBean方法中完成属性的注入
  • 2.依赖关系:一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象
  • 3.注意:如果属性类型也是一个对象,且两个对象相互依赖,会导致循环依赖问题

6.Spring AOP

  • 1.AOP:面向切面编程

7.循环依赖问题

在这里插入图片描述

  • 1.Spring创建的Bean对象默认情况下是单例的,整个容器类有且只有一个该对象
  • 2.依赖注入时,如果AB的属性都是对象且彼此依赖,即A对象依赖BB对象依赖A则会造成循环依赖的问题
  • 3.循环依赖问题可以采用三级缓存+提前暴露对象解决,本质是因为对象的实例化和初始化可以分开执行
  • 4.当持有了某一个对象的引用之后,可以在后续步骤给对象进行赋值操作,即先引用后续再进行赋值

1.三级缓存

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

  • 1.三级缓存结构存在于DefaultSingletonBeanRegistry类中,注意三级缓存的HashMap的值是一个ObjectFactory函数式接口
    • 1.ObjectFactory:函数式接口,可以将lambda表达式作为参数放到方法的实参中,方法执行时并不会实际调用该lambda表达式,只有在调用getObject方法时才会调用lambda表达式
  • 2.将对象按照状态来分类,可以分为成品和半成品
    在这里插入图片描述
  • 3.一级缓存中存放的是成品对象,二级缓存中存放的是半成品对象,三级缓存中存放的是匿名内部类(lambda表达式)
  • 4.三级缓存的解决流程
    在这里插入图片描述
  • 1.首先创建A对象并给A对象进行实例化操作,此时A对象是一个半成品对象,存放在二级缓存中
  • 2.然后给A对象的b属性赋值,先查看容器是否存在B对象,如果存在则直接赋值,如果没有则创建B对象并进行实例化,此时B对象也是一个半成品对象,存放在二级缓存中,此时可以将其赋给A对象的b属性,然后将A的成品对象放到一级缓存中
  • 3.然后给B对象的a属性赋值,先查看容器是否存在A对象,因为一级缓存中存在A的成品对象所以直接赋值完成B的初始化,此时B对象是成品,然后将BA的成品对象放到一级缓存中
  • 4.此时解决了循环依赖的闭环问题,但是此时既有AB的成品对象也有半成品对象,为了区分所以引入了三层缓存结构
    在这里插入图片描述
    在这里插入图片描述
  • 5.三层缓存在进行对象查找的时候是依次查找的,先查找一级缓存,再查找二级缓存,最后查找三级缓存
  • 6.两级缓存也能解决循环依赖问题,但是如果存在AOP或代理对象时只能使用三级缓存才能解决循环依赖问题
    • 1.因为一个容器,不能包含两个同名的对象
    • 2.对象创建过程中,原始对象可能需要生成代理对象,如果生成代理对象则需要通过lambda表达式在调用时确定是代理对象还是原始对象
    • 3.普通对象和代理对象不能同时出现在容器中,因此当一个对象代理时就需要使用代理对象覆盖普通对象,实际调用过程中无法确定会何时使用对象,因此需要在调用时才去判断对象是否需要代理对象
    • 4.该机制可以使用lambda表达式实现,类似一种回调机制,在调用时会去确认是代理还是普通对象,具体方法为getEarlyBeanReference
      在这里插入图片描述
  • 7.缓存的放置时间和删除时间
    • 1.三级缓存:调用createBeanInstance实例化对象后通过addSingletonFactory加入三级缓存
    • 2.二级缓存:通过getSingleton第一次从三级缓存中确定对象是代理对象还是普通对象,然后将其放入二级缓存同时删除三级缓存
    • 3.一级缓存:生成完整对象后通过addSingleton将完整对象放入一级缓存并删除二三级缓存

2.提前暴露对象

  • 1.提前暴露的是对象的引用,即半成品对象,此时对象还未完成初始化

8.Spring常见面试题

1.循环依赖问题

1.循环依赖如何解决
  • 1.循环依赖通过三级缓存和提前暴露对象可以解决
  • 2.具体可参考上述内容

2.IOC和DI的理解

  • 1.具体可参考上述内容

3.Bean的生命周期

  • 1.具体可参考上述内容

4.BeanFactory和FactoryBean有什么区别

  • 1.具体可参考上述内容

5.Spring中使用的设计模式

  • 1.单例模式:Bean创建默认是单例的
  • 2.工厂模式:BeanFactory工厂
  • 3.模板方法模式:postProcessBeanFactoryonRefresh
  • 4.策略模式:XmlBeanDefinitionReaderPropertiesBeanDefinitionReader
  • 5.观察者模式:listener
  • 6.适配器模式:Adapter
  • 7.装饰器模式:BeanWrapper
  • 8.代理模式:AOP动态代理

6.Spring AOP

  • 1.AOPIOC的一个扩展功能,先有IOC再有AOP
  • 2.AOP是在BeanPostProcessor中的后置处理器中对Bean对象进行的动态代理
  • 动态代理有两种方式
    • 1.JDK动态代理
    • 2.cglib动态代理

7.Spring 事务

  • 1.Spring事务传播(7种):事务的传播机制指不同方法的嵌套调用中,事务该如何处理,是用同一个事务还是不同的事务,当出现异常是回滚还是提交,两个方法之间相互影响
    • 1.Required
    • 2.Requires_new
    • 3.nested
    • 4.Support
    • 5.Not_Support
    • 6.Never
    • 7.Mandatory
  • 2.Spring事务的隔离级别
    • 1.Spring事务的隔离级别和数据库的一致,可参考MySQL文章中的事务隔离级别

2.Spring MVC

  • 1.Spring MVCSpring展示层提供的基于MVC设计理念的Web框架,其支持Rest风格的URL写法,且更加灵活具有扩展性
  • 2.Spring MVC让普通类成为Controller控制器且无需继承Servlet,实现了解耦操作

1.MVC模式

  • 1.Model模型层,提供程序的运行基础业务模型(Dao封装)
  • 2.View视图层,接收服务器的数据并展示到具体页面(htmljsp)
  • 3.Controller控制层,接收浏览器发送的请求并控制程序的流程(Servlet封装)

2.传统Servlet

  • 1.编码繁琐,开发效率低(必须继承父类HttpServlet重写service方法且一个servlet类最多提供一个service方法)
  • 2.获取请求数据繁琐(手动获取参数并完成类型转换)
  • 3.数据传输编码繁琐(手动将数据保存到对应作用域)
  • 4.跳转编码方式不统一(请求转发和重定向有不同的方法)
    @WebServlet("/xxx")
    public class XxxServlet extends HttpServlet{
    	public void service(xxx req,xxx resp){
    		//1.收参
    		String username = req.getParameter("username");
    		...
    		//2.调用业务层
    		...
    		//3.传递参数
    		req.setAttribute("名字""值")
    		...
    		//4.跳转
    		req.getRequestDispatcher("地址").forward(req,resp)//请求转发
    		resp.sendRedirect("地址")//重定向
    	}
    }
    

3.使用步骤

1.新建web项目

  • 1.创建maven项目,且使用mavenwebapp骨架
    在这里插入图片描述
    在这里插入图片描述
  • 2.删除pom.xml配置文件中多余的内容
    在这里插入图片描述
  • 3.手动配置包目录
    在这里插入图片描述

2.导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>SpringMVC</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>SpringMVC Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.8.RELEASE</version>
    </dependency>
  </dependencies>
</project>

3.编写springmvc.xml配置文件

  • 1.该springmvc.xml配置文件位于main/resources文件夹下
  • 2.注意:配置文件上需要导入mvccontext命名空间,否则无法识别对应子标签
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!-- 开启springmvc注解 -->
        <mvc:annotation-driven/>
    
        <!-- 配置要扫描注解的包,springmvc针对的是controller层-->
        <context:component-scan base-package="controller"/>
    </beans>
    
    在这里插入图片描述

4.配置web.xml文件

  • 1.前端请求通过DispatcherServlet请求分发器分发给不同的处理器,而使用DispatcherServlet请求分发器需要在web.xml文件中进行配置
    • 1.servlet-name子标签:指定DispatcherServlet请求分发器的名称
    • 2.servlet-class子标签:指定DispatcherServlet请求分发器的类路径
    • 3.url-pattern子标签:指定请求DispatcherServlet请求分发器的URL路径需要满足的模式
    • 4.init-param子标签:指定DispatcherServlet请求分发器将请求分发给哪些处理器进行处理
    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
    
      <!--请求分发器:将请求交给springmvc管理-->
      <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!--配置springmvc配置文件的位置-->
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--Servlet默认第一次访问时创建,配置load-on-startup则会在tomcat启动时提前创建,可以减少一次访问的时间-->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
    	<!--配置访问路径的通配符,需要以.do结尾-->
        <url-pattern>*.do</url-pattern>
      </servlet-mapping>
    </web-app>
    
  • 2.DispatcherServlet请求分发器的分发流程
    • 1.满足对应URL模式的请求通过DispatcherServlet请求分发器分发请求
    • 2.通过配置文件中指定配置文件路径找到对应的请求处理器
    • 3.通过controller处理器处理对应的请求
      在这里插入图片描述

5.安装并配置tomcat

  • 1.官网下载tomcat压缩包然后解压
  • 2.配置ideatomcat环境,即先配置本地tomcat服务器的地址,然后配置Deployment应用上下文路径(访问项目路径)
    在这里插入图片描述

6.编写控制器

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class FirstController {

    @RequestMapping("/hello")
    public ModelAndView hello(){
        //业务
        System.out.println("Hello SpringMVC");

        //跳转
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/index.jsp");
        //转发:forward:/index.jsp
        //重定向:redirect:/index.jsp

        //返回值
        return modelAndView;
    }

    @RequestMapping("/bey")
    public ModelAndView bey(){
        //业务
        System.out.println("bey SpringMVC");

        //跳转
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/index.jsp");
        //转发:forward:/index.jsp
        //重定向:redirect:/index.jsp

        //返回值
        return modelAndView;
    }
}

7.访问

  • 1.http://localhost:端口号/项目名/requestMapping路径.do
  • 2.例:http://localhost:8080/SpringMVC_war/hello.do
    在这里插入图片描述
  • 3.注意
    • 1.重定向会改变地址栏地址,而请求转发则不会
    • 2.访问路径后需要加上.do,因为web.xml中对URL的模式进行了限制,只要满足了条件才能被DispatcherServlet请求分发器分发请求

8.整体架构图

在这里插入图片描述

4.SpringMVC注解

1.@RequestMapping

在这里插入图片描述

  • 1.@RequestMapping注解:用于指定请求的映射的路径,可以用在类和方法上,其常用属性有以下几种
    • 1.value:匹配请求访问的路径
      //请求路径需要包含value的值,且需要满足URL的模式
      @RequestMapping(value = "/hello",method = RequestMethod.POST)
      
    • 2.method:设定处理的请求方式
      //get请求:a标签,地址栏输入,表单get提交方法
      //post请求:表单post提交方法
      @RequestMapping(value = "/hello",method = RequestMethod.POST)
      
  • 2.注意类和方法上的/都不会影响访问的路径,因此可省略但建议带上

2.@RequestParam

在这里插入图片描述

  • 1.@RequestParam注解:用于设置请求参数,只能用在方法参数上,其常用属性有以下几种
    • 1.value:设置参数值的别名,传参时需要使用该别名,否则无法获取
    • 2.required:设置该值是否为必须的
    • 3.defaultValue:设置当该值没有时的默认值
      @RequestMapping(value = "/insert",method = RequestMethod.GET)
      public ModelAndView insert(String name, Integer age, @RequestParam(value = "AliasHeight", required = true, defaultValue = "72") Double height){
      	//...
      }
      

3.@RestControllerAdvice

  • 1.@RestControllerAdvice注解是一个组合注解,由@ControllerAdvice@ResponseBody组成
  • 2.@ControllerAdvice注解:用于统一处理控制器(使用@RequestMapping的控制器的方法)的全局配置,注解了@RestControllerAdvice的类的方法上可以使用以下几种注解
    • 1.@ExceptionHandler:用于指定异常处理方法,当与@RestControllerAdvice配合使用时,用于全局处理控制器中的异常
    • 2.@InitBinder:用来设置WebDataBinder,用于自动绑定前台请求参数到Model
    • 3.@ModelAttribute:用于绑定键值对到Model中,当与@ControllerAdvice配合使用时,可以让全局的@RequestMapping都能获得在此处设置的键值对
  • 3.具体可参考异常及其使用方式文章中的统一异常处理类

4.@ExceptionHandler

  • 1.用于指定异常处理方法,当与@RestControllerAdvice配合使用时,用于全局处理控制器中的异常
  • 2.具体可参考上述@RestControllerAdvic注解

5.@PathVariable

  • 1.@PathVariable注解:用于将URL中占位符参数绑定到控制器处理方法的入参中
    • 1.若方法参数名称和需要绑定的URL中变量名称一致时,可以简写
      @RequestMapping("/getUser/{name}")
      public User getUser(@PathVariable String name){
          return userService.selectUser(name);
      }
      
    • 2.若方法参数名称和需要绑定的URL中变量名称不一致时,则需要指定
      @RequestMapping("/getUserById/{name}")
      public User getUser(@PathVariable("name") String userName){
          return userService.selectUser(userName);
      }
      

7.@RequestBody

  • 1.@RequestBody注解:将请求报文中的请求体(Json格式)转换为Java类型的数据
  • 2.该注解只能用于标识方法参数

8.@ResponseBody

  • 1.@ResponseBody注解:将Java类型的数据转换为响应报文中的响应体(Json格式)
  • 2.该注解可以用于标识控制器方法控制器类,将该方法或类中所有方法的返回值直接作为响应报文的响应体(Json格式)响应到浏览器

9.@RestController

  • 1.@RestController注解是一个组合注解,由@ResponseBody@Controller组成
  • 2.@Controller注解:是一种特殊化的@Component,用于将其交给Spring容器管理且标记该类是一个控制器

5.执行流程

在这里插入图片描述

  • 1.项目启动Tomcat服务器时首先会加载程序的web.xml文件
  • 2.实例并初始化web.xml文件中的核心控制器DispatcherServlet
  • 3.通过web.xml文件中的配置找到并加载springmvc.xml文件,创建Spring容器并初始化容器中的对象
  • 4.客户端浏览器发送请求,首先请求会到达前端控制器DispatcherServlet
  • 5.DispatcherServlet根据映射规则找到对应的controller处理器
  • 6.执行完controller业务代码后会返回页面路径,
    视图解析器InternalResourceViewResolver对其进行解析找到具体的页面
  • 7.将结果响应到浏览器,并渲染页面

1.核心组件及执行步骤

在这里插入图片描述

  • 1.核心控制器:DispatcherServlet
    • 1.所有用户请求首先会到达核心控制器DispatcherServletDispatcherServlet是整个流程的控制中心
    • 2.DispatcherServlet并不直接处理用户请求,而是将调用不同组件处理不同的功能
  • 2.三大组件
    • 1.处理器映射器:HandlerMapping
      • 1.对请求路径进行解析,实际负责查找controller处理器的组件
    • 2.处理器适配器:HandlerAdapter
      • 1.适配器模式的应用,实际通过HanderAdaptercontroller处理器进行执行
      • 2.通过扩展适配器可以对更多类型的处理器进行执行,不同的参数和不同的返回类型需要不同的适配器
    • 3.视图解析器:ViewResolver
      • 1.负责将controller处理后的结果生成View视图
      • 2.ViewResolver根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成View视图对象
      • 3.最后对View进行渲染,将处理结果通过页面展示给用户
  • 3.处理器:指开发人员编写的controller处理方法
  • 4.执行步骤
    • 1.客户端浏览器请求Tomcat服务器,请求首先到达前端控制器DispatcherServlet
    • 2.DispatcherServlet调用处理器映射器HandlerMapping去解析请求路径并通过其找到对应controller处理器,然后返回处理器执行链交给DispatcherServlet
    • 3.DispatcherServlet调用处理适配器HandlerAdapter去执行controller处理器方法,执行结束后返回封装好的ModeAndView交给DispatcherServlet
    • 4.DispatcherServlet调用视图解析器ViewResolver去解析ModelAndView中的信息并生成View对象交给DispatcherServlet
    • 5.DispatcherServletView对象放到视图层进行渲染,最后将视图信息响应给客户端浏览器
  • 5.注意
    • 1.需要在web.xml中配置对应的核心控制器DispatcherServlet以及拦截规则
      <!DOCTYPE web-app PUBLIC
       "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
       "http://java.sun.com/dtd/web-app_2_3.dtd" >
      
      <web-app>
        <display-name>Archetype Created Web Application</display-name>
      
        <!--请求分发器:将请求交给springmvc管理-->
        <servlet>
          <servlet-name>DispatcherServlet</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--配置springmvc配置文件的位置-->
          <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
          </init-param>
          <!--Servlet默认第一次访问时创建,配置load-on-startup则会在tomcat启动时提前创建-->
          <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
          <servlet-name>DispatcherServlet</servlet-name>
          <!--注意访问路径需要带上.do后缀才能被处理-->
          <url-pattern>*.do</url-pattern>
        </servlet-mapping>
      </web-app>
      
    • 2.需要在对应的springmvc.xml中配置上述三大组件以及处理器,其中处理器映射器HandlerMapping和处理器适配器HandlerAdapter可以简写
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:mvc="http://www.springframework.org/schema/mvc"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
             http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
      
          <!-- 配置处理器映射器 -->
          <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> -->
          <!-- 配置处理器适配器 -->
          <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> -->
          <!-- 上述可简写为该方式: 开启springmvc注解驱动,自动配置处理器和处理器适配器 -->
          <mvc:annotation-driven/>
      
          <!-- 配置视图解析器 -->
          <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
              <!--指定路径的前缀和后缀-->
              <property name="prefix" value="/WEB-INF/view/"></property>
              <property name="suffix" value=".jsp"></property>
          </bean>
      
          <!-- 配置要扫描注解的包 即处理器 -->
          <context:component-scan base-package="controller"/>
      </beans>
      

6.静态资源放行

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

  • 1.当url-pattern标签的拦截路径为/时会对所有除jsp页面的所有路径进行拦截
  • 2.当jsp页面中有静态资源访问时也会被拦截导致无法加载,此时需要配置静态资源放行
    在这里插入图片描述
    	<?xml version="1.0" encoding="UTF-8"?>
    	<beans xmlns="http://www.springframework.org/schema/beans"
    	       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	       xmlns:mvc="http://www.springframework.org/schema/mvc"
    	       xmlns:context="http://www.springframework.org/schema/context"
    	       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    	       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    	       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    	    <!-- 配置处理器映射器 -->
    	    <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> -->
    	    <!-- 配置处理器适配器 -->
    	    <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> -->
    	    <!-- 上述可简写为该方式: 开启springmvc注解驱动,自动配置处理器和处理器适配器 -->
    	    <mvc:annotation-driven/>
    	
    	    <!-- 配置视图解析器 -->
    	    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	        <!--指定路径的前缀和后缀-->
    	        <property name="prefix" value="/WEB-INF/view/"></property>
    	        <property name="suffix" value=".jsp"></property>
    	    </bean>
    
    	    <!-- 配置要扫描注解的包 即处理器 -->
    	    <context:component-scan base-package="controller"/>
    
    		<!-- 配置静态资源放行 -->
    	    <!-- mapping: 请求url中的路径 location: 对应项目目录的资源-->
    	    <mvc:resources mapping="/static/**" location="/static/"></mvc:resources>
    	</beans>
    
    在这里插入图片描述
    在这里插入图片描述

7.请求参数类型

1.基本数据包装类+String

  • 1.基本数据类型可以直接通过地址栏拼接,通过名称一致的形参自动赋值
  • 一般使用基本数据类型包装类,否则如果参数不存在,默认值为null时基本数据类型会报错500
    	@RequestMapping(value = "/insert",method = RequestMethod.GET)
        public ModelAndView insert(String name, Integer age, Double height){
            //业务
            System.out.println("Insert SpringMVC");
            System.out.println(name);
            System.out.println(age);
            System.out.println(height);
    
            //跳转
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("redirect:/index.jsp");
            //转发:forward:/index.jsp
            //重定向:redirect:/index.jsp
    
            //返回值
            return modelAndView;
        }
    
    http://localhost:8080/SpringMVC_war/test/insert.do?name=李四sir&age=18
    Insert SpringMVC
    ??????sir
    18
    null
    

2.对象类型

3.数组,List,Set

4.对象集合

5.Map

8.响应参数类型

1.String类型

  • 1.如果返回参数类型为String类型,则该参数表示要跳转的页面的路径,默认使用请求转发跳转,即地址栏地址不改变
  • 2.如果该页面存放在嵌套文件夹下,可以使用视图解析器简化该页面路径,即配置页面的前缀后缀,跳转时会在返回路径上自动加上前后缀
    在这里插入图片描述
  • 3.web.xml
    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
    
      <!--请求分发器:将请求交给springmvc管理-->
      <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!--配置springmvc配置文件的位置-->
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--Servlet默认第一次访问时创建,配置load-on-startup则会在tomcat启动时提前创建-->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
      </servlet-mapping>
    </web-app>
    
  • 4.springmvc.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!-- 开启springmvc注解 -->
        <mvc:annotation-driven/>
    
        <!-- 配置要扫描注解的包-->
        <context:component-scan base-package="controller"/>
    
        <!-- 配置视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!--指定路径的前缀和后缀-->
            <property name="prefix" value="/WEB-INF/view/"></property>
            <property name="suffix" value=".jsp"></property>
        </bean>
    </beans>
    
  • 5.controller
    package controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping(value = "/test")
    public class FirstController {
    
        @RequestMapping("/first")
        public String first(){
            //业务
            System.out.println("first SpringMVC");
            //返回值
            return "hello";
        }
    }
    
  • 6.访问路径
    在这里插入图片描述
  • 7.跳转页面
    在这里插入图片描述

9.Restful风格

  • 对应处理器指定请求方式的控制器方法,SpringMVC中提供了@RquestMapping的派生注解
  • 处理get请求的映射->@GetMapping
  • 处理post请求的映射->@PostMapping
  • 处理put请求的映射->@PutMapping
  • 处理delete请求的映射->@DeleteMaping

4.@RequestMapping的params属性

  • params属性通过请求的请求参数匹配请求映射,不匹配则报400
  • params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系,如果有多个参数则必须全部满足才能映射成功
  • param:要求请求映射所匹配的请求必须携带param请求参数
  • !param:要求请求映射所匹配的请求必须不能携带param请求参数
  • param=value:要求请求映射所匹配的请求必须携带param请求参数且param=value
  • param!=value:要求请求映射所匹配的请求必须携带param请求参数,但是param!=value

5.@RequestMapping的headers属性

  • headers属性通过请求的请求头信息匹配请求映射
  • headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
  • header:要求请求映射所匹配的请求必须携带header请求头信息
  • !header:要求请求映射所匹配的请求必须不能携带header请求头信息
  • header=value:要求请求映射所匹配的请求必须携带header请求头且header=value
  • header!=value:要求请求映射所匹配的请求必须携带header请求头,但是header!=value

SpringMVC支持路径中的占位符(重点)

  • 原始方式:/deleteUser?id=1
  • rest方式:/deleteUser/1
  • SpringMVC路径中的占位符常用于restful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的vallue属性中通过占位符{xxx}表示传输的数据,再通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
  • 如果value路径中有占位符,则请求路径中也必须有值,需要对应

SpringMVC获取请求参数

1.通过servletAPI获取

  • 将HttpServletRequset作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象

2.通过控制器方法的形参获取请求参数

  • 在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参
  • 对于单个的同名的参数直接赋值给形参,对于多个同名的参数可以使用字符串接收,结果中间会以逗号隔开,也可以通过字符串类型的数组接收
  • 如果请求参数和形参名称不一致则不能进行赋值

@RequestParam

  • 将请求参数和控制器方法的形参创建映射关系
  • RequestParam注解一共有三个属性
  • value:指定为形参赋值的请求参数的参数名
  • required:设置是否必须传输此参数请求参数,默认值为true
  • 若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400;若设置为false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标示的形参的值为null;
  • defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为“”时,则使用默认值为形参赋值

@RequestHeader

  • @ReqeustHeader是将请求头信息和控制器方法的形参创建映射关系
  • @RequestHeader注解一共有三个属性:value,required,defaultValue,用法同@RequestParam
  • 想要获取请求头信息则必须使用该参数

@CookieValue

  • CookieValue是将cookie数据和控制器方法的形参创建映射关系
  • CookieValue注解一共有三个属性:value,required,defaultValue,用法同@RequestParam

通过POJO获取请求参数

  • 可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值

解决获取请求参数的乱码问题

  • 解决获取请求参数的乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,但是必须在web.xml中进行注册
  • SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效

域对象共享数据

  • 1.使用servletAPI向request域对象共享数据
  • 2.使用ModelAndView向request域对象共享数据
  • 3.使用Model向request域对象共享数据
  • 4.使用map向requsest
  • 5.使用ModelMap向request域对象共享数据
  • 6.Model,ModelMap,Map的关系
    • Model,ModelMap,Map类型的参数其实本质上都是BingingAwareModelMap类型的

7.向session域共享数据

8.向application域共享数据

SpringMVC的视图

  • SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
  • SpringMVC视图的种类很多,默认有转发视图和重定向视图
  • 当工厂引入jstl的依赖,默认视图会自动转换为JstlView
  • 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymelead的视图解析器,由此视图解析器解析之后所得到的的是ThymeleafView

ThymeleafView

  • 当控制器方法中所设置的视图名称没有任何前缀时,

RESTFul

  • REST:Representatation State Transfer,表现层资源状态转移
  • 是一种软件架构的风格,一种格式

RESTFul的实现

  • 具体说就是HTTP协议里面,四个表示操作方式的动词:GET,POST,PUT,DELETE
  • 他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源
  • REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性
    操作传统方式REST风格
    查询操作getUserById?id=1user/1–>get请求方式
    保存操作saveUseruser–>post请求方式
    删除操作deleteUser?id=1user/1–>delete请求方式
    更新操作updateUseruser–>put请求方式

3.Mybatis

1.历史

  • 1.MyBatis最初是Apache的一个开源项目iBatis
  • 2.2010年6月这个项目由Apache Software Foundation迁移到了Google Code
  • 3.随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis,代码于2013年11月迁移到Github

2.定义

  • 1.MyBatis 是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibernate 的替代方案
  • 2.MyBatis 内部封装了JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,开发者只需要关注 SQL 语句本身。
  • 3.数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中数据模型的统称(例:文件的存储、数据的读取以及对数据表的增删改查等都是数据持久化操作)
  • 4.MyBatis 支持自定义 SQL、存储过程以及高级映射,可以在实体类和 SQL 语句之间建立映射关系,是一种半自动化的 ORM 实现。
  • 5.ORM(Object Relational Mapping,对象关系映射)是一种数据持久化技术,它在对象模型和关系型数据库之间建立起对应关系,并且提供了一种机制,通过 JavaBean 对象去操作数据库表中的数据。
  • 6.MyBatis 的主要思想是将JDBC中的大量 SQL 语句剥离出来,使用 XML 文件或注解的方式实现 SQL 的灵活配置,将 SQL 语句与程序代码分离,在不修改程序代码的情况下,直接在配置文件中修改 SQL 语句。

3.优缺点

1.优点

  • 1.MyBatis 免费且开源
  • 2.MyBatis 简化JDBC代码,小巧并且简单易学
  • 3.MyBatis 将SQL语句与逻辑代码分离,降低耦合度,便于统一管理和优化,提高了代码的可重用性
  • 4.MyBatis 提供 XML 标签,支持编写动态 SQL 语句
  • 5.MyBatis 提供映射标签,支持对象与数据库的 ORM 字段关系映射。
  • 6.MyBatis 可以使用一系列插件,简化开发

2.缺点

  • SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

4.适用场景

  • 1.对性能要求高,可以使用 JDBC
  • 2.业务简单的项目可以使用 Hibernate
  • 3.需要灵活的 SQL ,可以使用 MyBatis
  • 4.Spring JDBC 可以和 ORM 框架混用

5.MyBatis和Hibernate的区别

  • Hibernate 和 MyBatis 都是目前业界中主流的对象关系映射(ORM)框架,它们的主要区别如下

1.优化比较

  • 1.Hibernate 使用 HQL(Hibernate Query Language)语句,独立于数据库,不需要编写大量的 SQL就可以完全映射,但会多消耗性能,且开发人员不能自主的进行 SQL 性能优化
  • 2.MyBatis 需要手动编写 SQL,所以灵活多变。支持动态 SQL、处理列表、动态生成表名、支持存储过程

2.映射比较

  • 1.MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO 和 SQL 的映射关系。
  • 2.Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可

3.缓存机制比较

  • 1.Hibernate 的二级缓存配置在 SessionFactory 生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置缓存
  • 2.MyBatis 的二级缓存配置在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制,并且 Mybatis 可以在命名空间中共享相同的缓存配置和实例,通过 Cache-ref 来实现
  • 3.Hibernate 对查询对象有着良好的管理机制,用户无需关心 SQL,所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示
  1. MyBatis 使用二级缓存时需要特别小心,如果不能完全确定数据更新操作的波及范围,应避免 Cache 的盲目使用,否则脏数据的出现会给系统的正常运行带来很大的隐患

4.优缺点比较

Hibernate

  • 1.Hibernate 的 DAO 层开发比 MyBatis 简单;Mybatis 需要维护 SQL 和结果映射
  • 2.Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便
  • 3.Hibernate 数据库移植性很好;MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL
  • 4.Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳

Mybatis

  • 1.MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
  • 2.MyBatis 容易掌握,而 Hibernate 门槛较高。

6.第一个MyBatis程序

1.新建Maven项目

在这里插入图片描述

2.导入架包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <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>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <!--mysql连接,其中Driver就在其中-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
    </dependencies>

</project>

3.导入配置文件

1.log4j.properties(poperties失败,导入log4j.xml文件)

  • 作用:开启log4j日志功能,用于查看运行日志
  • 位置:main/resources根目录下
  • 内容:
    log4j.rootLogger=DEBUG, stdout
    
    # SqlMap logging configuration...
    log4j.logger.com.ibatis=DEBUG
    log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG
    log4j.logger.com.ibatis.sqlmap.engine.cache.CacheModel=DEBUG
    log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientImpl=DEBUG
    log4j.logger.com.ibatis.sqlmap.engine.builder.xml.SqlMapParser=DEBUG
    log4j.logger.com.ibatis.common.util.StopWatch=DEBUG
    log4j.logger.java.sql.Connection=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    
    # Console output...
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
            <param name="Encoding" value="UTF-8"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m(%F:%L) \n"/>
            </layout>
        </appender>
        <!--范围-->
        <logger name="java.sql">
            <!--级别-->
            <level value="debug"/>
        </logger>
        <logger name="org.apache.ibatis">
            <level value="info"/>
        </logger>
        <root>
            <level value="debug"/>
            <appender-ref ref="STDOUT"/>
        </root>
        <!--日志的级别
            FATAL(致命) > ERROR(错误) > WARN(警告) > INFO(信息) > DEBUG(调试)
        -->
    </log4j:configuration>
    

2.mybatis-config.xml

  • 作用:对Myabtis框架进行基本配置(例:数据库的用户名密码,mapper.xml的位置)
  • 位置:main/resources根目录下
  • 内容:
    <?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>
    
    	<!-- 标签的顺序需要按如下设置
    	1.properties
    	2.settings
    	3.typeAliases
    	4.typeHandlers
    	5.objectFactory
    	6.objectWrapperFactory
    	7.reflectorFactory
    	8.plugins
    	9.environments
    	10.databaseIdProvider
    	11.mappers
    	-->
    
    	<!-- environments标签配置数据库环境
    	1.environments标签下可以配置多个environment标签
    	2.每一个environment标签相当于一个数据库配置
    	3.environments的default属性与environment中的id属性连用确定当前使用哪一个数据库配置
    	-->
    	<environments default="mysql">
    		<environment id="mysql">
    			<!-- 设置事务管理方式
    			type: 1.JDBC:手动管理事务
    				  2.MANAGED:使用第三方事务管理插件
    			-->
    			<transactionManager type="JDBC"></transactionManager>
    			<!-- 设置连接池信息
    			type: 1.POOLED:使用默认打的连接池技术
    				  2.UNPOOLED:不使用默认的连接池技术
    			-->
    			<dataSource type="POOLED">
    				<property name="driver" value="com.mysql.jdbc.Driver"/>
    				<!--XML文件中表示&需要用&amp;-->
    				<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"/>
    				<property name="username" value="root"/>
    				<property name="password" value="root"/>
    			</dataSource>
    		</environment>
    	</environments>
    
    	<!--注册mapper.xml使用,如果不注册报错:Type interface 包.接口名 is not known to the MapperRegistry-->
    	<mappers>
    		<mapper resource="dao/UserDaoMapper.xml"/>
    	</mappers>
    </configuration>
    

4.测试配置

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

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

public class TestMybatis {

    /**
     * 获取Mybatis的连接 sqlSession
     * @throws IOException
     */
    @Test
    public void getSqlSession() throws IOException {
        //1.读取Mybatis的主配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        //2.创建sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //3.通过sqlSessionFactory获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //打印出sqlSession对象地址即表示配置成功(org.apache.ibatis.session.defaults.DefaultSqlSession@cc285f4)
        System.out.println(sqlSession);
    }
}

5.建表以及相应实体类

  • t_user表
    use mybatis_test;
    CREATE TABLE t_user(
    	id INT(11) PRIMARY KEY AUTO_INCREMENT,
    	name VARCHAR(20),
    	sex VARCHAR(2),
    	age INT(10),
    	height DOUBLE,
    	weihgt DOUBLE//注意和实体类中名称的不一致
    );
    
  • User实体类(此处需要导入lombok依赖,也可以手动填写getter,setter,构造,toStrong等方法)
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private String sex;
        private Integer age;
        private Double height;
        private Double weight;//注意和表中名称的不一致
    
    }
    

6.DAO层以及对应的mapper实现

  • 注意mapper文件按规范需要放入resources包下对应的目录下,否则会报错
    在这里插入图片描述
    package dao;
    
    import domains.User;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    public interface UserDao {
        User selectById(Integer id);
    
        User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
    
        List<User> selectAll();
    
        void insert(User user);
    
        void deleteById(Integer id);
    
        void updateById(User user);
    //    void updateById(@Param("user") User user);
    }
    
    <?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" >
    <!-- 命名空间
        namespace:该xml对应的dao接口的全类名
    -->
    <mapper namespace="dao.UserDao">
        <!-- 增删改查分别对应不同的标签
        id:对应的接口的方法名
        resultType:确定查询接口的返回值的类型
        -->
        <select id="selectById" resultType="domains.User">
           select * from t_user where  id = #{id}
        </select>
    
        <!-- 多个基本参数时,不能直接使用形参名
        方法1:使用参数的index(select * from t_user where  name = #{0} and age = #{1}),案例测试失败
        方法2:使用param+index(select * from t_user where  name = #{param1} and age = #{param2})
        方法3:接口使用@Param("别名")注解,然后在使用别名,推荐
        -->
        <select id="selectByNameAndAge" resultType="domains.User">
           select * from t_user where  name = #{name} and age = #{age}
        </select>
    
        <!--如果返回结果是集合或者数组,resultType只需要使用泛型或类型即可,会自动分析-->
        <select id="selectAll" resultType="domains.User">
            select * from t_user
        </select>
    
        <!--Mybatis默认不提交事务,做增删改时需要手动提交事务-->
        <!--没有返回值,resultType可以不写-->
        <insert id="insert">
            insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight})
        </insert>
    
        <delete id="deleteById">
            delete from t_user where id = #{abc}
        </delete>
    
    	<!--注意:#{}中的数据需要和javaBean对象的属性保持一致,而左边的属性需要和mysql表中的字段名保持一致,例: weihgt = #{weight}-->
        <update id="updateById">
    --      update t_user set name = #{user.name}, sex = #{user.sex}, age = #{user.age}, height = #{user.height}, weihgt = #{user.weight} where id = #{user.id}
            update t_user set name = #{name}, sex = #{sex}, age = #{age}, height = #{height}, weihgt = #{weight} where id = #{id}
        </update>
    </mapper>
    

7.测试mapper

@Test
    public void testSelect() throws IOException {
        //1.读取Mybatis的主配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        //2.创建sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //3.通过sqlSessionFactory获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
        UserDao mapper = sqlSession.getMapper(UserDao.class);

        //5.调用方法
        User user = mapper.selectById(1);

        User useLi = mapper.selectByNameAndAge("李四", 18);

        List<User> users = mapper.selectAll();

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

        System.out.println(user);

        System.out.println(useLi);

        System.out.println(users);
    }

    @Test
    public void testInsert() throws IOException {
        //1.读取Mybatis的主配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        //2.创建sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //3.通过sqlSessionFactory获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
        UserDao mapper = sqlSession.getMapper(UserDao.class);

        //5.调用方法
        mapper.insert(new User(null,"李四","男",28,185.0,130.0));

        //6.提交事务
        sqlSession.commit();

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

    @Test
    public void testDelete() throws IOException {
        //1.读取Mybatis的主配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        //2.创建sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //3.通过sqlSessionFactory获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
        UserDao mapper = sqlSession.getMapper(UserDao.class);

        //5.调用方法
        mapper.deleteById(1);

        //6.提交事务
        sqlSession.commit();

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

    @Test
    public void testUpdate() throws IOException {
        //1.读取Mybatis的主配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        //2.创建sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //3.通过sqlSessionFactory获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
        UserDao mapper = sqlSession.getMapper(UserDao.class);

        //5.调用方法
        mapper.updateById(new User(1,"李四","男",28,166.0,130.0));

        //6.提交事务
        sqlSession.commit();

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

8.编写业务层代码

package service;

import domains.User;

import java.util.List;

public interface UserService {
    User selectById(Integer id);

    User selectByNameAndAge(String name, Integer age);

    List<User> selectAll();

    void insert(User user);

    void deleteById(Integer id);

    void updateById(User user);
}
package service.impl;

import dao.UserDao;
import domains.User;
import org.apache.ibatis.session.SqlSession;
import service.UserService;
import utils.MybatisUtil;

import java.util.List;

public class UserServiceImpl implements UserService {
    public User selectById(Integer id) {
        SqlSession sqlSession = null;
        User user;
        try {
            //1.通过Mybatis工具类获取sqlSession
            sqlSession = MybatisUtil.getSqlSession();
            //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //3.调用方法
            user = mapper.selectById(1);
            sqlSession.commit();
        } catch (Exception e) {
            assert sqlSession != null;
            sqlSession.rollback();
            throw new RuntimeException("userServiceImpl.selectById异常:"+e);
        } finally {
            //4.关闭sqlSession
            assert sqlSession != null;
            MybatisUtil.closeSqlSession(sqlSession);
        }
        System.out.println(user);
        return user;
    }

    public User selectByNameAndAge(String name, Integer age) {
        SqlSession sqlSession = null;
        User user;
        try {
            //1.通过Mybatis工具类获取sqlSession
            sqlSession = MybatisUtil.getSqlSession();
            //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //3.调用方法
            user = mapper.selectByNameAndAge("李四",28);
            sqlSession.commit();
        } catch (Exception e) {
            assert sqlSession != null;
            sqlSession.rollback();
            throw new RuntimeException("userServiceImpl.sselectByNameAndAge异常:"+e);
        } finally {
            //4.关闭sqlSession
            assert sqlSession != null;
            MybatisUtil.closeSqlSession(sqlSession);
        }
        System.out.println(user);
        return user;
    }

    public List<User> selectAll() {
        SqlSession sqlSession = null;
        List<User> users;
        try {
            //1.通过Mybatis工具类获取sqlSession
            sqlSession = MybatisUtil.getSqlSession();
            //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //3.调用方法
            users = mapper.selectAll();
            sqlSession.commit();
        } catch (Exception e) {
            assert sqlSession != null;
            sqlSession.rollback();
            throw new RuntimeException("userServiceImpl.selectAll异常:"+e);
        } finally {
            //4.关闭sqlSession
            assert sqlSession != null;
            MybatisUtil.closeSqlSession(sqlSession);
        }
        System.out.println(users);
        return users;
    }

    public void insert(User user) {
        SqlSession sqlSession = null;
        try {
            //1.通过Mybatis工具类获取sqlSession
            sqlSession = MybatisUtil.getSqlSession();
            //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //3.调用方法
            mapper.insert(new User());
            sqlSession.commit();
        } catch (Exception e) {
            assert sqlSession != null;
            sqlSession.rollback();
            throw new RuntimeException("userServiceImpl.insert异常:"+e);
        } finally {
            //4.关闭sqlSession
            assert sqlSession != null;
            MybatisUtil.closeSqlSession(sqlSession);
        }
    }

    public void deleteById(Integer id) {
        SqlSession sqlSession = null;
        try {
            //1.通过Mybatis工具类获取sqlSession
            sqlSession = MybatisUtil.getSqlSession();
            //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //3.调用方法
            mapper.deleteById(1);
            sqlSession.commit();
        } catch (Exception e) {
            assert sqlSession != null;
            sqlSession.rollback();
            throw new RuntimeException("userServiceImpl.deleteById异常:"+e);
        } finally {
            //4.关闭sqlSession
            assert sqlSession != null;
            MybatisUtil.closeSqlSession(sqlSession);
        }
    }

    public void updateById(User user) {
        SqlSession sqlSession = null;
        try {
            //1.通过Mybatis工具类获取sqlSession
            sqlSession = MybatisUtil.getSqlSession();
            //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //3.调用方法
            mapper.updateById(new User());
            sqlSession.commit();
        } catch (Exception e) {
            assert sqlSession != null;
            sqlSession.rollback();
            throw new RuntimeException("userServiceImpl.updateById异常:"+e);
        } finally {
            //4.关闭sqlSession
            assert sqlSession != null;
            MybatisUtil.closeSqlSession(sqlSession);
        }
    }
}

7.方法参数绑定

1.一个基本类型参数(包含String)

  • 方法只包含一个基本类型参数时,对应的mapper实现中#{}和${}理论上可以使用任意值都可以查询,但是规范与形参名保持一致,见名知意
    void deleteById(Integer id);
    
    <delete id="deleteById">
        <!--不规范delete from t_user where id = #{abc}-->
    	delete from t_user where id = #{id}
    	<!--${}也可以实现,但是${}类似字符串拼接所以需要手动加上'' delete from t_user where id = '${id}'-->
    </delete>
    

2.多个基本类型参数(包含String)

  • 方法包含多个基本类型参数时,推荐使用@Param注解

    User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
    
     <!--多个基本参数时,不能直接使用形参名(否则报错Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2])
        方法1:使用参数的index(select * from t_user where  name = #{arg0} and age = #{arg1}),案例测试成功
        方法2:使用param+index(select * from t_user where  name = #{param1} and age = #{param2}),案例测试成功
        方法3:接口使用@Param("别名")注解,然后在使用别名,推荐
        -->
        <select id="selectByNameAndAge" resultType="domains.User">
           select * from t_user where  name = #{name} and age = #{age}
        </select>
    
  • 上述#{}都可以使用${}代替,但是需要手动加上’’

  • 方法一和方法二的原理:Mybatis会将这些参数放在一个map集合中,并以两种方式存储

    • 1.以arg0,arg1,…为键,以参数为值
    • 2.以param1,parm2,…为键,以参数为值
  • 因此只需要通过#{}和${}以键的方式访问即可

  • 补充:方法4(通过传入一个map集合,手动将值存入进入map集合)

    List<User> selectByMapNameAndAge(Map<String, Object> map);
    
    <!--List<User> selectByMapNameAndAge(Map<String, Object> map);-->
        <select id="selectByMapNameAndAge" resultType="user">
            select * from t_user where  name = #{name} and age = #{age}
        </select>
    
    	@Test
        public void testMybatisUtil(){
            //1.通过工具类获取SqlSession对象
            SqlSession sqlSession = MybatisUtil.getSqlSession();
            //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //3.调用方法
            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("name","李四");
            map.put("age",18);
            List<User> users = mapper.selectByMapNameAndAge(map);
            //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接
            MybatisUtil.closeSqlSession(sqlSession);
            System.out.println(users);
        }
    

3.对象类型参数

一个对象类型

  • 一个对象类型时可以直接在#{}中使用对象的属性名

多个对象类型

  • 多个对象类型时可以使用@Param注解然后在#{}中使用别名.属性名用以区分多个对象
         void updateById(User user);
    //   void updateById(@Param("user") User user);
    
        <update id="updateById">
    --      update t_user set name = #{user.name}, sex = #{user.sex}, age = #{user.age}, height = #{user.height}, weihgt = #{user.weight} where id = #{user.id}
            update t_user set name = #{name}, sex = #{sex}, age = #{age}, height = #{height}, weihgt = #{weight} where id = #{id}
        </update>
    

8.MybatisUtil工具类简化配置

package utils;

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.IOException;
import java.io.InputStream;

public class MybatisUtil {
 
    private static SqlSessionFactory sqlSessionFactory;

    //程序运行后只需要加载一次配置文件和SqlSessionFactory
    static {
        //1.加载Mybatis的主配置文件
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException("MybatisUtil配置文件加载异常" + e);
        }
        //2.使用sqlSessionFactoryBuilder创建sqlSessionFactory对象
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    //获取sqlSession
    public static SqlSession getSqlSession(){
        ///3.通过sqlSessionFactory获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }

    //关闭sqlSession
    public static void closeSqlSession(SqlSession sqlSession){
        sqlSession.close();
    }
}
@Test
    public void testMybatisUtil(){
        //1.通过工具类获取SqlSession对象
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        //3.调用方法
        List<User> users = mapper.selectAll();
        //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接
        MybatisUtil.closeSqlSession(sqlSession);
        System.out.println(users);
    }

9.service业务层代码

package service;

import domains.User;

import java.util.List;

public interface UserService {

    List<User> selectAll();

}
package service.impl;

import dao.UserDao;
import domains.User;
import org.apache.ibatis.session.SqlSession;
import service.UserService;
import utils.MybatisUtil;

import java.util.List;

public class UserServiceImpl implements UserService {

    public List<User> selectAll() {
        SqlSession sqlSession = null;
        List<User> users;
        try {
            //1.通过Mybatis工具类获取sqlSession
            sqlSession = MybatisUtil.getSqlSession();
            //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //3.调用方法
            users = mapper.selectAll();
            sqlSession.commit();
        } catch (Exception e) {
            assert sqlSession != null;
            sqlSession.rollback();
            throw new RuntimeException("userServiceImpl.selectAll异常:"+e);
        } finally {
            //4.关闭sqlSession
            assert sqlSession != null;
            MybatisUtil.closeSqlSession(sqlSession);
        }
        System.out.println(users);
        return users;
    }
}

10.mybatis-config.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>

	<!-- 标签的顺序需要按如下设置
	1.properties 属性
	2.settings 设置 通过该标签改变Mybatis的内部配置
	3.typeAliases 类型别名
	4.typeHandlers 类型处理器
	5.objectFactory 对象工厂
	6.objectWrapperFactory
	7.reflectorFactory
	8.plugins 插件
	9.environments 环境变量
	1.transactionManager 事务管理器
		2.dataSource 数据源
	10.databaseIdProvider 数据库厂商标识
	11.mappers 映射器
	-->

	<!--properties标签提供了引入外部文件或者设置变量的方法
	1.引入外部文件:
		resource:外部文件相对路径
		url:外部文件绝对路径,注意绝对路径前要加上file:
	2.设置变量:可以使用${}引入该变量
		<properties>
			<property name="testPojo" value="domains"/>
		</properties>
		<package name="${testPojo}"/>
	-->

	<properties resource="db.properties"/>
<!--	<properties url="file:E:\MySelfProject\Mybatis\src\main\resources\db.properties"/>-->

	<settings>
		<!--配置缓存的全局开关true|false 默认为true-->
	    <setting name="cacheEnabled" value="true"/>
	    <!--延迟加载的全局开关true|false	默认为false 开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态 -->
	    <setting name="lazyLoadingEnabled" value="true"/>
	    <!--是否允许单一语句返回多结果集(需要兼容驱动true|false	默认为true-->
	    <setting name="multipleResultSetsEnabled" value="true"/>
	    <!--使用列标签代替列名,默认为true-->
	    <setting name="useColumnLabel" value="true"/>
	    <!--允许JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作,默认为false-->
	    <setting name="useGeneratedKeys" value="false"/>
	    <!--指定 MyBatis 应如何自动映射列到字段或属性。
			NONE 表示取消自动映射。
			PARTIAL 表示只会自动映射,没有定义嵌套结果集和映射结果集。
			FULL 会自动映射任意复杂的结果集(无论是否嵌套)	NONE、PARTIAL、FULL	PARTIAL-->
	    <setting name="autoMappingBehavior" value="PARTIAL"/>
	    <!--指定自动映射当中未知列(或未知属性类型)时的行为,默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException 异常	NONE、WARNING、FAILING	NONE-->
	    <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
	    <!--配置默认的执行器。SIMPLE 是普通的执行器;REUSE 会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新 	SIMPLE、REUSE、BATCH	SIMPLE-->
	    <setting name="defaultExecutorType" value="SIMPLE"/>
	    <!--设置超时时间,它决定驱动等待数据库响应的秒数	任何正整数	Not Set (null)-->	>
	    <setting name="defaultStatementTimeout" value="25"/>
	    <!--设置数据库驱动程序默认返回的条数限制,此参数可以重新设置	任何正整数  	Not Set (null)-->
	    <setting name="defaultFetchSize" value="100"/>
	    <!--允许在嵌套语句中使用分页(RowBounds)。如果允许,设置 false,默认值false-->
	    <setting name="safeRowBoundsEnabled" value="false"/>
	    <!--是否开启自动驼峰命名规则映射-->
	    <setting name="mapUnderscoreToCamelCase" value="false"/>
	    <!--利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速联复嵌套査询。默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据-->
	    <setting name="localCacheScope" value="SESSION"/>
	    <!--当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型-->
	    <setting name="jdbcTypeForNull" value="OTHER"/>
	    <!--指定哪个对象的方法触发一次延迟加载	—	equals、clone、hashCode、toString-->
	    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
	</settings>

	<!-- 注意
	1.无论哪种形式起别名,别名不区分大小写
	2.mapper文件中的namespace不能使用别名,只能使用全类名
	3.可以使用@Alias("别名")注解直接在实体类上取别名
	-->
	<typeAliases>
		<!-- typeAlias标签配置实体类别名,然后在mapper文件中可以在resultType标签中直接使用别名
		1.type:实体类全类名
		2.alias:实体类别名
		缺点:每一个实体类都需要一个typeAlias标签,推荐使用package
		-->
<!--		<typeAlias type="domains.User" alias="user"/>-->

		<!-- package标签给指定包下的所有类批量起别名,默认小驼峰(User->user)
		1.name:需要别名实体类所在的包
		-->
		<package name="domains"/>
	</typeAliases>

	<!-- environments标签配置数据库环境
	1.environments标签下可以配置多个environment标签
	2.每一个environment标签相当于一个数据库配置
	3.environments的default属性与environment中的id属性连用确定当前使用哪一个数据库配置
	-->
	<environments default="mysql">
		<environment id="mysql">
			<!-- 设置事务管理方式
			type: 1.JDBC:JDBC原生事务管理,即手动管理事务
				  2.MANAGED:使用第三方事务管理插件
			-->
			<transactionManager type="JDBC"></transactionManager>
			<!-- 设置连接池信息
			type: 1.POOLED:使用默认的连接池技术
				  2.UNPOOLED:不使用默认的连接池技术
			-->
			<dataSource type="POOLED">
<!--				<property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!--				&lt;!&ndash;XML文件中表示&需要用&amp;&ndash;&gt;-->
<!--				<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"/>-->
<!--				<property name="username" value="root"/>-->
<!--				<property name="password" value="root"/>-->

				<!--使用properties引入配置文件后使用${}写入键的方式写入数据-->
				<property name="driver" value="${driver}"/>
				<!--XML文件中表示&需要用&amp;-->
				<property name="url" value="${url}"/>
				<property name="username" value="${username}"/>
				<property name="password" value="${password}"/>

			</dataSource>
		</environment>
	</environments>

	<!--每一个mapper.xml都需要在mybatis的核心配置文件注册-->
	<!--注册mapper.xml使用,如果不注册会报错Type interface dao.UserDao is not known to the MapperRegistry.-->
	<mappers>
		<!--注册一个mapper.xml文件-->
<!--		<mapper resource="dao/UserDaoMapper.xml"/>-->
		<!--批量注册,自动扫描包下的mapper文件并注册,
		注意使用包扫描mapper时mapper文件名需要dao接口名一致,否则会报错BindingException: Invalid bound statement (not found): dao.UserDao.selectById
		规范接口和mapper文件都统一用xxxMapper命名
		且mapper文件需要和dao接口所在包层一致,resources下包名之间用/分割不能用.
		-->
		<package name="dao"/>
	</mappers>
</configuration>
  • idea设置mybatis-config.xml文件模板
    在这里插入图片描述

11.xxxMapper.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" >
<!-- 命名空间
    namespace:该xml对应的dao接口的全类名
-->
<mapper namespace="dao.UserMapper">
 <!--开启二级缓存
    type:表示自定义的缓存的类,没有的情况下会存在一个默认的缓存类PerpetualCache.class。
    blocking:是否采用阻塞的方式(加锁)
    eviction:缓存到达上限时的淘汰策略
        LRU – 会淘汰使用频次最低的缓存内容
        FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
        SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
        WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
    flushInterval:定义缓存定时清空策略(ms),默认情况是不设置,没有刷新间隔,缓存仅仅调用语句时刷新
    readOnly:属性可以被设置为 true 或 false,默认是 false
    size:缓存的最大容量,默认值是 1024
    -->
    <cache size="1024" eviction="LRU" flushInterval="60000"/>
<!--    <cache type="" blocking="" eviction="" flushInterval="" readOnly="" size=""/>-->

    <!--1.可重复使用sql片段,利用include标签和对应的refid绑定该sql片段,可以提高效率
        <select id="selectById" resultType="user">
            <include refid="t_user_id"></include> from t_user where  id = #{id}
        </select>
    -->
    <sql id="t_user_id">
        select id,name,sex,age,height,weihgt
    </sql>

    <!--2.手动映射属性和字段,可以只映射不对应的字段
    id:引用标识
    type:实体类的全类名或别名
    -->
    <resultMap id="userMap" type="user">
        <!-- id映射主键
        property: 实体类的属性名
        column: 表的字段名
        -->
        <id property="id" column="id"/>
        <!-- result映射除主键外的普通字段-->
        <result property="weight" column="weihgt"/>
		<!-- 当一个属性为对象时使用association标签
        property: 对象属性的属性名
        javaType: 对象属性的类型
        -->
        <association property="card" javaType="domains.Card">
            <id property="userId" column="user_id"/>
            <result property="userCard" column="user_card"/>
        </association>
		<!-- 集合属性映射使用collection标签
	    property:对象属性名
	    javaType:集合的类型
	    ofType:泛型的类型
	    -->
        <collection property="orders" javaType="java.util.List" ofType="domains.Order">
            <id property="orderId" column="order_id"/>
            <result property="orderCode" column="order_code"/>
        </collection>
    </resultMap>

<!--    <cache type="" blocking="" eviction="" flushInterval="" readOnly="" size=""/>-->
<!--    <cache-ref namespace="" />-->

<!--    <parameterMap id="" type="" >-->
<!--        <parameter property=""></parameter>-->
<!--    </parameterMap>-->

    <!-- 增删改查分别对应不同的标签
    id:对应的接口的方法名
    resultType:确定查询接口的返回值的类型
    -->
<!--    <select id="selectById" resultType="user">-->
<!--      select * from t_user where  id = #{id}-->
<!--    </select>-->
    <!--使用resultMap手动映射,resultMap和resultType只能使用一个-->
    <select id="selectById" resultMap="userMap">
        select * from t_user where  id = ${id}<!--或#{id}-->
    </select>

    <!-- 多个基本参数时,不能直接使用形参名
    方法1:使用参数的index(select * from t_user where  name = #{arg0} and age = #{arg1})
    方法2:使用param+index(select * from t_user where  name = #{param1} and age = #{param2})
    方法3:接口使用@Param("别名")注解,然后在使用别名,推荐
    -->
    <select id="selectByNameAndAge" resultType="user">
        select * from t_user where  name = #{name} and age = #{age}
    </select>

    <!--如果返回结果是集合或者数组,resultType只需要使用泛型或类型即可,会自动分析-->
    <select id="selectAll" resultType="user">
        select * from t_user
    </select>

    <!--Mybatis默认不提交事务,做增删改时需要手动提交事务-->
    <!--没有返回值,resultType可以不写-->
    <insert id="insert">
        insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight})
    </insert>

    <delete id="deleteById">
        delete from t_user where id = #{abc}
    </delete>

    <update id="updateById">
--      update t_user set name = #{user.name}, sex = #{user.sex}, age = #{user.age}, height = #{user.height}, weihgt = #{user.weight} where id = #{user.id}
        update t_user set name = #{name}, sex = #{sex}, age = #{age}, height = #{height}, weihgt = #{weight} where id = #{id}
    </update>

    <!--
        1.在xml文件中
        > 使用 &gt; 表示
        》 使用 &gt;= 表示
        < 使用 &lt; 表示
        《 使用 &lt;= 表示

        2.#{}和${}的区别
            1.#{} select * from t_user id = ?
            #{}类似于占位符赋值
            1.可以防止sql注入
            2.只能拼接数据,不能拼接关键字(字段名,运算符等)

            2.${} select * from t_user id = 1
            ${}类似于字符串拼接
            1.容易被sql注入
            2.可以绑定关键字

            预编译时sql语句会提前发送给Mysql数据库,然后进行语法检测,语义检测,拆分sql
            语法检测:查看sql语法是否正确
            语义检测:查看sql中的关键字是否存在
            拆分sql:根据关键词将sql语句进行拆分
            ${}中的关键字会被拆分,容易被注入,例:id=xxx or 1=1
            #{}中的关键字不会被识别,只会被整体当成一个值
            原理:跳过语义检测阶段,防止值中关键字被识别
    -->
</mapper>
  • idea设置mapper文件模板
    在这里插入图片描述

1.三种解决字段名和属性名映射关系的方式

  • 1.通过起别名的方式,将表中的下划线字段名起别名为驼峰形式
    <select id="selectUserByTableName" resultType="user">
         select id,name,sex,age,height,weihgt weight from ${tableName}
    </select>
    
  • 2.通过全局配置mapUnderscoreToCamelCase属性
    <!--是否开启自动驼峰命名规则映射,默认false不开启
    自动将_转换为驼峰,例:emp_name -> empName
    -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    
  • 3.通过resultMap手动解决映射关系

12.表连接查询

1.一对一连接查询

  • A表中有且只有一条数据与B表相关

  • 1.可以通过级联属性赋值查询解决<result property="card.userId" column="user_id"/>

  • 2.也可以通过association 标签解决

  • 例:一个用户(t_user)只有一个识别卡(t_card)

  • CREATE TABLE t_user(
    	id INT(11) PRIMARY KEY AUTO_INCREMENT,
    	name VARCHAR(20),
    	sex VARCHAR(2),
    	age INT(10),
    	height DOUBLE,
    	weihgt DOUBLE
    );
    -------------------------------------
    CREATE TABLE t_card(
    	card_id INT(11) PRIMARY KEY AUTO_INCREMENT,
    	user_card VARCHAR(18) not null,
    	user_id INT(11) REFERENCES t_user(id)
    );
    -------------------------------------
    SELECT * FROM t_user
    LEFT JOIN t_card
    on t_user.id = t_card.user_id
    
  • 实体类

    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private String sex;
        private Integer age;
        private Double height;
        private Double weight;
        private Card card;//根据需求定谁包含谁的属性
    }
    -------------------------------------
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Card {
        private Integer cardId;
        private String  userCard;
        private Integer userId;
    }
    
    package dao;
    
    import domains.User;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    public interface UserMapper {
        List<User> selectAll();
    }
    
    <?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="dao.UserMapper">
    
    	 <resultMap id="userMap" type="user">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <result property="sex" column="sex"/>
            <result property="age" column="age"/>
            <result property="height" column="height"/>
            <result property="weight" column="weihgt"/>
            <result property="weight" column="weihgt"/>
            <association property="card" javaType="domains.Card">
                <id property="userId" column="user_id"/>
                <result property="userCard" column="user_card"/>
            </association>
        </resultMap>
    
    	<select id="selectAll" resultMap="userMap">
            SELECT * FROM t_user
                LEFT JOIN t_card
                    on t_user.id = t_card.user_id
        </select>
    </mapper>
    
    @Test
     public void testMybatisUtil(){
         //1.通过工具类获取SqlSession对象
         SqlSession sqlSession = MybatisUtil.getSqlSession();
         //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
         //3.调用方法
         List<User> users = mapper.selectAll();
         //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接
         MybatisUtil.closeSqlSession(sqlSession);
         System.out.println(users);
     }
    

2.一对多连接查询

  • A表中的一条数据B表有多条数据对应
  • 例:一个用户(t_user)有多个订单(t_order)
  • CREATE TABLE t_order(
    	order_id INT(10) PRIMARY KEY auto_increment,
    	order_code INT(20),
    	user_id int(11) REFERENCES t_user(id)
    );
    -------------------------------------------
    SELECT * FROM t_user
    LEFT JOIN t_order
    on t_user.id = t_order.user_id
    
  • 实体类
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private String sex;
        private Integer age;
        private Double height;
        private Double weight;
        private List<Order> orders;
    }
    ----------------------------------------
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Order {
        private Integer orderId;
        private String orderCode;
        //作为外键的字段一般不在实体类中体现
    //    private Integer userId;
    }
    
    <?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="dao.UserMapper">
        <resultMap id="userMap" type="user">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <result property="sex" column="sex"/>
            <result property="age" column="age"/>
            <result property="height" column="height"/>
            <result property="weight" column="weihgt"/>
            <result property="weight" column="weihgt"/>
            <collection property="orders" javaType="java.util.List" ofType="domains.Order">
                <id property="orderId" column="order_id"/>
                <result property="orderCode" column="order_code"/>
            </collection>
        </resultMap>
    
        <select id="selectAll" resultMap="userMap">
            SELECT * FROM t_user
                LEFT JOIN t_order
                    on t_user.id = t_order.user_id
        </select>
    </mapper>
    

3.对多对连接查询

  • A表中的一条数据关联B表中的多条数据
  • B表中的一条数据关联A表中的多条数据
  • 例:一个用户可以选择多门课程,一个课程也拥有多个用户
  • CREATE TABLE t_course(
    	course_id INT(10) PRIMARY KEY AUTO_INCREMENT,
    	course_name VARCHAR(20),
    	course_time INT(10)
    );
    -- 多对多关系不能使用外键解决
    -- 需要添加第三张表(关系表)
    -- 关系表只有两个字段
    -- A表的主键,B表的逐渐
    CREATE TABLE t_user_course(
    	user_id INT(11) REFERENCES t_user(id),
    	course_id INT(10) REFERENCES t_course(course_id)
    );
    ---------------------------
    select a.id,a.name,c.course_name,c.course_time 
    from
    t_user a left join t_user_course b 
    on a.id = b.user_id
    left join t_course c 
    on b.course_id = c.course_id;
    
  • 实体类
  • 根据具体需求编写实体类,将其变成一对多
  • 例:查询所有用户以及用户们选修的科目
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private String sex;
        private Integer age;
        private Double height;
        private Double weight;
        private List<Course> courses;
    }
    ---------------------
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Course {
        private Integer courseId;
        private String courseName;
        private Integer courseTime;
    }
    
    <?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="dao.UserMapper">
        <resultMap id="userMap" type="user">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <result property="sex" column="sex"/>
            <result property="age" column="age"/>
            <result property="height" column="height"/>
            <result property="weight" column="weihgt"/>
            <result property="weight" column="weihgt"/>
            <collection property="courses" javaType="java.util.List" ofType="domains.Course">
                <id property="courseId" column="course_id"/>
                <result property="courseName" column="course_name"/>
                <result property="courseTime" column="course_time"/>
            </collection>
        </resultMap>
     
        <select id="selectAll" resultMap="userMap">
            select a.id,a.name,c.course_name,c.course_time
                from
                    t_user a left join t_user_course b
                        on a.id = b.user_id
                            left join t_course c
                                on b.course_id = c.course_id;
        </select>
    </mapper>
    

13特殊查询和添加

1.模糊查询

  • 关键字:like
  • MySQL:select * from t_user where name like '%王%'
  • Mybatis:select * from t_user where name like concat('%',#{name},'%')select * from t_user where name like '%${name}%'select * from t_user where name like "%"#{name}"%"

2.区间查询

  • 关键字:>= xxx <= xxx / between xxx and xxx(全闭区间)
  • MySQL: select * from t_user where age >= 20 and age<= 40select * from t_user where age BETWEEN 20 and 40
  • Mybatis:select * from t_user where age &gt;= #{minAge} and age &lt;= #{maxAge}select * from t_user where age BETWEEN #{minAge} and #{maxAge}

3.添加时获得主键值

  • 前提:主键自增长
  • 方法一:使用selectKey子标签
    <insert id="insert">
            <!--
                keyProperty:查询语句的值赋值给对象的一个属性
                order:
                    BEFORE:在添加语句执行前执行查询
                    AFTER:在添加语句执行后执行查询
                resultType:返回值类型
            -->
            <selectKey keyProperty="id" order="AFTER" resultType="int">
            <!--LAST_INSERT_ID()方法可以获取最后一次增加的自增长的值-->
                select LAST_INSERT_ID()
            </selectKey>
            insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight})
        </insert>
    
  • 方法二:使用useGeneratedKeysv属性
    <!--useGeneratedKeys设置为 true 时,表示如果插入的表id以自增列为主键,则允许 >	JDBC 支持自动生成主键,并可将自动生成的主键id返回
        keyProperty:实体类中对应的主键
        keyColumn:表中的自增主键
        -->
        <insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
            insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight})
        </insert>
    

4.批量删除

  • 1.当参数类型为String类型使用${别名}可以批量删除,不能使用#{}因为它会自动拼接’'(Data truncation: Truncated incorrect DOUBLE value: ‘1,2,3’)
        void deleteByIds(@Param("ids") String ids);
    
    <delete id="deleteByIds">
            delete from t_user where id in (${ids})
    </delete>
    
  • 2.当参数类型为数组或集合时可以使用动态标签中的forEach标签

5.批量添加

void insertBatch(@Param("users") List<User> users);
<!--void insertBatch(List<User> users);-->
 <insert id="insertBatch" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
        insert t_user values
       <foreach collection="users" item="user" separator=",">
            (null,#{user.name},#{user.sex},#{user.age},#{user.height},#{user.weight})
       </foreach>
 </insert>

6.动态设置表名

  • 如果一张表中的数据过多,不利于性能,所以会将该表拆分成多张表共同存储一张表中的数据
  • 此时查询同一张表中的数据时表名可能不相同,所以SQL语句中的表名需要动态设置
  • 动态设置表名需要使用${},使用#{}会报错(You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''t_user'' at line 1)
    List<User> selectUserByTableName(@Param("tableName") String tableName);
    
    <select id="selectUserByTableName" resultType="user">
            select * from ${tableName}
    </select>
    

14.动态SQL

  • Mybatis中动态sql通过标签实现

1.if标签

  • 条件判断,如果test中的条件满足才会执行if标签中的语句
  • 可以通过在where后面加上恒成立表达式(1=1)防止多余的and关键字
    <select id="selectByNameLikeAndAgeRange" resultType="user">
            select * from t_user
                <!--test标签中的连接符为and或or-->
                <if test="name != null and name !=''">
                    <!--如果#{name}的值为空会报错,所以需要使用if标签进行条件判断,test满足才会被执行-->
                    name like concat('%',#{name},'%')
                </if>
                <!--如果值为null会报错,所以也需要if标签进行条件判断-->
                <if test="minAge != null and minAge > 0">
                    and age &gt;= #{minAge}
                </if>
                <if test="maxAge != null and maxAge > minAge">
                    and age &lt;= #{maxAge}
                </if>
    </select>
    

2.where标签

  • 代替where关键字(如果where标签中有内容时会自动生成where关键字,如果没有内容则不会生成where关键字)
  • 使用if标签时会出现多余的关键字,where标签可以自动去除if标签中头部多余的and/or关键字(只能去除头部多余的关键字)
    <select id="selectByNameLikeAndAgeRange" resultType="user">
            select * from t_user
            <where>
             <!--test标签中的连接符为and或or-->
                <if test="name != null and name !=''">
                    <!--如果#{name}的值为空会报错,所以需要使用if标签进行条件判断,test满足才会被执行-->
                    and name like concat('%',#{name},'%')
                </if>
                <!--如果值为null会报错,所以也需要if标签进行条件判断-->
                <if test="minAge != null and minAge > 0">
                    and age &gt;= #{minAge}
                </if>
                <if test="maxAge != null and maxAge > minAge">
                    and age &lt;= #{maxAge}
                </if>
            </where>
    </select>
    

3.set标签

  • 代替修改语句中的set关键字(一般只使用在修改语句中)
  • 自动清除修改语句中多余的逗号(只能自动消除if标签结尾的逗号)
    <update id="updateById">
            update t_user
            <set>
                <if test="name != null">
                    name = #{name},
                </if>
                <if test="sex != null">
                    sex = #{sex},
                </if>
                <if test="age != null">
                    age = #{age},
                </if>
                <if test="height != null">
                    height = #{height},
                </if>
                <if test="weight != null">
                    weihgt = #{weight}
                </if>
            </set>
            where id = #{id}
        </update>
    

4.forEach标签

  • 一般用在in中
  • 注意批量删除时,集合或数组在循环取值时,必须使用@Param注解为参数名其别名,否则会报错
    void deleteByIds(@Param("ids") List<Integer> ids);
    
    <delete id="deleteByIds">
            delete from t_user where id in
            <!-- 循环遍历标签
            collection:循环的参数
            item:每次循环的的元素名
            open:开始的标志
            separator:每个元素的间隔符
            close:结束的标志
    		index:表示迭代中每次迭代到的下标位置
            -->
            <foreach collection="ids" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
    		<!--delete from t_user where
            <foreach collection="ids" item="id"  separator="or">
                id = #{id}
            </foreach>-->
        </delete>
    

5.choose、when、otherwise标签

  • 类似Java 中的 switch 语句
  • 注意:when标签中不需要加and/or关键字,因为只会执行一个分支
    <select id="selectUser" resultType="domains.User">
        select * from user
        <where>
            <choose>
                <when test="name!= null and name!= ''">
                    age =#{age}
                </when>
                <when test="age!= null and age!= ''">
                    name =#{name}
                </when>
                <otherwise>
                    status = 'true'
                </otherwise>
            </choose>
        </where>
    </select>
    

6.trim标签

  • 特点:更加灵活添加或去除前缀或后缀,若标签中没有内容则trim没有任何效果
  • prefix:在trim标签内sql语句加上前缀
  • suffix:在trim标签内sql语句加上后缀
  • prefixOverrides:指定去除多余的前缀内容,如:prefixOverrides=“AND|OR”,去除trim标签内sql语句多余的前缀"AND"或者"OR"
  • suffixOverrides:指定去除多余的后缀内容,suffixOverrides=“,”,去除trim标签内sql语句多余的后缀","
    <update id="updateById">
            update t_user
            <trim prefix="set" suffixOverrides=",">
                <if test="name != null">
                    name = #{name},
                </if>
                <if test="sex != null">
                    sex = #{sex},
                </if>
                <if test="age != null">
                    age = #{age},
                </if>
             <if test="height != null">
                    height = #{height},
                </if>
                <if test="weight != null">
                    weihgt = #{weight}
                </if>
            </trim>
            where id = #{id}
        </update>
    

15.Mybatis缓存

  • 当用户第一次做查询时,Mybatis不仅将结果返回给用户,还将结果保存在缓存中
  • 如果用户第二次做相同的查询时,Mybatis不再从数据库中进行查询,而是从缓存中直接提取数据,节省了与数据库创建的IO流的开销
  • 注意:必须是第二次查询,且与之前的查询相同时缓存才有效
  • 学习步骤
    • 1.如何开启缓存
    • 2.缓存作用范围
    • 3.脏数据的处理
  • 脏数据:缓存中的数据由于增删改的操作和数据库中真实的数据不一致

1.一级缓存

  • 作用范围小,限制大,实践中不使用
1.如何开启缓存
  • 默认开启
2.缓存作用范围
  • 同一个sqlSession创建的mapper可以共用缓存
  • 注意:不能提交事务,一旦提交事务,缓存消失,就会提交多个语句
3.一级缓存失效的4种情况
  • 1.不同的sqlSeesion对应不同的一级缓存
  • 2.同一个sqlSession但查询条件不同
  • 3.同一个sqlSession两次查询查询期间执行了任何一次增删改操作
  • 4.同一个sqlSession两次查询查询期间手动清空了缓存(sqlSession.clearCache())
3.脏数据的处理
  • 一旦执行增删改就立即清空缓存

2.二级缓存

  • 作用范围大,限制小,实践中使用
1.如何开启缓存
  • 1.mybatis-config.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>
    	<settings>
    		<setting name="cacheEnabled" value="true"/>
    	</settings>
    </configuration>
    
  • 2.对应的mapper.xml文件中开启缓存
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="dao.UserMapper">
        <cache size="1024" eviction="LRU" flushInterval="60000"/>
    <!--    <cache type="" blocking="" eviction="" flushInterval="" readOnly="" size=""/>-->
    </mapper>
    
  • 3.sqlSession关闭或提交之后有效
    sqlSession.close()/sqlSession.commit()
    
  • 4.对应的实体类实现可序列化接口
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    import java.util.List;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private static final long serialVersionUID = -5704008945291005272L;
        private Integer id;
        private String name;
        private String sex;
        private Integer age;
        private Double height;
        private Double weight;
        private List<Course> courses;
    }
    
2.缓存作用范围
  • 同一个sqlSessionFactory创建的所有sqlSession共用缓存
  • 注意:二级缓存必须提交事务,这样数据才能被保存到缓存中
  • 二级缓存必须在sqlSession关闭或提交后之后有效,如果关闭或提交sqlSession则数据存放在二级缓存中,否则存放在一级缓存中
3.二级缓存失效的1种情况
  • 3.同一个sqlSessionFactory两次查询查询期间执行了任何一次增删改操作
4.脏数据的处理
  • 默认:一旦执行增删改,就清空缓存
  • 自定义:insert,delete,update,select标签中都有flushCache属性,如果为true则表示一执行该sql操作就清空缓存,false则表示不清空
  • insert,delete,update默认为true,select默认为false
  • useCache 是否使用缓存。可以在每个标签上加上该属性细化缓存
  • 从缓存中读取,如果存在,那么直接返回。不存在,则查询数据库并更新缓存。
  • flushCache 是否更新缓存
  • 强制更新缓存

3.Mybatis缓存查询的顺序

  • 1.先查二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以直接使用
  • 2.如果二级缓存没有命中,再查询一级缓存
  • 3.如果一级缓存也没有命中,则查询数据库
  • 4.sqlSession关闭之前,sqlSession中的数据默认保存在一级缓存,关闭之后,一级缓存中的数据会写入二级缓存

4.Mybatis整合第三方缓存EHCache

1.添加jar包
<!--Mybatis EHCache整合包-->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
        </dependency>

在这里插入图片描述

  • mybatis-ehcache:Mybatis和EHCache的整合包
  • ehcache:EHCache核心包
2.添加配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--磁盘保存路径-->
    <diskStore path="E:\ehcache"/>

    <defaultCache
        maxElementsInMemory="1000"
        maxElementsOnDisk="10000000"
        eternal="false"
        overflowToDisk="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">

    </defaultCache>
</ehcache>
3.设置二级缓存的类型
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

16.Mybatis整合slf4j

  • 存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志

1.添加jar包

<!--Mybatis EHCache整合包-->
		<dependency>
            <!--slf4j日志门面的一个具体实现-->
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

在这里插入图片描述

  • slf4j-api:SLF4J日志门面包
  • logback-classic:支持SLF4J门面接口的一个具体实现

2.添加配置文件logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true">
    <!--指定日志输出的位置-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--日志输出的格式-->
            <!--按照顺序分别是:时间,日志级别,线程名称,打印日志的类,日志主题内容,换行-->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5Level] [%thread] [%logger] [%msg]%n</pattern>
        </encoder>
    </appender>

    <!--设置全局日志级别。日志级别按顺序分别是:DEBUG,INFO,WARN,ERROR-->
    <!--指定任何一个日志级别都只打印当前级别和后面级别的日志-->
    <root level="DEBUG">
        <!--指定打印日志的appender,通过STDOUT引用上文的appender-->
        <appender-ref ref="STDOUT"/>
    </root>

    <!--根据特殊需求指定局部日志级别-->
    <logger name="com.wd.mapper" level="DEBUG"/>
</configuration>

17.Mybatis逆向工程

  • 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate,Ebean支持正向工程
  • 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
    • 1.java实体类
    • 2.Mapper接口
    • 3.Mapper映射文件

1.创建逆向工程的步骤

1.创建Maven项目
2.添加依赖和插件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Mybatis_MBG</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!--Mybatis核心包-->
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!--控制Maven在构建过程中相关配置-->
    <build>
        <!--构建过程中用到的插件-->
        <plugins>
            <!--具体插件,逆向工程的操作是以构建过程中插件形式出现的-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.0</version>
                <!--插件的依赖-->
                <dependencies>
                    <!--逆向工程的核心包-->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                    <!--MySQL驱动-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.38</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>
3.创建Mybatis的核心配置文件
  • mybatis-config.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-config.dtd" >
    <configuration>
    
    	<properties resource="db.properties"/>
    
    	<typeAliases>
    		<package name="domains"/>
    	</typeAliases>
    
    	<environments default="mysql">
    		<environment id="mysql">
    			<transactionManager type="JDBC"></transactionManager>
    			<dataSource type="POOLED">
    				<property name="url" value="${url}"/>
    				<property name="username" value="${username}"/>
    				<property name="password" value="${password}"/>
    			</dataSource>
    		</environment>
    	</environments>
    
    	<mappers>
    		<package name="mapper"/>
    	</mappers>
    </configuration>
    
  • log4j.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
            <param name="Encoding" value="UTF-8"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m(%F:%L) \n"/>
            </layout>
        </appender>
        <logger name="java.sql">
            <level value="debug"/>
        </logger>
        <logger name="org.apache.ibatis">
            <level value="info"/>
        </logger>
        <root>
            <level value="debug"/>
            <appender-ref ref="STDOUT"/>
        </root>
    </log4j:configuration>
    
  • db.properties
    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username=root
    password=root
    
4.创建逆向工程的配置文件
  • 文件名必须是generatorConfig.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <generatorConfiguration>
        <!--
            targetRuntime:执行生成逆向工程的版本
                Mybatis3Simple:(简化版)生成基本的CRUD
                Mybatis3:(完整版)生成带条件地CRUD
            context的内容必须匹配
                property*
                plugin*
                commentGenerator
                (connectionFactory|jdbcConnection)
                javaTypeResolver
                javaModelGenerator
                sqlMapGenerator
                javaClientGenerator
                table+
        -->
        <context id="DB2Tables" targetRuntime="MyBatis3Simple">
            <!--数据库的连接信息-->
            <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/mybatis_test"
                            userId="root"
                            password="root"
                            >
            </jdbcConnection>
            <!--JavaBean的生成策略
                targetPackage:指定生成JavaBean的包目录
                targetProject:指定文件下
             -->
            <javaModelGenerator targetPackage="com.domains" targetProject=".\src\main\java">
                <!--enableSubPackages:是否可以使用子包,true表示com.domains是多层目录,false表示com.domains只是目录名-->
                <property name="enableSubPackages" value="true" />
                <!--trimStrings:去掉字符串前后的空格,逆向将表中的字段名转换成实体类的属性名,去掉字段名中的前后空格-->
                <property name="trimStrings" value="true" />
            </javaModelGenerator>
            <!--SQL映射文件的生成策略-->
            <sqlMapGenerator targetPackage="com.mapper" targetProject=".\src\main\resources">
                <property name="enableSubPackage" value="true" />
            </sqlMapGenerator>
            <!--Mapper接口的生成策略-->
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.mapper" targetProject=".\src\main\java">
                <property name="enableSubPackage" value="true" />
            </javaClientGenerator>
            <!--逆向分析的表
            tableName:设置为*号可以对应所有的表,此时不需要指定domainObjectName,默认大驼峰
            domainObjectName:属性指定生成出来的实体类的类名
            Mapper接口和Mapper映射文件的名称会根据实体类的名称自动生成:实体类名+Mapper
            -->
            <table tableName="t_user" domainObjectName="User"/>
            <table tableName="t_order" domainObjectName="Order"/>
            <table tableName="t_card" domainObjectName="Card"/>
            <table tableName="t_course" domainObjectName="Course"/>
        </context>
    </generatorConfiguration>
    
5.构建
  • 点击即可在这里插入图片描述
  • 会报异常(未解决)
    org.apache.ibatis.exceptions.PersistenceException: 
    ### Error querying database.  Cause: java.lang.NullPointerException
    ### The error may exist in com/wd/mapper/UserMapper.xml
    ### The error may involve com.wd.mapper.UserMapper.selectAll
    ### The error occurred while executing a query
    ### Cause: java.lang.NullPointerException
    

2.逆向工程完整版

  • 修改配置文件的版本即可
    <context id="DB2Tables" targetRuntime="Mybatis3">
    

18.Mybatis分页插件

  • 分页是在查询语句后面使用limit关键字
  • limit index,pageSize
  • index:当前页的起始索引
  • pageSize:每页显示的条数
  • pageNum:当前页的页码
  • index = (pageNum-1) * pageSize

1.Mybatis分页插件使用步骤

1.添加依赖
<dependency>
         <groupId>com.github.pagehelper</groupId>
         <artifactId>pagehelper</artifactId>
         <version>5.2.0</version>
</dependency>
2.配置分页插件
  • mybatis-config.xml文件中添加分页插件
    <plugins>
    		<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
    
3.使用分页插件
 @Test
 public void testPageHelper(){
        //1.通过工具类获取SqlSession对象
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //开启分页
        PageHelper.startPage(1,2);
        3.调用方法
        List<User> users = mapper.selectAll();
		//将分页信息封装到PageInfo对象中(查询结果,导航页条数)
		PageInfo<User> userPageInfo = new PageInfo<User>(users, 5);
        //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接,
        MybatisUtil.closeSqlSession(sqlSession);
        System.out.println(users);
    }
	/* 结果
	Page
	{count=true, pageNum=1, pageSize=2, startRow=0, endRow=2, total=4, pages=2, reasonable=false, pageSizeZero=false}
	[
	User(id=7, name=李四, sex=男, age=28, height=185.0, weight=null, courses=null), 
	User(id=8, name=李四, sex=男, age=28, height=185.0, weight=null, courses=null)
	]
	*/
	count:
	pageNum:当前页的页码
	pageSize:每页显示的条数
	startRow:从第几行开始
	endRow:从第几行结束
	total:一共多少条数据
	pages:一共有多少页
	reasonable:
	pageSizeZero:

	/* 结果
	PageInfo
	{pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=5, pages=3, list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=5, pages=3, reasonable=false, pageSizeZero=false}
	[
	User(id=9, name=李四, sex=男, age=28, height=185.0, weight=null, courses=null), 	>		User(id=10, name=三大, sex=男, age=18, height=155.0, weight=null, courses=null)
	], 
	prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
	*/
	pageNum:当前页的页码
	pageSize:每页显示的条数
	size:当前页真实条数(最后一页不一定满足)
	startRow:从第几行开始
	endRow:从第几行结束
	total:一共多少条数据
	pages:一共有多少页
	list:表示上文Page
	prePage:上一页
	nextPage:下一页
	isFirstPage:是否是第一页
	isLastPage:是否是最后一页
	hasPreviousPage:是否有上一页
	hasNextPage:是否有下一页
	navigatePages:导航页的页数
	navigateFirstPage:导航页的第一页的页码
	navigateLastPage:导航页的最后一页的页码
	navigatepageNums:导航分页的页码
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值