MyBatisPlus + SpringBoot + Thymeleaf 完成簡單的入門案例【上】

這篇文章主要用於學習SpringBoot以及整合MyBatisPlus和前端框架LayUI,使用的都是最新的版本:
SpringBoot2.1.0 , MyBatisPlus3.0.6 , LayUI2.4.5 ,MySQL5.5.15, 以及SpringBoot默認支持的模板引擎Thymeleaf3.0.4,使用以上技術,瞭解SpringBoot怎麼集成以上框架技術,怎麼完成XML配置的替換等…

本來是做一個RBAC權限管理系統的,但是這樣做的話,這個博客會很臃腫,後面把它改了,重點還是介紹SpringBoot的相關知識。以及各個技術的集成,先簡單的入門,後面再統一完成項目說明,就不在做技術知識點講解了

SpringBoot簡介

Spring由於其繁瑣的配置,一度被人認爲“配置地獄”,各種XML、Annotation配置,讓人眼花繚亂,而且如果出錯了也很難找出原因。Spring Boot更多的是採用Java Config的方式,對Spring進行配置。
Spring Boot可以輕鬆創建獨立的,生產級的基於Spring的應用程序,您可以直接“運行”啓動服務,而不用再去配置Tomcat啓動。包括靜態資源處理,視圖解析器,註解掃描等…

  • SpringBoot特徵
  1. 創建獨立的Spring應用程序
  2. 直接嵌入Tomcat,Jetty或Undertow(無需部署WAR文件)
  3. 提供簡潔的“入門”依賴項以簡化構建配置,簡化Maven配置
  4. 儘可能自動配置Spring和第三方庫,大部分數據源,Mapper掃描,組件注入都不用再處理,去處理繁瑣的Xml配置
  5. 提供生產就緒功能,例如指標,運行狀況檢查和外部化配置
  6. 絕對沒有代碼生成,也不需要XML配置
  7. SpringBoot2.1.0官方學習地址
  • 依賴管理
    每個版本的Spring Boot都提供了它支持的依賴項的精選列表。實際上,您不需要爲構建配置中的任何這些依賴項提供版本,因爲Spring Boot會爲您管理這些依賴項。當您升級Spring Boot時,這些依賴項也會以一致的方式升級。就不用再單獨使用properties來提取對應的jar包版本,SpringBoot的parent幫你搞定.在配置對應jar的時候,不需要再寫版本號了。SpringBoot沒有提供的除外。
<! - 繼承默認值爲Spring Boot  - > 
<parent> 
	<groupId> org.springframework.boot </ groupId> 
	<artifactId> spring-boot-starter-parent </ artifactId> 
	<version> 2.1.0.RELEASE < / version> 
</ parent>

如果需要,您仍然可以指定版本並覆蓋Spring Boot的建議。當然,每個版本的Spring Boot都與Spring Framework的基本版本相關聯。我們強烈建議您不要指定其版本。SpringBoot沒有提供的除外.

  • SpringBoot提供的應用程序啓動器,也就是封裝好的Maven配置常用的如下,具體查看幫助文檔。
名稱 描述
spring-boot-starter 核心啓動器,包括自動配置支持,日誌記錄和YAML
spring-boot-starter-aop 使用Spring AOP和AspectJ進行面向方面編程的入門者
spring-boot-starter-freemarker 使用FreeMarker視圖構建MVC Web應用程序的入門者
spring-boot-starter-thymeleaf 使用Thymeleaf視圖構建MVC Web應用程序的入門者
spring-boot-starter-jdbc 將JDBC與HikariCP連接池一起使用的入門者
spring-boot-starter-json 配置jackson,完成對象轉換JSON的操作,需要注意的是要轉換的JSON對象需要符合JavaBean規範
spring-boot-starter-mail 使用Java Mail和Spring Framework的電子郵件發送支持的初學者
spring-boot-starter-test 使用JUnit,Hamcrest和Mockito等庫來測試Spring Boot應用程序的初學者
spring-boot-starter-validation 使用Java Bean Validation和Hibernate Validator的初學者
spring-boot-starter-web 使用Spring MVC構建Web(包括RESTful)應用程序的入門者。使用Tomcat作爲默認嵌入式容器
spring-boot-starter-webflux 使用Spring Framework的Reactive Web支持構建WebFlux應用程序的初學者
spring-boot-starter-websocket 使用Spring Framework的WebSocket支持構建WebSocket應用程序的初學者
spring-boot-starter-logging 使用Logback進行日誌記錄的入門。默認日誌啓動器
spring-boot-starter-log4j2 使用Log4j2進行日誌記錄的入門。替代spring-boot-starter-logging
spring-boot-starter-tomcat 使用Tomcat作爲嵌入式servlet容器的入門者。使用的默認servlet容器啓動器spring-boot-starter-web
spring-boot-starter-jetty 使用Jetty作爲嵌入式servlet容器的入門。替代spring-boot-starter-tomcat
  • SpringBoot和SpringMVC常用註解
