Spring+SpringMVC+Mybatis

Spring MVC 简介

DispatcherServlet前端控制器

SpringMVC 提供一个名为 org.springframework.web.servlet.DispatcherServlet 的 Servlet 充当前端控制器

    <servlet>
        <!-- Servlet 的名称 -->
        <servlet-name>springmvc</servlet-name>
        <!-- Servlet 对应的 Java 类 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 当前 Servlet 参数信息 -->
        <init-param>
            <!-- contextConfigLocation 是参数名称,该参数的值包含 SpringMVC 的配置文件路径 -->
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <!-- 在 Web 应用启动时立即加载 Servlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <!--请求对应的 Servlet 的名称 -->
        <servlet-name>springmvc</servlet-name>
        <!-- 监听当前域的所有请求 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

DispatcherServlet 加载时会需要一个 SpringMVC 的配置文件,

默认情况下,应用会去 /WEB-INF/ 下查找对应的 [servlet-name]-servlet.xml 文件

SpringMVC 执行的流程

DispatcherServlet 源代码

    protected void initStrategies(ApplicationContext context) {
        // 初始化上传文件解析器
        this.initMultipartResolver(context);
        // 初始化本地化解析器
        this.initLocaleResolver(context);
        // 初始化主题解析器
        this.initThemeResolver(context);
        // 初始化处理器映射器,将请求映射到处理器
        this.initHandlerMappings(context);
        // 初始化处理器适配器
        this.initHandlerAdapters(context);
    // 初始化啊处理异常解析器,执行过程中遇到异常交给HandlerExceptionResolvers解析
        this.initHandlerExceptionResolvers(context);
        // 初始化请求到视图名称解析器
        this.initRequestToViewNameTranslator(context);
        // 初始化视图解析器,通过 ViewResolvers 解析逻辑视图名到具体视图实现
        this.initViewResolvers(context);
        // 初始化 flash 映射管理器
        this.initFlashMapManager(context);
    }

org.springframework.web.servlet 路径下有一个 DispatcherServlet .properties 配置文件

该文件指定了 DispatcherServlet 所使用的默认组件

一次 请求 → 响应的完整流程

image-20201112152638804

①:用户向服务器发送请求,请求被 Spring 的前端控制器 DispatcherServlet 截获;

②:DispatcherServlet 对请求 URL(统一资源定位符)进行解析,得到URI(请求资源标识符)。然后根据该 URI 调用

HandlerMapping 获得该 Handler 配置的所有相关的对象,包括 Handler 对象以及 Handler 对象对应的拦截器,这些对象会被封装到一个 HandlerExecutionChain 对象当中返回。

③:DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter。HandlerAdapter 的设计符合面向对象中的单一职责原则,HandlerAdapter 会被用于处理多种 Handler,调用 Handler 实际处理请求的方法,

④:提取请求中的数据模型,开始执行 Handler(Controller),在填充 Handler 的入参过程中,根据配置,Spring 将帮你做一些额外的工作。

​ 👉 消息转换 :将请求信息(Json、XML 等数据),转换成一个对象,将对象转换为指定的响应信息。

​ 👉 数据转换 :对请求信息进行数据转换,如 String 转换成 Integer、Double等。

​ 👉 数据格式化 :对请求信息进行数据格式化,如将字符串转换成格式化数据或格式化日期等。

​ 👉 shuju验证(校验) :验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Errors 中。

⑤ :Handler 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象,ModelAndView 对象中应该包含视图名或视图名和模型。

⑥ :根据返回的 ModelAndView 对象,选择一个合适的 ViewResolver(视图解析器)返回给 DispatcherServlet 。

⑦ :ViewResolver 结合 Model 和 View 来渲染视图

⑧ :将视图渲染结果返回给客户端

基于 实现 Controller 接口

第一个 Spring MVC 应用

1、导包,可以把 spring-framework lib 包里面 所有 .jar 文件拷贝到 项目lib文件中

image-20201110105207719

2、配置前端控制器 DispatcherServlet 【web.xml】

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3、配置 Spring MVC 的 Controller

<?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">
    
    <!-- 配置 Handle,映射 “/hello” 请求-->
    <bean name="/hello" class="com.yuanwu.controller.HelloWorldController"/>
    <!-- 处理映射器将 bean 的 name 作为url进行查找,需要在配置 Handle 时指定name(即url)-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <!-- SimpleControllerHandlerAdapter 是一个处理器适配器,所有处理器适配器都要实现HandleAdapter接口 -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
</beans>

4、Controller 的实现

实现 Controller 接口

package com.yuanwu.controller;


import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author YuanWu
 * @ClassName HelloWorldController
 * @Date 2020/11/9
 **/
// @Controller
public class HelloWorldController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mv = new ModelAndView();
        //封装要显示到视图中的数据
        mv.addObject("message","Hello World");
        //视图名
        mv.setViewName("index.jsp");
        return mv;
    }
}

5、 View 页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  ${requestScope.message}
  </body>
</html>

6、测试

http://localhost:8080/hello

基于 注解的 Controller

1、Controller

@Controller
public class HelloWorldController{

    @RequestMapping(value = "/hello")
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mv = new ModelAndView();
        mv.addObject("message","Hello World");
        mv.setViewName("index.jsp");
        return mv;
    }
}

2、web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3、spring-config.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: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">

    <context:component-scan base-package="com.yuanwu.controller"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>

</beans>

4、测试

http://localhost:8080/hello

结果跳转的方式

1、设置 ModelAndView 对象,根据 View 的名称,和视图解析器 跳转到指定的页面。

​ 页面:视图解析器的前缀 + ViewName + 视图解析器的后缀

//视图名
mv.setViewName("hello");	//前缀+hello+后缀 

2、通过 ServletAPI 对象 来实现。不需要视图解析器

response.getWriter().println("。。。。。");

3、转发和重定向

4、@ResponseBody 响应 json 数据

5、无返回值

6、String 返回值

SpringMVC常用注解

常用注解

注解 使用
@Controller 用于标记一个类,标记的类就是一个SpringMVC Controller对象,即一个控制类,Spring会扫描这个类
@RequestMapping 告诉Spring用哪一个类或方法来处理请求动作,可用于类或方法
@RequestParam 用于将指定的请求参数赋值给方法中的形参 接收 key-value ,GET方式,用 @RequestParam 接收值 ,在方法中使用
@PathVariable 用于获取请求URL中的动态参数
@RequestHeader 用于将请求的头信息区数据映射到功能处理方法的参数上
@CookieValue 用于将请求的 Cookie 数据映射到功能处理方法的参数上
@SessionAttributes 允许我们有选择得指定 Model 中的哪些属性需要转存到 HttpSession 对象中(只能声明在类中)
@ModelAttribute 将请求参数绑定到 Model 对象
@ResponseBody 将 Java 对象转为 JSON 格式的数据。用来返回 JSON 数据或者 XML 数据。作用在方法上,一般在异步获取数据使用,效果等同于通过response输出数据,不会再走视图解析器,直接将数据写入输入流中。
@RequestBody 主要用来接收前端传递给后端的 json 字符串中的数据(请求体中的数据);GET无请求体,前端用POST进行提交,
@Valid 用于对提交的数据进行校验

信息转换

HttpMessageConverter<T>接口

接口 作用
HttpMessageConverter<T> 负责将请求信息转换为一个对象(类型为T),并将对象(类型为T)绑定到请求方法的参数中或输出为响应信息。

转换 JSON 数据

SpringMVC 提供了处理 JSON 格式请求/响应的 HttpMessageConverter

SpringMVC 默认使用 jackson 处理 json数据 MappingJackson2HttpMessageConverter

使用 Controller 返回 JSON 数据 【原生】

1、导包,springmvc所需包

2、web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

<!--    字符乱码过滤器-->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>

</web-app>

3、spring-config.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: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">

    <context:component-scan base-package="com.yuanwu.controller"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>

</beans>

4、controller

package com.yuanwu.controller;

import com.yuanwu.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author YuanWu
 * @ClassName UserController
 * @Date 2020/11/10
 **/
@Controller
public class UserController {

    @RequestMapping("/json")
    // 使用 @ResponseBody 注解,返回的数据不会走视图解析器,而是直接返回一个字符串给前端
    @ResponseBody
    public String json() {
        User user = new User(1,"李四",18);
        return user.toString();
    }
}

4、测试

http://localhost:8080/json

返回 User{Id=1, name='??', age=18}

使用 jackson 开源类包 返回JSON 格式

导包 jackson-annotations-2.10.0.jar jackson-core-2.10.0.jar jackson-databind-2.10.0.jar

1、controller

@Controller
public class UserController {

    //使用 Controller 返回JSON 格式
    @RequestMapping("/json")
    // 使用 @ResponseBody 注解,返回的数据不会走视图解析器,而是直接返回一个字符串给前端
    @ResponseBody
    public String json() {
        User user = new User(1,"李四",18);
        return user.toString();
    }

    //使用 jackson 返回JSON 格式
    @RequestMapping(value = "/jackJson",produces = "text/plain;charset=UTF-8")
    @ResponseBody
    public String jackJson() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        User user = new User(1,"李四",18);
        String str = mapper.writeValueAsString(user);
        return str;
    }

    //通过 JSON格式返回 集合数据
    @RequestMapping(value = "/arrayJson")
    @ResponseBody
    public String arrayJson() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        User user = new User(2,"李四1",18);
        User user1 = new User(1,"李四2",18);
        User user2 = new User(3,"李四3",18);
        List<User> userList = new ArrayList<>();
        userList.add(user);
        userList.add(user1);
        userList.add(user2);
        String string = mapper.writeValueAsString(userList);
        return string;
    }
}

2、spring-config.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">

    <context:component-scan base-package="com.yuanwu.controller"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>


</beans>

3、web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

<!--    字符乱码过滤器-->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>

</web-app>

4、测试

通过Controller返回

http://localhost:8080/json 页面返回:User

通过 jackson 返回 JSON 数据

http://localhost:8080/jackJson

通过jackson 返回 集合 的 JSON 数据(会有乱码,在@RequestMapping 注解中 加入 produces = "text/plain;charset=UTF-8" ) 后面会涉及到 如何一劳永逸的 解决乱码问题

http://localhost:8080/arrayJson

[{"name":"??1","age":18,"id":2},{"name":"??2","age":18,"id":1},{"name":"??3","age":18,"id":3}]

jackson使用小结:

1、在对应 controller 类中 的方法上添加 注解 @ResponseBody

2、在对应的 controller 类中的方法中 new 一个 ObjectMapping 对象 mapper

3、调用 mapper.writeValueAsString(对象的引用),将对象转为一个字符串

4、将字符串 返回

响应 JSON 数据,发送 ajax请求

1、controller

@Controller
public class UserController {

    /*
    * 模拟异步请求和响应
    * */
    @RequestMapping(value = "/hello")
    public void testAjax(@RequestBody String body) {
        System.out.println("执行testAjax()方法");
        System.out.println(body);
    }

}

2、index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script>
      //页面加载,绑定单击事件
      $(function () {
          $("#btn").click(function () {
              //发送 ajax 请求
            $.ajax({
              url:"/hello",
              contentType:"application/json;charset=UTF-8",
              data:'{"name":"张三","password":"123","age":36}',
              dataType:"json",
              type:"post",
              success:function (data) {
                  //data 服务器端响应的 json 的数据,进行解析
                alert("成功");
              }
            });
          });
      });
    </script>
  </head>
  <body>
    <button id="btn">发送 ajax 请求 </button>
  </body>
</html>

3、springmvc-servlet.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">

    <context:component-scan base-package="com.yuanwu.controller"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>

    <!-- 前端控制器,哪些静态资源不拦截
        mapping:映射请求
        location:类路径文件
    -->
    <mvc:resources mapping="/js/**" location="/js/"/>

    <mvc:annotation-driven/>

</beans>

jackson 把 字符串转成 对象,把对象转成json的字符串

响应 json 数据,响应json格式数据

发送 ajax 请求,传递 json 数据,UserController 收到请求后,@RequestBody 会将 json 数据设置到 Javabean 参数对应的属性当中;

用@ResponseBody 注解,后端把 json 数据封装到 Javabean对象中,响应给客户端;

客户端发送ajax请求,传的是json字符串,后端把json字符串封装到 user 对象中,

1、springmvc-servlet.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">

    <context:component-scan base-package="com.yuanwu.controller"/>
    <mvc:default-servlet-handler/>
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

    <mvc:annotation-driven/>

</beans>

2、controller

@RequestMapping(value = "/hello")
    @ResponseBody
    public User testAjax(@RequestBody User user) {
        System.out.println("执行testAjax()方法");
        // 客户端发送ajax请求,传的是json字符串,后端把json字符串封装到 user 对象中,
        System.out.println(user);
        //响应,模拟查询数据库
        user.setName("元五");
        user.setAge(18);
        return user;
    }

测试:

将 set进去的数据,响应出去,并且在控制台打印成功

image-20201110171508587 image-20201110171529437


自定义 HttpMessageConverter 接收 JSON格式的数据

使用 fastjson

1、导包,

fastson-xxx.jar

需要注意的是,使用fastson时,需要移除 Jackson的包,否则还是会默认加载 jackson

2、修改 json 方式为 fastjson

将转换器 配置成 com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter

3、springmvc-servlet.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/shema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/aop
    	http://www.springframework.org/schema/aop/spring-aop.xsd
        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">
    <!-- Spring 可以自动区扫描 base-pack 下面的包或者子包下面的 java 文件-->

    <context:component-scan base-package="com.yuanwu.controller"/>

    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>


    <!-- 设置配置方案 ,启用注解支持-->
    <mvc:annotation-driven>
        <!-- 设置不使用默认的消息转换器 -->
        <mvc:message-converters register-defaults="false">
            <!-- 配置 Spring 的转换器 -->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>

            <!-- 配置 fastjson 中 实现 HttpMessageConverter 接口的转换器 -->
                <!-- FastJsonHttpMessageConverter 是 fastjson 中实现了 HttpMessageConverter接口的类 -->
            <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <!-- 加入支持的媒体类型,返回 contentType  -->
                <property name="supportedMediaTypes">
                    <list>
                        <!-- 顺序不能反,一定要先写 text/html,不然 IE 会出现下载提示 -->
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

4、controller

@Controller
public class UserController {

    @RequestMapping(value = "/getJson")
    @ResponseBody
    public Object getJson() {
        List<User> userList = new ArrayList<>();
        userList.add(new User("张三","123",18));
        userList.add(new User("李四","123",28));
        userList.add(new User("王五","123",38));
        userList.add(new User("赵六","123",48));
        return userList;
    }
}

