久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

华清远见-重庆中心-框架个人总结

發布時間:2023/12/3 综合教程 38 生活家
生活随笔 收集整理的這篇文章主要介紹了 华清远见-重庆中心-框架个人总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

框架

一套規范。

實際是他人實現的一系列接口和類的集合。通入導入對應框架的jar文件(maven項目導入對應的依賴),進行適當的配置,就能使用其中的所有內容。

開發者可以省去很多模板代碼,如dao中的CRUD,MVC模式下層與層之間的關聯。只需要集中精力實現項目中的業務邏輯部分

主流框架

Spring、SpringMVC、MyBatis、MyBatisPlus、Hibernate、JPA等。

SSH:最初是Spring+Stucts2+Hibernate組成,之后Stucts2被SpringMVC取代。

SSM:Spring+SpringMVC+MyBatis

新項目使用SpringBoot,早起的SSH項目由于維護成本高,基本不會推翻重做,但會維護一些SSM項目。

無論是SSH還是SSM,Spring、SpringMVC必不可少。從2004年推出至今,依舊是主流框架中不可獲取的一部分。

SPring

一個輕量級開源的Java框架。是一個管理項目中對象的容器,同時也是其他框架的粘合器,目的就是對項目進行解耦。

Spring的核心是IOC控制反轉和AOP面向切面編程

Spring 應用

1.創建一個普通的Maven項目,不選擇模板

2.添加Spring核心依賴

jdk8用5.x版本

<!-- spring-context表示spring核心容器 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.23</version>
</dependency>

3.創建一個Java類

package com.hqyj.spring01;
/*
* 定義一個普通的Java類
* PlainOrdinaryJavaObject pojo  相當于簡化的javabean
* entity  vo  dto  pojo  都是在描述實體類
* */
public class PlainOrdinaryJavaObject {public void fun(){System.out.println("一個PlainOrdinaryJavaObject(普通的Java類)對象");}
}

4.創建Spring配置文件

在resources目錄下,創建一個xml文件,選擇spring config,通常命名為application

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--bean標簽表示,在Spring容器中,注入某個類的對象--><!--class屬性表示要注入哪個類,寫類的全限定名(包名+類名)--><!--id表示給類的對象的名稱--><bean class="com.hqyj.spring01.PlainOrdinaryJavaObject" id="pojo"></bean></beans>

5.創建main方法所在類

解析Spring配置文件,獲取注入的對象。

package com.hqyj.spring01;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {//創建一個自定義類的對象//傳統方式創建對象//PlainOrdinaryJavaObject pojo = new PlainOrdinaryJavaObject();//使用spring容器獲取對象//創建一個用于解析Spring配置文件的對象,參數為配置文件的名稱。實際就是獲取Spring容器ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");//獲取容器中的對象/*只通過名稱獲取,返回值為Object類型,需要轉換Object obj = context.getBean("pojo");PlainOrdinaryJavaObject pojo=(PlainOrdinaryJavaObject) obj;*///使用這種方式,直接用指定類型接收PlainOrdinaryJavaObject pojo = context.getBean("pojo", PlainOrdinaryJavaObject.class);//能成功調用方法,說明Spring管理了該類的對象pojo.fun();//關閉Spring容器context.close();}
}

bean標簽常用屬性

屬性 作用
class 定義類的全限定名
id 定義對象的名稱
lazy-init 是否為懶加載。默認值為false,在解析配置文件時就會創建對象。設置為true表示懶加載,只有在getBean()時才會創建對象。
scope 單例/原型模式。默認值為singleton,表示單例模式,只會創建一個對象。設置為prototype,表示原型模式,每調getBean()就創建一個對象。
init-method 初始化時觸發的方法。在創建完該對象時自動調用的方法。該方法只能是無參方法,該屬性的值只需要寫方法名即可
destory-method 銷毀時觸發的方法。Spring容器關閉時自動調用的方法,該方法只能是無參方法。只有在單例模式下有效。

屬性注入

給某個bean添加屬性的方式有兩種:構造器注入和setter注入

setter注入

這種方式注入屬性時,類中必須要有set方法

在bean標簽中,加入<property></property>標簽,

該標簽的name屬性通常表示該對象的某個屬性名,但實際是setXXX()方法中的XXX單詞。

如有age屬性,但get方法為getNianLing(),name屬性就需要寫成nianLing。

該標簽的value屬性表示給該類中的某個屬性賦值,該屬性的類型為原始類型或String

該標簽的ref屬性表示給該類中除String以外的引用類型屬性賦值,值為Spring容器中另一個bean的id。

<!--注入Car類對象并用set方式注入其屬性-->
<bean class="com.hqyj.spring01.Car" id="c"><!--該屬性是字符串或原始類型,使用value賦值--><property name="brand" value="寶馬"></property><!--name并不是類中是屬性名,而是該屬性對應的getXXX()方法中XXX的名稱--><!--如Car類中有color屬性,但get方法名為getColo(),這里就要寫為colo--><property name="colo" value="白色"></property>
</bean><!--注入Person類對象并用set方式注入其屬性-->
<bean class="com.hqyj.spring01.Person" id="p1"><property name="name" value="王海"></property><property name="age" value="22"></property><!--屬性是引用類型,需要通過ref賦值,值為另外的bean的id ref即references--><property name="car" ref="c"></property>
</bean>

構造方法注入

這種方式注入屬性時,類中必須要有相應的構造方法

在bean標簽中,加入<constructor-arg></constructor-arg>標簽,

該標簽的name屬性表示構造方法的參數名,index屬性表示構造方法的參數索引。

賦值時,原始類型和字符串用value,引用類型用ref。

<!--注入Person類對象并用構造方法注入其屬性-->
<bean class="com.hqyj.spring01.Person" id="p2"><!--constructor-arg表示構造方法參數  name是參數名 index是參數索引--><constructor-arg name="name" value="張明"></constructor-arg><constructor-arg index="1" value="20"></constructor-arg><constructor-arg name="car" ref="c"></constructor-arg>
</bean>

復雜屬性注入

/*
* 定義電影類
* */
public class Movie {//電影名private String movieName;//導演private String director;//時長private int duration;//主演private List<String> playerList;//類型private String movieType;//放映時間,最終格式為yyyy/MM/dd HH:mm:ssprivate String showTime;
}

List類型的屬性

<!--注入Movie對象-->
<bean class="com.hqyj.spring01.Movie" id="movie1"><property name="movieName" value="夏洛特煩惱"></property><property name="director" value="閆非、彭大魔"></property><property name="duration" value="87"></property><!--List類型屬性賦值--><property name="playerList" ><!--使用list標簽--><list><!--如果集合中保存的是引用類型,使用ref標簽--><!--如果集合中保存的是原始類型或字符串,使用value標簽--><value>沈騰</value><value>艾倫</value><value>馬麗</value></list></property><property name="movieType" value="喜劇"></property><property name="showTime" value="2010/06/01 0:0:0"></property>
</bean>

Set類型的屬性

<!--注入PetShop對象-->
<bean class="com.hqyj.spring01.test3.PetShop" id="petShop"><property name="shopName" value="xxx寵物店"></property><property name="petSet"><!--Set類型的屬性--><set><!--如果中保存的是原始類型或字符串,使用value子標簽--><!--如果保存的是引用類型,使用ref子標簽加bean屬性設置對應的id--><ref bean="p1"></ref><ref bean="p2"></ref><ref bean="p3"></ref></set></property>
</bean>

Map類型的屬性

<!--注入Cinema對象-->
<bean class="com.hqyj.spring01.Cinema" id="cinema"><property name="name" value="萬達影城"></property><property name="timeTable"><!--Map類型屬性賦值,標明鍵和值的類型--><map key-type="java.lang.Integer" value-type="com.hqyj.spring01.Movie"><!--entry標簽表示鍵值對 如果鍵值對都是原始類型或字符串,使用key和value--><!--如果鍵值對中有引用類型,使用key-ref或value-ref--><entry key="1" value-ref="movie1"></entry><entry key="2" value-ref="movie2"></entry></map></property>
</bean>

屬性值如果通過某個方法調用而來

如使用String保存yyyy/MM/dd格式的日期,需要通過SimpleDateFormat對象調用parse()方法而來

<!--SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");-->
<!--注入SimpleDateFormat對象,設置日期格式-->
<bean class="java.text.SimpleDateFormat" id="sdf"><!--如果構造方法注入時,該構造方法只有一個參數,可以不用寫name或index--><constructor-arg value="yyyy/MM/dd"></constructor-arg>
</bean>
<!--Date now = new Date();-->
<!--注入Date對象-->
<bean class="java.util.Date" id="now"></bean><!--注入Pet對象-->
<bean class="com.hqyj.spring01.test3.Pet" id="p1"><property name="petType" value="哈士奇"></property><property name="petNickName" value="小哈"></property><!--使用當前時間作為該屬性的值,以yyyy/MM/dd--><!--sdf.format(now);--><property name="petBirthday"><!--如果某個值是通過某個bean調用了某個方法而來--><bean factory-bean="sdf" factory-method="format"><!--方法的實際參數--><constructor-arg ref="now"></constructor-arg></bean></property>
</bean>

屬性自動注入autowire

以上所有案例中,如果要在A對象中注入一個引用類型的對象B,都是手動將對象B注入到對象A中。

如在Person中注入Car對象,在Cinema中注入Movie等。

這種情況下,如果當某個被注入的bean的id更改后,所有引用了該bean的地方都要進行修改。

所以將手動注入更改為自動注入(自動裝配),就無需添加相應的<property>標簽,甚至可以無需定義bean的id。

實現自動注入

在某個bean標簽中,加入autowire屬性,設置值為"byName"或"byType"。通常設置為"byType"。

Car類

package com.hqyj.spring01.test1;import java.util.HashMap;public class Car {private String brand;private String color;@Overridepublic String toString() {return "Car{" +"brand='" + brand + '\'' +", color='" + color + '\'' +'}';}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}
}

Person類

package com.hqyj.spring01.test1;public class Person {private String name;private int age;private Car car;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", car=" + car +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Car getCar() {return car;}//自動注入時,必須要有該方法//byType方式自動注入,會自動在容器中尋找該方法參數類型//byName方式自動注入,會自動在容器中尋找該方法setCar中的car這個idpublic void setCar(Car car) {this.car = car;}
}

配置文件

<?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"><!--在容器中注入Car類型的bean--><bean class="com.hqyj.spring01.test1.Car" id="car"> <property name="brand" value="寶馬"></property><property name="colo" value="白色"></property></bean><!--注入Person類的bean--><!--autowire="byType"表示自動檢測該類中是否需要使用引用類型參數,如果容器中正好有唯一的一個對應類型的bean,就會自動賦值給對應的屬性--><bean class="com.hqyj.spring01.test1.Person" id="person" autowire="byType"><property name="name" value="趙明"></property><property name="age" value="26"></property></bean><!--autowire="byName"表示自動檢測該類中的setXXX方法,如果某個setXXX方法的XXX和容器某個對象的id對應,就會自動賦值給對應的屬性--><bean class="com.hqyj.spring01.test1.Person" id="person2" autowire="byName"><property name="name" value="王海"></property><property name="age" value="26"></property></bean>
</beans>

Main

package com.hqyj.spring01.test1;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");Person person = context.getBean("person", Person.class);//此時打印時,會輸出姓名、年齡和Car對象System.out.println(person);context.close();}
}

autowire屬性的值

  • byType

    • 類中要有被注入的屬性的setter()方法
    • 被自動注入的對象可以沒有id
    • Spring容器中,某個對象的類型要與該setter()方法的參數類型一致,且容器中只有一個該類型的對象。
      • 如setCar(Car c),Spring就會自動在容器中尋找類型為Car的對象自動裝配
  • byName

    • 類中要有被注入的屬性的setter()方法
    • 被自動注入的對象必須要有id
    • 實際是根據setXXX()方法set后的單詞XXX關聯
      • 如setCar(Car c),Spring就會自動在容器中尋找id為car的對象自動裝配

在Web項目中,可以利用自動裝配,在控制層中自動裝配業務邏輯層的對象,在業務邏輯層中自動裝配數據訪問層的對象。

屬性自動注入練習

在電影院案例的基礎上

1.創建一個dao層CinemaDao類,包含Cinema對象,定義一個fun()方法,輸出Cinema對象

package com.hqyj.spring01.test2.dao;import com.hqyj.spring01.test2.Cinema;/*
* 定義數據訪問層
* 模擬查詢數據庫得到結果
* */
public class CinemaDao {private Cinema cinema;public void setCinema(Cinema cinema) {this.cinema = cinema;}public void fun(){System.out.println(cinema);}
}

2.創建一個controller層CinemaController類,包含CinemaDao對象,定義方法fun()調用CinemaDao中的fun()方法

package com.hqyj.spring01.test2.controller;import com.hqyj.spring01.test2.dao.CinemaDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*
* 定義控制層類,該層需要使用下一層dao中的方法
* */
public class CinemaController {//這里無需創建對象,讓Spring自動注入CinemaDao對象private CinemaDao cinemaDao;public void setCinemaDao(CinemaDao dao) {this.cinemaDao = dao;}public void fun(){cinemaDao.fun();}
}

最終在Main方法中獲取Controller對象后,調用fun()

public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("movie.xml");CinemaController controller = context.getBean("controller", CinemaController.class);controller.fun();
}

配置文件

<!--省略movie對象--><!--注入Cinema對象-->
<bean class="com.hqyj.spring01.test2.Cinema" id="cinema"><property name="name" value="萬達影城"></property><property name="timeTable"><!--Map類型屬性賦值,標明鍵和值的類型--><map key-type="java.lang.Integer" value-type="com.hqyj.spring01.test2.Movie"><!--entry標簽表示鍵值對 如果鍵值對都是原始類型或字符串,使用key和value--><!--如果鍵值對中有引用類型,使用key-ref或value-ref--><entry key="1" value-ref="movie1"></entry><entry key="2" value-ref="movie2"></entry></map></property>
</bean><!--該類中有一個Cinema類型的屬性,這里使用了autowire=byType自動裝配,會在容器中尋找Cinema類型的對象自動賦值給屬性-->
<bean class="com.hqyj.spring01.test2.dao.CinemaDao" autowire="byType"></bean><!--該類中有一個CinemaDao類型的屬性,這里使用了autowire=byType自動裝配,會在容器中尋找CinemaDao類型的對象自動賦值給屬性-->
<bean class="com.hqyj.spring01.test2.controller.CinemaController" id="controller" autowire="byType"></bean>

核心注解

在Spring配置文件中加入

<!--設置要掃描的包,掃描這個包下所有使用了注解的類-->
<context:component-scan base-package="com.hqyj.spring02.bookSystem"></context:component-scan>

類上加的注解

  • @Component
    • 當一個類不好歸納時,定義為普通組件
  • @Controller
    • 定義一個類為控制層組件
  • @Service
    • 定義一個類為業務層組件
  • @Repository
    • 定義一個類為持久層(數組訪問層)組件
  • @Lazy/@Lazy(value=true)
    • 設置該類為懶加載。
  • @Scope(value=“singleton/prototype”)
    • 設置為單例/原型模式。

屬性上加的注解

  • @Autowired

    • 優先使用byType方式從Spring容器中獲取對應類型的對象自動裝配。先檢索Spring容器中對應類型對象的數量,如果數量為0直接報錯;數量為1直接裝配

      數量大于1,會再嘗試使用byName方式獲取對應id的對象,但要配合@Qualifier(value=“某個對象的id”)一起使用,指定id進行裝配

  • @Qualifier(value=“某個對象的id”)

    • 配合@Autowired注解,使用byName方式獲取某個對象id的bean進行裝配
  • @Resource(name=“某個對象的id”)

    • 該注解相當于@Autowired+@Qualifier(value=“某個對象的id”)

    • 優先使用byName方式,從Spring容器中檢索name為指定名的對象進行裝配,如果沒有則嘗試使用byType方式,要求對象有且只有一個,否則也會報錯。

在Web項目中使用Spring

1.創建基于Maven的web-app項目

2.添加依賴

<!--servlet-->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>
<!--spring容器-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.23</version>
</dependency>
<!--web集成spring-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.23</version>
</dependency>

3.在main目錄下創建java和resources目錄,修改web.xml版本為4.0

4.在resources目錄下創建Spring配置文件application.xml,掃描使用了注解的根包

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!--掃描使用了Spring注解的根包--><context:component-scan base-package="com.hqyj.springweb"></context:component-scan>
</beans>

5.創建一個類,使用@Componet注解將其注入到Spring容器中

package com.hqyj.springweb.entity;import org.springframework.stereotype.Component;@Component
public class Pojo {public void fun(){System.out.println("hello springweb!");}
}

6.在web.xml中配置監聽器用于初始化Spring容器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!--配置監聽器ContextLoaderListener--><listener><!--監聽器全限定名--><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--定義全局參數contextConfigLocation用于讀取Spring配置文件--><context-param><!--參數名固定contextConfigLocation--><param-name>contextConfigLocation</param-name><!--只是Spring配置文件的路徑 classpath:表示從根目錄出發--><param-value>classpath:application.xml</param-value></context-param>
</web-app>

7.創建一個Servlet,訪問該Servlet,獲取Spring容器,從容器中獲取注入的對象

package com.hqyj.springweb.controller;import com.hqyj.springweb.entity.Pojo;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@WebServlet("/hello")
public class MyServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//獲取Spring容器WebApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(getServletContext());//從容器中獲取某個beanPojo pojo = app.getBean("pojo", Pojo.class);pojo.fun();}
}

在Spring-web項目中使用Spring-jdbc

Spring提供了一套自己的JDBC解決方案,相較于自己寫的JDBC,省去了繁瑣的過程(獲取連接、關閉),簡化了對數據庫的操作。

Spring-jdbc后續會用其他持久層框架代替,如MyBatis、JPA。

1.引入相關依賴

當前案例中核心的依賴是 spring-jdbc

<!--servlet-->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>
<!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version>
</dependency>
<!--jstl-->
<dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version>
</dependency>
<!--spring容器-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.23</version>
</dependency>
<!--web集成spring-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.23</version>
</dependency>
<!-- spring-jdbc -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.23</version>
</dependency>

2.創建包結構(controller、service、dao、entity),創建實體類

public class Hero {private int id;private String name;private String position;private String sex;private int price;private String shelfDate;//省略get/set、構造方法、toString等
}

3.在Spring配置文件中配置數據源和JDBC模板

<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!--掃描使用了Spring注解的根包--><context:component-scan base-package="com.hqyj.springweb"></context:component-scan><!--配置數據源--><bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource"><!--配置MySQL驅動--><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property><!--配置MySQL地址--><property name="url" value="jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai"></property><property name="username" value="root"></property><property name="password" value="root"></property></bean><!--配置JDBC模板--><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"><!--設置要操作的數據源--><property name="dataSource" ref="dataSource"></property></bean>
</beans>

4.數據訪問層(dao)的寫法

package com.hqyj.springweb.dao;import com.hqyj.springweb.entity.Hero;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public class HeroDao {@Autowired//通過jdbc模板對象實現CRUDprivate JdbcTemplate jdbcTemplate;/** 查詢所有* */public List<Hero> queryAll() {//定義sql語句String sql = "select * from hero";//BeanPropertyRowMapper對象表示將查詢到的字段與指定的類中的屬性進行映射BeanPropertyRowMapper<Hero> mapper = new BeanPropertyRowMapper<>(Hero.class);//query()方法執行查詢List<Hero> list = jdbcTemplate.query(sql, mapper);return list;}/** 刪除* */public boolean delete(int id) {String sql = "delete from hero where id=?";int i = jdbcTemplate.update(sql, id);return i > 0;}/** 修改* */public boolean update(Hero hero) {String sql = "update hero set name=?,sex=?,position=?,price=?,shelf_date=? where id=?";int i = jdbcTemplate.update(sql, hero.getName(), hero.getSex(), hero.getPosition(), hero.getPrice(), hero.getShelfDate(), hero.getId());return i > 0;}/** 添加* */public boolean insert(Hero hero) {String sql = "insert  into hero values(null,?,?,?,?,?)";int i = jdbcTemplate.update(sql, hero.getName(), hero.getSex(), hero.getPosition(), hero.getPrice(), hero.getShelfDate());return i > 0;}/** 根據id查詢* */public Hero findById(int id) {String sql = "select * from hero where id=?";BeanPropertyRowMapper<Hero> mapper = new BeanPropertyRowMapper<>(Hero.class);Hero hero = jdbcTemplate.queryForObject(sql, mapper, id);return hero;}
}

忽略后續service、servlet、jsp等;

JDBCTemplate常用方法

方法 作用 說明
query(String sql,RowMapper mapper) 無條件查詢 返回值為List集合
update(String sql) 無條件更新(刪除、修改) 返回值為受影響的行數
query(String sql,RowMapper mapper,Object… objs) 條件查詢 可變參數為?的值
update(String sql,Object… objs) 條件更新(增加、刪除、修改) 可變參數為?的值
queryForObject(String sql,RowMapper mapper) 無條件查詢單個對象 返回值為指定對象
queryForObject(String sql,RowMapper mapper,Object… objs) 條件查詢單個對象 返回值為指定對象
execute(String sql) 執行指定的sql 無返回值

SpringMVC

在Web階段中,控制層是由Servlet實現,傳統的Servlet,需要創建、重寫方法、配置映射。使用時極不方便,SpringMVC可以替換Servlet

SpringMVC是Spring框架中位于Web開發中的一個模塊,是Spring基于MVC設計模式設計的輕量級Web框架。

SpringMVC提供了一個DispatcherServlet的類,是一個Servlet。它在指定映射(通常設置為/或*.do)接收某個請求后,調用相應的模型處理得到結果,再通過視圖解析器,跳轉到指定頁面,將結果進行渲染。

原理大致為:配置SpringMVC中的DispatcherServlet將其映射設置為/或.do。*

如果是/表示一切請求先經過它,如果是*.do表示以.do結尾的請求先經過它,

它對該請求進行解析,指定某個Controller中的某個方法,這些方法通常返回一個字符串,

這個字符串是一個頁面的名稱,再通過視圖解析器,將該字符串解析為某個視圖的名稱,跳轉到該視圖頁面。

使用SpringMVC

1.創建webapp項目

  • 修改web.xml版本為4.0
  • 創建java和resources目錄
  • 創建包結構

2.添加依賴

<!-- spring-webmvc -->
<!-- 這個依賴會包含spring-web和spring-context -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.23</version>
</dependency>

3.配置初始化Spring

  • 在resources目錄下創建Spring配置文件application.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!--設置掃描使用了注解的根包--><context:component-scan base-package="com.hqyj.springmvc"></context:component-scan>
    </beans>
    
  • 在web.xml中使用監聽器ContextLoaderListener初始化Spring

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!--配置全局監聽器初始化Spring--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--定義全局參數讀取Spring配置文件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:application.xml</param-value></context-param>
    </web-app>
    

4.配置SpringMVC

  • 在resources目錄下創建配置SpringMVC的配置文件springmvc.xml

    • 配置要掃描的控制層類所在的包名
    • 配置內部資源視圖解析器以及視圖路徑的前后綴
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns: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 https://www.springframework.org/schema/context/spring-context.xsd"><!--掃描控制層所在的包--><context:component-scan base-package="com.hqyj.springmvc.controller"></context:component-scan><!--配置內部資源視圖解析器--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--最終控制層跳轉的頁面所在的路徑及頁面自身后綴名--><!--jsp頁面不建議直接通過瀏覽器訪問。在WEB-INF目錄下在資源,無法通過瀏覽器直接方法,所以將jsp保存在WEB-INF目錄下,最好創建一個pages--><property name="prefix" value="/WEB-INF/pages/"></property><!--現階段使用jsp輸出數據,所以后綴為.jsp--><property name="suffix" value=".jsp"></property></bean>
    </beans>
    
  • 在web.xml中配置DispatcherServlet

    • 將該Servlet的請求映射設置為/,表示所有請求都會訪問該Servlet,由該Servlet再進行分發
    <!--配置DispatcherServlet-->
    <servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--設置該Servlet的初始化參數,用于讀取SpringMVC配置文件--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param>
    </servlet>
    <servlet-mapping><servlet-name>dispatcherServlet</servlet-name><!--設置該servlet的映射為/或*.do--><url-pattern>/</url-pattern>
    </servlet-mapping>
    

5.在WEB-INF目錄下創建一個pages目錄,在其中創建一個welcome.jsp頁面

  • 通常jsp頁面不允許被瀏覽器直接訪問,需要保存在WEB-INF目錄下

6.編寫控制層代碼

  • 在controller包下創建一個類,加上@Controller注解

  • 該類中定義的方法方法加入@RequestMapping()注解表示訪問該方法的映射

  • 該類中定義的方法返回值通常為字符串,表示某個頁面的名稱,也可以是另一個controller中的方法映射名

    package com.hqyj.springmvc.controller;import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;//加入@Controller注解表示該類是一個控制層類,替換之前的servlet
    @Controller
    //該注解如果只設置請求映射,直接填字符串
    @RequestMapping("/first")
    public class HelloController {//該注解如果還有其他參數要設置,路徑用path賦值@RequestMapping(path="/hello")public String hello(){//返回的字符串是某個頁面的名稱或另一個控制層中方法的請求映射return "welcome";}
    }
    

將項目部署到tomcat后,訪問http://localhost:8080/SpringMVC_war_exploded/first/hello,即可跳轉到指定頁面

SpringMVC相關注解

  • @Controller

    • 只能寫在類上,表示該類屬于一個控制器
  • @RequestMapping(“/請求映射名”)/@RequestMapping(value=“/請求映射名”)/@RequestMapping(path=“/請求映射名”)

    • 該注解可以寫在類或方法上。寫在類上用于區分功能模塊,寫在類上用于區分具體功能
    • 默認寫一個屬性或value或path后的值,都表示訪問該類或該方法時的請求映射
  • @RequestMapping(value=“/請求映射名”,method=RequestMethod.GET/POST/PUT/DELETE)

    • method屬性表示使用哪種請求方式訪問該類或該方法
    • 如果注解中不止一個屬性,每個屬性都需要指定屬性名
  • **@GetMapping(“/請求映射名”)**相當于@RequestMapping(value=“/請求映射名”,method=RequestMethod.GET)

    • post、put、delete同理
    • @GetMapping只能寫在方法上
  • @PathVariable

    • 該注解寫在某個方法的某個形參上

    • 通常配合@RequestMapping(“/{path}”)獲取請求時傳遞的參數

      @RequestMapping("/{path}")
      public String fun(@PathVariable("path") String pageName){return pageName;
      }
      //當前方法如果通過"localhost:8080/項目名/hello"訪問,就會跳轉到hello.jsp
      //當前方法如果通過"localhost:8080/項目名/error"訪問,就會跳轉到error.jsp
      //映射中的/{path}就是獲取路徑中的hello或error,將其賦值給形參
      //通常用于跳轉指定頁面
      
  • @RequestParam(value=“傳遞的參數名”,defaultValue =“沒有傳遞參數時的默認值”)

    • 該注解寫在某個方法的某個參數上
    • 用于獲取提交的數據,可以設置默認值在沒有提交數據時使用