註解名稱 描述
@SpringBootApplication 包含了@ComponentScan、@Configuration和@EnableAutoConfiguration註解。其中@ComponentScan讓spring Boot掃描到Configuration類並把它加入到程序上下文
@ImportResource 用來加載xml配置文件
@Bean 用@Bean標註方法等價於XML中配置的bean
@Value 注入SpringBoot 核心配置文件(application.yml > application.properties)中屬性對應的值
@Configuration 等同於spring的XML配置文件;使用Java代碼可以檢查類型安全。替代xml配置,通過@Bean完成注入,可以使用@ImportResource註解加載xml配置文件
@ConfigurationProperties 方法註解,用於獲取核心配置文件中對應以什麼開頭的多個屬性,並自動注入到對象中
@Import 用來導入其他配置類
@Primary 方法註解,用於標註,如果IOC容器中存在多個相同類型的Bean,優先使用被標註的Bean對象
@EnableAutoConfiguration 自動配置
@ExceptionHandler 用在方法上面表示遇到這個異常就執行以下方法
@ControllerAdvice、@RestControllerAdivce 包含@Component。可以被掃描到。統一處理異常,類註解,需要和@ExceptionHandler配合使用
@ResponseBody 標註返回的信息使用Json轉換
@Controller 用於定義控制器類,通常方法需要配合註解@RequestMapping
@RestController 是@Controller和@ResponseBody的合集,表示這是個控制器bean,並且是將函數的返回值直 接填入HTTP響應體中,是REST風格的控制器
@PathVariable 獲取參數
@Autowired / @Resource 自動注入,獲取SpringIOC容器中的bean對象
@ComponentScan 組件掃描,可自動發現和裝配一些Bean
@Component、@Service、@Repository 把當前標註的類注入到IOC容器中,需要@ComponentScan配置註解標註類路徑,各個註解實現結果一致,只是應用場景不同,屬於語義話註解
  • 其它常用註解
  1. lombok註解
註解名稱 描述
@Data 註解在類上;提供類所有屬性的 getting 和 setting 方法,此外還提供了equals、canEqual、hashCode、toString 方法
@Setter / @Gette r 註解在屬性上;爲屬性提供 setting 方法/getting方法
@Log4j2 / @Slf4j 註解在類上;爲類提供一個 屬性名爲log 的 log4j 日誌對象,和@Log4j註解類似
@NoArgsConstructor / @AllArgsConstructor 註解在類上;爲類提供一個無參/有參的構造方法
@EqualsAndHashCode 默認情況下,會使用所有非瞬態(non-transient)和非靜態(non-static)字段來生成equals和hascode方法,也可以指定具體使用哪些屬性。
@toString 生成toString方法,默認情況下,會輸出類名、所有屬性,屬性會按照順序輸出,以逗號分割
@Builder 被註解的類加個構造者模式
@NonNull 如果給參數加個這個註解 參數爲null會拋出空指針異常
@Value 註解和@Data類似,區別在於它會把所有成員變量默認定義爲private final修飾,並且不會生成set方法
@Synchronized 加個同步鎖
  1. jackson註解
註解名稱 描述
@JsonNaming 字段命名映射策略: KebabCaseStrategy: 肉串策略 - 單詞小寫,使用連字符’-‘連接,SnakeCaseStrategy: 蛇形策略 - 單詞小寫,使用下劃線’_'連接;UpperCamelCaseStrategy: 駝峯策略 - 單詞首字母大寫其它小寫,不添加連接符
@JsonIgnoreProperties 類註解,指定序列化時忽略這些屬性,可以用於覆蓋超類中默認輸出的屬性
@JsonInclude 僅在屬性不爲空時序列化此字段,對於字符串,即null或空字符串
@JsonIgnore 字段註解,序列化時忽略此字段
@JsonProperty 指定序列化時的字段名,默認使用屬性名
@JsonFormat 指定Date類字段序列化時的格式,java8 LocalDateTime序列號需要加入jsr310解析包
@JsonUnwrapped 把成員對象中的屬性提升到其容器類,並添加給定的前綴,比如上例中: User類中有name和age兩個屬性,不使用此註解則序列化爲:… “user”: { “name”: “xxx”, “age”: 22 } …使用此註解則爲:… “user_name”: “xxx”, “user_age”: 22,
@JsonIgnoreType 類註解,序列化時忽略此類

使用IDEA工具快速構建SpringBoot入門案例

主要目的就是了解Spring的使用原理,以及它的Java替代XML配置的寫法,比如攔截器,異常處理,數據源注入,MyBatis配置信息等處理。這裏使用的IDEA使用2017.3.5這個版本。

在這裏插入圖片描述
在這裏插入圖片描述

pom.xml文件和之前的springMVC中的略有差異,使用了很多SpringBoot提供的繼承配置,直接在parent包中查找,大部分是不用提供版本號的。具體如下:

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

	<groupId>com.cdh.springboot</groupId>
	<artifactId>rbac-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>rbac-demo</name>
	<description></description>

	<!--SpringBoot核心依賴,是屬於maven的繼承父類,提供了很多集成配置,還有很多jar包的版本全局屬性等。-->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!--基本上包含了jdbc操作所需的所有jar:spring-jdbc,logback,log4j,sl4j,annotation... -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<!--模板引擎:thymeleaf,java8time-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<!--spring Web模塊:jackson,tomcat,validator,spring-web,webmvc-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--實體幫助類-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!--spring-boot-test,junit4.12,spring-test,json-path..-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!--**********以上是使用IDEA工具勾選後默認生成的,以下是我自定義添加的其他依賴包*****************-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<!--mybatis-plus,mybatis-spring,mybatis,mybatis-plus-generator ...-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.0.6</version>
		</dependency>
		<!--mybatis-plus完成項目構建所需模板,真實項目不需要使用-->
		<dependency>
			<groupId>org.freemarker</groupId>
			<artifactId>freemarker</artifactId>
		</dependency>
		<!--數據源+session監控-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.12</version>
		</dependency>
		<!-- 驗證碼 -->
		<dependency>
			<groupId>com.github.penggle</groupId>
			<artifactId>kaptcha</artifactId>
			<version>2.3.2</version>
		</dependency>
		<!-- 漢字轉換爲拼音 -->
		<dependency>
			<groupId>com.belerweb</groupId>
			<artifactId>pinyin4j</artifactId>
			<version>2.5.1</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<!--springBoot打包插件-->
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

