0%

Spring Review

這篇文章是觀看Bilibili上面黑馬程序員SSM相關視頻時,總結的筆記

Spring

Spring 概述

  1. Spring是分層的Java SE/EE應用的full-stack輕量級開源框架,以IOC(Inverse of control控制反轉)和AOP(Aspect oriented programming面向切片編程)為内核,提供了展現層Spring MVC和持久層Spring JDBC以及業務層事務管理等多種技術,是使用最多的Java EE企業應用框架

  2. Spring的優勢

    • 方便解耦,簡化開發
    • AOP編程的支持
    • 聲明式事務的支持
    • 方便程序的測試
    • 方便集成各種優秀框架
    • 降低JavaEE API的使用難度
  3. Spring的體系結構

    • Core Container提供了IOC的支持,其他功能必需核心容器的支持
    • AOP, Aspects, Instrumentation, Messaging和AOP有關
    • Data/Access層提供了持久層的支持
    • Web層提供了MVC的支持
    • Testing層提供了測試的支持

IOC的概念和應用

  1. 耦合和解耦
    • 耦合:程序之間存在依賴關係,可能是類之間的依賴,也可能是方法之間的依賴
    • 解耦:降低程序之間的依賴關係,應該做到編譯期不依賴,運行時才依賴
    • 解耦的思路:
      • 使用反射創建對象,而不使用new來創建對象
      • 對於反射傳入的字符串,不應該寫成固定的字符串,而是應該通過讀取配置文件來獲得
    • 表現層,業務層和持久層之間都應該解耦
  2. Bean
    • Bean: 在計算機語言中,指的是可重用組件
    • JavaBean: 用Java語言實現的可重用組件
    • Bean factory:創建Bean對象的工廠,負責創建service和dao對象。從配置文件讀取配置的内容,再通過反射創建對象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      public class BeanFactory {
      private static Properties props;
      private static Map<String, Object> beans;

      // 讀取配置文件, 并且通過反射獲得單例對象(效率更高),類加載的時候執行一次即可
      static {
      try {
      props = new Properties();
      // 這個bean.properties在resources目錄下面
      InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
      props.load(in);
      beans = new HashMap<String, Object>();
      Enumeration keys = props.keys();
      while (keys.hasMoreElements()) {
      String key = keys.nextElement().toString();
      String beanPath = props.getProperty(key);
      Object value = Class.forName(beanPath).newInstance();
      beans.put(key, value);
      }
      } catch (Exception e) {
      throw new ExceptionInitializerError("初始化失敗");
      }
      }

      // 讀取字符串,獲得對象
      public static Object getBean(String beanName) {
      return beans.get(beanName);
      }
      }
  3. IOC
    • 控制反轉把創建對象的權力從應用交給框架,是框架的重要特徵,包括依賴注入和以來查找。原先是由應用直接創建對象,耦合度高,現在是應用通過工廠的方法來獲得對象,應用把控制權交給了工廠,耦合度較低。
    • 作用:解耦

Spring的IOC解決程序耦合

  1. 使用XML配置IOC替代Bean factory的手動配置
    • 在resources目錄下面新建一個bean.xml文件,寫入以下内容
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <?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
      https://www.springframework.org/schema/beans/spring-beans.xsd">

      <bean id="獲取對象時的字符串1" class="反射時的全限定類名1"></bean>
      <bean id="獲取對象時的字符串2" class="反射時的全限定類名2"></bean>
      </beans>
    • 在應用中獲取IOC的核心容器,并根據id或許對象
      1
      2
      3
      4
      5
      6
      7
      public static void main(String[] args) {
      // 獲取核心容器
      ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
      // 根據id獲取對象,兩種方式都可以
      MyClass1 var1 = (MyClass) ctx.getBean("獲取對象時的字符串1");
      MyClass2 var2 = ctx.getBean("獲取對象時的字符串2", MyClass2.class);
      }
      • ApplicationContext是一個接口,他間接結成了Spring核心容器的頂層接口BeanFactory
        • 實現類
          • ClassPathXmlApplicationContext: 加載了類路徑下的配置文件,其他位置的配置文件加載不了
          • FileSystemXmlApplicationContext: 可以加載任意路徑下的配置文件,需要相應路徑有訪問權限
          • AnnotationConfigApplicationContext: 用於讀取注解創建容器
        • ApplicationContext只要讀取了配置文件,就創建了對應的Bean對象,并且將Bean對象封裝到容器裏面,適用於單例對象
        • BeanFactory讀取完配置文件之後,采取了延遲加載的方式,也就是只有在通過getBean()方法獲取對象的時候才會真正的創建對象,適用於多例對象
          1
          2
          3
          Resource resource = new ClassPathResource("bean.xml");
          BeanFactory ctx = new XmlBeanFactory(resource);
          MyClass1 var1 = (MyClass1) ctx.getBean("獲取對象時的字符串1");
        • ApplicationContext的各個類都有close()方法,用來銷毀容器,但是ApplicationContext自己沒有,所有不能用多態寫法
  2. Spring對Bean的管理細節
    • 創建Bean的三種方式
      • 通過默認構造函數創建: 在xml中使用<bean>標簽,有idclass屬性,那麽會使用類的默認構造函數創建對象
      • 使用類中的方法創建對象,並存入Spring容器: <bean id="獲取對象的字符串" factory-bean="工廠類類名" factory-method="工廠類方法名"></bean>
      • 使用類中的靜態方法創建對象,並存入Spring容器:<bean id="獲取對象的字符串" class="反射的全限定類名" factory-method="獲取對象的方法"></bean>
    • Bean對象的作用範圍
      • 使用<bean>標簽的scope屬性來指定Bean的作用範圍
      • 取值: singleton單例,prototype多例,requestweb應用的請求範圍,sessionweb應用的會話範圍,global-session集群環境的會話範圍,非集群的時候和session範圍一樣
    • Bean對象的生命周期
      • <bean init-method="m1" destroy-method="m2">中的init-method和destroy-method屬性制定了Bean類中對應的方法用來初始化和銷毀Bean對象
      • 單例對象:容器創建則Bean對象創建,容器銷毀則Bean對象銷毀
      • 多利對象: 使用Bean對象的時候創建Bean對象,容器銷毀并不會銷毀Bean對象,Bean對象滿足GC條件的時候會GC
  3. Spring依賴注入
    • Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过 Spring 容器获得被调用者实例,这称为依赖注入
    • 依賴注入的數據類型
      • 基本類型和String
      • 其他Bean類型(配置文件中或注解配置過的Bean)
      • 複雜類型/集合類型: 結構一樣,標簽可以互換, list/array/set可以互換,map/props可以互換
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        public class MyClass1 {
        private String[] strs;
        private List<String> list;
        private Set<String> set;
        private Map<String, String> map;
        private Properties props;

        public void setStrs(String[] strs) {
        this.strs = strs;
        }

        public void setList(List<String> list) {
        this.list = list;
        }

        public void setSet(Set<String> set) {
        this.set = set;
        }

        public void setMap(Map<String, String> map) {
        this.map = map;
        }

        public void setProps(Properties props) {
        this.props = props;
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        <bean id="提取id">
        <property name="strs">
        <array>
        <value>str1</value>
        <value>str2</value>
        </array>
        </property>

        <property name="list">
        <list>
        <value>str1</value>
        <value>str2</value>
        </list>
        </property>

        <property name="set">
        <set>
        <value>str1</value>
        <value>str2</value>
        </set>
        </property>

        <property name="map">
        <map>
        <!-- 這兩種寫法都可以 -->
        <entry key="k1" value="v1"></entry>
        <entry key="k2">
        <value>
        v2
        </value>
        </entry>
        </map>
        </property>

        <property name="props">
        <props>
        <prop key="k1">
        v1
        </prop>
        <prop key="k2">
        v2
        </prop>
        </props>
        </property>
        </bean>
    • 依賴注入的方式
      • 通過構造函數提供: 使用<constructor-arg>標簽 (有type, index, name, value, ref屬性),缺點是必須在·`中提供構造函數中所有參數,否則Bean對象無法創建

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        public class MyClass1 {
        private String v1;
        private Integer v2;
        private Date v3
        public MyClass1(String v1, Integer v2, Date v3) {
        this.v1 = v1;
        this.v2 = v2;
        this.v3 = v3;
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <bean id="class"> 
        <constructor-arg name="v1" value="2"></constructor-arg>
        <!-- 這裏的3是字符串,MyClass1中式Integer,Spring會幫我們完成類型轉換 -->
        <!-- value提供基本類型和String類型的值 -->
        <constructor-arg name="v2" value="3"></constructor-arg>
        <!-- ref提供Bean類型的值 -->
        <constructor-arg name="v2" ref="now"></constructor-arg>
        </bean>

        <bean id="now" class="java.util.Date"> </bean>

      • 使用set方法提供: 使用<property>標簽 (有name, value, ref屬性),創建對象的時候可以直接使用默認構造函數,如果某個參數是required,但是set方法并不能保證該參數的set方法一定被提供

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        public class MyClass1 {
        private String v1;
        private Integer v2;
        private Date v3

        public void setV1(String v1) {
        this.v1 = v1;
        }
        public void setV2(Integer v2) {
        this.v2 = v2;
        }
        public void setV3(Date v3) {
        this.v3 = v3;
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        <bean id="提取id"> 
        <!-- value提供基本類型和String的值 -->
        <property name="v1" value="2"></property>
        <property name="v2" value="3"></property>
        <!-- ref提供Bean類型的值 -->
        <property name="v2" ref="now"></property>
        </bean>
        <bean id="now" class="java.util.Date"> </bean>

      • 使用注解提供

Spring基於注解的IOC

  1. 注解:注解和XML的功能類似,但是更加精簡
    • 用於創建對象的注解, 類似XML中的<bean>標簽
      • @Component注解,用於把當前類存入Spring容器中,有value屬性,value屬性就是bean標簽的id,value屬性可以不寫。需要在核心配置文件xml中通過組件掃描加載bean<context:component-scan base-package="包名">, Spring會掃描包名下面的所有文件找對應的注解
      • @Component的三個衍生注解: @Controller用於表現層bean定義, @Service用於業務層bean定義, @Repository用於數據層bean定義
    • 用於注入數據的注解, 類似XML中的<property>標簽
      • @Autowired: 自動按照類型注入,只要容器中有唯一的一個Bean對象和需要注入的變量類型匹配,就可以諸如成功。可以用在變量或者方法上面。如果有同一類型的多個Bean對象匹配,比較注入變量名和Bean的id,如果有相同的就注入,否則報錯。
      • @Qualifier(id): 按照類型注入的基礎上再按照名稱注入,在給類型成員注入的時候不能單獨使用,在給方法注入的時候可以單獨使用
      • @Resource(name=id): 直接按照Bean的id注入,可以獨立使用,屬性name用於指定Bean的id
      • @Value(value=value): 用於注入基本類型和String類型的數據,有value屬性,可以使用String中的SpEL表達式,即${表達式}
    • 用於改變作用範圍的注解, 類似XML中的<bean>標簽的`scope
    • @Scope(value=value): 用於指定Bean的作用方位,value屬性常常為singleton, prototype等
    • 和生命周期有關的注解, 類似XML中的<bean>標簽的init-methoddestroy-method屬性
      • @PreDestroy: 用於指定銷毀方法
      • @PostConstruct: 用於指定初始化方法
  2. 一個基於XML的IOC例子
    • resources目錄中bean.xml配置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      <!-- 文件格式看Spring Framework官網 -->
      <?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
      https://www.springframework.org/schema/beans/spring-beans.xsd">
      <!-- 配置業務層對象-->
      <bean id="accountService" class="com.yanxuanshaozhu.service.impl.AccountServiceImpl">
      <!-- 注入dao-->
      <property name="accountDao" ref="accountDao"></property>
      </bean>
      <!--配置Dao對象-->
      <bean id="accountDao" class="com.yanxuanshaozhu.dao.impl.AccountDao">
      <!-- 注入QueryRunner-->
      <property name="runner" ref="runner"></property>
      </bean>
      <!-- 配置QueryRunner-->
      <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
      <!-- 注入數據源-->
      <constructor-arg name="ds" ref="dataSource"></constructor-arg>
      </bean>

      <!-- 配置數據源-->
      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
      <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/xmlioc"></property>
      <property name="user" value="root"></property>
      <property name="password" value="123456"></property>
      </bean>
      </beans>
    • java目錄中文件結構
      • dao目錄: 有 IAccountDao接口和 impl目錄中AccountDao實現組件,這個實現組件中使用了QueryRunner屬性
      • domain目錄: 有 Account實體類
      • service: 有 IAccountService接口和 impl目錄中AccountService實現組件
    • test目錄中文件結構: 使用Junit來進行測試
  3. 一個基於注解的IOC的例子
    • resources目錄中bean.xml配置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      <!-- 文件格式看Spring Framework官網 -->
      <?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
      https://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.yanxuanshaozhu"></context:component-scan>
      <!-- 配置QueryRunner-->
      <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
      <!-- 注入數據源-->
      <constructor-arg name="ds" ref="dataSource"></constructor-arg>
      </bean>

      <!-- 配置數據源-->
      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
      <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/xmlioc"></property>
      <property name="user" value="root"></property>
      <property name="password" value="123456"></property>
      </bean>
      </beans>
    • java目錄中文件結構
      • dao目錄: 有 IAccountDao接口和 impl目錄中AccountDao實現組件,這個實現組件中使用了QueryRunner屬性, 需要配置@Repository注解,對QueryRunner屬性需要配置@Autowired注解
      • domain目錄: 有 Account實體類
      • service: 有 IAccountService接口和 impl目錄中AccountService實現組件,需要配置@Service注解,對AccountDao屬性需要配置@Autowired注解
    • test目錄中文件結構: 使用Junit來進行測試
  4. Spring中的新注解和解釋
    • @Configuration: 使用一個Java配置類來完成bean.xml的功能, 如果AnnotationConfigApplicationContext中間傳入了這個Java類(這個方法接受多個類參數),則對應的類上面的@Configuration注解可以不寫
    • @ComponentScan: 寫在配置類裏面,使用注解指定需要掃描的包,其中basePackage屬性或者value屬性指定了需要掃描的包的名字
    • @Bean: 寫在配置類的對應方法前,用於把當前方法的返回值作爲Bean對象存入Spring的IOC容器中,name屬性用於指定Bean的id,不寫的話,id默認值是當前方法的名稱
      1
      2
      3
      4
      5
      // 不加Bean注解的話,只是創建了AccountDao對象,但是沒有存入Spring的IOC容器中
      @Bean(name="accountDao")
      public AccountDao accountDao(){
      return new AccountDao();
      }
      1
      <bean id="accountDao" class="com.yanxuanshaozhu.dao.AccountDao"></bean>
    • 把XML去掉之後,在測試的時候還需要修正ApplicationContext的具體實現類,從ClassPathXmlApplicationContext(bean.xml)改成AnnotationConfigApplicationContext(SpringConfiguration.class)
    • 為了更好的多綫程使用,QueryRunner應該不應該是單例對象,爲此在對應的@Bean注解下面加上@Scope("prototype")注解
    • @Import(類名.class)注解: 用於導入其他配置類,可以用一個類當作配置類入口,讓它導入其他配置類,這樣其他配置類可以省略@Configuration注解
    • @PropertySource@Value(${variable})
      • @PropertySource(classpath:路徑到文件名): 寫在配置的Java類上,用於指定properties文件的位置
      • @Value(${variable}): 讀取properties文件中的key, 拿到value注入到屬性變量中
        1
        2
        3
        4
        5
        <!-- File jdbc.properties -->
        driverClass=com.mysql.jdbc.Driver
        url=jdbc:mysql://localhost:3306/xmlioc
        user=root
        password=123456
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        // file SpringConfiguration.java
        @Configuration
        @ComponentScan("com.yanxuanshaozhu")
        @Import(JDBCConfiguration.class)
        @PropertySource("classpath:jdbc.properties")
        public class SpringConfiguration {

        @Bean(name = "runner")
        @Scope("prototype")
        public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        // file JDBCConfiguration.java
        public class JDBCConfiguration {

        @Value("${driverClass}")
        private String driverClass;
        @Value("${url}")
        private String url;
        @Value("${user}")
        private String user;
        @Value("${password}")
        private String password;

        @Bean(name = "dataSource")
        public DataSource createDataSource() {
        try {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        ds.setDriverClass(driverClass);
        ds.setJdbcUrl(url);
        ds.setUser(user);
        ds.setPassword(password);
        return ds;
        } catch (Exception e) {
        throw new RuntimeException();
        }
        }
        }
  5. Spring整合JUnit
    • 一個Test案例
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      public class AccountServiceTest {
      private ApplicationContext ctx;
      private IAccountService as;

      // 對於測試工程師來説,他應該只寫test方法,不應該需要懂Spring然後寫出來這個init方法
      @Before
      public void init() {
      ctx = new AnnotationConfigApplicationContext(SpringConfiguration.class);
      as = ctx.getBean("accountService", IAccountService.class);
      }

      @Test
      public void testFindAll() {
      List<Account> allAccount = as.findAllAccount();
      for (Account ac : allAccount) {
      System.out.println(ac);
      }
      }
      }
    • JUnit不知道測試的時候使用了Spring框架,也就不會創建Spring的IOC核心容器,那麽即使使用了Autowired注解也不會自動注入
    • Spring整合JUnit
      • 導入整合Spring框架的JUnit的maven坐標,注意spring-test需要junit版本至少4.12
        1
        2
        3
        4
        5
        6
        <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.22</version>
        <scope>test</scope>
        </dependency>
      • 使用JUnit的一個注解替換JUnit的main方法為Spring提供的main方法: @RunWith(SpringJUnit4ClassRunner.class)
      • 告知Spring運行器Spring的IOC創建是基於XML還是注解的,并且説明配置的位置: 用XML創建 ContextConfiguration(locations = "classpath:bean.xml")或者用注解創建@ContextConfiguration(classes = {SpringConfiguration.class})
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(classes = SpringConfiguration.class)
        public class AccountServiceTest {

        @Autowired
        private IAccountService as;

        @Test
        public void testFindAll() {
        List<Account> allAccount = as.findAllAccount();
        for (Account ac : allAccount) {
        System.out.println(ac);
        }
        }
        }
      • 注意Spring 5.x.x版本需要JUnit 4.12及以上版本才能使用

事務和動態代理

  1. 事務控制
    • 同一組操作應該使用一個Connection,從而防止部分成功部分失敗導致的數據庫錯誤,這個可以使用ThreadLocal綁定Connection來完成
    • 事務控制應該都在業務層完成,下面是兩個工具類的例子
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      /*鏈接的工具類,獲取連接并且和綫程綁定*/
      public class ConnectionUtils {

      private ThreadLocal<Connection> th = new ThreadLocal<>();

      private DataSource ds;

      public void setDs(DataSource ds) {
      this.ds = ds;
      }

      /*獲取當前綫程上的連接*/
      public Connection getConnection() {
      // 先從ThreadLocal上獲取
      Connection conn = th.get();
      // 判斷當前綫程是否有連接
      try {
      if (conn == null) {
      // 從數據源獲取連接,并且存入ThreadLocal中
      conn = ds.getConnection();
      th.set(conn);
      }
      // 返回當前綫程上的連接
      return conn;
      } catch (Exception e) {
      e.printStackTrace();
      }

      return conn;
      }

      /*綫程和連接解綁*/
      public void removeConnection() {
      th.remove();
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      /*和事務管理有關的工具類,包含開啓事務,提交事務,回滾事務,關閉事務*/
      public class TransactionManager {

      private ConnectionUtils connectionUtils;

      public void setConnectionUtils(ConnectionUtils connectionUtils) {
      this.connectionUtils = connectionUtils;
      }

      /*開啓事務*/
      public void beginTransaction() {
      try {
      connectionUtils.getConnection().setAutoCommit(false);
      } catch (Exception e) {
      e.printStackTrace();
      }
      }

      /*提交事務*/
      public void commitTransaction() {
      try {
      connectionUtils.getConnection().commit();
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }

      /*回滾事務*/
      public void rollbackTransaction() {
      try {
      connectionUtils.getConnection().rollback();
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }

      /*關閉事務*/
      public void closeTransaction() {
      try {
      connectionUtils.getConnection().close();
      connectionUtils.removeConnection();
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }
      }
  2. 動態代理
    • 特點:字節碼隨用隨創建,隨用隨加載,不修改源碼的基礎上對方法進行增强
    • 分類:基於接口的動態代理,基於子類的動態代理
    • 基於接口的動態代理
      • 被代理類最少實現一個接口,否則不能使用
      • Proxy
      • 使用newProxyInstance(ClassLoader loader, Class[] cls, InvocationHandler handler)方法創建對象
        • loader: 被代理對象的類加載器, 就是delegatedClass.getClass().getClassLoader()
        • cls: 讓代理對象和被代理對象有相同的方法, 就是delegatedClass.getClass().getInterfaces()
        • handler: 用於提供增强的代碼,就是寫一個接口的匿名内部類,并且實現invoke()方法
      1
      2
      3
      4
      5
      6
      7
      public interface IProducer {
      /*銷售*/
      void sellProduct(float amount);

      /*售後*/
      void customerService(float amount);
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class Producer implements IProducer {

      @Override
      public void sellProduct(float amount) {
      System.out.println("賣貨了,賺錢了 " + amount);
      }

      @Override
      public void customerService(float amount) {
      System.out.println("售後了,虧錢了");
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      public class Customer {
      public static void main(String[] args) {
      Producer producer = new Producer();
      producer.sellProduct(1000f);
      // 注意這裏只能轉換成接口IProducer,不能轉換成實現類Producer
      IProducer producer1 = (IProducer) Proxy.newProxyInstance(
      producer.getClass().getClassLoader(),
      producer.getClass().getInterfaces(),
      new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 賣貨的時候經銷商抽成20%
      Float earnings = (Float) args[0];
      if ("sellProduct".equals(method.getName())) {

      return method.invoke(producer, earnings * 0.8f);
      }
      return method.invoke(producer, args);
      }
      });
      producer1.sellProduct(2000);
      }
      }
    • 基於子類的動態代理
      • 被代理類不能是final類
      • Enhancer
      • 使用create(Class cls, MethodInterceptor interceptor)方法創建對象
        • cls: 被代理類的字節碼
        • interceptor: 就是寫一個接口的匿名内部類,并且實現intercept()方法
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          public class Producer {

          public void sellProduct(float amount) {
          System.out.println("賣貨了,賺錢了 " + amount);
          }

          public void customerService(float amount) {
          System.out.println("售後了,虧錢了");
          }
          }
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          // 因爲cblib和Java 17 不兼容,下面代碼沒有經過測試從而保證正確性:D
          public class Customer {
          public static void main(String[] args) {
          Producer producer = new Producer();
          producer.sellProduct(1000f);
          Enhancer enhancer = new Enhancer();
          enhancer.setSuperclass(producer.getClass());
          enhancer.setCallback(new MethodInterceptor() {
          @Override
          public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
          Float money = (Float) args[0];
          if ("sellProduct".equals(method)) {
          return method.invoke(producer, money * 0.8f);
          }
          return method.invoke(producer, args);
          }
          });
          Producer producer1 = (Producer) enhancer.create();
          producer1.sellProduct(1000f);
          }
          }

Spring的AOP

  1. AOP
    • AOP(Aspect Oriented Programming): 面嚮切麵編程,通過預編譯和運行期動態代理實現程序功能的統一維護的一種技術
    • AOP的作用和優勢
      • 作用:在程序運行期間,不修改源碼對已有方法進行增强
      • 優勢:減少重複代碼,提高開發效率,維護方便
    • AOP的實現: 通過動態代理實現
  2. Spring中的AOP
    • Spring中的AOP是通過配置XML或者注解實現的
    • AOP相關術語
      • Joinpoint連接點: 被攔截到的點,在Spring中這些點指的是方法,因爲Spring只支持方法類型的連接點。業務層的所有方法都是連接點
      • Pointout切入點: 指的是要對哪些Joinpoint進行攔截的定義,業務層中被增强的方法才是切入點,也就是説切入點一定是連接點
      • Advice通知/增强: 攔截到Joinpoint之後要做的事情就是通知,通知的方式有很多,根據對invoke方法的相對位置分成了:前置通知、后置通知、异常通知、环绕通知等
      • Introduction引介: 引介是一種特殊的通知,可以在運行期為類動態的增加一些方法或者屬性
      • Target目標對象: 代理的目標對象
      • Weaving織入: 把增强應用到目標對象來創建新的代理對象的過程,Spring采用動態代理織入,而AspectJ采用編譯期織入和類裝載期織入
      • Proxy: 一個類被AOP織入增强之後,就產生了一個結果代理類
      • Aspect切麵: 切入點和通知的結合,就是把自己手動寫的切入點和通知的結合通過配置來完成
  3. Spring基於XML的AOP配置
    • 項目pom.xml加入如下依賴,用來解析額切入點表達式
      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.9.1</version>
      </dependency>
    • 項目bean.xml配置Spring的IOC和AOP
      • <aop:config></aop:config>表示Spring的AOP配置,所有AOP相關的配置都應該在這個配置内部
      • <aop:aspect id=? ref=?></aop:aspect>表示配置切麵,切麵有獨立的id屬性和指向對應Bean的id的ref屬性
      • <aop:before method=? pointcut=? pointcut-ref=?/>表示前置通知, <aop:after-returning method=? pointcut=? pointcut-ref=?/>表示後置通知,<aop:after-throwing method=? pointcut=? pointcut-ref=?/>表示異常通知,<aop:after method=? pointcut=? pointcut-ref=?/>表示最終通知,後置通知和異常通知只有一個會執行
      • <aop:pointcut id=? expression=?>用來指定切入點表達式,多個通知可以各自使用pointcut指定自己的切入點表達式,也可以通過pointcut-ref指定一個共同的切入點表達式標簽的id,從而節約代碼。這個標簽可以寫在<aop:aspect>内部,那麽對這一個切麵生效,也可以寫在所有切麵的外部最前方(這個位置是固定的),那麽他對所有切麵生效
      • 切入點表達式: 用來指定業務層可以被增强的方法
        • execution([訪問修飾符] 返回值 包名.類名.方法名(參數列表)), 比如execution(public void com.yanxuanshaozhu.service.impl.AccountServiceImpl.saveAccount())
        • 全通配切入點表達式 execution(* *..*.*(..)),可以對業務層所有方法進行匹配
        • 訪問修飾符可以省略,返回值可以用*通配,包名需要用.表示層級,或者用..表示當前類及其子類,類名和方法名可以用*通配,參數列表..表示一個或者多個參數
        • 一般切入點表達式只包含業務層所有方法,所以是 * com.yanxuanshaozhu.service.impl.*.*(..)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          <?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:aop="http://www.springframework.org/schema/aop"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          https://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/aop
          https://www.springframework.org/schema/aop/spring-aop.xsd">
          <!--配置Spring的IOC,把Service配置進來-->
          <bean id="accountService" class="com.yanxuanshaozhu.service.impl.AccountServiceImpl"></bean>
          <!-- Spring AOP配置 -->
          <bean id="logger" class="com.yanxuanshaozhu.utils.logger"></bean>
          <aop:config>
          <aop:aspect id="logAdvice" ref="logger">
          <!-- 這個意思是對於業務層所有匹配的方法,在他們執行前先執行printLog方法進行增强 -->
          <aop:before method="printLog"
          pointcut="execution( * com.yanxuanshaozhu.service.impl.*.(..))"/>
          </aop:aspect>
          </aop:config>
          </beans>
    • 環繞通知: 可以收寫代碼,依靠Spring框架提供的ProceedingJoinPoint接口的proceed()方法,在環繞通知内部實現前置通知,後置通知,異常通知和最終通知
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public Object aroundPrintLog(ProceedingJoinPoint pjp) {
      Object result = null;
      try {
      // 獲得切入點方法執行需要的參數
      Object[] args = pjp.getArgs();
      System.out.println("前置通知")
      // 顯式執行切入點方法
      result = pjp.proceed(args);
      System.out.println("後置通知")
      return result;
      } catch (Throwable e) {
      // 這裏不能用Exception,只能用Throwable
      System.out.println("異常通知")
      throw new RuntimeException(e);
      } finally {
      System.out.println("最終通知")
      }
      }
  4. Spring基於注解的AOP配置
    • 項目pom.xml加入如下依賴,用來解析額切入點表達式
      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.9.1</version>
      </dependency>
    • bean.xml
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <?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:aop="http://www.springframework.org/schema/aop"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/aop
      https://www.springframework.org/schema/aop/spring-aop.xsd
      http://www.springframework.org/schema/context
      https://www.springframework.org/schema/context/spring-context.xsd">
      <!-- 配置Spring船艦容器時要掃描的包-->
      <context:component-scan base-package="com.yanxuanshaozhu"></context:component-scan>
      <!-- 配置Spring開啓注解AOP的支持,這個必須有-->
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      </beans>
    • 切麵類設置
      • @Aspect注解説明這個類是一個切麵
      • @Pointcut(切入點表達式)指定了可以被增强的切入點方法,這個方法名要被增强方法的注釋調用
      • @Before(方法調用)@After(方法調用)@AfterReturning(方法調用)@AfterThrowing(方法調用)@Around(方法調用),這些注解説明這個方法是一個切麵的增强方法
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        /**
        * 用於記錄日志的工具類,它裏面提供了公共的代碼
        */
        @Component("logger")
        @Aspect
        public class logger {

        @Pointcut("execution( * com.yanxuanshaozhu.service.impl.*.*(..))")
        private void expr() {}
        /**
        * 用於打印日志,并且在切入點方法(業務層方法)
        */
        @Before("expr()") // 必須是方法調用而不是方法名
        public void beforeLog() {
        System.out.println("1.0 Logger中的beforeLog方法開始執行了");
        }

        /**
        * 用於打印日志,并且在切入點方法(業務層方法)
        */
        @AfterReturning("expr()")
        public void afterReturnLog() {
        System.out.println("2.0 Logger中的afterReturnLog方法開始執行了");
        }

        /**
        * 用於打印日志,并且在切入點方法(業務層方法)
        */
        @AfterThrowing("expr()")
        public void afterThrowLog() {
        System.out.println("3.0 Logger中的afterThrowLog方法開始執行了");
        }

        /**
        * 用於打印日志,并且在切入點方法(業務層方法)
        */
        @After("expr()")
        public void afterLog() {
        System.out.println("4.0 Logger中的afterLog方法開始執行了");
        }
        }

Spring中的JdbcTemplate

  1. 概述
    • Spring框架提供了很多操作的模板類,關係型數據庫的JdbcTemplate, HibernateTemplate, NoSQL的RedisTemplate,消息隊列的JmsTemplate
    • JdbcTemplate需要mysql-connector-java, spring-jdbc.jar, spring-tx.jar的支持
    • JdbcTemplate用來和數據庫交互,實現對表的CRUD操作
    • 基本使用: 通過Spring提供的DriverManagerDataSource來配置數據源,然後通過JdbcTemplate來操作數據庫
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public static void main(String[] args) {
      DriverManagerDataSource ds = new DriverManagerDataSource();
      ds.setDriverClassName("com.mysql.jdbc.Driver");
      ds.setUrl("jdbc:mysql://localhost:3306/xmlioc");
      ds.setUsername("root");
      ds.setPassword("123456");
      // JdbcTemplage jt = new JdbcTemplate()
      // jt.setDataSource(ds);
      JdbcTemplate jt = new JdbcTemplate(ds);
      jt.execute("insert into account(name, money) values('ttt', 1000)");
      }
    • 配置XML使用
      • bean.xml
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        <?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--配置JdbcTemplate-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置dataSource-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/xmlioc"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
        </bean>
        </beans>
        1
        2
        3
        4
        5
         public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        jt.execute("insert into account(name, money) values ('xxx', 2500)");
        }
  2. 通過JdbcTemplate進行CRUD
    • 保存數據
      1
      jt.update("insert into account(name, money) values (?, ?)", "xxx", 2500);
    • 更新數據
      1
      jt.update("update account set money = ?, name = ? where id = ?", 3000, "xxx", 1);
    • 刪除數據
      1
      jt.update("delete from account where id = ?", 1);
    • 查詢所有
      • 手寫RowMapper
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        List<Account> accounts = jt.query("select * from account where money > ?", new AccountRowMapper(), 1000f);

        for (Account acc: accounts) {
        System.out.println(acc);
        }
        // 封裝實現類,把一行封裝成一個Bean對象,之後Spring可以自動把對象加入到List中
        class AccountRowMapper implements RowMapper<Account> {
        @Override
        public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
        Account account = new Account();
        account.setId(rs.getInt("id"));
        account.setName(rs.getString("name"));
        account.setMoney(rs.getFloat("money"));
        return account;
        }
        }
      • 使用Spring提供的BeanPropertyRowMapper()方法和Bean類
        1
        2
        3
        4
        List<Account> accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper<Account>(Account.class), 1000f);
        for (Account acc: accounts) {
        System.out.println(acc);
        }
    • 查詢一個
      1
      2
      List<Account> accounts = jt.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), 1);
      System.out.println(accounts.size() == 0 ? "沒有查詢到數據" : accounts.get(0));
    • 查詢返回一行一列
      1
      2
      3
      // 防止integer容量不足
      Long count = jt.queryForObject("select count(*) from account where money > ?", Long.class, 1000f);
      System.out.println(count);
  3. Dao的兩種寫法
    • 寫入JdbcTemplate, 需要配置bean.xml或者使用Autowired注解
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      /**
      * 賬戶的持久層實現類
      */
      public class AccountDaoImpl implements IAccountDao {

      private JdbcTemplate jdbcTemplate;

      public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
      this.jdbcTemplate = jdbcTemplate;
      }

      @Override
      public Account findAccountById(Integer id) {
      List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), id);
      return accounts.size() == 0 ? null : accounts.get(0);
      }

      @Override
      public Account findAccountByName(String name) {
      List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
      if (accounts.size() == 0) {
      return null;
      }
      if (accounts.size() > 1) {
      throw new RuntimeException("Account數量大於1");
      }
      return accounts.get(0);
      }

      @Override
      public void updateAccount(Account acc) {
      jdbcTemplate.update("update account set name = ?, money = ? where id = ?", acc.getName(), acc.getMoney(), acc.getId());
      }
      }
    • 不寫JdbcTemplate屬性,而是繼承JdbcDaoSupport類,這個類裏面有JdbcTemplateDataSource, 但是這樣不能使用注解的@Autowired
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      /**
      * 賬戶的持久層實現類
      */
      public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

      @Override
      public Account findAccountById(Integer id) {
      List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), id);
      return accounts.size() == 0 ? null : accounts.get(0);
      }

      @Override
      public Account findAccountByName(String name) {
      List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
      if (accounts.size() == 0) {
      return null;
      }
      if (accounts.size() > 1) {
      throw new RuntimeException("Account數量大於1");
      }
      return accounts.get(0);
      }

      @Override
      public void updateAccount(Account acc) {
      jdbcTemplate.update("update account set name = ?, money = ? where id = ?", acc.getName(), acc.getMoney(), acc.getId());
      }
      }

