這篇文章是觀看Bilibili上面黑馬程序員SSM相關視頻時,總結的筆記
Spring
Spring 概述
Spring是分層的Java SE/EE應用的full-stack輕量級開源框架,以IOC(Inverse of control控制反轉)和AOP(Aspect oriented programming面向切片編程)為内核,提供了展現層Spring MVC和持久層Spring JDBC以及業務層事務管理等多種技術,是使用最多的Java EE企業應用框架
Spring的優勢
- 方便解耦,簡化開發
- AOP編程的支持
- 聲明式事務的支持
- 方便程序的測試
- 方便集成各種優秀框架
- 降低JavaEE API的使用難度
Spring的體系結構
- Core Container提供了IOC的支持,其他功能必需核心容器的支持
- AOP, Aspects, Instrumentation, Messaging和AOP有關
- Data/Access層提供了持久層的支持
- Web層提供了MVC的支持
- Testing層提供了測試的支持
IOC的概念和應用
- 耦合和解耦
- 耦合:程序之間存在依賴關係,可能是類之間的依賴,也可能是方法之間的依賴
- 解耦:降低程序之間的依賴關係,應該做到編譯期不依賴,運行時才依賴
- 解耦的思路:
- 使用反射創建對象,而不使用
new
來創建對象 - 對於反射傳入的字符串,不應該寫成固定的字符串,而是應該通過讀取配置文件來獲得
- 使用反射創建對象,而不使用
- 表現層,業務層和持久層之間都應該解耦
- 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
29public 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);
}
}
- IOC
- 控制反轉把創建對象的權力從應用交給框架,是框架的重要特徵,包括依賴注入和以來查找。原先是由應用直接創建對象,耦合度高,現在是應用通過工廠的方法來獲得對象,應用把控制權交給了工廠,耦合度較低。
- 作用:解耦
Spring的IOC解決程序耦合
- 使用XML配置IOC替代Bean factory的手動配置
- 在resources目錄下面新建一個bean.xml文件,寫入以下内容
1
2
3
4
5
6
7
8
9
<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
7public 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
3Resource resource = new ClassPathResource("bean.xml");
BeanFactory ctx = new XmlBeanFactory(resource);
MyClass1 var1 = (MyClass1) ctx.getBean("獲取對象時的字符串1");- ApplicationContext的各個類都有
close()
方法,用來銷毀容器,但是ApplicationContext自己沒有,所有不能用多態寫法
- 實現類
- 在resources目錄下面新建一個bean.xml文件,寫入以下内容
- Spring對Bean的管理細節
- 創建Bean的三種方式
- 通過默認構造函數創建:
在xml中使用
<bean>
標簽,有id
和class
屬性,那麽會使用類的默認構造函數創建對象 - 使用類中的方法創建對象,並存入Spring容器:
<bean id="獲取對象的字符串" factory-bean="工廠類類名" factory-method="工廠類方法名"></bean>
- 使用類中的靜態方法創建對象,並存入Spring容器:
<bean id="獲取對象的字符串" class="反射的全限定類名" factory-method="獲取對象的方法"></bean>
- 通過默認構造函數創建:
在xml中使用
- Bean對象的作用範圍
- 使用
<bean>
標簽的scope
屬性來指定Bean的作用範圍 - 取值:
singleton
單例,prototype
多例,request
web應用的請求範圍,session
web應用的會話範圍,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
- 創建Bean的三種方式
- 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
27public 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
10public 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
15public 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
- 注解:注解和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-method
和destroy-method
屬性@PreDestroy
: 用於指定銷毀方法@PostConstruct
: 用於指定初始化方法
- 用於創建對象的注解, 類似XML中的
- 一個基於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官網 -->
<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來進行測試
- 一個基於注解的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官網 -->
<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來進行測試
- 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容器中
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=1234561
2
3
4
5
6
7
8
9
10
11
12
13// file SpringConfiguration.java
public class SpringConfiguration {
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 {
private String driverClass;
private String url;
private String user;
private String password;
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();
}
}
}
- Spring整合JUnit
- 一個Test案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class AccountServiceTest {
private ApplicationContext ctx;
private IAccountService as;
// 對於測試工程師來説,他應該只寫test方法,不應該需要懂Spring然後寫出來這個init方法
public void init() {
ctx = new AnnotationConfigApplicationContext(SpringConfiguration.class);
as = ctx.getBean("accountService", IAccountService.class);
}
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
public class AccountServiceTest {
private IAccountService as;
public void testFindAll() {
List<Account> allAccount = as.findAllAccount();
for (Account ac : allAccount) {
System.out.println(ac);
}
}
} - 注意Spring 5.x.x版本需要JUnit 4.12及以上版本才能使用
- 導入整合Spring框架的JUnit的maven坐標,注意spring-test需要junit版本至少4.12
- 一個Test案例
事務和動態代理
- 事務控制
- 同一組操作應該使用一個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();
}
}
}
- 動態代理
- 特點:字節碼隨用隨創建,隨用隨加載,不修改源碼的基礎上對方法進行增强
- 分類:基於接口的動態代理,基於子類的動態代理
- 基於接口的動態代理
- 被代理類最少實現一個接口,否則不能使用
Proxy
類- 使用
newProxyInstance(ClassLoader loader, Class[] cls, InvocationHandler handler)
方法創建對象loader
: 被代理對象的類加載器, 就是delegatedClass.getClass().getClassLoader()
cls
: 讓代理對象和被代理對象有相同的方法, 就是delegatedClass.getClass().getInterfaces()
handler
: 用於提供增强的代碼,就是寫一個接口的匿名内部類,并且實現invoke()
方法
1
2
3
4
5
6
7public interface IProducer {
/*銷售*/
void sellProduct(float amount);
/*售後*/
void customerService(float amount);
}1
2
3
4
5
6
7
8
9
10
11
12public class Producer implements IProducer {
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
22
23public 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() {
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
10public 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() {
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
- AOP
- AOP(Aspect Oriented Programming): 面嚮切麵編程,通過預編譯和運行期動態代理實現程序功能的統一維護的一種技術
- AOP的作用和優勢
- 作用:在程序運行期間,不修改源碼對已有方法進行增强
- 優勢:減少重複代碼,提高開發效率,維護方便
- AOP的實現: 通過動態代理實現
- Spring中的AOP
- Spring中的AOP是通過配置XML或者注解實現的
- AOP相關術語
- Joinpoint連接點: 被攔截到的點,在Spring中這些點指的是方法,因爲Spring只支持方法類型的連接點。業務層的所有方法都是連接點
- Pointout切入點: 指的是要對哪些Joinpoint進行攔截的定義,業務層中被增强的方法才是切入點,也就是説切入點一定是連接點
- Advice通知/增强: 攔截到Joinpoint之後要做的事情就是通知,通知的方式有很多,根據對invoke方法的相對位置分成了:前置通知、后置通知、异常通知、环绕通知等
- Introduction引介: 引介是一種特殊的通知,可以在運行期為類動態的增加一些方法或者屬性
- Target目標對象: 代理的目標對象
- Weaving織入: 把增强應用到目標對象來創建新的代理對象的過程,Spring采用動態代理織入,而AspectJ采用編譯期織入和類裝載期織入
- Proxy: 一個類被AOP織入增强之後,就產生了一個結果代理類
- Aspect切麵: 切入點和通知的結合,就是把自己手動寫的切入點和通知的結合通過配置來完成
- 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
<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
18public 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("最終通知")
}
}
- 項目
- 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
<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/**
* 用於記錄日志的工具類,它裏面提供了公共的代碼
*/
public class logger {
private void expr() {}
/**
* 用於打印日志,并且在切入點方法(業務層方法)
*/
// 必須是方法調用而不是方法名
public void beforeLog() {
System.out.println("1.0 Logger中的beforeLog方法開始執行了");
}
/**
* 用於打印日志,并且在切入點方法(業務層方法)
*/
public void afterReturnLog() {
System.out.println("2.0 Logger中的afterReturnLog方法開始執行了");
}
/**
* 用於打印日志,并且在切入點方法(業務層方法)
*/
public void afterThrowLog() {
System.out.println("3.0 Logger中的afterThrowLog方法開始執行了");
}
/**
* 用於打印日志,并且在切入點方法(業務層方法)
*/
public void afterLog() {
System.out.println("4.0 Logger中的afterLog方法開始執行了");
}
}
- 項目
Spring中的JdbcTemplate
- 概述
- 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
11public 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
<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
5public 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)");
}
- Spring框架提供了很多操作的模板類,關係型數據庫的
- 通過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
16List<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> {
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
4List<Account> accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper<Account>(Account.class), 1000f);
for (Account acc: accounts) {
System.out.println(acc);
}
- 手寫RowMapper
- 查詢一個
1
2List<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);
- 保存數據
- 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;
}
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);
}
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);
}
public void updateAccount(Account acc) {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", acc.getName(), acc.getMoney(), acc.getId());
}
} - 不寫JdbcTemplate屬性,而是繼承
JdbcDaoSupport
類,這個類裏面有JdbcTemplate
和DataSource
, 但是這樣不能使用注解的@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 {
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);
}
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);
}
public void updateAccount(Account acc) {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", acc.getName(), acc.getMoney(), acc.getId());
}
}
- 寫入JdbcTemplate, 需要配置bean.xml或者使用Autowired注解
Spring中的事務控制
- 概述
- JavaEE體系中,事務控制都是在業務層
- Spring框架提供了一組事務控制的接口
- Spring的事務控制是基於AOP的,可以通過編程方式實現,也可以通過配置方式實現
- 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進行持久化數據的時候使用
- 基於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
:指定事物的隔離級別,默認defaultpropagation
: 用於指定事務的傳播行爲,默認required,表示一定有事務,增刪改用這個,查詢可以設置爲supportsread-only
: 用於指定事務是否只讀,默認false,查詢應該是true,其他是false即可timeout
:用於指定事務的超時時間,默認是-1,表示永遠不超時rollback-for
:用於指定一個異常,產生這個異常的時候,事務回滾,其他異常的時候不回滾。沒有默認值,表示所有異常都回滾no-rollback-for
:用於指定一個異常,產生這個異常的時候不回滾,其他都回滾。沒有默認值,表示所有異常都回滾
- 用
<tx:method name=? propagation=? read-only=?></tx:method>
配置事務的方法,這個要在<tx:attributes>
内部,查詢方法是PROPAGATION_SUPPORTS
和true
,其他方法是PROPAGATION_REQUIRED
和false
- 在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
<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>
- 網路資料的數據庫隔離性
- 基於注解的聲明式事務控制
- 配置流程
- 使用
<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
// 設置創建容器時掃描的包
// 導入其他配置類
// 加載jdbc.properties
//開啓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/**
* 鏈接數據庫相關的配置
*/
public class JdbcConfig {
// 取得properties文件中的值
private String driver;
private String url;
private String user;
private String pwd;
/**
* 創建JdbcTemplate對象
*/
public JdbcTemplate createJdbcTemplate(DataSource ds) {
return new JdbcTemplate(ds);
}
/**
* 創建一個數據源對象
*/
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 {
/**
* 創建事務管理器對象
*/
public PlatformTransactionManager createTransactionManager(DataSource ds) {
return new DataSourceTransactionManager(ds);
}
} - 在需要事務的類和方法加上
@Transactional(propagation=?, read-only=?)
注解,按照對應方法來判斷傳播行爲和只讀屬性的值
- 寫一個配置類入口
- 配置流程
- 編程式事務控制的方式
- 事務控制基本都是聲明式的,編程式的事務控制應用極少
- 在
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
37public 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;
}
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
// 每一個需要事務的業務層方法都需要放在transactionTemplate.execute()方法中
public void transfer(String from, String to, float amount) {
transactionTemplate.execute(new TransactionCallback<Object>() {
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基礎
- 基本概念
- 三層架構
- 架構:C/S 或者B/S
- 三層架構
- 表現層:SpringMVC,表現層又叫做web層,主要負責接收客戶端的請求,向客戶端發送請求結果
- 業務層:Spring,負責業務邏輯處理,和項目中的需求挂鈎
- 持久層:MyBatis, 持久層又叫做dao層,負責數據持久化
- 表現層框架MVC
- Model模型:Java Bean,封裝數據
- View視圖:JSP,用來和用戶進行交互
- Controller控制器: Servlet,用來接受請求和返回數據
- SpringMVC是一個基於Java實現的MVC設計模型的請求驅動類型的輕量級Web框架,屬於Springframework的後續產品。Spring框架整合SpringMVC比整合其他的表現層框架要容易
- 三層架構
- 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
public class ControllerDemo {
// 設置當前方法的訪問路徑
// 當前方法的返回值作爲請求體返回給客戶端
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
// SpringMVC掃描的包
public class SpringMvcConfiguration {
}1
2
3
4
5
6
7
8
9
10
11
12// 創建Spring的配置文件,加載dao和service對應的bean
// Spring掃描的包,有兩種寫法,一種是寫多個包路徑,一般主要有這個
// @ComponentScan("com.yanxuanshaozhu.dao", "com.yanxuanshaozhu.service")
// 第二種寫法是使用excludeFilters屬性,排除掉不需要掃描的包,SpringBoot使用了這種方法
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容器的配置
protected WebApplicationContext createServletApplicationContext() {
// 創建SpringMVC的配置類,不能用AnnotationConfigApplicationContext,只能用AnnotationConfigWebApplicationContext
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfiguration.class);
return ctx;
}
// 規定哪些請求路徑會被SpringMVC處理
protected String[] getServletMappings() {
// 所有請求路徑規SpringMVC處理
return new String[]{"/"};
}
// 加載Spring容器的配置
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
17public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 加載Spring的配置類
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfiguration.class};
}
// 加載SpringMVC的配置類
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfiguration.class};
}
// 規定哪些請求路徑由SpringMVC處理
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
- 標準寫法
- 導入相關依賴:
- 使用Postman進行測試get請求和post請求
- 創建workspace和collection來保存測試,方便以後使用
- 創建maven攻城的時候選
SpringMVC的請求和響應
- SpringMVC處理請求
- 不同模塊的相同請求路徑有兩種寫法
- 方法1:
每個模塊的方法内部注解加上模塊名前綴,如
@RequestMapping("/user/save")
,@RequestMapping("/book/save")
- 方法2: 每個模塊上面加上請求路徑注解,
如
@RequestMapping("/user")
,@RequestMapping("/book")
,這樣模塊内部的方法就不用每一個方法的注解都加上路徑前綴了
- 方法1:
每個模塊的方法内部注解加上模塊名前綴,如
- 請求參數類型和具體寫法
- Controller和Servlet不一樣,Controller裏面的方法不區分接受的是get請求還是post請求
- 服務器中方法寫具體參數:
使用
@Request("請求參數") T 方法參數
完成請求參數和方法參數的對應,如果請求參數和方法參數的名字一樣,可以省略掉注解直接寫T 請求參數/方法參數
1
2
3
4
5
6
public String info(int age) String name, {
System.out.println("User info");
return "{'user':{'type': 'info', 'name': " + name + ", 'age': " + age + "}}";
} - 服務器中方法寫對象參數:
1
2
3
4
5
6
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
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
public String arrayInfo( String[] args){
System.out.println(Arrays.toString(args));
return "{'arrayInfo': 'resp' }";
} - 服務器中方法寫集合參數:
這時候請求端數組裏面的參數的key要相同,并且服務器中方法參數前加上
@RequestParam
注解,否則服務器會試圖把請求參數傳入集合的屬性1
2
3
4
5
6
public String arrayInfo( 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數組
public String jsonArray( List<String> args){
System.out.println(args);
return "{'jsonArrayInfo': 'resp'}";
}
// 傳遞JSON對象
public String jsonObject( 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對象數組
public String jsonObjectArray( 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
注解開啓之後可以根據類型匹配對應的類型轉換器
- SpringMVC默認支持標準格式(
- 請求參數包括中文可能會有亂碼,需要進行處理:
- 處理在服務器内部中文亂碼:
在初始化Servlet容器的類裏面加上一個過濾器,設置服務器的charsetEncoding為UTF-8
1
2
3
4
5
6
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")
- 處理在服務器内部中文亂碼:
在初始化Servlet容器的類裏面加上一個過濾器,設置服務器的charsetEncoding為UTF-8
- 不同模塊的相同請求路徑有兩種寫法
- SpringMVC發送響應
- 頁面跳轉
- 直接返回字符串:這種方式會將返回的字符串和視圖解析器的前後綴拼接后跳轉
1
2
3
4
5
6
7
8
9
10
11// 在SpringConfiguration.java中
public ViewResolver configureViewResolver() {
// 用來返回指定目錄下的指定後綴的文件
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
// 設置訪問前綴
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
// 設置訪問後綴
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}1
2
3
4
5// 在UserController.java中
public String info() {
return "info"; // 這代表自動跳轉到/WEB-INF/jsp/info.jsp
} - 通過
ModelAndView
返回: Model是用來封裝數據的,View是用來展示數據的1
2
3
4
5
6
7
8
9
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
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數據,返回字符串跳轉頁面
public String info(Model model) {
// 通過Model添加數據
model.addAttribute("name", "張三");
return "info";
}
// 類似的寫法是注入一個HttpServletRequest對象
public String info(HttpServletRequest request) {
// 通過Model添加數據
request.addAttribute("name", "張三");
return "info";
}
- 直接返回字符串:這種方式會將返回的字符串和視圖解析器的前後綴拼接后跳轉
- 回寫數據
- 直接返回字符串
1
2
3
4
5
6
7
8
9
10
11// 讓Spring注入HttpServletResponse對象,在寫數據到頁面上,耦合度高
public void info(HttpServletResponse response) throws IOException{
response.getWriter().writer("直接在頁面上寫入的字符串");
}
// 使用@ResponseBody注解,耦合度地
public String info() {
return "直接在頁面上寫入的字符串";
} - 返回JSON字符串的Java對象
- 加入
jackson-databind
依賴 - 使用
@ResponseBody
注解,返回JSON數據1
2
3
4
5
6
7
8
9
10
11
12
13
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風格
- 簡介
- 定義 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
- 傳統風格
- 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
public class UserController {
// 增加新數據
public String add() {
System.out.println("Add method called");
return "Add method called";
}
// 刪除數據
public String deleteById( Integer id){
System.out.println("Delete method called by id " + id);
return "Delete method called by id" + id;
}
// 修改數據
public String update() {
System.out.println("Update method called");
return " Update method called";
}
//查找所有數據
public String searchAll() {
System.out.println("SearchAll method called");
return "SearchAll method called";
}
// 查找單個數據
public String searchByID( 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的支持配置類
public class SpringMvcConfigurationSupport extends WebMvcConfigurationSupport {
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的配置類
public class SpringMvcConfiguration {
} - 注意前端頁面在訪問服務器的時候,路徑要從項目虛擬目錄開始寫,比如
axio.get("/項目虛擬目錄/請求資源路徑")
- 注意對於網站的靜態資源不應該經過SpringMVC的處理,應該是由Servlet容器直接訪問,因此需要增加一個配置類,設置攔截路徑,并讓SpringMVC的配置類掃描這個類/導入這個類
- 幾個注解比較
MyBatis
MyBatis基礎
- 原始JDBC操作分析
- 原始操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Class.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對象的映射需要手動配置:使用反射進行自動填充
- 原始操作
- MyBatis基礎
- 簡介
- MyBatis是一個Java持久層框架,它内部封裝了JDBC,執行sql語句并且將結果映射為Java對象返回,屏蔽了底層細節,使得開發者只需要關注sql語句本身
- MyBatis通過xml或者注解的方式將statement配置起來,應且通過Java對象和statement中的動態參數進行映射生成最終執行的sql語句
- 項目配置流程
- 導入mybatis的jar
mybatis-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官網複製即可 -->
<!-- 在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官網複製即可 -->
<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
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的jar
- 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
8InputStream 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
5InputStream 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
9InputStream 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
6InputStream 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
5InputStream 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進階
- MyBatis的dao層實現
- 傳統實現方式:手寫Dao的代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class UserDaoImpl implements UserDao{
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根據接口的定義創建動態代理對象- 遵循規範
XXXMapper.xml
中的namespace
屬性值要和Dao層的XXXMapper.java
的全限定類名一致(注意这里不能使用别名)XXXMapper.java
接口中方法的方法名稱和XXXMapper.xml
中的方法的id
屬性一致XXXMapper.java
接口中方法的返回類型和XXXMapper.xml
中的方法的resultType
屬性一致(注意比如返回值是List<User>
,那么resultType
屬性值是user
即可)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
7InputStream 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);
- 遵循規範
- 傳統實現方式:手寫Dao的代碼
- 動態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
2select * 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>
- SQL片段抽取
- 對於多個SQL語句中重複的部分,可以抽取出來,其他語句引用這部分即可,利於簡潔化和解耦
- 使用
<sql id="公共SQL的唯一id">SQL語句</sql>
抽取公共SQL語句 - 使用
<include refid="公共SQL的唯一id"></include>
引用gonggongSQL語句
- MyBatis自定義類型轉換器
- MyBatis自帶了數據庫類型和Java類型的轉換,這些轉換是自動完成了,不需要手動配置
- 自定義轉換器流程
- 自定義轉換器類實現
TypeHandler
接口,或者繼承BaseTypeHandler
類 - 重寫類中方法
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是要轉換的字段的索引
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
long time = date.getTime();
preparedStatement.setLong(i, time);
}
// 數據庫類型轉換成Java類型
// resultSet是查詢的結果集,s是要轉換的字段名稱
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
long time = resultSet.getLong(s);
Date date = new Date(time);
return date;
}
// 數據庫類型轉換成Java類型
// resultSet是查詢的結果集,s是要轉換的字段的索引
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
long time = resultSet.getLong(i);
Date date = new Date(time);
return date;
}
// 數據庫類型轉換成Java類型
// callableStatement用來調用存儲過程,i是要轉換的字段的索引
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
long time = callableStatement.getLong(i);
Date date = new Date(time);
return date;
}
} - 在MyBatis核心配置文件中注冊這個自定義的轉換器:在
typeHandlers
標簽中寫typeHandler
標簽1
2
3
4<!-- 這個標簽寫在typeAlias之後,environments之前 -->
<typeHandlers>
<typeHandler handler="com.yanxuanshaozhu.handler.DateTypeHandler"></typeHandler>
</typeHandlers>
- 自定義轉換器類實現
- 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
3public 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
3public interface UserMapper {
List<User> getUserRoles();
}
- MyBatis注解開發
- MyBatis注解開發主要是用來代替Mapper映射文件的編寫
- 一些注解:
@Insert
新增,@Update
更新,@Delete
刪除,@Select
查詢,@Results
封裝多個結果集代替resultMap
標簽),@Result
,封裝結果集(代替id
和result
標簽)@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 {
List<User> findAll();
User findById(Integer id);
void insert(User user);
void updateById(User user);
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
15public interface PurchaseMapper {
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 {
List<Purchase> findAll();
}
MyBatisPlus
- 介紹
- 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>
- 配置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
public SqlSessionFactory MyBatisSqlSessionFactoryBean( DataSource ds)throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setTypeAliasesPackage("com.yanxuanshaozhu.domain.Account");
return bean.getObject();
}
- 把Mybatis配置類注入
- Mybatis配置MP
- 來自BaseMapper的通用CRUD操作
SSM框架整合
- 基本整合
- 注意一个问题:在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配置文件讀取内容
public class JdbcConfig {
// 把properties文件的内容注入到變量中
private String driver;
private String url;
private String user;
private String password;
// 配置DruidDataSource
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
類,配置SqlSessionFactory
和MapperScannerConfigurer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class MyBatisConfig {
// 配置SqlSessionFactory,從而可以讓Spring注入XXXMapper
public SqlSessionFactory sqlSessionFactory( DataSource ds)throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(ds);
// 設置之後可以不用全限定類名,而是直接使用類名
sqlSessionFactoryBean.setTypeAliasesPackage("com.yanxuanshaozhu.domain.Account");
return sqlSessionFactoryBean.getObject();
}
// 配置MapperScannerConfigurer,指定掃描XXXMapper.java所在的包得到Mapper
public MapperScannerConfigurer getMapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.yanxuanshaozhu.mapper");
return configurer;
}
} SpringConfiguration.java
中導入上述兩個類
- 建立
- 注意一个问题:在jsp文件中使用
SpringBoot
SpringBoot基礎
尚硅谷Spring6
- 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
2Class 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
- Load the
- Log4j2 log framework
- Include dependencies in
pom.xml
,createlog4j2.xml
to control log format
- Include dependencies in
- IOC
- IOC container: contains map of beans
- Use
BeanDefinitionReader
to getBeanDefinition
- Use
BeanFactory
(spring internal interface, developer should use theApplicationContext
interface and its implementing classes instead) and Reflection to instantiate beans - Bean is then initialized
- Use
context.getBean(idName)
to get bean
- DI(Dependency injection)
- When creating beans, inject their dependencies using configurations
- How to
- Use set method
- Use constructor
- 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
- Use
- Import external properties file(database configuration, data source)
- Include the
context
namespace1
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 file1
<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>
- Include the
- 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
11public class MyFactoryBean implements FactoryBean<User> {
public User getObject() throws Exception {
return new User();
}
public Class<?> getObjectType() {
return User.class;
}
}
- Can be used to encapsulate object construction logic in class, often
are used to incorporate with other frameworks
- 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
18public 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;
}
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>
- Get bean object
- IOC: Annotation-based configuration
- Steps:
- Import dependencies: include the
context
namespace - 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 container1
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> - Use annotation to define beans:
@Component(value="")
(value is the same as the bean id in the XML configuration),@Repository
,@Service
,@Controller
- DI:
@Autowired
,@Qualified
,@Resource
- Import dependencies: include the
- 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")
- Directly annotate the field, no getter or setter is needed for the
field, this approach is not
recommended(
@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>
- By default, autowire uses match
byBame(
- Pure annotation development without the XML configuration file
1
2
3
4
5
// The ComponentScan is a supplement of the XML context:component-scan tag
public class SpringConfig {
}
- Steps:
- 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
29public 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
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
- An example of dynamic delegation
- 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
- Include the additional dependencies:
spring-aop
,spring-aspects
- Create target resources: target interface, target class
- 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 arguments1
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 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));
}
// AfterReturning is executed before the After method, and it can captures the returned value of the joinpoint method
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);
}
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
// Do something here
}
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
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()")
public void beforeMethod() {}
- Advice annotation syntax:
- If you use XML, include
aop
,context
, enable component scan, enable aspectj autoproxy1
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>
- Include the additional dependencies:
- Steps
- 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>
- Delegation: delegate and the original object. Spring AOP is based on
dynamic delegation
- 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
public class SpringTest {
private User user;
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
public class SpringTest {
private User user;
public void testUser() {
System.out.println(user);
}
} - An example for JUnit4 with XML
1
2
3
4
5
6
7
8
9
10
11
public class SpringTest {
private User user;
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
public class SpringTest {
private User user;
public void testUser() {
System.out.println(user);
}
}
- Spring Transaction
- JdbcTemplate
- Spring encapsulates JDBC, uses JdbcTemplate to do CRUD on the database
- Steps for using JdbcTempalte
- Include dependencies:
spring-jdbc
,mysql-connector-java
,c3p0/druid
- 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> - Basic CRUD
- For insert, update, delete, simply use the
update
method - For query, use
queryForObject
,queryForList
etc., which need a subclass instance of the interfaceRowMapper
, you can use arrow method or you can use built-int classes1
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);
- For insert, update, delete, simply use the
- Include dependencies:
- 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
3public 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
8public 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
5public int getBookPriceById(int bookId) {
String querySql = "select price from book where id = ?;";
Integer price = jdbcTemplate.queryForObject(querySql, Integer.class, bookId);
return price;
}
- Controller layer passes the business logic to a specific serviec
layer
- Declarative transaction using XML and annotation: this requires the
tx
namespace. Then you can use the@Transactional
annotation in the service layer1
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), whetherreadOnly
(if readOnly, then only query is allowed), rollback policy(whether rollback for specific type of errors, for examplenoRollbackFor = 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
public class SpringConfig {
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;
}
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
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>
- Transaction categories
- JdbcTemplate
- Resources
- The
Resource
interface in the spring framework is more capable for accessing low-level resources than thejava.net.URL
class - Implementing classes:
UrlResource
,ClassPathResource
,FileSystemResource
, etc.
- 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
interface1
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
7ApplicationContext 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}
- 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- Include dependencies:
hibernate-validator
,jakarta.el
- Implement the
Validator
interface, overridesupports
andvalidate
method1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class PersonValidator implements Validator {
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
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");
}
}
} - Test
1
2
3
4
5
6Person person = new Person();
DataBinder dataBinder = new DataBinder(person);
dataBinder.setValidator(new PersonValidator());
dataBinder.validate();
BindingResult bindingResult = dataBinder.getBindingResult();
System.out.println(bindingResult.getAllErrors());
- Include dependencies:
- Use annotation to do validation
- Create configure class and configure
LocalValidatorFactoryBean
1
2
3
4
5
6
7
8
public class SpringConfig {
public LocalValidatorFactoryBean getLocalValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
} - Create bean class with getter and setter methods, use annotations to
set validation rules
1
2
3
4
5
6// other parts are omitted
private int age;
private String name; - Create validators
1
2
3
4
5
6
7
8
9
public class Validator1 {
private Validator validator;
public boolean validatorByUser(User user) {
Set<ConstraintViolation<User>> validate = validator.validate(user);
return validate.isEmpty();
}
} - Validate
1
2
3
4
5ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Validator1 validator1 = context.getBean(Validator1.class);
User user = new User();
boolean result = validator1.validatorByUser(user);
System.out.println(result);
- Create configure class and configure
- Use validation based on methods
- Create configure class and configure
MethodValidationPostProcessor
1
2
3
4
public MethodValidationPostProcessor getMethodValidationPostProcessor() {
return new MethodValidationPostProcessor();
} - Create bean class with getter and setter methods, use annotations to set validation rules
- Create Validators
1
2
3
4
5
6
7
public class MyService {
public String testMethod( User user){
return user.toString();
}
} - Validate
1
2MyService service = context.getBean(MyService.class);
System.out.println(service.testMethod());
- Create configure class and configure
- Implement
- 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
- 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 theweb/WEB-INF/lib
folder, with those whose<scope>
is provided excluded - Properly construct the project so that the
web
folder and theweb/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 insideweb/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 ofparam-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>
- Default configuration: in this way, the
- Configure view resolver(maps view names to actual views) thymeleaf
in the
SpringMVC.xml
, files under theWEB-INF
cannot be accessed directly by the browser, you need to use the thymeleaf viewResolver1
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
public String target() {
return "target";
}
- Add dependencies:
- MVC framework
@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
// Similar to Express router
public class Controller1 {
// This actually handles localhost:8080/applicationContext/base/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 url1
2
3
4
5
6
7// If there is a placeholder in the url, the id must be provided,
// otherewise an error will occur
// The value of pathvariable should be the same as the requestmapping value
public String profile( 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 causedparamName
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
- Get request parameters
- Get parameters using Servlet api(The DispatcherServlet will inject
the request parameter for you)
1
2
3
4
5
6
7
// 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
// 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 name1
2
3
4
5
6
7
// 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";
} - 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
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>
- Get parameters using Servlet api(The DispatcherServlet will inject
the request parameter for you)
- 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
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, likeModel
,Map
,ModelMap
, all use ModelAndView internally to share data and return view1
2
3
4
5
6
7
8
9
10
11// Use model to shared data in the domain object
// Use view to render view
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 data1
2
3
4
5
public String testModel(Model model) {
model.addAttribute("dataKey", "dataValue");
return "success";
} - Use Map to share data
1
2
3
4
5
public String testMap(Map<String, Object> map) {
map.put("dataKey", "dataValue");
return "success";
} - Use SpringMVC
ModelMap
to share data1
2
3
4
5
public String testMap(ModelMap modelMap) {
modelMap.addAttribute("dataKey", "dataValue");
return "success";
}
- Use Servlet api to share data
- The session domain object
- Use Servlet API to share data
1
2
3
4
5
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
- Use Servlet API to share data
- The application(servlet context) domain object
- Use Servlet API to share data
1
2
3
4
5
6
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>
- Use Servlet API to share data
- 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
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
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>
- SpringMVC has different views,
- 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();
});
})
- 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 body1
2
3
4
5
public String testRequestBody( String requestBody){
System.out.println(requestBody);
return "redirect:/success";
} - Use
RequestEntity
to get the whole request entity1
2
3
4
5
6
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
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 body1
2
3
4
5
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 generateMappingJackson2HttpMessageConverter
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
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 file1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 forMultipartFile
: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>
- Incepter
- Request handling process: filter -> DispatcherServlet -> intercepter:preHandle -> controller(requestMappingHandler) -> intercepter:postHandle -> render view -> intercepter:afterCompletion
- Interceptor Steps
- Create a class that implements
HandlerInterceptor
and overrides 3 methods1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import 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
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
} - 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>
- Create a class that implements
- 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
- 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
public class ExceptionController {
// If exception occurs, add exception to request domain, jump to error page
public String handler(Exception ex, Model model) {
model.addAttribute("ex", ex);
return "error";
}
}
- XML-based exception mapping: if exception occurs, jump to a specific
page
- 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
38package 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
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
// SpringMVC configuration class
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
// Configure DispatcherServlet servlet-mapping
protected String[] getServletMappings() {
return new String[]{"/"};
}
// Configure filters
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 Thymeleaf1
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
86package 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
// Enable component-scan
// Enable mvc annotation driven
public class SpringMVCConfig {
// Config thymeleaf
// 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;
}
public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
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
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// enable default servlet handler
configurer.enable();
}
// Configure interceptors
// This method needs to implement WebMvcConfigurer
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
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}
// ExceptionHandler
// This method needs to implement WebMvcConfigurer
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
- 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
<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
<!-- 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
5package com.yanxuanshaozhu.mapper;
public interface UserMapper {
int insertUser();
} - A test
1
2
3
4
5
6
7
8
9
10
11
12
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
orresultMap
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, usingresultType
is much simpler.resultType
orresultMap
is either fully-qualified class name or type alias1
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>
- Dependencies:
- 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
,..., orparam1
,param2
, .... This is because MyBatis creates a map to store the paremeters1
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
4Map<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 map1
User checkLogin( String username, String password);
1
2
3<select id="checkLogin" resultType="User">
select * from t_user where username=#{username} and password = #{password};
</select>
- Two approaches:
- MyBatis Query
- Query for POJO
1
User getUserById( 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 includestring
,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( 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>
- One result
- String fuzzy match
1
List<User> getUsersByFuzzyNameMatch( 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( 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( 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
4User 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
- Query for POJO
- 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 adid
that is a foreign key and maps to thedid
oft_dept
. Then we need to add aDept
field in theEmp
class. Here are some examples for handling queries with join operation in this situation1
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 adid
that is a foreign key and maps to thedid
oft_dept
. Then we need to add aEmps
field in theDept
table, which is a list of employees in that department. Here are some examples for handling queries with join operation in this situation1
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>
- Dynamic SQL
- A series of xml tags used for conditionally concatenating strings of SQL together
<if test=""></if>
expression1
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="">
expression1
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>
expression1
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>
expression1
int deleteEmpByIds( 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( 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>
expression1
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>
- 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
- By default enabled, the data is cached in the
- Second level cache
- Steps to enable second level cache
- 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> - Add
<cache />
to the maper file where you want to enable 2nd level cache - 2nd level cache is only in effect when the sqlsession is closed or committed, before that data is cached into 1st level cache
- The POJO class must
implements Serializable
- Add the following configurations to the Mybatis core configuration
xml. This step can be omited because the default value is already true
- 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
- Steps to enable second level cache
- You can use a 3rd-party Java-based cache framework
EHCACHE
to replace Mybatis 2nd lvl cache1
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>
- MyBatis reverse engineering
- MyBatis revers enginering: Mybatis automatically generate POJO, Java interface, mapper.xml for you based on pre configurations
- Steps
- 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
<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> - Use
mybatis-generator:generate
undermybatis-generator
in maven settings to generate JavaBeans, mapper interfaces, mapper xml files
- Add the following dependicies and plugins
- 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
- Add a dependency
尚硅谷SSM整合
- 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
<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 insidespring.xml
(with the help ofmybatis-spring
dependency)- Configure
SqlSessionFactoryBean
, this bean addsSqlSessionFactory
to Spring IOC container1
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
public class EmpServiceImpl implements EmpService{
private SqlSessionFactory factory;
} - Configure
MapperScannerConfigurer
( this should work together withSqlSessionFactoryBean
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 SqlSessionFactory1
2
3<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.yanxuanshaozhu.ssm.mapper"></property>
</bean>1
2
3
4
5
public class EmpServiceImpl implements EmpService{
private EmpMapper mapper;
}
- Configure
- 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>
- Dependencies and plugins
尚硅谷SpringBoot
- 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. Thespring-boot-starter-parent
has common dependencies included, if you need to modify a specific dependency, find the key and add a property inpom.xml
like this1
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 useclasspath: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
.springframework.context.annotation.Configuration
public class Configuration {
public User user01() {
return new User(9, "Cangwei Xiao");
}
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
- 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
public User user01() {
return new User();
} - Add XML-based configurations to SpringBoot configuration
1
2
3
4
5
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
public class Car {
private String brand;
private Integer price;
}1
2"Porsche" =
10222 = - Approach 2: let the configuration class know that a specific java
class should be added to container
1
2
3
public class MyConfig {
}1
2
3
4
5
public class Car {
private String brand;
private Integer price;
}
- Approach 1: add the java class to container and set configuration
properties
- Analysis of
@SpringBootApplication
=@SpringBootConfiguration
(=@Configuration
) +@ComponentScan
+@EnableAutoConfiguration
(register packages to conatiner in batch) - Use
debug=true
inapplication.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
- Add components:
- SpringBoot Web Development
- For most cases, SpringBoot provides auto-configuration of SpringMVC
- Access Static resources:
- By default,
/static
,/public
,/resources
,/META-INF/resources
directories insideclasspath
can hold static resources. These resourecs can be accessed fromurl:port/project/
+ resource name. Processing order: controller -> default controller for static resources. Here's setting to change the default static resource location toclasspath:/sr/
1
2
3spring:
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 name1
2
3spring:
mvc:
static-path-pattern: /resources/**
- By default,
- 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
ifspring.mvc.hiddenmethod.filter.enabled
is provided and its value is true1
2
3
4
5
6
7// related source code
1
2
3
4
5spring:
mvc:
hiddenmethod:
filter:
enabled: true1
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
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 likejackson
- 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
the
MappingJackson2HttpMessageConverter
to convert any type of data to JSON viaObjectMapper
insidejackson
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
andViewResolver
for Thymeleaf, no further configuration is needed
- Add the following dependency
- Use Thymeleaf in SpringBoot
- Use interceptor for login purpose
- Create a class that implements
HandlerInterceptor
and overrides 3 methods - Create a class that implements
WebMvcConfigurer
and overridesaddInterceptors
method, notice access for static resources should not be blocked1
2
3
4
5
6
7
public void addInterceptors(InterceptorRegistry registry) {
// new your interceptor class
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // block all
.excludePathPatterns("/", "/login", "/static/**");
}
- Create a class that implements
- 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
public String upload( MultipartFile singleFile,
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
5spring:
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 awhitelabel
error view - To replace the default behavior, you can implement
ErrorController
and register a bean definition of that type or add a bean of typeErrorAttributes
to use the existing mechanism but replace the contents - Custom error pages: create an
error
folder under the static resources folders or under thetemplates
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(eg404.html
or4xx.html
) - Customized exception
1
2
3
4
5
6
7
8
9
public class MyError extends RuntimeException{
public MyError() {
}
public MyError(String msg) {
super(msg);
}
}
- Spring Boot provides an
- 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
public class MyServlet extends HttpServlet {
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
Filter.super.destroy();
}
}1
2
3
4
5
6
7
8
9
10
11
12
public class MyListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
System.out.println("MyListener initialized!!!!!!");
}
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("MyListener destroyed!!!!!!");
}
} - Use
ServletRegistrationBean
,FilterRegistrationBean
,ServletListenerRegistrationBean
classes1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyRegistrationBean {
public ServletRegistrationBean servletBean() {
MyServlet servlet = new MyServlet();
return new ServletRegistrationBean(servlet, "/*");
}
public FilterRegistrationBean filterBean() {
MyFilter filter = new MyFilter();
FilterRegistrationBean filterBean = new FilterRegistrationBean(filter);
String[] patterns = new String[] {"/*"};
filterBean.setUrlPatterns(Arrays.asList(patterns));
return filterBean;
}
public ServletListenerRegistrationBean listenerBean() {
MyListener listener = new MyListener();
return new ServletListenerRegistrationBean(listener);
}
}
- 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
9spring:
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.Driver1
2
3
4
5
6
7
8
9
10
11
public class MyDataSourceConfig {
// Bind the c3p0 in the properties/yml file, so no need to configure
// datasource here
public DataSource dataSource() {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
return dataSource;
}
}
- In order to use
- Include the following dependencies
- 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 thatDataSource
as an input using theSqlSessionFactoryBean
- Create and register a
SqlSessionTemplate
from theSqlSessionFactory
- Auto-scan the mappers, link them to the
SqlSessionTemplate
and register them to Spring context so they can be injected into beans
- Autodetect an existing
- Usage
1
2
3
4
5
6
7
8
9// This @Mapper can be replaced with one annotation on the configuration class
// @MapperScan("package");
public interface EmpMapper {
Integer amount();
// For complicated mappings, you can use xml together with annotation
List<Emp> getEmpLargerId( 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
public class DBTest {
EmpMapper mapper;
public void test () {
int res = mapper.amount();
System.out.println(res);
}
public void test2() {
List<Emp> res = mapper.getEmpLargerId(1);
for (Emp emp : res) {
System.out.println(emp);
}
}
}1
2
3
4
5
6mybatis:
# 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>
- Include the following dependency
- Basic Configuration
尚硅谷Redis
- 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++
, usegcc -v
to check gcc version - Install redis: use
redis-server -v
to check redis version, installation1
2
3
4
5
6curl -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
toprotected-mode no
- Comment
bind 127.0.0.1
to allow remote access - Add password:
requirepass <pwd>
- Change
- 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>
- Install C++ environment installation
- 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
- Keys:
- 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 theredis.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
- Redis transaction
- Use
multi
to start transaction, usediscard
to give up all commands in transaction and exit transaction, useexec
to execuate all commands in transaction and exit transaction, usewatch
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
- Use
- 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
- Write all commands in a script file, for exampl
- 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)
- Redis replication
- A replica machine can have its own replica machine
- In replica machine, edit the
redis.conf
, addreplicaof <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, usesalaveof no one
to set the current machine as master with no replications
- 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 onredis.io
1
2
3
4
5
6sentinel 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)
- 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
) - If m sentinels agree that the master is not
reachable(
SDOWN
), the master instance is considered to be objectively down (ODOWN
) - 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)
- 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
inredis.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 salveof no one
on the new master,slaveof host port
on replicas
- Sentinels periodically ping the redis master instance (called heart
beat),
- Start redis sentinel:
redis-sentinel <sentinel.conf> --sentinel
- Sentinel + redis replication does not ensure 0 data loss
- 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
- Simple hashing: hash(key) / node amount, difficult for sacling
- Consistent hashing: independent of the number of servers or objects in the hash table, can have data skew
- 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 cluster1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20cluster-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
7String 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");
- Sample code
- 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();
- Include the dependency
- 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
13spring:
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
public class TestService {
// Here are multiple classes, so provide
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);
}
}
- Configuration file
- Redis cluster connection
- Configuration file
application.yml
:1
2
3
4
5
6
7
8
9
10
11
12
13spring:
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
9spring:
data:
redis:
lettuce:
cluster:
refresh:
adaptive: true
# default refresh period is 60 seconds
period: 2000
- Configuration file
- Include the dependency
尚硅谷SpringCloud
- 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