5、web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--字符乱码过滤器-->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

6、index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>$Title$</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script>
    $(function () {
      $("#btn").click(function () {
        $.ajax({
          url: "/getJson",
          contentType: "application/json;charset=UTF-8",
          dataType: 'json',
          type: "post",
          success: function (data) {
            console.log("成功");
            console.log(data);
            console.log(data[0].age);
            console.log(data[1].name);
          }
        });
      });
    });
  </script>
</head>
<body>
<button id="btn">发送 ajax 请求</button>
</body>
</html>

7、测试

启动服务器,点击页面 发送ajax请求 chrome控制台输出

image-20201110163146339 ==>>> image-20201110163404854

使用Ajax 异步渲染html

导包: fastjson-1.2.73.jar、以及spring依赖包

如果不是大范围的刷新页面的话,建议使用 Ajax 异步请求,

步骤1、bean

public class User {

    private String name;
    private  int age;
    private String sex;
    .....省略 set get
}

步骤2、controller AjaxController.java

@RestController
public class AjaxController {

    @RequestMapping(value = "/emps")
    public List<User> ajax() {
        List<User> userList = new ArrayList<User>();
        userList.add(new User("java",1,"男"));
        userList.add(new User("java",1,"男"));
        userList.add(new User("java",1,"男"));
        userList.add(new User("java",1,"男"));

        return userList;
    }
}

步骤3、jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

    <script>
      $(function () {
        $("#btn").click(function () {
          console.log("111");
          $.ajax({
            url: "/emps",
            contentType: "application/json;charset=UTF-8",
            dataType: 'json',
            type: "post",
            success: function (data) {
              console.log("成功");
              var html = "";
              for (let i = 0; i < data.length; i++) {
                html += "<tr>" +
                        "<td>"+ data[i].name +"</td>" +
                        "<td>"+ data[i].age +"</td>" +
                        "<td>"+ data[i].sex +"</td>" +
                        "</tr>"
              }
              $("#content").html(html);
            }
          })
        });
      });
    </script>
  </head>
  <button id="btn" type="button">查询</button>
  <body>
    <table>
      <tr>
        <td>性名</td>
        <td>年龄</td>
        <td>性别</td>
      </tr>
      <tbody id="content">
      <%-- 数据:后台 --%>
      </tbody>
    </table>
  </body>
</html>

步骤4、xml配置文件 spring-config.xml

    <!-- Spring 可以自动区扫描 base-pack 下面的包或者子包下面的 java 文件-->
    <context:component-scan base-package="com.yuanwu.controller"/>

    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>


    <!-- 设置配置方案 ,启用注解支持-->
    <mvc:annotation-driven>
        <!-- 设置不使用默认的消息转换器 -->
        <mvc:message-converters register-defaults="false">
            <!-- 配置 Spring 的转换器 -->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>

            <!-- 配置 fastjson 中 实现 HttpMessageConverter 接口的转换器 -->
            <!-- FastJsonHttpMessageConverter 是 fastjson 中实现了 HttpMessageConverter接口的类 -->
            <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <!-- 加入支持的媒体类型,返回 contentType  -->
                <property name="supportedMediaTypes">
                    <list>
                        <!-- 顺序不能反,一定要先写 text/html,不然 IE 会出现下载提示 -->
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

步骤5、测试 http://localhost:8080/ 点击查询

image-20201112172843026 image-20201112172905491

补充

请求处理可出现、可返回的参数类型

参数类型 作用
Model SpringMVC在调用处理方法之前会创建一个隐含的模型对象,作为模型数据的存储容器,如果处理方法的参数为Model或ModelMap类型,则SpringMVC会将隐含模型的引用传递给这些参数,可以通过参数对象访问模型中的所有数据。
ModelMap 跟Model 用法基本一致,
ModelAndView 既包含数据模型信息,也包含视图信息,SpringMVC将使用包含的视图对数据模型进行渲染。
ModelAndView 方法名称 作用
addObject (String s,Object s) 添加数据模型
setViewName(String s) 设置视图
方法名称 用法
setAttribute(key,value) 将数据保存在 request/session 作用域中
getAttribute(key) 将保存在 request/session 作用域中的数据取出
request.getSession() 获取 HttpSession 类型的对象,session对象
request.getServletPath() 获取请求路径
java.lang.String.contains() 当前字符串是否包含指定的子字符串

重定向和转发

区别 转发 重定向
request.getRequestDispatcher("").forward() response.sendRedirect("")
地址栏 不会显示出转向的地址 会显示转向之后的地址
请求 至少提交了两次请求
数据 对 request 对象的信息不会丢失,可以在多个页面交互实现请求数据的共享 request 信息将丢失
原理 是在服务器内部控制器的转移,由服务器区请求 服务器告诉客户端要转向哪个地址,客户端再自己去请求转向的地址
选择 增删改需要跳转到其他页面,所以用重定向 而查询新,一般只在当前页面,常用转发方式

SpringMVC 标签库

标签 描述
form 渲染表单元素
input 渲染元素
password 渲染元素
hidden 渲染元素
textarea 渲染 textarea 元素
checkbox 渲染一个元素
checkboxs 渲染多个元素
radiobutton 渲染一个元素
radiobuttons 渲染多个元素
select 渲染一个选择元素
option 渲染一个可选元素
options 渲染一个可选元素列表
errors 在 span 元素中渲染字段错误

语法:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<form:form method="post" action="/login">
	<form:input path="name"/>
    	.................
</form:form>

使用 SpringMVC 的 form 标签主要有两个作用,第一个是它会自动的绑定来自 Model 中的属性值到当前 from 对应的实体对象,第二个是在提交表单的时候不仅仅能使用 GET POST提交,包括 DELETE PUT 等

默认从 request 域对象中读取 command 的表单bean,如果不存在,且没有 modelAttribute 属性,则会报错

1、使用 modelAttribute 属性指定绑定的模型属性

<form:form method="post" action="/login" modelAttribute="user">

2、手动将user对象添加到 request 请求域中

public String login() {
    User u = new User();
    model.addAttribute("u",u);
    return "index";
}

3、使用 @ModelAttribute

@ModelAttribute
public void login(@RequestParam("id") String id, Model model) {
    User u = new User();
    model.addAttribute("u",u);
}

更详细的 SpringMVC 标签库的使用 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

https://www.cnblogs.com/yanxiaoge/p/10880769.html

乱码 及 Restful

web.xml

  <filter>
  	<filter-name>CharacterEncodingFilter</filter-name>
  	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  	<init-param>
  		<param-name>encoding</param-name>
  		<param-value>UTF-8</param-value>
  	</init-param>
  </filter>
  <filter-mapping>
  	<filter-name>CharacterEncodingFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>

restful

原有的url:

http://localhost:8080/user/queryUserById?id=3

参数具体锁定到操作的是谁。id=3 的用户

rest 风格:

http://localhost:8080/user/queryUserById/3

@RequestMappin(value="/delete/{id}") 删除传进来的id 的用户记录

代表的是 id 为 3 的用户

GET 查询 获取资源

POST 增加 新建资源

PUT 更新

DELETE 删除资源

Spring MVC ⽀持 RESTful ⻛格请求,具体讲的就是使⽤ @PathVariable 注解获取 RESTful ⻛格的请求

Spring MVC 的数据转换、格式化和数据校验

数据转换

SpringMVC 中接收到的数据都是 String 形式,然后根据反射机制将 String 转换成对应的类型

比如 SpringMVC 接收到的是 String 类型,我们想要得到 Date类型,就可以定义一个转换器完成

示例1: 使用ConversionService 转换数据

导包,jstl标签jar包 standard-1.1.2.jar, , jstl-1.2.jar ,可以自行去spring官网下载 spirng相关源码 源码,包含jar包

省略了 bean ,User.java

步骤1、web.xml

<!--字符乱码过滤器-->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

步骤2、spring-config.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
       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">

    <context:component-scan base-package="com.yuanwu.controller"/>


    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>

    <!-- 设置配置方案 ,启用注解支持-->
    <mvc:annotation-driven conversion-service="conversionService"/>

    <!-- 自定义类型转换器 
		覆盖默认实现类
	-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.yuanwu.controller.UserController"
                      <!-- 给属性 datePattern 复制为 日期格式类 -->
                      p:datePattern="yyyy-MM-dd"></bean>
            </list>
        </property>
    </bean>

    <!--视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

步骤3、controller,UserController.java 实现 Converter 接口

开发自定义的转换器,将传递的字符串转换成 Date 类型。

import org.springframework.core.convert.converter.Converter;

import java.text.SimpleDateFormat;
import java.util.Date;
// 实现 Converter<S,T>接口
public class UserController implements Converter<String, Date> {

    // 日期类型模板:如yyyy-MM-dd,通过配置时注入
    private String datePattern;

    public void setDatePattern(String datePattern) {
        this.datePattern = datePattern;
    }

    // Converter<S,T>接口的类型转换方法
    @Override
    public Date convert(String date) {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat(this.datePattern);
            // 将日期字符串转换成Date类型返回
            return dateFormat.parse(date);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("日期转换失败!");
            return null;
        }

    }

步骤4、UserDatePattern.java

@Controller
public class UserDatePattern {

    @RequestMapping(value = "/{formName}")
    public String loginForm(@PathVariable String formName) {
        return formName;
    }

    @RequestMapping(value = "/register",method = RequestMethod.POST)
    public String register(@ModelAttribute User user, Model model) {
        model.addAttribute("user", user);
        return "success";
    }

}

步骤5、jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <h3>注册页面</h3>
    <form action="/register" method="post">
      <table>
        <tr>
          <td>登录名</td>
          <td><input type="text" id="name" name="name"></td>
        </tr>
        <tr>
          <td>生日</td>
          <td><input type="text" id="birthday" name="birthday"></td>
        </tr>
        <tr>
          <td><input type="submit" value="登录"></td>
        </tr>
      </table>
    </form>
  </body>
</html>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
登录名:${requestScope.user.name} <br/>
生日: <fmt:formatDate value="${requestScope.user.birthday}"
        pattern="yyyy年MM月dd日"/>
</body>
</html>

步骤6,测试 ,启动 tomcat 服务器

http://localhost:8080/ http://localhost:8080/register

image-20201111105636072 image-20201111105700109

示例2、@InitBinder 添加自定义编辑器转换数据

示例3、 WebBindingInitializer 注册全局自定义编辑器转换数据

多种转换器的优先顺序

既使用 ConversionService 装配自定义的转换器,又使用 通过 WebBindingInitializer 接口装配定义全局自定义编辑器,同时使用注解 @InitBinder 装配自定义编辑器,SpringMVC会根据优先顺序查找对应的编辑器:

  • 1)、查询通过 @InitBinder 装配的自定义编辑器;
  • 2)、查询通过 ConversionService 装配的自定义编辑器;
  • 3)、查询通过 WebBindingInitializer 接口装配的全局自定义编辑器;

数据格式化 Formatter<T>

Formatter 只能将 String 转换成另一种 Java 类型,例如将 String 字符串 转为 Date 类型。但是不能将 Long 转换成 Date 。如果项转换表单中的用于输入,建议使用 Formatter

使用 Formatter 格式化数据

导包,jstl标签jar包 standard-1.1.2.jar, , jstl-1.2.jar ,可以自行去spring官网下载 spirng相关源码 源码,包含jar包

web.xml 配置文件 基本是固定的,前面写过的,这里不再阐述

步骤1、index.jsp、success.jsp 为了方便,我将两张jsp代码,放在一起,用分割线隔开

<%--  -------------------------index.jsp--------------------------%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<h3>注册页面</h3>
<form action="/test" method="post">
    <table>
        <tr>
            <td>登录名</td>
            <td><input type="text" id="name" name="name"></td>
        </tr>
        <tr>
            <td>生日</td>
            <td><input type="text" id="birthday" name="birthday"></td>
        </tr>
        <tr>
            <td><input type="submit" value="登录"></td>
        </tr>
    </table>
</form>
</body>
</html>

<%--  -------------------------success.jsp--------------------------%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
登录名:${requestScope.user.name} <br/>
生日: <fmt:formatDate value="${requestScope.user.birthday}"
                    pattern="yyyy年MM月dd日"/>
</body>
</html>

步骤2、controller,实现 Formatter <T> 接口 ,实现业务逻辑

为了方便,我也是将两个java代码,也在一起

//--------------------------日期格式化类 Formatter <T> -------------------
public class UserFormatter implements Formatter<Date> {

    // 日期类型模板   比如 yyyy-MM-dd
    private String datePattern;

    // 日期格式化对象
    private SimpleDateFormat simpleDateFormat;

    // 构造器,通过依赖注入的日期类型 创建日期格式化对象
    public UserFormatter(String datePattern) {
        this.datePattern = datePattern;
        this.simpleDateFormat = new SimpleDateFormat(datePattern);
    }

    // 解析文本字符串,返回一个 Formatter<T> 的 T 类型对象
    @Override
    public Date parse(String s, Locale locale) throws ParseException {
        try {
            return simpleDateFormat.parse(s);
        } catch (Exception e) {
            throw new IllegalArgumentException();
        }
    }

    // 显示 Formatter<T> 的 T 类型对象
    @Override
    public String print(Date date, Locale locale) {
            return simpleDateFormat.format(date);
    }
}

//----------------------------业务逻辑--------------------------------------
@Controller
public class UserController {

    // //动态跳转页面
    // @RequestMapping(value = "/{formName}")
    // public String loginName(@PathVariable String formName) {
    //     return formName;
    // }

    @RequestMapping(value = "/test",method = RequestMethod.POST)
    public String test(@ModelAttribute User user, Model model) {
        model.addAttribute("user", user);
        System.out.println(user.toString());
        return "register";
    }
}

步骤3、spring-config.xml 配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:c="http://www.springframework.org/schema/c"
       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">

    <context:component-scan base-package="com.yuanwu.controller"/>

    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>

    <!-- 设置配置方案 ,启用注解支持-->
    <!-- 装配自定义格式化转换器 -->
    <mvc:annotation-driven conversion-service="conversionService"/>
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="formatters">
            <list>
                <bean class="com.yuanwu.controller.UserFormatter" c:_0="yyyy-MM-dd"/>
            </list>
        </property>
    </bean>

    <!--视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

步骤3、测试 http://localhost:8080 http://localhost:8080/test

image-20201111142649294 image-20201111142700137


示例:使用 FormatterRegister 注册 Formatter