控制層中獲取請求時傳遞的參數

  • controller中方法的形參名和表單的name或?后的參數名一致

    表單或a標簽

    <form action="${pageContext.request.contextPath}/first/sub"><input type="text" name="username"><input type="text" name="password"><input type="submit" >
    </form><a href="${pageContext.request.contextPath}/first/sub?username=admin">xxx</a>
    

    controller

    public String login(String username,int password){//此時可以正常獲取//無關數據類型,但是提交數據時必須是對應的類型,否則會有400錯誤
    }
    
  • controller中方法的形參名和表單的name或?后的參數名不一致

    controller

    public String login(@RequestParam(value="username",defaultValue="admin")String name,@RequestParam("username")int pwd){//如果沒有提交數據時,會使用設置的默認值
    }
    
  • 如果傳遞的參數都是某個實體類中的屬性時

    User類

    package com.hqyj.springmvc.entity;public class User {private String username;private String password;@Overridepublic String toString() {return "User{" +"username='" + username + '\'' +", password='" + password + '\'' +'}';}public User() {}public User(String username, String password) {this.username = username;this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
    }
    

    controller

    @RequestMapping("/login")
    public String login(User user){//某個實體類的屬性和提交的參數名一致時,可以直接將對象作為形參,會自動將對應參數賦值給對應屬性System.out.println(user);return "";
    }
    

SpringMVC中的跳轉

  • 控制層跳轉到某個jsp頁面

    • 在控制層中定義方法,這種方式跳轉,屬于請求轉發

    • 如果要使用重定向跳轉,在頁面名之前添加"redirect:"

      @RequestMapping("/hello")
      public String hello(){//返回頁面名稱return "hello";//請求轉發
      }@RequestMapping("/hello")
      public ModelAndView hello(ModelAndView mav){//設置頁面名稱mav.setViewName("hello");return mav;
      }
      
    • 在springmvc配置文件中

      <mvc:view-controller path="請求映射" view-name="頁面名稱"></mvc:view-controller>
      <!-- 訪問項目根目錄,跳轉到welcome.jsp頁面 -->
      <mvc:view-controller path="/" view-name="welcome"></mvc:view-controller>
      <!-- 這個標簽使用時,會讓@RequesMapping失效,如果要共存,添加以下標簽 -->
      <!--來自于xmlns:mvc="http://www.springframework.org/schema/mvc" -->
      <mvc:annotation-driven></mvc:annotation-driven>
      
  • 控制層跳轉到另一個控制層中的方法

    • 方法的返回值為"redirect/forward:另一個控制層中方法的映射名"

      @RequestMapping("/hello")
      public String hello(){return "redirect:hero";//使用重定向的方式,跳轉到映射名為hero的控制層return "forward:hero"//使用請求轉發的方式,跳轉到映射名為hero的控制層
      }
      
  • jsp頁面跳轉另一個jsp頁面

    • 當前項目中jsp頁面都在WEB-INF下,無法直接訪問,a標簽同樣如此,只能通過控制層跳轉頁面

    • 定義用于跳轉頁面控制層類

      package com.hqyj.springmvc.controller;import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestMapping;/** 定義一個用于跳轉指定頁面或controller的控制層* */
      @Controller
      public class ToPageController {/*項目啟動時或直接訪問根目錄,跳轉到指定的controller*/@RequestMapping("/")public String toIndex() {return "redirect:/hero/queryAll";}/** 這個方法的作用:會將請求中第一個/后的單詞截取出來命名為path賦值給參數page* 如 localhost:8080/web/hero,就會識別出hero,return "hero";* 就會跳轉到 /WEB-INF/pages/hero.jsp頁面* */@RequestMapping("/{path}")public String toPage(@PathVariable("path") String page) {return page;}
      }
      
    • 這時在頁面中這樣跳轉

      <%--這個路徑實際是/項目名/addHero,會截取addHero,跳轉到/項目名/WEB-INF/pages/addHero.jsp--%>
      <a href="${pageContext.request.contextPath}/addHero">添加</a>
      

ORM

Object Relational Mapping

對象關系映射 創建Java類的對象,將其屬性和數據庫表中的字段名之間構建映射關系。 這樣通過操作該類創建的一個對象,就能操作數據庫中對應的數據表。 ORM框架就是對象關系映射框架,用于簡化JDBC。 主流的ORM框架有Hibernate、JPA、MyBatis、MyBatisPlus等 ## MyBatis
一個半自動化的ORM框架。原本叫做ibatis,后來升級改名為MyBatis。 MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。 使用XML文件或注解的形式,將SQL語句映射為持久層(dao層)中的方法。 官網https://mybatis.org/mybatis-3/zh/index.html
特點
方便 簡化JDBC 解除SQL語句與業務代碼的耦合 sql和java代碼分離,sql寫在xml文件中,方便擴展維護。 支持動態SQL 不同的情況下構造不同的SQL 主流 SSM中的M

SSM項目搭建

整體流程
1.創建基于Maven的webapp項目
2.修改web.xml版本為4.0,創建java、resoureces目錄、項目包結構、web-inf下的頁面目錄
3.導入依賴 spring-webmvc mybatis mybatis-spring mysql druid spring-jdbc jstl
4.配置Spring 創建application.xml 掃描項目根包 配置web.xml 設置全局參數: ,讀取application.xml文件 設置全局監聽器:ContextLoaderListener,初始化Spring容器 5.配置SpringMVC 創建springmvc.xml 掃描控制層所在包 設置內部資源視圖解析器:InternalResourceViewResolver,設置前后綴 配置web.xml 設置請求分發器:DispatcherServlet,在其中讀取springmvc.xml配置文件 過濾器解決請求中文亂碼:CharacterEncodingFilter
6.配置MyBatis 創建mybatis-config.xml(官網模板) 在application.xml中注入 數據庫連接池Druid:DruidDataSource SQL會話工廠:SqlSessionFactoryBean 映射掃描配置器:MapperScannerConfigurer
7.執行sql語句 創建dao層接口,定義方法 在resoureces下創建mapper目錄創建sql映射文件xx.xml(官網模板),在其中定義sql語句

SSM項目搭建補充

通過db.properties文件保存連接數據庫的信息

.properties文件稱為屬性文件,其中的數據以鍵值對(key=value)的形式保存

  • 在resources目錄下新建db.properties文件

    DB_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
    DB_URL=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai
    DB_USERNAME=root
    DB_PASSWORD=root
    
  • 在application.xml中讀取properties文件

    <!--讀取properties文件-->
    <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
    
  • 讀取時使用EL表達式訪問其中的鍵

    <!--Druid數據源DruidDataSource-->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="druidDataSource"><property name="driverClassName" value="${DB_DRIVER_CLASS_NAME}"></property><property name="url" value="${DB_URL}"></property><property name="username" value="${DB_USERNAME}"></property><property name="password" value="${DB_PASSWORD}"></property>
    </bean>
    

MyBatis配置文件常用設置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--設置--><settings><!--開啟駝峰命名映射--><setting name="mapUnderscoreToCamelCase" value="true"/><!--開啟SQL日志--><setting name="logImpl" value="STDOUT_LOGGING"/></settings></configuration>

MyBatis基本增刪改查

dao層

package com.hqyj.ssm02.dao;import com.hqyj.ssm02.entity.BookInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public interface BookInfoDao {//查詢所有List<BookInfo> queryAll();//根據id刪除int delete(int id);//添加int insert(BookInfo bookInfo);//根據id查詢BookInfo findById(int no);//修改時,參數通常為一個完整的修改對象int update(BookInfo bookInfo);//批量刪除//分頁查詢//關鍵字分頁//如果dao層中某個方法不止1個參數,需要給每個參數添加@Param("參數名")注解,給該參數命名//命名后,才能在mybatis的sql映射文件中使用該參數,即#{參數名}//如這里的newPrice,在sql中用#{newPrice}獲取int update(@Param("newPrice") int bookPrice,@Param("newNum") int bookNum,@Param("updateId") int bookId);
}

service層

package com.hqyj.ssm02.service;import com.hqyj.ssm02.dao.BookInfoDao;
import com.hqyj.ssm02.entity.BookInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class BookInfoService {@Autowiredprivate BookInfoDao bookInfoDao;public List<BookInfo> queryAll() {return bookInfoDao.queryAll();}public boolean delete(int id) {return bookInfoDao.delete(id) > 0;}public void insert(BookInfo bookInfo) {bookInfoDao.insert(bookInfo);}public BookInfo findById(int no) {return bookInfoDao.findById(no);}public void update(BookInfo bookInfo) {bookInfoDao.update(bookInfo);}
}

controller層

package com.hqyj.ssm02.controller;import com.hqyj.ssm02.entity.BookInfo;
import com.hqyj.ssm02.entity.BookType;
import com.hqyj.ssm02.service.BookInfoService;
import com.hqyj.ssm02.service.BookTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;import java.util.List;@Controller
@RequestMapping("/bookInfo")
public class BookInfoController {@Autowiredprivate BookInfoService bookInfoService;//注入BookTypeService,用于獲取所有圖書類型@Autowiredprivate BookTypeService bookTypeService;@RequestMapping("/queryAll")public String queryAll(Model model) {List<BookInfo> list = bookInfoService.queryAll();model.addAttribute("list", list);return "bookInfoList";}@RequestMapping("/delete")public String delete(int id) {//如果在頁面中傳遞的參數名和方法的形參名一致,會自動獲取數據賦值if (bookInfoService.delete(id)) {//增刪改執行后,使用重定向跳轉return "redirect:queryAll";}return "error";}@RequestMapping("/insert")//如果表單提交的參數和方法的形參名一致,自動獲取并賦值//如果表單提交的所有參數正好是一個實體類對象,可以用對應的實體類對象獲取public String insert(BookInfo bookInfo){bookInfoService.insert(bookInfo);return "redirect:queryAll";}@RequestMapping("/findById")//如果表單提交的參數名和方法的參數名不一致,使用@RequestParam("提交的參數名")public String findById(@RequestParam("id") int no,Model model){//查詢對應的圖書信息BookInfo byId = bookInfoService.findById(no);model.addAttribute("book",byId);//查詢所有的圖書類型,保存到請求中List<BookType> bookTypeList = bookTypeService.queryAll();model.addAttribute("btList",bookTypeList);return "bookEdit";}@RequestMapping("/update")public String update(BookInfo bookInfo){bookInfoService.update(bookInfo);return "redirect:queryAll";}}

sql映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hqyj.ssm02.dao.BookInfoDao"><!--查詢所有--><select id="queryAll" resultType="com.hqyj.ssm02.entity.BookInfo">select *from book_info</select><!--根據id查詢--><select id="findById" resultType="com.hqyj.ssm02.entity.BookInfo">select *from book_infowhere book_id = #{no}</select><!--根據id刪除--><delete id="delete">deletefrom book_infowhere book_id = #{id}</delete><!--添加--><insert id="insert">insert into book_infovalues (null, #{typeId}, #{bookName}, #{bookAuthor}, #{bookPrice}, #{bookNum}, #{publisherDate}, #{bookImg})</insert><!--修改--><update id="update">update book_infoset book_price=#{newPrice},book_num=#{newNum},type_id=#{typeId}where book_id = #{updateId}</update></mapper>

使用BootStrap渲染數據

下載bootstrap的文件夾和jquery,保存到webapp根目錄下。

由于在web.xml中,SpringMVC的核心類DispatcherServlet(請求分發器)的映射設置成了"/",表示所有請求,包含靜態資源的請求都會交給SpringMVC處理。

解決無法引入靜態資源的問題

  • 在webapp目錄下,新建一個目錄,通常命名為static。將項目中的靜態資源文件都保存于此。

  • 在springmvc.xml中

    <!--映射靜態資源目錄-->
    <!--location表示要映射的靜態資源目錄-->
    <!--mapping表示最終通過哪種方式進行訪問。這里表示只要以/static開頭的請求,都可以訪問靜態資源目錄-->
    <mvc:resources mapping="/static/**" location="/static/"></mvc:resources>
    <!--開啟注解驅動,有這個標簽,SpringMVC就能區分哪個是資源文件,哪個是Controller-->
    <mvc:annotation-driven></mvc:annotation-driven>
    

導入BootStrap的樣式和JS文件

<%--導入bootstrap的css文件--%>
<link href="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" rel="stylesheet"type="text/css">
<%--導入jquery--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js"></script>
<%--導入boostrap的js文件--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>

創建頂部導航頁面

每個頁面都需要這三句話,為了方便起見,給每個頁面添加頂部導航頁面top.jsp

這樣其他頁面只需要通過<jsp:include>導入該頁面的同時,使用BootStrap的樣式和JS文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>Title</title><%--導入bootstrap的css文件--%><link href="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" rel="stylesheet"type="text/css"><%--導入jquery--%><script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js"></script><%--導入boostrap的js文件--%><script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script></head><body><nav class="navbar navbar-default"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="#">Brand</a></div><!-- Collect the nav links, forms, and other content for toggling --><div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav"><li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li><li><a href="#">Link</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a><ul class="dropdown-menu"><li><a href="#">Action</a></li><li><a href="#">Another action</a></li><li><a href="#">Something else here</a></li><li role="separator" class="divider"></li><li><a href="#">Separated link</a></li><li role="separator" class="divider"></li><li><a href="#">One more separated link</a></li></ul></li></ul><form class="navbar-form navbar-left"><div class="form-group"><input type="text" class="form-control" placeholder="Search"></div><button type="submit" class="btn btn-default">Submit</button></form><ul class="nav navbar-nav navbar-right"><li><a href="#">Link</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a><ul class="dropdown-menu"><li><a href="#">Action</a></li><li><a href="#">Another action</a></li><li><a href="#">Something else here</a></li><li role="separator" class="divider"></li><li><a href="#">Separated link</a></li></ul></li></ul></div><!-- /.navbar-collapse --></div><!-- /.container-fluid --></nav></body>
</html>

列表頁面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<%--使用jsp動作包含一個子頁面,也能使用其中的css和js文件--%>
<jsp:include page="top.jsp"></jsp:include>
<div class="row" style="width: 100%"><div class="col-md-2"></div><div class="col-md-8"><table class="table-striped table"><tr><th>圖書編號</th><th>類型編號</th><th>圖書名稱</th><th>圖書作者</th><th>圖書價格</th><th>圖書庫存</th><th>出版時間</th><th >操作</th><th><a href="${pageContext.request.contextPath}/bookAdd" class="btn btn-default btn-sm">添加</a></th></tr><c:forEach var="book" items="${list}"><tr><td>${book.bookId}</td><td>${book.typeId}</td><td>${book.bookName}</td><td>${book.bookAuthor}</td><td>${book.bookPrice}</td><td>${book.bookNum}</td><td>${book.publisherDate}</td><td><a href="${pageContext.request.contextPath}/bookInfo/findById?id=${book.bookId}" class="btn btn-primary btn-sm">編輯</a></td><td><a href="${pageContext.request.contextPath}/bookInfo/delete?id=${book.bookId}" class="btn btn-danger btn-sm">刪除</a></td></tr></c:forEach></table></div><div class="col-md-2"></div>
</div>
</body>
</html>

詳情頁面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>Title</title></head><body><jsp:include page="top.jsp"></jsp:include><div class="row"><div class="col-md-3"></div><div class="col-md-6"><form class="form-horizontal" action="${pageContext.request.contextPath}/bookInfo/update" method="post"><%--隱藏域提交id--%><input type="hidden" name="bookId" value="${book.bookId}"><div class="form-group"><label  class="col-sm-2 control-label">圖書名稱</label><div class="col-sm-10"><input type="text" readonly class="form-control" value="${book.bookName}" name="bookName" placeholder="請輸入圖書名稱" required></div></div><div class="form-group"><label  class="col-sm-2 control-label">圖書作者</label><div class="col-sm-10"><input type="text" readonly class="form-control" value="${book.bookAuthor}" name="bookAuthor" placeholder="請輸入圖書作者" required></div></div><div class="form-group"><label  class="col-sm-2 control-label">圖書類型</label><div class="col-sm-10"><select class="form-control" name="typeId" ><%--遍歷所有的圖書類型--%><c:forEach items="${btList}" var="bt"><option value="${bt.typeId}" ${bt.typeId==book.typeId?"selected":""}>${bt.typeName}</option></c:forEach></select></div></div><div class="form-group"><label  class="col-sm-2 control-label">圖書價格</label><div class="col-sm-10"><input type="number" min="1" class="form-control" value="${book.bookPrice}" name="bookPrice" placeholder="請輸入圖書價格" required></div></div><div class="form-group"><label  class="col-sm-2 control-label">圖書庫存</label><div class="col-sm-10"><input type="number" min="1" class="form-control" value="${book.bookNum}" name="bookNum" placeholder="請輸入圖書庫存" required></div></div><div class="form-group"><label  class="col-sm-2 control-label">出版時間</label><div class="col-sm-10"><input type="date" readonly class="form-control" value="${book.publisherDate}"   name="publisherDate" required></div></div><div class="form-group"><div class="col-sm-offset-2 col-sm-10"><button type="submit" class="btn btn-default">修改</button></div></div></form></div><div class="col-md-3"></div></div></body>
</html>

添加頁面

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2023/1/30Time: 14:17To change this template use File | Settings | File Templates.--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>Title</title></head><body><jsp:include page="top.jsp"></jsp:include><div class="row"><div class="col-md-3"></div><div class="col-md-6"><form class="form-horizontal" action="${pageContext.request.contextPath}/bookInfo/insert" method="post"><div class="form-group"><label class="col-sm-2 control-label">圖書名稱</label><div class="col-sm-10"><input type="text" class="form-control" name="bookName" placeholder="請輸入圖書名稱" required></div></div><div class="form-group"><label class="col-sm-2 control-label">圖書作者</label><div class="col-sm-10"><input type="text" class="form-control" name="bookAuthor" placeholder="請輸入圖書作者" required></div></div><div class="form-group"><label class="col-sm-2 control-label">圖書類型</label><div class="col-sm-10"><select class="form-control" name="typeId"></select></div></div><div class="form-group"><label class="col-sm-2 control-label">圖書價格</label><div class="col-sm-10"><input type="number" min="1" class="form-control" name="bookPrice" placeholder="請輸入圖書價格" required></div></div><div class="form-group"><label class="col-sm-2 control-label">圖書庫存</label><div class="col-sm-10"><input type="number" min="1" class="form-control" name="bookNum" placeholder="請輸入圖書庫存" required></div></div><div class="form-group"><label class="col-sm-2 control-label">出版時間</label><div class="col-sm-10"><input type="date" class="form-control" name="publisherDate" required></div></div><div class="form-group"><div class="col-sm-offset-2 col-sm-10"><button type="submit" class="btn btn-default">添加</button></div></div></form></div><div class="col-md-3"></div></div></body><script>/*使用ajax查詢所有圖書類型在頁面中使用ajax訪問后端如果要得到數據,該數據必須是JSON格式*/$.ajax({//請求地址url: "${pageContext.request.contextPath}/bookType/queryAllToJson",//訪問成功后的回調函數success: function (res) {//這里的res是所有類型對象的集合for (var i = 0; i < res.length; i++) {var $opt = $("<option value='" + res[i].typeId + "'>" + res[i].typeName + "</option>")$("select[name=typeId]").append($opt);}}});</script></html>

在SpringMVC中,讓某個控制層中的方法返回JSON格式的數據

  • 添加依賴

    <!--jackson:將數據轉換為JSON格式-->
    <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.14.2</version>
    </dependency>
    
  • @ResponseBody注解

    該注解可以加在類或方法上

    • 如果加在方法上,表示該方法的返回值類型為JSON格式
    • 如果加在類上,表示該類中的所有方法的返回值類型為JSON格式
    package com.hqyj.ssm02.controller;import com.hqyj.ssm02.entity.BookType;
    import com.hqyj.ssm02.service.BookTypeService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;import java.util.List;@Controller
    @RequestMapping("/bookType")
    public class BookTypeController {@Autowiredprivate BookTypeService bookTypeService;//如果controller的某個方法返回一個JSON字符串,需要使用@ResponseBody@RequestMapping("/queryAllToJson")@ResponseBody//表示該方法無論返回值是什么,都返回JSON格式字符串public List<BookType> queryAllToJson(){List<BookType> list = bookTypeService.queryAll();return list;}
    }

SSM項目中使用Ajax

ajax依賴于jquery,所以先保證頁面中存在jquery.js。

$.ajax({url:"訪問地址",data:{"提交的參數名":"實際值","提交的參數名":"實際值"},type:"get/post",success:function(res){//成功后的回調函數,res為訪問后的結果,必須是json格式}
});

在前端頁面中使用ajax訪問controller時,controller的返回值必須是一個JSON格式的字符串。

所以controller中的方法上要加入@ResponseBody注解

使用Aajx實現注冊和登錄

當前表為customer,包含字段如下

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qo1GsDU8-1676548026833)(D:/qqFileRecv/框架/框架/SSM03.assets/image-20230131154742977.png)]

使用該表中的phone和password作為登錄時的賬戶和密碼

實體類

package com.hqyj.ssm02.entity;
/*
* 對應customer表
* */
public class SysAdmin {//用戶名adminName屬性對應phone字段private String adminName;//密碼adminPwd屬性對應password字段private String adminPwd;//省略get/set/toString
}

注冊

注冊時先判斷用戶名是否存在,如果存在則不能注冊

  • dao

    /*查詢用戶名是否存在,返回查詢到的數量*/
    int findByAdminName(String adminName);
    /*添加*/
    int insert(SysAdmin sysAdmin);
    
  • mapper.xml

    <!--查詢用戶名(phone)是否存在-->
    <select id="findByAdminName" resultType="java.lang.Integer">select count(*)from customerwhere phone = #{adminName}
    </select><!--添加用戶-->
    <insert id="insert">insert into customervalues (null, #{adminName}, #{adminPwd}, 0, null)
    </insert>
    
  • service

    /** 檢查注冊的用戶名是否存在* */
    public boolean findAdminName(SysAdmin sysAdmin) {//查詢要注冊的用戶名是否存在int i = sysAdminDao.findByAdminName(sysAdmin.getAdminName());if (i != 0) {return false;}return true;
    }/** 注冊* */
    public boolean register(SysAdmin sysAdmin) {return sysAdminDao.insert(sysAdmin) > 0;
    }
    
  • controller

    /** 查詢名稱是否存在,返回boolean類型的json字符串* */
    @RequestMapping("/findAdminName")
    @ResponseBody
    public boolean findAdminName(SysAdmin sysAdmin) {return sysAdminService.findAdminName(sysAdmin);
    }/** 注冊成功后跳轉到登錄頁面* */
    @RequestMapping("/register")
    public String register(SysAdmin sysAdmin) {sysAdminService.register(sysAdmin);return "login";
    }
    
  • 頁面

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html><head><title>Title</title><style>.cus-portrait {margin: 0 auto;width: 100px;height: 100px;border: 1px solid #337ab7;text-align: center;line-height: 100px;border-radius: 50px;}</style></head><body><jsp:include page="top.jsp"></jsp:include><div class="col-md-4"></div><div class="col-md-4"><div class="panel panel-primary"><div class="panel-heading">用戶注冊</div><div class="panel-body"><form action="${pageContext.request.contextPath}/sysAdmin/register" method="post"><div class="form-group"><label class="warning">用戶名</label><input type="text" class="form-control" required name="adminName" placeholder="請輸入用戶名"></div><div class="form-group"><label>密碼</label><input type="password" class="form-control" required name="adminPwd" placeholder="請輸入密碼"></div><button  type="submit" class="btn btn-default">注冊</button></form></div></div></div><div class="col-md-4"></div><script>$(function () {//用戶名文本框失去焦點觸發$("input[name=adminName]").blur(function () {$.ajax({//訪問controllerurl:"${pageContext.request.contextPath}/sysAdmin/findAdminName",data:{//提交數據"adminName":$(this).val()},success:function(res){//res是true、false。true表示該用戶名不存在if(res){$(".warning").text("√").css("color","green");$("button[type=submit]").removeAttr("disabled");}else{$(".warning").text("該用戶名已存在").css("color","red");$("button[type=submit]").attr("disabled","disabled");}}});});});</script></body>
    </html>
    

登錄

  • dao

    /*登錄*/
    SysAdmin login(SysAdmin sysAdmin);
    
  • mapper.xml

    <!--登錄-->
    <!--如果查詢的字段名和實體的屬性名不一致,需要自定義查詢結果集映射-->
    <select id="login" resultMap="loginMap">select *from customerwhere phone = #{adminName}and password = #{adminPwd}
    </select>
    <!--自定義返回結果集映射-->
    <resultMap id="loginMap" type="com.hqyj.ssm02.entity.SysAdmin"><!--表中的phone字段對應SysAdmin對象的adminName字段--><result property="adminName" column="phone"></result><result property="adminPwd" column="password"></result>
    </resultMap>
    
  • service

    /** 登錄* */
    public SysAdmin login(SysAdmin sysAdmin){return sysAdminDao.login(sysAdmin);
    }
    
  • controller

    @RequestMapping("/login")
    @ResponseBody
    public SysAdmin login(SysAdmin sysAdmin,Model model) {SysAdmin login = sysAdminService.login(sysAdmin);//將登錄成功的對象保存到session中model.addAttribute("sysAdmin",login);return login;
    }
    
  • 頁面

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html><head><title>Title</title></head><body><jsp:include page="top.jsp"></jsp:include><div class="col-md-4"></div><div class="col-md-4"><div class="panel panel-primary"><div class="panel-heading">用戶登錄</div><div class="panel-body"><div class="form-group"><label>用戶名</label><input type="text" class="form-control" required id="name"  placeholder="請輸入用戶名"></div><div class="form-group"><label>密碼</label><input type="password" class="form-control" required id="pwd" placeholder="請輸入密碼"></div><div class="form-group"><label class="warning">驗證碼</label><span class="vcode"></span><input type="text" class="form-control inpVcode" required placeholder="請輸入驗證碼"></div><a class="btn btn-default loginBtn">登錄</a><a class="btn btn-default" href="">注冊</a></div></div></div><div class="col-md-4"></div><script>//1000~9999var vcode = Math.floor(Math.random() * 8999 + 1000);//顯示驗證碼$(".vcode").text(vcode).css("font-weight", "bolder");//定義一個boolean值用于提交數據時判斷驗證碼是否有誤,默認falsevar goon=false;//bind("綁定事件名",滿足時觸發的函數)   input propertychange表示監聽文本框輸入事件(只要內容有變化就觸發)$(".inpVcode").bind("input propertychange", function () {if (vcode != $(this).val()) {//如果驗證碼有誤,改變警告文字$(".warning").text("驗證碼輸入錯誤").css("color","#f00");}else{//如果輸入正確,改變boolean值為true$(".warning").text("√").css("color","#0f0");goon=true;}});//登錄按鈕單擊$(".loginBtn").click(function () {//判斷驗證碼if (!goon) {return;}//使用ajax提交數據進行登錄$.ajax({url:"${pageContext.request.contextPath}/sysAdmin/login",data:{"adminName":$("#name").val(),"adminPwd":$("#pwd").val()},success:function (res){if(res!=""){location.href="${pageContext.request.contextPath}/bookInfo/queryAll";}else{alert("用戶名或密碼錯誤");}}});});</script></body>
    </html>
    

在SpringMVC中使用Session

方式一:@SessionAttributes注解

由于SSM項目中,沒有使用servlet,所以不能通過request.getSession()方法來獲取session對象。

在控制器Controller中,在類上加入@SessionAttributes注解

**@SessionAttributes({“參數1”,“參數2”})**表示在session中保存兩個參數

再在某個方法中,通過Model對象調用addAttribute(“參數1”,對象)方法將指定對象保存到session中

使用和銷毀

@Controller
@RequestMapping("/sysAdmin")
//如果要將數據保存到session中,先使用該注解定義session中的參數名
@SessionAttributes({"sysAdmin"})
public class SysAdminController {@AutowiredSysAdminService sysAdminService;@RequestMapping("/login")@ResponseBodypublic SysAdmin login(SysAdmin sysAdmin,Model model) {SysAdmin login = sysAdminService.login(sysAdmin);//將登錄成功的對象保存到session中model.addAttribute("sysAdmin",login);return login;}/*登出時銷毀session*/@RequestMapping("/logout")public String logout(SessionStatus sessionStatus) {//在方法中使用SessionStatus參數表示session狀態對象//調用setComplete()方法,將session設置為完成狀態sessionStatus.setComplete();return "redirect:/login";}
}

方式二:HttpSession參數

給項目添加javax.servlet.api依賴,給controller中某個方法添加HttpSession參數后,獲取session使用

<!--如果要使用servlet相關內容-->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>

使用和銷毀

@Controller
@RequestMapping("/sysAdmin")
//如果要將數據保存到session中,先使用該注解定義session中的參數名
@SessionAttributes({"sysAdmin"})
public class SysAdminController {@AutowiredSysAdminService sysAdminService;@RequestMapping("/login")@ResponseBodypublic SysAdmin login(SysAdmin sysAdmin,HttpSession session) {SysAdmin login = sysAdminService.login(sysAdmin);//將登錄成功的對象保存到session中session.setAttribute("sysAdmin",login);return login;}/*登出時銷毀session*/@RequestMapping("/logout")public String logout(HttpSession session) {session.invalidate();return "redirect:/login";}
}

攔截器

每次請求controller時,都要經過的一個類。

當一個項目中有過濾器、攔截器和controller時的執行流程

實現過程

1.導入servlet依賴

<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version>
</dependency>

2.自定義一個類,實現攔截器HandlerInterceptor接口

其中有三個default方法可以重寫

  • preHandle
    • 在發送請求后,DispatcherServlet解析控制器中某個RequestMapping之前執行的方法
    • 該方法返回true時,請求才能繼續
  • postHandle
    • 在preHandle方法返回值為true后執行
    • 在DispatcherServlet解析控制器中某個RequestMapping之后執行
  • afterCompletion
    • 在preHandle方法返回true后執行
    • 在解析視圖后執行的方法

這里只需重寫preHandle方法即可

package com.hqyj.ssm02.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*
* 自定義攔截器,用戶攔截未登錄時的請求
* */
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();//登錄成功后,會在session中保存一個名為sysAdmin的對象Object sysAdmin = request.getSession().getAttribute("sysAdmin");//如果有對象,說明登錄成功,可以放行return trueif(sysAdmin!=null){return true;}else{response.sendRedirect(request.getContextPath()+"/login");}System.out.println(requestURI+"試圖訪問,攔截成功");return false;}
}

3.在springmvc.xml中配置攔截器

<!--配置攔截器們-->
<mvc:interceptors><!--配置某個攔截器--><mvc:interceptor><!--設置要攔截的請求,這里的/**表示攔截一切請求--><mvc:mapping path="/**"/><!--設置不攔截的請求--><!--放行登錄和注冊頁--><mvc:exclude-mapping path="/login"/><mvc:exclude-mapping path="/register"/><!--放行靜態資源--><mvc:exclude-mapping path="/static/**"/><!--放行用戶模塊--><mvc:exclude-mapping path="/sysAdmin/**"/><!--注入指定的攔截器--><bean class="com.hqyj.ssm02.interceptor.MyInterceptor"></bean></mvc:interceptor>
</mvc:interceptors>

單元測試

1.導入依賴

如果只在普通的Maven項目中使用,只需導入該依賴

<!--單元測試-->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope>
</dependency>

如果要在Spring環境下使用,還需

<!--spring集成JUnit-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.23</version><scope>test</scope>
</dependency>

2.創建單元測試類所在目錄

在項目的src目錄下新建test目錄,會自動在其中創建java目錄

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tng8Abg4-1676548026834)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201142036858.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qTD9QJb6-1676548026835)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201142055759.png)]

3.使用

  • 在test/java目錄下,新建一個類
  • 在類上加入
    • @ContextConfiguration(“classpath:application.xml”)
    • @RunWith(SpringJUnit4ClassRunner.class)
  • 在類中創建公開的無返回值的無參數方法,加入@Test注解
  • 在該方法上右鍵運行,運行的是當前方法,在類中空白處右鍵運行,運行的是當前類中的所有方法。
import com.hqyj.ssm02.dao.BookInfoDao;
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;//解析Spring配置文件
@ContextConfiguration("classpath:application.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {/** 單元測試的方法必須是無參數無返回值的公共方法* */@org.junit.Testpublic void fun(){System.out.println("單元測試");}@Autowiredprivate BookInfoDao bookInfoDao;@org.junit.Testpublic void test1(){System.out.println(bookInfoDao.queryAll());}
}

MyBatis整理

使用前提

  • 搭建好MyBatis的環境

    • mybatis配置文件模板

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration></configuration>
      
  • 根據表創建實體類

    • 默認屬性名要和字段名一致

    • 如果表中的字段名使用下劃線分割單詞,實體屬性使用駝峰命名法。在mybatis的配置文件中加入開啟駝峰命名方式映射

      <!--設置-->
      <settings><!--開啟駝峰命名映射--><setting name="mapUnderscoreToCamelCase" value="true"/><!--開啟SQL日志--><setting name="logImpl" value="STDOUT_LOGGING"/>
      </settings>
      
    • 如果表中字段名和實體的屬性名既不一致,也不是駝峰命名,就要在寫sql語句時,指定字段名和屬性名的映射關系

    • 實體類必須是公共的,其中必須有無參數的構造方法和get/set方法

  • 數據訪問層

    • 數據訪問層通常是一個接口,可以稱為dao層或mapper層,命名時可以是xxDao或xxMapper
  • 寫接口的sql映射文件

    • 命名通常為xxMapper.xml

    • sql映射文件模板

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="數據訪問層接口的全限定名">
      </mapper>
      
    • sql映射文件可以放在resources目錄下,spring的配置文件application.xml中通過"classpath:具體路徑"讀取

      如保存在resources/mapper下

      <!--SqlSessionFactoryBean-->
      <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory"><!--指定數據源--><property name="dataSource" ref="druidDataSource"></property><!--讀取mybatis配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"></property><!--讀取sql映射文件--><property name="mapperLocations" value="classpath:mapper/*.xml"></property>
      </bean>
      
    • sql映射文件也可能放在數據訪問層目錄下,即dao包下

      IDEA中,如果將xml文件保存在java包下時,默認在target目錄(真正運行時的目錄)不會編譯加載這些文件,要進行設置

      在pom.xml中的build標簽中加入以下內容,重新加載

      <!--解決xml如果放在java包下時無法編譯的問題-->
      <resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource>
      </resources>
      

      spring的配置文件application.xml中讀取路徑正確

      <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory"><!--指定數據源--><property name="dataSource" ref="druidDataSource"></property><!--讀取mybatis配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"></property><!--讀取sql映射文件--><property name="mapperLocations" value="classpath:com/hqyj/ssm02/dao/mapper/*.xml"></property>
      </bean>
      

案例

實體類

開啟駝峰命名映射,忽略get/set

BookInfo

public class BookInfo {private int bookId;private int typeId;private String bookName;private String bookAuthor;private int bookPrice;private int bookNum;private String publisherDate;private String bookImg;
}

BookType

public class BookType {private int typeId;private String typeName;
}

查詢<select></select>

單表查詢

dao中的方法

List<BookType> queryAll();

sql映射文件

  • 當數據庫表中的字段名和實體屬性一致或開啟了駝峰命名映射,使用resultType

    <select id="queryAll" resultType="com.xxx.entity.BookType">select * from book_type
    </select>
    
  • 當數據庫表中的字段名和實體屬性不一致,使用resultMap

    <select id="queryAll" resultMap="map">select * from book_type
    </select>
    <!--自定義結果集映射-->
    <resultMap id="map" type="com.xxx.entity.BookType"><!--主鍵使用id標簽,其他字段使用result標簽--><!--column對應字段名 property對應屬性名--><id column="type_id" property="typeId"></id><result column="type_name" property="typeName"></result>
    </resultMap>
    

多表查詢

多對一查詢

多對一表示多個從表實體對應一個主表實體。如多本圖書對應一個圖書類型。

在從表實體中,額外添加一個屬性:對應的主表實體對象

public class BookInfo {private int bookId;private int typeId;private String bookName;private String bookAuthor;private int bookPrice;private int bookNum;private String publisherDate;private String bookImg;//多對一查詢,額外添加外鍵對應的實體對象private BookType bt;
}

dao中的方法

List<BookInfo> queryAll();

sql映射文件

  • 關聯查詢:構建特殊的sql語句

    • 適合外鍵對應的表中字段比較少的情況下使用
    <!--sql語句除了查詢自身表之外,還要查詢關聯的表中的字段,將其命名為"實體對象.屬性"-->
    <select id="queryAll" resultType="com.xxx.entity.BookInfo">select bi.*,type_name as 'bt.typeName' from book_info bi,book_type bt where bi.type_id=bt.type_id
    </select>
    
  • 連接查詢(不建議)

    <select id="queryAll" resultMap="booksMap">select *from book_info bi,book_type btwhere bi.type_id = bt.type_id
    </select>
    <resultMap id="booksMap" type="com.hqyj.ssm02.entity.BookInfo"><!--主鍵用id標簽,其他字段用result標簽賦值--><id property="bookId" column="book_id"></id><result property="bookName" column="book_name"></result><result property="typeId" column="type_id"></result><result property="bookAuthor" column="book_author"></result><result property="bookPrice" column="book_price"></result><result property="bookNum" column="book_num"></result><result property="bookImg" column="book_img"></result><result property="publisherDate" column="publisher_date"></result><!--外鍵對應的實體對象使用association標簽,通過property屬性對應實體對象名--><association property="bt" ><!--給外鍵對象屬性賦值--><id column="type_id" property="typeId"></id><result column="type_name" property="typeName"></result></association>
    </resultMap>
    
  • 子查詢

    • 適合外鍵對應的表中字段比較多的情況
    <!--1.查詢從表-->
    <select id="queryAll" resultMap="booksMap">select * from book_info
    </select>
    <!--2.自定義結果集映射,使用type_id進行子查詢,下方的sql-->
    <resultMap id="booksMap" type="com.hqyj.ssm02.entity.BookInfo"><!--由于使用type_id進行了子查詢,所以如果要給type_id賦值,需要再次進行映射--><result column="type_id" property="typeId"></result><!--外鍵對應的實體對象,使用association賦值--><!--如果這里的子查詢來自當前映射文件--><association property="bt" column="type_id" select="findTypeByTypeId"></association><!--如果這里的子查詢來自于其他dao中的方法--><!--<association property="bookType" column="type_id" select="com.hqyj.ssm02.dao.BookTypeDao.findById"></association>-->
    </resultMap><!--3.根據type_id查詢完整對象-->
    <select id="findTypeByTypeId" resultType="com.hqyj.ssm02.entity.BookType">select * from book_type where type_id=#{typeId}
    </select>
    

一對多查詢

一對多表示一個主表實體對應多個從表實體。如一個圖書類型對應多本圖書。

在主表對應的實體類中,額外添加一個屬性:多個從表對象的集合

public class BookType {private int typeId;private String typeName;private List<BookInfo> books;
}

dao中的方法

BookType queryBooksByType(int typeId);

sql映射文件

  • 連接查詢

    <!--一對多查詢,方式一:連接查詢-->
    <select id="queryBooksByType" resultMap="testMap">select *from book_info biinner join book_type bt on bi.type_id = bt.type_idwhere bi.type_id = #{typeId}
    </select>
    <!--自定義結果集映射-->
    <resultMap id="testMap" type="com.hqyj.ssm02.entity.BookType"><id property="typeId" column="type_id"></id><result property="typeName" column="type_name"></result><!--集合類型的屬性,使用collection標簽,使用ofType設置集合中的對象類型--><collection property="books" ofType="com.hqyj.ssm02.entity.BookInfo"><id column="book_id" property="bookId"></id><result column="book_name" property="bookName"></result><result column="type_id" property="typeId"></result><result column="book_author" property="bookAuthor"></result><result column="book_num" property="bookNum"></result><result column="book_price" property="bookPrice"></result><result column="book_img" property="bookImg"></result><result column="publisher_date" property="publisherDate"></result></collection>
    </resultMap>
    
  • 子查詢

    <!--一對多查詢,方式二:子查詢-->
    <!--1.根據類型編號查詢自身表-->
    <select id="queryBooksByType" resultMap="testMap">select *from book_typewhere type_id = #{typeId}
    </select>
    <!--2.設置結果集映射-->
    <resultMap id="testMap" type="com.hqyj.ssm02.entity.BookType"><id column="type_id" property="typeId"></id><result column="type_name" property="typeName"></result><!--集合對象,使用collection標簽。使用type_id字段執行子查詢getBooksByTypeId,將結果映射到books屬性--><collection property="books" column="type_id" select="getBooksByTypeId"></collection>
    </resultMap>
    <!--3.子查詢,根據類型編號查詢所有對應的圖書集合-->
    <select id="getBooksByTypeId" resultType="com.hqyj.ssm02.entity.BookInfo">select *from book_infowhere type_id = #{typeId}
    </select>

總結

  • 多對一

    如果查詢要以從表數據為主,關聯主表相應的數據時,屬于多對一查詢。如查詢所有圖書的同時,顯示其類型。

    建議使用子查詢或自定義特殊的sql語句。都需要在從表實體中添加一個主表對象屬性。

    • 主表字段比較少,建議使用自定義特殊的sql語句,保證字段重命名為"主表對象.屬性"

    • 主表字段比較多,建議使用子查詢,使用<association>標簽映射主表對象屬性

  • 一對多

    如果查詢要以主表數據為主,關聯該主鍵對應的從表實體對象的集合時,屬于一對多查詢。如查詢某個類型,同時顯示該類型下的所有圖書。

    建議使用子查詢。在主表實體中添加一個從表集合屬性。

    使用<collection>標簽映射從表集合屬性

多條件查詢

參考#{}的用法

#{}和${}

在mybatis的sql映射文件中,可以使用#{}和${}表示sql語句中的參數。

#{}相當于預處理,會將參數用?占位后傳值

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WKsexlaL-1676548026836)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201174100891.png)]

${}相當于拼接,會將參數原樣拼接

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ybcWixzA-1676548026836)(D:/qqFileRecv/框架/框架/SSM04.assets/image-20230201174131695.png)]

