目錄
前言
SSM架構
一、Spring
1.1 Spring介紹
1.2 Spring優點
1.3 Spring項目
1.4 Spring子產品
二、 SpringMVC重構登入注冊業務
2.1導入Spring和SpringMVC依賴
2.2 配置web.xml
2.3 重構登入的Controller
2.4 重構注冊的Controller
三、MVC項目結構
3.1标準的MVC項目結構:
3.2 項目結構建構
四、Spring架構IOC
4.1 Spring容器管理對象
4.2依賴注入方式
4.2.1構造器注入(配置注入)
4.2.2 Set注入
4.2.3 自動注入(注解版)
4.3注冊背景邏輯代碼
五、MyBatis架構
5.1 外部配置檔案+c3p0+QueryRunner
5.2 MyBatis基礎知識
5.2 配置MyBatis
5.2.1在POM.Xml檔案中導入MyBatis依賴
5.2.2在Spring容器中配置資料源和屬性
5.2.3建立MyBatis的Mapper檔案
5.2.4建立MyBatis會話工廠和掃描器
前言
servlet的一大缺點就是隻能識别doGet 和doPost等7種請求,具體業務上的請求無法識别,通過在前端加一個隐藏元件(表單送出的時候傳給背景參數),給背景傳目前需要調用什麼方法,或者通過url給後端傳一個參數action或者method來告訴servlet調用什麼方法,以此來區分相同業務塊的不同請求;
Servlet的方式屬于弱規範,由于程式員的命名錯誤或者其他原因就會導緻404找不到的錯誤,能不能直接通過路徑直接就告訴web伺服器我們要處理請求的具體方法是哪個servlet的哪個方法,因為url路徑是存在層級關系的,對應于類和方法的層級關系
于是市面上就出現了springMVC這個架構,對servlet進行更新
SSM架構