使用注解定义格式化数据

注解 用法
@DateTimeFormat 可对 java.util.Date,java.util.Calendar 等时间属性进行格式化
@NumberFormat 对 数字类型 进行格式化

步骤1、index.jsp success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
      <h3>测试表单数据格式化</h3>
      <form action="/test" method="post">
          <table>
              <tr>
                  <td>日期类型:</td>
                  <td><input type="text" id="birthday" name="birthday"></td>
              </tr>
              <tr>
                  <td><input type="submit" value="提交"></td>
              </tr>
          </table>
      </form>
  </body>
</html>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h3>测试表单数据格式化</h3>
    <form:form modelAttribute="user" method="post" action="">
        <table>
            <tr>
                <td>日期类型:</td>
                <td><form:input path="birthday"/></td>
            </tr>
        </table>
    </form:form>
</body>
</html>

步骤2、web.xml spring-config.xml

<!--字符乱码过滤器-->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 拦截所有请求 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">

    <context:component-scan base-package="com.yuanwu.controller"/>


    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>
    <mvc:default-servlet-handler/>

    <!-- 设置配置方案 ,启用注解支持-->
    <mvc:annotation-driven/>


    <!--视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

步骤3、controller

@Controller
public class UserController {

    // @RequestMapping(value = "/{formName}")
    // public String loginName(String formName) {
    //     return "formName";
    // }

    @RequestMapping(value = "/test",method = RequestMethod.POST)
    public String test(@ModelAttribute User user, Model model) {
        model.addAttribute("user", user);
        return "success";
    }

}

步骤4、测试 http://localhost:8080/ http://localhost:8080/test

image-20201111115536167 image-20201111115554901


数据校验

Spring MVC 提供了两种数据校验功能,一种是 Spring 自带的 Validation 校验框架。一种是 JSR 303 (Java验证规范)实现校验功能

Validation 校验框架

示例:JSR303校验

导包 jar包可以去 https://mvnrepository.com/ 这个网站下载,

classmate-1.5.1.jar hibernate-validator-5.2.4.Final.jar log4j-api-2.6.2.jar
jboss-logging-3.2.1.Final.jar log4j-1.2.17.jar hibernate-validator-annotation-processor-5.2.4.Final.jar
slf4j-log4j12-1.6.1.jar validation-api-1.1.0.Final.jar hibernate-validator-cdi-5.2.4.Final.jar
slf4j-api-1.6.1.jar spring 依赖包

步骤1、xml配置文件 ,spring-config.xml

    <context:component-scan base-package="com.yuanwu.controller"/>

    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>

    <!-- 设置配置方案 ,启用注解支持-->
    <!-- 装配自定义格式化转换器 -->
    <mvc:annotation-driven/>


    <!--视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

步骤2、controller,CserController.xml

@Controller
public class UserController {

    @RequestMapping("/formName")
    public String index(Model model) {
        User user = new User();
        model.addAttribute("user", user);
        return "index";
    }

    @RequestMapping(value = "/login" , method = RequestMethod.POST)
    public String login(@Valid @ModelAttribute User user,  Errors errors, Model model) {
        System.out.println(user);
        System.out.println(errors);
        if (errors.hasErrors()) {
            return "index";
        }
        model.addAttribute("user", user);
        return "success";
    }
}

步骤3、jsp文件 ,index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form:form modelAttribute="user" method="post" action="login">
    <table>
        <tr>
            <td>用户名:</td>
            <td><form:input path="name"/></td>
            <td><form:errors path="name" cssClass="color-pink"/></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><form:input path="pwd"/></td>
            <td><form:errors path="pwd" cssClass="color-pink"/></td>
        </tr>
        <tr>
            <td><input type="submit" value="注册"></td>
        </tr>
    </table>
</form:form>
</body>
</html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
用户名:${requestScope.user.name} <br/>
密码:${requestScope.user.pwd}
</body>
</html>

步骤4、测试 http://localhost:8080/formName http://localhost:8080/login http://localhost:8080/login

image-20201112135325019 image-20201112135456527 image-20201112135520072


SpringMVC 实现文件上传

MultipartFilechan常用方法

方法名称 作用
byte[] getBytes() 获取文件数据
String getContentType() 获取文件MIME类型,比如 image/jpeg 等
InputStream getInputStream() 获取文件流
String getName() 获取表单中文件组件的名字
String getOriginalFilename() 获取上传文件的原名
long getSize() 获取文件的字节大小,单位 为 byte
boolean isEmpty() 是否有上传的文件
void transferTo(File dest) 将上传的文件保存在一个目标文件中
File.separator 间隔符

示例:SpringMVC 的文件上传

1、导入 jar 包, commons-fileupload-1.3.1.jar,commons-io-2.4.jar

2、在 spring-config.xml 中配置MultipartFile

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">

    <context:component-scan base-package="com.yuanwu.controller"/>


    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>

    <!-- 设置配置方案 ,启用注解支持-->
    <mvc:annotation-driven/>


    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 请求的编码格式,必须和 jsp 的 pageEncoding 属性一致, -->
        <property name="defaultEncoding" value="UTF-8"></property>
        <!--上传文件大小上限,单位为字节(10M)-->
        <property name="maxUploadSize" value="10240000"></property>
    </bean>

    <!--
    视图解析器 -->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>


</beans>

3、controller

@Controller
public class FileController {

    /*
    * 文件上传
    * */
    @RequestMapping(value = "/fileUpload",method = RequestMethod.POST)
    public String fileUpload01(HttpServletRequest request,@RequestParam("file") MultipartFile file) throws Exception {

        //如果文件不为空,写入上传路径
        if (!file.isEmpty()) {
            //上传文件路径
            String path = request.getServletContext().getRealPath("/images/");
            System.out.println(path);
            //上传文件名
            String filename = file.getOriginalFilename();
            File filepath = new File(path,filename);
                //判断路径是否存在,如果不存在就创建一个
            if (!filepath.getParentFile().exists()) {
                filepath.getParentFile().mkdirs();
            }
            //将上传文件保存到一个目标文件中
            file.transferTo(new File(path + File.separator + filename));
            return "success";
        } else {
            return "index.jsp";
        }
    }
}

4、jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Title</title>
  </head>
  <body>
      <h1>文件上传</h1>
      <form action="${pageContext.request.contextPath}/fileUpload" method="post" enctype="multipart/form-data">
            选择文件:<input type="file" id="file" name="file"> <br/>
            <input type="submit" value="上传">
      </form>
  </body>
</html>


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>成功上传</h1>
</body>
</html>

5、测试

http://localhost:8080/

image-20201110202133748 image-20201110202144091 image-20201110202201451

批量上传文件 如果上传单个,可以把for循环去除,并且把 [] 数组去除,即可

public  String filewpload(CommonsMultipartFile file[],HttpServletRequest request) throws Exception {
    //获取上传文件的路径
    String path = request.getRealPath("/upload");
    for (int i =0; i < file.length; i++) {
    InputStream is = file[i].getInputStream();
    OutputStream os = new FileOutputStream(new File(path, file[i].getOriginalFilename()));
    int len = 0;
    byte[] buffer = new byte[1024];
    while((len = is.read(buffer)) != -1){
        os.write(buffer,0,len);
        os.close();
        is.close();
    }
  }
    return "/index";
}

使用对象接收上传文件

上传的文件会作为对象的属性被保存

步骤1、jsp index.jsp userInfo.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>Title</title>
</head>
<body>
<h1>文件上传</h1>
<form action="/register" method="post" enctype="multipart/form-data">
  用户名:<input type="text" name="name"> <br/>
  选择文件:<input type="file" id="file" name="image"> <br/>
  <input type="submit" value="上传">
</form>
</body>
</html>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>文件下载</h2>
<a href="download?filename=${requestScope.user.image.originalFilename}">
    ${requestScope.user.image.originalFilename}
</a>
</body>
</html>

步骤2、controller,Javabean

@Controller
public class UserController {

    @RequestMapping(value = "/register")
    public String register(HttpServletRequest request, @ModelAttribute User user, Model model) throws IOException {
        System.out.println(user.getImage());

        //如果文件不为空,写入上传路径
        if (!user.getImage().isEmpty()) {
            String path = request.getServletContext().getRealPath("/images");
            //获取上传文件名
            String filename = user.getImage().getOriginalFilename();
            File filepath = new File(path,filename);
            //判断路径是否存在,如果不存在就创建一个
            if (!filepath.getParentFile().exists()) {
                filepath.getParentFile().mkdirs();
            }
            //将上传的文件保存到一个目标文件中
            user.getImage().transferTo(new File(path + File.separator + filename));
            //将用户添加到model
            model.addAttribute("user", user);
            //跳转到下载页面
            return  "userInfo";
        }
        return "error";
    }
}


public class User implements Serializable {

    private String name;

    private MultipartFile image;

    public String getName() {
        return name;
    }

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

    public MultipartFile getImage() {
        return image;
    }

    public void setImage(MultipartFile image) {
        this.image = image;
    }
}

步骤3、xml配置 spring-config.xml

    <context:component-scan base-package="com.yuanwu.controller"/>


    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>

    <!-- 设置配置方案 ,启用注解支持-->
    <mvc:annotation-driven/>


    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property>
        <property name="maxUploadSize" value="10240000"></property>
    </bean>

    <!--
    视图解析器 -->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

步骤4、测试 http://localhost:8080/ http://localhost:8080/register

image-20201111194022922 image-20201111194048004

文件下载

只需在上传文件的代码 添加如下代码即可 UserController.java

@RequestMapping(value = "/download")
    public ResponseEntity<byte[]> download(HttpServletRequest request, 
                           @RequestParam("filename") String filename, Model model) throws Exception {
        //获取下载路径
        String path = request.getServletContext().getRealPath("/images");
        File file = new File(path + File.separator + filename);
        HttpHeaders headers = new HttpHeaders();
        // 获取下载显示的中文名,解决中文名乱码问题
        String downloadFileName = new String(filename.getBytes("UTF-8"), "iso-8859-1");
        //通知浏览器以 attachment (下载方式)打开图片
        headers.setContentDispositionFormData("attachment", downloadFileName);
        // application/octet-stream ,二进制流数据(最常见的文件下载)
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // 201 HttpStatus.CREATED
        return new ResponseEntity<byte []>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
    }

文件下载测试就不展示,我测试过没问题

拦截器

主要作用是拦截用户请求,并对其进行相应的处理,可以通过拦截器进行用户权限验证,判断用户是否登录。通过实现 HandlerInterceptor 接口完成

示例:拦截器实现用户权限验证

导包 jstl标签包,spring依赖包

jstl-1.2.jar、standard-1.1.2.jar

taglibs-standard-compat-1.2.5.jar taglibs-standard-impl-1.2.5.jar
taglibs-standard-jstlel-1.2.5.jar taglibs-standard-spec-1.2.5.jar

如果用户直接跳过登录页面访问主页面,将被拦截下来

步骤1、xml配置文件 spring-config.xml

    <context:component-scan base-package="com.yuanwu.controller"/>

    <!-- 使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>

    <!-- 设置配置方案 ,启用注解支持-->
    <mvc:annotation-driven/>

    <!-- SpringMVC拦截器定义 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- 拦截所有请求 -->
            <mvc:mapping path="/*"/>
            <!-- 定义一个Interceptor拦截器 -->
            <bean class="com.yuanwu.interceptor.AuthorizationInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

    <!--
    视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

步骤2、controller,

UserController.java

@Controller
public class UserController {

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping(value = "/login")
    public ModelAndView login(
            String name,String password,
            ModelAndView mv,
            HttpSession session) {
        if (name != null && name.equals("admin") &&
            password != null && password.equals("123")) {
            User user = new User();
            user.setName(name);
            user.setPassword(password);
            user.setUserName("超级管理员");
            session.setAttribute("user", user);
            System.out.println(user.toString());
            mv.setViewName("redirect:main");
        } else {
            mv.addObject("message", "请重新输入");
            mv.setViewName("/index");
        }
        return mv;
    }
}

BookController

@Controller
public class BookController {

    @RequestMapping(value = "/main")
    public String main(Model model) {
        List<Book> bookList = new ArrayList<Book>();
        bookList.add(new Book("java1.jpg","Java基础","李刚",75.3));
        bookList.add(new Book("java2.jpg","Java基础","李刚",75.3));
        bookList.add(new Book("java3.jpg","Java基础","李刚",75.3));
        bookList.add(new Book("java4.jpg","Java基础","李刚",75.3));
        model.addAttribute("bookList", bookList);
        return "main";
    }
}

步骤3、拦截器 实现 Interceptor 接口

需要在 spring-config.xml 中配置 拦截器,在第一步中,已经写清楚了

public class AuthorizationInterceptor implements HandlerInterceptor {

    // 不拦截 “/index” 和 “/login” 请求
    private static final String[] IGNORE_URI = {"/index","/login"};