通常使用#{},防止sql注入

-- #{}會自動用''將參數引起來,如參數為admin
select * from user where username=#{username}
select * from user where username='admin'
-- ${}會將參數原樣拼接,如參數為admin
select * from user where username=${username}
select * from user where username=admin

#{}的使用

  • 如果dao層接口中的方法參數為一個原始類型或字符串時

    • sql語句#{}中的參數是接口中方法的形參名
    public interface UserDao{User findById(int userId);
    }
    
    <select id="findById" resultType="com.xxx.entity.User">select * from user where id=#{userId}
    </select>
    
  • 如果dao層接口中的方法參數為一個實體對象時

    • sql語句#{}中的參數必須是接口中方法參數的某個屬性
    public interface UserDao{User login(User user);
    }
    
    <select id="login" resultType="com.xxx.entity.User">select * from user where username=#{username} and password=#{password}
    </select>
    
  • 如果dao層接口中的方法參數不止一個時

    • 給每個參數添加@Param(“自定義參數名”)

    • sql語句#{}中的參數必須是@Param注解中自定義的名稱

    public interface UserDao{User update(@Param("bianhao")int id,@Param("mima")String password);
    }
    
    <update id="update">update user set password=#{mima} where id=#{bianhao}
    </update>
    

${}的使用

當需要拼接的參數不能帶引號時,必須使用${},如動態表名、排序條件等

動態表名

select * from ${表名}

排序條件

select * from 表名 order by ${字段}

刪除<delete></delete>

刪除單個

delete from 表 where 字段 = 值

只需一個值即可,通常為主鍵id

dao層接口

int delete(int id);

sql映射文件

<delete id="delete">delete from book_info where book_id=#{id}
</delete>

刪除多個

delete from 表 where 字段 in(數據集合)

如delete from book_info where book_id in (1001,1002,1005)

in后面的內容通常是一個數組

前端頁面通過復選框選中要刪除的數據,獲取選中的數據的id,保存到一個數組中

dao層接口

int deleteByIds(@Param("idList")List<Integer> idList)

sql映射文件

<delete id="deleteByIds">delete from book_info where book_id in <!-- foreach標簽用戶遍歷dao層傳遞的集合對象collection表示要遍歷的集合open和close表示將遍歷出的數據使用什么開頭和結尾separator表示將遍歷出的數據用什么分隔--><foreach collection="idList" item="id" separator="," open="(" close=")">#{id}</foreach>
</delete>

foreach標簽可以用于sql語句中條件是集合的情況,配合where條件使用,dao層接口中方法參數為集合。

如where 字段 in/not in (值1,值2,值3)

添加<insert></insert>

添加時的參數雖多,但通常為一個完整的實體對象,所以dao層接口中方法的參數要定義成一個實體對象。

dao層

int insert(BookType bookType);

sql映射文件

  • 通常加上parameterType參數指定添加的對象實體類全限定名
  • #{}中的參數一定來自于dao層接口方法實體參數對象的屬性
  • 如果要在添加成功的同時,獲取自動增長的主鍵值時,要添加
    • useGeneratedKeys=“true” 表示自動獲取自增的值
    • keyColumn=“type_id” 表示自增字段
    • keyProperty=“typeId” 表示自增字段對應的屬性名
<insert id="testInsert" useGeneratedKeys="true" keyColumn="type_id" keyProperty="typeId"parameterType="com.hqyj.ssm02.entity.BookType">insert into book_typevalues (null, #{typeName})
</insert>

修改<update></update>

修改可以分為修改所有字段和修改部分字段

修改所有字段

dao層

int update(BookInfo bookInfo);

sql映射文件

<update id="update">update book_info setbook_name = #{bookName},book_author = #{bookAuthor},book_num = #{bookNum},book_price = #{bookPrice},publisher_date = #{publisherDate}where book_id=#{bookId}
</update>

這種方式會修改所有字段,如果實體參數的某個屬性沒有賦值,就會用該屬性的默認值進行修改。

如String類型的屬性沒有賦值,就會用null修改,int類型用0修改,如果表中該字段非空,就會導致sql執行異常。

所以修改所有字段,參數為一個完整對象時,保證其中的屬性都有值。

修改部分字段

方式一:dao層只寫要修改的字段

dao

int updateSth(int bookId,int bookPrice,int bookNum);

sql映射文件

<update id="updateSth">update book_info setbook_num = #{bookNum},book_price = #{bookPrice}where book_id = #{bookId}
</update>

方式二:dao層寫完整對象,sql語句中判斷字段是否有值

dao

int updateSth(BookInfo bookInfo);

sql映射文件

<update id="updateSth">update book_info set<if test="bookPrice!=0">book_price = #{bookPrice},</if><if test="bookName!=null">book_name=#{bookName}</if>where book_id = #{bookId}
</update>

這樣寫,有以下幾個問題

1.如果只有第一個條件滿足,后續條件都不滿足,最終拼接的sql語句,會在where關鍵字之前多出一個逗號,導致語法錯誤

解決方式:將set替換成<set>標簽,mybatis會自動去除最后的逗號

2.替換為<set>標簽后,如果所有條件都不滿足,mybatis會自動去除set部分,sql語句就會變為update book_info where book_id=?,導致語法錯誤

解決方式:在<set>標簽中,添加一個不影響原始數據的條件,如 book_id = #{bookId},只需要一個book_id參數,sql語句沒有語法錯誤,對原始數據沒有任何影響

動態SQL

  • <set>搭配<if>用于修改

    <!--動態SQL:set-if用于修改-->
    <update id="testUpdate">update book_info<set>book_id=#{bookId},<if test="bookName!=null">book_name = #{bookName},</if><if test="bookPrice!=null">book_price = #{bookPrice}</if></set>where book_id=#{bookId}
    </update>
    
  • <where>搭配<if>用于查詢、修改、刪除時的條件

    <select id="queryByCondition" resultType="com.xxx.entity.BookInfo">select * from book_info<where><if test="bookName!=null">book_name = #{bookName}</if><if test="bookAuthor!=null">and book_author = #{bookAuthor}</if><if test="typeId!=null">and type_id = #{typeId}</if></where>
    </select>
    
  • <trim>搭配<if>可以替換set-if和where-if

    該標簽有四個屬性

    prefix 表示如果trim標簽中有if條件滿足,就在整個trim部分添加指定前綴

    suffix 表示如果trim標簽中有if條件滿足,就在整個trim部分添加指定后綴

    prefixOverrides 表示去除整個trim部分多余的前綴

    suffixOverrides 表示去除整個trim部分多余的后綴

使用trim實現修改

可以修改所有字段,也可以修改部分字段

<update id="testUpdate">update book_info<!--prefix="set"表示在所有內容前加入set關鍵字--><!--suffixOverrides=","表示所有內容之后如果有多余的逗號,去掉逗號--><trim prefix="set" suffixOverrides=",">book_id=#{bookId},<if test="bookName!=null">book_name = #{bookName},</if><if test="bookAuthor!=null">book_author = #{bookAuthor},</if><if test="bookNum!=null">book_num = #{bookNum},</if><if test="bookPrice!=null">book_price = #{bookPrice},</if><if test="publisherDate!=null">publisher_date = #{publisherDate}</if></trim>where book_id=#{bookId}
</update>

使用trim標簽實現多條件查詢

<select id="queryByCondition" resultType="com.hqyj.ssm02.entity.BookInfo">SELECT bi.*,type_name as 'bookType.typeName' FROM book_info bi,book_type bt<!--prefix="where"表示在所有內容前加入where關鍵字--><!--prefixOverrides="and"表示所有內容之前如果有多余的and,去掉and--><!--suffix="order by book_id desc"表示在所有內容之后加入order by book_id desc--><trim prefix="where" prefixOverrides="and" suffix="order by book_id desc">bi.type_id=bt.type_id<if test="bookName!=null">and book_name like concat ('%',#{bookName},'%')</if><if test="bookAuthor!=null">and book_author like concat ('%',#{bookAuthor},'%')</if><if test="typeId!=0">and bt.type_id =#{typeId}</if></trim>
</select>

多選刪除具體實現

頁面核心js

$(function () {//一鍵全選按鈕$("#checkAll").click(function () {var state = this.checked;$(".checkDel").each(function () {this.checked = state;});});//刪除所選按鈕$("#deleteAll").click(function () {//定義保存id的數組var ids = [];//獲取當前被選中的復選框所在的trlet $tr = $(".checkDel:checked").parent().parent();//遍歷所選的tr$tr.each(function () {//獲取id對應的td的值var id = $(this).children("td:eq(1)").text();//保存到數組中ids.push(id);});//至少選中一項if (ids.length == 0) {alert("請至少選中一項");return;}if (!confirm("確認要刪除這" + ids.length + "條數據嗎")) {return;}//使用ajax訪問controller刪除所選$.ajax({url: "${pageContext.request.contextPath}/bookInfo/deleteByChecked",data: {"ids": ids},type: "post",//ajax提交數組,需要添加一個參數traditional: true,success: function () {location.reload();}});});
});

dao

//批量刪除
int deleteByIds(@Param("idList") List<Integer> idList);

mapper.xml

<!--批量刪除-->
<delete id="deleteByIds">delete from book_info where book_id in<foreach collection="idList" item="id" open="(" close=")" separator=",">#{id}</foreach>
</delete>

service

public void deleteByChecked(Integer[] ids) {//將數組轉換為集合List<Integer> list = Arrays.asList(ids);bookInfoDao.deleteByIds(list);
}

controller

@RequestMapping("/deleteByChecked")
public String deleteByChecked(Integer[] ids){bookInfoService.deleteByChecked(ids);return "redirect:queryAll";
}

多條件查詢具體實現

搜索表單

<form class="navbar-form navbar-left"action="${pageContext.request.contextPath}/bookInfo/queryByCondition"><div class="form-group"><input type="text" class="form-control" name="bookName" placeholder="請輸入書名關鍵字"><input type="text" class="form-control" name="bookAuthor" placeholder="請輸入作者關鍵字"><select id="topSelect" class="form-control" name="typeId"><option value="0">全部</option></select></div><button type="submit" class="btn btn-default">搜索</button>
</form>

dao

List<BookInfo> queryByCondition(BookInfo bookInfo);

mapper.xml

<select id="queryByCondition" resultType="com.hqyj.ssm02.entity.BookInfo">SELECT bi.*,type_name as 'bookType.typeName' FROM book_info bi,book_type bt<trim prefix="where" prefixOverrides="and" suffix="order by book_id desc">bi.type_id=bt.type_id<if test="bookName!=null">and book_name like concat ('%',#{bookName},'%')</if><if test="bookAuthor!=null">and book_author like concat ('%',#{bookAuthor},'%')</if><if test="typeId!=0">and bt.type_id =#{typeId}</if></trim>
</select>

service

public List<BookInfo> queryByCondition(BookInfo bookInfo) {return bookInfoDao.queryByCondition(bookInfo);
}

controller

@RequestMapping("/queryByCondition")
public String queryByCondition(BookInfo bookInfo,Model model) {List<BookInfo> list= bookInfoService.queryByCondition(bookInfo);model.addAttribute("list",list);return "bookInfoList";
}

分頁

使用分頁組件PageHelper

1.導入依賴

<!--分頁組件-->
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.2</version>
</dependency>

2.在mybatis的配置文件中

<!--設置分頁插件-->
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><!--保證翻頁不會超出范圍--><property name="reasonable" value="true"/></plugin>
</plugins>

3.使用

  • 通過PageHelper類調用靜態方法startPage(當前頁數,每頁顯示的記錄數)開啟分頁
  • 查詢所有,返回集合
  • 創建PageInfo分頁模型對象,構造方法的參數為查詢出的集合,設置泛型,
//定義當前頁和每頁顯示的數量
int pageNum=1;
int size=10;
//開啟分頁
PageHelper.startPage(pageNum,size);
//正常調用查詢,返回查詢到的數據集合
BookInfo bookInfo = new BookInfo();
bookInfo.setTypeId(1);
List<BookInfo> list = bookInfoDao.queryByCondition(bookInfo);//創建分頁模型對象,構造方法的參數為查詢出的結果,設置泛型,
PageInfo<BookInfo> pageInfo = new PageInfo<>(list);//此時分頁相關數據都保存在pageInfo對象中
System.out.println("總記錄數"+pageInfo.getTotal());
System.out.println("最大頁數"+pageInfo.getPages());
System.out.println("當前頁"+pageInfo.getPageNum());
System.out.println("當前容量"+pageInfo.getSize());
System.out.println("當前分頁數據"+pageInfo.getList());
System.out.println("是否有上一頁"+pageInfo.isHasPreviousPage());
System.out.println("是否有下一頁"+pageInfo.isHasNextPage();
System.out.println("是否是首頁"+pageInfo.isIsFirstPage());
System.out.println("是否是尾頁"+pageInfo.isIsLastPage());
PageInfo對象常用屬性和方法 作用
total/getTotal() 得到總記錄數
pages/getPages() 得到最大頁數
pageNum/getPageNum() 得到當前頁
size/getSize() 得到每頁顯示的記錄數
list/getList() 得到按當前page和size查詢到的數據集合
isFirstPage/isIsFirstPage() 是否是首頁
isLastPage/isIsLastPage() 是否是尾頁

多條件分頁具體實現

controller

@RequestMapping("/queryByCondition")
public String queryByCondition(@RequestParam(defaultValue = "1") int pageNum,@RequestParam(defaultValue = "8") int size,BookInfo bookInfo,Model model) {//1.PageHelper.startPage()PageHelper.startPage(pageNum, size);//2.查詢集合List<BookInfo> list = bookInfoService.queryByCondition(bookInfo);//3.PageInfo(集合)PageInfo<BookInfo> pageInfo = new PageInfo<>(list);//將查詢到的分頁模型對象保存到model中model.addAttribute("pageInfo",pageInfo);//構造頁數的集合ArrayList<Integer> pageList = new ArrayList<>();for (int i = 1; i <= pageInfo.getPages(); i++) {pageList.add(i);}model.addAttribute("pageList",pageList);return "bookInfoList";
}

頁面分頁組件

<nav aria-label="Page navigation"><ul class="pagination"><c:if test="${!pageInfo.isFirstPage}"><li><a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum-1}"aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li></c:if><c:forEach items="${pageList}" var="pno"><li class="pno"><a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pno}">${pno}</a></li></c:forEach><c:if test="${!pageInfo.isLastPage}"><li><a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum+1}"aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li></c:if></ul>
</nav>

SpringBoot

Spring推出的一個Spring框架的腳手架。

不是一個新的框架,而是搭建Spring相關內容框架的平臺。

它省去了Spring、SpringMVC項目繁瑣的配置過程,讓開發變得更加簡單。

本質還是Spring+SpringMVC,可以搭配其他的ORM框架,如MyBatis、MyBatisPlus、JPA、Hibernate等。

特點

  • 內置了Tomcat服務器,不需要部署項目到Tomcat中
  • 內置了數據源Hikari
  • 減少了jar文件依賴的配置
  • SpringBoot中的配置文件可以使用yml格式文件,代替properties或xml

創建SpringBoot項目

通過IDEA創建

通過官網模板創建

官網模板https://start.spring.io/

點擊生成,會下載一個壓縮文件,解壓后通過IDEA打開。

無論哪種方式,都需要重寫設置Maven配置文件

創建成功后的目錄結構

第一個springboot項目的helloworld

熱部署

項目在開發過程中,可以不需要每次都重啟,等待一段時間后會自動更新編譯運行

使用

添加依賴,可以在創建的項目的時候選擇,也可以中途添加

<!--熱部署-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><version>2.7.8</version>
</dependency>

開啟熱部署

Lombok

用于簡化實體類中模板代碼的工具

使用

添加依賴,可以在創建的項目的時候選擇,也可以中途添加

<!--Lombok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope>
</dependency>

安裝插件(IDEA2020.2之后的版本會內置Lombok插件,無需安裝)

  • IDEA插件官網Versions: Lombok - IntelliJ IDEs Plugin | Marketplace (jetbrains.com)

  • IDEA內置插件市場搜索

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xEZQPWWk-1676548026840)(D:/qqFileRecv/框架/框架/day10-SpringBoot+MyBatis1.assets/image-20230203140602578.png)]

在某個實體類上添加注解

lombok常用注解 作用
@AllArgsConstructor 自動生成全參構造方法
@Data 以下注解之和
@Setter 自動生成set方法
@Getter 自動生成get方法
@NoArgsConstructor 自動生成無參構造方法
@ToString 自動生成toString方法
@EqualsAndHashcode 自動生成equals和hashcode方法

SpringBoot+MyBatis實現單表查詢

1.創建好SpringBoot項目

最好在創建的時候選擇以下依賴

  • spring-web(必選)
  • lombok
  • spring-devtools
  • springboot集成mybatis
  • mysql驅動

都可以后續添加

<!--web-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!--熱部署-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><version>2.7.8</version>
</dependency><!--Lombok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope>
</dependency>

2.在pom.xml中添加mybatis集成SpringBoot依賴和數據庫驅動

<!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version>
</dependency>
<!--springboot集成MyBatis-->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version>
</dependency>

3.在springboot配置文件application.properties中

#注釋
#.properties文件稱為屬性文件,數據以鍵值對"鍵=值"的形式保存
#設置項目啟動端口號
#server.port=9090
#設置項目上下文路徑
#server.servlet.context-path=/springbootday1#mybatis相關配置
#開啟駝峰命名映射
mybatis.configuration.map-underscore-to-camel-case=true
#打印sql語句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#掃描mybatis的sql映射文件(將mapper.xml文件保存在resources目錄下的mapper目錄下)
mybatis.mapper-locations=classpath:mapper/*.xml#數據庫信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

mybatis的sql映射文件模板

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--設置該文件對應的dao層接口全限定名-->
<mapper namespace=""></mapper>

4.根據數據表創建實體類、dao層接口、service、controller

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-H1SwbwcE-1676548026840)(D:/qqFileRecv/框架/框架/day10-SpringBoot+MyBatis1.assets/image-20230203154807705.png)]

Hero

@Data
public class Hero{private Integer id;private String name;private String position;private String sex;private Double price;private String shelfDate;
}

dao

@Repository
public interface HeroDao{List<Hero> queryAll();
}

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--設置該文件對應的dao層接口全限定名-->
<mapper namespace="com.xxx.dao.HeroDao"><select id="queryAll" resultType="com.xxx.entity.Hero">select * from hero</select>
</mapper>

service

@Service
public class HeroService{@Autowiredprivate HeroDao heroDao;public List<Hero> queryAll(){return heroDao.queryAll();   }
}

controller

@Controller
@RequestMapping("/hero")
public class HeroController{@Autowiredprivate HeroService heroService;@RequestMapping("/queryAll")@ResponseBodypublic List<Hero> queryAll(){return heroDao.queryAll();   }
}

5.在SpringBoot的啟動類上,加入@MapperScan注解,掃描dao層所在根包

@SpringBootApplication
//掃描dao層所在的根包
@MapperScan("com.hqyj.first.dao")
public class FirstApplication {public static void main(String[] args) {SpringApplication.run(FirstApplication.class, args);}}

啟動項目,按自定的項目上下文路徑和端口號訪問某個controller中的方法

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3V572UD0-1676548026841)(D:/qqFileRecv/框架/框架/day10-SpringBoot+MyBatis1.assets/image-20230203155852438.png)]

SpringBoot+LayUI實現酒店客房管理

核心知識點

  • SpringBoot項目搭建

    • 核心依賴

      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
    • 集成MyBatis

      <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version>
      </dependency>
      
  • 新注解

    • @RestController

      如果某個控制器類中的所有方法都要加@ResponseBody,可以在類上加@ResponseBody,也可以用@RestController代替@ResponseBody和@Controller

  • LayUI

    • 數據表格
      • 數據接口格式
      • 真假分頁
      • 頭工具欄事件
      • 行內事件
      • 單元格編輯事件
    • 彈出層
      • layer.msg()
      • layer.confirm()
      • layer.open()
  • ajax

    $.ajax({url:"",data:xxx,type:"get/post",success:function(){}
    })
    
  • 前后端分離

    • 該案例可以將頁面獨立出來,成為一個前后端分離項目,也可以將頁面作為靜態資源保存在static目錄下
  • 如果設計為前后端分離,要在控制器類上加入@CrossOrigin,表示該類中的所有方法允許跨域請求

  • 打包SpringBoot項目

    保證項目中無錯誤,包含單元測試中

? 打包后是一個.jar文件,位于target目錄中

? 在安裝有java環境的機器中,控制臺運行jar文件

java -jar 文件名.jar

數據庫部分設計

數據庫表詳細設計

核心Java代碼

實體類

訂單表

/*
* 訂單
* */
@Data
public class Orders {private Integer id;private Integer roomNo;private Integer cusId;//@JsonFormat是springboot集成的jackson包中的注解,用于格式化日期//pattern指定格式日期 timezone指定時區,這里和數據庫的時區保持一致@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")private Date leaveTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")private Date stayTime;private Integer cost;//顯示訂單的同時,要顯示房間的相關信息private Room room;//顯示訂單的同時,要顯示客戶的相關信息private Customer customer;
}