在數據庫中創建兩張表,用於測試

創建表可以直接使用SQL語句完成,也可以直接在數據庫UI工具中完成,當然比較建議大家使用建模工具,這樣不僅可以提高建錶速度,也可以提高對工具的熟練度,不要覺得麻煩,這是必須要掌握的,常用的建模工具有Power Designer和EZDML,我經常用的是後者,如果要學習它,可以查看我博客中有一篇是介紹這個工具的使用表設計工具EZDML使用詳細教程

表結構和字段說明如下:

在這裏插入圖片描述

SQL腳本:

使用MybatisPlus 的代碼生成器獲取所需文件

AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發效率。而要完成這一功能,只需要提供一個Java 可運行方法即可。文件結構如下:
在這裏插入圖片描述

-- sys_dept
/*
警告: 字段名可能非法 - status
*/
create table  `sys_dept`(
       `dept_id`         INT auto_increment primary key not null comment '編號',
       `dept_name`       VARCHAR(25) comment '名稱',
       `loc`             VARCHAR(300) comment '地址',
       `parent_id`       INT comment '上級部門',
       `level`           VARCHAR(64) comment '層級',
       `leader`          VARCHAR(16) comment '部門負責人',
       `phone`           VARCHAR(16) comment '聯繫電話',
       `order_num`       INT(4) comment '排序號',
       `status`          INT(1) comment '狀態',
       `del_flg`         INT(1) comment '刪除標誌',
       `create_by`       VARCHAR(64) comment '創建者',
       `create_time`     TIMESTAMP DEFAULT 0 comment '創建時間',
       `update_by`       VARCHAR(64) comment '更新着',
       `update_time`     TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '更新時間'
);
alter table `sys_dept` comment= '部門表';

-- sys_user
/*
警告: 字段名可能非法 - status
*/
create table  `sys_user`(
       `user_id`         INT auto_increment primary key not null comment '編號',
       `user_name`       VARCHAR(32) comment '真實名稱',
       `log_name`        VARCHAR(32) comment '登錄名',
       `user_type`       VARCHAR(8) comment '用戶類型 系統用戶,臨時用戶',
       `dept_id`         INT comment '部門編號',
       `user_pwd`        VARCHAR(64) comment '登錄密碼',
       `email`           VARCHAR(32) comment '郵箱',
       `phone`           VARCHAR(14) comment '聯繫方式',
       `sex`             INT(1) comment '性別',
       `order_num`       INT(4) comment '排序號',
       `status`          INT(1) comment '狀態',
       `del_flg`         INT(1) comment '刪除標誌',
       `create_by`       VARCHAR(64) comment '創建者',
       `create_time`     TIMESTAMP DEFAULT 0 comment '創建時間',
       `update_by`       VARCHAR(64) comment '更新者',
       `update_time`     TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '更新時間'
);
alter table `sys_user` comment= '用戶表';

alter  table `sys_user` add constraint `FK_sys_user_dept_id` foreign key (`dept_id`) references `sys_dept`(`dept_id`);

需要注意的是在MySQL5.7之前的版本在同一張表中寫入兩個日期字段必須有一個default 0否則無法執行.

MyBatisPlusGenerator.java文件信息

package com.cdh.springboot.generator;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

public class MyBatisPlusGenerator {

    @Test
    public void codeGenerator() {
        // 代碼生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("CDHong");//Mapper,Service類註解中顯示創建人信息
        //gc.setBaseColumnList(true); //在Mapper.xml文件中是否生成公用SQL代碼段
        //gc.setBaseResultMap(true);  //在Mapper.xml文件中是否生成公用返回集合ResultMap
        gc.setOpen(false);  //文件生成完畢後,是否需要打開所在路徑
        mpg.setGlobalConfig(gc);

        // 數據源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.cdh.springboot");  //父級公用包名,就是自動生成的文件放在項目路徑下的那個包中
        mpg.setPackageInfo(pc);

        // 自定義配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };
        List<FileOutConfig> focList = new ArrayList<>();
        focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定義Mapper.xml文件存放的路徑
                return projectPath + "/src/main/resources/mapper/"
                        + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);
        mpg.setTemplate(new TemplateConfig().setXml(null));

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);  //Entity文件名稱命名規範
        strategy.setColumnNaming(NamingStrategy.underline_to_camel); //Entity字段名稱
        strategy.setEntityLombokModel(true); //是否使用lombok完成Entity實體標註Getting Setting ToString 方法
        //strategy.setRestControllerStyle(true); //Controller註解使用是否RestController標註,否則是否開啓使用Controller標註
        strategy.entityTableFieldAnnotationEnable(true); //是否在Entity屬性上通過註解完成對數據庫字段的映射
        strategy.setControllerMappingHyphenStyle(true);  //Controller註解名稱,不使用駝峯,使用連字符
        strategy.setTablePrefix("sys_");  //表前綴,添加該表示,則生成的實體,不會有表前綴,比如sys_dept 生成就是Dept
        //strategy.setFieldPrefix("sys_");  //字段前綴
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}
  • 生成結果如圖所示:
    在這裏插入圖片描述