Spring中的事務控制

  1. 概述
    • JavaEE體系中,事務控制都是在業務層
    • Spring框架提供了一組事務控制的接口
    • Spring的事務控制是基於AOP的,可以通過編程方式實現,也可以通過配置方式實現
  2. Spring提供的事務控制API
    • pom.xml中配置maven坐標
      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.17</version>
      </dependency>
    • PlatformTransactionManager接口
      • TransactionStatus getTransaction(), void commit(), void rollback()
      • 實現類:
        • DataSourceTransactionManager: 使用Spring JDBC或者iBatis進行持久化數據的時候使用
        • HibernateTransactionManager: 使用Hibernate進行持久化數據的時候使用
        • JpaTransactionManager: 使用JPA進行持久化數據的時候使用
        • JtaTransactionManager: 使用JTA進行持久化數據的時候使用
  3. 基於XML的聲明式事務控制配置
    • 網路資料的數據庫隔離性
      • 髒讀,不可重複讀,幻讀
        • 臟讀(dirty read): 事務A正在訪問數據,并且進行了修改,但是沒有提交到數據庫,事務B也訪問了數據,并且使用了數據,這是B發生了髒讀,因爲A可能回滾,數據不一定會寫到數據庫中
        • 不可重複讀(non-repeatable read):事務A訪問數據,期間事務B訪問了數據,并進行了修改,這樣事務A下一次讀取數據庫會得到不同的數據,這樣A發生了不可重複讀
        • 幻讀(phantom problem): 在事務不是獨立執行的時候發生,同樣的查詢執行兩次,返回結果的行數不一致,就像幻覺一樣
      • 數據庫事務隔離級別
        • default: 使用數據庫設置的隔離級別
        • read_uncommitted: 可能出現髒讀,不可重複讀,幻讀。最低隔離級別,并發性高
        • read_committed: 可能出現不可重複讀,幻讀。鎖定正在讀取的行
        • repeatable_read: 幻讀。鎖定所讀取的所有行
        • serializable: 保證不會出現髒讀,不可重複讀,幻讀。鎖定整個表
      • Spring事務的傳播行爲
        • 事務的傳播行爲指的是一個事務調用了另一個事務,那麽應該繼續在這個事務處理,還是開啓新事務,還是其他等等
        • 傳播行爲分類
          • PROPAGATION_REQUIRED:當前方法必須運行在事務中。如果存在事務,則加入事務,如果沒有事務,則啓動一個新事務
          • PROPAGATION_SUPPORTS:當前方法支持運行在事務中。如果存在事務,則加入事務,否則非事務運行
          • PROPAGATION_MANDATORY:當前方法必須運行在事務中。如果存在事務,則加入事務,否則抛出異常
          • PROPAGATION_REQUIRED_NEW:當前方法必須運行在自己的事務中。如果存在事務,挂起已經存在的事務,啓動一個新事務執行
          • PROPAGATION_NOT_SUPPORTED:當前方法不支持在事務中運行,如果存在事務,則挂起已經存在的事務,非事務運行
          • PROPAGATION_NEVER:當前方法不能運行在事務中,如果存在當前事務,抛出異常
          • PROPAGATION_NESTED:類似於PROPAGATION_REQUIRED_NEW,但是是嵌套事務
    • 配置bean.xml
      • 配置事務管理器的bean
      • <tx:advice id=? transaction-manager=?></tx:advice>配置事務的通知
      • <tx:attributes></tx:attributes>配置事務的屬性
        • isolation:指定事物的隔離級別,默認default
        • propagation: 用於指定事務的傳播行爲,默認required,表示一定有事務,增刪改用這個,查詢可以設置爲supports
        • read-only: 用於指定事務是否只讀,默認false,查詢應該是true,其他是false即可
        • timeout:用於指定事務的超時時間,默認是-1,表示永遠不超時
        • rollback-for:用於指定一個異常,產生這個異常的時候,事務回滾,其他異常的時候不回滾。沒有默認值,表示所有異常都回滾
        • no-rollback-for:用於指定一個異常,產生這個異常的時候不回滾,其他都回滾。沒有默認值,表示所有異常都回滾
      • <tx:method name=? propagation=? read-only=?></tx:method>配置事務的方法,這個要在<tx:attributes>内部,查詢方法是PROPAGATION_SUPPORTStrue,其他方法是PROPAGATION_REQUIREDfalse
      • 在AOP内部用<aop:pointcut id=? expression=?>配置通用切入點表達式
      • 在AOP内部yong<aop:advisor advice-ref=? pointcut-ref=?>把切入點表達式和通知聯係起來,advice-ref是通知的id名,pointcut-ref是切入點表達式的id名
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        <?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:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!-- 配置賬戶持久層-->
        <bean id="accountDao" class="com.yanxuanshaozhu.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
        </bean>

        <!-- 配置賬戶業務層-->
        <bean id="accountService" class="com.yanxuanshaozhu.service.impl.AccountServiceImpl">
        <property name="dataSource" ref="dataSource"></property>
        </bean>

        <!-- 配置dataSource-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/xmlioc"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
        </bean>

        <!-- 配置事務管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置事務的通知-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
        <!-- 這個用來通配查詢方法-->
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        <!-- 這個用來通配其他所有方法-->
        <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        </tx:attributes>
        </tx:advice>

        <!-- 配置AOP-->
        <aop:config>
        <!-- 配置公用切入點表達式-->
        <aop:pointcut id="pt1" expression="execution(* com.yanxuanshaozhu.service.impl.*.*(..))"/>
        <!-- 建立事務通知和切入點表達式的對應關係-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
        </aop:config>
        </beans>
  4. 基於注解的聲明式事務控制
    • 配置流程
      • 使用<bean>標簽配置事務管理器
      • 開啓Spring對注解事務的支持: <tx:annotation-driven transaction-manager= ?></tx:annotation-driven>
      • 在需要事務的類和方法加上@Transactional(propagation=?, read-only=?)注解,按照對應方法來判斷傳播行爲和只讀屬性的值
    • 基於bean.xml和注解的事務控制
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
         <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      https://www.springframework.org/schema/context/spring-context.xsd
      http://www.springframework.org/schema/tx
      https://www.springframework.org/schema/tx/spring-tx.xsd">
      <!-- 配置Spring創建容器時要掃描的包-->
      <context:component-scan base-package="com.yanxuanshaozhu"></context:component-scan>
      <!--配置JdbcTemplate-->
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
      <property name="dataSource" ref="dataSource"></property>
      </bean>
      <!-- 配置dataSource-->
      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
      <property name="url" value="jdbc:mysql://localhost:3306/xmlioc"></property>
      <property name="username" value="root"></property>
      <property name="password" value="123456"></property>
      </bean>
      <!-- 配置事務管管理器-->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"></property>
      </bean>
      <!-- 開啓Spring對注解事務的支持-->
      <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
      </beans>
    • 基於純注解的事務控制
      • 寫一個配置類入口
        1
        2
        3
        4
        5
        6
        7
        @Configuration
        @ComponentScan("com.yanxuanshaozhu") // 設置創建容器時掃描的包
        @Import({JdbcConfig.class, TransactionConfig.class}) // 導入其他配置類
        @PropertySource(value = "jdbc.properties") // 加載jdbc.properties
        @EnableTransactionManagement //開啓Spring對注解的支持
        public class SpringConfiguration {
        }
      • Jdbc配置類
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        /**
        * 鏈接數據庫相關的配置
        */
        @Configuration

        public class JdbcConfig {
        @Value("${driver}") // 取得properties文件中的值
        private String driver;
        @Value("${url}")
        private String url;
        @Value("${user}")
        private String user;
        @Value("${pwd}")
        private String pwd;

        /**
        * 創建JdbcTemplate對象
        */
        @Bean(name = "jdbcTemplate")
        public JdbcTemplate createJdbcTemplate(DataSource ds) {
        return new JdbcTemplate(ds);
        }

        /**
        * 創建一個數據源對象
        */
        @Bean(name = "dataSource")
        public DataSource createDataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(user);
        ds.setPassword(pwd);
        return ds;
        }
        }
      • 創建事務管理器
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        /**
        * 和事務有關的類
        */
        public class TransactionConfig {

        /**
        * 創建事務管理器對象
        */
        @Bean(name = "transactionManager")
        public PlatformTransactionManager createTransactionManager(DataSource ds) {
        return new DataSourceTransactionManager(ds);
        }
        }
      • 在需要事務的類和方法加上@Transactional(propagation=?, read-only=?)注解,按照對應方法來判斷傳播行爲和只讀屬性的值
  5. 編程式事務控制的方式
    • 事務控制基本都是聲明式的,編程式的事務控制應用極少
    • bean.xml中配置<bean id=? class="org.springframework.transaction.support.TransactionTemplate"></bean>標簽
    • 業務層方法寫在transactionTemplate.execute()方法的匿名内部TransactionCallback類的doingTransaction()方法中,如果有異常, doingTransaction()會調用TransactionTemplate.rollback()方法回滾,如果沒有異常, doingTransaction()會調用TransactionTemplate.commit()方法事務提交
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      public class AccountServiceImpl implements IAccountService {

      private IAccountDao accountDao;

      // 創建事務模板對象和set方法讓Spring來進行注入
      private TransactionTemplate transactionTemplate;

      public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
      this.transactionTemplate = transactionTemplate;
      }

      public void setAccountDao(IAccountDao accountDao) {
      this.accountDao = accountDao;
      }

      @Override
      public Account findAccountById(Integer id) {
      return accountDao.findAccountById(id);
      }

      // 每一個需要事務的業務層方法都需要放在transactionTemplate.execute()方法中
      @Override
      public void transfer(String from, String to, float amount) {
      transactionTemplate.execute(new TransactionCallback<Object>() {
      @Override
      public Object doInTransaction(TransactionStatus status) {
      Account fromAccount = accountDao.findAccountByName(from);
      Account toAccount = accountDao.findAccountByName(to);
      fromAccount.setMoney(fromAccount.getMoney() - amount);
      accountDao.updateAccount(fromAccount);
      toAccount.setMoney(toAccount.getMoney() + amount);
      accountDao.updateAccount(toAccount);
      return null;
      }
      });
      }
      }
    • 可以發現,因爲每一個需要事務的方法都需要放在transactionTemplate.execute()中,有很多重複代碼,不利於解耦,因此實際上很少用到

SpringMVC

Spring MVC基礎

  1. 基本概念
    • 三層架構
      • 架構:C/S 或者B/S
    • 三層架構
      • 表現層:SpringMVC,表現層又叫做web層,主要負責接收客戶端的請求,向客戶端發送請求結果
      • 業務層:Spring,負責業務邏輯處理,和項目中的需求挂鈎
      • 持久層:MyBatis, 持久層又叫做dao層,負責數據持久化
    • 表現層框架MVC
      • Model模型:Java Bean,封裝數據
      • View視圖:JSP,用來和用戶進行交互
      • Controller控制器: Servlet,用來接受請求和返回數據
    • SpringMVC是一個基於Java實現的MVC設計模型的請求驅動類型的輕量級Web框架,屬於Springframework的後續產品。Spring框架整合SpringMVC比整合其他的表現層框架要容易
  2. SpringMVC的Maven環境配置
    • 創建maven攻城的時候選Create from atchetype裏面的org.apache.maven.archetypes:maven-archetype-webapp
    • 配置流程
      • 導入相關依賴: spring-contexg, spring-web, spring-webmvc, javax.servlet-api, jsp-api。 注意如果是tomcat10.x版本的話,tomcat的兩個maven依賴配置用新的。但是非常不幸,SpringMVC 5.x版本不支持tomcat10.x,所以要改成tomcat9.x,所以還是要用舊的... 之前使用Tomcat10.x的時候這裏使用的是自己寫的Servlet類,無論是實現接口還是繼承HttpServlet,都是用的Tomcat10.x的Jakarta的命名空間,但是這裏用的是Spring内部實現類,用的依舊是舊版的Javax命名空間,所以必須使用Tomcat9.x或者之前的版本
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
         <!-- 新版本API,傳説Spring framework 6.x會支持這個API -->
        <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-servlet-api</artifactId>
        <version>版本號</version>
        </dependency>
        <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-jsp-api</artifactId>
        <version>版本號</version>
        </dependency>

        <!-- 舊版本API -->
        <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
        </dependency>
        <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
        </dependency>
      • 創建SpringMVC控制器類(等同於Servlet功能),這個針對不同的請求和請求路徑寫多個方法即可
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        // 使用Controller定義bean
        @Controller
        public class ControllerDemo {
        // 設置當前方法的訪問路徑
        @RequestMapping("/save")
        // 當前方法的返回值作爲請求體返回給客戶端
        @ResponseBody
        public String save() {
        System.out.println("Save 方法執行了");
        return "{'info': 'saved'}";
        }
        }
      • 初始化SpringMVC配置環境,設定SpringMVC環境加載對應的bean。注意SpringMVC和Spring掃描的包應該不一樣,SpringMVC應該只掃描controller包,Spring應該只掃描dao包和service
        1
        2
        3
        4
        5
        6
        // 創建springmvc的配置文件,加載controller對應的bean
        @Configuration
        // SpringMVC掃描的包
        @ComponentScan(basePackages = "com.yanxuanshaozhu.controller")
        public class SpringMvcConfiguration {
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        // 創建Spring的配置文件,加載dao和service對應的bean
        @Configuration
        // Spring掃描的包,有兩種寫法,一種是寫多個包路徑,一般主要有這個
        // @ComponentScan("com.yanxuanshaozhu.dao", "com.yanxuanshaozhu.service")
        // 第二種寫法是使用excludeFilters屬性,排除掉不需要掃描的包,SpringBoot使用了這種方法
        @ComponentScan(basePackages = "com.yanxuanshaozhu",
        excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION, // 按照注解排除
        classes = Controller.class // 排除被@Controller注解的类
        ))
        public class SpringConfiguration {
        }
      • 初始化Servlet容器,加載SpringMVC和Spring的環境,并且設置SpringMVC技術處理的請求
        • 標準寫法
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          // 定義Servlet容器啓動的配置類,從而在Servlet啓動的時候加載Sprng的配置類
          public class ServletInitializer extends AbstractDispatcherServletInitializer {
          // 加載SpringMVC容器的配置
          @Override
          protected WebApplicationContext createServletApplicationContext() {
          // 創建SpringMVC的配置類,不能用AnnotationConfigApplicationContext,只能用AnnotationConfigWebApplicationContext
          AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
          ctx.register(SpringMvcConfiguration.class);
          return ctx;
          }

          // 規定哪些請求路徑會被SpringMVC處理
          @Override
          protected String[] getServletMappings() {
          // 所有請求路徑規SpringMVC處理
          return new String[]{"/"};
          }

          // 加載Spring容器的配置
          @Override
          protected WebApplicationContext createRootApplicationContext() {
          AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
          ctx.register(SpringConfiguration.class);
          return ctx;
          }
          }
        • 簡化寫法:AbstractAnnotationConfigDispatcherServletInitializer繼承了AbstractDispatcherServletInitializer
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
          // 加載Spring的配置類
          @Override
          protected Class<?>[] getRootConfigClasses() {
          return new Class[]{SpringConfiguration.class};
          }
          // 加載SpringMVC的配置類
          @Override
          protected Class<?>[] getServletConfigClasses() {
          return new Class[]{SpringMvcConfiguration.class};
          }
          // 規定哪些請求路徑由SpringMVC處理
          @Override
          protected String[] getServletMappings() {
          return new String[]{"/"};
          }
          }
    • 使用Postman進行測試get請求和post請求
      • 創建workspace和collection來保存測試,方便以後使用