數據訪問層

  • 實現隨意修改某個字段的值

    int update(@Param("field") String field,@Param("value") String value,@Param("id") int id);
    

    sql映射文件

    <update id="update">update room<set><if test="field=='roomType'">room_type= #{value}</if><if test="field=='roomPrice'">room_price= #{value}</if><if test="field=='roomUse'">room_use=#{value}</if></set>where room_no=#{id}
    </update>
    
  • 添加時獲取自增的值

    int insert(Customer customer);
    

    sql映射文件

    <!--添加顧客的同時,獲取自增的id-->
    <insert id="insert" useGeneratedKeys="true" keyColumn="cus_id" keyProperty="cusId">insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard})
    </insert>
    
  • 多表連接查詢/條件查詢

    List<Orders> queryAll();List<Orders> search(String keyword);//根據客戶編號查詢是否已入住
    Orders isStay(Integer cusId);//根據房間號查詢正在入住的訂單信息
    Orders findCheckInByRoomNo(Integer roomNo);//計算費用
    int checkOut(@Param("roomNo") Integer roomNo,@Param("cost") Integer cost);
    

    sql映射文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--設置該文件對應的dao層接口全限定名-->
    <mapper namespace="com.hqyj.hotel_sys.dao.OrdersDao"><insert id="insert">insert into ordersvalues (null, #{roomNo}, #{cusId}, now(), null, 0)</insert><!--關聯查詢方式一:特殊的SQL語句--><!--<select id="queryAll" resultType="com.hqyj.hotel_sys.entity.Orders">select o.*,cus_name as 'customer.cusName',cus_phone as 'customer.cusPhone',cus_idcard as 'customer.cusIdcard',room_type as 'room.roomType',room_price as 'room.roomPrice',room_use as 'room.roomUse'from customer c,room r,orders owhere c.cus_id = o.cus_idand r.room_no = o.room_no</select>--><!--多表連接條件查詢--><select id="search" resultMap="ordersMap">SELECT * FROM orders o,customer c<trim prefix="where" prefixOverrides="and">o.cus_id = c.cus_id<if test="keyword!=null">and cus_name like concat('%',#{keyword},'%')</if></trim></select><!--關聯查詢方式二:子查詢--><!--1.查詢自身表--><select id="queryAll" resultMap="ordersMap">select *from orders</select><!--自定義結果集映射,用cus_id和room_no做子查詢,重新映射一次到orders對象--><resultMap id="ordersMap" type="com.hqyj.hotel_sys.entity.Orders"><result property="roomNo" column="room_no"></result><result property="cusId" column="cus_id"></result><association property="room" column="room_no" select="findRoomByNo"></association><association property="customer" column="cus_id" select="findCustomerById"></association></resultMap><!--子查詢一:根據房號查房間--><select id="findRoomByNo" resultType="com.hqyj.hotel_sys.entity.Room">select *from roomwhere room_no = #{roomNo}</select><!--子查詢二:根據編號查客戶--><select id="findCustomerById" resultType="com.hqyj.hotel_sys.entity.Customer">select *from customerwhere cus_id = #{cusId}</select><select id="isStay" resultType="com.hqyj.hotel_sys.entity.Orders">select *from orderswhere cus_id = #{cusId}and leave_time is null</select><!--根據房號查詢正在入住的訂單信息,包含房間價格--><select id="findCheckInByRoomNo" resultType="com.hqyj.hotel_sys.entity.Orders">select o.*, room_price as 'room.roomPrice'from orders o,room rwhere o.room_no = r.room_noand r.room_no = #{roomNo}and leave_time is null</select><update id="checkOut">update ordersset leave_time=now(),cost=#{cost}where room_no = #{roomNo}and leave_time is null</update>
    </mapper>
    

業務流程層

package com.hqyj.hotel_sys.service;import com.hqyj.hotel_sys.dao.CustomerDao;
import com.hqyj.hotel_sys.dao.OrdersDao;
import com.hqyj.hotel_sys.dao.RoomDao;
import com.hqyj.hotel_sys.entity.Customer;
import com.hqyj.hotel_sys.entity.Orders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Date;
import java.util.List;@Service
public class CustomerService {@Autowiredprivate CustomerDao customerDao;@Autowiredprivate RoomDao roomDao;@Autowiredprivate OrdersDao ordersDao;/** 入住* 1.獲取顧客信息:姓名、電話、身份證號,*   根據身份證號判斷是否存在,如果存在,用存在的對象*   如果不存在,向customer表中添加一條記錄 insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard})*   添加成功時,要獲取自增的id,用于訂單表中使用*   判斷該顧客是否已入住:查詢訂單表中對應顧客編號的記錄,如果退房時間為空,說明已入住*   select * from orders where cus_id=#{cusId} and leave_time is null* 2.將對應房間的狀態改為1,對room表中修改 update room set room_use=1 where room_no=#{roomNo}* 3.向訂單表中添加一條記錄 insert into orders values (null,#{roomNo},#{cusId},now(),null,0)** 以上3個步驟屬于一個事務,要在該方法上加事務注解* */@Transactional//讓該方法成為一個事務,如果執行中途出錯,會自動回滾public boolean checkIn(Customer customer, int roomNo) {//1.添加客戶,根據身份證號判斷是否存在Customer byIdcard = customerDao.findByIdcard(customer.getCusIdcard());boolean b1;if (byIdcard == null) {//不存在,調用添加b1 = customerDao.insert(customer) > 0;} else {customer = byIdcard;b1 = true;}//查看是否已入住if (ordersDao.isStay(customer.getCusId()) != null) {return false;}//2.修改房間狀態boolean b2 = roomDao.update("roomUse", "1", roomNo) > 0;//3.添加訂單信息//創建訂單對象Orders orders = new Orders();//客戶編號在添加客戶成功后,會自動獲取orders.setCusId(customer.getCusId());orders.setRoomNo(roomNo);boolean b3 = ordersDao.insert(orders) > 0;return b1 & b2 & b3;}/** 退房* 1.根據房間號查詢對應正在入住的訂單記錄(同時查詢出房價)* select o.* from orders o,room r where o.room_no=r.room_no and r.room_no =#{roomNo} and leave_time is null* 2.結算* 添加退房時間、添加花費* update orders set leave_time =now() ,cost=#{cost} where room_no =#{roomNo} and leave_time is null* 3.修改房間狀態為空閑* update room set room_use = 0 where room_no=#{roomNo}* */@Transactionalpublic boolean checkOut(Integer roomNo) {//1.根據房間號查詢對應正在入住的訂單記錄Orders orders = ordersDao.findCheckInByRoomNo(roomNo);//2.計算費用Date stayTime = orders.getStayTime();Date now = new Date();double l = now.getTime() - stayTime.getTime();//轉換為天數double day = Math.ceil(l / 1000 / 3600 / 24);double cost = orders.getRoom().getRoomPrice() * day;//修改訂單中的花費和退房時間//update orders set leave_time=now(),cost=#{cost} where room_no=#{roomNo} and leave_time is nullboolean b1 = ordersDao.checkOut(roomNo, (int) cost) > 0;//3.修改房間狀態為空閑0//update room set room_use where room_no = #{roomNo}boolean b2 = roomDao.update("roomUse", "0", roomNo) > 0;return b1 & b2;}
}

控制層

如果某個控制器中的所有方法都需要返回JSON格式字符串,在類上加@ResponseBody

@Controller
@RequestMapping("/room")
public class RoomController {@Autowiredprivate RoomService roomService;@ResponseBody@RequestMapping("/queryAll")public ResultData<Room> queryAll(Integer page, Integer limit) {//使用PageHelper分頁PageHelper.startPage(page, limit);//正常查詢所有List<Room> rooms = roomService.queryAll();//創建分頁模型對象PageInfo<Room> pageInfo = new PageInfo<>(rooms);//返回的數據為分頁后的集合,數量為總記錄數return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList());}
}

LayUI數據表格

數據表格所需的數據接口格式為

{code:0,msg:"",count:1000,data:[{},{}]
}

構造滿足LayUI數據表格的數據模型類ResultData

/*
* 定義一個用于LayUI數據表格對應格式的模板類
* code  狀態碼         0成功
* msg   提示文字
* count 數據總量
* data  數據集合
* */
@Data
public class ResultData<T> {private Integer code;private String msg;private Integer count;private List<T> data;/** 定義帶count和data的構造方法,用于初始化code和msg* */public ResultData(Integer count, List<T> data) {code=0;msg="";this.count = count;this.data = data;}
}

最后在控制層中,將查詢的方法的返回值更改為ResultData類型

@RequestMapping("/queryAll")
public ResultData<Room> queryAll() {List<Room> rooms = roomService.queryAll();return new ResultData<>(rooms.size(), rooms);
}

最終頁面

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>LayUI</title><meta name="renderer" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all"><!-- 注意:如果你直接復制所有代碼到本地,上述css路徑需要改成你本地的 -->
</head>
<body>
<table class="LayUI-hide" id="test" lay-filter="test"></table>
<script type="text/html" id="toolbarDemo"><div class="LayUI-btn-container"><button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckData">獲取選中行數據</button><button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckLength">獲取選中數目</button><button class="LayUI-btn LayUI-btn-sm" lay-event="isAll">驗證是否全選</button><button class="LayUI-btn LayUI-btn-sm" lay-event="addRoom">添加客房</button></div>
</script>
<script type="text/html" id="barDemo"><!--插值表達式-->{{# if(d.roomUse==0){ }}<a class="LayUI-btn LayUI-btn-normal LayUI-btn-sm" lay-event="check-in">入住</a><a class="LayUI-btn LayUI-btn-danger LayUI-btn-sm" lay-event="del">刪除</a>{{# }else{ }}<a class="LayUI-btn LayUI-btn-warm LayUI-btn-sm" lay-event="check-out">退房</a>{{# } }}
</script><script src="../LayUI/LayUI.js" charset="utf-8"></script>
<!-- 注意:如果你直接復制所有代碼到本地,上述 JS 路徑需要改成你本地的 -->
<script>LayUI.use('table', function () {var table = LayUI.table;//引入Jqueryvar $ = LayUI.$;/** 渲染表格數據* url:數據訪問接口* cols:表格列* field:數據字段名* title:表頭* cellminwidth:100  自適應寬度* fixed:left    固定在某側* unresize:true 不可改變尺寸* sort:true 排序* edit:true 行內編輯* */table.render({elem: '#test'//設置數據接口, url: 'http://localhost:9090/hotel/room/queryAll', toolbar: '#toolbarDemo' //開啟頭部工具欄,并為其綁定左側模板, defaultToolbar: ['filter', 'exports', 'print', { //自定義頭部工具欄右側圖標。如無需自定義,去除該參數即可title: '提示', layEvent: 'LAYTABLE_TIPS', icon: 'LayUI-icon-tips'}], title: '用戶數據表', cols: [[{type: 'checkbox', fixed: 'left'}, {field: 'roomNo', title: '房間號', cellminwidth: 100, fixed: 'left', unresize: true, sort: true}, {field: 'roomType', title: '房間類型', cellminwidth: 100, edit: 'text'}, {field: 'roomPrice', title: '房間單價', cellminwidth: 100, edit: 'text', sort: true}, {field: 'roomUse', title: '使用狀態', cellminwidth: 100,templet: function (res) {return res.roomUse == 0 ? "<span style='color:green'>空閑中</span>" : "<span style='color:red'>使用中</span>";}}, {fixed: 'right', title: '操作', toolbar: '#barDemo', cellminwidth: 100,}]]//開啟分頁組件, page: true//設置每頁顯示記錄數的下拉選項, limits: [5, 10, 20]//默認每頁顯示的條數,沒有設置默認為10, limit: 10//解析數據表格url請求后的數據, parseData: function (res) {//res就是url對應的數據/*//假分頁var result;if (this.page.curr) {result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr);} else {result = res.data.slice(0, this.limit);}*/return {"code": res.code,"msg": res.msg,"count": res.count,"data": res.data}}});//頭工具欄事件table.on('toolbar(test)', function (obj) {var checkStatus = table.checkStatus(obj.config.id);switch (obj.event) {case 'addRoom':layer.open({title: '添加客房',type: 2,content: 'addRoom.html',area: ['350px', '250px'],resize: false,anim: 2,/* success: function(layero, index){console.log(layero, index);}*/})break;case 'getCheckData'://data是所選數據的集合var data = checkStatus.data;for (var i = 0; i < data.length; i++) {console.log(data[i].roomNo)}// layer.alert(JSON.stringify(data));break;case 'getCheckLength':var data = checkStatus.data;layer.msg('選中了:' + data.length + ' 個');break;case 'isAll':layer.msg(checkStatus.isAll ? '全選' : '未全選');break;//自定義頭工具欄右側圖標 - 提示case 'LAYTABLE_TIPS':layer.alert('這是工具欄右側自定義的一個圖標按鈕');break;};});//監聽單元格編輯事件table.on('edit(test)', function (obj) {// console.log(obj);//layer.confirm("提示文件",function(){確認觸發},function(){取消觸發})layer.confirm('確認要修改嗎', function (index) {//使用ajax提交要修改的字段名、修改后的值、要修改的編號$.ajax({url: "http://localhost:9090/hotel/room/update",data: {"field": obj.field,//要修改的字段"value": obj.value,//修改后的值"id": obj.data.roomNo//要修改的id},success: function () {//修改成功,關閉確認框layer.close(index);}});}, function () {//修改失敗,重新加載location.reload()})})//監聽行工具事件table.on('tool(test)', function (obj) {//data就是當前行中的數據var data = obj.data;// console.log(obj)if (obj.event === 'del') {//彈出確認框layer.confirm('真的刪除行么', function (index) {//使用ajax提交要刪除的id$.ajax({url: "http://localhost:9090/hotel/room/delete",data: {"delId": data.roomNo},success: function (res) {if (res) {//前端假刪除obj.del();//關閉確認框layer.close(index);}}});});} else if (obj.event === 'check-in') {//彈出輸入顧客信息表單layer.open({title: '輸入顧客信息',type: 2,content: 'addCustomer.html',area: ['350px', '400px'],resize: false,anim: 2,//彈出窗口后的回調函數success: function (layero, index) {//彈出成功后,在彈出頁面中加入當前點擊行的roomNo//獲取彈出層的body部分var body = layer.getChildFrame('body', index);//獲取彈出層body中的隱藏域,給其賦值body.find("input[name=roomNo]").val(data.roomNo);}})} else if (obj.event === 'check-out') {layer.confirm("確定要退房嗎", function () {$.ajax({url: "http://localhost:9090/hotel/customer/checkOut",data: {"roomNo": data.roomNo},success: function (res) {if (res) {location.reload();}}});})}});});
</script></body>
</html>

LayUI添加頁面

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all"></head><body><form class="LayUI-form" action=""><div class="LayUI-form-item"><label class="LayUI-form-label">房間類型</label><div class="LayUI-input-inline"><input type="text" name="roomType" required  lay-verify="required" placeholder="請輸入房間類型" autocomplete="off" class="LayUI-input"></div></div><div class="LayUI-form-item"><label class="LayUI-form-label">房間單價</label><div class="LayUI-input-inline"><input type="text" name="roomPrice" required lay-verify="required" placeholder="請輸入房間單價" autocomplete="off" class="LayUI-input"></div></div><div class="LayUI-form-item"><div class="LayUI-input-block"><button class="LayUI-btn" lay-submit lay-filter="formDemo">立即提交</button><button type="reset" class="LayUI-btn LayUI-btn-primary">重置</button></div></div></form><script src="../LayUI/LayUI.js" charset="utf-8"></script><script>//DemoLayUI.use('form', function(){var form = LayUI.form;//使用LayUI內置jqueryvar $=LayUI.$;//監聽提交form.on('submit(formDemo)', function(data){// data.field是當前表單中的所有數據// layer.msg(JSON.stringify(data.field));$.ajax({url:'http://localhost:9090/hotel/room/addRoom',//將表單中的所有數據一起提交,實際提交的是name=value,如roomType=值&roomPrice=值data:data.field,//相當于/* data:{roomType:"",roomPrice:""}*/success:function(res){if(res){//刷新父頁面parent.location.reload()}}})//return false時不提交表單return false;});});</script></body>
</html>

LayUI分頁

  • 假分頁

    查詢所有,在頁面中分頁,適合記錄比較少的情況

    table.render({//省略url等//開啟分頁組件, page: true//設置每頁顯示記錄數的下拉選項, limits: [5, 10, 20]//默認每頁顯示的條數,沒有設置默認為10, limit: 10//解析數據表格url請求后的數據, parseData: function (res) {//res就是url對應的數據//假分頁var result;if (this.page.curr) {result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr);} else {result = res.data.slice(0, this.limit);}return {"code": res.code,"msg": res.msg,"count": res.count,"data": result}}
    })
    
  • 真分頁

    可以使用PageHelper組件

    依賴

    <!--分頁組件SpringBoot集成PageHelper-->
    <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version>
    </dependency>
    

    在application.properties中配置

    # 防止不合理分頁
    pagehelper.reasonable=true
    

    在控制層中使用

    @ResponseBody
    @RequestMapping("/queryAll")
    //這里的page和limit參數是layui數據表格自動傳遞
    public ResultData<Room> queryAll(Integer page, Integer limit) {//使用PageHelper分頁PageHelper.startPage(page, limit);//正常查詢所有List<Room> rooms = roomService.queryAll();//創建分頁模型對象PageInfo<Room> pageInfo = new PageInfo<>(rooms);//返回的數據為分頁后的集合,數量為總記錄數return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList());
    }
    

LayUI條件查詢

頁面頭部工具欄中加入搜索框

<script type="text/html" id="toolbarDemo"><div class="layui-form-item"><div class="layui-input-inline"><input type="text" name="keyword" required lay-verify="required" placeholder="請輸入姓名關鍵字" autocomplete="off"class="layui-input"></div><button class="layui-btn layui-btn-primary" lay-event="search">搜索</button></div>
</script>

js部分

//頭工具欄事件
table.on('toolbar(test)', function (obj) {switch (obj.event) {case 'search'://獲取輸入的內容let keyword = $("input[name=keyword]").val();if(keyword==""){layer.msg("輸入不能為空");return;}//如果要修改數據表格中的數據,只能更改url的地址后,重新加載數據表格table.reload('test',{url:"http://localhost:9090/hotel/orders/search?keyword="+keyword});break;}
});

dao層參考上方數據訪問層條件查詢部分

MyBatisPlus

官網簡介 | MyBatis-Plus (baomidou.com)

MyBatis-Plus (簡稱 MP)是一個MyBatis的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。

只需簡單的配置,就能實現對單表的CURD。

其核心有兩個接口:BaseMapper和IService

BaseMapper中封裝了大量數據訪問層的方法

IServcie中封裝了大量業務流程層的方法

SpringBoot+MyBatisPlus

1.創建SpringBoot項目

創建時勾選依賴

  • devtools
  • lombok
  • spring-web
  • mysql-driver

2.導入SpringBoot集成MyBatisPlus依賴

<!-- SpringBoot集成MyBatisPlus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency>

3.配置application.properties文件

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root# 開啟sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 無需加入開啟駝峰命名映射,MyBatisPlus默認使用駝峰命名進行屬性-字段映射
#mybatis-plus.configuration.map-underscore-to-camel-case=true

4.根據數據表創建實體類

實體類的屬性名命名方式:

  • MyBatisPlus默認使用駝峰命名對字段和屬性進行映射。如將字段stu_name對應的屬性寫為stuName
  • 如果字段名和屬性名不一致,在屬性名上加入**@TableField(value = “字段名”)**
  • 主鍵字段對應的屬性,需要加入@TableId注解,其type屬性表示主鍵生成策略
    • @TableId(type = IdType.AUTO)表示主鍵自增,在數據庫也要將主鍵設置為自增
    • @TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根據時間和機器特征碼)生成一個id
    • @TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一個隨機字符串id
@Data
public class Hero {//type表示主鍵生成策略,@TableId(type = IdType.AUTO)// IdType.AUTO表示主鍵自增,在數據庫也要將主鍵設置為自增//@TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根據時間和機器特征碼)生成一個id//@TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一個隨機字符串idprivate Integer id;//如果屬性名和字段名不一致@TableField(value = "name")private String heroName;private String position;private String sex;private Integer price;private String shelfDate;
}

5.編寫數據訪問層接口

可以不用寫@Repository,繼承BaseMapper接口,設置泛型

/*
* 數據訪問層可以稱為dao或mapper層
* 可以不用加@Repository注解
* */
public interface HeroMapper extends BaseMapper<Hero> {}

6.在SpringBoot的啟動類中,掃描數據訪問層所在包

@SpringBootApplication
@MapperScan("com.hqyj.sbmp01.mapper")
public class Sbmp01Application {public static void main(String[] args) {SpringApplication.run(Sbmp01Application.class, args);}
}

測試

在SpringBoot自帶的單元測試類中,注入HeroMapper對象,調用BaseMapper中定義的方法即可實現CURD。

BaseMapper接口

BaseMapper接口中定義了常用的增刪改查方法,

在數據訪問層接口中繼承該接口

方法列表

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PJYyGHZA-1676548026845)(D:/qqFileRecv/框架/框架/SpringBoot+MyBatisPlus.assets/image-20230208172250774.png)]

使用

public interface HeroMapper extends BaseMapper<Hero> {}

BaseMapper接口中的常用方法

方法名 參數 作用
selectList(Wrapper wrapper) 條件構造器 根據條件查詢集合,如果實參為null表示查詢所有,返回List集合
selectById(Serializable id) 主鍵 根據主鍵查詢單個對象,返回單個對象
selectOne(Wrapper wrapper) 條件構造器 條件查詢單個對象,返回單個對象
insert(T entity) 實體對象 添加單個實體
updateById(T entity) 實體對象 根據實體對象單個修改,對象必須至少有一個屬性和主鍵
update(T entity,Wrapper wrapper) 實體對象和條件構造器 根據條件修改全部,對象必須至少有一個屬性
deleteById(Serializable id/T entity) 主鍵/實體對象 根據主鍵刪除單個對象
deleteBatchIds(Collection ids) 主鍵集合 根據集合刪除
delete(Wrapper wrapper) 條件構造器 根據條件刪除,如果實參為null表示無條件刪除所有

測試

package com.hqyj.sbmp01;import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;@SpringBootTest
class Sbmp01ApplicationTests {@Autowired//@Resourceprivate HeroMapper mapper;@Testvoid contextLoads() {//查詢所有List<Hero> list = mapper.selectList(null);for (Hero hero : list) {System.out.println(hero);}}@Testvoid test1() {//根據主鍵查詢單個對象System.out.println(mapper.selectById(20));}@Testvoid test2() {//根據id刪除,參數為id//System.out.println(mapper.deleteById(20));//根據id刪除,參數為帶有id的對象/*Hero hero = new Hero();hero.setId(100);System.out.println(mapper.deleteById(hero));*///刪除所有//mapper.delete(null);//根據id集合刪除ArrayList<Integer> list = new ArrayList<>();list.add(99);list.add(100);list.add(88);list.add(69);System.out.println(mapper.deleteBatchIds(list));}@Testvoid test3() {//根據id修改//參數為一個對象,這個對象至少要有一個非主鍵屬性有值Hero hero = new Hero();hero.setHeroName("a");hero.setPosition("b");hero.setSex("c");hero.setShelfDate("d");hero.setPrice(123);hero.setId(999);//System.out.println(mapper.updateById(hero));// 修改全部數據// mapper.update(修改后的對象,條件構造器)mapper.update(hero,null);}@Testvoid test4() {Hero hero = new Hero();hero.setHeroName("asdfsdfsdf");hero.setPosition("b");hero.setSex("c");hero.setShelfDate("1999-9-9");hero.setPrice(123);mapper.insert(hero);}
}

IService接口

IService接口減少業務邏輯層的代碼,并對BaseMapper進行了拓展

在業務流程類中繼承該接口

部分方法列表

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NRJfxDEr-1676548026845)(D:/qqFileRecv/框架/框架/SpringBoot+MyBatisPlus.assets/image-20230208172729669.png)]

使用

  • 1.創建一個業務層接口,繼承IService接口,設置泛型

    /*
    * 創建業務邏輯層接口,繼承IService<T>接口,設置泛型
    * */
    public interface HeroService extends IService<Hero> {
    }
  • 2.創建一個業務層接口的實現類,添加@Service注解,繼承 ServiceImpl<M, T>,實現上一步創建的接口

    • M是數據訪問層接口
    • T是實體類
    /*
    * 1.添加@Service
    * 2.繼承ServiceImpl<M, T> M是Mapper類 T是實體類
    * 3.實現自定義Service接口
    * */
    @Service
    public class ImpHeroService extends ServiceImpl<HeroMapper, Hero> implements HeroService {}
    

IService接口中的常用方法

方法 作用
list() 無條件查詢所有
list(Wrapper wrapper) 條件查詢素有
page(Page page) 無條件分頁查詢,Page是分頁模型對象
page(Page page,Wrapper wrapper) 條件分頁查詢,Page是分頁模型對象
getById(Serializable id) 根據主鍵查詢單個對象
getOne(Wrapper wrapper) 條件查詢單個對象
save(T entity) 添加單個對象
save(Collection col) 批量添加對象的集合
updateById(T entity) 修改,參數至少有一個屬性值和主鍵
saveOrUpdate(T entity) 添加或修改。如果實參對象的主鍵值不存在則添加,存在則修改
update(T entity,Wrapper wrapper) 條件修改,條件為null則修改全部數據
removeById(Serializable id/T entity) 根據主鍵或包含主鍵的對象刪除
removeBatchByIds(Collection ids) 根據集合刪除
remove(Wrapper wrapper) 根據條件刪除,條件為null則刪除全部

測試

package com.hqyj.sbmp01;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import com.hqyj.sbmp01.service.HeroService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;@SpringBootTest
class IServiceTest {@Autowiredprivate HeroService heroService;@Testvoid test1() {//無條件查詢所有//heroService.list().forEach(System.out::println);//條件查詢,如果條件為null表示查詢所有//heroService.list(null).forEach(System.out::println);//根據主鍵查詢//Hero hero = heroService.getById(22);//System.out.println(hero);//根據條件查詢,如果條件為null表示查詢所有,會導致異常//System.out.println(heroService.getOne(null));}@Testvoid test2() {//添加save(T entity);參數對象至少要有一個屬性值//添加后,主鍵自增的值會自動賦值給主鍵屬性/*Hero hero = new Hero();hero.setHeroName("cvb");hero.setSex("男");hero.setPrice(2000);hero.setPosition("戰士");heroService.save(hero);System.out.println(hero);*///批量添加,參數為要添加的對象集合/* Hero h1 = new Hero(0, "1", "1", "1", 100, "1999-9-9");Hero h2 = new Hero(0, "2", "1", "1", 100, "1999-9-9");Hero h3 = new Hero(0, "3", "1", "1", 100, "1999-9-9");List<Hero> heroes = Arrays.asList(h1, h2, h3);heroService.saveBatch(heroes);*/Hero h1 = new Hero(0, "666", "1", "1", 100, "1999-9-9");//如果添加的對象主鍵值已存在執行修改,不存在則添加heroService.saveOrUpdate(h1);}@Testvoid test3(){//根據對象修改Hero h1 = new Hero(999, "修改后", "修改后", "女", 100, "1999-9-9");//如果對象主鍵值已存在執行修改,不存在則添加//heroService.saveOrUpdate(h1);//根據主鍵修改heroService.updateById(h1);//根據條件修改//heroService.update(null);//根據實體和條件修改,如果條件為空,會將所有數據修改為指定實體的數據//heroService.update(h1,null);}@Testvoid test4(){//根據主鍵或帶有主鍵值的對象刪除//heroService.removeById(1103302663);/*Hero hero = new Hero();hero.setId(1103302662);heroService.removeById(hero);*///批量刪除//heroService.removeBatchByIds(Arrays.asList(77,88,99));//根據條件刪除,條件為null則刪除全部heroService.remove(null);}}

條件構造器Wrapper

BaseMapper和IService接口中有很多方法都有這個參數,表示一個條件構造器對象。

如果該參數實際傳遞的值為null,表示沒有任何條件,

這個Wrapper是一個抽象類,如果想要帶條件,就要創建一個該類的子類對象。

常用子類為QueryWrapper和UpdateWrapper,

查詢是創建QueryWrapper對象,更新時創建UpdateWrapper,實際使用無區別。

Wrapper對象帶參數

Wrapper<T> wrapper =  new QueryWrapper(T entity);
QueryWrapper<T> wrapper =  new QueryWrapper(T entity);

Wrapper構造方法的參數如果是一個實體對象,只要該對象的屬性不為空,就會將所有屬性用and拼接起來作為條件。

這種適合已知某個字段為某個值時使用。

@Test
void test1() {//創建實體對象,只帶兩個屬性值Hero hero = new Hero();hero.setHeroName("瑞茲");hero.setSex("女");//創建一個帶實體參數的條件構造器對象Wrapper<Hero> wrapper = new QueryWrapper<>(hero);System.out.println(heroService.getOne(wrapper));
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yizbWK9T-1676548026845)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209095105726.png)]

Wrapper對象不帶參數

Wrapper<T> wrapper =  new QueryWrapper();
QueryWrapper<T> wrapper =  new QueryWrapper();

當條件構造器不帶參數時,就需要自定義條件。

這種適用于自定義查詢條件。

默認多個條件時,用and連接,如果條件之間要使用or,調用or()方法

指定或范圍

@Test
void test2() {/** eq(String column,Object val)      equals          column = val* ne(String column,Object val)      not equals      column <> val* lt(String column,Object val)      less then       column < val* gt(String column,Object val)      great then      column > val* le(String column,Object val)      less equals     column <= val* ge(String column,Object val)      great equals    column >= val* between(String column Object val1,Object val2)    between and          column between val1 and bal2* notBetween(String column Object val1,Object val2) not between and      column not between val1 and bal2* *///創建一個不帶實體參數的條件構造器對象QueryWrapper<Hero> wrapper = new QueryWrapper<>();//自定義條件:指定值//查詢性別為男的數據  eq equals    =//wrapper.eq("sex","男");//查詢性別不為男的數據 ne not equals   <>//wrapper.ne("sex","男");//wrapper.between("price",1000,4000);//查詢價格小于5000的戰士//多個條件默認用and拼接/*wrapper.lt("price",5000);wrapper.eq("position","戰士");*///查詢性別為女或價格大于3000//or()方法會將前后的條件使用or拼接/*wrapper.gt("price",3000);wrapper.or();wrapper.eq("sex","女");*///查詢性別為男的戰士或價格大于3000的法師//支持鏈式寫法wrapper.eq("sex","男").eq("position","戰士").or().eq("position","法師").gt("price","3000");heroService.list(wrapper).forEach(System.out::println);
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-c1gIsBNh-1676548026846)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209102606876.png)]