SpringBoot啓動類

SpringBoot啓動類就是用於項目的啓動,而啓動方式是使用java的applications方式啓動,即在包根目錄下添加啓動類,必須包含main方法,再添加Spring Boot啓動方法:

  • SpringApplication.run(SampleController.class, args);
  • new SpringApplicationBuilder().run(args);
    不再使用之間的Tomcat部署,war包啓動。簡化了我們項目部署的步驟。文件內容如下:
package com.cdh.springboot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


//這是一個組合註解,包含了@Configuration,@EnableAutoConfiguration,@ComponentScan。
//@Configuration : 標識這爲一個配置文件類型,可以通過@Bean註解來標註配置文件中的Bean對象
//@EnableAutoConfiguration : 能夠自動配置spring的上下文,試圖猜測和配置你想要的bean類,通常會自動根據你的類路徑和你的bean定義自動配置。
//@ComponentScan : 會自動掃描指定包下的全部標有@Component的類,並註冊成bean,當然包括@Component下的子註解@Service,@Repository,@Controller。
//                   前提是標註的類是當前類的子孫包中。
@SpringBootApplication
@MapperScan(basePackages = "com.cdh.springboot.mapper") //掃描自定義的Mapper接口,並注入對應的SqlSession實例
public class RbacDemoApplication {

	//SpringBoot的啓動方法
	public static void main(String[] args) {
		SpringApplication.run(RbacDemoApplication.class, args);
	}

}

集成配置MyBatisPlus以及數據源

在SpringMVC中數據源配置和MyBatis相關配置都是在xml中進行的,而SpringBoot中不推薦這麼寫,且大部分功能都通過自動配置完成了,極少部分需要手動配置,那麼這些配置都是通過Java+Annotation的方式來完成的。
新建一個包(config)用於後續的其他配置(攔截器,異常…)在這個包中創建一個MyBatisPlusConfig類用於Druid數據源和MP的分頁注入:

package com.cdh.springboot.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.ResourceServlet;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration //標註它是一個配置類,類似於新建一個spring的xml配置文件
public class MyBatisPlusConfig {

    /**
     * Druid數據源配置
     * @return
     */
    @Bean("druidDataSource") //標註這是一個Bean對象,並取一個特有的名字,避免衝突
    @Primary //如果在IOC容器中找到相同類型的Bean對象,則優先使用這個
    @ConfigurationProperties(prefix = "spring.datasource.druid") //讀取核心配置文件application.properties中前綴是spring.datasource.druid的數據注入到當前Bean中
    //注意配置文件中的key值需要和DataSource中對應的set方法名稱一致,注意不是屬性值,是set方法
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    /**
     * 註冊一個Servlet ,把Druid提供的監控Servlet註冊進去,並提供一個訪問路徑,用戶名和密碼
     * 當前自定義Servlet的註冊方式一致,你也可以在web.xml中配置,只是SpringBoot項目中不建議這麼做
     * @return
     */
    @Bean
    public ServletRegistrationBean druidStatViewServlet(){
        //監控界面Servlet的訪問設置,訪問路勁爲根目錄下的/druid/**,Druid數據源提供了一套顯示頁面,StatViewServlet,只需要注入即可,
        ServletRegistrationBean servletRegistration = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
        //添加Servlet的初始值,訪問這個監控界面的用戶名和密碼,如果不配置,則默認不需要密碼,不顯示登錄界面
        servletRegistration.addInitParameter(ResourceServlet.PARAM_NAME_USERNAME,"admin");
        servletRegistration.addInitParameter(ResourceServlet.PARAM_NAME_PASSWORD,"admin");
        return servletRegistration;
    }