    /*
    * 该方法在整个请求完成之后执行,主要用于清理资源
    * 也之能在当前的 Interceptor 的 preHandle 方法的返回值为 true 时才会执行
    * */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("AuthorizationInterceptor afterCompletion......执行");
    }

    /*
    * 该方法将在 Controller 的方法调用之后执行,方法中可以对 ModelAndView 进行操作,
    * 但也只能在当前 Interceptor 的 preHandle 方法的返回值为 true 时才会执行
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("AuthorizationInterceptor postHandle.....执行");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("AuthorizationInterceptor preHandle.....执行");

        // flag 变量用于判断用户是否登录,默认为false
        boolean flag = false;

        // 获取请求的路径,进行判断
        String servletPath = request.getServletPath();
        System.out.println(servletPath);

        // 判断请求是否需要拦截
        for (String s : IGNORE_URI) {
            if (servletPath.contains(s)) {
                flag = true;
                break;
            }
        }

        // 拦截请求
        if (!flag) {
            // 1、获取 Session 中的用户
            User user = (User) request.getSession().getAttribute("user");
            System.out.println(user);

            // 2、判断用户是否已经登录
            if (user == null) {
                // 如果用户没有登录,则设置提示信息,跳转到登录页面
                System.out.println("AuthorizationInterceptor preHandle.....拦截请求");
                request.setAttribute("message", "请先登录再访问网页");
                request.getRequestDispatcher("/index").forward(request, response);
            } else {
                // 如果用户已经登录,则放行
                System.out.println("AuthorizationInterceptor preHandle.....放行");
                flag = true;
            }
        }
        return flag;
    }
}

步骤4、jsp文件

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<h1>登录页面</h1>
<form action="login" method="post">
    <font color="red" size="50">${requestScope.message}</font>
    <table>
        <tr>
            <td>登录名:</td>
            <td><input type="text" id="name" name="name"></td>
        </tr>
        <td>
            <td>密码:</td>
            <td><input type="password" id="password" name="password"></td>
        </td>
        <tr>
            <td><input type="submit" value="登录"></td>
        </tr>
    </table>
</form>
</body>
</html>

main.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>欢迎[${sessionScope.user.userName}]访问</h1>
<br>
<table border="1">
    <th>封面</th>
    <th>书名</th>
    <th>作者</th>
    <th>单价</th>
    <c:forEach items="${requestScope.bookList}" var="book">
        <tr>
            <td><img src="images/${book.image}" height="60"></td>
            <td>${book.name}</td>
            <td>${book.author}</td>
            <td>${book.price}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

步骤5、测试 http://localhost:8080/main http://localhost:8080/login http://localhost:8080/main

​ 输入账号admin 密码123

image-20201112102145125 image-20201112102217356 image-20201112102253490


MyBatis__全局配置文件

1、引入dtd约束

联网状态下: http://mybatis.org/dtd/mybatis-3-config.dtd

2、properties_引入外部配置

mybatis可以使用propertie来引入外部propertie配置文件的内容

resource == >> 引入类路径下的资源

url == >> 引入网络路径或者磁盘路径下的资源

<properties resource="dbconfig.properties"></properties>
<dataSource>
	<properties name="driver" value="${}"/>
    ..............
</dataSource>

3、settings_运行时行为设置

Mybatis3--配置 https://mybatis.org/mybatis-3/zh/configuration.html#settings

举例:【是否开启驼峰命名规则】

是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。

<!--
	setting:用来设置每一个设置项
	name:设置项名
	value:设置项取值
-->
<settings>
	<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

4、typeAliases_别名

【别名处理器】

类型别名可为 Java 类型设置一个缩写名字

引用【resultType="emp"】

【别名不区分大小写】

<typeAliases>
    <!--1、
		typeAlias:为某个Java类型起别名
		type:指定要起别名的全类名,默认别名是类名小写
		alias:指定别名
	-->
	<typeAlias type="全类名" alias="指定别名"/>
    
    <!--2、
		package:为某个包下的所有类批量取别名
		name:指定包名【起一个默认别名小写,当前包以及后代包】
	-->
  	<package name="包名"/>
    
    <!--3、
		【批量取别名】包扫描情况下,使用注解方式 @Alias 为某个类指定别名
	-->
</typeAliases>

5、typeHandlers_类型

typeHandlers_【类型处理器】

自定义类型处理器

6、plugins_插件简介

  • Executor 【执行器】(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler【参数处理器】 (getParameterObject, setParameters)
  • ResultSetHandler【结果集处理器】 (handleResultSets, handleOutputParameters)
  • StatementHandler【SQL语句处理器】 (prepare, parameterize, batch, update, query)

7、environments_环境配置

数据库数据源

<environments default="development">
    
    <!--
		environments:可以配置成适应多种环境 ,default:指定使用某种环境,达到快速切换环境
		environment:配置一个具体的环境信息,↓↓↓↓↓这两个参数必须存在
		transactionManager【事务管理器】,
			type:事务管理器类型,type="[JDBC|MANAGED]"
				自定义事务管理器:实现TransactionFactory 接口,type指定为全限定类名
		dataSource【数据源】 
			type:数据源类型,type="[UNPOOLED|POOLED|JNDI]"
			自定义数据源:实现 DataSourceFactory 接口,type指定为全类名
		id="development":表示当前环境的唯一标识
	-->
    <environment id="development">
        <transactionManager type=""></transactionManager>
        <dataSource type=""></dataSource>
    </environment>
    
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

MyBatis 可以配置成适应多种环境

每个SqlSessionFactory实例只能选择一种环境

一个environment 对应一个 SqlSessionFactory实例

8、databaseIdProvider_数据库厂商标识

<!--
	databaseIdProvider:支持多数据库厂商
	type="DB_VENDOR":得到数据库厂商标识【驱动getDatabaseProductName()】,mybatis就能根据数据库厂商标识来执行不同的sql
-->
<databaseIdProvider type="DB_VENDOR">
    <!--
		为不同的数据库厂商起别名,在xxxmapper.xml里面,sql语句databaseId="mysql"表示在mysql环境下才执行sql
	-->
  	<property name="MYSQL" value="mysql"/>
    ......
    
</databaseIdProvider>

9、mappers_映射器

MyBatis需要开发者自己写SQL语句,mapper映射器告诉MyBatis到哪里去找映射文件

<!--
	mappers:将sql映射 注册到全局配置中
-->
<mappers>
    <!--
		mapper:注册一个sql映射
			resource:引用类路径下的sql映射文件
			url:引用网络路径或者磁盘路径下的sql映射文件
			class:引用接口
	-->
	<mapper resource=""/>
    <!--批量注册,类名和xml要在同一路径下-->
    <package name="包名"/>
</mappers>

Mybatis__SQL映射文件

1、增删改查

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

EmployeeMapper.java

public void addEmp(Employee employee);

public void updateEmp(Employee employee);

public void deleteEmpById(Integer id);

EmployeeMapper.xml

<!-- public void addEmp(Employee employee); -->
<!--
        parameterType:可以省略
     -->
<insert id="addEmp" parameterType="com.yuanwu.mybatis.bean.Employee">
    insert tbl_employee(last_name,gender,email)
    values(#{lastName},#{gender},#{email})
</insert>

<!-- public void updateEmp(Employee employee); -->
<update id="updateEmp">
    update tbl_employee
    set last_name=#{lastName},gender=#{gender},email=#{email}
    where id=#{id}
</update>

<!-- public void deleteEmpById(Integer id); -->
<delete id="deleteEmpById">
    delete from tbl_employee where id=#{id}
</delete>

MyBatisTest.java

@Test
    public void test03() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //1、获取到的SqlSession不会自动提交数据
        SqlSession openSession = sqlSessionFactory.openSession();

        try {
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            //测试添加
            // Employee employee = new Employee(null, "Jerry", "0", "Jerry@yuanwu.com");
            // mapper.addEmp(employee);

            //测试修改
            // Employee employee = new Employee(1, "Back", "1", "Back@yuanwu.com");
            // mapper.updateEmp(employee);

            //测试删除
            mapper.deleteEmpById(1);

            //1、手动提交数据
            openSession.commit();
        } finally {
            openSession.close();
        }
    }

2、insert_获取自增主键的值

<!--
	获取自增主键的值:
		mybatis支持自增主键,自增主键值的获取,mybatis也是利用	statement.getGeneratedKeys();
    useGeneratedKeys="true";使用自增主键获取主键值策略
    keyProperty:指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给JavaBean的某个属性
-->
<insert id="addEmp" parameterType="com.yuanwu.mybatis.bean.Employee"
        useGeneratedKeys="true" keyProperty="id">
    insert tbl_employee(last_name,gender,email)
    values(#{lastName},#{gender},#{email})
</insert>

3、insert_获取非自增主键的值

<!--
        Oracle不支持自增:Oracle使用序列来模拟自增
        每次插入的数据的主键是从序列中拿到的值,如何拿?
    -->
<insert id="addEmp" databaseId="oracle">
    <!-- keyProperty:查出的主键值封装给JavaBean的哪个属性
                order="BEFORE" > 当前sql在插入sql之前运行
                        AFTER > 当前sql在插入sql之后运行
                resultType:查出的数据的返回类型

            BEFORE运行顺序:
                先运行selectKey 查询id的sql,查出的id值封装给JavaBean的id属性
                然后运行插入的sql,就可以取出id属性对应的值
        -->
    <selectKey keyProperty="id" order="BEFORE" resultType="Integer">
        <!-- 编写查询主键的sql语句 -->
        select EMPLOYEES_SEQ.nextval from dual
    </selectKey>
    <!-- 插入时的主键是从序列中拿到的 -->
    insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
    values(#{id},#{lastName},#{email})
</insert>

4、参数处理_单个参数&多个参数&命名

EmployeeMapper.java

public Employee getEmpByIdAndLastName(
        @Param("id") Integer id,@Param("lastName") String lastName);

EmployeeMapper.xml

<!-- public Employee getEmpByIdAndLastName(Integer id,String lastName); -->
<select id="getEmpByIdAndLastName" resultType="com.yuanwu.mybatis.bean.Employee">
    select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>

MyBatis.java

@Test
public void test04() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    //1、获取到的SqlSession不会自动提交数据
    SqlSession openSession = sqlSessionFactory.openSession();

    try {
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        //多个参数查询
        Employee employee = mapper.getEmpByIdAndLastName(2, "Jerry");
        System.out.println(employee);
    } finally {
        openSession.close();
    }
}

mybatis参数处理.txt 总结

传入单个参数:
        #{参数名}:取出参数值

传入多个参数:
    多个参数会被封装成一个map,
        key:param1, param2...或者参数的索引
        value:传入的参数值
    #{}就是从map中获取指定的key的值;

    绑定异常:
        Cause: org.apache.ibatis.binding.BindingException:
        Parameter 'id' not found.
        Available parameters are [arg1, arg0, param1, param2]
    操作:
        方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
        取值:id = #{id} and last_name=#{lastName}

命名参数:明确指定封装参数时map的key;@Param("id")
    多个参数会被封装成一个map,
        key:使用@Param注解指定的值
        value:参数值
    #{指定的key}取出对应参数值

5、参数处理_POJO&Map&TO

POJO:
如果多个参数正好是我们业务逻辑的数据模型,我们可以直接传入pojo;【传入Javabean对象】
    #{属性名},取出传入的pojo的属性值

Map:
如果多个参数不是业务逻辑的数据模型,没有对应的pojo,我们也可以传入map;【不经常使用】
    #{key},取出map中对应的值

TO
如果多个参数不是业务逻辑的数据模型,但是要经常使用,推荐编写一个TO(Transfer Object)数据传输对象

EmployeeMapper.xml

<!-- public Employee getEmpByMap(Map<String,Object> map); -->
<select id="getEmpByMap" resultType="com.yuanwu.mybatis.bean.Employee">
    select * from tbl_employee where id=#{id} and last_name=#{lastName}
</select>

EmployeeMapper.java

public Employee getEmpByMap(Map<String,Object> map);

MyBatis.java

@Test
public void test04() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    //1、获取到的SqlSession不会自动提交数据
    SqlSession openSession = sqlSessionFactory.openSession();

    try {
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        //多个参数查询
        // Employee employee = mapper.getEmpByIdAndLastName(2, "Jerry");
        Map<String,Object> map = new HashMap<>();
        map.put("id", 2);
        map.put("lastName","Jerry");
        Employee employee = mapper.getEmpByMap(map);
        System.out.println(employee);
    } finally {
        openSession.close();
    }
}

6、源码分析_参数封装map的过程

1.MapperProxy > 2.MapperMethod > 3.ParamNameResolver 
===================结合源码,mybatis怎么处理参数===============================

ParamNameResolver解析参数封装map
//1、names:{ 0 -> id,  1 -> lastName}:构造器的时候就确定好了

    确定流程:
        1、获取每个标注了@param注解的参数的@Param的值:id,lastName;赋值给name;【name = ((Param) annotation).value();】
        2、每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
             name的值:
                    标注了param注解:注解的值
                    没有标注:
                        1、全局配置:useActualParamName(jdk1.8):name=参数名;【在全局配置中配置了useActualParamName属性】
                        2、name = String.valueOf(map.size());相当于当前元素的索引
            { 0 -> id,  1 -> lastName,2=2}【如果没标注注解,索引是2,参数也是2】

args[2, "Jerry"]:
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    //1、如果参数为null,直接返回
    if (args == null || paramCount == 0) {
      return null;

    //2、如果只有一个元素,并且没有标注Param注解,返回第一个key, args[0],单个参数
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];

    //3、多个元素或者标注Param注解
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;

      //4、遍历names集合:{ 0 -> id,  1 -> lastName,2=2}
      for (Map.Entry<Integer, String> entry : names.entrySet()) {

        //names集合的value作为key,names集合的key又作为取值的参考args[0]
        //最终 ==>    eg:{ id=args[0]:1, lastName=args[1]:Jerry, 2=args[2] }
        param.put(entry.getValue(), args[entry.getKey()]);

        // add generic param names (param1, param2, ...)
        //额外的将每一个参数也保存到map中,并使用新的key:param1...paramN
        //也就是==> 既可以使用有Param注解的#{指定的key},也可以使用#{param1}
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }
}

7、参数处理_#与$取值区别

==============================参数值的获取==============================
#{}:可以获取map中的值或者pojo对象属性的值
${}:可以获取map中的值或者pojo对象属性的值
    区别:
        #{}:是以预编译的形式,将参数设置到sql语句中,PreparedStatement;防止sql注入
        ${}:取出的值直接拼装在sql语句中,会有安全问题;
        大多数情况下,我们取参数的值都应该使用#{}

        表名字段名都不支持预编译,字段值可以

        但是,原生jdbc不支持占位符的地方我们就可以使用${}进行取值
        比如分表、排序

8、参数处理_#取值时指定参数相关规则

#取值时指定参数相关规则
        jdbcType通常需要在某种特定的条件下被设置:
            在数据库为null时,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错);

            因为mybatis对所有的null都映射的是原生jdbc的OTHER类型,Oracle不认识

            全局配置中,jdbcTypeForNull=OTHER【默认】,Oracle不支持,有两种方式
            1、#{email,jdbcType=OTHER}
            2、jdbcTypeForNull=NULL
                   <setting name="jdbcTypeForNull" value="NULL"/>【全局配置】

9、select_返回List

<!-- public List<Employee> getEmpsByLastNameLike(String lastName);
        resultType:如果返回的是一个集合,要写集合中元素的类型
    -->
<select id="getEmpsByLastNameLike" resultType="com.yuanwu.mybatis.bean.Employee">
    select * from tbl_employee where last_name like #{lastName}
</select>

10、select_记录封装Map

EmployeeMapper.java

//多条记录封装一个map,Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的JavaBean
//告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("id")
public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);

//返回一条记录的map,key=列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);

EmployeeMapper.xml

<!-- public Map<String, Object> getEmpByIdReturnMap(Integer id); -->
<select id="getEmpByIdReturnMap" resultType="map">
    select * from tbl_employee where id=#{id}
</select>

<!-- public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName); -->
<select id="getEmpByLastNameLikeReturnMap" resultType="com.yuanwu.mybatis.bean.Employee">
    select * from tbl_employee where last_name like #{lastName}
</select>

MyBatisTest.java

// Map<String, Object> map = mapper.getEmpByIdReturnMap(3);
// System.out.println(map);

Map<Integer, Employee> map = mapper.getEmpByLastNameLikeReturnMap("%r%");
System.out.println(map);	//{2=Employee{id=2, lastName='Jerry', gender='0', email='Jerry@yuanwu.com'}, 3=Employee{id=3, lastName='Jerry', gender='0', email='Jerry@yuanwu.com'}}

11、select_resultMap【自定义结果映射规则】

EMployeeMapperPlus.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yuanwu.mybatis.dao.EmployeeMapperPlus">

    <!-- 自定义某个JavaBean的封装规则
            type:自定义规则的Java类型
            id:唯一标识符,方便引用
     -->
    <resultMap id="MyEmp" type="com.yuanwu.mybatis.bean.Employee">
        <!-- 指定主键列的封装规则
            id定义主键会底层有优化;
                column:指定哪一列
                property:指定对应的JavaBean属性
        -->
        <id column="id" property="id"/>
        <!-- result定义普通列封装规则 -->
        <result column="last_name" property="lastName"/>
    <!--    其他不指定的列会自动封装,只要写resultMap就把全部的映射规则都写上-->
        <result column="email" property="email"/>
        <result column="gender" property="gender"/>
    </resultMap>

    <!-- resultMap:自定义结果集映射规则 -->
    <!--    public Employee getEmpById(Integer id);-->
    <select id="getEmpById" resultMap="MyEmp">
        select * from tbl_employee where id=#{id}
    </select>
</mapper>

12、select_resultMap_级联属性

EmployeeMapperPlus.xml


<!--
    模拟场景一:
        查询Employee的同时查询员工对应的部门
        Employee===Department 【一个Employee 对应 一个部门,每一个员工都有部门信息】
        一个员工有与之对应的部门信息;
                id  last_name  gender  email     【d_id     did  dept_name【private Department dept;】
-->

    <!--
        联合查询:级联属性,封装结果集
    -->
    <resultMap id="MyDifEmp" type="com.yuanwu.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <result column="did" property="dept.id"/>
        <result column="dept_name" property="dept.departmentName"/>
    </resultMap>
    <!--  public Employee getEmpAndDept(Integer id);  -->
    <select id="getEmpAndDept" resultMap="MyDifEmp">
        SELECT e.id id,e.last_name last_name,e.gender gender,e.email email,e.d_id d_id,
        d.id did,d.dept_name dept_name
        FROM tbl_employee e,tbl_dept d
        WHERE e.d_id=d.id AND e.id=#{id}
    </select>

测试类

Employee empAndDept = mapper.getEmpAndDept(2);
System.out.println(empAndDept);
System.out.println(empAndDept.getDept());
/*
Employee{id=2, lastName='Jerry', gender='0', email='Jerry@yuanwu.com'}
Department{id=1, departmentName='开发部'}
*/

