核心类、常用注解与RESTfulDispatcherServletDispatcherServlet全称org.springframework.web.servlet.DispatcherServlet。DispatcherServlet并不直接处理请求，它只负责根据请求的信息把请求转发给合适的处理器，然后由处理器来执行实际的处理过程并生成响应，它是Spring MVC框架的入口点，它将所有这些步骤组合在一起，使得开发者可以更轻松地构建Web应用程序并处理客户端请求。每个Spring MVC应用程序通常只有一个。我们可以在web.xml里进行配置：<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:springCtx.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/springmvc/*</url-pattern> </servlet-mapping>上面例子中所有以/springmvc开头的请求都会被名称为springmvc的DispatcherServlet处理。另外，在上述代码中，有两个可选项<load-on-startup>和<init-param>。<load-on-startup>如果元素的值为1，则启动程序的时候会立刻加载Servlet；如果元素不存在，则在第一个请求时加载。<init-param>存在并配置了路径，则会根据路径寻找配置文件，否则将导WEB-INF下寻找servletName-servlet.xml命名形式的文件。ViewResolverViewResolver即视图解析器，我们可以在springCtx.xml文件中配置它。回顾之前的springCtx.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="/hello" class="top.cairbin.test7.controller.TestController" /> <bean class= "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /> <bean class= "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <bean class= "org.springframework.web.servlet.view.InternalResourceViewResolver"> </bean> </beans> 再来看看之前的controllerpackage top.cairbin.test7.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class TestController implements Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // 创建ModelAndView对象 ModelAndView mav = new ModelAndView(); // 向模型对象中添加数据 mav.addObject("message", "Hello Spring"); // 设置逻辑视图名 mav.setViewName("/WEB-INF/views/hello.jsp"); // 返回ModelAndView对象 return mav; } }我们在控制器中每次都要指定jsp格式文件和/WEB-INF/views/路径是一件很头疼的事，所以可以修改视图解析器的配置来帮助我们操作。<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean>@Controller、@RequestParam与映射相关的注解@Controller在程序中我们可以使用@Controller来声明这是一个控制器用于依赖注入，而无需去实现Controller接口。@RequestMapping@RequestMapping注解可以很方便地让我们设置类或者方法对应的URL路径，只需要将它们写在类或者方法上面即可例如[...] @Controller @RequestMapping("/myController") class MyController{ [...] }[...] @Controller @RequestMapping("/myController") class MyController{ [...] @RequestMapping("/myFunc1") public MyType MyFunc1([...]){ [...] } //还可以指定路径和请求类型(例如GET) @RequestMapping(path="/myFunc2", method=RequestMethod.GET) public MyType MyFunc2([...]){ [...] } //还有URI模式，路径可随参数变化。 //比如请求某些文章，都有各自ID，它们对应的方法都是这个，路径仅有id不一样，返回对应内容，就可以这样写，此时这里的param对应文章id。 //还可以指定路径和请求类型(例如GET) @RequestMapping(path="/{param}}", method=RequestMethod.GET) public MyType MyFunc3(@PathVariable ParamType param, [...]){ doForParam(param); [...] } }需要注意@RequestMapping有两种属性可以指定映射路径，分别是value和pathvalue被指定会覆盖默认值，path也一样，二者也都能匹配占位符。实际上path就是value的别名，没有区别。另外使用占位符的URL路径需要对参数添加注解@PathVariable组合注解为了更方便地指定Http请求方法类型，Spring也为我们定义了一些组合注解。@GetMapping匹配Get请求@PostMapping匹配Post请求@PutMapping匹配Put请求@DeleteMapping匹配Delete请求@PatchMapping匹配Patch请求至于这些请求是干什么用的，分别对应什么场景，因为并不属于主要内容，此处不过多描述，请查阅HTTP协议文档。（尽管如此，你必须对这些方法及HTTP语义有一定了解）@RequestParam@RequestParam注解为请求指定参数，用法如下。这是Controller里的一个方法@RequestMapping("/hello") public ModelAndView showMessage(@RequestParam(value = "name", required = false, defaultValue = "Spring") String name) { ModelAndView mv = new ModelAndView("hello");//指定视图 mv.addObject("message", message); mv.addObject("name", name); return mv; }实践与test7一样，创建webapp类型的maven项目top.cairbin.test8。（如果不会请返回上一节）在pom.xml里引入依赖文件。 <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.23</version> </dependency>在web.xml里进行配置<?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_3_0.xsd" id="WebApp_ID" version="3.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>classpath:springCtx.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>在src/main/resources里创建springCtx.xml配置文件这里与test7就不一样了，我们指定jsp文件的前后缀，并利用自动扫描来帮助我们完成控制器以及其他类的注入。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns: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="top.cairbin.test8" /> <bean id="viewResolver" class= "org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> </beans>请注意自动扫描的路径与你项目是否一致。另外，因为分层设计，我们在之后肯定不会仅在controller层进行依赖注入，在其他层也会，我们创建的包名都是top.cairbin.test8.xxx，也就是说都是top.cairbin.test8的子包，所以这里指定top.cairbin.test8进行扫描即可。在WEB-INF文件夹下创建views文件夹，并在views下创建hello.jsp文件<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ page isELIgnored="false"%> hello Hello, ${name}! 接下来创建top.cairbin.test8.controller包，包下创建类MyController，并将控制器映射到/hello路径下，将sayHello方法映射到/hello/sayHello/{name}路径，其中{name}是一个占位符，随用户提供的参数而定，让hello.jsp模板渲染，并显示hello,后面跟这个参数的值。package top.cairbin.test8.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/hello") public class MyController { @GetMapping(path="/sayHello/{name}") public ModelAndView sayHello( @PathVariable String name) throws Exception { //指定视图 ModelAndView mv = new ModelAndView("hello"); //向视图中添加所要展示或使用的内容，将在页面中使用 mv.addObject("name", name); return mv; } }我们来运行一下，右键项目->Run as...->Run on server选择你的Tomcat服务器并运行，这里有问题同样返回上一节。浏览器访问http://localhost:8080/test8/hello/sayHello/CairBin我们来看看在URL里不用占位符的方式传参是怎么样的。在MyController里添加一个方法@GetMapping(path="/sayHello2") public ModelAndView sayHello2( String name) throws Exception { //指定视图 ModelAndView mv = new ModelAndView("hello"); //向视图中添加所要展示或使用的内容，将在页面中使用 mv.addObject("name", name); return mv; }在浏览器访问http://localhost:8080/test8/hello/sayHello2我们发现并没有任何数据。换这个URL试试http://localhost:8080/test8/hello/sayHello2?name=cairbinamazing，竟然有了。实际上这对应两种不同的风格。你可能迷茫了，我到底以什么方式传递参数，接下来将具体说明。RESTful与RPC风格请容许我从网上找一张图片学过网络的都知道，TCP/IP模型的应用层实际上对应OSI的应用层、表示层和会话层。对于我们WEB使用的HTTP协议来讲，它是应用层协议。但是在上古年代，人们还在为网络世界里不同机器之间的通信方式而头疼，而最基础的方式是通过socket编程来实现的，不过这个难度有些大，人们迫切需要一个对新手友好且简单的方式。于是，1984年，有位大佬叫 Bruce Jay Nelson，发表了一篇论文叫做 Implementing Remote Procedure Calls，定义了一种标准叫RPC。RPC希望客户端可以像调用本地接口一样来调用服务端，RPC模式分为三层，RPCRuntime 负责最底层的网络传输，Stub 处理客户端和服务端约定好的语法、语义的封装和解封装，这些调用远程的细节都被这两层搞定了，用户和服务器这层就只要负责处理业务逻辑，调用本地 Stub 就可以调用远程。也就是说RPC可以基于TCP/UDP传输层协议传输，也可以使用它们的上层协议HTTP。也正如此HTTP更关注服务提供方，对于客户端怎么调用是不关心的；而对于RPC还要满足更多的东西，也就是需要客户端接口和服务端保持一致（客户端可以像调用本地接口一样来调用服务端）。这种API接口的设计风格就被称为RPC风格。而对于REST，中文描述表述性状态传递，实际上主要关注资源定位，REST是把服务端方法写好，客户端不想知道方法，只想获取资源，这与HTTP语义天然一致。所以从设计上看，RPC是面向方法的，REST是面向资源的。总的来说，RPC通常是服务器和服务器之间的通信，比如和中间件的通信，MQ、分布式缓存、分布式数据库等等。而REST通常是面向客户端的（一般是浏览器）。使用场景不一样。因此你在设计Web的时候两种接口风格实际上都会用到，而对于浏览器来讲，我们为方便实现客户端与服务器的缓存等功能，还是尽量用RESTful风格比较好。但是RESTful，它毕竟只是一种风格，或者说是一种建议，即使你不按照它来设计或者说“接口不够RESTful”也符合规范，但是我们为了尽量靠拢HTTP语义，还是尽量去这样做。接下来我们对比两种风格差异，分别以CRUD为例。RPChttp://127.0.0.1/article/query?id=1 查询文章(GET) http://127.0.0.1/article/add 新增文章(POST) http://127.0.0.1/article/update 更新文章(POST) http://127.0.0.1/article/delete?id=1 删除文章(GET或POST) RESTfulhttp://127.0.0.1/article/1 查询文章(GET) http://127.0.0.1/article 新增文章(POST) http://127.0.0.1/article 更新文章(PUT) http://127.0.0.1/article/1 删除文章(DELETE)
JSP语法入门前提在前一节中我们已经写过JSP的代码了，这一节将单独介绍JSP一些基础语法。当然，你可以跳过这一节，当后面有代码不太理解的时候再回来阅读。中文编码问题如果中文乱码，看看JSP是否是以UTF8的方式编码，使用此编码在JSP文件最上方需要添加下面内容<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>脚本程序脚本程序是包含任意Java语句、变量、方法或表达式。形式如下：<% 脚本程序代码片段 %>我们借助上一节的top.cairbin.test7，稍微对hello.jsp改造下看看效果。<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page isELIgnored="false" %> Hello SpringMvc <% out.println("Hello JSP"); %> 按照上述逻辑来讲应该会在页面显示一个h1的标题，内容为Hello JSP我们访问http://localhost:8080/test7/hello来看看，果然如此。哦，对了，在写语句的时候别忘了那该死的分号。你也可以选择以下等价的XML语句（仍是在JSP文件里写，这里对其他部分省略，用[...]表示）[...] <jsp:scriptlet> out.println("Hello JSP"); </jsp:scriptlet> [...]你大致已经知道JSP书写方式了，为了简洁，在接下来的介绍中除非特殊我们将不再给出演示结果，而是仅介绍用法，请自行在项目中进行测试。JSP表达式表达式先被转化成String，然后被插入到该出现的地方。由于表达式的值会被转化成String，所以您可以在一个文本行中使用表达式而不用去管它是否是HTML标签。形式如下<%= 这里写jsp表达式，注意前面那个等号 %>它也有等价的XML形式<jsp:expression> 表达式 </jsp:expression>示例，注意这里不能用分号结尾 日期 <%= (new java.util.Date()).toLocaleString() %> JSP声明JSP声明可以声明一个或多个变量、方法等供后面代码使用。JSP声明形式<%! 这里写声明 %>等价的XML<jsp:declaration> 声明的代码片段 </jsp:declaration>示例<%! int num = 114514; %>JSP注释<%-- 我就是JSP注释，此处不会被页面展示，也不会被浏览器看到 --%> 你可以在JSP文件中使用JSP注释和HTML注释，但应当注意JSP注释与HTML有些不同，JSP注释不会被发送到浏览器，而HTML注释会被发送至浏览器并且能在浏览器查看源代码功能中看到！！！在HTML注释中不要写敏感信息！！！JSP指令形式如下<%@ 指令 属性="值" %>经常用到的JSP指令标签有：<%@ page ... %>定义页面依赖属性，比如脚本语言、error页、缓存需求、编码等等<%@ include ... %>包含其他文件<%@ taglib ... %>引入标签库的定义，可以自定义标签对于page常用属性有：language声明当前页面脚本的语言，默认为javaextends指定jsp编译成servlet之后所需要继承的类或者所实现的接口import用于导入当前脚本中可能使用到的其他包里面的类info包含jsp的信息，一般作为当前jsp文件的说明用。可以通过getServletInfo()来获取errorPage指定当前jsp文件发生错误时，自动调用改属性值指定的jsp文件。如果不指定当前属性值，当发生错误时，会抛出异常信息给客户。contentType指定生成网页的文件格式和编码字符集isErrorPage用于指定当前jsp文件是否为错误处理jsp文件JSP行为JSP行为使用XML语法结构控制servlet引擎。它需要严格遵守XML标准：<jsp:行为名称 属性="值" />jsp:include用于在当前页面中包含静态或动态资源jsp:useBean寻找和初始化一个JavaBean组件jsp:setProperty设置JavaBean组件的值jsp:getProperty将JavaBean组件的值插入到output中jsp:plugin用于在生成HTML页面中包含Apple和JavaBean对象jsp:element动态创建一个XML元素jsp:forward从一个JSP文件向另一个文件传递一个包含用户请求的request对象jsp:attribute定义动态创建的XML元素属性jsp:body定义动态创建的XML元素的主体jsp:text用于封装模板数据JSP隐式对象JSP有九个无需额外声明或初始化的对象:request: HttpServletRequest类的实例，代表 HTTP 请求的对象，包含客户端发送到服务器的信息，如表单数据、URL参数等。response: HttpServletResponse类的实例，代表 HTTP 响应的对象，用于向客户端发送数据和响应。out: JspWriter类的实例，用于向客户端输出文本内容的对象，通常用于生成HTML。session: HttpSession类的实例，代表用户会话的对象，可用于存储和检索用户特定的数据，跨多个页面。application: ServletContext类的实例，代表 Web 应用程序的上下文，可以用于存储和检索全局应用程序数据。config: ServletConfig类的实例，包含有关当前 JSP 页面的配置信息，例如初始化参数。pageContext: PageContext类的实例，提供对JSP页面所有对象以及命名空间的访问page: 类似于 Java 类中的 this 关键字，代表当前 JSP 页面的实例，可以用于调用页面的方法。exception: exception 类的对象，代表发生错误的 JSP 页面中对应的异常对象，用于处理 JSP 页面中的异常情况，可用于捕获和处理页面中发生的异常。控制流语句判断语句<%! int num=114514; %> <% if(num==114514) { %> 114514 <% }else{ %> 不是114514 <% } %>switch语句switch语句与判断语句不一样，switch语句整个都被包裹在<% %>之中当然，别忘了break。<%! int num=114514; %> <% switch(num){ case 114514: out.println("114514"); break; default: out.println("不是114514"); } %>for语句<% for(int fontSize=1;fontSize<=5;fontSize++){ %> <font color="blue" size="<%= fontSize %>"> fontSize = <%= fontSize %> <%}%>输入出结果如下while与do...while语句while语句与do...while语句类似，这里仅演示while语句<%! int fontSize=1; %> <% while(fontSize<=5){ %> <font color="blue" size="<%= fontSize %>"> fontSize = <%= fontSize %> <% fontSize+=1; %> <%}%>JSP字面量JSP定义了如下几个字面量布尔类型boolean整型int浮点型float字符串string，单引号包裹或者双引号包裹NullJSP运算符JSP支持Java所有逻辑和算术运算符，这里不详细介绍。
Spring MVC项目创建什么是Spring MVCSpring MVC是Spring内置的，实现了Web MVC设计模式的框架。它解决了Web开发过程中很多的问题，例如参数接收、表单验证等。另外它采用松散耦合可插拔组件等结构，具有相对较高的灵活性和扩展性。Spring MVC官方文档可参考 docs.spring.io。Spring MVC执行流程按顺序可分为如下流程用户请求被DispatcherServlet进行拦截处理DispatcherServlet收到请求调用HandlerMappingHandlerMapping找到具体的处理器，生成处理器对象及处理器拦截器，再一起返回给DispatcherServletDispatcherServlet调用HandlerAdapterHandlerAdapter经过适配调用具体的处理器Controller执行完成返回ModelAndView对象HandlerAdapter将ModelAndView返回给DispatcherServletDispatcherServlet将ModelAndView传给ViewResloverViewReslover解析ModelAndView后返回View（给DispatcherServletDispatcherServlet根据View进行渲染DispatcherServlet响应View给用户通过上面流程可知，程序员需要配置DispatcherServlet，并开发View和Controller/Handler。项目创建即然如此，我们就来创建一个Spring MVC项目。打开Eclipse，创建一个Maven项目（想必经过前面学习已经很熟了），项目名称top.cairbin.test7。但是需要注意，创建时请勾选下图的Add project(s) to working set在Select an Archetype这一步需要注意，我们不再用之前的maven-archetype-quickstart，而是maven-archetype-webapp，如下图所示创建完成后项目目录结构大概如下我们还需要引入Spring MVC的依赖包以及Servlet，这你很清楚应该在pom.xml里配置 <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.23</version> </dependency>最终效果看起来是这样<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>top.cairbin</groupId> <artifactId>test7</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>test7 Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.23</version> </dependency> </dependencies> <build> <finalName>test7</finalName> </build> </project>相比于以往的项目，你会发现webapp已经为我们创建了一个resources资源文件夹，并且src/main下多了一个webapp目录。webapp它一般存放前端相关的文件。而webapp目录下有一个WEB-INF，里面有一个默认生成的web.xml配置文件。还记得我们刚才提到的Spring MVC执行流程吗，用户请求会被DispatcherServlet拦截，它叫前端控制器，而这个XML文件就是用来对它进行设置的。我们对web.xml进行修改<?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_3_0.xsd" id="WebApp_ID" version="3.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>classpath:springCtx.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>注意<param-value>classpath:springCtx.xml</param-value>这一段引入了一个文件，是用来配置Controller映射信息的。我们再来看看，webapp下还有一个index.jsp文件，它正是你的前端代码文件，它的内容如下 Hello World! 你可能对此表示疑惑，这不就是一个HTML文件，现实一个标题，标题内容为Hello World!吗。没错，但是对于传统的HTML来讲页面是静态的，而我们的JSP文件则能动态获取后端一些内容。有了View来实现USL前台还不够，我们还要实现USL后台，接下来我们在src/main/java下创建包top.cairbin.test7.controller，并在这个包下创建控制器类TestControllerpackage top.cairbin.test7.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class TestController implements Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // 创建ModelAndView对象 ModelAndView mav = new ModelAndView(); // 向模型对象中添加数据 mav.addObject("message", "Hello Spring"); // 设置逻辑视图名 mav.setViewName("/WEB-INF/views/hello.jsp"); // 返回ModelAndView对象 return mav; } }还记得刚才说的<param-value>classpath:springCtx.xml</param-value>吗，我们创建了控制器，还要告诉Spring MVC控制器与View的映射关系。springCtx.xml这个文件并不存在我们项目中，需要手动添加。在resources资源文件夹里创建它。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="/hello" class="top.cairbin.test7.controller.TestController" /> <bean class= "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /> <bean class= "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <bean class= "org.springframework.web.servlet.view.InternalResourceViewResolver"> </bean> </beans> 请务必注意<bean name="/hello" class="top.cairbin.test7.controller.TestController" />这一段的包名与你控制器一致。它告诉Spring MVC，将这个控制器映射到/hello路径上去，一会你就能在浏览器通过http://ip:port/项目名/hello的形式去访问它。在WEB-INF里创建views文件夹，请注意是WEB-INF下创建。并在这个viewsview文件夹里新建jsp文件hello.jsp<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page isELIgnored="false" %> Hello SpringMvc ${message} 我们在这里使用${...}的形式来使用后端提供的接口，以此获取后端返回的对象。那么问题来了，我们这个项目该怎么运行呢？显然对于Web项目我们需要Http服务器。还记得我们在配置环境的时候安装的Tomcat9吗，现在终于要到了使用它的时候了！另外，我们注意之前编写的TestController这个控制器，它需要导入的包中可能有两个报错。import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;如果出现这种情况，我们右键这个项目，打开属性找到Java Build Path选项的Libraries选项卡，点击Add Library...选择Server Runtime选项，然后下一步选择之前配置好的服务器（就是在环境配置那篇文章里的）保存并退出。接下来我们尝试运行它，与以往不同的是，我们需要Run As里面的Run on server如果你第一次这样做，会弹出个窗口选择Apache里对应的服务器，我这里是Tomcat9.0然后点击完成。很显然，你的浏览器已经打开了，说明你的项目跑成功了。并且在Eclipse的Package Explorer中多了个Servers文件夹。我们在浏览器访问的是index.jsp所渲染的页面在地址栏输入http://localhost:8080/test7/hello访问控制器和hello.jsp所渲染的页面问题在这一节其实有很多坑，一是Tomcat版本在10.0以上的话，包名变化问题。另外如果当初配置环境的时候，Eclipse没选Web选项，这里有些配置是没有的。上面这些问题请参考环境配置和此教程的问题模块。如果你项目菜单里的Run as没有Run on server选项的话需要如下操作打开项目属性搜索project facets，勾选Dynamic Web Module选项保存并退出。接下来如果操作不当的话可能导致Servers文件夹下配置文件里有多个context，这时候需要打开server.xml进行就该找到如下片段<Context>标签，把重复的删除
MVC模式与三层架构什么是MVC模式MVC模式代表Model-View-Controller（模型-视图-控制器）模式。这种应用模式用于应用程序的分层开发。Model代表存取数据的对象，它自身可带有逻辑，数据变化时更新Controller。View代表Model包含数据的可视化。Controller作用在View和Model之上，控制数据流向Model，并在数据变化的时候更新View，使Model与View分离。三者的关系如下图所示：最典型的MVC就是JSP+Servlet+JavaBean的模式。三层架构三层架构中的“三层”分别指：数据访问层（DAL，Data Access Layer）业务逻辑层（BLL，Business Logic Layer）表示层（USL，User Show Layer）DALDAL也被称为持久层，位于三层最下层，用于对数据进行处理。该层方法一般为“原子性”的。在Java程序中，DAL一般都在dao包中；有时也将实体类放在Dao包中，Mapper的方法放在Mapper包中。BLLBBL起到数据交换承上启下的作用，对于业务逻辑进行封装，但与DAL不同的是，这里的方法一般不是原子的，而是包含业务逻辑。例如删除，就要先进行逻辑判断或数据校验再进行DAL中的删除。BLL一般写在service包中。USL位于最上层，负责与用户进行交互，为用户提供交互界面。其中USL分为前台和后台，前台指用户能够看到的界面，后台指来调用BLL的代码。MVC与三层架构的关系MVC与三层架构没有任何关系。前者是用来解决B/S程序（即浏览器/服务器程序）的耦合关系的，而后者适用于任何技术。二者的设计角度不一样。之所以会混淆，是因为二者经常一块用，USL可以用View（USL前台）和Controller（USL后台）来实现，而BLL和DAL可以由Model来实现。
SqlSession以及Spring与MyBatis整合准备所需要的JAR包要实现MyBatis与Spring的整合，很明显需要这两个框架的JAR包，但是只是使用这两个框架中所提供的JAR包是不够的，还需要配合其他包使用：Spring的JAR包MyBatis的JAR包Spring与MyBatis整合中间包mybatis-spring-x.x.x.jar数据库驱动JAR(以MYSQL为例)mysql-connector-java-x.x.x-bin.jar数据源所需的JAR(DBCP)commons-dbcp2-x.x.x.jar和commons-pool2-x.x.x.jar配置文件介绍关于resources文件夹我们在之前的项目中，几乎每次都会创建这个文件夹，然后Use as a source folder，那么这个文件夹到底是用来干啥的，凭什么使用它里面的文件是直接写文件名？实际上这个文件夹是专门存放你的应用所需资源的，如XML等配置文件。这个文件夹被标记为source folder后，在编译后，里面的文件会放到与编译好的文件相同目录里，所以你读取的直接使用文件名实际上是相对路径。pom.xml如果我们创建的是一个Maven项目，就可以在pom.xml里添加<properties></properties>标签来定义库版本，然后在下方的<dependencies></dependencies>中的<dependency></dependency>的<version></version>以${...}形式引用它。下面的XML文件省略了很多，仅为了演示。<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <junit.version>4.12</junit.version> [...] </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> [...] </dependencies>db.propertiesdb.properties主要包含一些数据库连接信息，比如JDBC连接驱动、数据库账户名和密码等。我们在之前的项目中就已经使用过这个文件了，但我们一直没有详细介绍过其每条内容的含义（实际上语义很明显）jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db_mybatis?useUnicode=true&characterEncoding=utf8 jdbc.username=db_mybatis jdbc.password=db_mybatis首先第一行jdbc.driver是指定数据库连接驱动。JDBC即Java数据库连接，它定义了一套关系型数据库的规则，没错实际上就是接口，然后实现并不是由Java官方进行的，而是交给各个数据库厂商来做，它们打包的库也就是JAR包，就被叫做数据库驱动，同时也意味着不同数据库就有不同的驱动实现。程序设计者无需去关心每个数据库驱动是怎么实现的，而是像你把手机充电器插到墙上的插座一样，把驱动按照接口给“插”进去。这就是面向接口编程的好处。你的程序只是一个调用者，数据库驱动就像一个代理人，你要先告诉这个代理人怎么操作，然后让这个代理人去真正操纵数据库（怎么操纵你是不关心的，只要满足你的操作需求即可）。你与这个代理人约定的通用交流方式就是刚才的接口，而代理人具体的操纵方式就是驱动的实现。我们所给的代码中很显然是MySQL的数据库驱动，后面的mysql.jdbc.Driver实际上是随你pom.xml添加的包一块引入的。第二行的是数据库连接URL，它实际上是数据库JDBC连接字符串的一部分。它的书写形式应该参考数据库驱动提供方的文档！jdbc说明这是一个JDBC的连接字符串mysql说明连接的数据库为MYSQLlocalhost所在部分，一般是数据库的网络地址或域名，localhost实际上是一个域名，它在你系统的HOST文件中被解析到了127.0.0.1（通常是这样，不绝对，你完全可以修改它），这个地址是一个回环地址，它实际上表示你机器本身。同理，我的MySQL如果在远程服务器192.168.10.2上面，这里就不再是localhost，而是192.168.10.2或者对应的域名。db_mybatis部分对应数据库名称，这个我们在之前创建过。useUnicode这里的值为true表示使用Unicode编码，它是国际标准字符集，将世界上每一种自然语言每个字符都定义为一个唯一编码，满足跨语言需要，尤其是包含中文的时候。characterEncoding部分是指定Unicode编码的存储方式。Unicode只是一个符号集，但并没有规定怎么存放。这里的存放方式为utf8，被定义为将码点编码为1到4个字节byte，具体取决于有效二进制位的数量。类似于useUnicode的参数还有很多，比如设置SSL等，这里不过多介绍，碰到了请参考文档或搜索引擎。jdbc.username和jdbc.password就很简单了，一般是具有权限的数据库用户的用户名和密码。分配一个或多个具有部分权限的用户是好习惯，而不是一直用root（它权限太大了，不安全）。app.xmlapp.xml为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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 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="xxx.xxxx.xxx" /> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="xx.xxx.xx.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> </beans>mybatis-config.xmlmybatis-config.xml为MyBatis配置文件，当然你可以不写它，将它的配置写在Spring配置文件中（写法不太一样），但不建议这么做，当文件内容特别多时不利于维护。<?xml version="1.0" encoding="UTF-8" ?> <configuration> <properties resource="db.properties" /> <settings> <setting name="lazyLoadingEnabled" value="false" /> <setting name="aggressiveLazyLoading" value="true" /> <setting name="mapUnderscoreToCamelCase" value="true" /> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> <typeAliases> <package name="xx.xxx.xx.dao" /> </typeAliases> <mappers> <mapper resource="xx/xxx/xx/mapper/CustomerMapper.xml" /> </mappers> </configuration>log4j.properties日志的配置文件# Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... log4j.logger.top.cairbin.test5=DEBUG # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n进程、线程与CPU调度在开始Spring与MyBatis的整合之前，为了了解一些概念，我们来讲下进程与线程。什么是进程？进程是对程序过程的抽象。它是OS的动态执行单元，在传统OS上既是基本分配单元又是基本执行单元。举个不太严谨的例子，你在Windows下打开一个音乐播放器，随之你能在任务管理器中看到这一项，而这个正在运行的音乐播放器就是一个进程。线程实际上算是轻量级的进程，但二者还是不太相同，不过它们的相同点要多余不同点。它是看起来就像是对计算机创建进程的模拟，只不过这个模拟是在进程之中的。进程内存资源相对来讲较独立，而多个线程共享进程的内存资源。另外我们的CPU调度一般是抢占式的，我们在使用计算机的时候看起来没啥感觉，但实际上CPU一直在“抽疯”——在多个线程之间进行高速切换，这就带来一些问题。有些操作它不是原子的，即可分割为更小的操作，当一个活没有干完的时候，CPU便去干另一个活。同时，多个线程尝试对同一个变量进行修改的时候，顺序可能是乱的。这些都有可能导致线程不安全。什么是线程不安全？指多线程并发执行某个代码的时候，产生了逻辑上的错误，结果与预期值不同。当然，内存可变性以及Java编译器对指令优化也有可能导致这种情况。SqlSession对于MyBatis来讲实际上有三种SqlSession，我们来分别讲讲它们的区别和使用场景。首先看一张图片，描述再来描述它们的关系：DefaultSqlSessionDefaultSqlSession类是SqlSession接口的默认实现，它通常被使用于执行SQL操作数据库。//读取配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); // 构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); try { MyDao myDao = sqlSession.selectOne("MyMapper.selectDao", 1); System.out.println(myDao); } finally { sqlSession.close(); }DefaultSqlSession是一个线程不安全的，也就是说它不能是单例。啥是单例？某些组件在整个程序运行时只要一个实例对象，多个实例化可能会报错。DefaultSqlSession即然不能是单例，那每次从工厂中获取一个不就行了，实际上这会带来额外开销和资源重用。另外，DefaultSqlSession还需要手动调用close()方法，这很容易忘记（虽然对于C++程序员是家常便饭），但是聪明的你肯定能用我们所学过的东西来解决这一问题吧——没错就是AOP。我们既想要单例，又要线程安全，还想要自动关闭怎么办？这就有了SqlSessionManager。SqlSessionManagerSqlSessionManager使用了JDK动态代理技术（我们之前讲过），动态生成代理对象sqlSessionProxy，并通过SqlSessionInterceptor来对DefaultSqlSession进行增强。虽然对于SqlSessionManager实际上还是创建非单例的DefaultSqlSession来执行方法，但SqlSessionManager可以是单例！那你可能会怼我，说多个DefaultSqlSession这不还是会造成额外开销和资源重用吗？SqlSessionManager还有另外一种形态，它会复用线程本地的DefaultSqlSession！线程不安全是由于多个线程之间共享DefaultSqlSession导致的，那我在同线程内“共享”（复用）我自己的DefaultSqlSession那不就解决线程安全问题了吗。这就大大提高了效率。治不了洋人还治不了你吗（雾）！SqlSessionTemplateSqlSessionTemplate是MyBatis与Spring整合时的线程安全SqlSession。SqlSessionTemplate实现线程安全的思路与SqlSessionManager相反，我既然自己管不了，我就让别人管——它交给SqlSessionUtils去获取SqlSession。但从本质上讲SqlSessionTemplate与SqlSessionManager还是一样的。SqlSessionUtils会先尝试从事务同步器中获取，获取不到再从工厂里要。而事务同步器本身就是一个线程本地变量管理器。所以SqlSessionTemplate与SqlSessionManager在实现线程安全这一点上殊途同归。但是二者在自动关闭，即自动执行close()方法的时候就有区别了。SqlSessionTemplate分两种情况：当获取的对象由事务同步管理器返回的时候，那关闭是交给Spring的。如果是从工厂里拿的，直接调用close()方法。Spring与MyBatis整合Spring与MyBatis整合方式分为两种：传统Dao方式Mapper接口方式传统Dao方式整合采用传统的Dao方式整合Spring和MyBatis框架，可采用mybatis-spring中所提供的SqlSessionTemplate类或者SqlSessionDaoSupport类来实现。由于这种方式在现在的开发中已经不常用了，所以这里仅做演示。@Repository public class CustomerDaoImpl extends SqlSessionDaoSupport implements ICustomerDao{ @Autowired public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory){ super.sqlSessionFactory = sqlSessionFactory; } public void add(Customer customer){ [...] } }更细化的讲解可参考Spring | 整合MyBatis中SqlSessionTemplate和MapperScannerConfigurer类的使用Mapper接口方式传统Dao方式会产生大量重复代码，而且需要正确指定映射文件中的id。为了解决上述问题，我们采用Mapper的方式整合开发。基于MapperFactoryBean的整合MapperFactoryBean是MyBatis-Spring团队提供的一个用来根据Mapper接口生成Mapper对象的类，该类在配置文件中使用时可以配置一下参数mapperInterface用于指定接口SqlSessionFactory用于指定SqlSessionFactorySqlSessionTemplate用于指定SqlSessionTemplate，如果与SqlSessionFactory同时设定，则一般情况下只会启用SqlSessionTemplate。虽然使用Mapper接口编程的方式很简单，但是在具体使用的时候还是需要遵循一些规范：Mapper接口的名称和对应的XML映射文件名称必须一致XML映射文件中的namespace与Mapper接口的类路径相同Mapper接口方法名要和XML映射文件中定义的每个执行语句的id相同Mapper接口中的方法的输入参数类型和XML映射文件中的parameterType类型相同Mapper接口方法的输出类型要和XML映射文件的resultType类型相同基于MapperScannerConfigurer的方式使用上面的方式会使得配置文件臃肿，所以我们在做项目开发的时候一般是使用MapperScannerCongigurer的方式进行扫描。<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="top.cairbin.test6.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>我们新建一个项目top.cairbin.test6，至于哪些细节该注意你应当非常清楚了，这里就不多说了，如果不会请回去看之前的教程。对于配置文件我们也说的很清楚了，接下来直接给文件：pom.xml文件<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>top.cairbin</groupId> <artifactId>test6</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>test6</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <junit.version>4.12</junit.version> <spring.version>5.2.5.RELEASE</spring.version> <mybatis.version>3.5.4</mybatis.version> <mybatis.spring.version>2.0.4</mybatis.spring.version> <mysql.version>8.0.33</mysql.version> <commons-dbcp.version>2.7.0</commons-dbcp.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis.spring.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>${commons-dbcp.version}</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.4.0</version> </dependency> </dependencies> </project>db.properties文件jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db_mybatis?useUnicode=true&characterEncoding=utf8 jdbc.username=db_mybatis jdbc.password=db_mybatisSpring配置文件app.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:tx="http://www.springframework.org/schema/tx" 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="top.cairbin.test6" /> <context:property-placeholder location="db.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="mybatis-config.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="top.cairbin.test6.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> </beans>mybatis-config.xml文件<?xml version="1.0" encoding="UTF-8" ?> <configuration> <properties resource="db.properties" /> <settings> <setting name="lazyLoadingEnabled" value="false" /> <setting name="aggressiveLazyLoading" value="true" /> <setting name="mapUnderscoreToCamelCase" value="true" /> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> <typeAliases> <package name="top.cairbin.test6.dao" /> </typeAliases> <mappers> <mapper resource="top/cairbin/test6/mapper/CustomerMapper.xml" /> </mappers> </configuration>top.cairbin.test6.dao.Customer类package top.cairbin.test6.dao; public class Customer { private Integer id; // 主键id private String username; // 客户名称 private String jobs; // 职业 private String phone; // 电话 public Integer getId() { return this.id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getJobs() { return this.jobs; } public void setJobs(String jobs) { this.jobs = jobs; } public String getPhone() { return this.phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "Customer [id=" + id + ", username=" + username + ", jobs=" + jobs + ", phone=" + phone + "]"; } } top.cairbin.test6.mapper下的CustomerMapper.xml<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test6.mapper.CustomerMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test6.dao.Customer"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="jobs" jdbcType="VARCHAR" property="jobs" /> <result column="phone" jdbcType="VARCHAR" property="phone" /> </resultMap> <sql id="Base_Column_List"> id, username, jobs, phone </sql> select <include refid="Base_Column_List" /> from customer where id = #{id,jdbcType=INTEGER} </mapper>对应接口top.cairbin.test6.mapper.CustomerMapperpackage top.cairbin.test6.mapper; import top.cairbin.test6.dao.Customer; public interface CustomerMapper{ // 通过id查询客户 Customer selectByPrimaryKey(Integer id); }我们接下来实现Service层创建src/main/java下的包top.cairbin.test6.service然后在包中创建接口ICustomerServicepackage top.cairbin.test6.service; import top.cairbin.test6.dao.Customer; public interface ICustomerService { public Customer getCustomerByID(int id); }创建实现这个接口的类CustomerServiceImplpackage top.cairbin.test6.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import top.cairbin.test6.dao.Customer; import top.cairbin.test6.mapper.CustomerMapper; @Service public class CustomerServiceImpl implements ICustomerService{ @Autowired private CustomerMapper customerMapper; public Customer getCustomerByID(int id) { Customer customer = customerMapper.selectByPrimaryKey(id); return customer; } }在测试目录src/test/java下的top.cairbin.test6包创建测试类CustomerTestpackage top.cairbin.test6; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import top.cairbin.test6.dao.Customer; import top.cairbin.test6.service.ICustomerService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:app.xml" }) public class CustomerTest { @Autowired private ICustomerService customerService; @Test public void findTest() { Customer customer = customerService.getCustomerByID(1); System.out.println(customer); } }运行测试，得到结果
MyBatis关联映射为什么要关联映射实际开发中，对数据库操作常常会涉及多张表，所以在OOP中就涉及对象与对象的关联关系。针对多表操作，MyBatis提供关联映射。关联关系概述一对一：A类中定义B类的属性b，B类中定义A类属性a。一对多：一个A类类型对应多个B类。多对多：在A类中定义B的集合，B中定义A的集合。嵌套查询与嵌套结果嵌套查询是通过执行另外一条SQL映射语句来返回预期复杂类型：嵌套查询是在查询SQL嵌套一个子查询嵌套查询会执行多条SQL语句嵌套查询SQL语句编写相对简单嵌套结果是使用嵌套结果映射来处理的联合结果子集：嵌套结果是一个嵌套的多表查询SQL嵌套结果只会执行一条复杂的SQL语句嵌套结果SQL语句写起来相对麻烦对于嵌套查询有一个问题，那就是执行多条SQL语句导致性能开销很大。于是就有了MyBatis的延迟加载——fetchType。实践创建表我们还是用之前的账户连接数据库，终端命令可以如下mysql -u db_mybatis -p输入密码敲击回车，然后切换数据库到db_mybatisWindows用户可以用终端或者sqlyog执行下面语句如果你没有这个数据库请回到前面的章节创建它。USE db_mybatis;我们要使用一个新的customer表，所以把之前项目留下的删除掉DROP TABLE IF EXISTS customer;重新创建它CREATE TABLE customer( `id` int(32) NOT NULL AUTO_INCREMENT, `username` varchar(50) DEFAULT NULL, `jobs` varchar(50) DEFAULT NULL, `phone` varchar(16) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;给这张表插入两条数据方便我们接下来写代码用INSERT INTO customer(`id`,`username`,`jobs`,`phone`) VALUES (1,'zhangsan','teacher','11111111111'),(2,'lisi','student','11111111112');创建一张idcard表，并设置主键（主码）为id如果之前存在请删除DROP TABLE IF EXISTS `idcard`;CREATE TABLE idcard( `id` int(11) NOT NULL AUTO_INCREMENT, `code` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;同样插入两条数据（身份证号是我瞎编的，为了防止巧合不一定符合真实格式，但长度与现实一样）INSERT INTO idcard(`id`,`code`) VALUES(1,'37010219800X051118'),(2,'370203199X09092222');创建persion表，并插入数据DROP TABLE IF EXISTS person; CREATE TABLE person( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, `age` int(11) DEFAULT NULL, `card_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FK_person` (`card_id`), CONSTRAINT `FK_person` FOREIGN KEY (`card_id`) REFERENCES `idcard` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO person(`id`,`name`,`age`,`card_id`) values (1,'张三',18,1),(2,'李四',18,2);创建product表并插入数据DROP TABLE IF EXISTS product; CREATE TABLE product( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `price` double DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO product(`id`,`name`,`price`) values (1,'笔记本电脑',8888.8),(2,'华为手机',6666.6);创建user表并插入数据DROP TABLE IF EXISTS user; CREATE TABLE user( `id` int(11) NOT NULL, `name` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO user(`id`,`name`,`password`) values (1,'zhangsan','654321'),(2,'lisi','123456');创建orders表DROP TABLE IF EXISTS orders; CREATE TABLE orders( `id` int(11) NOT NULL AUTO_INCREMENT, `number` varchar(32) DEFAULT NULL, `user_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FK_orders` (`user_id`), CONSTRAINT `FK_orders` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `orders`(`id`,`number`,`user_id`) VALUES (1,'201901',1);创建orderitem表，还是一样，如果之前存在先删除。这个表一定放后面，否则创建外键会报错。DROP TABLE IF EXISTS orderitem; CREATE TABLE orderitem( `id` int(11) NOT NULL AUTO_INCREMENT, `orders_id` int(11) DEFAULT NULL, `product_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FK_orderitem` (`orders_id`), KEY `FK_orderitem_product` (`product_id`), CONSTRAINT `FK_orderitem_product` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`), CONSTRAINT `FK_orderitem` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO orderitem(`id`,`orders_id`,`product_id`) VALUES (1,1,1),(2,1,2);构建项目我们新创建一个Maven项目top.cairbin.test5pom.xml添加依赖包<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>top.cairbin</groupId> <artifactId>test5</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>test5</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> </project>创建与src平级的resources目录，并右键->Build Path->Use as Source Folder在里面添加一个数据库配置文件db.propertiesjdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db_mybatis jdbc.username=db_mybatis jdbc.password=db_mybatis配置MyBatis，在同目录下创建mybatis-config.xml，这个文件会使用db.properties里面的配置，以${...}的方式调用，在<properties resource="db.properties" />里声明位置。<?xml version="1.0" encoding="UTF-8" ?> <configuration> <properties resource="db.properties" /> <settings> <setting name="lazyLoadingEnabled" value="false" /> <setting name="aggressiveLazyLoading" value="true" /> <setting name="mapUnderscoreToCamelCase" value="true" /> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> <typeAliases> <package name="top.cairbin.test5" /> </typeAliases> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </dataSource> </environment> </environments> <mappers> <mapper resource="top/cairbin/test5/mapper/CustomerMapper.xml" /> <mapper resource="top/cairbin/test5/mapper/UserMapper.xml" /> <mapper resource="top/cairbin/test5/mapper/IdCardMapper.xml" /> <mapper resource="top/cairbin/test5/mapper/OrdersMapper.xml" /> <mapper resource="top/cairbin/test5/mapper/PersonMapper.xml" /> <mapper resource="top/cairbin/test5/mapper/ProductMapper.xml" /> </mappers> </configuration>注意上方的<mapper>的目录，我们一会介绍。然后添加log4j的配置文件log4j.properties# Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... log4j.logger.top.cairbin.test5=DEBUG # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%nDao层我们先把对应的类创建了再说，到src/main/java下top.cairbin.test5创建一个子包，名称为top.cairbin.test5.dao在我们新创建的包下面编写若干个类首先是Person类，此时我们的IdCard还没创建，IDE提示错误很正常package top.cairbin.test5.dao; public class Person { private Integer id; private String name; private Integer age; private Integer cardId; private IdCard card; public void setId(Integer id) { this.id = id; } public Integer getId() { return this.id; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void setAge(Integer age) { this.age = age; } public Integer getAge() { return this.age; } public Integer getCardId() { return cardId; } public void setCardId(Integer cardId) { this.cardId = cardId; } public void setCard(IdCard card) { this.card = card; } public IdCard getCard() { return this.card; } }接下来创建IdCardpackage top.cairbin.test5.dao; public class IdCard { private Integer id; private String code; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCode() { return code; } public void setCode(String code) { this.code = code == null ? null : code.trim(); } }然后是Customer类package top.cairbin.test5.dao; public class Customer { private Integer id; private String username; private String jobs; private String phone; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username == null ? null : username.trim(); } public String getJobs() { return jobs; } public void setJobs(String jobs) { this.jobs = jobs == null ? null : jobs.trim(); } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone == null ? null : phone.trim(); } }接着是Orderspackage top.cairbin.test5.dao; package top.cairbin.test5.dao; import java.util.List; public class Orders { private Integer id; private String number; private Integer userId; private List<Product> productList; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number == null ? null : number.trim(); } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public void setProductList(List<Product> productList) { this.productList = productList; } public List<Product> getProductList(){ return this.productList; } }Product类package top.cairbin.test5.dao; public class Product { private Integer id; private String name; private Double price; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } }User类package top.cairbin.test5.dao; import java.util.List; public class User { private Integer id; private String name; private String password; private List<Orders> ordersList; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public String getPassword() { return password; } public void setPassword(String password) { this.password = password == null ? null : password.trim(); } public void setOrdersList(List<Orders> ordersList) { this.ordersList = ordersList; } public List<Orders> getOrdersList(){ return this.ordersList; } }Util层这一层我们来放置一些工具类。创建包top.cairbin.test5.util然后在下面创建MyBatisHelper类package top.cairbin.test5.util; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisHelper { private static SqlSessionFactory sqlSessionFactory = null; // 初始化SqlSessionFactory对象 static { try { // 使用MyBatis提供的Resources类加载MyBatis的配置文件 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); // 构建SqlSessionFactory工厂 sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (Exception e) { e.printStackTrace(); } } // 获取SqlSession对象的静态方法 public static SqlSession getSession() { return sqlSessionFactory.openSession(); } }Mapper层接下来我们再为top.cairbin.test5创建一个子包top.cairbin.test5.mapper，该层主要实现数据持久化。与上面不一样的是，我们这里定义的是接口首先是CustomerMapper接口package top.cairbin.test5.mapper; import top.cairbin.test5.dao.Customer; public interface CustomerMapper { int deleteByPrimaryKey(Integer id); int insert(Customer record); int insertSelective(Customer record); Customer selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(Customer record); int updateByPrimaryKey(Customer record); }到这里你可能会问，谁去实现它们呢，还记得mybatis-config.xml里那些<mapper>吗，它们每一个都指定resource到Mapper层，也就是说我们要在这里写XML文件让MyBatis实现这些方法。第一个是CustomerMapper.xml，注意此处的namespace与<resultMap>的id，下文同理。<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test5.mapper.CustomerMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test5.dao.Customer"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="jobs" jdbcType="VARCHAR" property="jobs" /> <result column="phone" jdbcType="VARCHAR" property="phone" /> </resultMap> <sql id="Base_Column_List"> id, username, jobs, phone </sql> select <include refid="Base_Column_List" /> from customer where id = #{id,jdbcType=INTEGER} <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from customer where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="top.cairbin.test5.dao.Customer"> insert into customer (id, username, jobs, phone) values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{jobs,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" parameterType="top.cairbin.test5.dao.Customer"> insert into customer <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="username != null"> username, </if> <if test="jobs != null"> jobs, </if> <if test="phone != null"> phone, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="username != null"> #{username,jdbcType=VARCHAR}, </if> <if test="jobs != null"> #{jobs,jdbcType=VARCHAR}, </if> <if test="phone != null"> #{phone,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="top.cairbin.test5.dao.Customer"> update customer <set> <if test="username != null"> username = #{username,jdbcType=VARCHAR}, </if> <if test="jobs != null"> jobs = #{jobs,jdbcType=VARCHAR}, </if> <if test="phone != null"> phone = #{phone,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="top.cairbin.test5.dao.Customer"> update customer set username = #{username,jdbcType=VARCHAR}, jobs = #{jobs,jdbcType=VARCHAR}, phone = #{phone,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> </mapper>对了，请检查你XML中的方法的parameterType属性以及resultType对应类型的包是否正确！！！然后是IdCardMapper接口package top.cairbin.test5.mapper; import top.cairbin.test5.dao.IdCard; public interface IdCardMapper { int deleteByPrimaryKey(Integer id); int insert(IdCard record); int insertSelective(IdCard record); IdCard selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(IdCard record); int updateByPrimaryKey(IdCard record); }同样在IdCardMapper.xml里实现这些方法<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test5.mapper.IdCardMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test5.dao.IdCard"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="code" jdbcType="VARCHAR" property="code" /> </resultMap> <sql id="Base_Column_List"> id, code </sql> select <include refid="Base_Column_List" /> from idcard where id = #{id,jdbcType=INTEGER} <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from idcard where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="top.cairbin.test5.dao.IdCard"> insert into idcard (id, code) values (#{id,jdbcType=INTEGER}, #{code,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" parameterType="top.cairbin.test5.dao.IdCard"> insert into idcard <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="code != null"> code, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="code != null"> #{code,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="top.cairbin.test5.dao.IdCard"> update idcard <set> <if test="code != null"> code = #{code,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="top.cairbin.test5.dao.IdCard"> update idcard set code = #{code,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> </mapper>然后是ProductMapper接口以及ProductMapper.xmlpackage top.cairbin.test5.mapper; import top.cairbin.test5.dao.Product; public interface ProductMapper { int deleteByPrimaryKey(Integer id); int insert(Product record); int insertSelective(Product record); Product selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(Product record); int updateByPrimaryKey(Product record); }<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test5.mapper.ProductMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test5.dao.Product"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="price" jdbcType="DOUBLE" property="price" /> </resultMap> <sql id="Base_Column_List"> id, name, price </sql> SELECT * from product where id IN( SELECT product_id FROM orderitem WHERE orders_id = #{id} ) select <include refid="Base_Column_List" /> from product where id = #{id,jdbcType=INTEGER} <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from product where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="top.cairbin.test5.dao.Product"> insert into product (id, name, price ) values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{price,jdbcType=DOUBLE} ) </insert> <insert id="insertSelective" parameterType="top.cairbin.test5.dao.Product"> insert into product <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="name != null"> name, </if> <if test="price != null"> price, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="name != null"> #{name,jdbcType=VARCHAR}, </if> <if test="price != null"> #{price,jdbcType=DOUBLE}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="top.cairbin.test5.dao.Product"> update product <set> <if test="name != null"> name = #{name,jdbcType=VARCHAR}, </if> <if test="price != null"> price = #{price,jdbcType=DOUBLE}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="top.cairbin.test5.dao.Product"> update product set name = #{name,jdbcType=VARCHAR}, price = #{price,jdbcType=DOUBLE} where id = #{id,jdbcType=INTEGER} </update> </mapper>对于剩下来的几个Dao层类对应的Mapper需要特别注意下，因为它们的类里都包含复合类型，这也是我们这一章节真正要学习的东西——关联映射。PersonMapper接口package top.cairbin.test5.mapper; import top.cairbin.test5.dao.Person; public interface PersonMapper { int deleteByPrimaryKey(Integer id); int insert(Person record); int insertSelective(Person record); Person selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(Person record); int updateByPrimaryKey(Person record); }PersonMapper.xml文件，请重点关注findPersonById和findPersonById2这两个地方。<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test5.mapper.PersonMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test5.dao.Person"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="age" jdbcType="INTEGER" property="age" /> <result column="card_id" jdbcType="INTEGER" property="cardId" /> </resultMap> <sql id="Base_Column_List"> id, name, age, card_id </sql> SELECT * from person where id=#{id} <resultMap type="Person" id="IdCardWithPersonResult"> <id property="id" column="id" /> <result property="name" column="name" /> <result property="age" column="age" /> <association property="card" column="card_id" javaType="top.cairbin.test5.dao.IdCard" select="top.cairbin.test5.mapper.IdCardMapper.selectByPrimaryKey" /> </resultMap> SELECT p.*,idcard.code from person p,idcard idcard where p.card_id=idcard.id and p.id= #{id} <resultMap type="Person" id="IdCardWithPersonResult2"> <id property="id" column="id" /> <result property="name" column="name" /> <result property="age" column="age" /> <association property="card" javaType="IdCard"> <id property="id" column="id" /> <result property="code" column="code" /> </association> </resultMap> select <include refid="Base_Column_List" /> from person where id = #{id,jdbcType=INTEGER} <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from person where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="top.cairbin.test5.dao.Person"> insert into person (id, name, age, card_id) values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{cardId,jdbcType=INTEGER}) </insert> <insert id="insertSelective" parameterType="top.cairbin.test5.dao.Person"> insert into person <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="name != null"> name, </if> <if test="age != null"> age, </if> <if test="cardId != null"> card_id, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="name != null"> #{name,jdbcType=VARCHAR}, </if> <if test="age != null"> #{age,jdbcType=INTEGER}, </if> <if test="cardId != null"> #{cardId,jdbcType=INTEGER}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="top.cairbin.test5.dao.Person"> update person <set> <if test="name != null"> name = #{name,jdbcType=VARCHAR}, </if> <if test="age != null"> age = #{age,jdbcType=INTEGER}, </if> <if test="cardId != null"> card_id = #{cardId,jdbcType=INTEGER}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="top.cairbin.test5.dao.Person"> update person set name = #{name,jdbcType=VARCHAR}, age = #{age,jdbcType=INTEGER}, card_id = #{cardId,jdbcType=INTEGER} where id = #{id,jdbcType=INTEGER} </update> </mapper>对于上面这么长的文件，最重要的地方是这里 <association property="card" column="card_id" javaType="top.cairbin.test5.dao.IdCard" select="top.cairbin.test5.mapper.IdCardMapper.selectByPrimaryKey" />为了看到效果，我们在mybatis-config.xml里将延迟加载关掉。<settings> <setting name="lazyLoadingEnabled" value="false" /> <setting name="aggressiveLazyLoading" value="true"/> </settings>创建UserMapper接口和UserMapper.xmlpackage top.cairbin.test5.mapper; import top.cairbin.test5.dao.User; public interface UserMapper { int deleteByPrimaryKey(Integer id); int insert(User record); int insertSelective(User record); User selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(User record); int updateByPrimaryKey(User record); }<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test5.mapper.UserMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test5.dao.User"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="password" jdbcType="VARCHAR" property="password" /> <collection property="ordersList" column="id" ofType="Orders" select="top.cairbin.test5.mapper.OrdersMapper.findOrdersWithUser"> </collection> </resultMap> <sql id="Base_Column_List"> id, name, password </sql> select <include refid="Base_Column_List" /> from user where id = #{id,jdbcType=INTEGER} <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from user where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="top.cairbin.test5.dao.User"> insert into user (id, name, password ) values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR} ) </insert> <insert id="insertSelective" parameterType="top.cairbin.test5.dao.User"> insert into user <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="name != null"> name, </if> <if test="password != null"> password, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="name != null"> #{name,jdbcType=VARCHAR}, </if> <if test="password != null"> #{password,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="top.cairbin.test5.dao.User"> update user <set> <if test="name != null"> name = #{name,jdbcType=VARCHAR}, </if> <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="top.cairbin.test5.dao.User"> update user set name = #{name,jdbcType=VARCHAR}, password = #{password,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> </mapper>最后OrdersMapper和OrdersMapper.xmlpackage top.cairbin.test5.mapper; import top.cairbin.test5.dao.Orders; public interface OrdersMapper { int deleteByPrimaryKey(Integer id); int insert(Orders record); int insertSelective(Orders record); Orders selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(Orders record); int updateByPrimaryKey(Orders record); }<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test5.mapper.OrdersMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test5.dao.Orders"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="number" jdbcType="VARCHAR" property="number" /> <result column="user_id" jdbcType="INTEGER" property="userId" /> </resultMap> <sql id="Base_Column_List"> id, number, user_id </sql> select * from orders WHERE user_id=#{id} select <include refid="Base_Column_List" /> from orders where id = #{id,jdbcType=INTEGER} <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from orders where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="top.cairbin.test5.dao.Orders"> insert into orders (id, number, user_id ) values (#{id,jdbcType=INTEGER}, #{number,jdbcType=VARCHAR}, #{userId,jdbcType=INTEGER} ) </insert> <insert id="insertSelective" parameterType="top.cairbin.test5.dao.Orders"> insert into orders <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="number != null"> number, </if> <if test="userId != null"> user_id, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="number != null"> #{number,jdbcType=VARCHAR}, </if> <if test="userId != null"> #{userId,jdbcType=INTEGER}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="top.cairbin.test5.dao.Orders"> update orders <set> <if test="number != null"> number = #{number,jdbcType=VARCHAR}, </if> <if test="userId != null"> user_id = #{userId,jdbcType=INTEGER}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="top.cairbin.test5.dao.Orders"> update orders set number = #{number,jdbcType=VARCHAR}, user_id = #{userId,jdbcType=INTEGER} where id = #{id,jdbcType=INTEGER} </update> </mapper>我们编写测试类，在src/test/java下的top.cairbin.test5里PersonTest类来测试一对一的关系package top.cairbin.test5; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import top.cairbin.test5.dao.Person; import top.cairbin.test5.util.*; public class PersonTest { @Test public void findPersonByIdTest() { // 1、通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 2.使用MyBatis嵌套查询的方式查询id为1的人的信息 Person person = session.selectOne("top.cairbin.test5.mapper." + "PersonMapper.selectByPrimaryKey", 1); // 3、输出查询结果信息 System.out.println(person); // 4、关闭SqlSession session.close(); } //测试一对一（嵌套结果）： @Test public void findPersonByIdTest2() { // 1、通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 2.使用MyBatis嵌套查询的方式查询id为1的人的信息 Person person = session.selectOne("top.cairbin.test5.mapper." + "PersonMapper.findPersonById2", 1); // 3、输出查询结果信息 System.out.println(person); // 4、关闭SqlSession session.close(); } }点击运行，两个方法所用时间差距不小。我们再编写测试类UserTest来测试一对多package top.cairbin.test5; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import top.cairbin.test5.dao.User; import top.cairbin.test5.util.MyBatisHelper; public class UserTest { @Test public void findUserTest() { // 1、通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 2、查询id为1的用户信息，注意包名为mapper层的 User user = session.selectOne("top.cairbin.test5.mapper." + "UserMapper.selectByPrimaryKey", 1); // 3、输出查询结果信息 System.out.println(user); // 4、关闭SqlSession session.close(); } }最后编写OrdersTest来测试多对多package top.cairbin.test5; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import top.cairbin.test5.dao.Orders; import top.cairbin.test5.util.MyBatisHelper; public class OrdersTest { @Test public void findOrdersTest(){ // 1、通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 2、查询id为1的订单中的商品信息 Orders orders = session.selectOne("top.cairbin.test5.mapper." + "OrdersMapper.selectByPrimaryKey", 1); // 3、输出查询结果信息 System.out.println(orders); // 4、关闭SqlSession session.close(); } }总结在本章节，我们学习了MyBatis的关联映射。此外，你会发现我们写代码不再像以前一样只放在一个包里，而是创建好几个子包分层放置不同的代码，这有利于代码逻辑清晰并且方便维护，这种分层设计已经有一个正式项目的雏形了，当你写完这些东西的时候项目目录应该与我差不多。
动态SQL什么是动态SQL动态SQL是MyBatis强大特性之一，MyBatis3采用了功能强大的基于OGNL的表达式来完成SQL。动态SQL主要元素常用的动态SQL主要元素如下：如果单独对以上元素解释理解起来还是比较费力的，接下来还是以代码的形式展现。实践基础部分我们还是用之前的项目top.cairbin.test4，我们把src/main/java目录下的top.cairbin.test4包中的CustomerMapper.xml文件里的<mapper namespace="top.cairbin.test4.CustomerMapper"></mapper>中间的内容全部删除，最终效果看上去应该与下面一致：<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test4.CustomerMapper"> </mapper>然后把src/test/java目录下的top.cairbin.test4中的测试类MyBatisTest里面的测试方法也删除掉package top.cairbin.test4; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import top.cairbin.test4.*; public class MyBatisTest { }我们先来编写表与对象的映射关系，在CustomerMapper.xml的<mapper>元素之间填写如下内容（注意包名与你的一致）： <resultMap id="BaseResultMap" type="top.cairbin.test4.Customer"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="jobs" jdbcType="VARCHAR" property="jobs" /> <result column="phone" jdbcType="VARCHAR" property="phone" /> </resultMap> <sql id="Base_Column_List"> id, username, jobs, phone </sql>看起来效果应该跟下面一样，不过在后文中这个文件会越写越长，由于篇幅有限，我们每次仅给出增添部分的代码，而不是每次修改都粘贴全部配置到这里来<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test4.CustomerMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test4.Customer"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="jobs" jdbcType="VARCHAR" property="jobs" /> <result column="phone" jdbcType="VARCHAR" property="phone" /> </resultMap> <sql id="Base_Column_List"> id, username, jobs, phone </sql> </mapper>为了方便操作SqlSession，我们在src/main/java的top.cairbin.test4包下写一个工具类，名称为MyBatisHelperpackage top.cairbin.test4; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisHelper { private static SqlSessionFactory sqlSessionFactory = null; // 初始化SqlSessionFactory对象 static { try { // 使用MyBatis提供的Resources类加载MyBatis的配置文件 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); // 构建SqlSessionFactory工厂 sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (Exception e) { e.printStackTrace(); } } // 获取SqlSession对象的静态方法 public static SqlSession getSession() { return sqlSessionFactory.openSession(); } }你可能对static{...}这段代码有所疑惑，但是你在学习Java语言的时候应该涉及过才对，这也算一种技巧吧，即然碰到了我们就讲一下。还记不记得static关键字的特性？被它修饰的代码不依赖于任何实例化的对象，它们的内存是固定且唯一的，并且不会被Java的垃圾回收机制回收，直至程序结束。也就是说当它们的内存一旦被确认下来，在程序需运行的过程中，所指向的内存位置都是同一地址。当你定义一个类MyClass，类内有个public静态成员staticTest，你并不用(new MyClass()).staticTest，而是直接以MyClass.staticTest的方式去调用。因为对于每一个MyClass它所指向的staticTest都是同一个。那么跟我们要说的static修饰的代码块有什么关系呢？想一想你每次调用方法，都要连接一次数据库，操作完后被Java的回收机制释放掉，当调用次数过多的时候对于连接方和被连接方开销都是巨大的。那么这时候你就会想，能不能让这个过程只执行一次而且不会被Java的回收机制释放呢？这时候就可以利用我们的静态代码块了。无论你new MyBatisHelper()多少次，它只会加载一次且在整个程序运行时有效！编写方法回到正题，我们来编写第一个方法，插入Customer我们在CustomerMapper.xml中<mapper></mapper>标签中间，在原来的基础上追加以下内容（这句话描述的很准确了）<insert id="insertSelective" parameterType="top.cairbin.test4.Customer"> insert into customer <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="username != null"> username, </if> <if test="jobs != null"> jobs, </if> <if test="phone != null"> phone, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="username != null"> #{username,jdbcType=VARCHAR}, </if> <if test="jobs != null"> #{jobs,jdbcType=VARCHAR}, </if> <if test="phone != null"> #{phone,jdbcType=VARCHAR}, </if> </trim> </insert>然后在MyBatisTest测试类里添加测试方法（这里就用到了MyBatisHelper）：@Test public void insertCustomerTest() { SqlSession session = MyBatisHelper.getSession(); Customer cc = new Customer(); cc.setUsername("cairbin"); cc.setJobs("student"); cc.setPhone("888888"); int insert = session.insert("top.cairbin.test4.CustomerMapper.insertSelective", cc); session.commit(); System.out.println(insert + "," + cc); session.close(); }我们运行一下，看看结果看样子是成功了。来看看数据库里，操作还是那样，我这里仅给个结果，你可以跳过这一步，因为我们接下来要编写查询方法。继续编写CustomerMapper.xml文件，我们这里用到了 标签，并且同样用<if>来对数据进行判断。这里应当提一句，看一下 的resultMap属性，是不是正好是<resultMap>标签的id，对应的Java类也就是Customer，同样对应数据库的表customer。 select * from customer where 1=1 <if test="username !=null and username !=''"> and username like concat('%',#{username}, '%') </if> <if test="jobs !=null and jobs !=''"> and jobs= #{jobs} </if> 继续编写测试方法，结合XML，我们可知这个是测试根据username和jobs来查询数据的。@Test public void findCustomerByNameAndJobsTest() { // 通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 创建Customer对象，封装需要组合查询的条件 Customer customer = new Customer(); customer.setUsername("cairbin"); customer.setJobs("student"); // 执行SqlSession的查询方法，返回结果集 List<Customer> customers = session .selectList("top.cairbin.test4" + ".CustomerMapper.findCustomerByNameAndJobs", customer); // 输出查询结果信息 for (Customer customer2 : customers) { // 打印输出结果 System.out.println(customer2); } // 关闭SqlSession session.close(); }大致原理已经知道了，剩下的我们就不做演示了，自己尝试去写一写吧。这里给出完全的CustomerMapper.xml和MyBatis.javaCustomerMapper.xml<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test4.CustomerMapper"> <resultMap id="BaseResultMap" type="top.cairbin.test4.Customer"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="jobs" jdbcType="VARCHAR" property="jobs" /> <result column="phone" jdbcType="VARCHAR" property="phone" /> </resultMap> <sql id="Base_Column_List"> id, username, jobs, phone </sql> select <include refid="Base_Column_List" /> from customer where id = #{id,jdbcType=INTEGER} <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from customer where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="top.cairbin.test4.Customer" useGeneratedKeys="true" keyProperty="id"> insert into customer (id, username, jobs, phone) values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{jobs,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" parameterType="top.cairbin.test4.Customer"> insert into customer <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="username != null"> username, </if> <if test="jobs != null"> jobs, </if> <if test="phone != null"> phone, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="username != null"> #{username,jdbcType=VARCHAR}, </if> <if test="jobs != null"> #{jobs,jdbcType=VARCHAR}, </if> <if test="phone != null"> #{phone,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="top.cairbin.test4.Customer"> update customer <set> <if test="username != null"> username = #{username,jdbcType=VARCHAR}, </if> <if test="jobs != null"> jobs = #{jobs,jdbcType=VARCHAR}, </if> <if test="phone != null"> phone = #{phone,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="top.cairbin.test4.Customer"> update customer set username = #{username,jdbcType=VARCHAR}, jobs = #{jobs,jdbcType=VARCHAR}, phone = #{phone,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> select * from customer where 1=1 select * from customer where 1=1 <if test="username !=null and username !=''"> and username like concat('%',#{username}, '%') </if> <if test="jobs !=null and jobs !=''"> and jobs= #{jobs} </if> select * from customer where 1=1 <choose> <when test="username !=null and username !=''"> and username like concat('%',#{username}, '%') </when> <when test="jobs !=null and jobs !=''"> and jobs= #{jobs} </when> <otherwise> and phone is not null </otherwise> </choose> select * from customer where id in <foreach item="id" index="index" collection="list" open="(" separator="," close=")"> #{id} </foreach> <bind name="pattern_username" value="'%'+_parameter.getUsername()+'%'" /> select * from customer where username like #{pattern_username} </mapper>MyBatisTest.javapackage top.cairbin.test4; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import top.cairbin.test4.*; public class MyBatisTest { @Test public void insertCustomerTest() { SqlSession session = MyBatisHelper.getSession(); Customer cc = new Customer(); cc.setUsername("cairbin"); cc.setJobs("student"); cc.setPhone("888888"); int insert = session.insert("top.cairbin.test4.CustomerMapper.insertSelective", cc); session.commit(); System.out.println(insert + "," + cc); session.close(); } @Test public void findCustomerByNameAndJobsTest() { // 通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 创建Customer对象，封装需要组合查询的条件 Customer customer = new Customer(); customer.setUsername("cairbin"); customer.setJobs("student"); // 执行SqlSession的查询方法，返回结果集 List<Customer> customers = session .selectList("top.cairbin.test4" + ".CustomerMapper.findCustomerByNameAndJobs", customer); // 输出查询结果信息 for (Customer customer2 : customers) { // 打印输出结果 System.out.println(customer2); } // 关闭SqlSession session.close(); } /** * 根据客户姓名或职业查询客户信息列表 */ @Test public void findCustomerByNameOrJobsTest() { // 通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 创建Customer对象，封装需要组合查询的条件 Customer customer = new Customer(); customer.setUsername("cairbin"); // customer.setJobs("teacher"); // 执行SqlSession的查询方法，返回结果集 List<Customer> customers = session.selectList("top.cairbin.test4" + ".CustomerMapper.findCustomerByNameOrJobs", customer); // 输出查询结果信息 for (Customer customer2 : customers) { // 打印输出结果 System.out.println(customer2); } // 关闭SqlSession session.close(); } /** * 更新客户 */ @Test public void updateCustomerTest() { // 获取SqlSession SqlSession sqlSession = MyBatisHelper.getSession(); // 创建Customer对象，并向对象中添加数据 Customer customer = new Customer(); customer.setId(1); customer.setPhone("13311111234"); // 执行SqlSession的更新方法，返回的是SQL语句影响的行数 int rows = sqlSession.update("top.cairbin.test4" + ".CustomerMapper.updateByPrimaryKeySelective", customer); // 通过返回结果判断更新操作是否执行成功 if (rows > 0) { System.out.println("您成功修改了" + rows + "条数据！"); } else { System.out.println("执行修改操作失败！！！"); } // 提交事务 sqlSession.commit(); // 关闭SqlSession sqlSession.close(); } /** * 根据客户编号批量查询客户信息 */ @Test public void findCustomerByIdsTest() { // 获取SqlSession SqlSession session = MyBatisHelper.getSession(); // 创建List集合，封装查询id List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); // 执行SqlSession的查询方法，返回结果集 List<Customer> customers = session.selectList("top.cairbin.test4" + ".CustomerMapper.findCustomerByIds", ids); // 输出查询结果信息 for (Customer customer : customers) { // 打印输出结果 System.out.println(customer); } // 关闭SqlSession session.close(); } /** * bind元素的使用：根据客户名模糊查询客户信息 */ @Test public void findCustomerByNameTest() { // 通过工具类生成SqlSession对象 SqlSession session = MyBatisHelper.getSession(); // 创建Customer对象，封装查询的条件 Customer customer = new Customer(); customer.setUsername("cair"); // 执行sqlSession的查询方法，返回结果集 List<Customer> customers = session.selectList("top.cairbin.test4" + ".CustomerMapper.findCustomerByName", customer); // 输出查询结果信息 for (Customer customer2 : customers) { // 打印输出结果 System.out.println(customer2); } // 关闭SqlSession session.close(); } }
了解MyBatis什么是MyBatisMyBatis 是一款优秀的持久层框架，它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO（Plain Old Java Objects，普通老式 Java 对象）为数据库中的记录。MyBatis中文文档 可参考 https://mybatis.net.cn/。MyBatis是不是ORM？什么是ORM？即Object-Relationl Mapping，它的作用是在关系型数据库和对象之间作一个映射，这样，我们在具体的操作数据库的时候，就不需要再去写复杂的SQL语句。JPA是orm框架标准，MyBatis没有实现JPA，和orm框架的设计思路完全不一样。MyBatis是拥抱sql，而orm则更靠近面向对象。Mybatis是sql mapping框架而不是orm框架，当然orm和Mybatis都是持久层框架。MyBatis工作原理使用MyBatis废话不多说，我们直接上手用下。创建数据库与表我们来创建一个名称为db_mybatis的数据库还是老样子，我这里以终端为例，你可以用sqlyog等工具。mysql -uroot有密码的话mysql -u root -p执行SQL语句创建数据库CREATE DATABASE db_mybatis;创建管理该数据库的用户db_mybatis，密码为db_mybatis，访问主机为本机可访问localhost，如果远程访问请改为%。CREATE USER 'db_mybatis'@'localhost' IDENTIFIED BY 'db_mybatis';给它管理db_mybatis的所有权限GRANT ALL ON db_mybatis.* TO 'db_mybatis'@'localhost';刷新权限flush privileges;如果你用的MySQL8.0及以上版本，客户端版本较老，由于加密算法问题需要追加以下语句：ALERT USER 'db_mybatis'@'localhost' IDENTIFIED WITH mysql_native_password by 'db_mybatis';退出MySQL控制台，返回PC终端exit;我们用刚创建的用户连接下mysqlmysql -u db_mybatis -p然后出现提示，输入密码db_mybatis敲击回车，如果密码跟我不一样，这里与你上面的设置一致。切换数据库到db_mybatisUSE db_mybatis;如果表名称已经存在，会影响我们创建，如果你之前不太重要的话就直接删了，如果想要保留那么新建的表就要修改名字DROP TABLE IF EXISTS `customer`;我们创建一张表，名称为customer，主键为id如果用标准的SQL语句来写的话，我这里版本为MySQL5.7，可能会报错如果报错，就替换成以下语句CREATE TABLE customer ( id int(32) AUTO_INCREMENT, username varchar(50) DEFAULT NULL, jobs varchar(50) DEFAULT NULL, phone varchar(16) DEFAULT NULL, PRIMARY KEY(`id`) );我们插入条数据（这里的电话号码是我瞎编的）INSERT INTO customer(id,username,jobs,phone) VALUES(1,'joy','doctor','15544433322');通过SELECT语句查看下，看来是成功的SELECT * FROM customer;创建项目我们创建一个Maven项目，不会的请回Spring初步部分看看。项目名称为top.cairbin.test4，然后修改pom.xml添加依赖项<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>top.cairbin</groupId> <artifactId>test4</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>test4</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.4.0</version> </dependency> </dependencies> </project>我们创建一个resources文件夹，并use as source folder在里面创建一个log4j.properties，可以让我们在控制台看到SQL语句# Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... log4j.logger.top.cairbin.test4=DEBUG # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n在src/main/java下的top.cairbin.test4包下创建一个持久化类Customerpackage top.cairbin.test4; public class Customer { private Integer id; // 主键id private String username; // 客户名称 private String jobs; // 职业 private String phone; // 电话 public void setId(Integer id) { this.id = id; } public Integer getId() { return this.id; } public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } public void setJobs(String jobs) { this.jobs = jobs; } public String getJobs() { return this.jobs; } public void setPhone(String phone) { this.phone = phone; } public String getPhone() { return this.phone; } //为了方便测试时输出结果，建议生成toString()方法。 @Override public String toString() { return "Customer [id=" + id + ", username=" + username + ", jobs=" + jobs + ", phone=" + phone + "]"; } }在top.cairbin.test4下创建一个映射文件CustomerMapper.xml，注意<mapper>标签的namespace属性。<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test4.CustomerMapper"> select * from customer where id = #{id} </mapper>在resources文件夹里新建一个mybatis核心配置文件mybatis-config.xml<?xml version="1.0" encoding="UTF-8" ?> <configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true" /> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/db_mybatis" /> <property name="username" value="db_mybatis" /> <property name="password" value="db_mybatis" /> </dataSource> </environment> </environments> <mappers> <mapper resource="top/cairbin/test4/CustomerMapper.xml" /> </mappers> </configuration>注意这里的位置<mappers> <mapper resource="top/cairbin/test4/CustomerMapper.xml" /> </mappers>我们在src/test/java目录下创建测试类MyBatisTestpackage top.cairbin.test4; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import top.cairbin.test4.*; public class MyBatisTest { /** * 根据客户编号查询客户信息 */ @Test public void findCustomerByIdTest() throws Exception { // 1、读取配置文件 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 2、根据配置文件构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3、通过SqlSessionFactory创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 4、SqlSession执行映射文件中定义的SQL，并返回映射结果 Customer customer = sqlSession.selectOne("top.cairbin.test4" + ".CustomerMapper. findCustomerById", 1); // 打印输出结果，Customer类中记得生成toString()方法。 System.out.println(customer.toString()); // 5、关闭SqlSession sqlSession.close(); } }注意这条语句Customer customer = sqlSession.selectOne("top.cairbin.test4" + ".CustomerMapper. findCustomerById", 1);我们正是在调用刚才在CustomerMapper.xml里编写的方法，对应 标签的id属性。点击运行看到输出结果在CustomerMapper.xml映射文件中添加模糊查询,添加,更新和删除的配置,在测试类中添加对应的测试方法,然后进行测试。注意每一个方法的parameterType和resultType属性对应的包。<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.cairbin.test4.CustomerMapper"> select * from customer where id = #{id} select * from customer where username like concat('%',#{value},'%') <insert id="addCustomer" parameterType="top.cairbin.test4.Customer"> insert into customer(username,jobs,phone) values(#{username},#{jobs},#{phone}) </insert> <update id="updateCustomer" parameterType="top.cairbin.test4.Customer"> update customer set username=#{username},jobs=#{jobs},phone=#{phone} where id=#{id} </update> <delete id="deleteCustomer" parameterType="Integer"> delete from customer where id=#{id} </delete> </mapper>同样的测试类修改为（请注意代码中的包名）package top.cairbin.test4; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import top.cairbin.test4.*; public class MyBatisTest { /** * 根据客户编号查询客户信息 */ @Test public void findCustomerByIdTest() throws Exception { // 1、读取配置文件 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 2、根据配置文件构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3、通过SqlSessionFactory创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 4、SqlSession执行映射文件中定义的SQL，并返回映射结果 Customer customer = sqlSession.selectOne("top.cairbin.test4" + ".CustomerMapper. findCustomerById", 1); // 打印输出结果，Customer类中记得生成toString()方法。 System.out.println(customer.toString()); // 5、关闭SqlSession sqlSession.close(); } /** * 根据用户名称来模糊查询用户信息列表 */ @Test public void findCustomerByNameTest() throws Exception{ // 1、读取配置文件 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 2、根据配置文件构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3、通过SqlSessionFactory创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 4、SqlSession执行映射文件中定义的SQL，并返回映射结果 List<Customer> customers = sqlSession.selectList("top.cairbin.test4" + ".CustomerMapper.findCustomerByName", "j"); for (Customer customer : customers) { //打印输出结果集 System.out.println(customer); } // 5、关闭SqlSession sqlSession.close(); } /** * 添加客户 */ @Test public void addCustomerTest() throws Exception{ // 1、读取配置文件 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 2、根据配置文件构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3、通过SqlSessionFactory创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 4、SqlSession执行添加操作 // 4.1创建Customer对象，并向对象中添加数据 Customer customer = new Customer(); customer.setUsername("rose"); customer.setJobs("student"); customer.setPhone("13333533092"); // 4.2执行SqlSession的插入方法，返回的是SQL语句影响的行数 int rows = sqlSession.insert("top.cairbin.test4" + ".CustomerMapper.addCustomer", customer); // 4.3通过返回结果判断插入操作是否执行成功 if(rows > 0){ System.out.println("您成功插入了"+rows+"条数据！"); }else{ System.out.println("执行插入操作失败！！！"); } // 4.4提交事务 sqlSession.commit(); // 5、关闭SqlSession sqlSession.close(); } /** * 更新客户 */ @Test public void updateCustomerTest() throws Exception{ // 1、读取配置文件 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 2、根据配置文件构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3、通过SqlSessionFactory创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 4、SqlSession执行更新操作 // 4.1创建Customer对象，对对象中的数据进行模拟更新 Customer customer = new Customer(); customer.setId(2); //注意该id必须是你数据库中已有的id. customer.setUsername("rose"); customer.setJobs("programmer"); customer.setPhone("13311111111"); // 4.2执行SqlSession的更新方法，返回的是SQL语句影响的行数 int rows = sqlSession.update("top.cairbin.test4" + ".CustomerMapper.updateCustomer", customer); // 4.3通过返回结果判断更新操作是否执行成功 if(rows > 0){ System.out.println("您成功修改了"+rows+"条数据！"); }else{ System.out.println("执行修改操作失败！！！"); } // 4.4提交事务 sqlSession.commit(); // 5、关闭SqlSession sqlSession.close(); } /** * 删除客户 */ @Test public void deleteCustomerTest() throws Exception{ // 1、读取配置文件 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 2、根据配置文件构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3、通过SqlSessionFactory创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 4、SqlSession执行删除操作 // 4.1执行SqlSession的删除方法，返回的是SQL语句影响的行数 int rows = sqlSession.delete("top.cairbin.test4" + ".CustomerMapper.deleteCustomer", 2); //注意该id必须是你数据库中已有的id. // 4.2通过返回结果判断删除操作是否执行成功 if(rows > 0){ System.out.println("您成功删除了"+rows+"条数据！"); }else{ System.out.println("执行删除操作失败！！！"); } // 4.3提交事务 sqlSession.commit(); // 5、关闭SqlSession sqlSession.close(); } }运行，然后测试即可。
Spring事务管理概念事务（Transaction）是访问并可能操作各种数据项的一个数据库操作序列，这些操作要么全部执行，要么都不执行，是一个不可分割的工作单元。事务有如下特性：原子性隔离性一致性持久性如果多个事务同时操作同一批数据，则会引发并发异常，设置不同的隔离级别可以解决这些问题。事务的隔离级别如下隔离界别从小到大，安全性越高，但效率就越低。事务的传播行为是指在同一个方法中，不同操作前后所使用的事务。传播行为可以控制是否需要创建事务以及如何创建事务，Spring默认传播行为是REQUIRED。事务的管理方式主要分为两种：编程式事务管理：通过编写代码实现的事务管理，包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚声明式事务管理：通过AOP技术实现的事务管理，主要思想是将事务作为一个“切面”代码单独编写，然后通过AOP技术将事务管理的“切面”植入到业务目标类中为了解耦，我们一般用后者。声明式事务管理基于XMLSpring的声明式事务管理可以通过两种方式来实现，一种是基于XML的方式，另一种是基于注解的方式。基于XML方式的声明式事务是在配置文件中通过<tx:advice>元素配置事务规则来实现的。当配置了事务的增强处理后，就可以通过编写的AOP配置，让Spring自动对目标生成代理。其属性如下。基于注解我们更关心基于注解的方式，下面用代码来了解下我们用之前的top.cairbin.test3项目app.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:tx="http://www.springframework.org/schema/tx" 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="top.cairbin.test3" /> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/db_javaee" /> <property name="username" value="db_javaee" /> <property name="password" value="dbjavaeepassword" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>在IAccountDao中声明方法public void transfer(String outUser,String inUser,Double money);在AccountDao类中添加方法及注解@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false) public void transfer(String outUser, String inUser, Double money) { // 收款时，收款用户的余额=现有余额+所汇金额 this.jdbcTemplate.update("update account set balance = balance +? " + "where username = ?",money, inUser); // 模拟系统运行时的突发性问题 int i = 1/0; // 汇款时，汇款用户的余额=现有余额-所汇金额 this.jdbcTemplate.update("update account set balance = balance-? " + "where username = ?",money, outUser); }在JdbcTemplateTest测试类中添加测试方法@Test public void transTest(){ accountDao.transfer("lisi", "zhangsan", 100.0); // 输出提示信息 System.out.println("转账成功！"); }运行程序前向数据库中插入两条记录，我们使用终端或者sqlyog连接数据库，我这里以Mac的终端为例mysql -u db_javaee -p注意这里，我们用的不再是root账户，而是之前创建的db_javaee输入密码dbjavaeepassword，如果你之前设置的与我不一样这里换成你的密码输入密码的时候是不显示密码内容的，敲击回车，如果界面跟我下面差不多就说明成功了后面来写SQL语句，首先把数据库切换到db_javaee下面USE db_javaee;如果出现Database changed提示则表示成功。我们先来查看下之前创建的Account表SELECT * FROM account;这是我的表里面的内容，你的应该与我不太一样，不过不影响接下来的操作。我们使用一下语句来插入两条数据INSERT INTO account(id,username,balance) VALUES(17,'zhangsan',100); INSERT INTO account(id,username,balance) VALUES(18,'lisi',200);成功的话会有提示来看下目前表里的数据SELECT * FROM account;接下来我们回到Eclipse IDE，运行我们的代码运行结果如下不出意外的话该出意外了，注意看左边的transTest()方法竟然报错，那是正常的，因为我们public void transfer(String outUser, String inUser, Double money);代码写了int i = 1/0;这种语句来模拟报错。我们到数据库中查看数据SELECT * FROM account;很显然数据没有啥变化，我们将transfer方法中的int i = 1/0;删掉试试。注意方法名称，别搞错了。@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false) public void transfer(String outUser, String inUser, Double money) { // 收款时，收款用户的余额=现有余额+所汇金额 this.jdbcTemplate.update("update account set balance = balance +? " + "where username = ?",money, inUser); // 模拟系统运行时的突发性问题 // int i = 1/0; // 汇款时，汇款用户的余额=现有余额-所汇金额 this.jdbcTemplate.update("update account set balance = balance-? " + "where username = ?",money, outUser); }运行一下，发现控制台输出转账成功！。回到数据库查看下SELECT * FROM account;下图是操作前后两次的对比，上面是转账成功之前的，后面是转账成功之后的。数据显然是发生变化了！问题到了这里，你可能会有疑问，敲了这么多的代码，我们还不断地进行测试、查看数据库，到底是想干什么。@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false) public void transfer(String outUser, String inUser, Double money) { // 收款时，收款用户的余额=现有余额+所汇金额 this.jdbcTemplate.update("update account set balance = balance +? " + "where username = ?",money, inUser); // 模拟系统运行时的突发性问题 int i = 1/0; // 汇款时，汇款用户的余额=现有余额-所汇金额 this.jdbcTemplate.update("update account set balance = balance-? " + "where username = ?",money, outUser); }我们看看上方的代码。在前面提到过事务的特性是原子的，也就是不可分割的。倘若我们没有事务，当this.jdbcTemplate.update(...);这条语句执行的时候，数据库就已经更新了，而int i = 1/0;却出现了一个突发性问题，按理来说我们转账transfer这一方法不应该成立才对，而数据库中的记录的确发生了变动。如果你是一个恶意用户，然后你发现了这一漏洞，向银行存款，每次存款失败代码异常银行都退回你钱，然后你不断重复这个操作就可以使你的账户余额不断增加，然而你实际上并没有存一分钱进去，这显然是不合理的！然而我们有了事务，就不会发生这种情况了，因为对于transfer这一操作是不可分割成小操作的，也就是说对于方法里面的东西要么全成功，要么全失败回滚，不可能出现部分成功部分失败这种情况。结束Spring初步这一部分就到此为止了，你会发现对于数据库操作在代码里嵌入SQL语句是很麻烦而且不可靠的，如果不做好过滤，用户提交一段包含SQL语句的字符串就有可能利用你的权限操作数据库，还会有安全问题，所以接下来我们将学习MyBatis这个框架。
Spring JDBC概念Spring的JDBC模块负责数据库资源管理和错误处理，简化了开发人员对数据库的操作。Spring JDBC通过配置数据源和JDBC模板来配置。针对数据库操作，Spring框架提供了JdbcTemplate类，它是Spring框架数据抽象层的基础，是Spring JDBC的核心类。JdbcAccessor提供了访问数据库使用的公共属性，例如Data Source，DataSource主要功能是获取数据库连接，还可以引入对数据库连接的缓冲池和分布式事务的支持；JdbcOperations定义了可以使用的操作集合，例如增删改查（CURD）。Spring JDBC模块由4个包组成：下面我们真正来用一下。Spring JDBC实践数据库创建首先我们在MySQL中创建一个数据库，由于我是Mac环境，所以我直接在终端中执行SQL语句来完成。如果你没有修改过密码，执行以下命令mysql -uroot如果修改过密码请执行以下命令，根据提示输入密码，输入的时候看不见mysql -u root -p如果你是Windows可以通过sqlyog连接。以下部分通用：执行SQL语句创建数据库CREATE DATABASE db_javaee;我们接下来为这个数据库创建一个单独的用户来管理它，用户名为db_javaee，密码是dbjavaeepassword。CREATE USER 'db_javaee'@'localhost' IDENTIFIED BY 'dbjavaeepassword';这里有必要说明下，如果数据库与你开发环境在同一台机器上，这里可写'localhost'，如果是远程的数据库要改为'%'，即（以下同理）CREATE USER 'db_javaee'@'%' IDENTIFIED BY 'dbjavaeepassword';给这个用户授予权限，如果是远程别忘了改成'%'GRANT ALL ON db_javaee.* TO 'db_javaee'@'localhost';刷新权限flush privileges;这里有个问题得注意下，如果你的MySQL版本是5.7到这里就没啥问题了。如果是MySQL8.0以及更高，则加密算法由原来的sha256_password改成了caching_sha2_password，导致连接新版本数据库报错。新版本用户需要追加ALERT USER 'db_javaee'@'localhost' IDENTIFIED WITH mysql_native_password by 'dbjavaeepassword';如果你root用户登陆不上也有可能是因为这个原因，这时候你就要像Mac用户一样到终端里连接，并将此处的db_javaee改为root才能在旧版sqlyog里或者navcat里连接。项目创建我们还是创建一个Maven项目，名称为top.cairbin.test3，然后在pom.xml里添加依赖包<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.5.RELEASE</version> <scope>test</scope> </dependency>注意这里的mysql-connector-java请用较新的，我之前用5.1.47会报错。同样创建与src平级的resources目录并use as source folder，在里面创建app.xml（跟我们之前的AppCtx.xml一样，名称不固定随你便，不过在main中的必须与这里的文件名一致），并写入如下内容<?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="top.cairbin.test3" /> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/db_javaee" /> <property name="username" value="db_javaee" /> <property name="password" value="dbjavaeepassword" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </beans>如果你数据库用户名和密码设置与我不一样，记得修改这里的value属性。在App.java中测试package top.cairbin.test3; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; public class App { public static void main( String[] args ) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml"); // 获取JdbcTemplate实例 JdbcTemplate jdTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); // 使用execute()方法执行SQL语句，创建用户账户管理表account jdTemplate.execute("create table account(" + "id int primary key auto_increment," + "username varchar(50)," + "balance double)"); System.out.println("账户表account创建成功！"); } }点击运行按钮后就可以看到表创建成功了编写测试我们创建一个Account测试类package top.cairbin.test3; public class Account { private int id; // 账户id private String username; // 用户名 private double balance; // 账户余额 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Double getBalance() { return balance; } public void setBalance(Double balance) { this.balance = balance; } public String toString() { return "Account [id=" + id + ", " + "username=" + username + ", balance=" + balance + "]"; } }再建立一个IAccountDao接口package top.cairbin.test3; import java.util.List; public interface IAccountDao { // 添加 public int addAccount(Account account); // 更新 public int updateAccount(Account account); // 删除 public int deleteAccount(int id); // 通过id查询 public Account findAccountById(int id); // 查询所有账户 public List<Account> findAllAccount(); }然后定义AccountDao类实现这个接口package top.cairbin.test3; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; @Repository("accountDao") public class AccountDao implements IAccountDao { // 声明JdbcTemplate属性并添加注解 @Autowired private JdbcTemplate jdbcTemplate; // 添加账户 public int addAccount(Account account) { // 定义SQL String sql = "insert into account(username,balance) value(?,?)"; // 定义数组来存放SQL语句中的参数 Object[] obj = new Object[] { account.getUsername(), account.getBalance() }; // 执行添加操作，返回的是受SQL语句影响的记录条数 int num = this.jdbcTemplate.update(sql, obj); return num; } // 更新账户 public int updateAccount(Account account) { // 定义SQL String sql = "update account set username=?,balance=? where id = ?"; // 定义数组来存放SQL语句中的参数 Object[] params = new Object[] { account.getUsername(), account.getBalance(), account.getId() }; // 执行添加操作，返回的是受SQL语句影响的记录条数 int num = this.jdbcTemplate.update(sql, params); return num; } // 删除账户 public int deleteAccount(int id) { // 定义SQL String sql = "delete from account where id = ? "; // 执行添加操作，返回的是受SQL语句影响的记录条数 int num = this.jdbcTemplate.update(sql, id); return num; } // 通过id查询账户数据信息 public Account findAccountById(int id) { //定义SQL语句 String sql = "select * from account where id = ?"; // 创建一个新的BeanPropertyRowMapper对象 RowMapper<Account> rowMapper = new BeanPropertyRowMapper<Account>(Account.class); // 将id绑定到SQL语句中，并通过RowMapper返回一个Object类型的单行记录 return this.jdbcTemplate.queryForObject(sql, rowMapper, id); } // 查询所有账户信息 public List<Account> findAllAccount() { // 定义SQL语句 String sql = "select * from account"; // 创建一个新的BeanPropertyRowMapper对象 RowMapper<Account> rowMapper = new BeanPropertyRowMapper<Account>(Account.class); // 执行静态的SQL查询，并通过RowMapper返回结果 return this.jdbcTemplate.query(sql, rowMapper); } }创建测试类SpringJdbcTest，编写测试单元package top.cairbin.test3; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; public class SpringJdbcTest { @Test public void mainTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml"); // 获取JdbcTemplate实例 JdbcTemplate jdTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); // 使用execute()方法执行SQL语句，创建用户账户管理表account jdTemplate.execute("create table account(" + "id int primary key auto_increment," + "username varchar(50)," + "balance double)"); System.out.println("账户表account创建成功！"); } @Test public void addAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml"); // 获取AccountDao实例 IAccountDao accountDao = (IAccountDao) applicationContext.getBean("accountDao"); // 创建Account对象，并向Account对象中添加数据 Account account = new Account(); account.setUsername("tom"); account.setBalance(1000.00); // 执行addAccount()方法，并获取返回结果 int num = accountDao.addAccount(account); if (num > 0) { System.out.println("成功插入了" + num + "条数据！"); } else { System.out.println("插入操作执行失败！"); } } @Test public void updateAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取AccountDao实例 IAccountDao accountDao = (IAccountDao) applicationContext.getBean("accountDao"); // 创建Account对象，并向Account对象中添加数据 Account account = new Account(); account.setId(1); account.setUsername("tom"); account.setBalance(2000.00); // 执行updateAccount()方法，并获取返回结果 int num = accountDao.updateAccount(account); if (num > 0) { System.out.println("成功修改了" + num + "条数据！"); } else { System.out.println("修改操作执行失败！"); } } @Test public void deleteAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml"); // 获取AccountDao实例 IAccountDao accountDao = (IAccountDao) applicationContext.getBean("accountDao"); // 执行deleteAccount()方法，并获取返回结果 int num = accountDao.deleteAccount(1); if (num > 0) { System.out.println("成功删除了" + num + "条数据！"); } else { System.out.println("删除操作执行失败！"); } } }在Eclipse中src/main/java使用@Test注解需要进行配置：右键项目-> properties -> Java Build Path -> Libraries -> Add Library -> JUint 添加完成后导入包import java.util.List;就能用了。点击运行，我这里已经测试了多次了，结果可能跟你的不一样从图中左侧可以看到两个方法成功了，两个方法失败了。失败的方法图标又个红色的叉号。我们再创建一个JdbcTemplateTest类，测试下查询方法package top.cairbin.test3; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; public class JdbcTemplateTest { @Test public void findAccountByIdTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml"); // 获取AccountDao实例 IAccountDao accountDao = (IAccountDao) applicationContext.getBean("accountDao"); // 执行findAccountById()方法 Account account = accountDao.findAccountById(1); System.out.println(account); } @Test public void findAllAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml"); // 获取AccountDao实例 IAccountDao accountDao = (IAccountDao) applicationContext.getBean("accountDao"); // 执行findAllAccount()方法,获取Account对象的集合 List<Account> account = accountDao.findAllAccount(); // 循环输出集合中的对象 for (Account act : account) { System.out.println(act); } } }关于SpringTest在Spring中使用Junit测试，每一个测试方法都会初始化Spring容器，导致容器被初始化多次，需要通过ctx.getBean()获取测试Bean。SpringTest可以解决上面的问题，通过在类之上添加注解即可@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:applicationContext.xml")这里的Junit版本为4.12，需要修改pom.xml。此处不对该功能过多解释。