    /**
     * 過濾器註冊,需要配置Druid監控器需要監控的請求和操作
     * 配置一下過濾規則,讓靜態資源和它自己的視圖界面不攔截
     * @return
     */
    @Bean
    public FilterRegistrationBean druidStatFilter(){
        //那些信息要監控,需要定義該過濾器來進行攔截,Druid是數據源,當然只攔截請求操作了,靜態資源需要放行
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean(new WebStatFilter());
        //過濾器攔截路徑
        filterRegistration.addUrlPatterns("/*");
        //不攔截的請求
        filterRegistration.addInitParameter(WebStatFilter.PARAM_NAME_EXCLUSIONS,"*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistration;
    }

    /**
     * MyBatisPlus分頁插件啓用,比較簡單,只需要實例化即可
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }

}

SpringBoot核心配置文件

添加一些Druid數據源冬天配置新和MyBatisPlus的相關配置信息,這些動態信息可以直接在SpringBoot的核心配置文件中application.properites中配置,在java文件中通過@Value(key)註解注入單個值,或 @ConfigurationProperties(prefix = “spring.datasource.druid”)注入多個值。

#durid 數據源配置 特別注意 常規的4個字符串連接的名字,必須符合DruidDataSource的命名規則,注意是set方法,不是字段名稱,比如url
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#####自定義的配置信息,即SpringBoot中沒有提供的配置,是我們自己額外提供的動態配置信息###########
spring.datasource.druid.username=root
spring.datasource.druid.password=root
spring.datasource.druid.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8
# 初始化大小,最小,最大
spring.datasource.druid.initialSize=5
spring.datasource.druid.minIdle=2
spring.datasource.druid.maxActive=20
# 配置獲取連接等待超時的時間
spring.datasource.druid.maxWait=60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.dbType=mysql
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
spring.datasource.druid.filters=stat,wall
# 通過connectProperties屬性來打開mergeSql功能(參數不同的sql合併統計)、慢SQL記錄(執行時間長的sql)
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

#############################SpringBoot內置的配置信息,它會自動讀取注入
#mybatisplus配置
mybatis-plus.mapper-locations=mapper/*.xml
mybatis-plus.configuration.use-column-label=true
mybatis-plus.configuration.auto-mapping-behavior=full
mybatis-plus.configuration.map-underscore-to-camel-case=true
#配置SpringBoot默認的日誌環境,開啓打印SQL語句的Debug模式,語法:logging.level.<mapper所在包名>=debug
logging.level.com.cdh.springboot.mapper=debug

#Tomcat端口號 默認是8080端口
server.port=80

整合測試

到此整合基本完畢,我們通過啓動項目訪問數據源的監控頁面,確認我們配置的監控沒有問題,還可以通過數據源配置信息的顯示來查看我們注入的druid是否成功。即核心配置文件中的信息生效沒有。

  • 運行SpringBoot啓動類查看打印信息
  • 訪問localhost/druid/ 來打開監控界面,並查看數據源配置信息
    在這裏插入圖片描述
    在瀏覽器上輸入localhost/durid/進入數據源監控界面,因爲我們配置了登錄名和密碼,所以會被攔截器,需要登錄才能正常方法,如果沒有配置則可以直接進入,不用登錄。
    在這裏插入圖片描述
    輸入正確的用戶名和密碼,我們就可以進入到監控界面如下,可以查看到我們在覈心配置文件中配置的信息生效了。到此整合就完畢了。
    在這裏插入圖片描述

MyBatisPlus 封裝的CRUD接口

MyBatisPlus爲我們封裝了一套CRUD接口,提供了我們常用的方法及實現,不僅僅是Mapper接口,還有Service接口和實現以及封裝了一個條件構造器,爲我們的簡單條件提供了便利,具體情況可以通過官網去了解,在這裏我就簡單羅列一下:

Mapper CRUD 接口

說明:
通用 CRUD 封裝BaseMapper接口,爲 Mybatis-Plus 啓動時自動解析實體表關係映射轉換爲 Mybatis 內部對象注>入容器
泛型 T 爲任意實體對象
參數 Serializable 爲任意類型主鍵 Mybatis-Plus 不推薦使用複合主鍵約定每一張表都有自己的唯一 id 主鍵
對象 Wrapper 爲 條件構造器

Mapper接口 Mapper接口 Mapper接口 Mapper接口 Mapper接口 Mapper接口 Mapper接口 Mapper接口 Mapper接口
insert deleteById deleteByMap delete deleteBatchIds updateById update selectById selectBatchIds
selectByMap selectOne selectCount selectList selectMaps selectObjs selectPage selectMapsPage

Service CRUD 接口

說明:
通用 Service CRUD 封裝IService接口,進一步封裝 CRUD 採用 get 查詢單行 remove 刪除 list
查詢集合 page 分頁 前綴命名方式區分 Mapper 層避免混淆, 泛型 T 爲任意實體對象 建議如果存在自定義通用 Service
方法的可能,請創建自己的 IBaseService 繼承 Mybatis-Plus 提供的基類 對象 Wrapper 爲 條件構造器

Service接口 Service接口 Service接口 Service接口 Service接口 Service接口 Service接口 Service接口 Service接口
save saveBatch saveOrUpdateBatch removeById removeByMap remove removeByIds updateById update
updateBatchById saveOrUpdate getById listByIds listByMap getOne getMap getObj count
list page listMaps listObjs pageMaps

條件構造器

QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父類
用於生成 sql 的 where 條件, entity 屬性也用於生成 sql 的 where 條件,特別注意的是,因爲是直接應用於SQL,所以它們其中使用的參數key都是數據庫中表的字段名稱,和實體屬性沒有關係。

條件方法 條件方法 條件方法 條件方法 條件方法 條件方法 條件方法 條件方法 條件方法
allEq eq ne gt ge lt le between notBetween
like notLike likeLeft likeRight isNull isNotNull in notIn inSql
notInSql groupBy orderByAsc orderByDesc orderBy having or and last
  • QueryWrapper對象特有的方法:select
  • UpdateWrapper對象特有的方法:set , setSql

使用SpringBootTest熟悉一下MyBatisPlus提供的CRUD接口

SpringBootTest的使用方式和之前的spring-test+junit的模式差不多,只需要在測試類上配置如下幾個點:

  • 在類上添加第一個註解: @RunWith(SpringRunner.class)
  • 在類上添加第二個註解: @SpringBootTest
  • 在需要測試的方法上添加@Test即可,需要注意的是,測試的方法必須是public void 開頭的
  • 如果需要注入對象在類中直接使用@Autowired 或 @Resource 放在對應的字段上即可
package com.cdh.springboot;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RbacDemoApplicationTests {
	
	@Resource
	private IDeptService deptService; //這裏可以使用接口類型接收(多態),也可以使用實現類接收。
	
	@Test
	public void contextLoads() {
	}

}

接下來我們測試幾個常見的方法

  • 添加一個 ,代碼如下:
	@Test
  	public void saveDeptTest() {
  		//給Dept實體類添加lombok的@Builder註解,就可以使用如下的方式構建對象
  		Dept dept = Dept.builder().deptName("科技部").loc("廣州").parentId(0).level("0").phone("12345678901")
  		                   .orderNum(1).status(0).delFlg(0).createTime(LocalDateTime.now()).build();
  		boolean flg = deptService.save(dept);
  		System.out.println(flg);
  	}

執行結果,以及SQL打印信息:
在這裏插入圖片描述

  • 添加多個,代碼如下
	@Test
	public void saveBatchDeptTest(){
		Dept dept1 = Dept.builder().deptName("科技部1").loc("廣州1").parentId(1).level("0").phone("12345678901").orderNum(1).status(0).delFlg(0).createTime(LocalDateTime.now()).build();
		Dept dept2 = Dept.builder().deptName("科技部2").loc("廣州2").parentId(1).level("0.1").phone("12345677701").orderNum(2).status(0).delFlg(0).createTime(LocalDateTime.now()).build();
		Dept dept3 = Dept.builder().deptName("科技部3").loc("廣州3").parentId(1).level("0.2").phone("12345668901").orderNum(3).status(0).delFlg(0).createTime(LocalDateTime.now()).build();

		List<Dept> list = Arrays.asList(dept1,dept2,dept3);
		boolean flg = deptService.saveBatch(list);
		//這裏使用日誌打印,在要打印的類上添加lombok註解@Slf4j,它會提供一個log對象,我們就可以使用它打印統一樣式的日誌信息了
		log.info("添加多個部門,結果爲:{}",flg);
	}

執行結果爲:
在這裏插入圖片描述- 根據id刪除,代碼如下:

	@Test
	public void delByIdTest(){
		boolean flg = deptService.removeById(1);
		log.info("刪除1號部門,執行結果爲:{}",flg);
	}

執行結果爲:
在這裏插入圖片描述

  • 根據ids刪除,代碼如下:
	@Test
	public void delByIdsTest(){
		List<Integer> ids = Arrays.asList(4, 5);
		boolean flg = deptService.removeByIds(ids);
		log.info("刪除多個部門,執行結果爲:{}",flg);
	}

執行結果爲:
在這裏插入圖片描述

  • 根據id修改,代碼如下:
	@Test
	public void updateById(){
		Dept dept = Dept.builder().deptId(1).deptName("市場部").build();
		boolean flg = deptService.updateById(dept);
		log.info("修改部門,執行結果爲:{}",flg);
	}
  • 查詢所有,代碼如下:
	@Test
	public void findAll(){
		//查詢所有,注意需要添加有參構造和無參構造 @AllArgsConstructor , @NoArgsConstructor
		List<Dept> list = deptService.list();
		list.forEach(System.out::println);
	}
  • 根據id查詢,代碼如下:
@Test
	public void findById(){
		Dept dept = deptService.getById(7);
		log.info(dept.toString());
	}
  • 根據自定義條件查詢,代碼如下:
	@Test
	public void findByInfo(){
		//自定義查詢條件,這個時候就需要使用MyBatisPlus提供的條件構造器QueryWrapper<T>了
		//條件如下loc like '廣州%' and status = 0 and create_time between '2018-11-28 02:02:20' and '2018-11-28 02:05:15' order by dept_id
		Map<String,Object> where = new HashMap<>();
		where.put("status",0);
		QueryWrapper<Dept> queryWrapper = new QueryWrapper<>();
		queryWrapper.likeRight("loc","廣州")
					.eq("status",0)
					.between("create_time","2018-11-28 02:02:20","2018-11-28 02:05:15")
					.orderByAsc("dept_id");
		List<Dept> list = deptService.list(queryWrapper);
		list.forEach(dept -> log.info(dept.toString()));
	}

執行結果,以及打印的SQL語句如下:
在這裏插入圖片描述

  • 分頁查詢,代碼如下:
	@Test
	public void page(){
		Page<Dept> page = new Page<>(1,3); //current:頁碼 , size:每頁顯示的條數
		QueryWrapper<Dept> queryWrapper = new QueryWrapper<>();
		queryWrapper.like("loc","廣州").orderByAsc("dept_id");
		IPage<Dept> pageInfo = deptService.page(page, queryWrapper);
		log.info("總條數:{}",pageInfo.getTotal());
		log.info("顯示數據:{}:",pageInfo.getRecords());
		log.info("頁碼:{}",page.getCurrent());
		log.info("每頁顯示的條數:{}",page.getSize());
	}

執行結果,以及打印的SQL語句如下:
在這裏插入圖片描述

  • 多表帶條件分頁查詢 ,多表查詢需要自定義SQL,也就是需要在Mapper映射文件中添加自己的需求,這個時候需要自定映射實體,也就是經常所見的VO。我們先添加一個員工,外鍵關聯部門表,然後查詢該員工對應的部門信息。
    添加多個員工:
@Autowired
private IUserService userService;

@Test
public void saveBatch(){
	//給User實體添加三個註解@NoArgsConstructor	@AllArgsConstructor	@Builder
	User user1 = User.builder().userName("admin").userPwd("admin").createTime(LocalDateTime.now()).deptId(7).build();
	User user2 = User.builder().userName("zhangsan").userPwd("zhangsan").createTime(LocalDateTime.now()).deptId(8).build();
	User user3 = User.builder().userName("lis").userPwd("lisi").createTime(LocalDateTime.now()).deptId(9).build();
	List<User> users = Arrays.asList(user1,user2,user3);
	boolean flg = userService.saveBatch(users);
	log.info("添加多個員工,執行結果爲:{}",flg);
}

在entity包中添加一個vo包,在該包下添加一個UserDeptVO類,定義要獲取的信息字段。如下:

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class UserDeptVO {
    private Integer userId;
    private String userName;

    private Integer deptId;
    private String deptName;
    private String loc;
    private String level;
    private LocalDateTime createTime;
}

在UserMapper中添加兩個接口,一個查詢員工詳細信息,一個帶條件的分頁查詢。

package com.cdh.springboot.mapper;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cdh.springboot.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cdh.springboot.entity.vo.UserDeptVO;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * <p>
 * 用戶表 Mapper 接口
 * </p>
 *
 * @author CDHong
 * @since 2018-11-27
 */
public interface UserMapper extends BaseMapper<User> {

    UserDeptVO findByUserId(Integer userId);

    /**
     * 分頁查詢一定要添加IPage作爲參數,傳入頁碼和每頁顯示的條數
     * @param page
     * @param vo 查詢的條件
     * @return
     */
    List<UserDeptVO> userPage(IPage<UserDeptVO> page,@Param("vo") UserDeptVO vo);
}

Mapper.xml中SQL映射信息如下;

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

    <select id="findByUserId" resultType="com.cdh.springboot.entity.vo.UserDeptVO">
        SELECT user_id,user_name,d.dept_id,dept_name,loc,level,u.create_time user_create_time
        FROM sys_user u
        JOIN sys_dept d ON u.dept_id = d.dept_id WHERE user_id = #{userId}
    </select>

    <select id="userPage" resultType="com.cdh.springboot.entity.vo.UserDeptVO">
        SELECT user_id,user_name,d.dept_id,dept_name,loc,level,u.create_time user_create_time
        FROM sys_user u
        JOIN sys_dept d ON u.dept_id = d.dept_id
        <where>
            <if test="vo!=null">
                <if test="vo.userId!=null"> AND user_id = #{vo.userId}</if>
                <if test="vo.userName!=null"> AND user_name like '%${vo.userName}%' </if>
                <if test="vo.deptId!=null"> AND d.dept_id = #{vo.deptId} </if>
                <if test="vo.deptName!=null"> AND dept_name like '${vo.deptName}' </if>
                <if test="vo.loc!=null"> AND d.loc like '${vo.loc}' </if>
            </if>
        </where>
    </select>

</mapper>

IUserService接口中添加一個查詢方法,基本上和UserMapper接口中的方法一致,你可以直接粘貼複製過去,這裏就不在提供代碼了,接着在UserServiceImp實現類中完成接口的實現,這裏需要注入UserMapper的實例,注意這裏想要獲取到UserMapper實例,需要在SpringBoot的啓動類中添加註解掃描Mapper接口(@MapperScan(basePackages = "com.cdh.springboot.mapper")),否則會報找不到對應的方法`

package com.cdh.springboot.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cdh.springboot.entity.User;
import com.cdh.springboot.entity.vo.UserDeptVO;
import com.cdh.springboot.mapper.UserMapper;
import com.cdh.springboot.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用戶表 服務實現類
 * </p>
 *
 * @author CDHong
 * @since 2018-11-27
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public UserDeptVO findById(Integer userId) {
        return userMapper.findByUserId(userId);
    }

    @Override
    public List<UserDeptVO> userPage(IPage<UserDeptVO> page, UserDeptVO vo) {
        return userMapper.userPage(page,vo);
    }

}

Junit測試,根據ID獲取對應的多表數據

@Test
public void findUserAndDeptByUserId(){
	UserDeptVO userDeptVO = userService.findById(2);
	log.info(userDeptVO.toString());
}

@Test
public void userPage(){
	UserDeptVO vo = new UserDeptVO();
	vo.setDeptName("科技部");
	vo.setUserName("m");
	Page<UserDeptVO> page = new Page<>(1,3);
	List<UserDeptVO> list = userService.userPage(page, vo);
	log.info("總條數:{}",page.getTotal());
	list.forEach(userDeptVO -> log.info(userDeptVO.toString()));
}

測試結果以及打印SQL語句如下:
在這裏插入圖片描述

接下來是把我們寫好的方法通過Thymeleaf模板進行數據展示。

Thymeleaf模板引擎簡單入門

SpringBoot建議使用模板引擎Thymeleaf替代JSP的操作,Thymeleaf是面向Web和獨立環境的現代服務器端Java模板引擎,能夠處理HTML,XML,JavaScript,CSS甚至純文本。
Thymeleaf的主要目標是提供一個優雅和高度可維護的創建模板的方式。爲了實現這一點,它建立在自然模板的概念上,將其邏輯注入到模板文件中,不會影響模板被用作設計原型。這改善了設計的溝通,彌合了設計和開發團隊之間的差距。也就是可以動態更改路徑和數據。

注意在SpringBoot中Thymeleaf是開箱即用的,不需要做什麼配置,當然如果你有其他需求,也可以更改默認配置,直接在SpringBoot的核心配置文件中寫入對應的鍵值對即可。

  • SpringBoot中 Thymeleaf常見配置項:
# Thymeleaf模板引擎配置
#開啓模板緩存(默認值:true)  
spring.thymeleaf.cache=true
#在呈現模板之前檢查模板是否存在
spring.thymeleaf.check-template=true
#檢查模板位置是否正確(默認值:true)  
spring.thymeleaf.check-template-location=true
#Content-Type的值(默認值:text/html)  
spring.thymeleaf.servlet.content-type=text/html
#開啓MVC Thymeleaf視圖解析(默認值:true)  
spring.thymeleaf.enabled=true
#模板編碼  
spring.thymeleaf.encoding=UTF-8
#要被排除在解析之外的視圖名稱列表,用逗號分隔  
spring.thymeleaf.excluded-view-names=
#在構建URL時添加到視圖名稱前的前綴(默認值:classpath:/templates/)  
spring.thymeleaf.prefix=classpath:/templates/
#在構建URL時添加到視圖名稱後的後綴(默認值:.html)  
spring.thymeleaf.suffix=.html
#可解析的視圖名稱列表,用逗號分隔  
spring.thymeleaf.view-names=
  • Thymeleaf使用詳情:
    Thymeleaf模板引擎使用其實功能和JSP差不多,都是在靜態頁面對後臺作用域中的數據進行相應的處理,替換默認值,邏輯判斷,循環遍歷,簡單表達式計算,路徑映射…

    1. 語法

    • 引入靜態資源文件: 在html中,資源文件路徑是一個頭痛的問題,而模板引擎中,只需要使用@{/url}包裹路徑,則代表該路徑從項目根目錄開始。之前的路徑不用刪除,它會自動替換。th:href="@{/css/public.css}"
    • 訪問後端作用域數據: 訪問model中的數據,和EL表達式一致, th:attr="class= ${btn.code}" , 訪問session中的數據需要加上作用域,th:text="${session.currUser.relName}"
    • 條件判斷:在html標籤中,加入th:if = 表達式,可以根據條件顯示html元素
    • 循環迭代:在要循環的標籤上添加 th:each 或者單獨構建一個塊標籤:th:block:
    <th:block th:each="job : ${jobs}">
         <option th:text="${job.name}" th:value="${job.id}"></option>
    </th:block>

2. 表達式

  • 簡單表達式 :  ${…} 變量表達式 , *{…} 選擇變量表達式 , @{…} 鏈接url表達式

  • 字面量表達式: 文本 ->‘one text’ , 數值->5 ,布爾->true ,空值->null ,

  • 操作符: 算術運算符,布爾運算符(and,or,!,not),關係運算符(gt , lt , ge , le , eq , ne ,>= , <= , == ),三目運算

  • 表達式工具對象
    1. #dates 與java.util.Date對象的方法對應,格式化、日期組件抽取等等
    2. #calendars 類似#dates,與java.util.Calendar對象對應
    3. #numbers 格式化數字對象的工具方法
    4. #strings 與java.lang.String對應的工具方法:contains、startsWith、prepending/appending等等
    5. #objects 用於對象的工具方法
    6. #bools 用於布爾運算的工具方法
    7. #arrays 用於數組的工具方法
    8. #lists 用於列表的工具方法
    9. #sets 用於set的工具方法
    10. #maps 用於map的工具方法
    11. #aggregates 用於創建數組或集合的聚合的工具方法
    12. #messages 用於在變量表達式內部獲取外化消息的工具方法,與#{…}語法獲取的方式相同
    13. #ids 用於處理可能重複出現(例如,作爲遍歷的結果)的id屬性的工具方法

  • 頁面操作:

    內嵌標記 內嵌標記 內嵌標記 內嵌標記 內嵌標記 內嵌標記 內嵌標記 內嵌標記
    th:action th:align th:alt-title th:autocomplete th:cellpadding th:cellspacing th:class th:attr
    :async th:autofocus th:autoplay th:checked th:disabled th:hidden th:readonly th:required
    :selected th:each th:classappend th:styleappend th:attrappend

    以上寫法可能比較怪異,可以使用HTML5友好的屬性及元素名來完成操作,<tr data-th-each="user : ${users}"> 或 <td data-th-text="${user.login}">...</td>

  • 條件運算: th.switch 、 th:if 不只運算布爾條件,它對以下情況也運算爲true:

  1. 值不爲null , 爲boolean且爲true ,值爲數字且非0 , 值爲字符且非0 , 值是字符串且不是:“false”,“off”,“no” , 值不是boolean、數字、字符、字符串 , 如果值爲null,則th:if運算結果爲false
  2. th:if的反面是th:unless
  <div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
 </div>
  • 循環遍歷: th:each 除了獲取對象外,還可以得到獲取一些遍歷狀態,通過指定狀態變量iterStat獲取:擁有的屬性(index ,count,size,current,even,odd,first,last),若不指定狀態變量,Thymeleaf會默認生成一個名爲“變量名Stat”的狀態變量:
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
 </tr>
 <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
   <td th:text="${prod.name}">Onions</td>
   <td th:text="${prod.price}">2.41</td>
   <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
 </tr>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章