SpringMVC的請求和響應

  1. SpringMVC處理請求
    • 不同模塊的相同請求路徑有兩種寫法
      • 方法1: 每個模塊的方法内部注解加上模塊名前綴,如@RequestMapping("/user/save")@RequestMapping("/book/save")
      • 方法2: 每個模塊上面加上請求路徑注解, 如@RequestMapping("/user")@RequestMapping("/book"),這樣模塊内部的方法就不用每一個方法的注解都加上路徑前綴了
    • 請求參數類型和具體寫法
      • Controller和Servlet不一樣,Controller裏面的方法不區分接受的是get請求還是post請求
      • 服務器中方法寫具體參數: 使用@Request("請求參數") T 方法參數 完成請求參數和方法參數的對應,如果請求參數和方法參數的名字一樣,可以省略掉注解直接寫T 請求參數/方法參數
        1
        2
        3
        4
        5
        6
        @RequestMapping("/info")
        @ResponseBody
        public String info(@RequestParam("name") String name, @RequestParam("age") int age) {
        System.out.println("User info");
        return "{'user':{'type': 'info', 'name': " + name + ", 'age': " + age + "}}";
        }
      • 服務器中方法寫對象參數:
        1
        2
        3
        4
        5
        6
        @RequestMapping(value = "/userInfo", produces = "text/html;charset=utf-8")
        @ResponseBody
        public String info(User user) {
        System.out.println("User info with object");
        return "{'user':{'type': 'info', 'name': " + user.getName() + ", 'age': " + user.getAge() + "}}";
        }
      • 服務器中方法寫對象+嵌套對象參數傳遞: 這時候請求端參數要寫name, age, address.city, address.province用點分隔嵌套對象和屬性
        1
        2
        3
        4
        5
        6
        7
        8
        @RequestMapping(value = "/userNestedInfo", produces = "text/html;charset=utf-8")
        @ResponseBody
        public String infoNested(User user) {
        System.out.println("User info with nested object");
        System.out.println(user);
        return "{'user':{'type': 'info', 'name': " + user.getName() + ", 'age': " + user.getAge() +
        ", 'city': " + user.getAddress().getCity() + ", 'province': " + user.getAddress().getProvince() + "}}";
        }
      • 服務器中方法寫數組參數: 這時候請求端數組裏面的參數的key要相同
        1
        2
        3
        4
        5
        6
        @RequestMapping("/arrayInfo")
        @ResponseBody
        public String arrayInfo(@RequestParam("請求端數組名") String[] args) {
        System.out.println(Arrays.toString(args));
        return "{'arrayInfo': 'resp' }";
        }
      • 服務器中方法寫集合參數: 這時候請求端數組裏面的參數的key要相同,并且服務器中方法參數前加上@RequestParam注解,否則服務器會試圖把請求參數傳入集合的屬性
        1
        2
        3
        4
        5
        6
        @RequestMapping("/listInfo")
        @ResponseBody
        public String arrayInfo(@RequestParam("請求端數組名") List<String> args) {
        System.out.println(args);
        return "{'listInfo': 'resp' }";
        }
      • 客戶端和服務器之間傳遞JSON
        • 首先在項目中加入jackson-databind依賴
        • 在SpringMVC的配置類上面加入@EnableWebMvc注解,開啓JSON和Java對象的轉換功能
        • 在服務器方法參數前加入@RequestBody注解
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          // 傳遞JSON數組
          @RequestMapping("/jsonArray")
          @ResponseBody
          public String jsonArray(@RequestBody List<String> args) {
          System.out.println(args);
          return "{'jsonArrayInfo': 'resp'}";
          }

          // 傳遞JSON對象
          @RequestMapping("/jsonObject")
          @ResponseBody
          public String jsonObject(@RequestBody User user) {
          System.out.println(user);
          return "{'user':{'type': 'info', 'name': " + user.getName() + ", 'age': " + user.getAge() +
          ", 'city': " + user.getAddress().getCity() + ", 'province': " + user.getAddress().getProvince() + "}}";
          }

          // 傳遞JSON對象數組
          @RequestMapping("/jsonObjectArray")
          @ResponseBody
          public String jsonObjectArray(@RequestBody List<User> users) {
          StringBuilder builder = new StringBuilder();
          builder.append("[\n");
          for (User user : users) {
          builder.append("{'user':{'type': 'info', 'name': " + user.getName() + ", 'age': " + user.getAge() +
          ", 'city': " + user.getAddress().getCity() + ", 'province': " + user.getAddress().getProvince() + "}},\n");
          }
          builder.replace(builder.length() - 2, builder.length(), "");
          builder.append("\n]");
          String res = builder.toString();
          System.out.println(res);
          return res;
          }
      • 客戶端和服務器之間傳遞日期
        • SpringMVC默認支持標準格式(年/月/日,或者月/日/年)的日期字符串自動轉換成Date對象
        • 對於非標準日期格式,需要使用@DateTimeFormat(pattern="請求參數日期格式")注解修飾請求參數,否則報錯, 比如@DateTimeFormat(pattern = "yyyy-dd-MM") @RequestParam("data") Date date
        • 内部原理: spring-core中的Converter接口有衆多實現類,完成了輸入類型到目標類型的轉換。@EnableWebMvc注解開啓之後可以根據類型匹配對應的類型轉換器
      • 請求參數包括中文可能會有亂碼,需要進行處理:
        • 處理在服務器内部中文亂碼: 在初始化Servlet容器的類裏面加上一個過濾器,設置服務器的charsetEncoding為UTF-8
          1
          2
          3
          4
          5
          6
          @Override
          protected Filter[] getServletFilters() {
          CharacterEncodingFilter filter = new CharacterEncodingFilter();
          filter.setEncoding("UTF-8");
          return new Filter[]{filter};
          }
        • 處理響應中文亂碼
          • 第一步: 按照上面的方法首先處理服務器内部中文亂碼
          • 第二部:修改相關Controller内部的@RequestMapping()注解,加上produced屬性,比如@RequestMapping(value= "/info", produces = "text/html;charset=utf-8")
  2. SpringMVC發送響應
    • 頁面跳轉
      • 直接返回字符串:這種方式會將返回的字符串和視圖解析器的前後綴拼接后跳轉
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // 在SpringConfiguration.java中
        @Bean
        public ViewResolver configureViewResolver() {
        // 用來返回指定目錄下的指定後綴的文件
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        // 設置訪問前綴
        internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
        // 設置訪問後綴
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
        }
        1
        2
        3
        4
        5
        // 在UserController.java中
        @RequestMapping("/info")
        public String info() {
        return "info"; // 這代表自動跳轉到/WEB-INF/jsp/info.jsp
        }
      • 通過ModelAndView返回: Model是用來封裝數據的,View是用來展示數據的
        1
        2
        3
        4
        5
        6
        7
        8
        9
        @RequestMapping("/info")
        public ModelAndView info() {
        ModelAndView modelAndView = new ModelAndView();
        // 設置展示頁面名稱,同樣會拼接視圖解析器的前後綴
        modelAndView.setViewName("info"); // 訪問/WEB-INF/jsp/info.jsp
        // 通過模型添加數據,在頁面上可以通過${key}來拿到數據
        modelAndView.addObject("name", "張三");
        return modelAndView;
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // ModelAndView可以使用Spring直接注入,不用自己去new
        @RequestMapping("/info")
        public ModelAndView info(ModelAndView modelAndView) {
        // 設置展示頁面名稱,同樣會拼接視圖解析器的前後綴
        modelAndView.setViewName("info"); // 訪問/WEB-INF/jsp/info.jsp
        // 通過模型添加數據,在頁面上可以通過${key}來拿到數據
        modelAndView.addObject("name", "張三");
        return modelAndView;
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        // 還可以只讓Spring注入Model數據,返回字符串跳轉頁面
        @RequestMapping("/info")
        public String info(Model model) {
        // 通過Model添加數據
        model.addAttribute("name", "張三");
        return "info";
        }
        // 類似的寫法是注入一個HttpServletRequest對象
        @RequestMapping("/info")
        public String info(HttpServletRequest request) {
        // 通過Model添加數據
        request.addAttribute("name", "張三");
        return "info";
        }
    • 回寫數據
      • 直接返回字符串
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // 讓Spring注入HttpServletResponse對象,在寫數據到頁面上,耦合度高
        @RequestMapping("/info")
        public void info(HttpServletResponse response) throws IOException{
        response.getWriter().writer("直接在頁面上寫入的字符串");
        }
        // 使用@ResponseBody注解,耦合度地
        @RequestMapping("/info")
        @ResponseBody
        public String info() {
        return "直接在頁面上寫入的字符串";
        }
      • 返回JSON字符串的Java對象
        • 加入jackson-databind依賴
        • 使用@ResponseBody注解,返回JSON數據
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          @RequestMapping("/jsonData")
          @ResponseBody
          public User userInfo() {
          System.out.println("Respond json object");
          User user = new User();
          user.setName("jing wang");
          user.setAge(12);
          Address address = new Address();
          address.setCity("xicheng");
          address.setProvince("beijing");
          user.setAddress(address);
          return user;
          }
    • @ResponseBody注解
      • 作用:設置當前控制器方法的返回值作爲響應躰
      • 内部原理: 使用HttpMessageConverter接口的實現類MappingJackson2HttpMessageConverter完成轉換

REST風格

  1. 簡介
    • 定義 REST(Representional State Transfer),表現形式狀態轉換
    • 傳統風格資源描述方式和REST風格資源描述舉例
      • 傳統風格
        • 查詢用戶信息 http://localhost:8080/user/getById?id=1
        • 保存用戶信息 http://localhost:8080/user/save
      • REST風格
        • 查詢用戶信息 http://localhost:8080/user/1
        • 保存用戶信息 http://localhost:8080/user
      • REST風格優點
        • 書寫簡化
        • 隱藏資源的訪問行爲,無法通過URL得知是對資源的何種操作
      • REST風格通過行爲的不同來實現CRUD的區別
        • 查詢全部用戶信息 http://localhost:8080/users GET方法
        • 查詢指定用戶信息 http://localhost:8080/users/1 GET方法
        • 添加用戶信息 http://localhost:8080/users POST方法(新增/保存)
        • 修改用戶信息 http://localhost:8080/users PUT方法(修改/更新)
        • 刪除指定用戶信息 http://localhost:8080/users/1 DELETE方法
      • REST風格裏URL的模塊名稱用複數
      • 根據REST風格對資源進行訪問叫做RESTful
  2. RESTful入門案例
    • 幾個注解比較
      • @RequestBody用於接受請求體中的JSON數據
      • @RequestParam用於接受URL地址中key=value形式的數據
      • @PathVariable用於接受URL路徑參數,使用{參數名}描述路徑參數
    • Rest風格實現: 使用@RequestMapping注解的method屬性指定請求的訪問方式, 使用@PathVariable注解指定訪問路徑中的參數
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      @Controller

      @RequestMapping("/users")
      public class UserController {

      // 增加新數據
      @RequestMapping(method = RequestMethod.POST)
      @ResponseBody
      public String add() {
      System.out.println("Add method called");
      return "Add method called";
      }

      // 刪除數據
      @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
      @ResponseBody
      public String deleteById(@PathVariable Integer id) {
      System.out.println("Delete method called by id " + id);
      return "Delete method called by id" + id;
      }

      // 修改數據
      @RequestMapping(method = RequestMethod.PUT)
      @ResponseBody
      public String update() {
      System.out.println("Update method called");
      return " Update method called";
      }

      //查找所有數據
      @RequestMapping(method = RequestMethod.GET)
      @ResponseBody
      public String searchAll() {
      System.out.println("SearchAll method called");
      return "SearchAll method called";
      }

      // 查找單個數據
      @RequestMapping(value = "/{id}", method = RequestMethod.GET)
      @ResponseBody
      public String searchByID(@PathVariable Integer id) {
      System.out.println("SearchById method called by id " + id);
      return "SearchById method called by id " + id;
      }
      }
    • 快速開發的簡寫
      • 如果每個方法都有@ResponseBody注解的話,這個注解就可以放到類上面11
      • @Controller注解和@ResponseBody注解一起出現的話,等價於@RestController注解
      • @RequestMapping(method = RequestMethod.POST)注解等價於@PostMapping注解,其他請求方法的等價性是類似的
    • 入門案例
      • 注意對於網站的靜態資源不應該經過SpringMVC的處理,應該是由Servlet容器直接訪問,因此需要增加一個配置類,設置攔截路徑,并讓SpringMVC的配置類掃描這個類/導入這個類
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        // SpringMVC的支持配置類
        @Configuration
        public class SpringMvcConfigurationSupport extends WebMvcConfigurationSupport {

        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 黨訪問pages目錄下面的資源的時候,不被MVC攔截,而是讓Servlet直接訪問資源
        // **代表任意目錄
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
        }
        }

        // SpringMVC的配置類
        @Configuration
        @ComponentScan({"com.yanxuanshaozhu.controller", "com.yanxuanshaozhu.config"})
        @EnableWebMvc

        public class SpringMvcConfiguration {
        }

      • 注意前端頁面在訪問服務器的時候,路徑要從項目虛擬目錄開始寫,比如axio.get("/項目虛擬目錄/請求資源路徑")

MyBatis

MyBatis基礎

  1. 原始JDBC操作分析
    • 原始操作
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      Class.forName("com.mysql.jdbc.Driver");
      Connection conn = DriverManager.getConnection(url, user, password);
      PreparedStatement pstmt = conn.prepareStatement(sql);
      // 查詢
      ResultSet rs = pstmt.executeQuery();
      while (rs.next()) {
      // 封裝數據庫中一行到JavaBean
      }
      rs.close();
      stmt.close();
      conn.close();
      // 增刪改數據
      pstmt.executeUpdate();
      stmt.close();
      conn.close();
    • 原始JDBC缺陷分析和解決方式
      • 數據庫頻繁創建,釋放資源浪費系統性能: 使用數據連接池
      • sql數據硬編碼不易維護:將sql語句抽取到xml配置文件中或者使用注解進行配置
      • 查詢中結果中數據和JavaBean對象的映射需要手動配置:使用反射進行自動填充
  2. MyBatis基礎
    • 簡介
      • MyBatis是一個Java持久層框架,它内部封裝了JDBC,執行sql語句并且將結果映射為Java對象返回,屏蔽了底層細節,使得開發者只需要關注sql語句本身
      • MyBatis通過xml或者注解的方式將statement配置起來,應且通過Java對象和statement中的動態參數進行映射生成最終執行的sql語句
    • 項目配置流程
      • 導入mybatis的jarmybatis-x.x.x.jar包或者在pom.xml中配置mybatis的坐標,還需要數據庫驅動
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
          <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>x.x.x</version>
        </dependency>
        <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
        </dependency>
      • 創建數據庫中表XXX
      • 在項目中創建XXX實體類
      • 編寫映射文件XXXMapper.xml:寫sql語句,路徑和JavaBean對應,比如java目錄下面的com.yanxuanshaozhu.model.XXX,就要寫resources目錄下面的com/yanxuanshaozhu/config/XXXMapper.xml
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        <!-- 這個表的格式直接在mybatis官網複製即可 -->
        <?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">
        <!-- 在Java程序裏面通過使用namespaceId.methodId的方式調用sql,比如userMapper.findAll -->
        <mapper namespace="userMapper">
        <!-- 查詢寫上結果對應的JavaBean對象,mybatis就可以自動設置映射 -->
        <select id="findAll" resultType="user">
        select * from User;
        </select>
        </mapper>
      • 編寫核心文件SqlMapConfig.xml:寫mybatis的核心配置,和上面的XXXMapper.xml文件放在同一個目錄中
        • transactionManager: 可選為JDBC(通過JDBC管理事務)或者MANAGED(通過容器管理事務,很少使用)
        • dataSource: 可選為POOLED(連接池), UNPOOLED(不使用連接池), JNDI(很少用)
        • mappers内部的mappter使用resource配置相對於resources目錄的路徑, 或者用url配置磁盤上絕對路徑, 或者用class類映射器接口實現類的全限定類名, 或者用name將包内的映射器接口實現全部注冊爲映射器
        • properties加載外部配置文件,防止核心配置文件中硬編碼
        • typeAliases和内部的typeAlias定義別名
          • 這樣在映射文件XXXMapper.xml内部就可以不寫全限定類名,而是寫別名,簡化開發
          • configuration内部最開始寫properties, 再寫typeAliases, 再寫typeHandlers, 再寫environments, 再寫mappers, 順序固定
          • MyBatis内部定義了基本類型和String等的別名(Integer-> int, String ->string)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          <!--這個表的格式直接在mybatis官網複製即可  -->
          <?xml version="1.0" encoding="UTF-8" ?>
          <!DOCTYPE configuration
          PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
          "http://mybatis.org/dtd/mybatis-3-config.dtd">
          <configuration>
          <properties resource="com/yanxuanshaozhu/properties/db.properties"/>
          <typeAliases>
          <typeAlias type="com.yanxuanshaozhu.domain.User" alias="user"></typeAlias>
          </typeAliases>
          <!-- 可以配置多個環境,然後選擇一個默認環境 -->
          <environments default="development">
          <environment id="development">
          <!-- transactionManager和dataSource這裏都是固定寫法 -->
          <transactionManager type="JDBC"/>
          <dataSource type="POOLED">
          <!-- 下面使用${鍵名}讀取了db.properties中的内容 -->
          <property name="driver" value="${driver}"/>
          <property name="url" value="${url}"/>
          <property name="username" value="${username}"/>
          <property name="password" value="${password}"/>
          </dataSource>
          </environment>
          </environments>
          <!-- 配個多個mapper,用來加載映射文件 -->
          <mappers>
          <mapper resource="com/yanxuanshaozhu/mapper/UserMapper.xml"></mapper>
          </mappers>
          </configuration>
      • 編寫測試類
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        @Test
        public void test() throws IOException {
        // 獲取核心配置文件
        InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/SqlMapperConfig.xml");
        // 通過工廠對象的build方法加載核心配置文件,從而獲得session工廠對象
        SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(in);
        // 獲得session會話對象,并且自動提交事務
        // sqlSessionFactory.openSession()和sqlSessionFactory.openSession(false)都不會自動提交事務,需要手動sqlSession.commit()
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 執行操作,參數是namespaceId.methodId
        List<Object> userList = sqlSession.selectList("userMapper.findAll");
        System.out.println(userList);
        // 釋放資源
        sqlSession.close();
        }
    • MyBatis簡單增刪改查操作
      • 插入
        1
        2
        3
        4
        5
        6
        7
        <mapper namespace="userMapper">
        <!-- 插入數據,數據來源於JavaBean,這裏使用了別名-->
        <insert id="add" parameterType="user">
        <!-- 這裏使用#{JavaBean對象屬性名指定數據來源} -->
        insert into User(username, password) values(#{username}, #{password})
        </insert>
        </mapper>
        1
        2
        3
        4
        5
        6
        7
        8
        InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/SqlMapperConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        User user = new User();
        user.setUsername("insertedName");
        user.setPassword("insertedPassword");
        sqlSession.insert("userMapper.add", user);
        sqlSession.close();
      • 刪除
        1
        2
        3
        4
        5
        6
        7
        <mapper namespace="userMapper">
        <!-- 刪除數據,條件來源於JavaBean,這裏使用了別名 -->
        <delete id="delete" parameterType="int">
        <!-- 這裏只有一個參數,#{參數名}裏面的參數名隨便寫什麽都可以 -->
        delete from user where id = #{id};
        </delete>
        </mapper>
        1
        2
        3
        4
        5
        InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/SqlMapperConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        sqlSession.delete("userMapper.delete", 6);
        sqlSession.close();
      • 修改
        1
        2
        3
        4
        5
        6
        7
        <mapper namespace="userMapper">
        <!-- 修改數據,數據來源於JavaBean,這裏使用了別名 -->
        <update id="update" parameterType="user">
        update user set username = #{username}, password = #{password}
        where id = #{id};
        </update>
        </mapper>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/SqlMapperConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        User user = new User();
        user.setId(1);
        user.setUsername("updatedName");
        user.setPassword("updatedPwd");
        sqlSession.update("userMapper.update", user);
        sqlSession.close();
      • 查詢多個
        1
        2
        3
        4
        5
        6
        <mapper namespace="userMapper">
        <!-- 查詢數據,數據來源於JavaBean,這裏使用了別名 -->
        <select id="findAll" resultType="user">
        select * from User;
        </select>
        </mapper>
        1
        2
        3
        4
        5
        6
        InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/SqlMapperConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        List<Object> userList = sqlSession.selectList("userMapper.findAll");
        System.out.println(userList);
        sqlSession.close();
      • 查詢一個
        1
        2
        3
        4
        5
        6
        7
        8
        <mapper namespace="userMapper">
        <!-- 查詢數據,數據來源於JavaBean,這裏使用了別名 -->
        <select id="findById" resultType="user" parameterType="int">
        select *
        from user
        where id = #{id};
        </select>
        </mapper>
        1
        2
        3
        4
        5
        InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/SqlMapperConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        User user = (User)sqlSession.selectOne("userMapper.findById", 1);
        System.out.println(user);

MyBatis進階

  1. MyBatis的dao層實現
    • 傳統實現方式:手寫Dao的代碼
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class UserDaoImpl implements UserDao{
      @Override
      public List<User> findAll() {
      try {
      InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/config.xml");
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
      SqlSession sqlSession = sqlSessionFactory.openSession(true);
      List<User> users = sqlSession.selectList("userMapper.findAll");
      return users;
      } catch (Exception e) {
      e.printStackTrace();
      }
      return null;
      }
      }
    • 通過代理開發方式實現:只需要編寫Dao層接口(這個接口一般叫XXXMapper.java),由MyBatis根據接口的定義創建動態代理對象
      • 遵循規範
        1. XXXMapper.xml中的namespace屬性值要和Dao層的XXXMapper.java的全限定類名一致(注意这里不能使用别名)
        2. XXXMapper.java接口中方法的方法名稱和XXXMapper.xml中的方法的id屬性一致
        3. XXXMapper.java接口中方法的返回類型和XXXMapper.xml中的方法的resultType屬性一致(注意比如返回值是List<User>,那么resultType屬性值是user即可)
        4. XXXMapper.java接口中方法的參數類型和XXXMapper.xml中的方法的parameterType屬性一致
      • 使用方法:在需要使用XXXMapper对象的时候,通过XXXMapper xxxMapper = sqlSession.getMapper(XXXMapper.class)
      • 一个例子
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        <!-- 在userMapper.xml文件中 -->
        <mapper namespace="com.yanxuanshaozhu.dao.UserMapper">
        <sql id="commonSql">select *
        from user</sql>
        <select id="findById" resultType="user" parameterType="int">
        <include refid="commonSql"></include>
        where id = #{id};
        </select>

        <select id="findAll" resultType="user">
        <include refid="commonSql"></include>
        </select>

        <update id="updateById" parameterType="user">
        update user
        set username = #{username},
        password =#{password}
        where id = #{id};
        </update>
        <insert id="insert" parameterType="user">
        insert into user (username, password)
        values (#{username}, #{password});
        </insert>
        <delete id="deleteById" parameterType="user">
        delete
        from user
        where id = #{id};
        </delete>
        </mapper>
        1
        2
        3
        4
        5
        6
        7
        8
        // UserMapper.java
        public interface UserMapper {
        User findById(Integer id);
        List<User> findAll();
        void updateById(User user);
        void deleteById(User user);
        void insert(User user);
        }
        1
        2
        3
        4
        5
        6
        7
        InputStream in = Resources.getResourceAsStream("com/yanxuanshaozhu/mapper/config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // MyBatis通过动态代理创建对象
        UserDao userDao = sqlSession.getMapper(UserMapper.class);
        User user = userDao.findById(1);
        System.out.println(user);
  2. 動態SQL
    • 動態SQL: 根據用戶輸入條件或者外部條件動態組合的SQK語句塊
    • if語句舉例
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <select id="findByCondition" resultType="user" parameterType="user">
      select * from user
      <!-- 使用where保證進入判斷,如果内部的test全部不成立,則相當於沒有where -->
      <where>
      <!-- 如果test成立那麽執行内部語句,否則跳過内部語句 -->
      <if test="id != null">
      id = #{id}
      </if>
      <if test="username != null">
      and username = #{username}
      </if>
      <if test="password != null">
      and password = #{password}
      </if>
      </where>
      ;
      </select>
    • foreach語句舉例
      1
      2
      select * from user
      where id in (1, 2, 3);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <select id="findByIds" resultType="user" parameterType="list">
      select *
      from user
      <where>
      <!-- 如果傳入的是數組寫collection="array", 如果傳入的是列表寫collection="list" -->
      <!-- item名字隨便寫,和foreach内部一致即可 -->
      <foreach collection="list" item="id" open="id in (" close=")" separator=",">
      #{id}
      </foreach>
      </where>
      </select>
  3. SQL片段抽取
    • 對於多個SQL語句中重複的部分,可以抽取出來,其他語句引用這部分即可,利於簡潔化和解耦
    • 使用<sql id="公共SQL的唯一id">SQL語句</sql>抽取公共SQL語句
    • 使用<include refid="公共SQL的唯一id"></include>引用gonggongSQL語句
  4. MyBatis自定義類型轉換器
    • MyBatis自帶了數據庫類型和Java類型的轉換,這些轉換是自動完成了,不需要手動配置
    • 自定義轉換器流程
      1. 自定義轉換器類實現TypeHandler接口,或者繼承BaseTypeHandler
      2. 重寫類中方法
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        /**
        * 目標是實現Java的Date和SQL的Bigint之間轉換
        */
        public class DateTypeHandler extends BaseTypeHandler<Date> {

        // Java類型轉換成數據庫類型
        // preparedStatement是要執行的語句,i是要轉換的字段的索引
        @Override
        public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
        long time = date.getTime();
        preparedStatement.setLong(i, time);
        }

        // 數據庫類型轉換成Java類型
        // resultSet是查詢的結果集,s是要轉換的字段名稱
        @Override
        public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
        long time = resultSet.getLong(s);
        Date date = new Date(time);
        return date;
        }

        // 數據庫類型轉換成Java類型
        // resultSet是查詢的結果集,s是要轉換的字段的索引
        @Override
        public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
        long time = resultSet.getLong(i);
        Date date = new Date(time);
        return date;
        }


        // 數據庫類型轉換成Java類型
        // callableStatement用來調用存儲過程,i是要轉換的字段的索引
        @Override
        public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        long time = callableStatement.getLong(i);
        Date date = new Date(time);
        return date;
        }
        }
      3. 在MyBatis核心配置文件中注冊這個自定義的轉換器:在typeHandlers標簽中寫typeHandler標簽
        1
        2
        3
        4
        <!-- 這個標簽寫在typeAlias之後,environments之前 -->
        <typeHandlers>
        <typeHandler handler="com.yanxuanshaozhu.handler.DateTypeHandler"></typeHandler>
        </typeHandlers>
  5. MyBatis多表操作(多表查詢)
    • 單表查詢的時候,MyBatis自動完成了列名和JavaBean屬性的映射,多表查詢需要手動設置映射
    • 一对一模型: 在多表關聯的時候,設置好結果表列名到各個表的屬性的映射,然後把查詢操作的resultMap屬性設置成這個映射即可
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <!-- purchaseMapper.xml文件中 -->
      <mapper namespace="com.yanxuanshaozhu.mapper.PurchaseMapper">
      <!-- 这里的type是别名-->
      <resultMap id="userPurchase" type="purchase">
      <!-- 内部写手动指定字段和实体的映射关系, column是结果表的列名,property是实体类的属性-->
      <!-- id代表結果表的主键, 其他是列使用result-->
      <id column="pid" property="id"></id>
      <result column="uid" property="user.id"></result>
      <result column="username" property="user.username"></result>
      <result column="password" property="user.password"></result>
      <result column="birthday" property="user.birthday"></result>
      <result column="time" property="time"></result>
      <result column="total" property="total"></result>
      </resultMap>
      <select id="findUserPurchase" resultMap="userPurchase">
      select u.id as uid,
      username,
      password,
      birthday,
      p.id as pid,
      time, total
      from user as u,
      purchase as p
      where u.id = p.uid;
      </select>
      </mapper>
      1
      2
      3
      4
      5
      // purchaseMapper.java文件中
      public interface PurchaseMapper {
      // 返回Purchase類型,因爲xml文件中resultMap的type設置成了Purchase類型
      List<Purchase> findUserPurchase();
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
         <!-- 另一種配置映射的方法 -->
      <mapper namespace="com.yanxuanshaozhu.mapper.PurchaseMapper">
      <!-- 这里的type是别名-->
      <resultMap id="userPurchase" type="purchase">
      <!-- 内部写手动指定字段和实体的映射关系, column是结果表的列名,property是实体类的属性-->
      <!-- id代表主键, 其他是result-->
      <id column="pid" property="id"></id>
      <result column="time" property="time"></result>
      <result column="total" property="total"></result>
      <!-- 這裏的property書purchase實體類中的屬性名, javaType是User實體類的別名 -->
      <association property="user" javaType="user">
      <id column="uid" property="id"></id>
      <result column="password" property="password"></result>
      <result column="username" property="username"></result>
      <result column="birthday" property="birthday"></result>
      </association>
      </resultMap>
      <select id="findUserPurchase" resultMap="userPurchase">
      select u.id as uid,
      username,
      password,
      birthday,
      p.id as pid,
      time, total
      from user as u,
      purchase as p
      where u.id = p.uid;
      </select>
      </mapper>
    • 一對多模型:如果一個TypeA對應多個TypeB,那麽需要在TypeA的JavaBean中設置一個List<TypeB>的屬性,然後在自定義映射的resultMap中設置collection來配置TypeB的JavaBean和數據庫表的映射
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <!-- 使用了別名 -->
      <resultMap id="userPurchase" type="user">
      <id column="uid" property="id"></id>
      <result column="username" property="username"></result>
      <result column="password" property="password"></result>
      <result column="birthday" property="birthday"></result>
      <!-- 用來配置purchase的映射, property是user的JavaBean中屬性名, ofType是Purchase全限定類名的別名 -->
      <collection property="purchases" ofType="purchase">
      <id column="pid" property="id"></id>
      <result column="time" property="time" ></result>
      <result column="total" property="total" ></result>
      </collection>
      </resultMap>
      <select id="getUserPurchases" resultMap="userPurchase">
      select u.id as uid, username, password, birthday, p.id as pid, time, total
      from user as u, purchase as p
      where u.id = p.uid;
      </select>
      1
      2
      3
      public interface UserMapper {
      List<User> getUserPurchases();
      }
    • 多對多模型:和一對多相比多一個中間表
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <resultMap id="userRoles" type="user">
      <id column="uid" property="id"></id>
      <result column="username" property="username"></result>
      <result column="password" property="password"></result>
      <result column="birthday" property="birthday"></result>
      <collection property="roles" ofType="role">
      <id column="rid" property="rid"></id>
      <result column="rname" property="rname"></result>
      <result column="rdesp" property="rdesp"></result>
      </collection>
      </resultMap>
      <select id="getUserRoles" resultMap="userRoles">
      select u.id as uid, username, password, birthday, r.rid as rid, rname, rdesp
      from user as u,
      userrole as ur,
      role as r
      where u.id = ur.uid
      and ur.rid = r.rid;
      </select>
      1
      2
      3
      public interface UserMapper {
      List<User> getUserRoles();
      }
  6. MyBatis注解開發
    • MyBatis注解開發主要是用來代替Mapper映射文件的編寫
    • 一些注解: @Insert新增, @Update更新, @Delete刪除, @Select查詢, @Results封裝多個結果集代替resultMap標簽), @Result,封裝結果集(代替idresult標簽) @One一對一模型結果集封裝(代替association標簽), @Many一對多結果集封裝(代替collection標簽)
    • 基本增刪改查
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      // 在xxxMapper.java中加入對應方法的注解
      public interface UserMapper {

      @Select("select * from user;")
      List<User> findAll();

      @Select("select * from user where id = #{id};")
      User findById(Integer id);

      @Insert("insert into user (username, password) values (#{username}, #{password});")
      void insert(User user);

      @Update("update user\n" +
      "set username = #{username}, password = #{password} where id = #{id};")
      void updateById(User user);

      @Delete("delete from user where id = #{id};")
      void deleteById(User user);
      }
      1
      2
      3
      4
      5
      <!-- 在核心配置類裏面加載映射關係,然後刪除xxxMapper.xml-->
      <mappers >
      <!-- 設置核心配置文件掃描的包路徑來找到XXXMapper.java文件 -->
      <package name="com.yanxuanshaozhu.mapper"/>
      </mappers>
    • 多表查詢
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public interface PurchaseMapper {
      @Select("select u.id as uid, username, password, birthday, p.id as pid, time ,total from user as u, purchase as p where p.uid = u.id;")
      @Results({
      // 這裏的user就是上面sql語句中的user
      @Result( column = "uid", property = "user.id"),
      @Result( column = "username", property = "user.username"),
      @Result( column = "password", property = "user.password"),
      @Result( column = "birthday", property = "user.birthday"),
      // 使用id = true代表這個是結果表的主鍵
      @Result( id = true, column = "pid", property = "id"),
      @Result( column = "time", property = "time"),
      @Result( column = "total", property = "total")
      })
      List<Purchase> findAll();
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      // 使用類似association標簽的寫法
      public interface PurchaseMapper {
      @Select("select u.id as uid, username, password, birthday, p.id as pid, time, total from purchase as p join user as u on p.uid = u.id;")
      @Results({
      // 使用id = true代表這個是結果表的主鍵
      @Result(id = true, column = "pid", property = "id"),
      @Result(column = "time", property = "time"),
      @Result(column = "total", property = "total"),
      @Result(
      property = "user", // Purchase中的User屬性的名稱
      column = "uid", // 按照哪列名進行查詢
      one = @One(select = "com.yanxuanshaozhu.mapper.UserMapper.findById"), // 調用接口中的類來查詢
      javaType = User.class // 返回結果的類型

      )
      })
      List<Purchase> findAll();
      }

MyBatisPlus

  1. 介紹
    • MyBatis-Plus(MP)是一個MyBatis增强工具,簡化開發,提高效率
    • 特點
      • 只做增强,不做改變:不影響使用MyBatis的項目
      • 效率高:只需要簡單配置,就可以快速地進行CRUD操作
      • 豐富功能:代碼生成,分頁,性能分析功能都包含在内
    • Maven
      1
      2
      3
      4
      5
      6
      <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus -->
      <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus</artifactId>
      <version>3.5.2</version>
      </dependency>
  2. 配置MP的方法
    • Mybatis配置MP
      • pom.xml中加入MP的依賴
      • dom.XXX類中加入映射的數據庫表的注解@TableName("數據庫的表名")
      • XXXMapper繼承BaseMapper<XXX>, 這樣就可以使用MP中的自帶方法了
      • 修改new SqlSessionFactoryBuilder()new SqlSessionFactoryBuilder()
      • 使用MP中的方法進行搜索,比如mapper.selectList(null)等價於自定義的mapper.findAll()
    • Spring配置MP
      • 把Mybatis配置類注入SqlSessionFactory的方法改成注入MybatisSqlSessionFactoryBean的方法
        1
        2
        3
        4
        5
        6
        7
        8
        @Bean
        public SqlSessionFactory MyBatisSqlSessionFactoryBean(@Autowired DataSource ds) throws Exception {

        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(ds);
        bean.setTypeAliasesPackage("com.yanxuanshaozhu.domain.Account");
        return bean.getObject();
        }
  3. 來自BaseMapper的通用CRUD操作

SSM框架整合

  1. 基本整合
    • 注意一个问题:在jsp文件中使用${pageContext.request.contextPath}拿到项目虚拟目录的时候,要注意有这个<%@ page isELIgnored="false"%>,比如idea自动生成的jsp就没有,会导致EL表达式不解析,然后路径报错
    • 注意另一個問題:如果你的JUnit test報錯的話,刪掉@EnableWebMvc,然後再執行就可以了......
    • Spring整合MyBatis:把MyBatis核心配置文件刪除,使用純注解的方式讓Spring自動注入相關配置
      • 建立JdbcConfig.java配置類,配置DataSource
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        // 從properties配置文件讀取内容
        @PropertySource("classpath:/com/yanxuanshaozhu/properties/db.properties")
        public class JdbcConfig {
        // 把properties文件的内容注入到變量中
        @Value("${driver}")
        private String driver;
        @Value("${url}")
        private String url;
        @Value("${user}")
        private String user;
        @Value("${password}")
        private String password;

        // 配置DruidDataSource
        @Bean
        public DataSource getDataSource() throws PropertyVetoException, SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
        }
        }
      • 建立MyBatisConfig.java類,配置SqlSessionFactoryMapperScannerConfigurer
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        public class MyBatisConfig {

        // 配置SqlSessionFactory,從而可以讓Spring注入XXXMapper
        @Bean
        public SqlSessionFactory sqlSessionFactory(@Autowired DataSource ds) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(ds);
        // 設置之後可以不用全限定類名,而是直接使用類名
        sqlSessionFactoryBean.setTypeAliasesPackage("com.yanxuanshaozhu.domain.Account");
        return sqlSessionFactoryBean.getObject();
        }

        // 配置MapperScannerConfigurer,指定掃描XXXMapper.java所在的包得到Mapper
        @Bean
        public MapperScannerConfigurer getMapperScannerConfigurer() {
        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
        configurer.setBasePackage("com.yanxuanshaozhu.mapper");
        return configurer;
        }
        }
      • SpringConfiguration.java中導入上述兩個類