13、select_resultMap_association属性

    <!--
      使用association标签定义关联单个对象的封装规则;
  -->
    <resultMap id="MyDifEmp2" type="com.yuanwu.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>

        <!-- association标签可以指定联合的JavaBean对象
            property="dept":指定哪个属性是联合对象
            javaType:指定这个属性对象的类型【不能省略】
        -->
        <association property="dept" javaType="com.yuanwu.mybatis.bean.Department">
            <id column="did" property="id"/>
            <result column="dept_name" property="departmentName"/>
        </association>
    </resultMap>
    <!--  public Employee getEmpAndDept(Integer id);  -->
    <select id="getEmpAndDept" resultMap="MyDifEmp2">
        SELECT e.id id,e.last_name last_name,e.gender gender,e.email email,e.d_id d_id,
      d.id did,d.dept_name dept_name
        FROM tbl_employee e,tbl_dept d
        WHERE e.d_id=d.id AND e.id=#{id}
    </select>

14、select_resultMap_association分布查询

DepartmentMapper.xml

<!--  public Department getDeptById(Integer id);  -->
<select id="getDeptById" resultType="com.yuanwu.mybatis.bean.Department">
    select id,dept_name departmentName from tbl_dept where id=#{id}
</select>

EmployeeMapperPlus.xml

<!--  使用association进行分布查询
        1、先按照员工id查询员工信息
        2、根据查询员工信息中的d_id值 拿去部门表查出部门信息
        3、部门设置到员工中;
    -->
    <!--     id  last_name  gender  email     d_id   -->
    <resultMap id="MyEmpByStep" type="com.yuanwu.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!-- association定义关联对象的封装规则
            select:表明当前属性是调用select指定方法查出的结果
            column:将哪一列的值传给这个方法

            流程:
                使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
        -->
        <association property="dept" select="com.yuanwu.mybatis.dao.DepartmentMapper.getDeptById" column="d_id">

        </association>
    </resultMap>
    <!-- public Employee getEmpByIdStep(Integer id); -->
    <select id="getEmpByIdStep" resultMap="MyEmpByStep">
        select * from tbl_employee where id=#{id}
    </select>

测试类

Employee empByIdStep = mapper.getEmpByIdStep(2);
System.out.println(empByIdStep);
System.out.println(empByIdStep.getDept());
/*
Employee{id=2, lastName='Jerry', gender='0', email='Jerry@yuanwu.com'}
Department{id=1, departmentName='开发部'}
*/

15、select_resultMap_懒加载

mybatis-config.xml 【在全局配置文件中,设置两个属性】

    <!--  可以使用延迟加载【懒加载】:
        Employee==>Dept 【Employee对象包含一个Dept属性】
            我们每次查询Employee对象的时候,都将一起查询处理。
            我们希望部门信息在我们需要使用的时候再去查询;
            分布查询的基础之上加上两个配置;
      -->
<!-- 显式的指定需要更改的配置的值,即使它是默认的。-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>

测试类

System.out.println(empByIdStep.getLastName());
System.out.println(empByIdStep.getDept());
/*
DEBUG 10-16 21:55:46,405 ==>  Preparing: select * from tbl_employee where id=?   (BaseJdbcLogger.java:143) 
DEBUG 10-16 21:55:46,447 ==> Parameters: 2(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 10-16 21:55:46,551 <==      Total: 1  (BaseJdbcLogger.java:143) 
Jerry

DEBUG 10-16 21:55:46,552 ==>  Preparing: select id,dept_name departmentName from tbl_dept where id=?   (BaseJdbcLogger.java:143) 
DEBUG 10-16 21:55:46,553 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 10-16 21:55:46,562 <==      Total: 1  (BaseJdbcLogger.java:143) 
Department{id=1, departmentName='开发部'}
*/

场景二

    <!--
        场景二:
            查询部门的时候将部门对应的所有员工信息也查询出来,注释在DepartmentMapper.xml
    -->

Mybatis__动态SQL

if:判断
choose (when, otherwise):分支选择 switch-case        
		如果带了id就用id查,如果带了gender就用gender查,只会进入一个分支
trim (where(封装查询条件), set(封装修改条件)):字符串截取
foreach(遍历集合)

1、if_判断&OGNL

<!-- 查询员工,要求携带了哪个字段查询条件就带上这个字段的值 -->
<!-- test:判断表达式(OGNL)
                从参数中取值进行判断
            -->
		select * from tbl_employee
            <if test="id!=null">
                id=#{id}
            </if>
            <if test="lastName!=null and lastName!=''">
                and last_name like #{lastName}
            </if>
            <if test="gender==0 or gender==1">
                and gender=#{gender}
            </if>
            <if test="email!=null and email.trim()!=''">
                and email=#{email}
            </if>

2、where_查询条件

<!--where
            查询的时候如果某些条件没带可能sql拼装会有问题
                1、在where 后面 添加1=1(不安全)
                2、mybatis使用where标签来将所有的查询条件包括在内【mybtais推荐】
                    mybatis会将where标签中拼装的sql,多出来的and或者or去掉
                    where只会去掉第一个多出来的and或者or
        -->
		select * from tbl_employee
        <where>
            <!-- test:判断表达式(OGNL)
                从参数中取值进行判断
            -->
            <if test="id!=null">
                id=#{id}
            </if>
            <if test="lastName!=null and lastName!=''">
                and last_name like #{lastName}
            </if>
            <if test="gender==0 or gender==1">
                and gender=#{gender}
            </if>
            <if test="email!=null and email.trim()!=''">
                and email=#{email}
            </if>
        </where>

3、trim_自定义字符串截取

select * from tbl_employee
        <!--
            prefix="" :前缀,trim标签体中是整个字符串拼装后的结果。
                            prefix给拼串后的整个字符串加一个前缀【where】
            prefixOverrides="":前缀覆盖,去掉整个字符串前面多余的字符
            suffix="":后缀,
                            suffix给拼串后的整个字符串加一个后缀
            suffixOverrides="":后缀覆盖,去掉整个字符串后面多余的字符
        -->
        where
        <!-- 自定义字符串的截取规则 -->
        <trim suffix="" suffixOverrides="and">
            <if test="id!=null">
                id=#{id} and
            </if>
            <if test="lastName!=null and lastName!=''">
                 last_name like #{lastName} and
            </if>
            <if test="gender==0 or gender==1">
                 gender=#{gender} and
            </if>
            <if test="email!=null and email.trim()!=''">
                 email=#{email}
            </if>
        </trim>

4、choose_分支选择

select * from tbl_employee
        <where>
           <!-- 如果带了id就用id查,如果带了gender就用gender查,只会进入一个分支 -->
            <choose>
                <when test="id!=null">
                    id=#{id}
                </when>
                <when test="lastName!=null">
                    last_name like #{lastName}
                </when>
                <when test="email!=null">
                    email=#{email}
                </when>
                <otherwise>
                    1=1
                    <!-- gender =0 -->
                </otherwise>
            </choose>
        </where>

5、set_与if结合的动态更新

查询不需要提交外,增删改都需要提交,openSession.commit();手动提交
<!-- public void updateEmp(Employee employee);
            可以使用set标签,或者trim标签,来去除多余的,逗号
    -->
    <update id="updateEmp">
        update tbl_employee
        <!--<set>
        <if test="lastName!=null">
            last_name=#{lastName},
        </if>
        <if test="gender!=null">
            gender=#{gender},
        </if>
        <if test="email!=null">
            email=#{email}
        </if>
        </set>-->
        set
        <trim suffixOverrides=",">
            <if test="lastName!=null">
                last_name=#{lastName},
            </if>
            <if test="gender!=null">
                gender=#{gender},
            </if>
            <if test="email!=null">
                email=#{email}
            </if>
        </trim>
        where id=#{id}
    </update>

6、foreach_遍历集合

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
带IN关键字的查询 【IN关键字用于判断某个字段的值是否在指定集合中,如果是,则满足条件,该字段所在的记录将被查询出来,【也可以是增删改】】
SQL语句:
		select * 【或者】字段名1,字段名2,...
		from 表名
		where 字段名 [not]in(元素1[值],元素2,...)
<!--    public List<Employee> getEmpsByConditionForeach(List<Integer> ids);-->
    <select id="getEmpsByConditionForeach" resultType="com.yuanwu.mybatis.bean.Employee">
        select * from tbl_employee
        where id in
        <!--
            collection:指定要遍历的集合;
                  1、强制指定为list且不可改变
                      [传入list的时候,MyBatis会自动包装在一个map中,
								List实例将会以“list”做为键,会通过“list”去寻找]
                  2、利用注解@Param指定入参名称
                  3、将List包装成Map参数进行传递
            item:将当前遍历出的元素赋值给指定的变量;
            separator:每个元素之间的分隔符;
            open:遍历出所有结果拼接一个开始的字符
            close:遍历出所有结果拼接一个结束的字符
            index:索引,遍历list的时候index就是所有,item就是当前值
                        遍历map的时候index表示map的key,item就是map的值value

            #{变量名} 取出变量的值,当前遍历出的元素
         -->
        <foreach collection="list" item="item_id" separator=","
            open="(" close=")" index="">
            #{item_id}
        </foreach>
    </select>

测试类

List<Employee> list = mapper.getEmpsByConditionForeach(Arrays.asList(1,2,3,4));
            for (Employee emp: list) {
                System.out.println(emp);
            }
/*
Preparing: select * from tbl_employee where id in ( ? , ? , ? , ? )   (BaseJdbcLogger.java:143) 
Parameters: 1(Integer), 2(Integer), 3(Integer), 4(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 10-21 22:23:55,801 <==      Total: 4  (BaseJdbcLogger.java:143) 
Employee{id=1, lastName='Admin', gender='1', email='Tom@yuanwu.com'}
Employee{id=2, lastName='Jerry', gender='0', email='Jerry@yuanwu.com'}
Employee{id=3, lastName='Jerry', gender='0', email='Jerry@yuanwu.com'}
Employee{id=4, lastName='Herbert', gender='1', email='Herbert@yuanwu.com'}
*/

Mybatis_缓存

缓存_缓存介绍

  • MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便的配置和定制。缓存可以极大的提升查询效率

  • MyBatis系统中 默认定义了两级缓存

  • 一级缓存和二级缓存

    • 1、默认情况下:只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启;
    • 2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存
    • 3、为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

缓存_一级缓存体验

两级缓存

​ 一级缓存:(本地缓存)sqlSession级别的缓存。一级缓存是一直开启的;SqlSession级别的Map

​ 与数据库同一次会话期间查询到的数据会放在本地缓存中。

​ 如果再次获取相同的数据,直接从缓存中拿。

​ 二级缓存:(全局缓存)

缓存_一级缓存失效的四种情况

一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还要再次向数据库发送sql)

1、sqlSession不同

2、sqlSession相同,查询条件不同

3、sqlSession相同,两次查询之间执行增删改操作(这次增删改操作可能影响当前数据)

4、sqlSession相同,手动清除一级缓存(缓存情况)openSession.clearCache();

@Test
    public  void testFirstLevelCache() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try {
            //一级缓存初体验
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Employee empById01 = mapper.getEmpById(1);
            System.out.println(empById01);

            //1、sqlSession不同。
            /*SqlSession openSession2 = sqlSessionFactory.openSession();
            EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);*/

            //3、sqlSession相同,两次查询之间执行了增删改操作;(增删改操作有可能影响当前数据)
            /*mapper.addEmp(new Employee(null,"cache","1","testCache@qq.com"));
            System.out.println("数据添加成功");*/

            //4、sqlSession相同,手动清空一级缓存
            // openSession.clearCache();

            //2、sqlSession相同,查询条件不同
            Employee empById02 = mapper.getEmpById(1);
            System.out.println(empById02);
            System.out.println(empById01 == empById02);

            // openSession2.close();

        } finally {
            openSession.close();
        }
    }