空值和模糊查詢

@Test
void test3() {/** isNull(String column)                            column is null* isNotNull(String column)                         column is not null* like(String column,Object val)                   column like '%val%'* likeLeft(String column,Object val)               column like '%val'* likeRight(String column,Object val)              column like 'val%'* notLike(String column,Object val)                column not like '%val%'* notLikeLeft(String column,Object val)            column not like '%val'* notLikeRight(String column,Object val)           column not like 'val%'* */QueryWrapper<Hero> wrapper = new QueryWrapper<>();//字段為空//wrapper.isNull("shelf_date");//帶有指定關鍵字//wrapper.like("name","琳");//以指定關鍵字結尾//wrapper.likeLeft("name","琳");//以指定關鍵字開頭//wrapper.likeRight("name","伊");//查詢名字中帶有琳字且上架時間為空wrapper.like("name","琳").isNull("shelf_date");heroService.list(wrapper).forEach(System.out::println);
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FUvSAhMg-1676548026846)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209104130810.png)]

集合和排序

@Testvoid test4() {/**   集合*   in(String column,Object... vals)            column in (val1,val2...)*   in(String column,Collection vals)           column in (val1,val2...)*   notIn(String column,Object... vals)         column not in (val1,val2...)*   notIn(String column,Collection vals)        column not in (val1,val2...)**   排序*   orderByAsc(String column)                   order by column asc*   orderByDesc(String column)                  order by column desc** */QueryWrapper<Hero> wrapper = new QueryWrapper<>();//職業為戰士或法師//wrapper.eq("position","戰士").or().eq("position","法師");//可變參數//wrapper.in("position","戰士","法師");//集合//wrapper.in("position", Arrays.asList("戰士","法師"));//所有男性角色,按價格降序wrapper.eq("sex","男").orderByDesc("price");heroService.list(wrapper).forEach(System.out::println);}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lkh0EDLG-1676548026847)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209105104809.png)]

分組和聚合函數(自定義查詢)

分組通常會和聚合函數(count、sum、min、max、avg)一起使用,但MyBatisPlus中沒有現成的聚合函數。

需要使用**select(String… fields)**自定義查詢字段。

如查詢每個每個位置的總數

條件構造器對象.select(“position”,“count(id)”),對應的sql查詢部分為select “position”,“count(id)” from hero

@Test
void test() {/** select(String... fields)          select field1,field2...* groupBy(String column)            group by column* */QueryWrapper<Hero> wrapper = new QueryWrapper<>();//無參數時,查詢所有字段//wrapper.select();//查詢指定字段//wrapper.select("name");//使用聚合函數wrapper.select("count(id)");//查詢不同性別的人數//select sex,count(id) from hero group by sex//wrapper.select("sex","count(id)");//根據指定字段分組//wrapper.groupBy("sex");//當前的sql語句查詢的結果是一個訂制數據,如查詢總數只會得到一個數據,所以使用listMaps()方法//List<Map<String, Object>> maps = heroService.listMaps(wrapper);//根據定位分組,查詢值為法師、戰士的人數,按人數降序wrapper.select("position","count(id) as count").in("position","戰士","法師").groupBy("position").orderByDesc("count");List<Map<String, Object>> maps = heroService.listMaps(wrapper);System.out.println(maps);
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-H3xj8aEw-1676548026847)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209110021881.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ecySX50A-1676548026847)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209112235219.png)]

分頁查詢

MyBatisPlus中集成了分頁功能,只需在項目的啟動類中注入一個分頁攔截器對象

/*注入一個MyBatisPlus提供的分頁攔截器對象定義一個方法,在方法上加入@Bean注解,將該方法的返回值注入到Spring容器中*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){//創建一個MybatisPlus攔截器對象MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//通過攔截器對象添加分頁攔截器對象,設置數據庫類型interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;
}
@Test
void test1(){//無條件分頁查詢page(Page pageInfo)//1.創建一個分頁模型對象,設置泛型,參數為當前頁和每頁顯示的記錄數Page<Hero> pageInfo = new Page<>(1, 5);//2.調用IService中的page方法,參數為分頁模型對象,返回值也是該對象pageInfo = heroService.page(pageInfo);//分頁的相關信息都保存在分頁模型對象pageInfo中System.out.println("總頁數"+pageInfo.getPages());System.out.println("當前頁"+pageInfo.getCurrent());System.out.println("每頁顯示的記錄數"+pageInfo.getSize());System.out.println("總記錄數"+pageInfo.getTotal());System.out.println("分頁后的集合"+pageInfo.getRecords());System.out.println("是否有下一頁"+pageInfo.hasNext());System.out.println("是否有上一頁"+pageInfo.hasPrevious());//條件分頁page(Page pageInfo,Wrapper wrapper)//分頁查詢所有法師pageInfo= heroService.page(pageInfo,new QueryWrapper<Hero>().eq("position","法師"));pageInfo.getRecords().forEach(System.out::println);
}

代碼生成器

MyBatisPlus中用于自動生成entity、mapper、service、controller這些類和接口的工具

1.添加依賴

<!-- 代碼生成器 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.3</version>
</dependency>
<!--代碼生成器所需引擎-->
<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version>
</dependency>

2.代碼生成器類

修改以下幾處

  • 數據庫信息
  • 輸出目錄為本地硬盤目錄
  • 父包名
  • 要生成的表名
package com.hqyj.question_sys.util;import com.baomidou.mybatisplus.generator.FastAutoGenerator;public class CodeGenerator {public static void main(String[] args) {//***數據庫的url, 用戶名和密碼FastAutoGenerator.create("jdbc:mysql://localhost:3306/question_sys?serverTimezone=Asia/Shanghai","root", "root").globalConfig(builder -> {builder.author("HQYJ") // 設置作者,生成文件的注釋里的作者.outputDir("F:\\框架\\question_sys\\src\\main\\java"); //***指定輸出目錄}).packageConfig(builder -> {builder.parent("com.hqyj.question_sys"); //***設置父包名// .controller("controller") //控制層包名// .service("service") //service接口包名// .serviceImpl("service.impl")  //service接口實現包名// .mapper("mapper")  //mapper接口包名// .xml("mapper.xml") //映射文件的包名(如果要生成的話,加上這句,去掉下面的.xml方法)// .entity("entity"); //實體類的包名}).strategyConfig(builder -> {builder.addInclude("question_catalog","question")  //***設置需要生成的表名.controllerBuilder();//只生成Controller//.enableRestStyle(); //生成的Controller類添加@RestController;}).templateConfig(builder -> {builder.xml(null); //禁止生成xml映射文件}).execute();}
}

運行該類即可自動生成

MyBatisPlus中的邏輯刪除和物理刪除

物理刪除:將數據真正刪除。delete from 表 where id=值

邏輯刪除:數據不刪除,而是打個"被刪除"的標記。額外添加一個是否被刪除的字段"deleted",其值為0或1,0表示未刪除,1表示已刪除。

? 當刪除某條數據時,執行修改 update 表 set deleted=1 where id=值

添加了"deleted"字段后,查詢要更改為select * from 表 where deleted=0

實現過程

1.在表中添加一個新字段

deleted:默認為0表示未刪除,1表示已刪除

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QOCHl702-1676548026848)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209171301480.png)]

2.在實體類中對應屬性,添加@TableLogic注解

@TableLogic//該注解表示該字段是邏輯刪除字段
private Integer deleted;

此時調用IService接口中的list()方法查詢所有時,會在條件中加入deleted=0

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ejE8Xe5w-1676548026848)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209171655736.png)]

此時調用IService接口中的刪除時,會執行修改

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8NijaP96-1676548026848)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209171854655.png)]

Thymeleaf

SpringBoot官方建議使用的頁面模板引擎,代替之前的JSP。

它是以HTML為基礎,可以展示靜態數據,方便前端人員開發靜態頁面,

也可以通過Thymeleaf的語法,配合EL輸出由控制器跳轉而來的動態數據。

Thymeleaf從入門到精通 - 知乎 (zhihu.com)

用法

1.添加依賴

<!--thymeleaf頁面模板引擎-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId><version>2.7.3</version>
</dependency>

2.在temlates目錄下創建一個HTML頁面

在頁面的HTML標簽中,加入xmlns:th="http://www.thymeleaf.org"屬性。

可以通過修改IDEA中的HTML頁面模板,之后每次創建HTML頁面都會有該屬性

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gtk3shs1-1676548026849)(D:/qqFileRecv/框架/框架/day14-SpringBoot+MyBatisPlus2.assets/image-20230209143658152.png)]

3.thymeleaf具體使用

  • 雙標簽中的輸出變量: th:text

    <h1 th:text="${作用域中某個對象名}"></h1><h1 th:text="${username}"></h1>
    
  • 單標簽中設置value的值:th:value

    <input type="text" th:value="${作用域中某個對象名}"><input type="text" th:value="${變量.屬性名}">
    
  • 遍歷:th:each

    <table><tr th:each="變量:${作用域中的集合名}"><td th:text="${變量.屬性名}"></td></tr>
    </table><table><tr th:each="hero:${heroList}"><td th:text="${hero.id}"></td><td th:text="${hero.name}"></td><td th:text="${hero.sex}"></td></tr>
    </table>
    
  • 超鏈接獲取全局路徑:th:href=“@{/}”

    • 不帶參數

      <link th:href="@{/bootstrap-3.4.1-dist/css/bootstrap.css}" rel="stylesheet">
      
    • 帶參數

      <a th:href="@{'/hero/delete?id='+${hero.id}">刪除</a><a th:href="@{/question/findById(id=${question.quesCode})}">編輯</a>
      
  • 表單提交路徑:th:action=“@{/}”

    <form methos="post" th:action="@{/hero/insert}"></form>
    
  • 判斷:th:if

    <h1 th:if="判斷邏輯">用戶信息</h1><h1 th:if="${userinfo.username==null}">請登錄</h1>
    
  • 選中單選按鈕或復選框:th:checked

    <input type="radio" value="男" name="sex" th:checked="邏輯判斷"><input type="radio" value="男" name="sex" th:checked="${user.sex=='男'}">男
    <input type="radio" value="女" name="sex" th:checked="${user.sex eq '女'}">女
    
  • 選中下拉菜單:th:selected

    <select><option th:selected="邏輯判斷"></option>
    </select><select><option th:selected="${book.typeId==bookType.typeId}" th:each="bookType:${list}" th:value="${bookType.typeId}" th:text="${bookType.typeName}"></option>
    </select>
    
  • src屬性:th:src

    <script th:src="@{/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js}"></script>
    
  • 內聯表達式

    Thymeleaf頁面中使用[[]]可以在script標簽中使用EL表達式,這兩對中括號拼接的內容稱為內聯表達式。

    在給script標簽加入**th:inline=“javascript”**后使用

    <script th:inline="javascript">$(function () {//遍歷頁碼$(".pno").each(function (){//如果頁碼和當前頁一致,高亮顯示if($(this).text()==[[${pageInfo.current}]]){$(this).addClass("active");}});});
    </script><script th:inline="javascript">$(function () {//使用ajax讀取所有的題目類型對象,遍歷成option$.ajax({url:[[@{/qc/queryAll}]],success:function (res){for(var i=0;i<res.length;i++){let qcId = res[i].qcId;let qcName = res[i].qcName;$("<option></option>").text(qcName).val(qcId).appendTo($("select[name=qcId]"));}}});});
    </script>
    

    單表條件分頁

    原理:調用IService接口中的**page(Page page,Wrapper wrapper)**方法,創建分頁模型對象和條件構造器對象

    在Springboot啟動類中加入mybatis分頁攔截器

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;
    }
    

    搜索框

    <form class="navbar-form navbar-left" th:action="@{/question/queryAll}"><div class="form-group"><input type="text" class="form-control" name="keyword" placeholder="請輸入題目關鍵字"></div><button type="submit" class="btn btn-default">搜索</button>
    </form>
    

    控制層

    @RequestMapping("/queryAll")
    public String queryAll(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "5") Integer size,@RequestParam(defaultValue = "") String keyword,Model model) {//1.創建分頁模型對象Page<Question> pageInfo = new Page<>(page, size);//創建條件構造器對象QueryWrapper<Question> wrapper = new QueryWrapper<>();wrapper.like("ques_title",keyword);//2.調用條件分頁查詢的方法questionService.page(pageInfo,wrapper);// 將分頁模型對象保存到請求中model.addAttribute("pageInfo", pageInfo);//構造頁數List<Long> pageList = new ArrayList<>();//每次最多顯示5頁//定義最大數字long maxPage = page + 4 > pageInfo.getPages() ? pageInfo.getPages() : page + 4;//定義最小數字long minPage = page - 4 < 1 ? page : maxPage - 4;for (long i = minPage; i <= maxPage; i++) {pageList.add(i);}model.addAttribute("pageList", pageList);return "questionList";
    }
    

    分頁組件

    <nav aria-label="Page navigation"><ul class="pagination"><li><a th:if="${pageInfo.hasPrevious}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current-1})}"aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li><li class="pno" th:each="pno:${pageList}"><a th:href="@{/question/queryAll(keyword=${param.keyword},page=${pno})}" th:text="${pno}"></a></li><li><a th:if="${pageInfo.hasNext}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current+1})}"aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li></ul>
    </nav>
    

    MyBatisPlus關聯查詢

    可以通過創建xml文件來實現,過程同MyBatis關聯查詢。

    這里使用注解實現。

    主表question_catalog

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Nc9I9gWS-1676548026849)(D:/qqFileRecv/框架/框架/day15-SpringBoot+MyBatisPlus3.assets/image-20230210142342982.png)]

    從表question

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IshUMlt8-1676548026849)(D:/qqFileRecv/框架/框架/day15-SpringBoot+MyBatisPlus3.assets/image-20230210142322936.png)]

    多對一

    查詢時以從表數據為主體,關聯對應的主表信息。

    多個question對象對應一個question_catalog對象。

    在查詢question表的同時,顯示關聯question_catalog表中的數據。

    實體類

    在從表的實體類中,額外添加一個外鍵對應的主表實體對象

    @Data
    public class Question implements Serializable {private static final long serialVersionUID = 1L;@TableId(type = IdType.ASSIGN_UUID)private String quesCode;private Integer qcId;private String quesTitle;private String quesOptA;private String quesOptB;private String quesOptC;private String quesAns;@TableLogic//該注解表示該字段是邏輯刪除字段private Integer deleted;/** 如果在實體類中添加了一個不屬于該表中字段的屬性* 要在該屬性上加@TableField(exist = false)表示該屬性沒有對應的字段* */@TableField(exist = false)private QuestionCatalog qc;
    }
    

    原理

    如果直接通過IService接口中的list()方法查詢,實際調用的是BaseMapper接口中的selectList()方法,默認查詢自身表。

    如果重寫業務層中的list()方法,或在業務層中自定義一個方法,讓其調用數據訪問層中自定義的某個方法,重新定制sql語句,就能得到想要的數據

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zc2Egb23-1676548026850)(D:/qqFileRecv/框架/框架/day15-SpringBoot+MyBatisPlus3.assets/image-20230210145612843.png)]

    實現過程

    1.在Mapper(QuestionMapper)接口中自定義一個方法,使用注解編寫sql語句

    有兩種方式:

    • 方式一:構造自定義的sql語句

      • 建議外鍵關聯字段比較少的情況下使用
      • sql語句需要連接查詢,將外鍵關聯的表的字段重命名為"外鍵實體對象名.屬性名"
      @Select("select q.*,qc_name as 'qc.qcName' from question q,question_catalog qc where q.qc_id=qc.qc_id ")
      List<Question> mySelect();
      
    • 方式二:使用子查詢,自定義結果集映射

      • 建議外鍵關聯字段比較多的情況下使用
      • 先查詢從表,再使用外鍵字段查詢主表
      • 外鍵字段需要重新映射一次
      //1.查詢自身表
      @Select("select * from question")
      //2.自定義結果集映射
      @Results({//qc_id字段在下面的子查詢中已經使用了,將外鍵字段qc_id重新映射@Result(property = "qcId",column = "qc_id"),//遇到qc屬性,使用qc_id進行子查詢//執行QuestionCatalogMapper中的selectById方法//該方法是BaseMapper接口中默認就存在的方法@Result(property ="qc" ,column ="qc_id",one =@One(select = "com.hqyj.question_sys.mapper.QuestionCatalogMapper.selectById"))
      })
      List<Question> mySelect();
      

    2.在Service層中調用Mapper層中自定義的方法

    @Service
    public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements IQuestionService {/** 如果直接調用該方法查詢,會最終調用BaseMapper中的selectList()方法* 這里重寫該方法,讓其調用BaseMapper中的自定義方法* */@Overridepublic List<Question> list() {return baseMapper.mySelect();}
    }
    

    一對多

    以主表為主體,查詢時顯示關聯的從表對象集合。

    查詢question_catalog表,同時顯示qc_id對應的question表中的數據集合。

    實體類

    在主表實體類中,額外添加一個從表實體對象集合。

    @TableName("question_catalog")
    @Data
    public class QuestionCatalog implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "qc_id", type = IdType.AUTO)private Integer qcId;private String qcName;/** 一對多查詢,定義關聯的從表對象集合* @TableField(exist = false)標明該屬性沒有對應的字段* */@TableField(exist = false)private List<Question> questionList;
    }

    原理同多對一查詢,重寫service層中的list()方法,改變原本方法中調用的內容

    實現過程

    1.在Mapper(QuestionCatalogMapper)接口中自定義一個方法,使用注解編寫sql語句

    /** 自定義查詢* 使用子查詢實現一對多* */
    //1.查詢question_catalog表
    @Select("select * from question_catalog")
    //2.自定義結果集映射
    @Results({//由于在下面使用qc_id進行了子查詢,對其重新映射@Result(property = "qcId",column = "qc_id"),//3.使用qc_id進行子查詢,查詢question表,調用根據qc_id查詢對應數據集合的方法,沒有現成的方法,需要自定義@Result(property = "questionList", column = "qc_id", many = @Many(select = "com.hqyj.question_sys.mapper.QuestionMapper.queryByQcId"))
    })
    List<QuestionCatalog> mySelect();
    

    2.在Mapper(QuestionMapper)接口中定義方法

    /** 根據qc_id查詢對應數據集合* */
    @Select("select * from question where qc_id=#{qcId}")
    List<Question> queryByQcId();
    

    3.在Service層中調用Mapper層中自定義的方法

    /** 重寫原本的查詢所有,執行自定義的查詢* */
    @Override
    public List<QuestionCatalog> list() {return baseMapper.mySelect();
    }
    

    多對一關聯查詢條件分頁

    原理

    參考原本的條件分頁查詢方法selectPage(P page, Wrapper<Question> queryWrapper)

    page用于分頁,queryWrapper用于構造條件。通過自定義的sql語句關聯兩張表實現。
    在自定義的sql語句中,

    通過ew.sqlSegment或{ew.sqlSegment}或ew.sqlSegment或{ew.customSqlSegment}使用條件構造器Wrapper對象

    ${ew.sqlSegment} 將條件原樣拼接

    ${ew.customSqlSegment} 在所有條件前自動加入where關鍵字

    實現過程

    1.在Mapper(QuestionMapper)中自定義關聯查詢的sql

    • ${ew.sqlSegment} 將條件原樣拼接
    • ${ew.customSqlSegment} 在所有條件前加入where關鍵字
    //${ew.sqlSegment}   將條件原樣拼接
    //@Select("SELECT q.*,qc_name AS 'qc.qcName' FROM question q,question_catalog qc WHERE q.qc_id = qc.qc_id and ${ew.sqlSegment}")//${ew.customSqlSegment}  在所有條件前加入where關鍵字
    @Select("SELECT q.*,qc_name AS 'qc.qcName' FROM question q inner join question_catalog qc on q.qc_id = qc.qc_id ${ew.customSqlSegment}")
    <P extends IPage<Question>> P queryByCondition(P page, @Param("ew") Wrapper<Question> queryWrapper);
    

    2.在service層中重寫或自定義方法,調用mapper層中自定義的方法

    @Override
    public <E extends IPage<Question>> E page(E page, Wrapper<Question> queryWrapper) {return baseMapper.queryByCondition(page,queryWrapper);
    }
    

    3.controller層

    • 接收所需參數
    • 創建分頁模型對象
    • 創建條件構造器對象
    @RequestMapping("/queryAll")
    public String queryAll(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "5") Integer size,@RequestParam(defaultValue = "") String keyword,@RequestParam(defaultValue = "0") Integer qcId,Model model) {//1.創建分頁模型對象Page<Question> pageInfo = new Page<>(page, size);//創建條件構造器對象QueryWrapper<Question> wrapper = new QueryWrapper<>();wrapper.like("ques_title", keyword);if (qcId != 0) {wrapper.eq("q.qc_id", qcId);}//2.調用條件分頁查詢的方法questionService.page(pageInfo, wrapper);model.addAttribute("pageInfo", pageInfo);//構造頁數List<Long> pageList = new ArrayList<>();//每次最多顯示5頁//定義最大數字long maxPage = page + 4 > pageInfo.getPages() ? pageInfo.getPages() : page + 4;//定義最小數字long minPage = page - 4 < 1 ? page : maxPage - 4;for (long i = minPage; i <= maxPage; i++) {pageList.add(i);}model.addAttribute("pageList", pageList);return "questionList";
    }
    

    4.頁面表單

    <form class="navbar-form navbar-left" th:action="@{/question/queryAll}"><div class="form-group"><input type="text" class="form-control" name="keyword" placeholder="請輸入題目關鍵字"><select class="form-control" name="qcId"></select></div><button type="submit" class="btn btn-default">搜索</button>
    </form>
    <script th:inline="javascript">$(function () {$.ajax({url:[[@{/qc/queryAll}]],success:function (res){for(var i=0;i<res.length;i++){let qcId = res[i].qcId;let qcName = res[i].qcName;$("<option></option>").text(qcName).val(qcId).appendTo($("select[name=qcId]"));}}});});
    </script>
    

答題功能核心內容

隨機出題

/** 隨機5條記錄* */
@Select("select  * from  question order by rand() limit 5 ")
List<Question> queryByRand();