SpringBoot

SpringBoot基礎

尚硅谷Spring6

  1. How an object is created in Spring using XML configuration
    • Load the bean.xml file
    • Analyze the xml file
    • Get the id and class attribute from the bean tag
    • Use reflection to create the instance
      1
      2
      Class clazz = class.forName("fullClassName");
      User user = (User)clazz.getDeclaredConstructor().newInstance();
    • The created objects are stored inside the Map<String, BeanDefinition>
    • Use context.getBean(idName) to get the objects
  2. Log4j2 log framework
    • Include dependencies in pom.xml,create log4j2.xml to control log format
  3. IOC
    • IOC container: contains map of beans
    • Use BeanDefinitionReader to get BeanDefinition
    • Use BeanFactory(spring internal interface, developer should use the ApplicationContext interface and its implementing classes instead) and Reflection to instantiate beans
    • Bean is then initialized
    • Use context.getBean(idName) to get bean
  4. DI(Dependency injection)
    • When creating beans, inject their dependencies using configurations
    • How to
      • Use set method
      • Use constructor
  5. IOC: XML-based configuration
    • Get bean object
      1
      2
      3
      4
      5
      6
      7
      // by id
      User u1 = (User)context.getBean("beanId");
      // by type: cannot have multiple beans with the same class, if user is an interface
      // and it has one implementing class, polymorphism works here
      User u2 = context.getBean(User.class);
      // by id and type
      User u3 = context.getBean("beanId", User.class);
    • DI via setter/constructor
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <!-- via setter, this requires the constructor with no arguments -->
      <bean id="book-setter" class="com.yanxuanshaozhu.book.Book">
      <property name="name" value="name1"></property>
      <property name="author" value="author1"></property>
      <property name="author">
      <!-- Use <null></null> or <null/> for null values -->
      <null/>
      </property>

      </bean>
      <!-- via constructor, this requires the constructor with the corresponding arguments -->
      <bean id="book-constructor" class="com.yanxuanshaozhu.book.Book">
      <constructor-arg name="name" value="name2"></constructor-arg>
      <constructor-arg name="author" value="author2"></constructor-arg>
      </bean>
    • DI with object
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      <!-- via referencing collaborator bean -->
      <bean id="dept1" class="com.yanxuanshaozhu.dept.Department">
      <property name="name" value="department1"></property>
      </bean>
      <bean id="e1" class="com.yanxuanshaozhu.dept.Employee">
      <!-- the value of ref is the id of a collaborator bean -->
      <property name="dept" ref="dept1"></property>
      </bean>

      <!-- via using inner bean -->
      <bean id="e2" class="com.yanxuanshaozhu.dept.Employee">
      <property name="dept">
      <bean id="dept2" class="com.yanxuanshaozhu.dept.Department">
      <property name="name" value="department2"></property>
      </bean>
      </property>
      </bean>

      <!-- via compound property names -->
      <bean id="e3" class="com.yanxuanshaozhu.dept.Employee">
      <property name="dept" ref="dept1"></property>
      <!-- name is a field of in the Department class -->
      <property name="dept.name" value="department3"></property>
      </bean>
    • DI with array/collection
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <!-- DI for array -->
      <bean id="e4" class="com.yanxuanshaozhu.dept.Employee">
      <property name="hobbies">
      <array>
      <value>H1</value>
      <value>H2</value>
      <value>H3</value>
      </array>
      </property>
      </bean>

      <!-- DI for object list -->
      <bean id="dept5" class="com.yanxuanshaozhu.dept.Department">
      <property name="employees">
      <list>
      <!-- the value of bean is the id of a collaborator bean -->
      <ref bean="e1"></ref>
      <ref bean="e2"></ref>
      </list>
      </property>
      </bean>

      <!-- DI for map -->
      <bean id="stu1" class="com.yanxuanshaozhu.school.Student">
      <property name="teachers">
      <map>
      <!-- if the entry value is a built-in object, use value="xxx" -->
      <entry key="1" value-ref="t1" />
      <entry key="2" value-ref="t2" />
      </map>
      </property>
      </bean>
    • DI using other methods
      • Use util for collections(util:list, util:map, etc.), this requires modification of the XML namespace
      • Use p:attribute = "value", p:attribute-ref="beanId" for DI, this requires modification ox the XML namespace
    • Import external properties file(database configuration, data source)
      • Include the context namespace
        1
        2
        3
        4
        <beans 
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd"/>
      • Use context tag to import the properties file
        1
        <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
      • Configure C3p0 dataSource in the XML file
        1
        2
        3
        4
        5
        6
        7
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- value is the key in the jdbc.properties file -->
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        </bean>
    • Scope of bean
      • Singleton: bean is created when the IOC container is initialized
      • Prototype: bean is created when it is required
      • Usage: <bean scope="singleton/prototype"></bean>
    • FactoryBean
      • Can be used to encapsulate object construction logic in class, often are used to incorporate with other frameworks
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        public class MyFactoryBean implements FactoryBean<User> {
        @Override
        public User getObject() throws Exception {
        return new User();
        }

        @Override
        public Class<?> getObjectType() {
        return User.class;
        }
        }
    • Autowire configuration
      • For the controller, service, dao framework, let spring to initialize fields automatically
      • autowire="byName/byType/constructor/no"(if byName is used, the bean id must be the same as the filed name), it works fine if a bean is matched, if no bean is matched, the corresponding field will be null, if multiple bean are matched, an error will be raised
      • Example
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        public class UserControllerImpl implements UserController {
        // The getter and setter should be generated
        private UserService userService;

        public UserService getUserService() {
        return userService;
        }

        public void setUserService(UserService userService) {
        this.userService = userService;
        }

        @Override
        public void run() {
        userService.run();
        System.out.println("UserControllerImpl is called");
        }
        }
        1
        2
        3
        4
        <!-- Include the autowire="byType" -->
        <bean id="userController" class="com.yanxuanshaozhu.csdarchitecture.controller.UserControllerImpl" autowire="byType"></bean>
        <bean id="userService" class="com.yanxuanshaozhu.csdarchitecture.service.UserServiceImpl" autowire="byType"></bean>
        <bean id="userDAO" class="com.yanxuanshaozhu.csdarchitecture.dao.UserDAOImpl" autowire="byType"></bean>
  6. IOC: Annotation-based configuration
    • Steps:
      1. Import dependencies: include the context namespace
      2. Enable component scanning: Spring will scan the base package and its children packages, whenever a class is annoated with @Component, it will be included in the IOC container
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        <context:component-scan base-package="com.yanxuanshaozhu">
        <!-- The following filters are excluded from the IOC container -->
        <!-- Exclude by qualified class name -->
        <context:exclude-filter type="assignable" expression="com.yanxuanshaozhu.annotation.User"/>
        <!-- Exclude by annotation type -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!-- Only scan the following filters -->
        <!-- Include by qualified class name -->
        <context:include-filter type="assignable" expression="com.yanxuanshaozhu.annotation.User"/>
        <!-- Include by annotation type -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        </context:component-scan>
      3. Use annotation to define beans: @Component(value="")(value is the same as the bean id in the XML configuration), @Repository, @Service, @Controller
      4. DI: @Autowired, @Qualified, @Resource
    • You can use both pure annotation configuration development or combine annotation and XML file together in development
    • @Autowired
      • By default, autowire uses match byType
      • Can be used on the constructor, setter method, method parameter, field, annotation
      • Use Autowired for field
        • Directly annotate the field, no getter or setter is needed for the field, this approach is not recommended(Field injection is not recommended, because the list of required dependencies are unclear during instance creation. )
        • Create a setter method for the field and annotate the setter method
        • Create a constructor with the field as the parameter and annotate the constructor
        • Create a constructor with the field as the parameter and annotate the parameter
        • If you only have a constructor with parameters, Autowired can be omitted
        • You can use Autowired and Qualified together to enable match byName: @Qualified(value="fieldName")
    • @Resource
      • By default, autowire uses match byBame(@Resource(name="xxx") the name attribute should match the value attribute of another bean, for example @Service("xxx")), if name is not assigned, match by fieldName(the field name xxx annotated with @Resource should match the value attribute of another bean, for example @Service("xxx")), then byType
      • Can be used on the field and setter method
      • For JDK higher than 11, you should include the following dependencies, DO NOT use the Javax Annotation API, USE the Jakarta Annotations API
        1
        2
        3
        4
        5
        6
        <!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
        <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
        </dependency>
    • Pure annotation development without the XML configuration file
      1
      2
      3
      4
      5
      @Configuration
      // The ComponentScan is a supplement of the XML context:component-scan tag
      @ComponentScan("com.yanxuanshaozhu")
      public class SpringConfig {
      }
  7. AOP
    • Delegation: delegate and the original object. Spring AOP is based on dynamic delegation
      • An example of dynamic delegation
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        public class ProxyFactory {

        // The target object to be delegated
        private Object target;

        public ProxyFactory(Object target) {
        this.target = target;
        }

        public Object getProxy() {

        ClassLoader classLoader = target.getClass().getClassLoader();
        // Interfaces implemented by the target class
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // The handler deals with how proxy mimics the target object
        InvocationHandler handler = new InvocationHandler() {

        // Method from the target object with args to be implemented by the proxy
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        // Put other common features here
        return result;
        }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, handler);
        }
        }
      • Dynamic dynamic proxy category
        • JDK dynamic proxy: can only proxy by interface(proxy and target implements the same interfaces)
        • cglib dynamic proxy: can create a proxy by subclassing(proxy becomes the subclass of the target class)
      • AspectJ: AspectJ itself is a static proxy framework, the Spring framework depends on the annotations AspectJ to achieve its dynamic proxy and AOP
    • AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does this by adding additional behavior to existing code without modifying the code itself
    • AOP concepts
      • Joinpoint: a point during the execution of a program, in Spring, joinpoint is always the execution of a program
      • Pointcut: a predicate that matches join points, advices is associated with a pointcut and runs at any join point matched by the pointcut
      • Advice: action taken by an aspect at a particular join point, different types of advice include @Before, @AfterReturning, @AfterThrowing, @After, @Around
      • Aspect: describe how advice is associated with pointcut, it's the central of AOP
    • Annotation-based AOP
      • Steps
        1. Include the additional dependencies: spring-aop, spring-aspects
        2. Create target resources: target interface, target class
        3. Create aspect class that contains the pointcut and advice. This class should be annotated by the @Aspect, @Component annotations
          • Advice annotation syntax: @XXX(value="pointcut expression")
          • Point expression syntax: @Before(value="execution(modifier returnType qualifiedClassName.methodName(argTypes))"), example @Before(value="execution(com.yanxuanshaozhu.calculator.CalculatorImpl.add(int, int))"), use * to match arbitrary type/name, use .. to match arbitrary amount of arguments
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            34
            35
            36
            @Before(value = "execution(public * com.yanxuanshaozhu.annotationaop.CalculatorImpl.*(..))")
            public void beforeMethod(JoinPoint joinPoint) {
            // Get the joinpoint method name
            String methodName = joinPoint.getSignature().getName();
            // Get the jointpoint method arguments
            Object[] args = joinPoint.getArgs();
            System.out.println("Before executed on: " + methodName + " method with arguments: " + Arrays.toString(args));
            }

            @After(value = "execution(public * com.yanxuanshaozhu.annotationaop.CalculatorImpl.*(..))")
            // AfterReturning is executed before the After method, and it can captures the returned value of the joinpoint method
            @AfterReturning(value = "execution(public * *.*(..))", returning = "result")
            public void afterReturningMethod(JoinPoint joinPoint, Object result) {
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
            System.out.println("AfterReturning executed on: " + methodName + " method with arguments: " + Arrays.toString(args) + " and result: " + result);
            }

            @AfterThrowing(value = "execution(public * *.*(..))", throwing = "ex")
            public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
            // Do something here
            }

            @Around(value = "execution(public * *.*(..))")
            public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
            Object result = null;
            try {
            System.out.println("Before");
            result = proceedingJoinPoint.proceed();
            System.out.println("AfterReturning");
            } catch (Throwable a) {
            System.out.println("AfterThrowing");
            } finally {
            System.out.println("After");
            }
            }
          • Reuse pointcut expression to reduce redundancy
            1
            2
            3
            4
            5
            6
            7
            @Pointcut(value="execution(public * *.*(..))")
            public void pointCut() {}

            // Calling the pointCut method is equivalent to calling execution(public * *.*(..))
            // If the pointcut method is defined in another package, use @Before(value="pkgName.className.methodName()")
            @Before(value="pointCut()")
            public void beforeMethod() {}
        4. If you use XML, include aop, context, enable component scan, enable aspectj autoproxy
          1
          2
          3
          4
          <!--    Enable component scan-->
          <context:component-scan base-package="com.yanxuanshaozhu.annotationaop"></context:component-scan>
          <!-- Enable autoproxy in AspectJ-->
          <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    • XML-based AOP
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <!-- Enable component scan-->
      <context:component-scan base-package="com.yanxuanshaozhu.annotationaop"></context:component-scan>
      <!-- Enable autoproxy-->
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      <!-- Configure AOP settings -->
      <aop:config>
      <!-- Configure Aspect Class-->
      <aop:aspect ref="logAspect">
      <aop:pointcut id="pointcut"
      expression="execution(public * com.yanxuanshaozhu.annotationaop.CalculatorImpl.*(..))"/>
      <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
      <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
      <aop:after-returning method="afterReturningMethod" returning="result"
      pointcut-ref="pointcut"></aop:after-returning>
      <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
      <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
      </aop:aspect>
      </aop:config>
  8. Incorporate Junit with Spring
    • You do not need to create the ApplicationContext manually yourself, Spring can do it all for you
    • Include the following dependencies: junit-jupiter-api for JUnit5, junit for JUnit4, spring-test
    • An example for JUnit5 with XML
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @SpringJUnitConfig(locations = "classpath:bean.xml")
      public class SpringTest {
      @Autowired
      private User user;

      @Test
      public void testUser() {
      System.out.println(user);
      }
      }
    • An example for JUnit 5 with configuration class
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @ExtendWith(SpringExtension.class)
      @ContextConfiguration(classes = SpringConfig.class)
      public class SpringTest {
      @Autowired
      private User user;

      @Test
      public void testUser() {
      System.out.println(user);
      }
      }
    • An example for JUnit4 with XML
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @RunWith(SpringJUnit4ClassRunner.class)
      @SpringJUnitConfig(locations = "classpath:bean.xml")
      public class SpringTest {
      @Autowired
      private User user;

      @Test
      public void testUser() {
      System.out.println(user);
      }
      }
    • An example for JUnit5 with configuration class
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(classes = SpringConfig.class)
      public class SpringTest {
      @Autowired
      private User user;

      @Test
      public void testUser() {
      System.out.println(user);
      }
      }
  9. Spring Transaction
    • JdbcTemplate
      • Spring encapsulates JDBC, uses JdbcTemplate to do CRUD on the database
      • Steps for using JdbcTempalte
        1. Include dependencies: spring-jdbc, mysql-connector-java, c3p0/druid
        2. Configure the XML settings
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          <!-- Import the properties file  -->
          <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
          <!-- Create a dataSource -->
          <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="jdbcUrl" value="${jdbc.url}"></property>
          <property name="user" value="${jdbc.user}"></property>
          <property name="password" value="${jdbc.password}"></property>
          <property name="driverClass" value="${jdbc.driver}"></property>
          </bean>
          <!-- Create a jdbcTemplate -->
          <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <property name="dataSource" ref="dataSource"></property>
          </bean>
        3. Basic CRUD
          • For insert, update, delete, simply use the update method
          • For query, use queryForObject, queryForList etc., which need a subclass instance of the interface RowMapper, you can use arrow method or you can use built-int classes
            1
            2
            3
            4
            5
            6
            7
            8
            9
            // Insert
            String insertSql = "insert into student(name, id)values(?, ?);";
            int rows = jdbcTemplate.update(insertSql, "student4", 4);
            // Update
            String updateSql = "update student set name = ? where id = ?;";
            int rows = jdbcTemplate.update(updateSql, "st4", 4);
            // Delete
            String updateSql = "delete from student where id = ?;";
            int rows = jdbcTemplate.update(updateSql, 4);
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            // Query for an object with user-defined mapper
            String querySql = "select * from student where id = ?;";
            Student student = jdbcTemplate.queryForObject(querySql, (rs, rowNum) -> {
            Student tmp = new Student();
            tmp.setId(rs.getInt("id"));
            tmp.setName(rs.getString("name"));
            return tmp;
            }, 1);

            // Query for an object with built-in Mapper, the column names
            // in the database should be the same as the object field names
            String querySql = "select * from student where id = ?;";
            Student student = jdbcTemplate.queryForObject(querySql, new BeanPropertyRowMapper<>(Student.class), 1);

            // Query for a list of objects
            String querySql = "select * from student where id >= ?;";
            List<Student> results = jdbcTemplate.query(querySql, new BeanPropertyRowMapper<>(Student.class), 1);

            // Query for a single value
            String querySql = "select count(*) from student;";
            Integer res = jdbcTemplate.queryForObject(querySql, Integer.class);
    • Transaction
      • Transaction categories
        • Programmatic transaction: developer manually control transactions
        • Declarative transaction: use configuration to let the Spring framework automaticall control the transactions
      • Controller, Service, DAO revisited
        • Controller layer passes the business logic to a specific serviec layer
          1
          2
          3
          public void buyBook(int bookId, int userId) {
          bookService.buyBook(bookId, userId);
          }
        • Service layer decomposes the business logic into interactions with the database
          1
          2
          3
          4
          5
          6
          7
          8
          public void buyBook(int bookId, int userId) {
          // search book price
          int price = bookDAO.getBookPriceById(bookId);
          // book store - 1
          bookDAO.updateBookStorageById(bookId);
          // user balance -= book price
          bookDAO.updateUserBalanceByIdAndPrice(userId, price);
          }
        • DAO layer actually implements methods to interact with the database
          1
          2
          3
          4
          5
          public int getBookPriceById(int bookId) {
          String querySql = "select price from book where id = ?;";
          Integer price = jdbcTemplate.queryForObject(querySql, Integer.class, bookId);
          return price;
          }
      • Declarative transaction using XML and annotation: this requires the tx namespace. Then you can use the @Transactional annotation in the service layer
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        <beans xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
        <!-- Enable component scan -->
        <!-- Configure dataSource -->
        <!-- Configure jdbcTemplate -->
        <!-- Add transaction manager -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- Enable the annotation-based transaction -->
        <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
        </beans>
      • @Transactional()
        • This annotation can be placed on a class or a method, if it annotates a class, all methods inside are required to have the ACID property
        • This annotation can have arguments to indicate timeout(-1 for no timeout, n for n seconds), whether readOnly(if readOnly, then only query is allowed), rollback policy(whether rollback for specific type of errors, for example noRollbackFor = ArithmeticException.class), transaction isolation levels (isolation=xxx), transaction propagation type(propagation=xxx)
      • Declarative transaction using pure annotation configuration
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        @Configuration
        @ComponentScan("com.yanxuanshaozhu.declarativetransaction")
        @EnableTransactionManagement
        public class SpringConfig {

        @Bean
        public DataSource getDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        return dataSource;
        }

        @Bean
        public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
        }

        @Bean
        public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
        }
        }
      • Declarative transaction using pure XML configuration
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        <beans xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
        <!-- Enable component scan -->
        <!-- Configure dataSource -->
        <!-- Configure jdbcTemplate -->
        <!-- Add transaction manager -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- Advice configuration -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attribute>
        <!-- Advice for all get* methods -->
        <tx:method name="get*" read-only="true">
        <!-- Advice for all update* methods -->
        <tx:method name="update*" read-only="false">
        </tx:attribute>
        </tx:advice>
        <!-- Configure pointcut expression -->
        <aop:config>
        <aop:pointcut id="pt" expression="execution(public * *.*.*(..))">
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
        </aop:config>
        </beans>
  10. Resources
  • The Resource interface in the spring framework is more capable for accessing low-level resources than the java.net.URL class
  • Implementing classes: UrlResource, ClassPathResource, FileSystemResource, etc.
  1. i18n internationalization
  • Use properties file to achieve internationalization: len(internationalization) = 20, i + len(18) + n
  • Java internationalization
    • java.util.Locale is used to locate the current user's language settings, java.util.ResourceBundle is used to search the corresponding properties file
    • The properties file naming practice: basename_language_country.properties
      1
      2
      3
      4
      5
      // Suppose we have msg_zh_CN.properties and msg_en_US.properties
      ResourceBundle bundle1 = ResourceBundle.getBundle("msg", new Locale("zh", "CN"));
      System.out.println(bundle1.getString("test"));
      ResourceBundle bundle2 = ResourceBundle.getBundle("msg", new Locale("en", "US"));
      System.out.println(bundle2.getString("test"));
  • Spring internationalization using the MessageSource interface
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
    <list>
    <value>msg</value>
    </list>
    </property>
    <property name="defaultEncoding">
    <value>utf-8</value>
    </property>
    </bean>
    1
    2
    3
    4
    5
    6
    7
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    // Use an array to match the placeholders in the properties {0}, {1},...
    Object[] objs = new Object[] {"hao"};
    String value1 = context.getMessage("test", objs, Locale.CHINA);
    String value2 = context.getMessage("test", objs, Locale.US);
    System.out.println(value1);
    System.out.println(value2);
    1
    test=nimen {0}
  1. Validation
  • Validation should not be tied to the web tier and should be easy to localize, and it should be possible to plug in any available validator
  • Validation approaches
    • Implement org.springframework.validation.Validator, and call the implementing class in the code
      1. Include dependencies: hibernate-validator, jakarta.el
      2. Implement the Validator interface, override supports and validate method
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        public class PersonValidator implements Validator {
        @Override
        public boolean supports(Class<?> clazz) {
        return Person.class.equals(clazz);
        }

        @Override
        public void validate(Object target, Errors errors) {
        // name cannot be empty rejectIfEmpty(errors, field name, user-defined errorCode, user-defined error message )
        ValidationUtils.rejectIfEmpty(errors, "name", "name.empty", "Name cannot be null");
        // age >= 0 and age < 150
        Person p = (Person) target;
        if (p.getAge() < 0) {
        errors.rejectValue("age", "age.value.negative", "Age cannot be negative");
        } else if (p.getAge() >= 150) {
        errors.rejectValue("age", "age.value.large", "Age cannot be larger than 150");
        }
        }
        }
      3. Test
        1
        2
        3
        4
        5
        6
        Person person = new Person();
        DataBinder dataBinder = new DataBinder(person);
        dataBinder.setValidator(new PersonValidator());
        dataBinder.validate();
        BindingResult bindingResult = dataBinder.getBindingResult();
        System.out.println(bindingResult.getAllErrors());
    • Use annotation to do validation
      1. Create configure class and configure LocalValidatorFactoryBean
        1
        2
        3
        4
        5
        6
        7
        8
        @Configuration
        @ComponentScan(value = "com.yanxuanshaozhu.annotationapproach")
        public class SpringConfig {
        @Bean
        public LocalValidatorFactoryBean getLocalValidatorFactoryBean() {
        return new LocalValidatorFactoryBean();
        }
        }
      2. Create bean class with getter and setter methods, use annotations to set validation rules
        1
        2
        3
        4
        5
        6
        // other parts are omitted
        @Min(0)
        @Max(150)
        private int age;
        @NotNull
        private String name;
      3. Create validators
        1
        2
        3
        4
        5
        6
        7
        8
        9
        @Service
        public class Validator1 {
        @Autowired
        private Validator validator;
        public boolean validatorByUser(User user) {
        Set<ConstraintViolation<User>> validate = validator.validate(user);
        return validate.isEmpty();
        }
        }
      4. Validate
        1
        2
        3
        4
        5
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        Validator1 validator1 = context.getBean(Validator1.class);
        User user = new User();
        boolean result = validator1.validatorByUser(user);
        System.out.println(result);
    • Use validation based on methods
      1. Create configure class and configure MethodValidationPostProcessor
        1
        2
        3
        4
        @Bean
        public MethodValidationPostProcessor getMethodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
        }
      2. Create bean class with getter and setter methods, use annotations to set validation rules
      3. Create Validators
        1
        2
        3
        4
        5
        6
        7
        @Service
        @Validated
        public class MyService {
        public String testMethod(@NotNull @Valid User user) {
        return user.toString();
        }
        }
      4. Validate
        1
        2
        MyService service = context.getBean(MyService.class);
        System.out.println(service.testMethod());
  1. AOT
  • JIT(just in time): compile bytecode to machine code at runtime, can have large thoughput, can be optimized at runtime, but is low in start and occupies large memory
  • AOT(ahead of time): compile bytecode machine code before runtime, start fast, occupies small proportion of memory, cannot be optimized at runtime, cannot run on multiple platforms
  • Spring uses Graalvm to support AOP, Java HotSpot supports JIT
  • You can use Graalvm native image to use Java AOT