缓存_二级缓存使用&细节

1、开启全局二级缓存配置

<setting name="cacheEnabled" value="true"></setting>

2、去mapper.xml中配置使用二级缓存

	<cache eviction="" flushInterval="" readOnly="" size="" type=""></cache>
	<!--
		eviction:缓存回收策略
			· LRU:最近使用最少的,移除最长时间不被使用的对象
			· FIFO:先进先出,按对象进入缓存的顺序来移除
			· SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
			· WEAK:弱引用,更积极的移除垃圾回收器状态和弱引用规则的对象
			· 默认是LRU

		flushInterval:刷新间隔,单位毫秒
			· 默认情况下是不设置,没有刷新时间,缓存仅仅调用语句时刷新

		size:引用数目,正整数
			· 代表缓存最大可以存储多少个对象,太大容易导致内存溢出

		readOnly:只读,true/false
			· true:只读缓存,会给所有调用者返回缓存对象的相同实例
			1 false:读写缓存,会返回缓存对象的拷贝(通过序列化),
		type:指定自定义缓存的全类名;
                实现Cache接口即可
	-->
/**
*两级缓存:
     *  一级缓存(本地缓存):sqlSession级别的缓存,一级缓存是一直开启的,sqlSession级别的一个Map
     *      与数据库同一次会话期间查询到的数据会放在本地缓存中。
     *
     *      一级缓存失效情况(没有使用到当前一级缓存的情况):
     *          1、sqlSession不同。
     *          2、sqlSession相同,查询条件不同(当前一级缓存中还没有这个数据)
     *          3、sqlSession相同,两次查询之间执行了增删改操作;(增删改操作有可能影响当前数据)
     *          4、sqlSession相同,手动清空一级缓存
     *
     *  二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存;
     *      工作机制:
     *          1、一次会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
     *          2、如果会话关闭,一级缓存中的数据会被保存到二级缓存中。新的会话查询信息,就可以参照二级缓存
     *          3、不同的namespace查出的数据会放在自己对应的缓存中(namespace级别的map)
     *
     *              查出的数据都会默认先放在一级缓存中,
     *                  只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存
     *  使用&细节:
     *          1)、开启全局二级缓存配置.<setting name="cacheEnabled" value="true"/>
     *          2)、去mapper.xml中配置使用二级缓存;<cache></cache>
     *          3)、POJO需要实现序列化接口
     */
@Test
    public void testSecondLevelCache() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        SqlSession openSession2 = sqlSessionFactory.openSession();
        try {
            //
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);

            Employee emp01 = mapper.getEmpById(1);
            System.out.println(emp01);
            openSession.close();

            //第二次查询是从二级缓存中拿到的数据
            Employee emp02 = mapper2.getEmpById(1);
            System.out.println(emp02);
            openSession2.close();
        } finally {
            // openSession.close();
        }
    }

缓存_缓存有关的配置以及属性

/**
	 *  和缓存有关的设置/属性
     *          1)、cacheEnabled=true,【false:关闭缓存(二级缓存关闭,不会关闭一级缓存)】
     *          2)、每个select标签都有一个useCache标签,useCache="true"
     *              false:不使用缓存【一级缓存仍然可以使用,二级缓存不使用】
     *          3)、每个增删改标签都有flushCache标签,flushCache="true"【一级二级都会清除】
     *              增删改执行后就会清除缓存
     *              测试:flushCache="true",一级缓存清空了,二级缓存也会被清除
     *              查询标签也有flushCache="true"
     *                  如果设置为false,每次查询之后都会清空缓存,缓存不会被使用
     *          4)、openSession.clearCache();只是清除当前session的一级缓存
     *          5)、localCacheScope:本地缓存作用域(一级缓存SESSION),当前会话的所有数据保存在会话缓存中。
     *                  若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。【禁用一级缓存】
     * */

缓存_缓存原理图示

缓存原理图示.pptx

MyBatis_Spring整合_ssm

配置文件

log4j.properties

LOG4J 日志框架 jar包,类路径下增加一个 log4j.properties

控制台输出SQL语句

# 全局的日志配置
log4j.rootLogger=ERROR, stdout
# MyBatis的日志配置
log4j.logger.[SQL映射文件]全限定类名=DEBUG
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPatern=%5 [%t] - %m%n

web.xml

前端控制器DispatcherServlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    <!-- 定义Spring MVC 控制器 -->
  <servlet>
  	<servlet-name>springmvc</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  	<init-param>
  		<param-name>contextConfigLocation</param-name>
  		<param-value>classpath:spring-configs.xml</param-value>
  	</init-param>
  	<load-on-startup>1</load-on-startup>
  </servlet>
    <!-- 拦截所有请求 -->
  <servlet-mapping>
  	<servlet-name>springmvc</servlet-name>
  	<url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

springmvc-config.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-4.2.xsd        
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
        http://www.springframework.org/schema/context                            
		http://www.springframework.org/schema/context/spring-context-4.2.xsd">
    
<!--自动配置-->
<!--自动扫描包,让指定包下的注解生效,由IOC容器统一管理-->
    
<context:component-scan base-packge="全限定类名【controller】"/>

<!--让Spring Mvc不处理静态资源 .css .js .html ....-->
<mvc:default-servlet-handler/>
<!--
启用注解,分别为HandlerMapping的实现类和HandlerAdapter的实现类
自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter
		spring 3.x以后 对应实现类改为RequestMappingHandlerMapping的Bean
注解驱动,以使得访问路径与方法的匹配可以通过注解配置
-->
<mvc:annotation-driven/>

<!--视图解析器:DispatcherSevlet给他的ModelAdnView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
        id="internalResourceViewResolver">
    <!-- 前缀 -->
    <property name="prefix" value="/WEB-INF/jsp/" />
    <!-- 后缀 -->
    <property name="suffix" value=".jsp" />
</bean>
<!-------------------------------------------------------------------------------------->
<!--手动配置-->
<bean id="/hello" class="全限定类名,【controller控制器类】"/>
</beans>

Spring学习

Spring 模块划分图:

n'g

从底向上看:一个绿色就是一个模块

Test :Spring 的单元测试模块 ;

spring-test-5.1.5.RELEASE.jar

Core Container :核心容器(IOC0) ;黑色部分代表这部分的功能由哪些 jar 包组成 ;要使用完整功能,这些 jar 包要导入

spring-beans-5.1.5.RELEASE.jar、
spring-context-5.1.5.RELEASE.jar、
spring-core-5.1.5.RELEASE.jar、
spring-expression-5.1.5.RELEASE.jar

AOP+Aspects(面向切面编程模块)

spring-aop-5.1.5.RELEASE.jar、
spring-aspects-5.1.5.RELEASE.jar

数据访问/ :Spring 访问数据库模块

spring-jdbc-5.1.5.RELEASE.jar、spring-orm(Object RelationMapping)-5.1.5.RELEASE.jar、
spring-tx-5.1.5.RELEASE.jar(事务)	(上面三个jar包跟数据访问相关)
spring-ox(xml)m-5.1.5.RELEASE.jar、spring-jms-5.1.5.RELEASE.jar、(Intergration 集成)

Web :Spring 开发web应用的模块 ;

spring-websocket-5.1.5.RELEASE.jar、(新的技术)
spring-web-5.1.5.RELEASE.jar、和原生的web相关(servlet)
spring-webmvc-5.1.5.RELEASE.jar、开发web项目的(web)

IOC

IOC :(Inversion(反转) Of Control):控制反转;

​ 控制 :资源的获取方式 ;

​ 主动式 :(要什么资源都自己创建出来即可)

BookServlet {
    BookService bs = new BookService();
    AirPlane ap = new AirPlane();// 但是要创建飞机对象,飞机有很多零件。复杂对象的创建时比较庞大的工程 ;
}

​ 被动式 :资源的获取不是我们自己创建,而是交给一个容器来创建和设置 ;

BookServlet {
    BookService bs;
    public void test01() {
        bs.checkout();
    }
}

容器 :管理所有的组件(有功能的类) ;假设 ,BookServlet受容器管理 ,BookService也受容器管理 ,容器可以自动探查出哪些组件(类)需要用到另一个组件(类) ;容器帮我们创建BookService对象 ,并把BookService对象赋值过去 ;

组件就是所说的 类

容器 :主动的 new 资源变为被动的接收资源 ;

(容器)婚介所 :容器相当于婚介所 ,主动创建变为被动获取

IOC是一种思想,DI就是对这种思想实现的一种描述 ;

DI :(Dependency Injection)依赖注入 ;

​ 容器能指定哪个组件(类)运行的时候 ,需要另外一个组件(类) ;容器通过反射的形式 ,将容器中准备好的BookService对象注入(利用反射给属性赋值)到BookServlet中 ;

Spring运行的是依赖一日志包,commons-logging

HelloWorld第一步:给容器注册一个组件

  • 注入对象 :使用 ref 属性

实验1、通过IOC容器创建对象,并为属性赋值

省略Person对象.......有兴趣的可以自行创建

IOC.xml

<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">

    <!-- 注册一个Person对象,Spring会自动创建这个Person对象  -->
    <!-- 一个bean标签可以注册一个主键(对象、类) -->
    <!--
        class:写要注册的组件的全类名
        id:这个对象的唯一标识;
     -->
    <bean class="com.yuanwu.bean.Person" id="person01">
        <!--  使用property标签为Person对象的属性赋值
            name:指定属性名
            value:为属性赋值
          -->
        <property name="lastName" value="张三"/>
        <property name="age" value="18"/>
        <property name="gender" value="男"/>
        <property name="email" value="zhangsan@163.com"/>
    </bean>

</beans>

HelloWorld第二步:从容器中获取这个组件

public class IOCTest {

    /**
     * 从容器中拿到这个组件
     * */
    @Test
    public void test() {
        /*
        * ApplicationContext:代表ioc容器
        * ClassPathXmlApplicationContext:当前应用的xml配置文件在ClassPath(类路径下)
        * 根据Spring 的配置文件得到ioc容器对象
        * */
        ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");

        // 容器帮我们创建好对象
        Person bean = (Person) ioc.getBean("person01");
        System.out.println(bean);
        // Person{lastName='张三', age=18, gender='男', email='zhangsan@163.com'}
    }
}

实验2:根据bean的类型从IOC容器中获取bean的示例★

ioc.xml

    <bean class="com.yuanwu.bean.Person" id="person01">
        <!--  使用property标签为Person对象的属性赋值
            name:指定属性名
            value:为属性赋值
          -->
        <property name="lastName" value="张三"/>
        <property name="age" value="18"/>
        <property name="gender" value="男"/>
        <property name="email" value="zhangsan@163.com"/>
    </bean>

    <bean class="com.yuanwu.bean.Person" id="person02">
    <!--  使用property标签为Person对象的属性赋值
        name:指定属性名
        value:为属性赋值
      -->
    <property name="lastName" value="小花"/>
    </bean>

</beans>
    /**
     * 实验2:根据bean的类型从IOC容器中获取bean的示例★
     * 如果ioc容器中当前类型的bean有多个,查找会报错
     * org.springframework.beans.factory.NoUniqueBeanDefinitionException:
     * No qualifying bean of type 'com.yuanwu.bean.Person' available:
     * expected single matching bean but found 2: person01,person02
     *
     * */
    @Test
    public void test02() {
        // 当前类型的bean只有一个的时候可用
        /*Person bean = ioc.getBean(Person.class);
        System.out.println(bean); */

        Person person02 = ioc.getBean("person02", Person.class);
        System.out.println(person02);
        /*
Person被创建
setLastName:>>>小花
Person{lastName='小花', age=null, gender='null', email='null'}
        */
    }

实验3、通过构造器为bean的属性赋值

ioc.xml

    <bean class="com.yuanwu.bean.Person" id="person03">
        <!--     public Person(String lastName, Integer age, String gender, String email) -->
        <constructor-arg name="lastName" value="小明"/>
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="gender" value="男"/>
        <constructor-arg name="email" value="xiaoming@qq.com"/>
    </bean>

测试类

private ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
@Test
public void test03() {
    Person person03 = ioc.getBean("person03", Person.class);
    System.out.println(person03);
    /*
    Person被创建
    setLastName:>>>张三
    Person被创建
    setLastName:>>>小花
    有参构造器
    Person{lastName='小明', age=18, gender='男', email='xiaoming@qq.com'}
    */
}

通过构造器为bean的属性赋值(index-type)

index(指定索引)、(type):指定类型,例如 :type="java.lang.Integer"

通过P名称空间为bean赋值

ioc.xml

    <bean class="com.yuanwu.bean.Person" id="person06"
          p:age="18" p:email="xiaoming@163.com"
          p:gender="男" p:lastName="哈哈" >

    </bean>

测试类

private ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
@Test
public void test03() {
    Person person06 = ioc.getBean("person06", Person.class);
    System.out.println(person06);
    /*
    有参构造器
    Person被创建
    setLastName:>>>哈哈
    Person{lastName='哈哈', age=18, gender='男', email='xiaoming@163.com'}
     */
}

实验4、正确的为各种属性赋值

测试 null

<bean class="..." id="...">
	<property name="lastName">
        <!-- 进行复杂的赋值 -->
    	<null/
    </property>
</bean>

ref引用外部的值

ioc.xml

<bean class="x.y.Car" id="car01">
	<property name="color" value="红色"></property>
    ......
</bean>
<bean class="x.y.Person" id="person">
    <!-- 引用类型 对象-->
	<property name="car" ref="car01"></property>
</bean>

引用内部bean

ioc.xml

<bean class="x.y.Person" id="person">
    <!-- 引用类型 对象-->
	<property name="car">
    	<bean class="x.y.Car">
        	<property name="color" value="红色"></property>
        </bean>
    </property>
</bean>

如果是引用类型 、对象,可用在引用内部bean

  • 集合注入总结
  • List 属性使用 <list> 元素定义注入,使用多个 <ref> 元素 去引用Bean
<bean class="x.y.Book" id="book01">
	<property name="bookName" value="十万个为什么"/>
</bean>
<bean class="x.y.Person" id="person01">
    <property name="list">
        <list>
            <bean class="x.y.Book" id="book0XX0" p:Bookname="西游记"></bean>
            <!-- 引用外部一个元素 -->
            <ref bean="bean1"/>
        </list>
    </property>