答題頁面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><head><title>Title</title><link rel="shortcut icon" href="https://www.baidu.com/favicon.ico" type="image/x-icon"><!--bootstrap的css文件路徑--><link th:href="@{/bootstrap-3.4.1-dist/css/bootstrap.css}" rel="stylesheet"></head><body><div class="col-md-4"></div><div class="col-md-4"><form id="result"><div class="panel panel-primary" th:each="question:${list}"><div class="panel-heading" th:text="${question.quesTitle}"></div><div class="panel-body"><div class="form-group"><label><input type="radio" value="a" th:name="${question.quesCode}"><span th:text="${question.quesOptA}"></span></label><label><input type="radio" value="b" th:name="${question.quesCode}"><span th:text="${question.quesOptB}"></span></label><label><input type="radio" value="c" th:name="${question.quesCode}"><span th:text="${question.quesOptC}"></span></label></div></div></div><button class="btn btn-default loginBtn" type="submit">提交</button></form></div><div class="col-md-4"></div><script th:src="@{/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js}"></script><script th:inline="javascript">$(function () {$("#result").submit(function () {//創建數組保存答題結果var ansList = [];//獲取表單的數據 表單對象.serialize()可以獲取表單中每個name-value,將其拼接為字符串var data = $(this).serialize();//name=value&name=value//切分答題結果let list = data.split("&");//遍歷答題結果,將其構造為一個答題結果對象for (var i = 0; i < list.length; i++) {//創建一個對象var ans = {};//構造對象的屬性ans.code = list[i].split("=")[0];ans.opt = list[i].split("=")[1];ansList.push(ans);}//提交ansList$.ajax({url: [[@{/question/result}]],data: {ansList: JSON.stringify(ansList)},traditional: true,success: function (res) {alert("你答對了"+res+"道題");}});return false;})});</script></body>
</html>

答題

答題對象

package com.hqyj.question_sys.entity;import lombok.Data;/*
* 創建答題結果對象
* */
@Data
public class AnswerResult {private String code;private String opt;
}

controller

@RequestMapping("/result")
@ResponseBody
public Integer result(String ansList) throws JsonProcessingException {//將JSON格式的字符串轉換為對象//SpringBoot中集成了jackson,可以用于對象和JSON之間的轉換//創建ObjectMapper對象ObjectMapper jsonTool = new ObjectMapper();//讀取接收到的JSON字符串,轉換為Result對象數組AnswerResult[] list = jsonTool.readValue(ansList, AnswerResult[].class);//遍歷答題結果Integer count=0;for (AnswerResult res : list) {/*//答題題目String code = res.getCode();//回答結果String opt = res.getOpt();//原本題目Question question = questionService.getById(code);//真正答案String quesAns = question.getQuesAns();*/if (questionService.getById(res.getCode()).getQuesAns().equals(res.getOpt())) {count++;}}return count;
}

Spring Data JPA

2001年推出了Hibernate,是一個全自動ORM框架。可以不用編寫SQL語句,就能實現對數據庫的持久化操作。

SUN公司在Hibernate的基礎上,制定了JPA,全稱 Java Persisitence API,中文名Java持久化API,

是一套Java訪問數據庫的規范,由一系列接口和抽象類構成。

后來Spring團隊在SUN公司制定的JPA這套規范下,推出了Spring Data JPA,是JPA的具體實現。

如今常說的JPA,通常指Spring Data JPA。

SpringBoot集成Spring Data JPA

1.創建SpringBoot項目,選擇依賴

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VHccTb66-1676548026850)(D:/qqFileRecv/框架/框架/day16-SpringBoot+JPA.assets/image-20230213141450488.png)]

2.編輯配置文件,設置要連接的數據庫信息

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root# 設置數據庫類型
spring.jpa.database=mysql
# 打印SQL語句
spring.jpa.show-sql=true

3.創建實體類

  • 類上加**@Entity**注解

  • 主鍵屬性上加

    • @Id注解標明主鍵

    • **@GeneratedValue(strategy = GenerationType.IDENTITY)**設置MySQL數據庫主鍵生成策略,數據庫設置為自增

  • 其他屬性名與字段名一致或駝峰命名法

    • 如果字段名多個單詞之間用_,使用駝峰命名法
    • 如果不相同,使用**@Column(name=“字段名”)**注解指定該屬性對應的字段名
@Data
@Entity
/*
* 實體類的屬性名建議使用駝峰命名法
* */
public class BookInfo {@Id//主鍵字段//主鍵生成策略,GenerationType.IDENTITY表示MySQL自增@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer bookId;private Integer typeId;private String bookName;private String bookAuthor;//如果字段名和屬性名不一致,使用@Column指定字段名@Column(name = "book_price")private Integer price;private Integer bookNum;private String publisher_date;
}

4.數據訪問層接口

  • 類上加@Repository注解
  • 繼承JpaRepository<實體類型,主鍵類型>接口
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {}

5.測試常用方法

方法名 返回值 作用
findAll() List 查詢所有數據。
save(T entity) T entity 添加或修改。如果不存在主鍵屬性或主鍵值不存在,執行添加;如果存在主鍵屬性且有該主鍵值,執行修改。
delete(T entity) void 根據對象刪除。如果該對象中有主鍵屬性且有該主鍵值,根據該主鍵值刪除。
findById(主鍵) Optional 根據主鍵值查詢。返回的對象調用isPresent()結果為true,表示查詢到了數據,繼續調用get()得到查詢到的對象。
@SpringBootTest
class Day16SpringBootJpaApplicationTests {@Autowiredprivate BookInfoDao bookInfoDao;@Testvoid contextLoads() {List<BookInfo> all = bookInfoDao.findAll();all.forEach(System.out::println);}@Testvoid insert() {BookInfo bookInfo = new BookInfo();bookInfo.setBookName("測試");bookInfo.setBookAuthor("測試");bookInfo.setBookNum(156);bookInfo.setPrice(20);bookInfo.setTypeId(1);//save()方法調用時,如果對象中沒有主鍵或主鍵值不存在,作為添加使用BookInfo save = bookInfoDao.save(bookInfo);//添加成功后,會自動獲取主鍵自增的值System.out.println(save);}@Testvoid update() {BookInfo bookInfo = new BookInfo();bookInfo.setBookName("xxxxxxxxxxx");bookInfo.setBookAuthor("測試");bookInfo.setBookNum(356);bookInfo.setPrice(23);bookInfo.setTypeId(1);//save()方法調用時,如果對象中有主鍵且存在,作為修改使用BookInfo save = bookInfoDao.save(bookInfo);//修改成功后,返回修改后的對象System.out.println(save);}@Testvoid delete() {//根據主鍵值刪除,如果值不存在,會報錯//bookInfoDao.deleteById(36);//根據對象刪除,如果對象中包含主鍵值則刪除,如果沒有值或不存在,不會報錯BookInfo bookInfo = new BookInfo();//bookInfo.setBookId(330);bookInfoDao.delete(bookInfo);}@Testvoid findOne() {//根據主鍵查詢,返回值Optional類型Optional<BookInfo> byId = bookInfoDao.findById(60);//isPresent()如果為true表示查詢到了數據if (byId.isPresent()) {//get()將查詢到的數據轉換為對應的實體類BookInfo bookInfo=byId.get();System.out.println(bookInfo);}else{System.out.println("未查詢到數據");}}
}

JPA進階

分頁查詢

調用數據訪問層中的**findAll(Pageable pageable)**方法,即可實現分頁。

參數Pageable是org.springframework.data.domain包中的一個接口,通過其實現類

PageRequest,調用靜態方法of(int page,int size),當做Pageable對象使用。

這里的page從0開始為第一頁。

@Test
void queryByPage(){//PageRequest是Pageable的實現類,調用靜態方法of(int page,int size)//這里的page的值0表示第一頁//調用findAll(Pageable pageable)方法,返回分頁模型對象Page<BookInfo> pageInfo = bookInfoDao.findAll(PageRequest.of(0,5));//分頁相關數據System.out.println("總記錄數"+pageInfo.getTotalElements());System.out.println("最大頁數"+pageInfo.getTotalPages());System.out.println("分頁后的數據集合"+pageInfo.getContent());System.out.println("當前頁數"+pageInfo.getNumber());System.out.println("每頁顯示的記錄數"+pageInfo.getSize());System.out.println("是否還有下一頁"+pageInfo.hasNext());System.out.println("是否還有上一頁"+pageInfo.hasPrevious());
}

條件查詢

在JPA中,使用自定義方法名自動生成對應的SQL語句,實現條件查詢。

如在dao中定義了queryById(int id)方法,就表示根據id查詢,自動生成sql語句。

方法命名格式

[xxx] [By] [字段對應的屬性名] [規則] [Or/And] [字段對應的屬性名] [規則] …

  • **xxx可以是find、get、query、search
  • 方法如果有參數,參數的順序和方法名中的參數順序一致

如findByBookNameAndBookAuthor(String bookName,String bookAuthor),

對應的sql語句為 select * from book where book_name =? and book_author=?

常用規則

規則 方法名 SQL中的條件
指定值 findByBookName(String name) book_name = name
Or/And findByBookNameOrBookAuthor(String name,String author) book_name = name or book_author = author
After/Befor findByBookPriceAfter(double price) book_price > price
GreaterThanEqual/LessThanEqual findByBookNumLessThanEqual(int num) book_num <= num
Between findByBookNumBetween(int min,int max) book_num between min and max
Is[Not]Null findByPublisherDateIsNull() publish_date is null
[Not]Like findByBookNameLike(String condition) book_name like ‘condition’
[Not]Contains findByBookNameContains(String keyword) book_name like ‘%keyword%’
StartsWith/EndsWith findByBookNameStartsWith(String firstName) book_name like ‘firstName%’
無條件排序:findAllByOrderBy字段[Desc/Asc] findAllByOrderByBookId() order by book_id asc
有條件排序:findAllBy條件OrderBy字段[Desc/Asc] findAllByTypeIdOrderByBookIdDesc() type_id = ? order by book_id desc
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {//指定值查詢//根據書名查詢List<BookInfo> getAllByBookName(String x);//查詢價格大于指定值   字段對應的屬性名  After/GreaterThanList<BookInfo> findAllByPriceAfter(int price);//查詢價格小于于指定值    字段對應的屬性名  Before/LessThanList<BookInfo> findAllByPriceLessThan(int price);//查詢庫存大于等于指定值 GreaterThanEqualList<BookInfo> queryAllByBookNumGreaterThanEqual(int num);//查詢庫存在指定閉區間內 Between(int min,int max)List<BookInfo> findAllByBookNumBetween(int min,int max);//空值查詢 null//查詢出版日期為空  IsNull/IsNotNullList<BookInfo> findAllByPublisherDateIsNull();//書名中帶有關鍵字 Like/NotLike 實參一定要使用%或_List<BookInfo> getAllByBookNameLike(String keyword);//作者名中帶有關鍵字  Contains/NotContains 實參只需要關鍵字List<BookInfo> getAllByBookAuthorContains(String keyword);//指定作者的姓   指定開頭/結尾  StartsWith/EndsWithList<BookInfo> getAllByBookAuthorStartsWith(String keyword);//查詢所有數據,按價格降序    無條件排序 OrderBy字段[Desc/Asc]List<BookInfo> getAllByOrderByPriceDesc();//查詢指定類型,按id降序List<BookInfo> getAllByTypeIdOrderByBookIdDesc(Integer typeId);}

條件分頁查詢

只需在定義方法時,將方法的返回值設置為Page類型,在參數中就如Pageable對象

//根據作者和書名的關鍵字分頁查詢
Page<BookInfo> getAllByBookNameContainsOrBookAuthorContains(String nameKeyword,String authorKeyword,Pageable pageable);
Page<BookInfo> pageInfo = bookInfoDao.getAllByBookNameContainsOrBookAuthorContains("龍","山",PageRequest.of(0,5));
//分頁相關數據保存在pageInfo對象中

聚合函數分組查詢

自定義SQL

在數據訪問層接口中的方法上,可以加入@Query注解,默認要使用HQL(Hibernate專用)格式的語句。

如果要使用原生的SQL語句,需要添加nativeQuery=true屬性,用value屬性定義SQL語句

/** 在JPA中,如果要使用自定義的SQL語句* nativeQuery = true 開啟原生SQL語句* value="sql語句"* */
@Query(nativeQuery = true, value = "select book_author,count(book_id) from book_info group by book_author")
List testQuery();
@Test
void test(){List list = bookInfoDao.testQuery();//查詢的結果為集合,集合中保存的是每一行數據for (Object row : list) {//每一行頁數一個對象數組Object[] obj= (Object[])row;//根據索引得到查詢出的內容System.out.println(obj[0]+"---"+obj[1]);}
}

自定義SQL中帶參數

SQL語句中的":XXX"表示參數

如果方法的形參名和xxx一致時直接使用,如果不一致,在形參上加入@Param注解設置形參名

/** 根據作者查詢其圖書總庫存* 使用":形參名"在SQL語句中帶參數* 在方法中通過@Prama定義形參* */
@Query(nativeQuery = true, value = " select book_author,sum(book_num) from book_info where book_author=:zuozhe")
List testQuery3(@Param("zuozhe") String xxx);
@Test
void test(){List list = bookInfoDao.testQuery3("金庸");//查詢的結果為集合,集合中保存的是每一行數據for (Object row : list) {//每一行頁數一個對象數組Object[] obj= (Object[])row;//根據索引得到查詢出的內容System.out.println(obj[0]+"---"+obj[1]);}
}

關聯查詢

主表book_type

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZCwFQTbl-1676548026851)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214105234875.png)]

從表book_info

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sM1NxWG1-1676548026851)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214105247332.png)]

實體類

主表實體BookType

@Entity
@Data
public class BookType {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer typeId;private String typeName;
}

從表實體BookInfo

  • 無需寫出外鍵字段屬性
  • 額外添加外鍵字段對應的實體類對象屬性
@Data
@Entity
public class BookInfo {@Id//主鍵字段//主鍵生成策略,GenerationType.IDENTITY表示MySQL自增@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer bookId;//private Integer typeId;private String bookName;private String bookAuthor;//如果字段名和屬性名不一致,使用@Column指定字段名@Column(name = "book_price")private Integer price;private Integer bookNum;private String publisherDate;//多對一查詢,以當前從表信息為主體,關聯相應的主表信息@JoinColumn(name = "type_id")//使用type_id字段進行多對一查詢@ManyToOne//多對一private BookType bt;
}

使用

  • 多對一查詢,調用dao中的查詢方法,就會自動給外鍵字段對應的對象賦值

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-e6GbVH67-1676548026851)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214111732161.png)]

  • 添加時,參數所需外鍵字段,使用外鍵字段對應的對象名代替

    //添加
    @PostMapping("/book")
    public RestResult<BookInfo> insert(BookInfo bookInfo) {return RestResult.ok("添加成功", bookInfoDao.save(bookInfo));
    }
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-diRNCC1k-1676548026852)(D:/qqFileRecv/框架/框架/day16day17-SpringBoot+JPA.assets/image-20230214113125279.png)]

  • 如果根據外鍵字段查詢,要在從表dao中定義方法findBy外鍵對象名_外鍵對象屬性名(數據類型 外鍵字段)

    //如果根據外鍵字段查詢,方法名寫為 By外鍵實體類屬性名_屬性名
    List<BookInfo> getAllByBt_TypeId(Integer typeId);
    

前后端分離項目

前后端分離,就是將web應用中的前端頁面和后端代碼分開完成、部署。

  • 前后端的開發者只需要完成各自的事情,最終以文檔的形式約定數據接口(URL、參數、返回值、請求方式)
  • 前后端分別用獨立的服務器
  • 后端只需處理數據并提供訪問接口(路徑),以RESTFul風格的JSON格式傳輸數據
  • 前端只需負責渲染頁面和展示數據

傳統項目和前后端分離項目對比

傳統項目

前端和后端的代碼運行在一個服務器上,頁面經由控制器跳轉

SSM項目、圖書管理系統、答題系統

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fthntOJP-1676548026852)(D:/qqFileRecv/框架/框架/day16-SpringBoot+JPA.assets/image-20230213170658853.png)]

前后端分離項目

前后端的代碼分別運行在各自的服務器上

后端提供JSON格式字符串的數據接口

前端負責跳轉、解析JSON數據。

酒店客房管理系統

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-n5XClYA1-1676548026852)(D:/qqFileRecv/框架/框架/day16-SpringBoot+JPA.assets/image-20230213171038191.png)]

前后端分離項目后端控制層設計

請求方式設計:RESTFul風格

風格,不是標準,可以不用強制遵循。

RESTFul風格:用不同的請求方式去訪問同一個URL地址時,執行不同的操作。

特點

  • 通過URL就能知道當前在哪個模塊
  • 通過不同的請求方式決定執行什么操作
  • 通過返回的狀態碼得到操作結果

使用RESTFul風格和普通方式對比

普通方式

localhost:8080/user/queryAll							查詢所有
localhost:8080/user/queryById?id=1001					條件查詢
localhost:8080/user/insert?name=ez&sex=男&age=20			添加
localhost:8080/user/update?name=ez&id=1001				修改
localhost:8080/user/delete?id=1001						刪除

RESTFul風格

localhost:8080/user							查詢所有get請求
localhost:8080/user/1001					條件查詢get請求
localhost:8080/user							添加post請求
localhost:8080/user							修改put請求
localhost:8080/user/1001					刪除delete請求

RESTFul風格具體使用

  • 在請求映射的命名上,統一用小寫字母的名詞形式表示當前位于哪個模塊。如/user、/book_info

  • 訪問時如果要傳參,使用"/模塊名/參數"方式,配合controller中的@PathVariable獲取

    @GetMapping("/book/{id}")
    public BookInfo queryById(@PathVariable("id")Integer id){return service.findById(id);
    }
    
  • 在controller的方法上,使用@XXXMapping()設置訪問該方法的請求方式

    • @GetMapping(“路徑”) 查詢
    • @PostMapping(“路徑”) 添加
    • **@PutMapping(“路徑”) ** 修改
    • **@DeleteMapping(“路徑”) ** 刪除
    • @RequestMapping(value=“路徑”,method=RequestMethod.GET/POST/PUT/DELETE))
  • 如果請求方式不匹配,會報405異常

  • 在同一個controller中,不能出現兩個請求方式和路徑都一致的方法

返回值設計

前后端分離項目的控制層方法的返回值也需要進行統一。

返回值通常包含以下信息

  • 傳遞狀態,用狀態碼表示Integer code
  • 傳遞消息,用字符串表示String msg
  • 傳遞集合,用集合表示List list
  • 傳遞對象,用對象表示Object obj

將這些信息封裝到一個對象中,這個對象稱為返回結果類RestResult對象

RestResult類具體設計

Postman

Postman API Platform | Sign Up for Free

Java技術棧

服務器端

? JavaSE(API、OOP)、JavaEE(Servlet、Spring、SpringMVC)

? tomcat

前端0

? HTML+CSS+JS+JQUERY、Bootstrap、LayUI、EasyUI、VUE

數據庫

? MySQL、Oracle、Redis

? JDBC、SpringJDBC、MyBatis、MyBatisPlus、JPA

工具

? IDEA、HBuilder、VSCode、Notepad++、Sublime、Navicat、Postman

JavaEE過程

Servlet + JDBC + JSP(bootstrap) 圖書商場

Spring + SpringMVC + SpringJDBC + JSP(bootstrap)

Spring + SpringMVC + MyBatis+ JSP 圖書管理系統SSM

SpringBoot + MyBatis + html(LayUI) 酒店客房管理(可前后端分離)

SpringBoot + MyBatisPlus + html(Thymeleaf+bootstrap) 答題系統

SpringBoot + Spring Data JPA

項目一

SpringBoot+MyBatisPlus+Vue 倉庫管理(前后端分離)

findAllByPriceAfter(int price);

//查詢價格小于于指定值    字段對應的屬性名  Before/LessThan
List<BookInfo> findAllByPriceLessThan(int price);//查詢庫存大于等于指定值 GreaterThanEqual
List<BookInfo> queryAllByBookNumGreaterThanEqual(int num);//查詢庫存在指定閉區間內 Between(int min,int max)
List<BookInfo> findAllByBookNumBetween(int min,int max);//空值查詢 null
//查詢出版日期為空  IsNull/IsNotNull
List<BookInfo> findAllByPublisherDateIsNull();//書名中帶有關鍵字 Like/NotLike 實參一定要使用%或_
List<BookInfo> getAllByBookNameLike(String keyword);//作者名中帶有關鍵字  Contains/NotContains 實參只需要關鍵字
List<BookInfo> getAllByBookAuthorContains(String keyword);//指定作者的姓   指定開頭/結尾  StartsWith/EndsWith
List<BookInfo> getAllByBookAuthorStartsWith(String keyword);//查詢所有數據,按價格降序    無條件排序 OrderBy字段[Desc/Asc]
List<BookInfo> getAllByOrderByPriceDesc();//查詢指定類型,按id降序
List<BookInfo> getAllByTypeIdOrderByBookIdDesc(Integer typeId);

}

## 條件分頁查詢只需在定義方法時,將方法的返回值設置為Page類型,在參數中就如Pageable對象```java
//根據作者和書名的關鍵字分頁查詢
Page<BookInfo> getAllByBookNameContainsOrBookAuthorContains(String nameKeyword,String authorKeyword,Pageable pageable);
Page<BookInfo> pageInfo = bookInfoDao.getAllByBookNameContainsOrBookAuthorContains("龍","山",PageRequest.of(0,5));
//分頁相關數據保存在pageInfo對象中

聚合函數分組查詢

自定義SQL

在數據訪問層接口中的方法上,可以加入@Query注解,默認要使用HQL(Hibernate專用)格式的語句。

如果要使用原生的SQL語句,需要添加nativeQuery=true屬性,用value屬性定義SQL語句

/** 在JPA中,如果要使用自定義的SQL語句* nativeQuery = true 開啟原生SQL語句* value="sql語句"* */
@Query(nativeQuery = true, value = "select book_author,count(book_id) from book_info group by book_author")
List testQuery();
@Test
void test(){List list = bookInfoDao.testQuery();//查詢的結果為集合,集合中保存的是每一行數據for (Object row : list) {//每一行頁數一個對象數組Object[] obj= (Object[])row;//根據索引得到查詢出的內容System.out.println(obj[0]+"---"+obj[1]);}
}

自定義SQL中帶參數

SQL語句中的":XXX"表示參數

如果方法的形參名和xxx一致時直接使用,如果不一致,在形參上加入@Param注解設置形參名

/** 根據作者查詢其圖書總庫存* 使用":形參名"在SQL語句中帶參數* 在方法中通過@Prama定義形參* */
@Query(nativeQuery = true, value = " select book_author,sum(book_num) from book_info where book_author=:zuozhe")
List testQuery3(@Param("zuozhe") String xxx);
@Test
void test(){List list = bookInfoDao.testQuery3("金庸");//查詢的結果為集合,集合中保存的是每一行數據for (Object row : list) {//每一行頁數一個對象數組Object[] obj= (Object[])row;//根據索引得到查詢出的內容System.out.println(obj[0]+"---"+obj[1]);}
}

關聯查詢

實體類

主表實體BookType

@Entity
@Data
public class BookType {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer typeId;private String typeName;
}

從表實體BookInfo

  • 無需寫出外鍵字段屬性
  • 額外添加外鍵字段對應的實體類對象屬性
@Data
@Entity
public class BookInfo {@Id//主鍵字段//主鍵生成策略,GenerationType.IDENTITY表示MySQL自增@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer bookId;//private Integer typeId;private String bookName;private String bookAuthor;//如果字段名和屬性名不一致,使用@Column指定字段名@Column(name = "book_price")private Integer price;private Integer bookNum;private String publisherDate;//多對一查詢,以當前從表信息為主體,關聯相應的主表信息@JoinColumn(name = "type_id")//使用type_id字段進行多對一查詢@ManyToOne//多對一private BookType bt;
}

使用

  • 多對一查詢,調用dao中的查詢方法,就會自動給外鍵字段對應的對象賦值

    [外鏈圖片轉存中…(img-e6GbVH67-1676548026851)]

  • 添加時,參數所需外鍵字段,使用外鍵字段對應的對象名代替

    //添加
    @PostMapping("/book")
    public RestResult<BookInfo> insert(BookInfo bookInfo) {return RestResult.ok("添加成功", bookInfoDao.save(bookInfo));
    }
    
  • 如果根據外鍵字段查詢,要在從表dao中定義方法findBy外鍵對象名_外鍵對象屬性名(數據類型 外鍵字段)

    //如果根據外鍵字段查詢,方法名寫為 By外鍵實體類屬性名_屬性名
    List<BookInfo> getAllByBt_TypeId(Integer typeId);
    

前后端分離項目

前后端分離,就是將web應用中的前端頁面和后端代碼分開完成、部署。

  • 前后端的開發者只需要完成各自的事情,最終以文檔的形式約定數據接口(URL、參數、返回值、請求方式)
  • 前后端分別用獨立的服務器
  • 后端只需處理數據并提供訪問接口(路徑),以RESTFul風格的JSON格式傳輸數據
  • 前端只需負責渲染頁面和展示數據

傳統項目和前后端分離項目對比

傳統項目

前端和后端的代碼運行在一個服務器上,頁面經由控制器跳轉

SSM項目、圖書管理系統、答題系統

前后端分離項目

前后端的代碼分別運行在各自的服務器上

后端提供JSON格式字符串的數據接口

前端負責跳轉、解析JSON數據。

酒店客房管理系統

前后端分離項目后端控制層設計

請求方式設計:RESTFul風格

風格,不是標準,可以不用強制遵循。

RESTFul風格:用不同的請求方式去訪問同一個URL地址時,執行不同的操作。

特點

  • 通過URL就能知道當前在哪個模塊
  • 通過不同的請求方式決定執行什么操作
  • 通過返回的狀態碼得到操作結果

使用RESTFul風格和普通方式對比

普通方式

localhost:8080/user/queryAll							查詢所有
localhost:8080/user/queryById?id=1001					條件查詢
localhost:8080/user/insert?name=ez&sex=男&age=20			添加
localhost:8080/user/update?name=ez&id=1001				修改
localhost:8080/user/delete?id=1001						刪除

RESTFul風格

localhost:8080/user							查詢所有get請求
localhost:8080/user/1001					條件查詢get請求
localhost:8080/user							添加post請求
localhost:8080/user							修改put請求
localhost:8080/user/1001					刪除delete請求

RESTFul風格具體使用

  • 在請求映射的命名上,統一用小寫字母的名詞形式表示當前位于哪個模塊。如/user、/book_info

  • 訪問時如果要傳參,使用"/模塊名/參數"方式,配合controller中的@PathVariable獲取

    @GetMapping("/book/{id}")
    public BookInfo queryById(@PathVariable("id")Integer id){return service.findById(id);
    }
    
  • 在controller的方法上,使用@XXXMapping()設置訪問該方法的請求方式

    • @GetMapping(“路徑”) 查詢
    • @PostMapping(“路徑”) 添加
    • **@PutMapping(“路徑”) ** 修改
    • **@DeleteMapping(“路徑”) ** 刪除
    • @RequestMapping(value=“路徑”,method=RequestMethod.GET/POST/PUT/DELETE))
  • 如果請求方式不匹配,會報405異常

  • 在同一個controller中,不能出現兩個請求方式和路徑都一致的方法

返回值設計

前后端分離項目的控制層方法的返回值也需要進行統一。

返回值通常包含以下信息

  • 傳遞狀態,用狀態碼表示Integer code
  • 傳遞消息,用字符串表示String msg
  • 傳遞集合,用集合表示List list
  • 傳遞對象,用對象表示Object obj

將這些信息封裝到一個對象中,這個對象稱為返回結果類RestResult對象

RestResult類具體設計

Postman

Postman API Platform | Sign Up for Free

Java技術棧

服務器端

? JavaSE(API、OOP)、JavaEE(Servlet、Spring、SpringMVC)

? tomcat

前端0

? HTML+CSS+JS+JQUERY、Bootstrap、LayUI、EasyUI、VUE

數據庫

? MySQL、Oracle、Redis

? JDBC、SpringJDBC、MyBatis、MyBatisPlus、JPA

工具

? IDEA、HBuilder、VSCode、Notepad++、Sublime、Navicat、Postman

JavaEE過程

Servlet + JDBC + JSP(bootstrap) 圖書商場

Spring + SpringMVC + SpringJDBC + JSP(bootstrap)

Spring + SpringMVC + MyBatis+ JSP 圖書管理系統SSM

SpringBoot + MyBatis + html(LayUI) 酒店客房管理(可前后端分離)

SpringBoot + MyBatisPlus + html(Thymeleaf+bootstrap) 答題系統

SpringBoot + Spring Data JPA

項目一

SpringBoot+MyBatisPlus+Vue 倉庫管理(前后端分離)

總結

以上是生活随笔為你收集整理的华清远见-重庆中心-框架个人总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

