文章目錄
收藏這三篇筆記,完整回顧Spring常見問題及使用方式速查:
0. 基本概念
- Spring 框架提供的JDBC支持主要由四個包組成,分別是 core(核心包)、object(對象包)、dataSource(數據源包)和 support(支持包)。
<!-- JDBC模板 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- 事務控制 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- mysql依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
1. JDBCTemplate使用示例
1.1 數據源及配置
數據源的基本類即爲 org.springframework.jdbc.dataSource.DriverManagerDataSource
,主要功能是獲取數據庫連接,還可以引入緩衝池、分佈式事務等,需要進行以下配置:
<!-- 配置數據源 -->
<bean id="dataSource" class="org.springframework.jdbc.dataSource.DriverManagerDataSource">
<!-- 數據庫驅動-->
<!-- 注意:com.mysql.jdbc.Driver 在`8.0.x`的mysql驅動中已廢棄 -->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<!-- 連接數據庫的url,需要同步時區-->
<property name= "url" value="jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai" />
<!-- 連接數據庫的用戶名 -->
<property name="username" value="$root" />
<!-- 連接數據庫的密碼 -->
<property name="password" value="$password" />
<!-- 連接池的配置 -->
<property name="initialPoolSize" value="3"></property>
<property name="maxPoolSize" value="10"></property>
<property name="maxStatements" value="100"></property>
<property name="acquireIncrement" value="2"></property>
</bean>
核心類是 org.springframework.jdbc.core.JDBCTemplate
,需要載入數據源進行實例化:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.jdbcTemplate">
<!--默認必須使用數據源-->
<property name="dataSource" ref="dataSource"/>
</bean>
也可以使用配置文件類完成以上配置:
@Bean
public DriverManagerDataSource dataSource(){
String url = "jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai";
String username = "root";
String password = "$password";
DriverManagerDataSource d = new DriverManagerDataSource(url, username, password);
d.setDriverClassName("com.mysql.cj.jdbc.Driver");
return d;
}
@Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}
1.2 數據庫配置
假設數據庫有表 student
:
CREATE TABLE student (
id BIGINT(20) NOT NULL COMMENT '主鍵ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年齡',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
PRIMARY KEY (id)
);
INSERT INTO student (id, name, age, email) VALUES
(1, 'Jack', 28, '[email protected]'),
(2, 'Louis', 20, '[email protected]'),
(3, 'Tom', 24, '[email protected]'),
(4, 'Sandy', 12, '[email protected]'),
(5, 'Lily', 85, '[email protected]');
以及對應的Model:
@Data
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private String email;
}
1.3 查詢語句示例
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
jdbcTemplate.query(
"SELECT * FROM student;",
(resultSet, i) -> new Student(resultSet.getInt(1), resultSet.getString(2), resultSet.getInt(3), resultSet.getString(4))
).forEach(System.out::println);
2. Spring事務控制
- 事務控制一般在Service層,分爲“編程式事務”和“聲明式事務”;Sring提供的是後者,基於AOP實現。
- Spring基於JDBC的事務管理器爲
DataSourceTransactionManager
。
2.1 配置文件
由於Spring的事務控制是基於AOP實現的,因此也需要引入AOP的命名空間。
<beans xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
2.2 事務接口
Spring-tx
(即Spring Transaction)中的三個核心接口爲:
PlatformTransactionManager
:用於管理事務。TransactionDefinition
:用於定義事務的的相關信息。TransactionStatus
:描述事務的狀態。
2.2.1 事務管理:PlatformTransactionManager
TransactionStatus getTransaction(TransactionDefinition definition)
:用於獲取事務狀態信息。void commit(TransactionStatus status)
:用於提交事務。void rollback(TransactionStatus status)
:用於回滾事務。
2.2.2 事務信息:TransactionDefinition
String getName()
:獲取事務對象名稱。int getIsolationLevel()
:獲取事務的隔離級別。int getPropagationBehavior()
:獲取事務的傳播行爲。int getTimeout()
:獲取事務的超時時間。boolean isReadOnly()
:獲取事務是否只讀。
其中,事務的傳播行爲被定義爲:
屬性名稱 | 描述 | 的值 |
---|---|---|
PROPAGATION_REQUIRED | 如果當前有事務環境就加入當前正在執行的事務環境,否則就新建一個事務。【默認】 | REQUIRED |
PROPAGATION_SUPPORTS | 指定當前方法加入當前事務環境,如果當前沒有事務,就以非事務方式執行。 | SUPPORTS |
PROPAGATION_MANDATORY | 指定當前方法必須加入當前的事務環境,如果當前沒有事務,則拋出異常。 | MANDATORY |
PROPAGATION_REQUIRES_NEW | 將創建新的事務,如果當前方法已經在事務中,則將當前新建的事務掛起等待執行。 | REQUIRES_NEW |
PROPAGATION_NOT_SUPPORTED | 不支持當前事務,以非事務狀態執行。如果當前存在事務環境,則將其掛起等待當前方法先執行。 | NOT_SUPPORTED |
PROPAGATION_NEVER | 不支持當前事務,如果當前方法在事務中,則拋出異常。 | NEVER |
PROPAGATION_NESTED | 指定當前方法執行時,如果已經有一個事務存在,則運行在這個嵌套的事務中。如果當前環境沒有運行的事務,就新建並與父事務相互獨立的新事務,這個事務擁有多個可以回滾的保存點——內部事務回滾不會對外部事務造成影響(只對 DataSourceTransactionManager 事務管理器起效)。 |
NESTED |
2.2.3 事務狀態:TransactionStatus
void flush()
:刷新事務。boolean hasSavepoint()
:獲取是否存在保存點。boolean isCompleted()
:獲取事務是否完成。boolean isNewTransaction()
:獲取是否是新事務。boolean isRollbackOnly()
:獲取是否回滾。void setRollbackOnly()
:設置事務回滾。
2.3 Spring的聲明式事務管理
構建一個轉賬的數據庫事務場景,則數據庫表如下:
CREATE TABLE account (
id INT (11) PRIMARY KEY AUTO_INCREMENT COMMENT '主鍵ID',
username VARCHAR(20) NOT NULL COMMENT '姓名',
money INT DEFAULT NULL COMMENT '賬戶餘額'
);
/* 初始數據 */
INSERT INTO account VALUES (1, '張三', 2000);
INSERT INTO account VALUES (2, '李四', 1000);
對應的Model類:
@Data
@AllArgsConstructor
public class Account {
private Integer id;
private String username;
private Integer money;
}
出具持久層:
@Repository(value = "accountDao")
public class AccountDaoImpl implements AccountDaoInterface{
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void send(int money, Account sbPaid) {
val sql = "UPDATE account SET money=money-? WHERE username=?;";
this.jdbcTemplate.update(sql, money, sbPaid.getUsername());
}
@Override
public void receive(int money, Account sbReceived) {
val sql = "UPDATE account SET money=money+? WHERE username=?;";
this.jdbcTemplate.update(sql, money, sbReceived.getUsername());
}
@Override
public Account query(Account account){
val sql = "SELECT * FROM account WHERE username=?";
return this.jdbcTemplate.query(sql,
(res, index) -> new Account(res.getInt(1),
res.getString(2),
res.getInt(3)
),
account.getUsername()).get(0);
}
}
業務邏輯爲:
@Service(value = "accountService")
public class AccountServiceImpl implements AccountServiceInterface {
private AccountDaoInterface accountDao;
@Autowired
public void setAccountDao(AccountDaoInterface accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(Account from, Account to, int money) throws Throwable {
this.accountDao.send(money, from);
if(money >= 1000) throw new Exception("轉賬超過限額");
this.accountDao.receive(money, to);
}
@Override
public Account query(Account account){
return this.accountDao.query(account);
}
}
測試類:
AccountServiceInterface accountService = (AccountServiceInterface)context.getBean("accountService");
System.out.println(accountService.query(new Account(1, "張三", -1)));
System.out.println(accountService.query(new Account(2, "李四", -1)));
accountService.transfer(
new Account(1, "張三", -1),
new Account(2, "李四", -1), 500); // 改成1000之後將會中斷轉賬
System.out.println(accountService.query(new Account(1, "張三", -1)));
System.out.println(accountService.query(new Account(2, "李四", -1)));
2.3.1 示例①:基於XML的聲明式事務管理
<!-- 編寫通知:對事務進行增強(通知),需要編寫切入點和具體執行事務的細節 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 給切入點方法添加事務詳情:
name :表示方法名稱(*表示任意方法名稱)
propagation :用於設置傳播行爲
isolation :表示隔離級別
rollback-for :需要回滾的異常類 -->
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<!-- aop編寫,讓Spring自動對目標生成代理,需要使用AspectJ的表達式 -->
<aop:config>
<!-- 切入點 -->
<aop:pointcut expression="execution(* MVC.Service.AccountServiceImpl.transfer(..))" id="txPointCut" />
<!-- 通知器:將切入點與通知整合 -->
<aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
</aop:config>
2.3.2 tx:method的屬性詳解
屬性 | 類型 | 默認值 | 說明 |
---|---|---|---|
propagation | propagation枚舉 | REQUIRED | 事務傳播屬性(詳見2.2.2) |
isolation | isolation枚舉 | DEFAULT(所用數據庫默認級別) | 事務隔離級別 |
read-only | boolean | false | 是否才用優化的只讀事務 |
timeout | int | -1 | 超時(秒) |
rollbackFor | Class[] | {} | 需要回滾的異常類 |
rollbackForClassName | String[] | {} | 需要回滾的異常類名 |
noRollbackFor | Class[] | {} | 不需要回滾的異常類 |
noRollbackForClassName | String[] | {} | 不需要回滾的異常類名 |
- 若
read-only
的值爲true
,則爲只讀事務,即連接點內無INSERT
等寫操作。 - 隔離等級共有五級:
DEFAULT
:【默認】採用數據庫默認隔離級別。SERIALIZABLE
:最嚴格的級別,事務串行執行,資源消耗最大。REPEATABLE_READ
:保證了一個事務不會修改已經由另一個事務讀取但未提交的數據。避免了“髒讀取”和“不可重複讀取”的情況,但是帶來了更多的性能損失。READ_COMMITTED
:大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的數據,避免了“髒讀取”。該級別適用於大多數系統。READ_UNCOMMITTED
:保證了讀取過程中不會讀取到非法數據。隔離級別在於處理多事務的併發問題。
2.3.3 示例②:基於註解的聲明式事務管理
在配置文件中開啓事務註解的驅動及註冊事務管理器:
<!-- 配置事務管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 註冊事務管理驅動 -->
<tx:annotation-driven transaction-manager="txManager"/>
修改業務層:
@Service(value = "accountService")
// 添加此註釋,參數與 2.3.1 & 2.3.2 小節中一致
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = {Exception.class})
public class AccountServiceImpl implements AccountServiceInterface {
private AccountDaoInterface accountDao;
@Autowired
public void setAccountDao(AccountDaoInterface accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(Account from, Account to, int money) throws Throwable {
this.accountDao.send(money, from);
if(money >= 1000) throw new Exception("轉賬超過限額");
this.accountDao.receive(money, to);
}
@Override
public Account query(Account account){
return this.accountDao.query(account);
}
}
附錄 學習筆記①~③代碼及工程文件
- (下載後修改爲zip文件) —— springtest.pdf
- 想看更多?——SpringBoot、Restful、文件上傳…