以前是SSH ->spring+struts+hibernate
SpringMVC和Spring是無縫連接配接的,一家公司的産品,性能更好,是以SpringMVC替代了struts
SpringMVC解決了前言中所說的sevlet的痛點問題,早年用的是配置版,現在是用的注解版(通過java的注解方式屏蔽掉了很多共同的代碼邏輯)
MyBatis:進行OR映射
一、Spring
1.1 Spring介紹
Spring是一個基于控制反轉Ioc和面向切面程式設計Aop的輕量級開源架構!它是由一個叫做Rod Johnson的音樂學博士在2002年提出并建立的,他提出了著名的輪子理論,就是:不要重複發明輪子。Spring之是以叫做Spring,就是它期望給軟體行業帶來一個春天!讓我們的開發變得更加簡單更加快速。它使得現有的技術變的更加容易使用,它本身是一個大雜燴,整合了現有的技術架構。
具體描述:
輕量級:Spring 是非侵入性的 – 基于 Spring 開發的應用中的對象可以不依賴于Spring 的 API
依賴注入(DI—-dependency injection、IOC)
面向切面程式設計(AOP—aspect oriented programming)
容器:Spring是一個容器,因為它包含并且管理應用對象的生命周期
架構:Spring實作了使用簡單的元件配置組合成一個複雜的應用,在Spring中可以使用XML和Java注解組合這些對象
一站式:在IOC和AOP的基礎上可以整合各種企業應用的開源架構和優秀的第三方類庫(實際上Spring自身也提供了展現層的SpringMVC和持久層的Spring JDBC)
1.2Spring優點
低侵入式設計,代碼污染極低。
獨立于各種應用伺服器,基于Spring架構的應用,可以真正實作Write Once,Run Anywhere的承諾。
Spring的DI機制降低了業務對象替換的複雜性,提高了元件之間的解耦。
Spring的AOP支援允許将一些通用任務如安全、事務、日志等進行集中式管理,進而提供了更好的複用,橫向地攔截資料,對資料進行過濾。
Spring的ORM和DAO提供了與第三方持久層架構的良好整合,并簡化了底層的資料庫通路。
Spring并不強制應用完全依賴于Spring,開發者可自由選用Spring架構的部分或全部。
1.3Spring項目
Springboot
對三大架構的高度高度整合
Springcloud
1.4Spring子產品
Spring架構由7個子產品組成,核心子產品定義了建立、配置和管理bean的方式
Spring架構
最重要的Spring業務處理邏輯
DisparcherServlet攔截用戶端所有的請求,前端不需要再通路具體的.jsp檔案,直接通路背景服務,DisparcherServlet會主動去尋找對應的Controller(類似servlet),最後響應還給DisparcherServlet後進行視圖映射,判斷傳回給前端的資料是靜态資料還是頁面
我們要處理的部分隻有:(1)前端業務請求 (2)背景的Controller,處理完以後封裝給ModelAndView就行了,其他的就不用管了
二、 SpringMVC重構登入注冊業務
2.1導入Spring和SpringMVC依賴
在pom.xml中引入Spring相關的dependency,讓Maven下載下傳相應的jar包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ysu</groupId> <artifactId>demo4</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>demo4 Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <spring-vison>5.1.2.RELEASE</spring-vison> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency> </dependencies> <build> <finalName>demo4</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project> |
引入的比較多,隻有一部分是前端會用到的,其中Spring是主流架構,接下來的項目中會用到,Spring全家桶互相之間是會有依賴的,是以就一次性全部引入了
導入依賴後表示項目具有使用Spring的能力了
2.2 配置web.xml
Web.xml是tomcat主動加載的配置檔案,要使用DisparcherServlet肯定要讓tomcat進行加載,web.xml内的結點聲明是有先後順序的,重要的需要先加載
原來的使用自己寫的servlet
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>loginServlet</servlet-name> <servlet-class>com.ysu.controller.LoginController</servlet-class> </servlet> <servlet-mapping> <servlet-name>loginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <servlet> <servlet-name>registerServlet</servlet-name> <servlet-class>com.ysu.controller.RegisterController</servlet-class> </servlet> <servlet-mapping> <servlet-name>registerServlet</servlet-name> <url-pattern>/register</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>login.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> |
現在我們不需要用自己的servlet了,直接用DisparcherServlet
Spring和SpringMVC的配置檔案.xml需要放在項目的資源檔案resources中,在web.xml中告訴web伺服器去哪裡加載這個配置檔案
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Archetype Created Web Application</display-name> <!-- 優先配置Spring架構,初始化上下文,然後才能加載配置檔案--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 加載Spring的配置檔案--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring\spring-context.xml</param-value> </context-param> <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring\spring-mvc-context.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring-mvc</servlet-name> <!-- 架構攔截所有的請求(界面或者靜态請求),然後再進行分流--> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>RequestContextFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>RequestContextFilter</filter-name> <!-- 攔截所有的請求,設定編碼格式為UTF-8--> <url-pattern>/</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>login.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> |
Spring的配置檔案:spring-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--打開包掃描器,掃描com.ysu中哪些類有@Controller或者@RestController,将這些類作為控制器執行個體化到IOC(spring容器)中--> <context:component-scan base-package="com.ysu"/> <!--開啟注解驅動,才能使@Controller等正常使用--> <mvc:annotation-driven/> </beans> |
SpringMVC的配置:Spring-mvc-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--剔除前段發送的請求中的靜态資源,傳回給web容器進行處理--> <mvc:default-servlet-handler/> <!-- 配置攔截處理請求函數的傳回字元串轉化為.jsp頁面--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> </beans> |
Spring-context.xml配置檔案是可以派生出Spring-mvc-context.xml配置的,如果不想區分Spring配置和Spring前端業務,可以把這倆配置放在一起,都放在Spring-context.xml檔案中,是以後面的Mybatis的配置就是直接放在Spring的配置檔案中的
2.3 重構登入的Controller
原本的servlet處理邏輯:
package com.ysu.controller; //import com.sun.deploy.util.StringUtils; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import org.apache.commons.lang3.StringUtils; public class LoginController extends HttpServlet { @Override //加上這個表示參數寫多了或者寫少了不會出錯 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { //接受浏覽器的GET請求 this.doPost(request,response); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //接收前端送出的資料 String username=request.getParameter("username"); String password=request.getParameter("password"); //背景正常判空判null操作 //這種代碼如果前端的參數很多就會很長,而且這種代碼會經常寫,由apache commons來解決,極大地簡化了代碼 //if(null==username || username.equals("") || null==password ||password.equals("")) if(StringUtils.isEmpty(username)||StringUtils.isEmpty(password)) { //一般背景是傳回狀态,不要一會兒傳回字元串,一會兒傳回狀态 printView(response,"使用者名或密碼為空"); } else { printView(response,"登入成功,等待跳轉..."); } // System.out.println("使用者名:"+username+"密碼:"+password); //???表示第一次轉碼錯誤,菱形什麼的奇怪符合是轉碼的轉碼的錯誤,亂碼的亂碼 } public void printView(HttpServletResponse response, String msg) throws IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out=response.getWriter(); out.println("<html>"); out.println(" <head><title>登入</title></head>"); out.println(" <body>"); out.println(" <h1>"+msg+"<h1>"); out.println(" </body>"); out.println("</html>"); //重新整理輸出流,避免資料沒有發送給浏覽器 out.flush(); out.close(); } } |
Spring架構的登入界面Controller:
package com.ysu.controller; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller //猴頭Controller,建立目前類的對象并存在IOC(Spring容器中),如果不加controller表示這就是一個普通的java類 //@RequestMapping("/login") //類級别的攔截,完成servlet-mapping的工作,表示将攔截什麼請求 //但是隻有類級别的攔截還是隻能對應一個servlet,不能攔截具體的方法實作不同業務處理 //可以沒有類攔截,直接方法級攔截 public class LoginController extends HttpServlet { //@RequestMapping("/a") //方法級别的攔截,login/a映射到此 @RequestMapping("/login") //如果傳回String類型就預設是對應于.jsp頁面 public String doLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //接收前端送出的資料 String username=request.getParameter("username"); String password=request.getParameter("password"); String message =""; //背景正常判空判null操作 //這種代碼如果前端的參數很多就會很長,而且這種代碼會經常寫,由apache commons來解決,極大地簡化了代碼 //if(null==username || username.equals("") || null==password ||password.equals("")) if(StringUtils.isEmpty(username)||StringUtils.isEmpty(password)) { //一般背景是傳回狀态,不要一會兒傳回字元串,一會兒傳回狀态 message="使用者名或密碼為空"; } else { message="登入成功,等待跳轉..."; } //給request設定屬性,傳回的界面是請求轉發過去的 request.setAttribute("mag",message); //return new ModelAndView("index"); return "index"; } } |
2.4 重構注冊的Controller
原來的servlet處理注冊請求
package com.ysu.controller; import com.ysu.domain.User; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class RegisterController extends HttpServlet { private static final String REGISTER_METHOD ="register"; //重名方法 private static final String IS_SAME_NAME_METHOD="isSameName"; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doPost(request,response); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String method=request.getParameter("method"); PrintWriter out = response.getWriter(); //進行注冊校驗 if((StringUtils.equals(REGISTER_METHOD,method))) { boolean registerState=doRegister(request); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //站内請求轉發 if(registerState) request.getRequestDispatcher("/login.jsp").forward(request,response); } //進行重名校驗 else if(StringUtils.equals(IS_SAME_NAME_METHOD,method)) { String username=request.getParameter("username"); boolean isSameName=isSameName(username); out.print(isSameName); out.flush(); } } private boolean doRegister(HttpServletRequest request) { User newUser=new User(); //特殊處理newUser為null的情況,如果前端沒有傳過來參數,資料庫直接錄入空串資訊 newUser.setUsername(ObjectUtils.identityToString(request.getParameter("username"))); newUser.setPassword(ObjectUtils.identityToString(request.getParameter("password"))); newUser.setEmail(ObjectUtils.identityToString(request.getParameter("email"))); newUser.setTel(ObjectUtils.identityToString(request.getParameter("tel"))); //使用者資訊入庫 return true; } private boolean isSameName(String username) { //這樣寫防止出現空指針的調用 return "admin".equals(username); } } |
Spring重構後:
前端表單送出:action=”register/doRegister”
Ajax異步請求路徑:"register/doVerifySameName”
Spring的傳回值是請求轉發的方式跳轉到頁面,項目的上下文路徑不變,可以給request設定屬性,以此來進行傳值
package com.ysu.controller; import com.ysu.domain.User; import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //@Controller //定義了傳回值隻能是頁面,不能是資料,但是可以用ModelAndView攜帶資料,比較麻煩,也可以再spring-mvc-context.xml檔案中進行配置,配置傳回的字元串都轉化為界面 @RestController //這個就可以傳回頁面或者資料 @RequestMapping("/register") public class RegisterController { @RequestMapping("/doRegister") //每次都傳回一個ModelAndView,傳回一個jsp頁面 public ModelAndView doRegister(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); //進行注冊校驗 User newUser=new User(); //特殊處理newUser為null的情況,如果前端沒有傳過來參數,資料庫直接錄入空串資訊 newUser.setUsername(ObjectUtils.identityToString(request.getParameter("username"))); newUser.setPassword(ObjectUtils.identityToString(request.getParameter("password"))); newUser.setEmail(ObjectUtils.identityToString(request.getParameter("email"))); newUser.setTel(ObjectUtils.identityToString(request.getParameter("tel"))); //使用者資訊入庫 //http://localhost:8080/demo4_war_exploded/register/login.jsp因為是請求轉發,會跳轉到這個路徑(錯誤路徑) return new ModelAndView("login"); //return "login"; //跳轉後資源是不存在的,隻有基本的html界面,靜态資源因為是相對路徑,加載不出js、css等資源, //是以要在前端頁面中設定所有的相對路徑是動态擷取上下文路徑 } @RequestMapping("/doSameName") @ResponseBody //聲明目前接口可以傳回資料 private String doSameName(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //objeftUtils防止得到的資料為空 String username=ObjectUtils.toString(request.getParameter("username")); // String username=request.getParameter("username"); //如果沒有加ResponseBody,往前端傳回的是true.jsp或者false.jsp,預設是去找一個頁面 return "admin".equals(username)+""; } } |
三、MVC項目結構
3.1标準的MVC項目結構:
一定要注意不要直接在Controller層注入Services層,亂了
3.2 項目結構建構
Controller層和其以下的層都是逐層調用的,并且采用面向接口程式設計,執行個體化對象後調用相應的處理方法
代碼撰寫中,先寫類之間耦合的僞代碼,然後直接alt+enter利用idea直接生成相應的接口和類,每層示例化的對象都是接口引用指向相應的執行個體化類對象
Controller層注入Model依賴:RegisterController類中
//I開頭表示這個是個接口(innerface),在目前類中隻會有一個接口,一個Model,是以直接取名為model private IRegisterModel model=new RegisterModelImpl(); //建立一個實作IRegisterModel接口的類的對象 |
Model層注入Service依賴:RegisterModelImpl類中
private IRegisterService service =new RegisterServiceImpl(); |
Service層注入Dao依賴:RegisterServiceImpl類中
private IRegisterDao dao =new RegisterDaoImpl(); |
最終的項目結構:
采用直接在類中new對象的缺點:
1.正确的方式應該是在構造器中進行執行個體化,比較麻煩
2.主動建立導緻強耦合,new了一個對象就要維護該對象,但是目前類隻想用它的方法,不想去維護該對象
四、Spring架構IOC
解決上面的痛點問題,Spring架構提供一個平台來建立、維護和管理項目中的耦合對象,解除了類之間的耦合性,實作了控制翻轉、依賴注入的功能。
4.1 Spring容器管理對象
管理類之間具有耦合性的所有對象,包括最頂層的Controller對象
早期的配置管理對象:
容器自動讀取.xml配置檔案,并建立對應id值和類的對象
在Spring-context.xml中進行配置,并且配置的時候一定要保證類中有相應的構造器,否則會報錯
<bean id="controller" class="com.ysu.controller.RegisterController"> <constructor-arg ref="model"/> </bean> <bean id="model" class="com.ysu.model.impl.RegisterModelImpl"> <constructor-arg ref="service"/> </bean> <bean id="service" class="com.ysu.service.impl.RegisterServiceImpl"> <constructor-arg ref="dao"/> </bean> <bean id="dao" class="com.ysu.dao.impl.UserDaoImpl"/> |
xml配置檔案的可讀性很高,但是如果類很多,依賴對象很多,檔案就會很大,維護起來也很麻煩
注解版管理對象:
首先Controller層的@Controller就是告訴容器幫我們建立和維護管理這個類的一個對象,其他的每一層也都有對應的注解來提示容器建立和管理該類的對象
Model層:@Component
Service層:@Service (父接口是@Component)
Dao層:@Repository (父接口是@Component)
4.2依賴注入方式
4.2.1構造器注入(配置注入)
以前所有的對象都在Spring-mvc-context.xml中的bean标簽中進行管理(需要顯示聲明)
早期Spring提供構造器注入方式,通過構造器傳入一個依賴
private IUserDao dao; public RegisterServiceImpl(IUserDao dao) { this.dao=dao; } |
4.2.2 Set注入
每個類中先聲明一個引用,然後通過set映射函數給該引入指派
private IUserDao dao; public void setDao(IUserDao dao) { this.dao=dao; } |
上面兩種注入方式都是早期的依賴注入方式,實作了依賴的外部注入的特性,但是代碼寫起來繁瑣,如果依賴對象很多就要寫很多這種函數
4.2.3 自動注入(注解版)
直接通過@Autowired這一個注解自動注入,容器幫我們去找這個類型比對的對象,反射機制
@Autowired private IUserDao dao; |
4.3注冊背景邏輯代碼
Controller層:RegisterController類
package com.ysu.controller; import com.ysu.domain.User; import com.ysu.model.IRegisterModel; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //@Controller //定義了傳回值隻能是頁面,不能是資料,但是可以用ModelAndView攜帶資料,比較麻煩 @RestController //這個就可以傳回頁面或者資料 @RequestMapping("/register") public class RegisterController { //注入model依賴 @Autowired private IRegisterModel model; @RequestMapping("/doRegister") //每次都傳回一個ModelAndView,傳回一個jsp頁面 public String doRegister(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); //進行注冊校驗 User newUser=new User(); //特殊處理newUser為null的情況,如果前端沒有傳過來參數,資料庫直接錄入空串資訊 newUser.setUsername(ObjectUtils.identityToString(request.getParameter("username"))); newUser.setPassword(ObjectUtils.identityToString(request.getParameter("password"))); newUser.setEmail(ObjectUtils.identityToString(request.getParameter("email"))); newUser.setTel(ObjectUtils.identityToString(request.getParameter("tel"))); //使用者資訊入庫 //傳對象資料給service,不要直接傳request過去 boolean registerState =model.verifyRegister(newUser); request.setAttribute("regState",registerState); //return String.valueOf(registerState); return "/login.jsp"; } @RequestMapping("/doVerifySameName") @ResponseBody //聲明目前接口可以傳回資料 private String doSameName(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //objeftUtils防止得到的資料為空 String username=ObjectUtils.toString(request.getParameter("username")); // String username=request.getParameter("username"); //如果沒有加ResponseBody,往前端傳回的是true.jsp或者false.jsp,預設是去找一個頁面 return "admin".equals(username)+""; } } |
Model層:RegisterModelImpl類
package com.ysu.model.impl; import com.ysu.domain.User; import com.ysu.model.IRegisterModel; import com.ysu.service.IRegisterService; import com.ysu.service.impl.RegisterServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class RegisterModelImpl implements IRegisterModel { // private IRegisterService service =new RegisterServiceImpl(); @Autowired private IRegisterService service; @Override public boolean verifyRegister(User newUser) { return service.verifyRegister(newUser); } } |
Service層:RegisterServiceImpl類
package com.ysu.service.impl; import com.ysu.dao.IUserDao; import com.ysu.domain.User; import com.ysu.service.IRegisterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class RegisterServiceImpl implements IRegisterService { @Autowired private IUserDao dao; @Override public boolean verifyRegister(User newUser) { //資料入庫 dao.insertUser(newUser); return true; } } |
Dao層:UserDaoImpl類
package com.ysu.dao.impl; import com.ysu.dao.IUserDao; import com.ysu.domain.User; import org.springframework.stereotype.Repository; import java.sql.*; import java.util.ArrayList; import java.util.List; @Repository public class UserDaoImpl implements IUserDao { @Override public void insertUser(User user) { } } |
五、MyBatis架構
直接使用javaAPI操作資料庫的痛點:
- 資料庫連結需要統一管理
- 屬性寫死導緻代碼反複被修改(資料庫、驅動、資料庫使用者名、密碼)
- 資料庫表字段不能與實體對象自動封裝
5.1 外部配置檔案+c3p0+QueryRunner
首先導入需要用到的配置依賴
<dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> |
解決第一個痛點:通過配置檔案來管理資料庫屬性
Java提供的配置檔案:mysql.properties
url = jdbc:mysql://localhost:3306/ysu-demo?useUnicode=true &characterEncoding=utf8&serverTimezone=GMT jdbcDriver = com.mysql.cj.jdbc.Driver userName = root password = lcf2773743863 initialPoolSize = 10 //資料庫連結池裡最大連結的個數 |
在Dao層讀取這個檔案,并擷取裡面的内容,加載得到的Properties類是繼承自HashTable的,通過get方法擷取屬性值
Properties props=new Properties(); //動态擷取屬性檔案的路徑 props.load(new FileReader(new File(MYSQL_PROPERTIES))); |
解決第二個痛點:通過C3P0管理資料庫的連接配接池
資料庫連接配接池的基本思想就是為資料庫連接配接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接配接,當需要建立資料庫連接配接時,隻需從“緩沖池”中取出一個,使用完畢之後再放回去。我們可以通過設定連接配接池最大連接配接數來防止系統無盡的與資料庫連接配接。擷取一個連接配接,系統要在背後做很多消耗資源的事情,大多時候,建立連接配接的時間比執行sql語句的時間還要長。使用者每次請求都需要向資料庫獲得連結,而資料庫建立連接配接通常需要消耗相對較大的資源,建立時間也較長。
//c3p0連接配接池(管理連接配接) ComboPooledDataSource dataSource=new ComboPooledDataSource(); dataSource.setDriverClass(ObjectUtils.toString(props.get("jdbcDriver"))); dataSource.setUser(ObjectUtils.toString(props.get("userName"))); dataSource.setPassword(ObjectUtils.toString(props.get("password"))); dataSource.setJdbcUrl(ObjectUtils.toString(props.get("url"))); dataSource.setInitialPoolSize(NumberUtils.toInt(ObjectUtils.toString(props.get("initialPoolSize")))); |
解決第三個痛點:通過QueryRunner向資料庫發起增删改查操作
//通過這個向資料庫發起增删改查操作 QueryRunner qr=new QueryRunner(dataSource); //傳入類類型,通過反射自動進行封裝 String srcSql="INSERT INTO tbl_user(userName,password,email,tel) VALUES('%s','%s','%s','%s')"; String destSql=String.format(srcSql,user.getUsername(),user.getPassword(),user.getEmail(),user.getTel()); qr.insert(destSql,new BeanHandler<User>(User.class)); List<User>userList= qr.query("SELECT * FROM tbl_user",new BeanListHandler<User>(User.class)); for(User currentUser :userList) { System.out.println(currentUser.getUsername()+""+currentUser.getPassword()); } |
DAO層優化後的完整代碼:
package com.ysu.dao.impl; import com.mchange.v2.c3p0.ComboPooledDataSource; import com.ysu.dao.IUserDao; import com.ysu.domain.User; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.stereotype.Repository; import java.beans.PropertyVetoException; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.sql.*; import java.util.List; import java.util.Properties; @Repository public class UserDaoImpl implements IUserDao { //系統的根目錄 private static final String ROOT_PATH=UserDaoImpl.class.getResource("/").getPath(); //資料庫配置檔案 private static final String MYSQL_PROPERTIES=ROOT_PATH+"mysql.properties"; @Override public void insertUser(User user) { try { Properties props=new Properties(); //動态擷取屬性檔案的路徑 props.load(new FileReader(new File(MYSQL_PROPERTIES))); //c3p0連接配接池(管理連接配接) ComboPooledDataSource dataSource=new ComboPooledDataSource(); dataSource.setDriverClass(ObjectUtils.toString(props.get("jdbcDriver"))); dataSource.setUser(ObjectUtils.toString(props.get("userName"))); dataSource.setPassword(ObjectUtils.toString(props.get("password"))); dataSource.setJdbcUrl(ObjectUtils.toString(props.get("url"))); dataSource.setInitialPoolSize(NumberUtils.toInt(ObjectUtils.toString(props.get("initialPoolSize")))); //通過這個向資料庫發起增删改查操作 QueryRunner qr=new QueryRunner(dataSource); //傳入類類型,通過反射自動進行封裝 String srcSql="INSERT INTO tbl_user(userName,password,email,tel) VALUES('%s','%s','%s','%s')"; String destSql=String.format(srcSql,user.getUsername(),user.getPassword(),user.getEmail(),user.getTel()); qr.insert(destSql,new BeanHandler<User>(User.class)); List<User>userList= qr.query("SELECT * FROM tbl_user",new BeanListHandler<User>(User.class)); for(User currentUser :userList) { System.out.println(currentUser.getUsername()+""+currentUser.getPassword()); } } catch (SQLException | IOException | PropertyVetoException e) { e.printStackTrace(); } } } |
以上三個技術就形成了一個簡單的O/R映射架構,能小程度地做到功能可擴充,代碼不需修改,但是Sql和java代碼還是高度耦合的,比如利用java代碼格式化了sql語句,對sql的維護是弱維護,經常會發生錯誤,如果能把sql語句提取出來進行統一管理會更好,又是一個痛點問題,于是就要引入大型架構MyBatis
5.2 MyBatis基礎知識
Hibernate屬于全自動化,可以通過内部機制生成sql代碼,但是機器生成的sql代碼和我們手寫的不一樣,無法進行優化和維護,代碼不能做到精簡高效;
MyBatis半自動化,程式員主動對sql代碼進行管理和優化
基礎配置:
5.2 配置MyBatis
5.2.1在POM.Xml檔案中導入MyBatis依賴
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency> <!-- 這個是mybatis的核心依賴,上一個是專門用于MyBatis和Spring整合的--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> |
5.2.2在Spring容器中配置資料源和屬性
資料庫參數檔案:mysql.properties
jdbc.url = jdbc:mysql://localhost:3306/ysu-demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT jdbc.jdbcDriver = com.mysql.cj.jdbc.Driver jdbc.userName = root jdbc.password = lcf2773743863 jdbc.initialPoolSize= 10 #注意這兒的名字要取得特殊一點,避免在前面導入的時候出現命名沖突 |
資料源對應的就是前面的c3p0入口類,就是建立一個這個類的對象在Spring容器中管理
Spring-context.xml:
<!--将配置檔案關聯進來,導入項目外的資源檔案到Spring空間--> <context:property-placeholder location="classpath*:mysql.properties"/> <!--配置資料源--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--屬性注入相應的參數--> <property name="driverClass" value="${jdbc.jdbcDriver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.userName}"/> <property name="password" value="${jdbc.password}"/> <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/> </bean> |
5.2.3建立MyBatis的Mapper檔案
檔案映射一個java類,這個類中的主場是SQL語句,可以便利地進行SQL語句編寫和維護
所有的Mapper檔案都必須與持久層的Dao有關聯,能夠通過檔案名進行自動映射,就像setter和getter函數一樣,這樣持久層的實作類就不需要我們自己實作,由MyBatis自動建立,我們就需要提供給他映射關系,接口和Mapper檔案的映射關系
關聯的要求是:Mapper配置檔案名是接口的名字最左側的檔案名小寫
接口:IUserDao Mapper檔案名:iUserDao.xml
iUserDao.xml :
<?xml version="1.0" encoding="UTF-8"?> <!--映射檔案配置 --> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace命名空間,作用:對sql進行分類化管理 --> <!--根據這個命名空間的接口會自動生成一個實作類,跟我們的UserDaoImpl一樣,可能命名不同--> <mapper namespace="com.ysu.dao.IUserDao"> <!-- 裡面含有update、select、query等結點--> <!-- insertUser方法傳入的一個User對象--> <insert id="insertUser" parameterType="com.ysu.domain.User"> INSERT INTO tbl_user(userName,password,email,tel) VALUES(#{userName},#{password},#{email},#{tel}) </insert> <!-- #{userName}是映射找到類中對應的屬性--> <select id="queryUserByName" resultType="com.ysu.domain.User" parameterType="com.ysu.domain.User"> SELECT * FROM tbl_user WHERE userName=#{userName} and password=#{password} </select> </mapper> |
這樣Dao層的實作類就可以删除了
5.2.4建立MyBatis會話工廠和掃描器
Spring-context.xml:
<!--加載MyBatis會話工廠--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--同級的對象必須用ref來關聯--> <property name="dataSource" ref="dataSource"/> <!--加載Mapper檔案,用一個通配符,表示以後加載所有與這個檔案名比對的Mapper檔案--> <property name="mapperLocations" value="classpath*:mybatis/mapper/i*Dao.xml"/> </bean> <!--按類裝配的,不需要寫id值了--> <!--Mapper掃描器,說明去哪兒找需要映射的Java類(Dao實作類)--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--接口Class檔案存放位置--> <property name="basePackage" value="com.ysu.dao"/> <!--關聯會話工廠--> <property name="sqlSessionFactoryBeanName" value="sqlSession"/> </bean> |
配置完成後調試代碼可以看見,Service層的dao引用會指向一個MyBatis建立的代理對象,這個是自動生成的
Spring-context.xml完整代碼:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--打開包掃描器,掃描com.ysu中哪些類有@Controller或者@RestController,将這些類作為控制器執行個體化到IOC(springring器)中--> <context:component-scan base-package="com.ysu"/> <!--開啟注解驅動--> <mvc:annotation-driven/> <!--将配置檔案關聯進來,導入項目外的資源檔案到Spring空間--> <context:property-placeholder location="classpath*:mysql.properties"/> <!--配置資料源--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--屬性注入相應的參數--> <property name="driverClass" value="${jdbc.jdbcDriver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.userName}"/> <property name="password" value="${jdbc.password}"/> <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/> </bean> <!--加載MyBatis會話工廠--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--同級的對象必須用ref來關聯--> <property name="dataSource" ref="dataSource"/> <!--加載Mapper檔案,用一個通配符,表示以後加載所有與這個檔案名比對的Mapper檔案--> <property name="mapperLocations" value="classpath*:mybatis/mapper/i*Dao.xml"/> </bean> <!--按類裝配的,不需要寫id值了--> <!--Mapper掃描器--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--接口Class檔案存放位置--> <property name="basePackage" value="com.ysu.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSession"/> </bean> <!--事務配置--> <!--分頁插件配置或者用page-happer進行分頁--> </beans> |