亚洲国精产品一二二线 | 亚洲成av人综合在线观看 | 亚洲一区二区三区香蕉 | 强伦人妻一区二区三区视频18 | 久久国内精品自在自线 | 黑人粗大猛烈进出高潮视频 | 婷婷色婷婷开心五月四房播播 | 少妇性荡欲午夜性开放视频剧场 | 精品熟女少妇av免费观看 | 在线视频网站www色 | 亚洲成a人片在线观看日本 | 激情内射日本一区二区三区 | 欧美第一黄网免费网站 | 黄网在线观看免费网站 | 久久久久av无码免费网 | 亚洲精品国产精品乱码不卡 | 玩弄人妻少妇500系列视频 | 日韩亚洲欧美精品综合 | 免费人成网站视频在线观看 | 乱人伦中文视频在线观看 | 欧美人与牲动交xxxx | 中国女人内谢69xxxx | 欧美激情一区二区三区成人 | 日韩少妇内射免费播放 | 人人澡人摸人人添 | 日韩人妻少妇一区二区三区 | 久精品国产欧美亚洲色aⅴ大片 | 老子影院午夜精品无码 | 午夜免费福利小电影 | 啦啦啦www在线观看免费视频 | 亚洲七七久久桃花影院 | 欧洲熟妇精品视频 | 色婷婷综合中文久久一本 | 中文字幕无码热在线视频 | 欧美怡红院免费全部视频 | 性做久久久久久久免费看 | 久久成人a毛片免费观看网站 | 亚洲人成影院在线无码按摩店 | 青青青手机频在线观看 | 九月婷婷人人澡人人添人人爽 | 久久久久国色av免费观看性色 | 女人高潮内射99精品 | 天天爽夜夜爽夜夜爽 | 日韩少妇白浆无码系列 | 成人影院yy111111在线观看 | 美女黄网站人色视频免费国产 | 国模大胆一区二区三区 | 国产av一区二区三区最新精品 | 国产网红无码精品视频 | 无码一区二区三区在线观看 | 荡女精品导航 | 乱中年女人伦av三区 | 欧美日韩综合一区二区三区 | 久久www免费人成人片 | 在教室伦流澡到高潮hnp视频 | 风流少妇按摩来高潮 | 国产suv精品一区二区五 | 欧美亚洲国产一区二区三区 | 丰满人妻精品国产99aⅴ | 乱人伦中文视频在线观看 | 无码人妻丰满熟妇区毛片18 | 内射后入在线观看一区 | 午夜丰满少妇性开放视频 | 日本熟妇人妻xxxxx人hd | 国产免费久久精品国产传媒 | 天堂亚洲2017在线观看 | 久久99精品久久久久久 | 激情五月综合色婷婷一区二区 | www成人国产高清内射 | 色婷婷综合激情综在线播放 | 精品一区二区不卡无码av | 大屁股大乳丰满人妻 | 高潮毛片无遮挡高清免费视频 | 国产精品多人p群无码 | 国产成人一区二区三区在线观看 | 最近的中文字幕在线看视频 | 在线 国产 欧美 亚洲 天堂 | 亚洲国产精品一区二区第一页 | 亚洲国产精品一区二区第一页 | av无码久久久久不卡免费网站 | 人妻无码αv中文字幕久久琪琪布 | 亚洲成av人影院在线观看 | 风流少妇按摩来高潮 | 日韩精品无码一区二区中文字幕 | 97色伦图片97综合影院 | 国产午夜视频在线观看 | 亚洲国产成人av在线观看 | 日日碰狠狠躁久久躁蜜桃 | 久久久久久久人妻无码中文字幕爆 | 永久免费观看美女裸体的网站 | 国产一区二区不卡老阿姨 | 亚洲va欧美va天堂v国产综合 | 国产国语老龄妇女a片 | 无码乱肉视频免费大全合集 | 精品久久久中文字幕人妻 | 最新国产乱人伦偷精品免费网站 | 亚洲中文字幕成人无码 | 国产精品亚洲专区无码不卡 | 国产精品亚洲五月天高清 | 女人被爽到呻吟gif动态图视看 | 国产精品高潮呻吟av久久4虎 | 色综合久久久久综合一本到桃花网 | 西西人体www44rt大胆高清 | 波多野结衣av在线观看 | 成人三级无码视频在线观看 | 久久综合九色综合欧美狠狠 | 亚洲精品一区二区三区四区五区 | 久久久久免费精品国产 | 欧美成人高清在线播放 | 久久久精品人妻久久影视 | 国产国产精品人在线视 | 人人爽人人爽人人片av亚洲 | 无码一区二区三区在线 | 欧美 亚洲 国产 另类 | 亚洲中文字幕无码一久久区 | 国产97在线 | 亚洲 | 伊人色综合久久天天小片 | 亚洲狠狠色丁香婷婷综合 | 日韩 欧美 动漫 国产 制服 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 亚洲熟熟妇xxxx | 黄网在线观看免费网站 | 日韩精品成人一区二区三区 | 亚洲欧美日韩综合久久久 | 一二三四在线观看免费视频 | 国产午夜亚洲精品不卡下载 | 丰满护士巨好爽好大乳 | 国产疯狂伦交大片 | 国产精品高潮呻吟av久久 | 黄网在线观看免费网站 | 少妇太爽了在线观看 | 我要看www免费看插插视频 | 午夜福利试看120秒体验区 | 日本一区二区更新不卡 | 漂亮人妻洗澡被公强 日日躁 | 夜先锋av资源网站 | 大色综合色综合网站 | 日韩亚洲欧美精品综合 | 亚洲色无码一区二区三区 | 亚洲精品久久久久久久久久久 | 欧美日韩综合一区二区三区 | 成熟女人特级毛片www免费 | 国产无套内射久久久国产 | 国产乱人伦偷精品视频 | 在线 国产 欧美 亚洲 天堂 | 日本www一道久久久免费榴莲 | 精品aⅴ一区二区三区 | 午夜成人1000部免费视频 | 人人妻人人藻人人爽欧美一区 | 欧洲熟妇精品视频 | 76少妇精品导航 | 久久亚洲日韩精品一区二区三区 | 天天躁日日躁狠狠躁免费麻豆 | 国产亚洲精品精品国产亚洲综合 | 国产精品丝袜黑色高跟鞋 | 国产内射爽爽大片视频社区在线 | 乱人伦人妻中文字幕无码 | 亚洲 a v无 码免 费 成 人 a v | 澳门永久av免费网站 | 无码人妻久久一区二区三区不卡 | 熟妇人妻激情偷爽文 | 亚洲精品一区二区三区四区五区 | 好男人社区资源 | 国产精品成人av在线观看 | 无码人妻av免费一区二区三区 | 国产一区二区不卡老阿姨 | 波多野结衣 黑人 | 国产香蕉尹人综合在线观看 | 日本高清一区免费中文视频 | 午夜精品久久久内射近拍高清 | 高清无码午夜福利视频 | 男人扒开女人内裤强吻桶进去 | 免费国产黄网站在线观看 | 亚洲中文字幕在线观看 | 在线欧美精品一区二区三区 | 超碰97人人做人人爱少妇 | 成人性做爰aaa片免费看 | 日本精品人妻无码77777 天堂一区人妻无码 | 美女张开腿让人桶 | 久久久久久久久888 | 国产亲子乱弄免费视频 | 女人高潮内射99精品 | 精品日本一区二区三区在线观看 | 欧美猛少妇色xxxxx | 国产精品久久久久无码av色戒 | 久久久久久九九精品久 | 亚洲大尺度无码无码专区 | 色欲av亚洲一区无码少妇 | 97久久精品无码一区二区 | 青草青草久热国产精品 | 国产欧美精品一区二区三区 | 人妻少妇被猛烈进入中文字幕 | 国产一区二区不卡老阿姨 | 亚洲精品国产精品乱码视色 | 乱人伦人妻中文字幕无码 | 小泽玛莉亚一区二区视频在线 | 久久99久久99精品中文字幕 | 国産精品久久久久久久 | 亚洲成av人影院在线观看 | 99久久婷婷国产综合精品青草免费 | 亚洲色欲色欲欲www在线 | 动漫av一区二区在线观看 | 亚洲精品成人福利网站 | 99视频精品全部免费免费观看 | 日日鲁鲁鲁夜夜爽爽狠狠 | 日本一区二区三区免费高清 | 亚洲精品一区二区三区婷婷月 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 玩弄中年熟妇正在播放 | 双乳奶水饱满少妇呻吟 | 内射老妇bbwx0c0ck | 一本大道久久东京热无码av | 乱人伦中文视频在线观看 | 亚洲综合无码一区二区三区 | 两性色午夜视频免费播放 | 人人妻人人澡人人爽欧美一区 | 国产精品爱久久久久久久 | 色偷偷人人澡人人爽人人模 | 亚洲成熟女人毛毛耸耸多 | 天堂亚洲免费视频 | 少妇太爽了在线观看 | 国产无av码在线观看 | 正在播放老肥熟妇露脸 | 婷婷丁香五月天综合东京热 | 欧美日韩一区二区综合 | 国产两女互慰高潮视频在线观看 | 樱花草在线社区www | 国产精品丝袜黑色高跟鞋 | 中文无码成人免费视频在线观看 | 在线观看国产一区二区三区 | 国产人妻大战黑人第1集 | 精品久久综合1区2区3区激情 | 永久免费观看国产裸体美女 | 日本大香伊一区二区三区 | 日产精品99久久久久久 | 九九久久精品国产免费看小说 | 丁香花在线影院观看在线播放 | 日本护士毛茸茸高潮 | 丁香花在线影院观看在线播放 | 国产av一区二区精品久久凹凸 | 精品久久久无码人妻字幂 | 精品久久8x国产免费观看 | 久久综合香蕉国产蜜臀av | 国产午夜亚洲精品不卡 | 亚洲中文字幕久久无码 | 亚欧洲精品在线视频免费观看 | 久久久国产一区二区三区 | 大乳丰满人妻中文字幕日本 | 国内精品人妻无码久久久影院 | 亚洲中文字幕无码一久久区 | 青草视频在线播放 | 国产精品va在线播放 | 国产成人一区二区三区别 | 麻豆国产丝袜白领秘书在线观看 | 男女性色大片免费网站 | 国产片av国语在线观看 | 中文字幕无线码免费人妻 | 中文字幕 亚洲精品 第1页 | 丁香花在线影院观看在线播放 | 久久99热只有频精品8 | 国产精品人妻一区二区三区四 | yw尤物av无码国产在线观看 | 久久精品无码一区二区三区 | 国产精品视频免费播放 | 亚洲国产精品一区二区美利坚 | 奇米影视888欧美在线观看 | 国産精品久久久久久久 | 人妻体内射精一区二区三四 | аⅴ资源天堂资源库在线 | 97人妻精品一区二区三区 | v一区无码内射国产 | 国产口爆吞精在线视频 | 久久成人a毛片免费观看网站 | 亚洲 另类 在线 欧美 制服 | 久久精品国产一区二区三区 | 久久精品国产99精品亚洲 | 性欧美熟妇videofreesex | 国产精品人人爽人人做我的可爱 | 日韩精品久久久肉伦网站 | 一个人看的www免费视频在线观看 | 成 人影片 免费观看 | 人人妻人人澡人人爽欧美一区九九 | 日本熟妇乱子伦xxxx | 亚洲成av人片天堂网无码】 | 亚洲精品国产a久久久久久 | 人妻天天爽夜夜爽一区二区 | 亚洲国产欧美国产综合一区 | 成人毛片一区二区 | 国产精品人人爽人人做我的可爱 | 黑人巨大精品欧美黑寡妇 | 精品人人妻人人澡人人爽人人 | 精品一区二区三区波多野结衣 | 欧美兽交xxxx×视频 | 日日摸日日碰夜夜爽av | 亚洲欧美精品伊人久久 | 性色欲情网站iwww九文堂 | 精品国产青草久久久久福利 | 精品水蜜桃久久久久久久 | 精品久久久中文字幕人妻 | 少妇人妻偷人精品无码视频 | 精品无码国产自产拍在线观看蜜 | 两性色午夜免费视频 | 无码人妻少妇伦在线电影 | 波多野结衣av一区二区全免费观看 | 欧美freesex黑人又粗又大 | 亚洲国产成人av在线观看 | 国产精品无码永久免费888 | 国产美女精品一区二区三区 | 亚洲日韩乱码中文无码蜜桃臀网站 | 牲交欧美兽交欧美 | 精品国产精品久久一区免费式 | 性欧美videos高清精品 | 欧美日韩亚洲国产精品 | 人妻少妇精品视频专区 | 国产午夜亚洲精品不卡下载 | 色婷婷久久一区二区三区麻豆 | 又大又黄又粗又爽的免费视频 | 国产精品怡红院永久免费 | 狠狠色噜噜狠狠狠7777奇米 | 少女韩国电视剧在线观看完整 | 午夜精品久久久内射近拍高清 | 荫蒂添的好舒服视频囗交 | 人妻无码αv中文字幕久久琪琪布 | 无码av中文字幕免费放 | 奇米影视7777久久精品人人爽 | 无码国产激情在线观看 | 国产亚洲精品久久久久久久久动漫 | 国产人妻精品一区二区三区 | 国精产品一区二区三区 | 5858s亚洲色大成网站www | 一本久道久久综合婷婷五月 | 欧美日本免费一区二区三区 | 久久亚洲国产成人精品性色 | 131美女爱做视频 | 精品欧美一区二区三区久久久 | 我要看www免费看插插视频 | 天堂а√在线地址中文在线 | 久久国产精品二国产精品 | 人人澡人人妻人人爽人人蜜桃 | 成人亚洲精品久久久久软件 | 中文字幕色婷婷在线视频 | 国产精品久久久一区二区三区 | 亚洲熟妇色xxxxx欧美老妇 | 国产精品毛片一区二区 | 兔费看少妇性l交大片免费 | 欧美日韩一区二区免费视频 | 国产精品亚洲五月天高清 | av无码久久久久不卡免费网站 | 精品久久8x国产免费观看 | 无码任你躁久久久久久久 | 久久亚洲日韩精品一区二区三区 | 色综合天天综合狠狠爱 | 亚洲国产精品久久久久久 | 国产午夜亚洲精品不卡 | 亚洲午夜福利在线观看 | 狂野欧美激情性xxxx | 国产午夜福利亚洲第一 | 一个人看的www免费视频在线观看 | 日本护士xxxxhd少妇 | 久久精品中文字幕一区 | 在线成人www免费观看视频 | a片免费视频在线观看 | 国产成人无码一二三区视频 | 少妇一晚三次一区二区三区 | 国产特级毛片aaaaaaa高清 | 午夜时刻免费入口 | 久久久久久久久蜜桃 | 曰本女人与公拘交酡免费视频 | 激情综合激情五月俺也去 | 女人和拘做爰正片视频 | 精品国产麻豆免费人成网站 | 亚洲色欲久久久综合网东京热 | 动漫av网站免费观看 | 又粗又大又硬毛片免费看 | 国产精品爱久久久久久久 | 欧美日本精品一区二区三区 | 欧美成人午夜精品久久久 | 国产午夜精品一区二区三区嫩草 | 国产免费无码一区二区视频 | 成人试看120秒体验区 | 欧美日韩色另类综合 | 丝袜美腿亚洲一区二区 | 狠狠色噜噜狠狠狠7777奇米 | 伊人久久大香线蕉av一区二区 | 午夜免费福利小电影 | 久久精品国产大片免费观看 | 性做久久久久久久免费看 | 中文久久乱码一区二区 | 性生交大片免费看l | 国产熟妇另类久久久久 | 国产舌乚八伦偷品w中 | 无码帝国www无码专区色综合 | 麻豆国产人妻欲求不满谁演的 | а√资源新版在线天堂 | 性色av无码免费一区二区三区 | a片在线免费观看 | 日韩无码专区 | 大地资源中文第3页 | 国产乱人伦av在线无码 | 狠狠色噜噜狠狠狠7777奇米 | 久久久久成人精品免费播放动漫 | 牲欲强的熟妇农村老妇女视频 | 在线成人www免费观看视频 | 日韩在线不卡免费视频一区 | 亚洲va中文字幕无码久久不卡 | 中文字幕乱码亚洲无线三区 | 久9re热视频这里只有精品 | 奇米影视888欧美在线观看 | 亚洲精品成人av在线 | 好男人社区资源 | 精品国产一区二区三区四区 | 欧美熟妇另类久久久久久不卡 | 麻豆精品国产精华精华液好用吗 | 丰腴饱满的极品熟妇 | 伊人久久婷婷五月综合97色 | 国产精品怡红院永久免费 | 亚洲日韩一区二区 | 久久综合九色综合97网 | 国产女主播喷水视频在线观看 | 日本一区二区三区免费高清 | 中文无码伦av中文字幕 | 无码人中文字幕 | 成 人 免费观看网站 | 欧美丰满熟妇xxxx性ppx人交 | 国产精品亚洲lv粉色 | 国产成人综合在线女婷五月99播放 | 高潮毛片无遮挡高清免费视频 | 亚洲男女内射在线播放 | 欧美激情内射喷水高潮 | 国产成人无码av片在线观看不卡 | 东京无码熟妇人妻av在线网址 | 激情亚洲一区国产精品 | 麻豆精产国品 | 人妻夜夜爽天天爽三区 | 欧美精品国产综合久久 | 国产人妻精品一区二区三区不卡 | 欧美丰满熟妇xxxx性ppx人交 | 中文亚洲成a人片在线观看 | 丰腴饱满的极品熟妇 | 久久精品国产一区二区三区肥胖 | 天干天干啦夜天干天2017 | 国产精品人妻一区二区三区四 | 国产免费久久久久久无码 | 激情内射亚州一区二区三区爱妻 | 又大又硬又爽免费视频 | 51国偷自产一区二区三区 | 麻豆av传媒蜜桃天美传媒 | 久在线观看福利视频 | v一区无码内射国产 | 少妇无码av无码专区在线观看 | 日韩av无码一区二区三区不卡 | 国产手机在线αⅴ片无码观看 | 国产av一区二区精品久久凹凸 | 欧美三级a做爰在线观看 | 亚洲精品久久久久中文第一幕 | 欧美午夜特黄aaaaaa片 | 国模大胆一区二区三区 | 国产97人人超碰caoprom | 2020久久香蕉国产线看观看 | 国产成人一区二区三区别 | 一本无码人妻在中文字幕免费 | 国产亚洲精品久久久久久久 | 久久综合香蕉国产蜜臀av | 高潮毛片无遮挡高清免费视频 | 久久久久99精品成人片 | 熟妇人妻中文av无码 | 亚洲区欧美区综合区自拍区 | 中文字幕av日韩精品一区二区 | 内射后入在线观看一区 | 人人爽人人澡人人高潮 | 国语自产偷拍精品视频偷 | 亚洲色大成网站www | 亚洲第一无码av无码专区 | 少妇久久久久久人妻无码 | 亚洲一区av无码专区在线观看 | 中文字幕无码热在线视频 | 国产乱人伦偷精品视频 | 日日麻批免费40分钟无码 | 一本无码人妻在中文字幕免费 | 未满成年国产在线观看 | 亚洲综合无码一区二区三区 | 在线欧美精品一区二区三区 | 5858s亚洲色大成网站www | 鲁鲁鲁爽爽爽在线视频观看 | 伊人久久大香线焦av综合影院 | 婷婷五月综合缴情在线视频 | 人妻少妇精品无码专区二区 | 自拍偷自拍亚洲精品被多人伦好爽 | 波多野结衣av一区二区全免费观看 | 正在播放老肥熟妇露脸 | 午夜性刺激在线视频免费 | 亚洲欧美精品aaaaaa片 | 天堂一区人妻无码 | 又紧又大又爽精品一区二区 | 久久精品丝袜高跟鞋 | 亚洲精品午夜国产va久久成人 | 亚洲成av人在线观看网址 | 人妻aⅴ无码一区二区三区 | 奇米影视7777久久精品人人爽 | 图片小说视频一区二区 | 久久成人a毛片免费观看网站 | 欧美日本免费一区二区三区 | 久久久久久久人妻无码中文字幕爆 | 国产艳妇av在线观看果冻传媒 | 免费男性肉肉影院 | 欧美 亚洲 国产 另类 | 精品无码国产自产拍在线观看蜜 | 精品偷自拍另类在线观看 | 六十路熟妇乱子伦 | 成人动漫在线观看 | 一本色道久久综合亚洲精品不卡 | 亚洲 激情 小说 另类 欧美 | 日韩欧美成人免费观看 | 国产又粗又硬又大爽黄老大爷视 | 伊人色综合久久天天小片 | 日本丰满护士爆乳xxxx | 欧美熟妇另类久久久久久不卡 | 综合激情五月综合激情五月激情1 | 国产 浪潮av性色四虎 | 婷婷五月综合缴情在线视频 | 久久久久人妻一区精品色欧美 | 乱人伦人妻中文字幕无码久久网 | 色一情一乱一伦 | 清纯唯美经典一区二区 | 久久久婷婷五月亚洲97号色 | 十八禁真人啪啪免费网站 | 国内精品一区二区三区不卡 | 玩弄少妇高潮ⅹxxxyw | 美女极度色诱视频国产 | 久久久久久久久蜜桃 | 国产热a欧美热a在线视频 | 亚洲啪av永久无码精品放毛片 | 国产精品香蕉在线观看 | 大肉大捧一进一出好爽视频 | 国产又爽又猛又粗的视频a片 | 99riav国产精品视频 | av在线亚洲欧洲日产一区二区 | 国产三级久久久精品麻豆三级 | 久久久亚洲欧洲日产国码αv | 无码乱肉视频免费大全合集 | 亚洲中文字幕无码中文字在线 | 日韩少妇白浆无码系列 | 亚洲天堂2017无码 | 色五月丁香五月综合五月 | 18禁止看的免费污网站 | 久久久中文字幕日本无吗 | 精品成在人线av无码免费看 | 老熟女重囗味hdxx69 | 成熟人妻av无码专区 | 午夜无码区在线观看 | 又粗又大又硬毛片免费看 | 亚洲aⅴ无码成人网站国产app | 欧美刺激性大交 | 国产真人无遮挡作爱免费视频 | 一区二区三区高清视频一 | 亲嘴扒胸摸屁股激烈网站 | 国产激情精品一区二区三区 | 国产尤物精品视频 | 樱花草在线播放免费中文 | 国产亚洲精品久久久久久 | www国产亚洲精品久久久日本 | 国产亚洲日韩欧美另类第八页 | 国产精品无码永久免费888 | 欧美freesex黑人又粗又大 | 乱码av麻豆丝袜熟女系列 | 日日橹狠狠爱欧美视频 | 澳门永久av免费网站 | 好男人www社区 | 亚洲va欧美va天堂v国产综合 | 国产小呦泬泬99精品 | 俄罗斯老熟妇色xxxx | 亚洲 激情 小说 另类 欧美 | 精品国产aⅴ无码一区二区 | 麻豆成人精品国产免费 | 日本一本二本三区免费 | 亚洲性无码av中文字幕 | 大乳丰满人妻中文字幕日本 | 亚洲综合在线一区二区三区 | 国产亚洲精品久久久久久大师 | 无码国模国产在线观看 | 牲欲强的熟妇农村老妇女视频 | av人摸人人人澡人人超碰下载 | 国产无遮挡又黄又爽免费视频 | 人人妻人人澡人人爽人人精品 | 国产福利视频一区二区 | 成 人 网 站国产免费观看 | 亚洲人成网站色7799 | 老太婆性杂交欧美肥老太 | 帮老师解开蕾丝奶罩吸乳网站 | 久久精品人妻少妇一区二区三区 | 无码成人精品区在线观看 | 日本丰满熟妇videos | 欧美乱妇无乱码大黄a片 | 亚洲国产欧美在线成人 | 麻豆md0077饥渴少妇 | 性色欲网站人妻丰满中文久久不卡 | 小sao货水好多真紧h无码视频 | 精品一区二区三区无码免费视频 | 高清无码午夜福利视频 | 中文字幕日产无线码一区 | 人人妻人人澡人人爽欧美一区 | 鲁鲁鲁爽爽爽在线视频观看 | 国产免费久久久久久无码 | 我要看www免费看插插视频 | 国产精品18久久久久久麻辣 | 亚洲日本va午夜在线电影 | 国产免费久久精品国产传媒 | 日本又色又爽又黄的a片18禁 | 婷婷五月综合缴情在线视频 | 中文字幕乱码中文乱码51精品 | 澳门永久av免费网站 | 日韩人妻无码一区二区三区久久99 | 亚洲码国产精品高潮在线 | a片在线免费观看 | 精品少妇爆乳无码av无码专区 | 国产人妻久久精品二区三区老狼 | 99精品久久毛片a片 | 扒开双腿疯狂进出爽爽爽视频 | 婷婷综合久久中文字幕蜜桃三电影 | 狂野欧美激情性xxxx | 久久99精品久久久久久动态图 | 国产精品久久久久久亚洲影视内衣 | 亚洲综合伊人久久大杳蕉 | 亚洲欧美精品伊人久久 | 中文无码精品a∨在线观看不卡 | 波多野结衣高清一区二区三区 | 日日躁夜夜躁狠狠躁 | 丁香花在线影院观看在线播放 | 亚洲无人区午夜福利码高清完整版 | 97久久超碰中文字幕 | 国产精品欧美成人 | 久久综合九色综合欧美狠狠 | 中文字幕乱码中文乱码51精品 | 亚洲乱码国产乱码精品精 | 小泽玛莉亚一区二区视频在线 | 漂亮人妻洗澡被公强 日日躁 | 欧美丰满熟妇xxxx | 中文无码成人免费视频在线观看 | 亚洲 另类 在线 欧美 制服 | 强辱丰满人妻hd中文字幕 | 国内精品久久久久久中文字幕 | 欧美老妇交乱视频在线观看 | 免费国产黄网站在线观看 | 久久人人爽人人爽人人片ⅴ | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 人妻中文无码久热丝袜 | 少妇人妻偷人精品无码视频 | 久青草影院在线观看国产 | 免费人成在线观看网站 | 一本久道久久综合狠狠爱 | 丁香花在线影院观看在线播放 | 久在线观看福利视频 | 国产莉萝无码av在线播放 | 亚洲精品鲁一鲁一区二区三区 | 成人亚洲精品久久久久 | 99久久亚洲精品无码毛片 | 国产国语老龄妇女a片 | 人人爽人人爽人人片av亚洲 | 偷窥日本少妇撒尿chinese | 3d动漫精品啪啪一区二区中 | 色综合视频一区二区三区 | 中文字幕乱码中文乱码51精品 | 国产精品欧美成人 | 一本久久a久久精品亚洲 | 少妇性l交大片 | 红桃av一区二区三区在线无码av | 任你躁国产自任一区二区三区 | 99久久精品日本一区二区免费 | 亚洲国产一区二区三区在线观看 | 97色伦图片97综合影院 | 久久久久免费精品国产 | 欧美 亚洲 国产 另类 | 亚洲欧美国产精品久久 | 好爽又高潮了毛片免费下载 | 婷婷丁香五月天综合东京热 | 精品久久久无码人妻字幂 | 激情内射亚州一区二区三区爱妻 | www国产精品内射老师 | 天海翼激烈高潮到腰振不止 | 成人毛片一区二区 | av小次郎收藏 | 好屌草这里只有精品 | 色噜噜亚洲男人的天堂 | 久久天天躁夜夜躁狠狠 | 亚洲天堂2017无码中文 | 亚洲精品一区二区三区四区五区 | 欧美 亚洲 国产 另类 | 欧美喷潮久久久xxxxx | 亚欧洲精品在线视频免费观看 | 精品一区二区三区无码免费视频 | 亚洲欧洲无卡二区视頻 | 亚洲啪av永久无码精品放毛片 | 亚洲色大成网站www | 特大黑人娇小亚洲女 | 免费无码的av片在线观看 | 东北女人啪啪对白 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 久久综合网欧美色妞网 | 中文字幕无码av波多野吉衣 | 无码毛片视频一区二区本码 | 亚洲男女内射在线播放 | 成熟女人特级毛片www免费 | 亚洲 高清 成人 动漫 | 内射老妇bbwx0c0ck | 国产乱人偷精品人妻a片 | 国产美女极度色诱视频www | 国产极品美女高潮无套在线观看 | 无遮无挡爽爽免费视频 | 日韩精品a片一区二区三区妖精 | 欧洲欧美人成视频在线 | 激情亚洲一区国产精品 | 丝袜人妻一区二区三区 | 精品午夜福利在线观看 | 国产人妻精品午夜福利免费 | 无码人妻出轨黑人中文字幕 | 日韩精品a片一区二区三区妖精 | 亚洲 欧美 激情 小说 另类 | 色婷婷av一区二区三区之红樱桃 | 精品人妻人人做人人爽夜夜爽 | 精品人人妻人人澡人人爽人人 | 丰满人妻一区二区三区免费视频 | 亚拍精品一区二区三区探花 | 激情内射亚州一区二区三区爱妻 | 久久综合狠狠综合久久综合88 | 国产手机在线αⅴ片无码观看 | 亚洲国产一区二区三区在线观看 | 色综合久久88色综合天天 | 5858s亚洲色大成网站www | 欧美日韩人成综合在线播放 | 天堂在线观看www | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 欧美成人高清在线播放 | 伊人久久大香线蕉av一区二区 | 亚洲а∨天堂久久精品2021 | 日韩精品久久久肉伦网站 | 亚洲爆乳大丰满无码专区 | 欧洲精品码一区二区三区免费看 | 极品嫩模高潮叫床 | 国产免费无码一区二区视频 | 亚洲精品久久久久中文第一幕 | 国产精品亚洲а∨无码播放麻豆 | 国产精品第一国产精品 | 丝袜 中出 制服 人妻 美腿 | 老子影院午夜伦不卡 | 亚洲中文字幕乱码av波多ji | 激情内射亚州一区二区三区爱妻 | 日韩av激情在线观看 | 国内精品人妻无码久久久影院蜜桃 | 日本精品久久久久中文字幕 | 日本高清一区免费中文视频 | 99久久精品国产一区二区蜜芽 | 97久久国产亚洲精品超碰热 | 国产精品毛多多水多 | 中文字幕无码免费久久99 | 国产三级精品三级男人的天堂 | 国产成人无码区免费内射一片色欲 | 成人亚洲精品久久久久软件 | 少妇人妻av毛片在线看 | 久久综合网欧美色妞网 | www国产精品内射老师 | 国产午夜亚洲精品不卡 | 18黄暴禁片在线观看 | 伊人久久大香线蕉av一区二区 | 亚洲精品成人av在线 | 性做久久久久久久久 | 国产人妻精品一区二区三区不卡 | 99久久精品国产一区二区蜜芽 | 东京热一精品无码av | 成年美女黄网站色大免费全看 | 欧美人与牲动交xxxx | 午夜熟女插插xx免费视频 | 麻豆md0077饥渴少妇 | 国产高潮视频在线观看 | 婷婷五月综合激情中文字幕 | 国产精品无码mv在线观看 | 国产做国产爱免费视频 | 无遮挡国产高潮视频免费观看 | 2020最新国产自产精品 | 男人的天堂2018无码 | 99精品视频在线观看免费 | 中文字幕无码热在线视频 | 中文字幕无码免费久久99 | 日本一卡2卡3卡四卡精品网站 | 成年女人永久免费看片 | 色综合天天综合狠狠爱 | 天天摸天天透天天添 | 人人澡人人妻人人爽人人蜜桃 | 亚洲一区二区观看播放 | 国产区女主播在线观看 | 国产激情无码一区二区app | 国产精品亚洲а∨无码播放麻豆 | 欧美35页视频在线观看 | 国产人妻大战黑人第1集 | 国产在线一区二区三区四区五区 | 亚洲另类伦春色综合小说 | 性色欲情网站iwww九文堂 | 亚洲精品一区二区三区在线观看 | 亚洲爆乳精品无码一区二区三区 | 真人与拘做受免费视频一 | 乱中年女人伦av三区 | 少妇人妻大乳在线视频 | 久久精品中文字幕一区 | 日韩无套无码精品 | 亚洲色欲色欲天天天www | 性生交大片免费看l | 98国产精品综合一区二区三区 | 精品偷自拍另类在线观看 | 最新国产乱人伦偷精品免费网站 | 大乳丰满人妻中文字幕日本 | 午夜肉伦伦影院 | 欧美性黑人极品hd | 最新版天堂资源中文官网 | 久久久av男人的天堂 | 亚洲国产精品久久久天堂 | 人人妻人人藻人人爽欧美一区 | 久久久久亚洲精品男人的天堂 | 丰满诱人的人妻3 | 88国产精品欧美一区二区三区 | 久精品国产欧美亚洲色aⅴ大片 | 丁香花在线影院观看在线播放 | 亚洲欧美日韩成人高清在线一区 | 成年美女黄网站色大免费全看 | 久久久久se色偷偷亚洲精品av | 乱人伦人妻中文字幕无码 | 久久久久亚洲精品中文字幕 | 久久人人爽人人爽人人片av高清 | 久久久久免费精品国产 | 爽爽影院免费观看 | 无码乱肉视频免费大全合集 | 日日摸日日碰夜夜爽av | 欧美三级不卡在线观看 | 久9re热视频这里只有精品 | 国产欧美精品一区二区三区 | 国产av无码专区亚洲a∨毛片 | aa片在线观看视频在线播放 | 精品亚洲成av人在线观看 | 国产一区二区三区日韩精品 | 综合激情五月综合激情五月激情1 | 无遮无挡爽爽免费视频 | 一本色道婷婷久久欧美 | 熟妇激情内射com | 熟妇人妻激情偷爽文 | 国产亚洲美女精品久久久2020 | 国产在线精品一区二区三区直播 | 女人高潮内射99精品 | 亚洲综合色区中文字幕 | 国产福利视频一区二区 | 黑人大群体交免费视频 | 婷婷丁香五月天综合东京热 | а√资源新版在线天堂 | 亚洲 高清 成人 动漫 | 一本久久a久久精品vr综合 | 日本爽爽爽爽爽爽在线观看免 | 成年美女黄网站色大免费全看 | 久青草影院在线观看国产 | 亚洲乱码国产乱码精品精 | 亚洲国产av精品一区二区蜜芽 | 国产猛烈高潮尖叫视频免费 | 免费乱码人妻系列无码专区 | 午夜精品一区二区三区的区别 | 亚洲精品一区二区三区在线观看 | 97久久精品无码一区二区 | 欧美色就是色 | 国产精品第一国产精品 | 精品乱码久久久久久久 | 久久久久亚洲精品男人的天堂 | 人妻天天爽夜夜爽一区二区 | 国产精品久久久久影院嫩草 | 国产精品资源一区二区 | 国产免费久久精品国产传媒 | 一本色道久久综合亚洲精品不卡 | 亚洲乱码国产乱码精品精 | 国产高清av在线播放 | 欧美日韩一区二区综合 | 免费人成在线观看网站 | a国产一区二区免费入口 | 国产亚洲精品久久久久久久 | 国产亚洲精品久久久久久大师 | 美女黄网站人色视频免费国产 | 夜精品a片一区二区三区无码白浆 | 丰腴饱满的极品熟妇 | 国产又爽又猛又粗的视频a片 | 欧美freesex黑人又粗又大 | 中文字幕精品av一区二区五区 | 少妇被黑人到高潮喷出白浆 | 在线观看国产午夜福利片 | 日韩人妻少妇一区二区三区 | аⅴ资源天堂资源库在线 | 国产精品久久久久久亚洲毛片 | 久久熟妇人妻午夜寂寞影院 | 丰满肥臀大屁股熟妇激情视频 | 人人妻在人人 | 精品aⅴ一区二区三区 | 在线播放免费人成毛片乱码 | 扒开双腿疯狂进出爽爽爽视频 | 无码国产激情在线观看 | 无码纯肉视频在线观看 | 成人片黄网站色大片免费观看 | 国产成人一区二区三区在线观看 | 日本精品高清一区二区 | 欧美丰满熟妇xxxx性ppx人交 | 九九在线中文字幕无码 | 成人精品天堂一区二区三区 | 国产成人综合在线女婷五月99播放 | 99久久精品日本一区二区免费 | 国产偷自视频区视频 | 日日摸天天摸爽爽狠狠97 | 成人无码精品1区2区3区免费看 | 无码国产乱人伦偷精品视频 | 牲欲强的熟妇农村老妇女 | 国产精品无码mv在线观看 | 欧美成人免费全部网站 | 欧洲欧美人成视频在线 | 欧美 日韩 人妻 高清 中文 | 少妇性俱乐部纵欲狂欢电影 | 性生交大片免费看女人按摩摩 | 亚洲精品国产品国语在线观看 | 日韩人妻系列无码专区 | 天海翼激烈高潮到腰振不止 | 夜夜影院未满十八勿进 | 欧美阿v高清资源不卡在线播放 | 亚洲精品国产精品乱码不卡 | 国产肉丝袜在线观看 | 国产情侣作爱视频免费观看 | 中文字幕无码日韩专区 | 欧美第一黄网免费网站 | 亚洲国产精品无码久久久久高潮 | 欧美日韩一区二区三区自拍 | 夜精品a片一区二区三区无码白浆 | 99久久久国产精品无码免费 | 国产综合久久久久鬼色 | 狂野欧美性猛xxxx乱大交 | 午夜丰满少妇性开放视频 | 久久久久久国产精品无码下载 | 亚洲一区二区三区 | 日本va欧美va欧美va精品 | 亚洲大尺度无码无码专区 | 欧美人与物videos另类 | 亚洲成a人一区二区三区 | 野外少妇愉情中文字幕 | 曰本女人与公拘交酡免费视频 | 久久www免费人成人片 | 亚洲欧美国产精品专区久久 | 国产人妻精品一区二区三区 | 少妇激情av一区二区 | 亚洲 高清 成人 动漫 | 波多野结衣av一区二区全免费观看 | 性做久久久久久久久 | 久久久久久国产精品无码下载 | 麻豆精品国产精华精华液好用吗 | 欧美日本日韩 | 熟妇女人妻丰满少妇中文字幕 | 国产午夜手机精彩视频 | 国产无av码在线观看 | 久久久无码中文字幕久... | 老太婆性杂交欧美肥老太 | 高清国产亚洲精品自在久久 | 欧洲极品少妇 | 色婷婷综合中文久久一本 | 免费看男女做好爽好硬视频 | 99久久精品日本一区二区免费 | 国产猛烈高潮尖叫视频免费 | 亚洲成色www久久网站 | 四十如虎的丰满熟妇啪啪 | 国产午夜精品一区二区三区嫩草 | 亚洲小说春色综合另类 | 5858s亚洲色大成网站www | 欧美日韩亚洲国产精品 | 美女扒开屁股让男人桶 | 国产欧美熟妇另类久久久 | 亚洲の无码国产の无码影院 | 十八禁真人啪啪免费网站 | 国产精品对白交换视频 | 老太婆性杂交欧美肥老太 | 樱花草在线播放免费中文 | 亚洲毛片av日韩av无码 | 精品久久8x国产免费观看 | 老熟妇乱子伦牲交视频 | 亚洲第一网站男人都懂 | 国产精品理论片在线观看 | 久久国产精品精品国产色婷婷 | а√天堂www在线天堂小说 | 欧美人与牲动交xxxx | 国内揄拍国内精品少妇国语 | 性欧美熟妇videofreesex | 狠狠色噜噜狠狠狠狠7777米奇 | 欧美精品国产综合久久 | 欧美zoozzooz性欧美 | 无码av中文字幕免费放 | 成人无码视频免费播放 | 欧美熟妇另类久久久久久不卡 | 99精品国产综合久久久久五月天 | 欧美亚洲国产一区二区三区 | 欧美激情一区二区三区成人 | 日韩成人一区二区三区在线观看 | 亚洲а∨天堂久久精品2021 | 一本加勒比波多野结衣 | 男女性色大片免费网站 | 麻豆国产丝袜白领秘书在线观看 | 国产激情综合五月久久 | 无码乱肉视频免费大全合集 | 久久久久久九九精品久 | 亚洲 另类 在线 欧美 制服 | 亚洲爆乳无码专区 | 久9re热视频这里只有精品 | 无码人妻丰满熟妇区五十路百度 | 色综合视频一区二区三区 | 亚洲高清偷拍一区二区三区 | 欧美精品免费观看二区 | 免费乱码人妻系列无码专区 | 成人亚洲精品久久久久 | 综合人妻久久一区二区精品 | 一本色道久久综合亚洲精品不卡 | 4hu四虎永久在线观看 | 久久国产36精品色熟妇 | 暴力强奷在线播放无码 | 九九久久精品国产免费看小说 | 狠狠色色综合网站 | 精品国产av色一区二区深夜久久 | 国产又爽又黄又刺激的视频 | 亚洲成av人在线观看网址 | 成人精品视频一区二区三区尤物 | 国内老熟妇对白xxxxhd | 女人被爽到呻吟gif动态图视看 | 女人被爽到呻吟gif动态图视看 | 男女下面进入的视频免费午夜 | 天天做天天爱天天爽综合网 | 最近中文2019字幕第二页 | 色婷婷香蕉在线一区二区 | 国产成人无码av片在线观看不卡 | 两性色午夜视频免费播放 | 日本丰满熟妇videos | 亚洲区小说区激情区图片区 | 噜噜噜亚洲色成人网站 | 国产三级精品三级男人的天堂 | 日韩 欧美 动漫 国产 制服 | 无码人妻精品一区二区三区不卡 | 曰本女人与公拘交酡免费视频 | 乱中年女人伦av三区 | 国产激情综合五月久久 | 大地资源中文第3页 | 欧美日韩久久久精品a片 | 国产精品无套呻吟在线 | 四虎永久在线精品免费网址 | 欧美丰满熟妇xxxx | 任你躁在线精品免费 | 国内综合精品午夜久久资源 | 狠狠cao日日穞夜夜穞av | 国产无av码在线观看 | 人妻熟女一区 | 国産精品久久久久久久 | 中文字幕乱妇无码av在线 | 免费看男女做好爽好硬视频 | 国产精品鲁鲁鲁 | 国产va免费精品观看 | 兔费看少妇性l交大片免费 | 欧美丰满熟妇xxxx性ppx人交 | 亚洲精品久久久久avwww潮水 | 免费播放一区二区三区 | 国产乱人伦av在线无码 | 131美女爱做视频 | 美女毛片一区二区三区四区 | 精品久久8x国产免费观看 | 精品无码成人片一区二区98 | 男女超爽视频免费播放 | 一本久道久久综合狠狠爱 | 日本饥渴人妻欲求不满 | 国产av一区二区精品久久凹凸 | 5858s亚洲色大成网站www | 亚洲中文字幕无码一久久区 | 无码福利日韩神码福利片 | 亚洲精品久久久久avwww潮水 | 国产又爽又黄又刺激的视频 | 国产精品亚洲а∨无码播放麻豆 | 97久久精品无码一区二区 | 国产艳妇av在线观看果冻传媒 | 精品无码国产自产拍在线观看蜜 | 日韩人妻系列无码专区 | 娇妻被黑人粗大高潮白浆 | 亚洲人亚洲人成电影网站色 | 国产成人亚洲综合无码 | 国产精品久久精品三级 | 日韩精品乱码av一区二区 | 日韩人妻少妇一区二区三区 | 日本精品少妇一区二区三区 | 日韩精品无码一本二本三本色 | 2019nv天堂香蕉在线观看 | 在线a亚洲视频播放在线观看 | 亚洲自偷自拍另类第1页 | 亚洲色偷偷男人的天堂 | 国产精品亚洲一区二区三区喷水 | 欧美丰满少妇xxxx性 | 亚洲精品国产第一综合99久久 | 无码一区二区三区在线观看 | 亚洲色欲色欲欲www在线 | 天堂亚洲免费视频 | 欧美乱妇无乱码大黄a片 | 色婷婷av一区二区三区之红樱桃 | 日本精品人妻无码77777 天堂一区人妻无码 | 啦啦啦www在线观看免费视频 | 国内精品九九久久久精品 | 波多野结衣 黑人 | 亚洲爆乳大丰满无码专区 | 香港三级日本三级妇三级 | 日本肉体xxxx裸交 | 久久国产精品精品国产色婷婷 | 精品一区二区不卡无码av | 亚洲中文字幕无码中文字在线 | 好屌草这里只有精品 | 国内精品久久毛片一区二区 | 色老头在线一区二区三区 | 亚洲欧洲无卡二区视頻 | 成人欧美一区二区三区 | 精品国偷自产在线视频 | 久9re热视频这里只有精品 | 亚洲精品一区二区三区婷婷月 | 狠狠综合久久久久综合网 | 精品少妇爆乳无码av无码专区 | 影音先锋中文字幕无码 | 午夜成人1000部免费视频 | 亚洲国产欧美国产综合一区 | 精品偷拍一区二区三区在线看 | 精品无码一区二区三区爱欲 | 亚洲人亚洲人成电影网站色 | 秋霞成人午夜鲁丝一区二区三区 | 国产口爆吞精在线视频 | 亚洲色成人中文字幕网站 | 日日麻批免费40分钟无码 | 国产精品久久久久久久影院 | 亚洲一区二区三区香蕉 | 无码一区二区三区在线 | 亚洲日韩中文字幕在线播放 | 最近免费中文字幕中文高清百度 | 久久久久久国产精品无码下载 | 爽爽影院免费观看 | 国产成人一区二区三区在线观看 | 无码av免费一区二区三区试看 | 国产一区二区三区影院 | 18精品久久久无码午夜福利 | 精品 日韩 国产 欧美 视频 | 鲁一鲁av2019在线 | 宝宝好涨水快流出来免费视频 | 学生妹亚洲一区二区 | 日本护士xxxxhd少妇 | 久久www免费人成人片 | 狠狠色欧美亚洲狠狠色www | 国产av剧情md精品麻豆 | 成人精品视频一区二区三区尤物 | 国产另类ts人妖一区二区 | 无遮无挡爽爽免费视频 | 中文字幕人妻丝袜二区 | 国产两女互慰高潮视频在线观看 | 国产午夜福利亚洲第一 | 亚洲第一无码av无码专区 | 国产xxx69麻豆国语对白 | 在线成人www免费观看视频 | 久精品国产欧美亚洲色aⅴ大片 | 久久亚洲中文字幕精品一区 | 乱码午夜-极国产极内射 | 天天爽夜夜爽夜夜爽 | 亚洲s码欧洲m码国产av | 中文字幕无码乱人伦 | 久热国产vs视频在线观看 | 好爽又高潮了毛片免费下载 | 丰满人妻被黑人猛烈进入 | 丰腴饱满的极品熟妇 | 亚洲精品国偷拍自产在线麻豆 | 午夜理论片yy44880影院 | 高清国产亚洲精品自在久久 | 欧美性猛交内射兽交老熟妇 | 亚洲日本一区二区三区在线 | 亚洲一区av无码专区在线观看 | 国产精品久久久久9999小说 | 欧洲vodafone精品性 | 欧美午夜特黄aaaaaa片 | 国产真实乱对白精彩久久 | www国产亚洲精品久久久日本 | 国产性生交xxxxx无码 | 亚洲の无码国产の无码影院 | 精品无码国产一区二区三区av | 亚洲成a人片在线观看无码3d | 18精品久久久无码午夜福利 | 久久久久亚洲精品男人的天堂 | 久久人妻内射无码一区三区 | 国产成人无码午夜视频在线观看 | 少妇无码av无码专区在线观看 | 兔费看少妇性l交大片免费 | 国产亚洲精品久久久久久 | 精品少妇爆乳无码av无码专区 | 精品偷拍一区二区三区在线看 | 成人无码视频在线观看网站 | 无码精品国产va在线观看dvd | 亚洲国产精品一区二区美利坚 | 乱人伦人妻中文字幕无码久久网 | 久久久久久久人妻无码中文字幕爆 | 亚洲国产精品毛片av不卡在线 | 大地资源中文第3页 | 丰满人妻一区二区三区免费视频 | 欧美三级不卡在线观看 | 午夜精品久久久内射近拍高清 | 欧美精品一区二区精品久久 | 麻豆国产人妻欲求不满谁演的 | 午夜肉伦伦影院 | 又粗又大又硬又长又爽 | 性色av无码免费一区二区三区 | 7777奇米四色成人眼影 | 成人aaa片一区国产精品 | 红桃av一区二区三区在线无码av | 亚洲精品成人av在线 | 在线 国产 欧美 亚洲 天堂 | 小鲜肉自慰网站xnxx | 蜜臀aⅴ国产精品久久久国产老师 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 欧美日韩亚洲国产精品 | 亚洲区欧美区综合区自拍区 | 久久亚洲中文字幕无码 | 夜夜影院未满十八勿进 | 亚洲精品国产a久久久久久 | 久久亚洲精品成人无码 | 国产明星裸体无码xxxx视频 | 成人片黄网站色大片免费观看 | 一本色道久久综合狠狠躁 | 亚洲一区二区三区国产精华液 | 日本成熟视频免费视频 | 国产精品丝袜黑色高跟鞋 | 国产舌乚八伦偷品w中 | 亚洲综合伊人久久大杳蕉 | 国产午夜精品一区二区三区嫩草 | 成在人线av无码免费 | 大肉大捧一进一出视频出来呀 | 香蕉久久久久久av成人 | 一本久久伊人热热精品中文字幕 | a在线亚洲男人的天堂 | 亚洲精品午夜国产va久久成人 | 超碰97人人做人人爱少妇 | 亚洲国产精品成人久久蜜臀 | 中文精品无码中文字幕无码专区 | 国产九九九九九九九a片 | 性欧美大战久久久久久久 | av无码久久久久不卡免费网站 | 久久精品女人天堂av免费观看 | 亚洲精品鲁一鲁一区二区三区 | 欧美日韩综合一区二区三区 | 国产亚洲精品久久久闺蜜 | 精品成人av一区二区三区 | 福利一区二区三区视频在线观看 | 在线观看欧美一区二区三区 | 无码av最新清无码专区吞精 | 欧美野外疯狂做受xxxx高潮 | 久久精品中文字幕一区 | 国产区女主播在线观看 | 国产av剧情md精品麻豆 | 欧美日韩一区二区综合 | 免费看少妇作爱视频 | 日日噜噜噜噜夜夜爽亚洲精品 | 国产综合久久久久鬼色 | 青青青爽视频在线观看 | 免费男性肉肉影院 | 精品人妻中文字幕有码在线 | 国精产品一品二品国精品69xx | 亚洲综合伊人久久大杳蕉 | 在线观看国产一区二区三区 | 日韩少妇白浆无码系列 | 啦啦啦www在线观看免费视频 | 高清不卡一区二区三区 | 麻豆av传媒蜜桃天美传媒 | 无码人妻少妇伦在线电影 | 国产精品国产三级国产专播 | 99久久婷婷国产综合精品青草免费 | 对白脏话肉麻粗话av | 免费看少妇作爱视频 | 成 人 网 站国产免费观看 | 国产av一区二区三区最新精品 | 欧美日韩视频无码一区二区三 | 亚洲一区二区三区国产精华液 | 国产人妖乱国产精品人妖 | 精品人妻中文字幕有码在线 | √8天堂资源地址中文在线 | 午夜精品一区二区三区在线观看 | 久久久久久九九精品久 | 狂野欧美性猛交免费视频 | 丰满人妻精品国产99aⅴ | 日韩精品a片一区二区三区妖精 | 日韩在线不卡免费视频一区 | 青草视频在线播放 | 67194成是人免费无码 | 激情内射日本一区二区三区 | 少妇性l交大片欧洲热妇乱xxx | 亚洲精品www久久久 | 国产亲子乱弄免费视频 | 久久 国产 尿 小便 嘘嘘 | 国产亚洲精品久久久久久国模美 | 国产精品久久久久久久影院 | 国产香蕉尹人视频在线 | 亚洲色www成人永久网址 | 久久99热只有频精品8 | 国产又爽又猛又粗的视频a片 | 亚洲国产av精品一区二区蜜芽 | 色综合久久中文娱乐网 | 无码一区二区三区在线 | 免费无码肉片在线观看 | 鲁大师影院在线观看 | 男女超爽视频免费播放 | 国产卡一卡二卡三 | 国产色在线 | 国产 | 亚洲狠狠婷婷综合久久 | 久久精品国产大片免费观看 | 日本乱人伦片中文三区 | 亚洲va中文字幕无码久久不卡 | 好爽又高潮了毛片免费下载 | 樱花草在线播放免费中文 | 又湿又紧又大又爽a视频国产 | 婷婷六月久久综合丁香 | 男女猛烈xx00免费视频试看 | 在线а√天堂中文官网 | 国产成人精品一区二区在线小狼 | 中文精品久久久久人妻不卡 | 精品成人av一区二区三区 | 无套内射视频囯产 | 成人免费视频一区二区 | 亚洲精品美女久久久久久久 | 精品亚洲韩国一区二区三区 | 精品一区二区三区波多野结衣 | 久久国内精品自在自线 | 国产亚洲精品久久久ai换 | 麻豆md0077饥渴少妇 | 无码福利日韩神码福利片 | 欧美日韩一区二区综合 | 亚洲国产精品久久久天堂 | 性做久久久久久久久 | 俄罗斯老熟妇色xxxx | 亚洲国产成人a精品不卡在线 | 亚洲熟妇色xxxxx欧美老妇y | 国产精品嫩草久久久久 | 在线 国产 欧美 亚洲 天堂 | 日本爽爽爽爽爽爽在线观看免 | 亚洲国产精品成人久久蜜臀 | 最新版天堂资源中文官网 | 久久精品成人欧美大片 | 巨爆乳无码视频在线观看 | 大色综合色综合网站 | 久久午夜无码鲁丝片秋霞 | 婷婷丁香六月激情综合啪 | 婷婷五月综合激情中文字幕 | 一二三四社区在线中文视频 | 色综合久久中文娱乐网 | 久久综合给久久狠狠97色 | 国产乱子伦视频在线播放 | 亚洲一区二区三区香蕉 | 亚洲精品国偷拍自产在线观看蜜桃 | 人妻无码久久精品人妻 | 久久精品中文闷骚内射 | 国产激情艳情在线看视频 | 夜夜躁日日躁狠狠久久av | 久久精品99久久香蕉国产色戒 | 国产人妻久久精品二区三区老狼 | 天天摸天天透天天添 | 亚洲乱亚洲乱妇50p | 成年女人永久免费看片 | 欧美兽交xxxx×视频 | 一本久道久久综合狠狠爱 | 精品国产一区二区三区av 性色 | 亚洲成a人片在线观看日本 | 亚洲啪av永久无码精品放毛片 | 女人和拘做爰正片视频 | 天天燥日日燥 | 婷婷综合久久中文字幕蜜桃三电影 | 国产精品久久久 | 久久午夜夜伦鲁鲁片无码免费 | 377p欧洲日本亚洲大胆 | 少妇性俱乐部纵欲狂欢电影 | 乱中年女人伦av三区 | 国产午夜无码视频在线观看 | 成人欧美一区二区三区黑人免费 | 一本精品99久久精品77 | aa片在线观看视频在线播放 | 亚洲精品欧美二区三区中文字幕 | 伊人久久婷婷五月综合97色 | 亚洲日本va午夜在线电影 | 欧美日韩视频无码一区二区三 | 精品厕所偷拍各类美女tp嘘嘘 | 色综合久久久久综合一本到桃花网 | 无码av免费一区二区三区试看 | 丝袜足控一区二区三区 | 婷婷色婷婷开心五月四房播播 | 高清不卡一区二区三区 | 久久亚洲中文字幕无码 | 亚洲精品一区二区三区婷婷月 | 精品无码国产一区二区三区av | 99久久精品午夜一区二区 | 麻豆蜜桃av蜜臀av色欲av | 中文字幕乱妇无码av在线 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 亚洲一区二区三区在线观看网站 | 又粗又大又硬毛片免费看 | 国产熟妇另类久久久久 | 久久午夜无码鲁丝片午夜精品 | 亚无码乱人伦一区二区 | 沈阳熟女露脸对白视频 | 偷窥村妇洗澡毛毛多 | 人妻插b视频一区二区三区 | 国内精品久久毛片一区二区 | 色五月丁香五月综合五月 | 欧美阿v高清资源不卡在线播放 | 18精品久久久无码午夜福利 | 小sao货水好多真紧h无码视频 | 亚洲gv猛男gv无码男同 | 成人毛片一区二区 | 中文毛片无遮挡高清免费 | 久久精品一区二区三区四区 | 红桃av一区二区三区在线无码av | 波多野42部无码喷潮在线 | 国产午夜精品一区二区三区嫩草 | 无遮挡国产高潮视频免费观看 | 欧美日韩人成综合在线播放 | 国产成人久久精品流白浆 | 亚洲男人av香蕉爽爽爽爽 | 国产一区二区三区日韩精品 | 国产午夜视频在线观看 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 色婷婷综合中文久久一本 | 撕开奶罩揉吮奶头视频 | 国产9 9在线 | 中文 | 夫妻免费无码v看片 | 偷窥日本少妇撒尿chinese | 成人无码精品一区二区三区 | 亚洲精品国产品国语在线观看 | 国产成人无码一二三区视频 | 天下第一社区视频www日本 | 国产精品美女久久久网av | 网友自拍区视频精品 | 成人亚洲精品久久久久软件 | 国产精品第一国产精品 | 亚洲国产一区二区三区在线观看 | 十八禁真人啪啪免费网站 | 日产国产精品亚洲系列 | 亚洲欧美精品aaaaaa片 | 精品乱子伦一区二区三区 | 成人毛片一区二区 | 亚洲大尺度无码无码专区 | 人人妻人人澡人人爽欧美精品 | 亚洲日韩av一区二区三区四区 | 欧美激情综合亚洲一二区 | 一区二区三区乱码在线 | 欧洲 | 在线欧美精品一区二区三区 | 在线观看免费人成视频 | 国产精品无码永久免费888 | 水蜜桃亚洲一二三四在线 | 人妻熟女一区 | 亚洲欧美国产精品久久 | 亚洲va欧美va天堂v国产综合 | 丰满人妻一区二区三区免费视频 | 国产乱人偷精品人妻a片 | 亚洲娇小与黑人巨大交 | 国产热a欧美热a在线视频 | 无码任你躁久久久久久久 | 国产人妻精品一区二区三区 | 清纯唯美经典一区二区 | 国产午夜无码视频在线观看 | 免费人成在线观看网站 | 2020最新国产自产精品 | 丰满人妻翻云覆雨呻吟视频 | 99国产欧美久久久精品 | 亚洲欧美精品aaaaaa片 | 色婷婷久久一区二区三区麻豆 | a片免费视频在线观看 | 国产色精品久久人妻 | 国产性生交xxxxx无码 | 人人澡人摸人人添 | 国产黄在线观看免费观看不卡 | 国产成人综合在线女婷五月99播放 | 久久99精品久久久久婷婷 | 亚洲欧美国产精品专区久久 | 亚洲精品一区二区三区大桥未久 | 成人无码视频在线观看网站 | 中文精品无码中文字幕无码专区 | 午夜精品久久久久久久 | 国产亚洲欧美在线专区 | 中文字幕中文有码在线 | 日本xxxx色视频在线观看免费 | 大肉大捧一进一出视频出来呀 | 内射老妇bbwx0c0ck | 国产精品久久久 | 国产精品va在线观看无码 | 国产亚洲精品久久久闺蜜 | 全黄性性激高免费视频 | 国产精品人妻一区二区三区四 | 99久久亚洲精品无码毛片 | 精品成人av一区二区三区 | 亚洲国产精品久久久久久 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 国产午夜精品一区二区三区嫩草 | 野狼第一精品社区 | 在线 国产 欧美 亚洲 天堂 | 日本熟妇乱子伦xxxx | 亚洲国产欧美国产综合一区 | 日韩精品无码一本二本三本色 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 大胆欧美熟妇xx | 狂野欧美激情性xxxx | 亚洲 a v无 码免 费 成 人 a v | 日本丰满护士爆乳xxxx | 乱码av麻豆丝袜熟女系列 | 人妻互换免费中文字幕 | 熟女少妇在线视频播放 | 成人免费无码大片a毛片 | 亚洲呦女专区 | 精品乱码久久久久久久 | 欧美熟妇另类久久久久久多毛 | 亚洲人成人无码网www国产 | 国产成人av免费观看 | 精品aⅴ一区二区三区 | 亚洲第一无码av无码专区 | 国产精品igao视频网 | 国产精品亚洲а∨无码播放麻豆 | 精品亚洲韩国一区二区三区 | 中文字幕乱码人妻二区三区 | 无码人妻少妇伦在线电影 | 国产精品久久久一区二区三区 | 人妻无码久久精品人妻 | 成人精品视频一区二区三区尤物 | 天天躁夜夜躁狠狠是什么心态 | 精品国产av色一区二区深夜久久 | 国产乱人伦av在线无码 | 久青草影院在线观看国产 | 麻豆国产97在线 | 欧洲 | 兔费看少妇性l交大片免费 | 国产乱人偷精品人妻a片 | 西西人体www44rt大胆高清 | 精品久久综合1区2区3区激情 | 亚洲精品国偷拍自产在线观看蜜桃 | 动漫av网站免费观看 | 天天综合网天天综合色 | 国产情侣作爱视频免费观看 | 中文字幕色婷婷在线视频 | 久久久久成人精品免费播放动漫 | 中文精品久久久久人妻不卡 | 牲欲强的熟妇农村老妇女 | 成熟妇人a片免费看网站 | 一本大道伊人av久久综合 | 熟妇人妻中文av无码 | 中国大陆精品视频xxxx | 青青青爽视频在线观看 | 女人高潮内射99精品 | 正在播放老肥熟妇露脸 | 中文字幕乱码中文乱码51精品 | 亚洲色欲色欲欲www在线 | 在线а√天堂中文官网 | 亚洲精品国产品国语在线观看 | 久久精品中文字幕大胸 | 国产成人综合在线女婷五月99播放 | 精品国产一区av天美传媒 | 在线观看免费人成视频 | 性史性农村dvd毛片 | 日本丰满护士爆乳xxxx | 国产精品久久久久9999小说 | 在线观看欧美一区二区三区 | 久久亚洲国产成人精品性色 | 又紧又大又爽精品一区二区 | 性开放的女人aaa片 | 少妇太爽了在线观看 | 国产成人亚洲综合无码 | 亚欧洲精品在线视频免费观看 | 沈阳熟女露脸对白视频 | 国产又粗又硬又大爽黄老大爷视 | 野外少妇愉情中文字幕 | 亚洲小说春色综合另类 | 亚洲 欧美 激情 小说 另类 | 欧美人与牲动交xxxx | 中文字幕色婷婷在线视频 | 一本色道婷婷久久欧美 | 亚洲日本在线电影 | 久久人人爽人人爽人人片av高清 | 国产成人无码专区 | 成年美女黄网站色大免费全看 | 国产av一区二区三区最新精品 | 俄罗斯老熟妇色xxxx | 又粗又大又硬毛片免费看 | а天堂中文在线官网 | 强辱丰满人妻hd中文字幕 | 强开小婷嫩苞又嫩又紧视频 | 色综合久久88色综合天天 | 国产精品二区一区二区aⅴ污介绍 | 欧美激情综合亚洲一二区 | 久久国产自偷自偷免费一区调 | 国产亚洲tv在线观看 | 波多野结衣av一区二区全免费观看 | 精品国产麻豆免费人成网站 | 人人澡人人透人人爽 | 88国产精品欧美一区二区三区 | 国产成人无码午夜视频在线观看 | 88国产精品欧美一区二区三区 | 国产激情综合五月久久 | 欧美性猛交内射兽交老熟妇 | 久久伊人色av天堂九九小黄鸭 | 天堂а√在线地址中文在线 | 一区二区传媒有限公司 | 亚洲精品一区二区三区四区五区 | 久久久久99精品成人片 | 国产成人精品优优av | 永久免费观看美女裸体的网站 | 四十如虎的丰满熟妇啪啪 | 精品少妇爆乳无码av无码专区 | 又大又黄又粗又爽的免费视频 | 狠狠色色综合网站 | 亚洲日韩av一区二区三区中文 | 亚洲精品国产精品乱码视色 | 国产办公室秘书无码精品99 | 真人与拘做受免费视频一 | 亚洲中文字幕在线无码一区二区 | 人妻体内射精一区二区三四 | 色狠狠av一区二区三区 | 亚洲日本在线电影 | 亚洲国产精品无码久久久久高潮 | 国产在线一区二区三区四区五区 | 任你躁在线精品免费 | 99riav国产精品视频 | 精品偷自拍另类在线观看 | 色诱久久久久综合网ywww | 亚洲另类伦春色综合小说 | 久久综合九色综合欧美狠狠 | 欧美精品无码一区二区三区 | 亚洲aⅴ无码成人网站国产app | 女人被男人爽到呻吟的视频 | 大胆欧美熟妇xx | 图片小说视频一区二区 | 精品久久久无码人妻字幂 | 无码人妻精品一区二区三区下载 | 天海翼激烈高潮到腰振不止 | 久久无码专区国产精品s | 美女毛片一区二区三区四区 | 麻花豆传媒剧国产免费mv在线 | 婷婷五月综合缴情在线视频 | 亚洲欧美日韩成人高清在线一区 | 国产特级毛片aaaaaaa高清 | 久久成人a毛片免费观看网站 | 蜜臀av无码人妻精品 | 久久这里只有精品视频9 | 亚洲国产av美女网站 | 97无码免费人妻超级碰碰夜夜 | 狠狠躁日日躁夜夜躁2020 | 无码国模国产在线观看 | 永久免费精品精品永久-夜色 | 野狼第一精品社区 | 十八禁真人啪啪免费网站 | 国产精品亚洲专区无码不卡 | 精品人妻中文字幕有码在线 | 国产午夜视频在线观看 | 久久综合给久久狠狠97色 | 在教室伦流澡到高潮hnp视频 | 国产在线无码精品电影网 | 99久久久无码国产aaa精品 | 免费无码午夜福利片69 | 青春草在线视频免费观看 | 国产69精品久久久久app下载 | 久久久久99精品成人片 | 国产香蕉97碰碰久久人人 | 国产成人综合色在线观看网站 | 国产精品多人p群无码 | 色窝窝无码一区二区三区色欲 | 青草青草久热国产精品 | 亚洲男女内射在线播放 | 国产极品美女高潮无套在线观看 | 美女毛片一区二区三区四区 | 久青草影院在线观看国产 | 亚洲另类伦春色综合小说 | 捆绑白丝粉色jk震动捧喷白浆 | 少妇被粗大的猛进出69影院 | 女人被男人躁得好爽免费视频 | 超碰97人人射妻 | 黑人玩弄人妻中文在线 | 久久综合久久自在自线精品自 | 成熟女人特级毛片www免费 | 国产高清av在线播放 | 亚洲热妇无码av在线播放 | 亚洲无人区午夜福利码高清完整版 |