</bean>
  • Map
  • image-20201113115607569

c命名空间装配bean

会直接引用构造方法中的参数的名称 ;

测试 :学生类

package pojo;

public class Student {

	int id;
	String name;

	public Student(int id, String name) {
		this.id = id;
		this.name = name;
	}
    // setter and getter
}

xml

<!-- 引入 c-命名空间之前 -->
<bean name="student1" class="pojo.Student">
    <constructor-arg name="id" value="1" />
    <constructor-arg name="name" value="学生1"/>
</bean>

<!-- 引入 c-命名空间之后 -->
<bean name="student2" class="pojo.Student"
      c:id="2" c:name="学生2"/>

Spring_依赖注入(修正)

1、依赖注入_Dependency injection

  • 依赖 :指 bean 对象创建依赖于容器 ,Bean 对象的依赖资源

  • 注入 :指 bena 对象依赖的资源由容器来设置和装配

2、Spring 注入 --- 构造器注入

3、Spring 注入 --- setter 注入

//测试类
public class TestStudent {
    private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    @Test
    public void test01() {
        Student student = (Student) ioc.getBean("student");
        System.out.println(student);
    }
}
  • 要求被注入的属性必须要有 set 方法 。

    • a)常量注入
        <bean id="student" class="com.yuanwu.bean.Student">
            <property name="name" value="灭绝师太"/>
        </bean>
    
    • b)bean(对象)注入
    <bean class="com.yuanwu.bean.Address" id="address">
        <property name="address" value="湖北" />
    </bean>
    <bean id="student" class="com.yuanwu.bean.Student">
        <property name="name" value="灭绝师太"/>
        <property name="address" ref="address"/>
    </bean>
    
    • c) 数组注入
    <bean id="student" class="com.yuanwu.bean.Student">
        <property name="name" value="灭绝师太"/>
        <property name="address" ref="address"/>
        <!-- 数组注入 -->
        <property name="books">
            <array>
                <value>傲慢与偏见</value>
                <value>雾都孤儿</value>
                <value>水浒传</value>
            </array>
        </property>
    </bean>
    
    • List 注入

    部分代码,List注入 格式如下 ,后面会将所有方式的注入代码贴出来 ;【参考 】

    <property name="hobbies">
        <list>
            <value>羽毛球</value>
            <value>乒乓球</value>
            <value>篮球</value>
            <value>足球</value>
            <value>橄榄球</value>
        </list>
    </property>
    
    • Map 注入
    <property name="cards">
        <map>
            <entry key="中国银行" value="147258369123456"/>
            <entry key="广发银行" value="147258369331654"/>
            <entry key="浦发银行" value="147258369987456"/>
            <entry>
                <key><value>建设银行</value></key>
                <value>123456987456321</value>
            </entry>
        </map>
    </property>
    
    • Set 注入
    <property name="games">
        <set>
            <value>死神vs火影1.1</value>
            <value>死神vs火影1.2</value>
            <value>死神vs火影1.3</value>
        </set>
    </property>
    
    • null 注入
    <property name="wife"><null/></property>
    
    • properties 注入
    <property name="info">
        <props>
            <prop key="学号">1213698</prop>
            <prop key="sex">女</prop>
            <prop key="name">小明</prop>
        </props>
    </property>
    
    • c命名空间 注入

    要求有对应参数的构造方法

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:c="http://www.springframework.org/schema/c"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="bar" class="x.y.Bar"/>
        <bean id="baz" class="x.y.Baz"/>
    
        <!-- traditional declaration -->
        <bean id="foo" class="x.y.Foo">
            <constructor-arg ref="bar"/>
            <constructor-arg ref="baz"/>
            <constructor-arg value="foo@bar.com"/>
        </bean>
    
        <!-- c-namespace declaration -->
        <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
    
    </beans>
    

IOC常用注解分类

在 xml 配置文件中 开启对注解的支持

<beans>
	<context:component-scan base-package="告诉spring ,创建容器时要扫描的包"/>
</beans>
  • 用于创建对象的 :

    • 作用和 xml配置文件中 标签实现的功能是一样的
    • @Component
      • 作用 :用于把当前类对象存入 spring 容器中
      • 属性
        • value :用于指定 bean 的id,默认是当前类名且首字母小写
  • 用于注入数据的 :

    • 作用和 xml配置文件中 bean标签中 写一个 标签作用一样

    • @Autowired

      • 作用:自动按照类型注入。只要容器中有唯一的一个 bean 对象类型和要注入的变量类型匹配,就可以注入成功。
      • 如果ioc容器中没有任何 bean 的类型和要注入的变量类型匹配,则报错。
      • 如果Ioc容器中有多个类型匹配时:使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
      • 出现位置:
        可以是变量上,也可以是方法上。
    • Qualifier

      • 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用必需和 @Autowire一起使用。但是在给方法参数注入时可以独立使用。
      • 属性:
        value:用于指定注入bean的id。
    • @Resource

      • 作用:直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
      • 属性: name:指定 bean 的 id。
    • @Value

      • 作用:注入基本数据类型和 String 类型数据的

      • 属性: value:用于指定值。它可以使用spring中SpEL

        ​ SpEL 写法 :#

  • 用于改变作用范围的 :

    • 作用和在 bean 标签中使用 属性实现的功能是一样的
    • @Scope
      • 作用:用于指定bean的作用范围
      • 属性:
        value:指定范围的取值。常用取值:singleton prototype
  • 是生命周期相关的 :

    • 作用和在 bean标签中使用 init-metho 和 destory-method 作用一样
    • @PostConstruct
      • 作用:用于指定初始化方法
    • @PreDestroy
      • 作用 :用于指定销毁方法
注解 作用
@Component 用于把当前类对象存入 spring 容器中
@Controller
@Service
@Repository 一般用在持久层
@Autowired 自动按照类型注入
@Qualifier 在按照类中注入的基础上再按照名称注入
@Resource 按照 bean 的 id 注入 ,
@Value 注入基本数据类型和 String 类型数据
@Scope 用于指定 bean 的作用范围
@PostConstruct 用于指定初始化方法
@PreDestroy 用于指定销毁方法

bean的作用域

官方文档 :https://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/htmlsingle/#beans-factory-scopes

scope="singleton"
scope="prototype"
scope="request"
scope="session"
  • singleton 单例 :整个容器中只有一个对象实例
  • prototype 原型 :每次获取bean都产生一个新的对象
  • request :在每次请求时创建一个新的对象
  • session :在会话的范围内是一个对象

bean的生命周期

单例对象 :

  • 出生 : :当容器创建时对象出生
  • 活着 :只要容器还在,对象一直存活
  • 死亡 :容器销毁 ,对象消亡
  • 总结 :单例对象的生命周期和容器相同

多例对象 :

  • 出生 :当我们使用对象时 Spring 为我们创建
  • 活着 :对象只是在使用过程中就一直活着
  • 死亡 :当对象长时间不用 ,且没有别的对象引用时,由Java垃圾回收器回收

创建bean 对象的三种方式

1、通过构造器(有参无参)

<bean id="" class=""/>

2、通过静态工厂

<bean id="" class="工厂类" factory-method="静态工厂方法"/>

3、通过实例工厂方法(非静态方法)

<bean id="factory" class="工厂类"/>
<bean id="" factory-bean="factory" factory-method="实例工厂方法"/>

自动装配

@Autowired

由 Spring 自己发现对应的 Bean ,自动完成装配工作的方式 。

使用 @Autowired ,这个时候 Spring 会根据类型去寻找定义的 Bean 然后将其注入 。

  • 再次理解 :@Autowired 注解表示在 Spring IOC 定位所有的 Bean 后 ,再根据类型寻找资源 ,然后将其注入
  • 过程 :定义 Bean ----> 初始化 Bean(扫描)---->根据属性需要从 SpringIOC容器中搜寻满足要求的 Bean ---->满足要求则注入
  • @Autowired 的required 属性指定某个熟悉允许不被设置

context:component-scan :自动组件扫描

base-package :指定扫描的基础包 ,扫描基础包及子包所有加了注解的类 ,自动扫描进 容器中

动态代理

  • 1、动态代理和静态代理的角色是一样的 。

  • 2、动态代理的代理类是动态生成的 。

  • 3、分为两类实现方式 :基于接口动态代理、基于类的动态代理

    • a)、基于接口的动态代理 :jdk 动态代理
    • b)、基于类的动态代理 :cglib

    现在 Javasist 来生成动态代理 。

  • 4、 jdk 动态代理 --- Proxy 类 和 InvocationHandler 接口

    1、 InvocationHandler 是代理实例的调用处理程序 实现的接口。

    每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法

方法类型 作用
Object invoke(Object proxy, Method method, Object[] args) 在代理实例上处理方法调用并返回结果。
方法参数
proxy 调用方法的代理实例(代理类)
method 代理实例上调用的接口方法的 Method 实例
args 代理实例上方法调用的参数值的对象数组

2、Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

方法类型 作用
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
方法参数
loader 代理类的类加载器 ,
interfaces 代理类要实现的接口列表 ,(被代理类的接口)
h 指派方法调用的调用处理程序,(代理类的实例)

JDK动态代理的一般实现步骤如下:

(1)创建一个实现InvocationHandler接口的类,它必须实现invoke方法

public class ProxyInvocationHandler implements InvocationHandler {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

(2)创建被代理的类以及接口

interface Rent {}

(3)调用Proxy的静态方法newProxyInstance,创建一个代理类

return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

(4)通过代理调用方法

Rent proxy = (Rent) proxyInvocationHandler.getProxy();
proxy.rent();


动态代理具体步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;

public class ProxyInvocationHandler implements InvocationHandler {

  1. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
  1. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        // 利用反射调用类里面的实际方法
        Object result = method.invoke(target , args);
        fare();
        return result;
    }
  1. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
Host host = new Host();
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
proxyInvocationHandler.setTarget(host);
Rent proxy = (Rent) proxyInvocationHandler.getProxy();
proxy.rent();

测试,实现 InvocationHandler 接口

public class ProxyInvocationHandler implements InvocationHandler {

    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     * 生成代理类
     * */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    /**
     * proxy :代理类
     * method :代理类的调用处理程序的方法对象
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        // 利用反射调用类里面的实际方法
        Object result = method.invoke(target , args);
        fare();
        return result;
    }

    private void seeHouse() {
        System.out.println("带租客看房");
    }

    private void fare() {
        System.out.println("收取中介费");
    }

}

测试类

public class Client {

    @Test
    public void test01() {
        Host host = new Host();
        System.out.println(host.getClass());
        System.out.println(host.getClass().getInterfaces());
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        proxyInvocationHandler.setTarget(host);
        Rent proxy = (Rent) proxyInvocationHandler.getProxy();
        proxy.rent();
    }
}

一个动态代理可用代理多个类

面向切面编程 —aop

1、aop :aspect oriented programming 面向切面彼岸

2、aop 在 Spring 中作用

  • ​ 提供声明式事务
  • 允许用户实现自定义切面

面向切面编程【AOP模块】

AOP的一个思想:让关注点代码与业务代码分离

使用注解来开发 Spring AOP

  • 🎃 第一步:选择连接点

    Spring是方法级别的AOP框架,选择哪一个类的哪一个方法用以增强功能

    package com.yuanwu.pojo;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @Author YuanWu
     * @ClassName Landlord
     * @Date 2020/8/8
     **/
    @Component("landlord")
    public class Landlord {
        public void service(){
            System.out.println("签合同");
            System.out.println("收房租");
        }
    }
    

    选择Landlord类中的service()方法作为连接点

    配置xml

    在bean.xml中配置自动注入,并告诉Spring IOC容器去哪里扫描这两个Bean:

     <?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:context="http://www.springframework.org/schema/context"
           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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        
    	<context:component-scan base-package="com.yuanwu.aspect"/>
        <context:component-scan base-package="com.yuanwu.pojo"/>
    
        <aop:aspectj-autoproxy/>
        
    </beans>
    
  • 🎃 第二步:创建切面

在Spring 中只需要使用 @Aspect 注解类,Spring IOC容器就会认为这是一个切面

package com.yuanwu.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Author YuanWu
 * @ClassName Broker
 * @Date 2020/8/8
 **/
@Component
@Aspect
public class Broker {

    @Before("execution(* com.yuanwu.pojo.Landlord.service())")
    public void before(){
        System.out.println("带租客看房子");
        System.out.println("谈价格");
    }

    @After("execution(* com.yuanwu.pojo.Landlord.service())")
    public void after(){
        System.out.println("交钥匙");
    }
}
  • 这里需要注意一下: 被定义为切面的类仍然是一个Bean,需要 @Component 注解标注

Spring 中的AspectJ注解:

注解 说明
@Before 前置通知,在连接点方法前调用
@Around 环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法
@After 后置通知,在连接点方法后调用
@AfterReturning 返货通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常
@AfterThrowing 异常通知,当连接点方法异常时调用

注解中间使用了定义切点的正则式,告诉SpringAOP需要拦截说明对象的说明方法

  • 🎃第三步:定义切点

    Spring通过这个正则表达式判断具体要拦截的是哪一个类的哪一个方法

execution(* com.yuanwu.pojo.Landlord.service())
  • execution :代表执行方法的时候会触发
  • ***** : 代表任意返回类型的反法
  • com.yuanwu.pojo.Landlord : 代表全限定类名
  • service() :被拦截的方法名称

可以使用 @Pointcut 注解来定义一个切点,来避免很多重复的表达式

package com.yuanwu.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Author YuanWu
 * @ClassName Broker
 * @Date 2020/8/8
 **/
@Component
@Aspect
public class Broker {

    @Pointcut("execution(* com.yuanwu.pojo.Landlord.service())")
    public void lService(){
       
    }
    
    //@Before("execution(* com.yuanwu.pojo.Landlord.service())")
    @Before("lService()")
    public void before(){
        System.out.println("带租客看房子");
        System.out.println("谈价格");
    }

    //@After("execution(* com.yuanwu.pojo.Landlord.service())")
    @After("lService()")
    public void after(){
        System.out.println("交钥匙");
    }
}
  • 🎃第四步:测试