尚硅谷SpringMVC

  1. Introduction
    • MVC framework
      • Model: java bean(beans to store data(database models), beans to handle business logic(Service, Dao)), used to handle data analysis
      • View: html/jsp pages, deal with user interaction and data display
      • Controller, servlet, used to receive and respond to requests
    • SpringMVC
      • A lightweight web framework based on Java MVC model, part of the Spring framework
      • Used for the presentation layer(web and controller)
      • Based on Serverlet, uses DispatcherServlet to handle requests
    • Intro project
      • Add dependencies: spring-webmvc, jakarta.servlet-api, thymeleaf-spring6(according to the official documentation of thymeleaf, thymeleaf-spring 3.1.1 supports spring 6 and servlet api >= 5.0)
      • If the packaging is <war> in your project, the dependencies will be included in the web/WEB-INF/lib folder, with those whose <scope> is provided excluded
      • Properly construct the project so that the web folder and the web/WEB-INF/web.xml is located at the right place
      • Configure web.xml
        • Default configuration: in this way, the xxx-servlet.xml should be created and placed inside web/WEB-INF
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          <servlet>
          <!-- The servlet name xxx should be the same as the file name: xxx-servlet.xml -->
          <servlet-name>SpringMVC</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          </servlet>
          <servlet-mapping>
          <!-- Same as the servlet-name -->
          <servlet-name>SpringMVC</servlet-name>
          <!-- Match /login or .html or .css, does not match .jsp, which is handled by other servlets -->
          <url-pattern>/</url-pattern>
          </servlet-mapping>
        • Custome configuration: in this way, the xxx-servlet.xml should be plaed inside the value of param-value
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          <servlet>
          <servlet-name>SpringMVC</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!-- Configure name and location-->
          <init-param>
          <param-name>contextConfigLocation</param-name>
          <!-- Here you need to create resources/SpringMVC.xml -->
          <param-value>classpath:SpringMVC.xml</param-value>
          </init-param>
          <!-- Servlet by default is initialized when it's accessed for the first time,
          since the DispatcherServlet handles lots staff, it will be very slow. Use this
          setting to initialize the DispatcherServlet when server starts-->
          <load-on-startup>1</load-on-startup>
          </servlet>
          <servlet-mapping>
          <servlet-name>SpringMVC</servlet-name>
          <url-pattern>/</url-pattern>
          </servlet-mapping>
      • Configure view resolver(maps view names to actual views) thymeleaf in the SpringMVC.xml, files under the WEB-INF cannot be accessed directly by the browser, you need to use the thymeleaf viewResolver
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        <!-- This resolver handles html files -->
        <bean id="viewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <property name="order" value="1"></property>
        <property name="characterEncoding" value="UTF-8"></property>
        <property name="templateEngine">
        <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
        <property name="templateResolver">
        <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
        <property name="prefix" value="/WEB-INF/templates/"></property>
        <property name="suffix" value=".html"></property>
        <property name="templateMode" value="HTML"></property>
        <!-- In the video it uses HTML5 as the value, which causes an error when using either JDK 17 or JDK 19 -->
        <!-- <property name="templateMode" value="HTML5"></property>-->
        <property name="characterEncoding" value="UTF-8"></property>
        </bean>
        </property>
        </bean>
        </property>
        </bean>
      • Modify the html file, add the thymeleaf namespace
        1
        <meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
      • Page redirection using thymeleaf
        1
        2
        3
        4
        5
        6
        7
        <!-- th:href let thymeleaf resolve this tag  -->
        <!-- The absolute path starts from localhost:8080, you need to add the application
        context path, which could be found in the Tomcat deployment configuration -->
        <!-- @{/path} will be resolved by thymeleaf to /application-context/path -->
        <!-- This path is actually localhost:8080/application-context/path, here is localhost:8080/applicationContext/target -->
        <!-- Since we already configure the thymeleaf resource, it will render the WEB-INF/templates/target.html page -->
        <a th:href="@{/target}">Go to Target page</a>
        1
        2
        3
        4
        @RequestMapping("/target")
        public String target() {
        return "target";
        }
  2. @RequestMapping()
    • A request mapping value should be unique among all controllers
    • This annotation can be placed on class and on method
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Controller
      // Similar to Express router
      @RequestMapping("/base")
      public class Controller1 {
      // This actually handles localhost:8080/applicationContext/base/detail
      @RequestMapping("/detail")
      public String detail() {
      return "detail";
      }
      }
    • Match URL placeholder(like /user/id in javascript) using the @PathVariable annotation. You can use multiple placeholders in a url
      1
      2
      3
      4
      5
      6
      7
      // If there is a placeholder in the url, the id must be provided,
      // otherewise an error will occur
      @RequestMapping("/user/{id}")
      // The value of pathvariable should be the same as the requestmapping value
      public String profile(@PathVariable("id") Integer id) {
      return "profile";
      }
    • If there is only one value parameter inside the annotation, the value= can be omitted
    • Value can be an array, this means the controller can handles multiple request urls
    • method=RequestMethod.GET/POST/..., default is get method. You can use @GetMapping annotation for get method, etc. If request method is not allowed, an 405 error will be caused
    • paramName request must have parameters, !paramName request cannot have parameters, param=value parameter name and value, param!= value must have parameter and its value cannot be the above value
    • URL parameters in thymeleaf: (k1=v1, k2=v2), the original ?k1=va&k2=v2 also works
    • headerName must contain this header, !headerName cannot contain this header, header=value must contain this header and this value, header!=value must contain this header and its value cannot be the above value
  3. Get request parameters
    • Get parameters using Servlet api(The DispatcherServlet will inject the request parameter for you)
      1
      2
      3
      4
      5
      6
      7
      @RequestMapping("/login")
      // The request object is injected by the DispatcherServlet
      public String loginGet(HttpServletRequest request) {
      String username = request.getParameter("username");
      String password = request.getParameter("password");
      return "success";
      }
    • Get parameters using SpringMVC
      1
      2
      3
      4
      5
      6
      7
      @GetMapping("/login2")
      // As long as the parameter names are the same as the parameter names used
      // in the url, SpringMVC will inject the parameters for you
      public String loginGet(String username, String password) {
      System.out.println(username + "------" + password);
      return "login";
      }
    • For parameters with the same name(like a checkbox), use array to match them in request mapping methods
    • If the request mapping method parameter names are not the same as the url parameter names, you need to use the @RequestParam parameter to specify the url parameter name
      1
      2
      3
      4
      5
      6
      7
      @GetMapping("/login")
      // As long as the parameter names are the same as the parameter names used
      // in the url, SpringMVC will inject the parameters for you
      public String loginGet(@RequestParam("user") String username, @RequestParam("pwd") String password) {
      System.out.println(username + "------" + password);
      return "login";
      }
    • Parameter settings: @RequestParam(value="", required=true/false, defaultValue=""), if the value is not provided or value is an empty string, defaultValue will be used
    • Other annotations
      • @RequestHeader(value, required, defaultValue)
      • @CookieValue(value, required, defaultValue)
    • Use POJO(plain old java object) to get form data, if the form input names are the same as the POJO fields. The POJO class should contain the default constructor for Spring to use reflection to create the POJO instance
      1
      2
      3
      4
      5
      @PostMapping("/login")
      public String login(User user) {
      System.out.println(user);
      return "success";
      }
    • In order to correctly display Chinese, add filter in the xml(Tomcat execution order: listener, filter, servlet)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <filter>
      <filter-name>CharacterEncodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
      <!-- Let request encoding be UTF-8, the name is from the CharacterEncodingFilter class -->
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
      </init-param>
      <init-param>
      <!-- Let response encoding be UTF-8, the name is from the CharacterEncodingFilter class -->
      <param-name>forceResponseEncoding</param-name>
      <param-value>UTF-8</param-value>
      </init-param>
      </filter>
      <filter-mapping>
      <filter-name>CharacterEncodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
      </filter-mapping>
  4. Use domain objects(域对象) to share data
    • Common domain objects: request, session, servlet context
    • You should use the domain object this has the smallest scope but is able to accomplish your task
    • The request domain object
      • Use Servlet api to share data
        1
        2
        3
        4
        5
        @GetMapping("/success")
        public String success(HttpServletRequest request) {
        request.setAttribute("data", "This is the success page");
        return "success";
        }
        1
        2
        3
        <!-- This is the success html, use th:text="${key}" to get 
        the attribute value -->
        <h1 th:text="${data}"></h1>
      • Use SpringMVC ModelAndView to share data, other SpringMVC methods, like Model, Map, ModelMap, all use ModelAndView internally to share data and return view
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // Use model to shared data in the domain object
        // Use view to render view
        @GetMapping("/success2")
        public ModelAndView testModelAndView() {
        ModelAndView mav = new ModelAndView();
        // set shared data
        mav.addObject("dataKey", "dataValue");
        // set view nam
        mav.setViewName("success");
        return mav;
        }
        1
        <h1 th:text="${dataKey}"></h1>
      • Use SpringMVC Model to shared data
        1
        2
        3
        4
        5
        @GetMapping("/success3")
        public String testModel(Model model) {
        model.addAttribute("dataKey", "dataValue");
        return "success";
        }
      • Use Map to share data
        1
        2
        3
        4
        5
        @GetMapping("/success4")
        public String testMap(Map<String, Object> map) {
        map.put("dataKey", "dataValue");
        return "success";
        }
      • Use SpringMVC ModelMap to share data
        1
        2
        3
        4
        5
        @GetMapping("/success4")
        public String testMap(ModelMap modelMap) {
        modelMap.addAttribute("dataKey", "dataValue");
        return "success";
        }
    • The session domain object
      • Use Servlet API to share data
        1
        2
        3
        4
        5
        @GetMapping("/session")
        public String sessionGet(HttpSession session) {
        session.setAttribute("sessionData", "The Page");
        return "success";
        }
        1
        2
        <!-- Use session.key to get its corresponding value  -->
        <h1 th:text="${session.sessionData}"></h1>
      • You can also use the @SessionAttributes annotation
    • The application(servlet context) domain object
      • Use Servlet API to share data
        1
        2
        3
        4
        5
        6
        @GetMapping("/application")
        public String getServletContext(HttpSession session) {
        ServletContext servletContext = session.getServletContext();
        servletContext.setAttribute("applicationKey", "The page");
        return "success";
        }
        1
        2
        <!-- Use application.key to get its corresponding value  -->
        <h1 th:text="${application.applicationKey}"></h1>
  5. SpringMVC View
    • SpringMVC has different views, InternalResourceView is used for forwarding, RedirectView is used for redirecting
    • If a view name is resolved by Thymeleaf view resolver, the view is ThymeleafView. This happens when the view name has no prefix
    • If a view name has prefix forward:/URL, then it's an InternalResourceView and will be forwarded and then resolved by the mapping that handles /URL
      1
      2
      3
      4
      5
      6
      // The URL is localhost:8080/applicationContext/forwardTest
      // The content is the view resolved by the /getThymeleafView mapping
      @GetMapping("/forwardTest")
      public String testForwardView() {
      return "forward:/getThymeleafView";
      }
    • If a view name has prefix redirect:/URL, then it's an RedirectView and will be redirected and the resolved by the mapping that handles /URL
      1
      2
      3
      4
      5
      6
      // The URL is localhost:8080/applicationContext/getThymeleafView
      // The content is the view resolved by the /getThymeleafView mapping
      @GetMapping("/redirectTest")
      public String testRedirectView() {
      return "redirect:/getThymeleafView";
      }
    • If you only need a render a view without including any other program logic, you can use the <mvc:view-controller> in the xml file. Meantime, this view-controller configuration will disable all annotation based request mappings. You need to include <mvc: annotation-driven> to re-enable those mappings.
      1
      2
      3
      <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
      <!-- This tag has lots features, for example, render static resources, convert pojo to jsons, etc. -->
      <mvc:annotation-driven />
    • Use SpringMVC to render JSP views
      1
      2
      3
      4
      5
      <!-- In the springMVC.xml -->
      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/templates"></property>
      <property name="suffix" value=".jsp"></property>
      </bean>
  6. RESTFul
    • REST: representation state transfer
    • Post for new, put for update
    • An example
      • /user get all users
      • /user/id get a user by id
      • /user post a new user
      • /user/id delete a user by id
      • /user update a user by id
    • For get and post request, no additional effort is needed. For put and delete request, a filter needs to be added to the spring configuration xml(from the source code, this filter basically converts POST method to other method types and create a new request based on the new method). Meantime, the form method type should be POST, an input with name _method and a specific method type as value for the requst should be provided. This filter should be placed after the CharacterEncodingFilter:
      1
      2
      3
      4
      5
      6
      7
      8
      <filter>
      <filter-name>HiddenHttpMethodFilter</filter-name>
      <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
      </filter>
      <filter-mapping>
      <filter-name>HiddenHttpMethodFilter</filter-name>
      <url-pattern>/*</url-pattern>
      </filter-mapping>
      1
      2
      3
      <form>
      <input type="hidden" name="_method" value="PUT">
      </form>
    • Iteration in thymeleaf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <!-- th:each="item: ${dataFromRequest}" -->
      <tr th:each="employee : ${employees}">
      <td th:text="${employee.id}"></td>
      <td th:text="${employee.name}"></td>
      <td>
      <!-- Remember this twp path concatenation formats -->
      <a th:href="@{'/employee/'+ ${employee.id}}">Delete</a>
      <a th:href="@{/employee/} + ${employee.id}">Delete</a>
      </td>
      </tr>
    • PUT request
      1
      <a th:href="@{'/employee/'+${employee.id}}">update</a>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <form th:action="@{/employee}" method="POST">
      <input type="hidden" name="_method" value="PUT">
      <input type="hidden" name="id" th:value="${employee.id}">
      lastName: <input type="text" name="lastName" th:value="${employee.lastName}"><br>
      email: <input type="text" name="email" th:value="${employee.email}"><br>
      gender: <input type="radio" name="gender" value="1" th:field="${employee.gender}">male
      <input type="radio" name="gender" value="0" th:field="${employee.gender}">female<br>
      <button type="submit">Update</button>
      </form>
    • DELETE request via anchor link:prevent anchor linkd default behavior, let it instead submit a form with hidden input to change request method. If you want to enable access to static resources like css, js, etc. Add <mvc:default-servlet-handler> to the configufation xml.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // get all anchors, add click event, change the action of form
      document.querySelectorAll(".deleteEmployee").forEach((anchor) => {
      anchor.addEventListener('click' ,(event) => {
      event.preventDefault();
      let form = document.querySelector("#delete_form");
      form.setAttribute('action', anchor.getAttribute("href"));
      form.submit();
      });
      })
  7. HttpMessageConverter
    • HttpMessageConverter is a strategy interface that converts request to object representation and from object representation to response
    • Two annotations: @RequestBody, @ResponseBody, two types: RequestEntity, ResponseEntity
    • Use @RequestBody to get request body
      1
      2
      3
      4
      5
      @RequestMapping(value = "/requestBody", method = RequestMethod.POST)
      public String testRequestBody(@RequestBody String requestBody) {
      System.out.println(requestBody);
      return "redirect:/success";
      }
    • Use RequestEntity to get the whole request entity
      1
      2
      3
      4
      5
      6
      @RequestMapping(value = "/requestEntity", method = RequestMethod.POST)
      public String testRequestEntity(RequestEntity<String> requestEntity) {
      System.out.println(requestEntity.getHeaders());
      System.out.println(requestEntity.getBody());
      return "redirect:/success";
      }
    • Use vanilla HttpServletResponse to respond data
      1
      2
      3
      4
      @RequestMapping(value = "/servletResponse", method = RequestMethod.GET)
      public void servletResponse(HttpServletResponse response) throws IOException {
      response.getWriter().write("Http Servlet Response!!!!");
      }
    • Use @ResponseBody to set response body, the return value of a method will be set as the response body
      1
      2
      3
      4
      5
      @RequestMapping("/responseBody")
      @ResponseBody
      public String testResponseBody() {
      return "success";
      }
    • Use @ResponseBody to respond Java object. This annotation by default can only set String into respond body. You need to following dependency to automatically convert Java object to JSON in response. And also enable <mvc:annotation-driven /> in the configuration xml. Then the package will generate MappingJackson2HttpMessageConverter to convert Java Object to JSON String in response.
      1
      2
      3
      4
      5
      <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.15.1</version>
      </dependency>
      1
      2
      3
      4
      5
      6
      7
      8
      @RequestMapping("/objectResponse")
      @ResponseBody
      public User testObjectResponse() {
      User user = new User();
      user.setName("username");
      user.setEmail("user@gmail.com");
      return user;
      }
    • @RestController = @Controller on the class + @ResponseBody on each method inside the class
    • Use ResponseEntity to download a specific file. This requires <mvc:default-servlet-handler> in the configuration xml to access the static file
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      @RequestMapping("/downloadFile")
      public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
      // Get servlet context
      ServletContext servletContext = session.getServletContext();
      // Get file real path on the server
      // The files is /webapp/static/img/gallery.png
      String realPath = servletContext.getRealPath("/static/img/gallery.png");
      // Create input stream
      InputStream is = new FileInputStream(realPath);
      // Create byte array to contain the file
      byte[] bytes = new byte[is.available()];
      // Read file into the byte array
      is.read(bytes);
      // Create response header
      MultiValueMap<String, String> headers = new HttpHeaders();
      // Create download method and file path
      headers.add("Content-Disposition", "attachment;filename=gallery.png");
      // Create response status code
      HttpStatus statusCode = HttpStatus.OK;
      // Create response enetity
      ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
      // Close the stream
      is.close();
      return responseEntity;
      }
    • File upload, include the commons-fileupload dependency, include resolved for MultipartFile: CommonsMultipartResolver. For Spring >= 6.0, commons-fileupload is not supported and you can user another internal resolver.
      1
      2
      3
      4
      5
      6
      <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
      <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.5</version>
      </dependency>
      1
      <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
      1
      2
      3
      4
      5
      6
      <!-- For file upload, method should be POST and enctype should be  multipart/form-data. 
      This will convert form data to binary data so that a file could be uploaded-->
      <form th:action="@{/uploadFile}" method="POST" enctype="multipart/form-data">
      <input type="file" name="photo"><br>
      <button type="submit">Submit</button>
      </form>
  8. Incepter
    • Request handling process: filter -> DispatcherServlet -> intercepter:preHandle -> controller(requestMappingHandler) -> intercepter:postHandle -> render view -> intercepter:afterCompletion
    • Interceptor Steps
      1. Create a class that implements HandlerInterceptor and overrides 3 methods
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        import jakarta.servlet.http.HttpServletRequest;
        import jakarta.servlet.http.HttpServletResponse;
        import org.springframework.web.servlet.HandlerInterceptor;
        import org.springframework.web.servlet.ModelAndView;

        public class MyInterceptor implements HandlerInterceptor {
        // If return false, block controller execution, else continue to execute
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
        }

        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
        }
        }
      2. Add the conceptor in the configuration file
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
           <!-- This has effect on all request mappings  -->
        <mvc:interceptors>
        <bean class="com.yanxuanshaozhu.ic.MyInterceptor"></bean>
        </mvc:interceptors>
        <!-- This has effect on all request mappings -->
        <!-- Need to annotate the interceptor class with @Component to make it a bean,
        make sure this interceptor bean is scanned -->
        <mvc:interceptors>
        <ref bean="myInterceptor"></ref>
        </mvc:interceptors>
        <!-- Customize interceptor url -->
        <mvc:interceptor>
        <!-- /** maps all request mappings, /* does not map all -->
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/test"/>
        <bean class="com.yanxuanshaozhu.ic.MyInterceptor"></bean>
        </mvc:interceptor>
    • Execution order of multiple interceptors
      • preHandle executes in the order of definition order in the configuration xml
      • postHandl and afterCompletion executes in the reverse order of definition order in the configuration xml
  9. Exception handler
    • XML-based exception mapping: if exception occurs, jump to a specific page
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="exceptionMappings">
      <props>
      <!-- key is exception qualified name, value is html page name -->
      <prop key="java.lang.ArithmeticException">error</prop>
      </props>
      </property>
      <!-- save the exception to request domain, key is the value here, value
      is the exception -->
      <property name="exceptionAttribute" value="ex"></property>
      </bean>
      1
      <span th:text="${ex}></span>
    • Annotation based configuration: if exception occurs, jump to a specific page
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // Enhanced controller annotation
      @ControllerAdvice
      public class ExceptionController {
      // If exception occurs, add exception to request domain, jump to error page
      @ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
      public String handler(Exception ex, Model model) {
      model.addAttribute("ex", ex);
      return "error";
      }
      }
  10. Annotation based SpringMVC configuration
  • Create a class to replace web.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package com.yanxuanshaozhu.anno;

    import jakarta.servlet.Filter;
    import org.springframework.web.filter.CharacterEncodingFilter;
    import org.springframework.web.filter.HiddenHttpMethodFilter;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

    // Use this to replace web.xml
    public class MyInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // Spring configuration class
    @Override
    protected Class<?>[] getRootConfigClasses() {
    return new Class[]{SpringConfig.class};
    }

    // SpringMVC configuration class
    @Override
    protected Class<?>[] getServletConfigClasses() {
    return new Class[]{SpringMVCConfig.class};
    }

    // Configure DispatcherServlet servlet-mapping
    @Override
    protected String[] getServletMappings() {
    return new String[]{"/"};
    }

    // Configure filters
    @Override
    protected Filter[] getServletFilters() {
    CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
    characterEncodingFilter.setEncoding("UTF-8");
    characterEncodingFilter.setForceResponseEncoding(true);
    HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
    return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
    }
  • Create a class called SpringMVCConfig to replace its xml configuration file. This is a working one based on Spring 6 and Thymeleaf >= 3.1 based on the official documentation of Thymeleaf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    package com.yanxuanshaozhu.anno;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.context.ContextLoader;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.thymeleaf.spring6.SpringTemplateEngine;
    import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
    import org.thymeleaf.spring6.view.ThymeleafViewResolver;
    import org.thymeleaf.templatemode.TemplateMode;

    // This is a configuration class
    @Configuration
    // Enable component-scan
    @ComponentScan("com.yanxuanshaozhu.anno")
    // Enable mvc annotation driven
    @EnableWebMvc
    public class SpringMVCConfig {
    // Config thymeleaf
    @Bean
    // For Thymeleaf version <= 3.0, this method has several changes
    public SpringResourceTemplateResolver templateResolver(){
    WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
    SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
    templateResolver.setApplicationContext(webApplicationContext);
    templateResolver.setPrefix("/WEB-INF/templates/");
    templateResolver.setSuffix(".html");
    templateResolver.setCharacterEncoding("UTF-8");
    templateResolver.setTemplateMode(TemplateMode.HTML);
    return templateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    return templateEngine;
    }

    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
    viewResolver.setCharacterEncoding("UTF-8");
    viewResolver.setTemplateEngine(templateEngine);
    return viewResolver;
    }

    // Configure default servlet handler for static resources
    // This method needs to implement WebMvcConfigurer
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    // enable default servlet handler
    configurer.enable();
    }

    // Configure interceptors
    // This method needs to implement WebMvcConfigurer
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    MyInterceptor myInterceptor = new MyInterceptor();
    // Add interceptor and apply on all urls
    registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/hello");
    }

    // Configure view-controllers
    // This method needs to implement WebMvcConfigurer
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/hello").setViewName("hello");
    }

    // ExceptionHandler
    // This method needs to implement WebMvcConfigurer
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
    Properties properties = new Properties();
    properties.setProperty("java.lang.ArithmeticException", "error");
    resolver.setExceptionMappings(properties);
    resolver.setExceptionAttribute("ex");
    resolvers.add(resolver);
    }
    }

尚硅谷MyBatis

  1. Introduction
    • Based on iBatis, a persistence framework with support for custom SQL, stored proceures and advanced mappings
    • Persistence layer technologies
      • JDBC
      • Hibernate and JPA: no need to write SQL at all,auto framework
      • MyBatis: semi-auto
    • MyBatis configuration file
      • Dependencies:
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
        </dependency>
        <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.13</version>
        </dependency>
      • Configuration XML file and the Mapper XML files
      • Configuration XML: the transactionManager can be set to JDBC or MANAGED. For JDBC, transaction needs to be managed manually, for MANAGED, transaction can be managed by Spring. The child tags inside the configuration should be placed in order: properties -> settings -> typeAliases -> ... -> environments -> ... -> mapers
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
        <configuration>
        <!-- Include the properties file, the file is located inside the resources folder -->
        <properties resource="jdbc.properties"></properties>
        <!-- Create type aliases, so resultType/resultMap etc can be easier -->
        <typeAliases>
        <!-- alias can be omitted, the default alias will be user in all cases -->
        <typeAlias type="com.yanxuanshaozhu.pojo.User" alias="User"></typeAlias>
        </typeAliases>
        <!-- Configure database settings -->
        <environments default="development">
        <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        </dataSource>
        </environment>
        </environments>
        <!-- Include the mapper file -->
        <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
        </mappers>
        </configuration>
      • An example mapper configuration xml file and its corresponding mapper interface
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
        <!-- namespace is the fully-qualified name of the UserMapper-->
        <mapper namespace="com.yanxuanshaozhu.mapper.UserMapper">
        <!-- id is the method name in UserMapper -->
        <insert id="insertUser">
        insert into t_user(null, "admin", "123456", 23, "M", "admin@gmail.com");
        </insert>
        </mapper>
        1
        2
        3
        4
        5
        package com.yanxuanshaozhu.mapper;

        public interface UserMapper {
        int insertUser();
        }
      • A test
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        @Test
        public void testMyBatis() throws IOException {
        // The configuration file is located inside the resources folder
        InputStream is = getClass().getResourceAsStream("/mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // create session and set auto commit to true
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        int result = userMapper.insertUser();
        System.out.println(result);
        }
      • For query statements, you need to provide either resultType or resultMap so that MyBatis can convert a row in the ResultSet into a Java Obejct. If the database column names are the same as the names of fields, using resultType is much simpler. resultType or resultMap is either fully-qualified class name or type alias
        1
        2
        3
        4
        5
        6
        7
        <select id="getUserById" resultType="com.yanxuanshaozhu.pojo.User">
        select * from t_user where id = 1;
        </select>

        <select id="getAllUsers" resultType="com.yanxuanshaozhu.pojo.User" >
        select * from t_user;
        </select>
  2. Use MyBatis to access parameters
    • Two approaches:
      • ${} is string concatenation in essense. For java types that are mapped to characters in SQL, use quotation marks on the variables: '${}'. For #{}, not quotation marks are needed
      • #{} is string interpolation in essense, this will add quotation marks and cannot be used in some situations
    • Single literal parameter
      1
      List<User> getUserBySex(String sex);
      1
      2
      3
      4
      <select id="getUserBySex" resultType="User">
      select * from t_user where sex = '${sex}';
      <!-- select * from t_user where sex = #{sex} -->
      </select>`
    • Multiple literal parameters, use arg0,arg1,..., or param1, param2, .... This is because MyBatis creates a map to store the paremeters
      1
      User checkLogin(String username, String password, String sex);
      1
      2
      3
      4
      5
      6
      7
      8
      <select id="checkLogin" resultType="User">
      <!-- select * from t_user where username='${arg0}' and password = '${arg1}' and sex = '${arg2}'; -->
      select * from t_user where username=#{arg0} and password = #{arg1} and sex = #{arg2};
      </select>
      <select id="checkLogin" resultType="User">
      <!-- select * from t_user where username='${param1}' and password = '${param2}' and sex = '${param3}'; -->
      select * from t_user where username=#{param1} and password = #{param2} and sex = #{param3};
      </select>
    • Multiple literal parameters with map
      1
      User checkLogin(Map<String, Object>);
      1
      2
      3
      4
      5
      <select id="checkLogin" resultType="User">
      <!-- The values depend on the actual map keys in your code-->
      <!-- select * from t_user where username='${username}' and password = '${password}'; -->
      select * from t_user where username=#{username} and password = #{password};
      </select>
      1
      2
      3
      4
      Map<String, Object> map = new HashMap<>();
      map.put("username", "admin");
      map.put("password", "123");
      mapper.checkLogin(map);
    • A POJO parameter
      1
      int insertUser(User user);
      1
      2
      3
      4
      <insert id="insertUser" >
      insert into t_user(id, username, password, age, sex, email)
      values (null, #{username}, #{password}, #{age}, #{sex}, #{email});
      </insert>
    • Use @Param(value) to customize map key names of the MyBatis auto generated map
      1
      User checkLogin(@Param("username") String username, @Param("password") String password);
      1
      2
      3
      <select id="checkLogin" resultType="User">
      select * from t_user where username=#{username} and password = #{password};
      </select>
  3. MyBatis Query
    • Query for POJO
      1
      User getUserById(@Param("id") Integer id);
      1
      2
      3
      4
      5
      <select id="getUserById" resultType="User">
      select *
      from t_user
      where id = #{id};
      </select>
    • Query for list of POJO
      1
      List<User> getAllUsers();
      1
      2
      3
      4
      <select id="getAllUsers" resultType="User">
      select *
      from t_user;
      </select>
    • Query for one value: java.lang.Integer, int, Int, Integer all works, these are built-in type aliases for Integer. For int , use _int, or _integer. Other aliases include string, map, list, arraylist, collection, ...
      1
      int getUserCount();
      1
      2
      3
      4
      <select id="getUserCount" resultType="int">
      select count(1)
      from t_user;
      </select>
    • Convert query result to map: key is field/column name, value is value/column value
      • One result
        1
        Map<String, Object> getUserByIdToMap(@Param("id") Integer id); 
        1
        2
        3
        4
        5
        <select id="getUserByIdToMap" resultType="map">
        select *
        from t_user
        where id = #{id};
        </select>
      • Multiple results
        1
        List<Map<String, Object>> getAllUsersToMap();
        1
        2
        3
        4
        <select id="getAllUsersToMap" resultType="map">
        select *
        from t_user;
        </select>
    • String fuzzy match
      1
      List<User> getUsersByFuzzyNameMatch(@Param("username") String username);
      1
      2
      3
      4
      5
      6
      7
      <select id="getUsersByFuzzyNameMatch" resultType="User">
      select *
      from t_user
      where username like '%${username}%';
      <!-- where username like concat('%', #{username}, '%'); -->
      <!-- where username like "%"#{username}"%"; -->
      </select>
    • Multiple deletion using ${}
      1
      int multipleDeletion(@Param("ids") String ids);
      1
      2
      3
      4
      5
      6
      7
      <delete id="multipleDeletion">
      delete
      from t_user
      where id in (${ids});
      <!-- cannot use #{ids}, because it adds quotation marks and results
      in syntax error in the SQL statement -->
      </delete>
      1
      int result = mapper.multipleDeletion("7,8,9");
    • Dynamic access table name using ${}, cannot use #{}
      1
      List<User> getData(@Param("tblName") String tblName);
      1
      2
      3
      4
      <select id="getData" resultType="User">
      select *
      from ${tblName};
      </select>
    • Get auto_increment keys after insertion
      1
      int getKeyAfterInsertion(User user);
      1
      2
      3
      4
      5
      6
      <!-- key id is used so useGeneratedKeys is true -->
      <!-- the generated will be added to the id property so keyProperty is id -->
      <insert id="getKeyAfterInsertion" useGeneratedKeys="true" keyProperty="id">
      insert into t_user(id, username, password, age, sex, email)
      values (null, #{username}, #{password}, #{age}, #{sex}, #{email});
      </insert>
      1
      2
      3
      4
      User user = new User(null, "user1", "123", 20,"M","user1@gmail.com");
      System.out.println(user.getId()); // null
      int result = mapper.getKeyAfterInsertion(user);
      System.out.println(user.getId()); // an Integer id
  4. Customzie mapping between fields and column names
    • There will be problems when the POJO filed names and the database column names do not match: the corresponding filed values will be their zero-equivalent values.
    • Solution 1: create name alias in sql statements
      1
      2
      3
      4
      5
      6
      <!-- database column name: emp_name -->
      <!-- POJO field name: empName -->
      <select id="getAllEmps" resultType="Emp">
      select eid, emp_name empName, age, sex, did, email
      from t_emp;
      </select>
    • Solution 2: use global settings in the mybatis-config.xml
      1
      2
      3
      4
      <!-- This setting requires column names and fields meet their respective naming requirements and only differ in styling-->
      <settings>
      <setting name="mapUnderscoreToCamelCase" value="true"/>
      </settings>
    • Solution 3: use resultMap
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <resultMap id="empResultMap" type="Emp">
      <!-- Use id for primary key mapping -->
      <id property="id" column="id"></id>
      <!-- Use result for other mappings -->
      <result property="empName" column="emp_name"></result>
      <result property="age" column="age"></result>
      <result property="sex" column="sex"></result>
      <result property="email" column="email"></result>
      </resultMap>
      <!-- Use the result map id -->
      <select id="getAllEmps" resultMap="empResultMap">
      select *
      from t_emp;
      </select>
    • N-to-1 mapping: suppose t_emp table has a did that is a foreign key and maps to the did of t_dept. Then we need to add a Dept field in the Emp class. Here are some examples for handling queries with join operation in this situation
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <!-- Approach 1: pure resultMap -->
      <resultMap id="empWithDept" type="Emp">
      <id property="eid" column="eid"></id>
      <result property="empName" column="emp_name"></result>
      <result property="age" column="age"></result>
      <result property="sex" column="sex"></result>
      <result property="email" column="email"></result>
      <!-- Handle mappings for did and dept_name int the t_dept table -->
      <result property="dept.did" column="did"></result>
      <result property="dept.deptName" column="dept_name"></result>
      </resultMap>

      <select id="getEmpAndDept" resultMap="empWithDept">
      select *
      from t_emp emp,
      t_dept dept
      where emp.did = dept.did
      and emp.eid = #{id};
      </select>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <!-- Approach 2: resultMap with association -->
      <resultMap id="empWithDept2" type="Emp">
      <id property="eid" column="eid"></id>
      <result property="empName" column="emp_name"></result>
      <result property="age" column="age"></result>
      <result property="sex" column="sex"></result>
      <result property="email" column="email"></result>
      <!-- Use association for reference -->
      <association property="dept" javaType="Dept">
      <id property="did" column="did"></id>
      <result property="deptName" column="dept_name"></result>
      </association>
      </resultMap>
      <!-- SQL query for this is the same as the above one -->
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      <!-- Approach 3: query by step -->
      <!-- The following code is inside EmpMapper.xml -->
      <resultMap id="empWithDeptByStep" type="Emp">
      <id property="eid" column="eid"></id>
      <result property="empName" column="emp_name"></result>
      <result property="age" column="age"></result>
      <result property="sex" column="sex"></result>
      <result property="email" column="email"></result>
      <!-- select is the fully-qualified name of the step 2 method, the method will call the select statements in step 2 -->
      <!-- column is the column name in step 1, also the one used in step 2 -->
      <association property="dept" select="com.yanxuanshaozhu.mapper.DeptMapper.getEmpAndDeptStepTwo" column="did">
      <id property="did" column="did"></id>
      <result property="deptName" column="dept_name"></result>
      </association>
      </resultMap>
      <select id="getEmpAndDeptStepOne" resultMap="empWithDeptByStep">
      select *
      from t_emp
      where eid = ${id};
      </select>
      <!-- The following code is inside DeptMapper.xml -->
      <resultMap id="empWithDeptResult" type="Dept">
      <id property="did" column="did"></id>
      <result property="deptName" column="dept_name"></result>
      </resultMap>
      <select id="getEmpAndDeptStepTwo" resultMap="empWithDeptResult">
      select *
      from t_dept
      where did = #{id};
      </select>
    • 1-to-N mapping: suppose t_emp table has a did that is a foreign key and maps to the did of t_dept. Then we need to add a Emps field in the Dept table, which is a list of employees in that department. Here are some examples for handling queries with join operation in this situation
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <!-- Approach 1: resultMap with collection  -->
      <resultMap id="getDeptAndEmp1" type="Dept">
      <id property="did" column="did"></id>
      <result property="deptName" column="dept_name"></result>
      <!-- 1 dept maps to many emps so use collection here -->
      <collection property="emps" ofType="Emp">
      <id property="eid" column="eid"></id>
      <result property="empName" column="emp_name"></result>
      <result property="email" column="email"></result>
      <result property="age" column="age"></result>
      <result property="sex" column="sex"></result>
      </collection>
      </resultMap>
      <select id="getDeptAndEmp" resultMap="getDeptAndEmp1">
      select *
      from t_dept,
      t_emp
      where t_dept.did = t_emp.did
      and t_dept.did = #{did};
      </select>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <!-- Approach 2: query by step  -->
      <!-- The following code is inside DeptMapper.xml -->
      <resultMap id="getDeptAndEmpStepOneMap" type="Dept">
      <id property="did" column="did"></id>
      <result property="deptName" column="dept_name"></result>
      <!-- select is the fully-qualified name of the select id/method name -->
      <!-- column is the column name in step 1, also the one used in step 2 -->
      <collection property="emps" select="com.yanxuanshaozhu.mapper.EmpMapper.getDeptAndEmpStepTwo" column="did">
      </collection>
      </resultMap>
      <select id="getDeptAndEmpStepOne" resultMap="getDeptAndEmpStepOneMap">
      select *
      from t_dept
      where did = #{did};
      </select>
      <!-- The following code is inside EmpMapper.xml -->
      <select id="getDeptAndEmpStepTwo" resultType="Emp">
      select *
      from t_emp
      where did = #{did};
      </select>
  5. Dynamic SQL
    • A series of xml tags used for conditionally concatenating strings of SQL together
    • <if test=""></if> expression
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
            <!-- The resultMap is only used to map emp_name and empName here-->
      <!-- Approach 1: addition a true condition-->
      <select id="getEmpByCondition" resultMap="empResultMap">
      <!-- 1 = 1 is added to make sure when empName is null
      but other fields are not null, the and keyword does not
      cause an error -->
      select * from t_emp where 1 = 1
      <if test="empName != null and empName != ''">
      emp_name = #{empName}
      </if>
      <if test="age != null and age != ''">
      and age = #{age}
      </if>
      <if test="email != null and email != ''">
      and email = #{email}
      </if>
      <if test="sex != null and sex != ''">
      and sex = #{sex}
      </if>
      ;
      </select>
      * `<where></where>` expression
      ```xml
      <!-- Approach 2: using where statement-->
      <select id="getEmpByCondition" resultMap="empResultMap">
      select * from t_emp
      <!-- where can automatically generate the where keyword, and remove the preceding or/and
      keyword when its if statment evaluates to false -->
      <where>
      <if test="empName != null and empName != ''">
      emp_name = #{empName}
      </if>
      <if test="age != null and age != ''">
      and age = #{age}
      </if>
      <if test="email != null and email != ''">
      and email = #{email}
      </if>
      <if test="sex != null and sex != ''">
      and sex = #{sex}
      </if>
      </where>
      ;
      </select>
    • <trim prfix="" suffix="" prefixOverrides="" suffixOverrides=""> expression
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <!-- Approach 3: using trim to customize where-->
      <select id="getEmpByCondition" resultMap="empResultMap">
      select * from t_emp
      <!-- prefix: prefix of trim -->
      <!-- prefixOverrides: if condition evaluates to false, remove words specified here -->
      <trim prefix="where" prefixOverrides="and | or ">
      <if test="empName != null and empName != ''">
      emp_name = #{empName}
      </if>
      <if test="age != null and age != ''">
      and age = #{age}
      </if>
      <if test="email != null and email != ''">
      and email = #{email}
      </if>
      <if test="sex != null and sex != ''">
      and sex = #{sex}
      </if>
      </trim>
      ;
      </select>
    • <choose><when></when><otherwise></otherwise></choose> expression
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      <select id="getEmpByCondition" resultMap="empResultMap">
      select * from t_tmp
      <where>
      <!-- Only one when condition could be met -->
      <!-- If all when conditions are false, go to the otherwise statement -->
      <choose>
      <when test="empName != null and empName != ''">
      emp_name = #{empName}
      </when>
      <when test="age != null and age != ''">
      age = #{age}
      </when>
      <when test="email != null and email != ''">
      email = #{email}
      </when>
      <when test="sex != null and sex != ''">
      and sex = #{sex}
      </when>
      <otherwise>
      did = 1;
      </otherwise>
      </choose>
      </where>
      ;
      </select>
    • <foreach collection="" item="" separator=""></foreach> expression
      1
      int deleteEmpByIds(@Param("ids") Integer[] ids);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <!--  Delete multiple records -->
      <!-- Approach 1: id in (ids) -->
      <delete id="deleteEmpByIds">
      delete from t_emp where eid in
      (<foreach collection="ids" item="id" separator=",">
      #{id}
      </foreach>)
      </delete>
      <!-- Approach 2: id = ? or id = ? -->
      <delete id="deleteEmpByIds">
      delete from t_emp where
      (<foreach collection="ids" item="id" separator="or">
      eid = #{id}
      </foreach>)
      </delete>
      1
      int insertMultipleEmp(@Param("emps") List<Emp> emps);
      1
      2
      3
      4
      5
      6
      7
      <!-- Insert multiple records -->
      <insert id="insertMultipleEmp">
      insert into t_emp values
      <foreach collection="emps" item="emp" separator=",">
      (null, #{emp.empName}, #{emp.age}, #{emp.sex}, #{emp.email}, null)
      </foreach>
      </insert>
    • <sql></sql> expression
      1
      2
      3
      4
      5
      6
      7
      8
      <!-- Defined sql segment for reuse  -->
      <sql id="empColumns">
      eid, emp_name, age, sex, email
      </sql>
      <select id="getEmpByCondition" resultMap="empResultMap">
      <!-- Include sql segments -->
      select <include refid="empColumns"></include> from t_emp
      </select>
  6. Cache in Mybatis
    • Mybatis has two levels of cache: 1st level session cache, 2nd level global cache. With cache, the data is retrieved from the cache and the same sql statement will not be executed again
    • First level cache
      • By default enabled, the data is cached in the sqlSession
      • Situations when 1st level cache fails
        • Different sqlsession
        • Same sqlsession but different query conditions
        • Same sqlsession but execute update/delete inbetween two queries
        • Same sqlsession but manually delete cache (call sqlSession.clearCache()) between two queries
    • Second level cache
      • Steps to enable second level cache
        1. Add the following configurations to the Mybatis core configuration xml. This step can be omited because the default value is already true
          1
          2
          3
          <settings>
          <setting name="cacheEnabled" value="true"/>
          </settings>
        2. Add <cache /> to the maper file where you want to enable 2nd level cache
        3. 2nd level cache is only in effect when the sqlsession is closed or committed, before that data is cached into 1st level cache
        4. The POJO class must implements Serializable
      • Some configurations
        1
        2
        <!-- If flushInterval is not set, 2nd level cache only fails when there are update or deletion -->
        <cache eviction="LRU/FIFO/SOFT/WEAK" flushInterval="milliseconds" size="n" readOnly="true/false"/>
      • Cache query order: 2nd lvl cache -> 1st lvl cache -> query database -> save data into 1st lvl cache -> save data into 2nd lvl cache when sqlSession is closed/comitted
    • You can use a 3rd-party Java-based cache framework EHCACHE to replace Mybatis 2nd lvl cache
      1
      2
      3
      4
      5
      6
      7
      <!-- Dependencies -->
      <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
      <dependency>
      <groupId>org.mybatis.caches</groupId>
      <artifactId>mybatis-ehcache</artifactId>
      <version>1.2.3</version>
      </dependency>
      1
      2
      3
      4
      5
      6
      7
      8
      <!-- In the mapper.xml you want to enable 2nd lvl cache -->
      <cache type="org.mybatis.caches.ehcache.EhcacheCache">
      <property name="timeToIdleSeconds" value="3600"/><!--1 hour-->
      <property name="timeToLiveSeconds" value="3600"/><!--1 hour-->
      <property name="maxEntriesLocalHeap" value="1000"/>
      <property name="maxEntriesLocalDisk" value="10000000"/>
      <property name="memoryStoreEvictionPolicy" value="LRU"/>
      </cache>
  7. MyBatis reverse engineering
    • MyBatis revers enginering: Mybatis automatically generate POJO, Java interface, mapper.xml for you based on pre configurations
    • Steps
      1. Add the following dependicies and plugins
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
              <dependencies>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.13</version>
        </dependency>
        </dependencies>
        <build>
        <plugins>
        <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.4.2</version>
        <dependencies>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
        </dependency>
        </dependencies>
        </plugin>
        </plugins>
        </build>
        ```
        2. Create a `generatorConfig.xml` with code like this. Be aware that this setting will overrite contents of files whose names are the same as those specified in this xml, so you should better use this generator in a separate project
        ```xml
        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
        <generatorConfiguration>
        <!--
        targetRuntime: used to specify whtich version should use for reverse engineering, could be
        MyBatis3Simple: generate basic CRUD, or
        MyBatis3: generate CRUD with conditions
        -->
        <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- Database configuration -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
        connectionURL="jdbc:mysql://localhost:3306/mybatis"
        userId="root"
        password="123456">
        </jdbcConnection>
        <!-- javaBean generator strategy-->
        <javaModelGenerator targetPackage="com.yanxuanshaozhu.mybatis.bean"
        targetProject=".\src\main\java">
        <!-- Specify targetPackage is a multi-layer package -->
        <property name="enableSubPackages" value="true"/>
        <!-- Trim whitespace in database column names -->
        <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- mapper.xml generator strategy -->
        <sqlMapGenerator targetPackage="com.yanxuanshaozhu.mybatis.mapper"
        targetProject=".\src\main\resources">
        <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- mapper.java interface generator strategy -->
        <javaClientGenerator type="XMLMAPPER"
        targetPackage="com.yanxuanshaozhu.mybatis.mapper" targetProject=".\src\main\java">
        <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- Database tables and Java class mappings -->
        <!-- tableName is database table name, if tableName is *, domainObjectName is not needed -->
        <!-- domainObjectName is JavaBean name -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
        </context>
        </generatorConfiguration>
      2. Use mybatis-generator:generate under mybatis-generator in maven settings to generate JavaBeans, mapper interfaces, mapper xml files
  8. Mybatis pagehelper
    • Add a dependency
      1
      2
      3
      4
      5
      6
      <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
      <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.3.2</version>
      </dependency>
    • Add the following page interceptor in mybatis-config.xml
      1
      2
      3
      4
      <plugins>
      <!--Configure page interceptor-->
      <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
      </plugins>
    • How to use
      1
      2
      3
      4
      5
      // Query for the first page with 2 records
      PageHelper.startPage(1, 2); // 1 is pageNum, 2 is pageSize
      // Then the emps only contains the first two records
      List<Emp> emps = mapper.selectAll();
      emps.forEach(emp -> System.out.println(emp));
    • The logics behind: the pagehelper internally modifies the sql statement based on pageNum and pageSize, adding limit and offset keywords

尚硅谷SSM整合

  1. Preparation
    • Dependencies and plugins
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      <dependencies>
      <!-- Spring -->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
      </dependency>
      <!-- SpringMVC -->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
      </dependency>
      <!-- For transaction manager -->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
      </dependency>
      <!-- For JSON data -->
      <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
      <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.15.1</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>${spring.version}</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
      <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.4.0</version>
      <scope>test</scope>
      </dependency>
      <!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
      <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>6.0.0</version>
      <scope>provided</scope>
      </dependency>
      <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.33</version>
      </dependency>
      <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.5</version>
      </dependency>
      <!-- Mybatis-->
      <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
      </dependency>
      <!-- Mybatis-spring: Mybatis >= 3.5, Spring >= 6.0, Java >= Java 17 -->
      <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>3.0.0</version>
      </dependency>
      <!-- MyBatis PageHelper -->
      <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
      <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.3.2</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
      <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.9.2</version>
      <scope>test</scope>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring6 -->
      <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf-spring6</artifactId>
      <version>3.1.1.RELEASE</version>
      </dependency>
      </dependencies>
      <build>
      <plugins>
      <plugin>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.4.2</version>
      <dependencies>
      <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.5</version>
      </dependency>
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.33</version>
      </dependency>
      </dependencies>
      </plugin>
      </plugins>
      </build>
    • web.xml configuration(Tomcat execution order: listener -> filter -> servlet)
      • SPringMVC IOC container is created when DispatcherServlet is initialized, it manages controller layer and depends on service layer
      • Spring IOC container manages service layer. The Autowired service is injected into controller when SpringMVC IOC container is created, which means the service should have already been created by the Spring IOC container. Thus the Spring IOC container should be created before the SpringMVC IOC container, creataion of Spring IOC container can be done using the ServletContextListener
      • SpringMVC IOC container is the sub-container of the Spring IOC container, it can access all beans defined in the Spring IOC container, but Spring IOC container cannot access beans only defined in the SpringMVC IOC container
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        <?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, load Spring configuration on startup-->
        <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>

        <!-- Spring configuration file name and location-->
        <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
        </context-param>

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

        <!-- Filter for handling put/delete request -->
        <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
        </filter>
        <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
        </filter-mapping>

        <!-- SpringMVC DispatcherServlet -->
        <servlet>
        <servlet-name>ssmDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
        <servlet-name>ssmDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
        </servlet-mapping>
        </web-app>
    • Configure springmvc.xml: component-scan, annotation-driven, view-controller, Thymeleaf settings, default-servlet-handler(for static resources)
    • Congigure spring.xml: component-scan (exclude-filter for controller), dataSource bean with properties file included (context:property-placeholder), transactionManager,...
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="jdbcUrl" value="${jdbc.url}"></property>
      <property name="user" value="${jdbc.user}"></property>
      <property name="password" value="${jdbc.password}"></property>
      <property name="driverClass" value="${jdbc.driver}"></property>
      </bean>
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"></property>
      </bean>
      <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    • Configure Mybatis mybatis-config.xml, XXXMapper.xml, and configure related beans inside spring.xml (with the help of mybatis-spring dependency)
      1. Configure SqlSessionFactoryBean, this bean adds SqlSessionFactory to Spring IOC container
        1
        2
        3
        4
        <!-- Approach 1: only include the mybatis-config.xml file -->
        <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        </bean>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        <!-- Approach 2: configure all settings here, then there's no need to have mybatis-config.xml at all -->
        <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- Configure datasource, refer the datSource bean in spring.xml -->
        <property name="dataSource" ref="dataSource"></property>
        <!-- Configure typeAliasesPackage -->
        <property name="typeAliasesPackage" value="com.yanxuanshaozhu.ssm.pojo"></property>
        <!-- Configure mapper locations: if the location is exactly the same as the locations of maper
        interfaces, this can be omitted -->
        <property name="mapperLocations" value="classpath:com/yanxuanshaozhu/ssm/mapper/*.xml"></property>
        <!-- Configure pageHelper -->
        <property name="plugins">
        <array>
        <bean class="com.github.pagehelper.PageInterceptor"></bean>
        </array>
        </property>
        </bean>
        1
        2
        3
        4
        5
        @Service
        public class EmpServiceImpl implements EmpService{
        @Autowired
        private SqlSessionFactory factory;
        }
      2. Configure MapperScannerConfigurer( this should work together with SqlSessionFactoryBean bean), this creates delegate objects of all mappers inside basePackage, and adds all these delegate objects to Spring IOC container, thus further simplifying the process of creating SqlSession and getting mappers from the SqlSessionFactory
        1
        2
        3
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.yanxuanshaozhu.ssm.mapper"></property>
        </bean>
        1
        2
        3
        4
        5
        @Service
        public class EmpServiceImpl implements EmpService{
        @Autowired
        private EmpMapper mapper;
        }
    • A Thymeleaf paging example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <!-- page is the PageInfo object passed to the current page  -->
      <div style="text-align: center">
      <a th:if="${page.isHasPreviousPage()}" th:href="@{/employee/page/1}">First Page</a>
      <a th:if="${page.isHasPreviousPage()}" th:href="@{'/employee/page/' + ${page.getPrePage()}}">Previous Page</a>
      <span th:each="num : ${page.getNavigatepageNums()}">
      <a th:href="@{'/employee/page/' + ${num}}" th:text="${num}"></a>
      </span>
      <a th:if="${page.isHasNextPage()}" th:href="@{'/employee/page/' + ${page.getNextPage()}}">Next Page</a>
      <a th:if="${page.isHasNextPage()}" th:href="@{'/employee/page/' + ${page.getPages()}}">Last Page</a>
      </div>

尚硅谷SpringBoot

  1. Introduction
    • Spring Boot makes it easy to create stand-alone, production-grade Spring based applications that you can just run.
    • Features
      • Create Spring applications with ease
      • Embed tomcat
      • Provide starter dependencies
      • Automatically configure Spring and 3rd party libraries
      • No need to use XML
      • Spring Boot 3.0 has migrated from Java EE to Jakarta EE APIs for all dependencies
    • Intro-project notice
      • pom.xml settings. The spring-boot-starter-parent has common dependencies included, if you need to modify a specific dependency, find the key and add a property in pom.xml like this
        1
        2
        3
        4
        <!-- Modify dependency example -->
        <properties>
        <mysql.version>8.0.10</mysql.version>
        </properties>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        <!-- pom.xml important settings -->
        <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
        <relativePath/>
        </parent>
        <dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        </dependencies>
      • By default the main class is in a root package above other classes, otherwise there will an error like this: "Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback". Another solution is to add scanned package locations: @SpringBootApplication(scanBasePackages="xxx")
      • To modify application settings, create and modify classpath:application.properties, you can also use classpath:application.yml
      • Spring boot dependency control
        • spring-boot-starter-* are official collections of dependencies used in proper situations
        • *-spring-boot-starter are 3rd party starters
      • Add configuration file to define beans and get beans in the application. Beans defined in SpringBoot are singletons. The Configuration class itself is also a bean.
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        // MyConfiguration.java
        @org.springframework.context.annotation.Configuration
        public class Configuration {
        @Bean(name="userCangweiXiao")
        public User user01() {
        return new User(9, "Cangwei Xiao");
        }

        @Bean(name = "pet01")
        public Pet pet01() {
        return new Pet("pet01");
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // MainApplication.java
        public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        // Get all bean definitions, with a lot built-int bean definitions
        String[] names = run.getBeanDefinitionNames();
        Pet pet01 = run.getBean("pet01", Pet.class);
        User user01 = run.getBean("userCangweiXiao", User.class);
        System.out.println(user01);
        }
    • Use Spring-initializer to initialize a SpringBoo project
  2. Annotations
    • Add components: @Configuration, @Bean, @Component, @Controller, @Service, @rEPOSITORY, @ComponentScan, @Import({XXX.class}) (add components into the container), @Conditional (and subclasses like @ConditionalOnBean, @ConditionalOnMissingBean)
      1
      2
      3
      4
      5
      6
      // user bena is only added to the container when there is already pet bean inside the container
      @ConditionalOnBean(name="pet")
      @Bean("user")
      public User user01() {
      return new User();
      }
    • Add XML-based configurations to SpringBoot configuration
      1
      2
      3
      4
      5
      @ImportResource("classpath:beans.xml")
      @Configuration()
      public class MyConfig {

      }
    • Bind java bean and values inside properties files
      • Approach 1: add the java class to container and set configuration properties
        1
        2
        3
        4
        5
        6
        @Component
        @ConfigurationProperties(prefix = "car")
        public class Car {
        private String brand;
        private Integer price;
        }
        1
        2
        car.brand="Porsche"
        car.price=10222
      • Approach 2: let the configuration class know that a specific java class should be added to container
        1
        2
        3
        @EnableConfigurationProperties(Car.class)
        public class MyConfig {
        }
        1
        2
        3
        4
        5
        @ConfigurationProperties(prefix = "car")
        public class Car {
        private String brand;
        private Integer price;
        }
    • Analysis of @SpringBootApplication = @SpringBootConfiguration(=@Configuration) + @ComponentScan + @EnableAutoConfiguration(register packages to conatiner in batch)
    • Use debug=true in application.properties to view auto configuration report
    • Lombok annotations to facilitate development: @Data for getter and setter, @AllArgsConstructor, @NoArgsConstructor@ToString. Notice Lombok suports JDK 17 (reqired for SpringBoot 3) since version 1.8.22
  3. SpringBoot Web Development
    • For most cases, SpringBoot provides auto-configuration of SpringMVC
    • Access Static resources:
      • By default, /static, /public, /resources, /META-INF/resources directories inside classpath can hold static resources. These resourecs can be accessed from url:port/project/ + resource name. Processing order: controller -> default controller for static resources. Here's setting to change the default static resource location to classpath:/sr/
        1
        2
        3
        spring:
        resources:
        static-locations: classpath: /sr/
      • Default request url for static resource is: url:port/project/ + resource name, here's the configuration for changing the url to: url:port/project/resources/ + resource name
        1
        2
        3
        spring:
        mvc:
        static-path-pattern: /resources/**
    • Welcome page: index.html under static resource folder, or a index templage can be detacted by SpringBoot as the welcome page
    • Favicon: favicon.ico under static resource folder can be detacted by SpringBoot as the favicon
    • Restful request: SpringBoot provides auto-configuration for HiddenHttpMethodFilter if spring.mvc.hiddenmethod.filter.enabled is provided and its value is true
      1
      2
      3
      4
      5
      6
      7
      // related source code
      @Bean
      @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
      @ConditionalOnProperty(
      prefix = "spring.mvc.hiddenmethod.filter",
      name = {"enabled"}
      )
      1
      2
      3
      4
      5
      spring:
      mvc:
      hiddenmethod:
      filter:
      enabled: true
      1
      2
      <!-- How to change the method from post to delete in form -->
      <input type="hidden" name="_method" value="DELETE">
      1
      2
      3
      4
      5
      6
      7
      // Customize the _method: change from _method to realMethod here
      @Bean
      public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
      HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
      filter.setMethodParam("realMethod");
      return filter;
      }
      1
      2
      <!-- Customize the _method: change from _method to realMethod here -->
      <input type="hidden" name="realMethod" value="DELETE">
    • Common request annotations and parameter types: @RequestParam, @PathVariable, @RequestHeader, @ModelAttribute, @RequestBody, ..., HttpServletRequest, MultipartRequest, HttpSession, Model, ModelAndView, ...
    • Respond JSON data
      • spring-boot-starter-web includes JSON related dependencies like jackson
      • Use @ResponseBody to respond JSON String
      • Spring MVC supported return type: View, ModelAndView, Model, ResponseEntity, HttpEntity, HttpHeaders, Callable, RequestResponseBodyMethodProcessor (when annotated by @ResponseBody), ...
      • Logics behind: content negotiation
        • Client accepts certain MIME types(sent to server in request headers), server can produce certain MIME types
        • Servet choose a certain MIME type and find the proper message converter to convert data into that type, then respond to client
        • For Java objects, SpringMVC uses theMappingJackson2HttpMessageConverter to convert any type of data to JSON via ObjectMapper inside jackson package
    • ViewResolver and Page Template
      • Use Thymeleaf in SpringBoot
        • Add the following dependency
          1
          2
          3
          4
          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-thymeleaf</artifactId>
          </dependency>
        • SpringBoot has already configured TemplateEngine and ViewResolver for Thymeleaf, no further configuration is needed
    • Use interceptor for login purpose
      1. Create a class that implements HandlerInterceptor and overrides 3 methods
      2. Create a class that implements WebMvcConfigurer and overrides addInterceptors method, notice access for static resources should not be blocked
        1
        2
        3
        4
        5
        6
        7
        @Overrider
        public void addInterceptors(InterceptorRegistry registry) {
        // new your interceptor class
        registry.addInterceptor(new LoginInterceptor())
        .addPathPatterns("/**") // block all
        .excludePathPatterns("/", "/login", "/static/**");
        }
    • File upload
      1
      2
      3
      4
      5
      <form method="post" enctype="multipart/form-data" th:action="@{/upload}">
      <input type="file" name="singleFile"><br>
      <input type="file" name="multipleFile" multiple><br>
      <button type="submit">Submit</button>
      </form>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @RequestMapping("/upload")
      public String upload(@RequestPart("singleFile") MultipartFile singleFile,
      @RequestPart("multipleFile") MultipartFile[] multipartFiles) throws IOException {
      if (!singleFile.isEmpty()) {
      String singleFileName = singleFile.getOriginalFilename();
      // The directory must exist otherwise error will be caused
      // You could use file.getParentFile().mkdirs() to create folders
      singleFile.transferTo(new File("C:\\cache\\" + singleFileName));
      }
      for (MultipartFile file : multipartFiles) {
      if (!file.isEmpty()) {
      String fileName = file.getOriginalFilename();
      file.transferTo(new File("C:\\cache\\multiple\\" + fileName));
      }
      }
      return "success";
      }
      1
      2
      3
      4
      5
      spring:
      servlet:
      multipart:
      max-file-size: 10MB # Single file size restriction, by default 1MB
      max-request-size: 100MB # Total size restriction, by default 10MB
    • Error handling
      • Spring Boot provides an /error mapping that handles all errors: for machine clients it produces a JSON response of error, for browser clients, it provides a whitelabel error view
      • To replace the default behavior, you can implement ErrorController and register a bean definition of that type or add a bean of type ErrorAttributes to use the existing mechanism but replace the contents
      • Custom error pages: create an error folder under the static resources folders or under the templates folder, and you can use either static HTML error pages or ones built by using templates, the name of the file should be the exact status code or a series mask(eg 404.html or 4xx.html)
      • Customized exception
        1
        2
        3
        4
        5
        6
        7
        8
        9
        @ResponseStatus(code = HttpStatus.FORBIDDEN, reason = "Customized Error")
        public class MyError extends RuntimeException{
        public MyError() {

        }
        public MyError(String msg) {
        super(msg);
        }
        }
    • Embedded servlet container support
      • Spring boot includes support for embedded Tomcat, Jetty, and Undertow servers.
      • Servlets, filters, and all the listeners can be registered either by using Spring beans of by scanning for servlet components
      • Scan servlet, filter, listener, @WebServlet, @ServletComponentScan, @WebFilter, @ServletComponentScan, @WebServlet
        1
        2
        3
        4
        5
        6
        7
        8
        @WebServlet(urlPatterns = "/servlet")
        public class MyServlet extends HttpServlet {

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("/servlet");
        }
        }
        1
        2
        // Annotation on the main class, identify the servlet packages
        @ServletComponentScan(basePackages = "com.yanxuanshaozhu.demo1.servlet")
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        @WebFilter(urlPatterns = {"/**"})
        public class MyFilter implements Filter {

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        filterChain.doFilter(servletRequest, servletResponse);
        }

        @Override
        public void destroy() {
        Filter.super.destroy();
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        @WebListener
        public class MyListener implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent sce) {
        System.out.println("MyListener initialized!!!!!!");
        }

        @Override
        public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("MyListener destroyed!!!!!!");
        }
        }
      • Use ServletRegistrationBean, FilterRegistrationBean, ServletListenerRegistrationBean classes
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        @Configuration
        public class MyRegistrationBean {
        @Bean
        public ServletRegistrationBean servletBean() {
        MyServlet servlet = new MyServlet();
        return new ServletRegistrationBean(servlet, "/*");
        }

        @Bean
        public FilterRegistrationBean filterBean() {
        MyFilter filter = new MyFilter();
        FilterRegistrationBean filterBean = new FilterRegistrationBean(filter);
        String[] patterns = new String[] {"/*"};
        filterBean.setUrlPatterns(Arrays.asList(patterns));
        return filterBean;
        }

        @Bean
        public ServletListenerRegistrationBean listenerBean() {
        MyListener listener = new MyListener();
        return new ServletListenerRegistrationBean(listener);
        }
        }
  4. SpringBoot Data Access
    • Basic Configuration
      • Include the following dependencies
        1
        2
        3
        4
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
      • Driver is not included, so you should also include the driver dependencies, make sure this version-decided driver is compatible with your database
        1
        2
        3
        4
        <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        </dependency>
      • Some auto configurations: DataSourceAutoConfiguration, DataSourceTransactionManagerAutoConfiguration, JdbcTemplateAutoConfiguration, and more, the connection pool is HikariCP
      • DataSource configurations
        1
        2
        3
        4
        5
        6
        7
        8
        9
        spring:
        datasource:
        url: jdbc:mysql://localhost:3306/mybatis
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc:
        template:
        query-timeout: 3
      • Some notice
        • In order to use @Autowired JdbcTemplate, you need to add @SpringBootTest
        • To replace the HikariCP connection pool, you need to provide a customized datasource(The HikariCP DataSource is only auto configured when no customized DataSource is provided)
          1
          2
          3
          4
          5
          6
          # The yml key is different from the HikariCP, so you need to write it yourself
          c3p0:
          user: root
          password: 123456
          jdbcUrl: jdbc:mysql://localhost:3306/mybatis
          driverClass: com.mysql.cj.jdbc.Driver
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          @Configuration
          public class MyDataSourceConfig {
          // Bind the c3p0 in the properties/yml file, so no need to configure
          // datasource here
          @ConfigurationProperties("c3p0")
          @Bean
          public DataSource dataSource() {
          ComboPooledDataSource dataSource = new ComboPooledDataSource();
          return dataSource;
          }
          }
    • MyBatis Configuration
      • Include the following dependency
        1
        2
        3
        4
        5
        6
        <!-- For SpringBoot 3.0-3.1, use mybatis-spring-boot-starter 3.0 -->
        <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.2</version>
        </dependency>
      • To use MyBatis with Spring, at least an SqlSessionFactory and at least one mapper interface are needed. MyBatis-Spring-Boot-Starter will:
        • Autodetect an existing DataSource
        • Create and register a SqlSessionFactory passing that DataSource as an input using the SqlSessionFactoryBean
        • Create and register a SqlSessionTemplate from the SqlSessionFactory
        • Auto-scan the mappers, link them to the SqlSessionTemplate and register them to Spring context so they can be injected into beans
      • Usage
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // This @Mapper can be replaced with one annotation on the configuration class
        // @MapperScan("package");
        @Mapper
        public interface EmpMapper {
        @Select("select count(1) from t_emp")
        Integer amount();
        // For complicated mappings, you can use xml together with annotation
        List<Emp> getEmpLargerId(@Param("eid") Integer eid);
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        <resultMap id="empMap" type="com.yanxuanshaozhu.demo1.bean.Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="email" column="email"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="dept.did" column="did"></result>
        </resultMap>
        <select id="getEmpLargerId" resultMap="empMap">
        select *
        from t_emp
        where eid > #{eid};
        </select>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        @SpringBootTest
        public class DBTest {
        @Autowired
        EmpMapper mapper;

        @Test
        public void test () {
        int res = mapper.amount();
        System.out.println(res);
        }

        @Test
        public void test2() {
        List<Emp> res = mapper.getEmpLargerId(1);
        for (Emp emp : res) {
        System.out.println(emp);
        }
        }
        }
        1
        2
        3
        4
        5
        6
        mybatis:
        # If this property is not specified, then the xml location should have
        # the same pattern as the mapper interface
        mapper-locations: classpath:mapper/*.xml
        configuration:
        map-underscore-to-camel-case: true
      • Use PageHelper
        1
        2
        3
        4
        5
        <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.4.6</version>
        </dependency>

尚硅谷Redis

  1. Introduction
    • Remote Dictionary Server(Redis) is an in-memory key-value database
    • High in-memory performance, support persistence, support scalability, and more
    • Installation on Linux
      • Install C++ environment installation yum -y install gcc-c++, use gcc -v to check gcc version
      • Install redis: use redis-server -v to check redis version, installation
        1
        2
        3
        4
        5
        6
        curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

        echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list

        sudo apt-get update
        sudo apt-get install redis
      • Find Redis config file: sudo find / -name redis.conf
      • Edit redis.conf
        • Change protected-mode yes to protected-mode no
        • Comment bind 127.0.0.1 to allow remote access
        • Add password: requirepass <pwd>
      • Start redis server with customized redis.conf: redis-server <redis.conf-location>
      • Use redis-cli to connect to redis-server: redis-cli -a <pwd> -p <6379-or-other-port>
    • Redis data types
      • Key is string, data types are value types
      • String: SET, SETNX(store if not exist), GET, MGET(multiple retrieve)
      • List: linked lists of string values, LPUSH, RPUSH, LPOP, RPOP, LLEN(length), LMOVE(move values from one list to another), LTRIM(reduce a list to specified range of elements)
      • Set: unordered collection of unique strings, SADD, SREM, SISMEMBER, SINTER(intersection), SCARD(size)
      • Sorted Set: collection of unique strings ordered by an associated score, ZADD, ZRANGE(return values in a range), ZRANK(ascending order rank), ZREVRANK
      • Hash: HSET, HGET, HMGET, HINCRBY(increment the value of a field by integer)
      • Stream: XADD, XREAD (read entries), XRANGE (return range of entries), XLEN(length), often used for message queue
      • Geospatial: geospatial indexes, GEOADD(add location to geospatial index), GEOSEARCH(return location with radius/box)
      • HyperLogLog: a data structure that estimates the cardinality of a set, PFADD(add item), PFCOUNT(return an estimate of number of items in the set), PFMERGE(combine hyperloglogs into one)
      • Bitmap: extension of string to treat it like a vector, SETBIT, GETBIT, BITOP
      • Bitfield: set, increment, and get integer values of arbitrary bit length, BITFIELD, BITFIELD_RO
    • Common operations
      • Keys: keys <pattern>(keys * for all keys), exists <key>, type <key>, del <key>(blocking deletion), unlink <key>(unblocking deletion), ttl <key>(time to live key, -1 is infinite, -2 is expired), move <key> <dbindex> (redis has 16 dbs, 0~15), select <dbindex>(switch db), dbsize(amount of keys in current db), flushdb(clear keys in current db), flushall(clear keys in all dbs)
      • help @type check available commands
      • String: getrange key start end(both inclusive), setrange key offset replace(replace starts from offset), INCR/DECR key, INCRBy/DECRBY key val, append key substr
      • List: lrange list start end(lrange list 0 -1 for all items in the list)
      • Hash: hset key field value [field value...], hget key field, hkeys key (show all fields), hvals key (show all values), hgetall key (get all keys and all values), hincrby key field value
      • Set: sadd key value [value...], srem key value(remove value from set), srandmember key amount, smove s1 s2 value (remove value from s1, add value into s2), sdiff s1 s2, sunion s1 s2, sinter s1 s2, sintercard s1 s2(return cardinality of intersection set of s1 and s2)
      • Sorted Set: zadd key score1 value1 [score value...], zrange key 0 -1(display all values in ascending order of score), zrange key 0 -1 withscores(display all values and corresponding scoreds in ascending order of score), zrangebyscore key minscore maxscore withscores, zcard key, zrem key value
      • Bitmap: setbit key offset 0/1, strlen key (byte count), bitcount key(1 amount)
      • HyperLogLog: used for deduplication: pfadd key val1 [val2...], pfcount key(return cardinality estimation, relatively equal to unique amount, can contain around 0.81% error)
      • Geospatial: geoadd key longitude latitude member1 [longitude latitude m2...], geopos key member1 [m2...](return longitude and latitude), geohash key member1 [me...](return hashvalues of longitude and latitude), geodist key m1 m2(return distance), georadius key longitude latitude radius km/m/ft/mi withdist(return members with the radius of the center position)
      • Stream: message queue, xadd key *|id field value [f v...] (use * to auto generate id, id should be larger than previous id), xrange key - +(display all items in the stream), xdel key id, xlen key(amount of ids), xtrim key minid/maxid id(trim the stream and only key ids that meet the requirement), xread count <amount> streams <stream> $/0( read amount entries in the stream after the one with the largest id $, or after the one with the smallest id 0), xgroup create <stream> <group> $/0 (create consumer group to consume the stream), xreadgroup group <group> <consumer> streams <stream> > (let the consumer in a group read all unread messaages, > represent start from the first unread message. Notice if a consumer in a group read a specific message, other consumers in the same group cannot read that message again), xpending <stream> <group> (return minId read, maxId read, amount read, which consumer read), xpending <stream> <group> <minId> <maxId> <amount> <consumer> (show messages read by a consumer, messages read but ack is not provided are in the pending state), xack <stream> <group> <id> (confirm that a message has been completely consumed, will reduce amount of pending messages)
      • Bitfield: occasionally used
  2. Redis persistence
    • Redis writes data to disk for persistence
    • Options: RDB(Redis database, PIT snapshot), AOF(append only file, log operation and play at server startup), No persistence, RDB + AOF
    • RDB
      • Automatic snapshot for certain time period
      • Generate dump.rdb, default location is inside folder /var/lib/redis
      • Manually generate rdb file: save for blocking save, bgsave for unblocking save
    • AOF
      • RDB may lose data, AOF is the option to ensure 100% recovery
      • By default AOF is not enables, you need to edit the redis.conf to enable AOF: appendonly yes, restart redis on the redis.conf
      • Strategies to write cache to disk: always, everysec, no
      • Generate appendonly.aof, default location is inside folder /var/lib/redis/appendonlydir
      • Manually generate AOF file: bgwriteaof
    • No persistence: save "" to remove RDB, appendonly no to remove AOF
  3. Redis transaction
    • Use multi to start transaction, use discard to give up all commands in transaction and exit transaction, use exec to execuate all commands in transaction and exit transaction, use watch to monitor transaction
    • If error occurs before exec(like compile time error in Java), then executing the exec command will abort all commands in the transaction
    • If error occurs after exec(like runtime error in Java), because Redis does not support rollback, after executing the exec command, commands without error in the transaction will be executed, commands with error in the transaction will be aborted
  4. Redis pipelining
    • Pipelining is used to optimize round-trip times by batching redis commands(no waiting times in between commands)
    • Use redis pipeline
      • Write all commands in a script file, for exampl a.txt
      • Use redis-cli pipeline to execute the file: cat ./a.txt | redis-cli -a pwd --pipe
  5. Redis pub sub
    • Implement the publish/subscribe messaging paradigm, senders and subscribers interact with channels
    • subscribe <channel>, publish <channel> <msg>, pubsub channels (show active channels), pubsub numsub <channel> (show subsciber count of a channel)
  6. Redis replication
    • A replica machine can have its own replica machine
    • In replica machine, edit the redis.conf, add replicaof <masterIp> <masterPort>
    • If master has password, replica configure masterauth with master password
    • In redis-cli: use salveof host port to set new master machine and port, use salaveof no one to set the current machine as master with no replications
  7. Redis sentinel
    • Provides HA for redis when no using redis cluster. So in realtime development, you can use either redis replication + redis sentinel, or redis cluster
    • Automatic failover: if a master is not working as expected, sentinel can start a failover process where a replica is promoted to master, the other replicas are reconfigured to use the new master, and the applications using the Redis server are informed about the new address to use when connecting
    • At least three sentinel instances should be used for roubust deployment, by defaulf a sentinel listens to port 26379
    • Example of sentinel.conf, an example sentinel.conf can be found on redis.io
      1
      2
      3
      4
      5
      6
      sentinel monitor mymaster 127.0.0.1 6379 2
      sentinel down-after-milliseconds mymaster 60000
      sentinel failover-timeout mymaster 180000
      sentinel parallel-syncs mymaster 1
      # Configure master insance password when needed
      sentinel auth-pass <master-name> <password>
    • sentinel monitor <master-name> <ip> <port> <quorum>, quorum is the number of sentinels that need to agree about the fact the master is not reachable, in order to really mark the master as failing, and eventually start a failover procedure if possible
    • Failover process for n sentinels and m quorum (n is odd, n < m)
      1. Sentinels periodically ping the redis master instance (called heart beat), sentinel down-after-milliseconds <master-name> <milliseconds>(default is 30 seconds), it a sentinal does not receive response from the master instance at least that amount of milliseconds, it will consider that the master instance is in the subjectively down state (SDOWN)
      2. If m sentinels agree that the master is not reachable(SDOWN), the master instance is considered to be objectively down (ODOWN)
      3. If (n + 1) / 2 sentinels are reachable and the master instance is ODOWN, then one reachable sentinel will be elected as leader using the Raft algorithm(basic idea: votes are granted in a first-come-first-serve fashion)
      4. The leader sentinel will start the failover process, the following information are taken into account for replica promotion: disconnection time from the master(a replica that is disonnected from the master for more than ten times the configured master timeout is not suitable for promotion), replica priority(replica-priority in redis.conf, default value 100, lower priority value is preferred, but one with 0 value will not be promoted), replication offset processed(one with newer data will be promoted), run id (random id to identify a sentinel, lexicographically smaller id is preferred). Order: compare replica-priority -> compare replication offset -> compare run id
      5. salveof no one on the new master, slaveof host port on replicas
    • Start redis sentinel: redis-sentinel <sentinel.conf> --sentinel
    • Sentinel + redis replication does not ensure 0 data loss
  8. Redis cluster
    • Redis Cluster is a distributed implementation of redis
    • Provide HA, write safety(no single point of failure), data is automatically shared among redis nodes
    • Key distribution model: cluster's key space split into 16384 hash slots, setting man upper limit of 16384 master nodes, but the suggested max amount of master nodes is ~ 1000. Each node in a redis cluster is responsibile for a subnet of the hash slots
    • Data mapping approach
      1. Simple hashing: hash(key) / node amount, difficult for sacling
      2. Consistent hashing: independent of the number of servers or objects in the hash table, can have data skew
      3. Hash slot: data is inside hash slot, server manages hash slot instead of managing data directly, HASH_SLOT = CRC16(key) mod 16384
    • Why maximum slot is 16384(2^14): CRC-16 hash has 16 bits, which is up to 65536([0, 65535]): Redis heartbeat packet contains configuration of a node, if there are 16384(2^14) nodes, the packet size would be 2k, but for 65536(2^16) nodes, the heartbeat packet would require 8k size. Generally redis suggests ~ 1000 nodes, and 16384 slots are enough for 1000 nodes
    • Redis cluster also does not support 100% write safety
    • redis.conf for cluster
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
               cluster-enabled yes
      cluster-config-file <nodes-xxx.conf>
      cluster-node-timeout <xxx-milliseconds>
      ````
      * Allow auto switch among nodes in redis cluster: `redis-cli -c`

      9. Redis with SpringBoot
      * Java client for redis
      * Jedis, lightweight client, offers fewer features
      * Lettuce, fully non-blocking redis java client that supports sync and async communication
      * RedisTemplate: a class provided by Spring Data Redis which can be used to perform redis operations
      * Jedis
      * Include the dependency
      ```xml
      <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
      <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>4.4.1</version>
      </dependency>
      • Sample code
        1
        2
        3
        4
        5
        6
        7
        String host = "1.1.1.1";
        int port = 6379;
        String pwd = "pwd";
        Jedis jedis = new Jedis(host, port);
        jedis.auth(pwd);
        List<String> l1 = jedis.lrange("l1", 0, -1);
        String v1 = jedis.get("k1");
    • Lettuce
      • Include the dependency
        1
        2
        3
        4
        5
        6
        <!-- https://mvnrepository.com/artifact/io.lettuce/lettuce-core -->
        <dependency>
        <groupId>io.lettuce</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>6.1.8.RELEASE</version>
        </dependency>
      • Sample code
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        //        RedisURI uri = RedisURI
        // .builder()
        // .redis("149.28.62.112")
        // .withAuthentication("default", "123456")
        // .withPort(6379).build();
        String uri = "redis://123456@149.28.62.112:6379";
        RedisClient client = RedisClient.create(uri);
        StatefulRedisConnection<String, String> conn = client.connect();
        RedisCommands<String, String> commands = conn.sync();
        String v1 = commands.get("k1");
        System.out.println(v1);
        List<String> list = commands.lrange("l1", 0, -1);
        list.forEach(item -> System.out.println(item));
        conn.close();
        client.shutdown();
    • RedisTemplate
      • Include the dependency
        1
        2
        3
        4
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
      • Standalone redis connection
        • Configuration file application.yml:
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          spring:
          data:
          redis:
          host: 1.1.1.1
          port: 6379
          password: 123
          database: 0
          lettuce:
          pool:
          max-active: 8
          max-wait: -1ms
          max-idle: 8
          min-idle: 0
        • Sample code
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          @Service
          public class TestService {
          // Here are multiple classes, so provide
          @Qualifier("stringRedisTemplate")
          @Autowired
          private RedisTemplate template;

          public void addOrder() {
          int id = ThreadLocalRandom.current().nextInt(1000) + 1;
          String key = "k" + id;
          String serialNo = UUID.randomUUID().toString();
          template.opsForValue().set(key, serialNo);
          }

          public String getOrderById(Integer id) {
          String key = "k" + id;
          return (String) template.opsForValue().get(key);
          }
          }
      • Redis cluster connection
        • Configuration file application.yml:
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          spring:
          data:
          redis:
          password: 123456
          cluster:
          max-redirects: 3
          nodes: 1.1.1.1:6379,2.2.2.2:6379,3.3.3.3:6379
          lettuce:
          pool:
          max-active: 8
          max-wait: -1ms
          max-idle: 8
          min-idle: 0
        • If you use the same code, there can be error: the client(springboot) does not know if there's re-election inside the redis cluster and it cannot automatically switch to the new master instance. You need to enable the redis cluster topology view refresh to let the client notice the lastest redis cluster configuration
          1
          2
          3
          4
          5
          6
          7
          8
          9
          spring:
          data:
          redis:
          lettuce:
          cluster:
          refresh:
          adaptive: true
          # default refresh period is 60 seconds
          period: 2000

尚硅谷SpringCloud

  1. Introduction
    • Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configuration management, service discovery, circuit breakers, intelligent routing, micro-proxy, control bus, one-time tokens, global locks, leadership election, distributed sessions, cluster state)
    • Compatible versions: https://start.spring.io/actuator/info json info
    • Configuration registration and discovery: Eureka, Zookeeper, Consul, Nacos
    • Load balancer: Ribbon, LoadBalancer, Feign, OpenFeign
    • Circuit breaker: Hystrix, Resilience4J, Alibaba Sentinel
    • Intelligent routing: Zuul, Gateway
    • Configuration management: Config, Nacos
    • Bus: Bus, Nacos