  • 带租客看房子
    谈价格
    签合同
    收房租
    交钥匙
    

🥨 环绕通知

Spring AOP 中最强大的通知,因为它集成了前置和后置通知,保留了连接点原有方法的功能

使用 @Around 注解来同时完成前置和后置通知

@Around("execution(* com.yuanwu.pojo.Landlord.service())")
    public void around(ProceedingJoinPoint joinPoint){
        System.out.println("带租客看房子");
        System.out.println("谈价格");

        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        System.out.println("交钥匙");
    }

使用XML配置开发SpringIOC

AOP 配置元素 用途 备注
aop:advisor 定义 AOP 的通知 一种很古老的方式,很少使用
aop:aspect 定义一个切面 ——
aop:before 定义前置通知 ——
aop:after 定义后置通知 ——
aop:around 定义环绕通知 ——
aop:after-returning 定义返回通知 ——
aop:after-throwing 定义异常通知 ——
aop:config 顶层的 AOP 配置元素 AOP 的配置是以它为开始的
aop:declare-parents 给通知引入新的额外接口,增强功能 ——
aop:pointcut 定义切点 ——
<!-- 装配Bean -->
<bean name="productService" class="com.yuanwu.service.ProductService"/>
<bean name="loggerAspect" class="com.yuanwu.aspect.LoggerAspect" />

    <!-- 配置AOP -->
    <aop:config>
<!--        where: 在那些地方(包.类.方法)做增加-->
        <aop:pointcut id="loggerCutpoint" expression="execution(* com.yuanwu.service.ProductService.*(..))"/>
<!--        what: 做什么增强-->
        <aop:aspect id="logAspect" ref="loggerAspect">
<!--            when: 在什么时机(方法前/方法后)-->
            <aop:around pointcut-ref="loggerCutpoint" method="log"/>
        </aop:aspect>
    </aop:config>

在Spring 中使用 AspectJ注解支持

1、导入jar 包

头文件

<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"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task-3.1.xsd">
    
    <!-- 组件扫描 -->
    <context:component-scan base-package="包名"></context:component-scan>
    
    <!-- 基于注解使用AspectJ 	主要作用是为切面中通知能作用到目标类生成代理-->
    <aop:aspectJ-autoproxy/>
    
</beans>

定义一个日志切面类

前置通知

目标方法执行之前执行

@Component //标识为一个组件
@Aspect		//标识为一个切面
public class LoggingAspect{
    @Before("execution(public int 全类名.方法名(参数类型,参数类型))")
    public void beforeMethod(){
        System.out.println("日志记录");
    }
}

后置通知

目标方法执行之后执行;不管目标方法有没有抛出异常都会执行不能获取方法的结果

execution(* com.yuanwu.anntion.类名.*(..))

*:任意修饰符

*:任意类

*:任意方法

..:任意参数

获取方法名字

连接点对象 Joinpoint

getSignature()===》方法签名

public void afterMethod(Joinpoint joinpoinr){

String methodName = joinpoint.getSignature().getName();

System.out.println("日志记录");

}

@After("execution(* 全限定类名.*(..))")
public void afterMethod(){
    System.out.println("日志记录");
}

返回通知

在目标方法正常执行结束后执行;可以获取到返回值

@AfterReturning(value="execution(* 全限定类名.*(..))" ,returning="result")
												//指定名字必须与参数名一致
public void afterRetueningMethod(Object result){
    System.out.println("日志记录" + result);
}

异常通知

在目标方法抛出异常后执行

可以通过形参异常的类型,去设置抛出指定异常,才执行异常通知;比如指定空指针异常,才会执行异常通知

public void afterThrowingMethod(NullPointerExecption EX){
    System.out.println("日志记录" + EX);
}
@AfterThrowing(value="execution(* 全限定类名.*(..))" , throwing="EX")
												//指定名字必须与参数名一致 
public void afterThrowingMethod(Execption EX){
    System.out.println("日志记录" + EX);
}

环绕通知

环绕目标方法执行,可以理解为:前置后置返回异常,四个通知的组合体,更像是动态代理的整个过程

@Around("execution(* 全限定类名.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjp){
    try{
        //前置通知
        ...
    	//执行目标方法
    	pjp.proceed();
        //返回通知
        ...
    } catch(Throwable e){
        //异常通知
        ...
        e.printStackTrace();
    }finally {
        //后置通知
        ...
    }
    return null;
}

@Oreder();

优先级

重用切入点表达式

@Pointcut("execution(* com.atguigu.spring.aop.impl.Cacl.*(int,int))")
public void declareJoinPoint(){}

@After("declareJoinPoint()")
public void after(JoinPoint joinPoint){

补充

jdbcTemplate

jdbctemplate 对 JDBC 简单的封装

QueryRunner 对JDBC 的简单封装


Spring 事务管理

事务的概念:

以转账为例,必定要有两个操作:先将A账户的金额减去要转账的数目,然后将B账户加上响应的金额数目。这两个操作必定要全部完成,才表示当前转账操作成功。若有任何一方失败,则另一方必须回滚(即全部失败)。即事务:这组操作是不可分割的,要么全部成功,要么全部失败。

所谓事务管理,就是“按照给定的事务规则来执行提交或者回滚操作”;

事务的特性:

具有ACID四个特性:

原子性(Atomicity):事务是不可分割的工作单位,要么都发生,要么都不发生。

一致性(Consistency):事务在完成后数据的完整性必须保持一致。

隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发事务之间的数据要相互隔离;

持久性(Durability):一个事务被提交,它对数据库数据的改变应该是永久性的,即使数据库发生故障也不应该对其有任何影响;

Spring 事务管理接口:

三个高层抽象接口:PlatformTransactionManager,TransactionDefinition,TransactionStatus

1、PlatformTransactionManager事务管理器

org.springframework.transaction.PlatformTransactionManager 事务管理器接口;事实上,Spring 框架并不直接管理事务,而是通过这个接口为不同的持久层框架提供了不同的PlatformTransactionManager接口实现类,将事务管理的职责 委托给Hibernate 或者iBatis等持久化框架的事务来实现;

PlatformTransactionManager源码:

public interface PlatformTransactionManager {
    //事务管理器通过TransactionStatus获得"事务状态",从而管理事务
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    //根据状态提交
    void commit(TransactionStatus status) throws TransactionException;
    //根据状态回滚
    void rollback(TransactionStatus status) throws TransactionException;
}

在使用JDBCC或者MyBatis进行数据持久化操作时,xml配置通常:

    <!-- 事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

2、TransactionDefinition 定义事务基本属性

包: org.springframework.transaction.TransactionDefinition ;用于定义一个事务。

它定义了 Spring 事务管理的五大属性:隔离级别、传播行为、是否只读、事务超时、回滚规则

2.1、隔离级别

隔离级别是用来描述并发事务之间隔离程度的大小;

脏读: 一个事务读到了另一个事务的未提交的数据;

不可重复读: 一个事务读到了另一个事务已经提交的 update 的数据,导致多次查询结果不一致;

幻读: 一个事务读到了另一个事务已经提交的 insert 的数据,导致多次查询结果不一致;

Spring 事务管理定义的隔离级别::

ISOLATION_DEFAULT:使用数据库默认的隔离级别;

ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读;

ISOLATION_READ_COMMITTED:允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生;

ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生;

ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读、以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的;

2.2、传播行为

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

Spring 事务管理传播机制规定了事务方法和事务方法 发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间发生调用时的事务上下文的规则;

Spring 定义了七种传播行为:方法之间发生嵌套调用时如何传播事务?

PROPAGATION_REQUIRED: A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务;

PROPAGATION_SUPPORTS: A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行;

PROPAGATION_MANDATORY: A如果有事务,B将使用该事务;如果A没有事务,B将抛出异常;

PROPAGATION_REQUIRES_NEW: A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务;

PROPAGATION_NOT_SUPPORTED: A如果有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行;

PROPAGATION_NEVER:A如果有事务,B将抛出异常;A如果没有事务,B将以非事务执行;

PROPAGATION_NESTED:A和B底层采用保存点机制,形成嵌套事务;

2.3、是否只读

如果将事务设置为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务;

2.4、事务超时

事务超时就是事务的一个定时器,在特定的时间内事务如果没有执行完毕,就会自动回滚,而不是一致等待其结束。在TransactionDefinition 中以 int 的值表示超时时机,默认值是 -1 ,单位秒

2.5、回滚规则

回滚规则定义了哪些异常会导致事务回滚或哪些不会。默认情况下,事务只有遇到运行期异常才会回滚;

3、TransactionStatus事务状态

org.springframework.transaction.TransactionStatus 接口是用来记录事务的状态,用来获取或判断事务的相应状态信息:

TransactionStatus源码:

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();		//是否是新的事务
    boolean hasSavepoint();		//是否有回复点
    void setRollbackOnly();		//设置为只回滚
    boolean isRollbackOnly();		//是否为只回滚
    void flush();		//刷新
    boolean isCompleted();		//是否已完成
}

Spring 事务管理实现方式

Spring 事务管理有两种方式:编程式事务管理;声明式事务管理;

编程式事务管理:通过TransactionTemplate 手动管理事务;

声明式事务管理:基于TransactionProxyFactoryBean的方式、基于Aspect的XML方式、基于注解(@Transactional)的方式;代码侵入性最小,实际是通过AOP实现的;(推荐使用)

配置事务管理器

数据源 让 事务管理器 进行管理

ref="dataSource" ==>>引用数据源 id名字

<!-- 事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transactionManager-manager="dataSourceTransactionManager"/>

xml方式配置事务

<!-- 基于xml配置事务管理, -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="toAdvice">
	<!-- 配置事务属性 -->
    <tx:attributes>
    	<!-- 具体方法使用的事务属性 -->
        <tx:method name="方法名" ...事务属性/>
        
        <!-- 约定方法的名字 -->
        <!-- select开头的方法 -->
        <tx:method name="select*"/>
        
        <!-- 上述指定之外的所有方法 -->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<aop:config>
	<!-- 切入点表达式 -->
    <aop:pointcut expression="executiom(* 全限定类名.*.*(..))" id="toPointCut"/>
    <aop:advisor advice-ref="toAdvice" pointcut-ref="toPointCut"/>
</aop:config>

注解方式配置事务

1、开启 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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置扫描包路径 -->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!-- 配置jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///spring_01"></property>
        <property name="username" value="root"></property>
        <property name="password" value="abc123"></property>
    </bean>

    <!-- 1. 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 2.开启spring对注解事务的支持 -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

2、在需要事务支持的方法或类上使用 @Transactional 注解

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class AccountServiceImpl implements AccountService {
  1. propagation: 指定事务的传播级别
  2. isolation:指定事务隔离级别
  3. timeout: 指定事务的超时时间
  4. readOnly: 是否只读
  5. rollbackFor:异常回滚

3、抽离jdbcTemplate 【可选】

如果使用注解开发 ,需要自己抽离 jdbcTemplate

    <!-- 配置jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="引用数据源"></property>
    </bean>

更多Spring事务基于注解参考

https://blog.csdn.net/java_green_hand0909/article/details/78602920


Spring 整合 MyBatis 未完待续...

1、导包

spring核心包 、aopalliance.jar、aspectjweaver.jar、mybatis核心包、mybatis-spring

image-20201115110731973


SSM整合思想

什么是SSM ?

Spring + SpringMVC + MyBatis 三个开源框架组成的框架集 ;

Spring 是一个轻量级(在系统初始化的时候不用加载所有的)的控制反转(IOC)和面向切面编程(AOP)的容器 ;

  • 何为IOC ?
    • 将主动创建资源变为被动接收资源
    • 比如 :new ,将主动 new 变为被动接收 ,

SpringMVC 分离了控制器、模型对象、分派器以及处理程序对象的角色 ;

MyBatis :支持普通 SQL 查询 ,SQL映射 持久层框架;

将各自的对象讲个各自容器来管理

1、SpringMVC 是视图层(UI) 框架 ,将视图使用的对象交给 SpringMVC 容器管理,放在其配置文件中 。

  • 1)、处理器对象 (controller)
  • 2)、注册组件扫描器 <context:component-scan base-package="controller 注解所在包名"/>
  • 3)、注册视图解析器 :IntemalResourceViewResolver ,配置前缀和后缀
  • 4)、注册注解驱动 :<mvc:anntation-driven /> ;
  • 5)、注册处理器的映射器 ,注册处理器的适配器 (可选)

2、Spring 管理业务层和持久层对象(Service 和 Dao),这些对象放在 Spring 的配置文件中 ,交给 Spring 的容器管理

  • 1)、Service 对象交给 Spring ,使用 @Service 注解 ;

<context:component-scan base-package="Service 注解所在类的全类名"/>

  • 2)、Dao 对象交给 Spring ,MyBatis 对象交给 Spring 。
    • a)、数据源 DataSource :c3p0 ,dbcp ;
    • b)、注册 SqlSessionFactoryBean ,目的是创建 SqlSessionFactory ;
    • c)、注册动态代理扫描器 ,目的是创建Dao 接口的动态代理对象 ,即Dao 层的对象 ;【Mapper接口】

c 步骤详解 : https://blog.csdn.net/Edison_03/article/details/72796691?utm_source=blogxgwz4

  • 3)、把事务管理交给 Spring 。
    • a)、使用注解处理事务 ;
    • b)、使用 AspectJ 的AOP 在配置文件中管理事务 。

Spring 容器和 SpringMVC 容器的关系 :

​ Spring 是 SpringMVC 的父容器 。

​ 在 SpringMVC容器(子容器)中可以知道Spring容器(父容器)的存在 ,子容器可以访问父容器 ,而父容器不能访问子容器 。

SSM整合的配置文件 :

1)、SpringMVC 的配置文件 ,文件名是自定义的 ,dispatcherServlet.xml 。

2)、Spring 的配置文件 ,文件名是自定义的 ,applicationContext.xml 。

3)、MyBatis 的主配置文件 ,配置别名和SQL映射文件的位置 ;

​ SQL映射文件 ,编写 SQL 语句 。

4)、数据库属性配置文件 ,dbconf.properties 。

5)、web.xml

  • a)、注册 SpringMVC 的中央调度器 :作用是接收请求 ,在启动的时候创建 SpringMVC 容器 ,读取 SpringMVC配置文件
  • b)、注册 Spring 监听器 ContextLoadListener ,在启动的时候创建 Spring 容器 ,读取 Spring 配置文件 。
  • b)、注册字符集过滤器 ,解决 POST 请求的乱码问题 。

开发步骤 :

  • 重要 导 jar 包

1、建表

2、创建动态web项目

3、建包 :实体类 ,Service ,Controller ...... 工具包

4、写配置文件

5、编写 第三步骤 的代码 (逻辑代码)

6、定义请求页面

7、定义处理结果页面

8、定义事务

.............

posted @ 2020-11-12 20:36  san只松鼠  阅读(123)  评论(0)    收藏  举报