1. Spring JdbcTemplate
- JdbcTemplate是Spring對JDBC的封裝,目的是使JDBC更加易於使用
- JdbcTemplate處理了資源的建立和釋放。幫助我們避免一些常見的錯誤,比如忘了總要關閉連接
- JdbcTemplate運行核心的JDBC工作流,如Statement的建立和執行,而我們只需要提供SQL語句和提取結果
1.1 配置並測試數據源
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mashibing</groupId>
<artifactId>spring_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.oracle/ojdbc6 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4.0-atlassian-hosted</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>
</project>
- dbconfig.properties
jdbc.username=c50hst
jdbc.password=c50hst
jdbc.url=jdbc:oracle:thin:@192.168.15.110:1521:fcrhost
jdbc.driverClassName=oracle.jdbc.OracleDriver
- applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
</beans>
- MyTest.java
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource);
System.out.println(dataSource.getConnection());
}
}
1.2 給spring容器添加JdbcTemplate
- pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
- jdbcTemplate.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
</beans>
- MyTest.java
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
System.out.println(jdbcTemplate);
}
}
1.3 插入數據
- MyTest.java
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into emp(empno,ename) values(?,?)";
int result = jdbcTemplate.update(sql, 1111, "zhangsan");
System.out.println(result);
}
}
1.4 批量插入數據
- MyTest.java
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into emp(empno,ename) values(?,?)";
List<Object[]> list = new ArrayList<Object[]>();
list.add(new Object[]{1,"zhangsan1"});
list.add(new Object[]{2,"zhangsan2"});
list.add(new Object[]{3,"zhangsan3"});
int[] result = jdbcTemplate.batchUpdate(sql, list);
for (int i : result) {
System.out.println(i);
}
}
}
1.5 查詢某個值,並以對象的方式返回
- MyTest.java
import com.mashibing.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select * from emp where empno = ?";
Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 7369);
System.out.println(emp);
}
}
1.6 查詢返回集合對象
- MyTest.java
import com.mashibing.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
import java.util.List;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select * from emp where sal > ?";
List<Emp> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class), 1500);
for (Emp emp : query) {
System.out.println(emp);
}
}
}
1.7 返回組合函數的值
- MyTest.java
import com.mashibing.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
import java.util.List;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select max(sal) from emp";
Double aDouble = jdbcTemplate.queryForObject(sql, Double.class);
System.out.println(aDouble);
}
}
1.8 使用具備具名函數的JdbcTemplate
- 具名參數:允許SQL按名稱(以冒號開頭、名隨意)而不是按位置進行指定,否則一旦參數的順序發生變化, 就必須改變參數綁定
- 具名參數更易於維護,也提升了可讀性。具名參數由框架類在運行時用佔位符取代
- 具名參數只在 NamedParameterJdbcTemplate 中得到支持,所以需要先引入該類,將其交給Spring管理
- jdbcTemplate.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
</beans>
- MyTest.java
import com.mashibing.bean.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
NamedParameterJdbcTemplate jdbcTemplate = context.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);
String sql = "insert into emp(empno,ename) values(:empno,:ename)";
Map<String,Object> map = new HashMap<>();
map.put("empno",2222);
map.put("ename","sili");
int update = jdbcTemplate.update(sql, map);
System.out.println(update);
}
}
1.9 整合EmpDao
- jdbcTemplate.xml
<context:component-scan base-package="com.mashibing"></context:component-scan>
- EmpDao.java
package com.mashibing.dao;
import com.mashibing.bean.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
public class EmpDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(Emp emp){
String sql = "insert into emp(empno,ename) values(?,?)";
int update = jdbcTemplate.update(sql, emp.getEmpno(), emp.getEname());
System.out.println(update);
}
}
- MyTest.java
import com.mashibing.bean.Emp;
import com.mashibing.dao.EmpDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
EmpDao empDao = context.getBean("empDao", EmpDao.class);
empDao.save(new Emp(3333,"wangwu"));
}
}
2 聲明式事務
- 編程式事務:在代碼中顯式調用setAutoCommit、commit、rollback等方法來處理事務
- 聲明式事務:採用在方法的外部添加註解或直接在配置文件中進行聲明的方式來處理事務
- 聲明式事務是建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務
- 聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基於@Transactional註解的方式),便可以將事務規則應用到業務邏輯中
2.1 環境準備
- 數據庫
create table BOOK
(
id NUMBER not null,
book_name VARCHAR2(256),
price NUMBER
);
create table BOOK_STOCK
(
id NUMBER not null,
stock NUMBER
);
create table ACCOUNT
(
username VARCHAR2(10) not null,
balance NUMBER
);
insert into book (ID, BOOK_NAME, PRICE)
values (1, 'book1', 100);
insert into book (ID, BOOK_NAME, PRICE)
values (2, 'book2', 100);
insert into book (ID, BOOK_NAME, PRICE)
values (3, 'book3', 100);
insert into book (ID, BOOK_NAME, PRICE)
values (4, 'book4', 100);
insert into book_stock (ID, STOCK)
values (1, 1000);
insert into book_stock (ID, STOCK)
values (2, 1000);
insert into book_stock (ID, STOCK)
values (3, 1000);
insert into book_stock (ID, STOCK)
values (4, 1000);
insert into account (USERNAME, BALANCE)
values ('lisi', 1000);
insert into account (USERNAME, BALANCE)
values ('zhangsan', 1000);
- BookDao.java
package com.mashibing.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 減去某個用戶的餘額
* @param userName
* @param price
*/
public void updateBalance(String userName,int price){
String sql = "update account set balance=balance-? where username=?";
jdbcTemplate.update(sql,price,userName);
}
/**
* 按照圖書的id來獲取圖書的價格
* @param id
* @return
*/
public int getPrice(int id){
String sql = "select price from book where id=?";
return jdbcTemplate.queryForObject(sql,Integer.class,id);
}
/**
* 減庫存,減去某本書的庫存
* @param id
*/
public void updateStock(int id){
String sql = "update book_stock set stock=stock-1 where id=?";
jdbcTemplate.update(sql,id);
}
}
- BookService.java
package com.mashibing.service;
import com.mashibing.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Autowired
BookDao bookDao;
/**
* 結賬:傳入哪個用戶買了哪本書
* @param username
* @param id
*/
public void checkout(String username,int id){
bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}
}
- MyTest.java
import com.mashibing.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.checkout("zhangsan","1");
}
}
- tips:選中類名後,ctrl+h/右側hierarchy,可以查看該類的繼承關係,hierarchy:等級制度
2.2 聲明式事務的簡單配置
- Spring的核心事務管理抽象是PlatformTransactionManager。它爲事務管理封裝了一組獨立於技術的方法。無論使用Spring的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的
- 事務管理器可以以普通的bean的形式聲明在Spring IOC容器中
- jdbcTemplate.xml:在配置文件中添加事務管理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:component-scan base-package="com.mashibing"></context:component-scan>
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--事務控制-->
<!--配置事務管理器的bean-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--開啓基於註解的事務控制模式,依賴tx名稱空間-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
- BookService.java:在需要開啓事務的方法上,加入@Transactional註釋
package com.mashibing.service;
import com.mashibing.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookService {
@Autowired
BookDao bookDao;
@Transactional
public void checkout(String username,int id){
bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}
}
3 @Transactional的屬性
3.1 timeout
- 事務超出指定執行時長後自動終止並回滾,單位是秒
//超過3s自動回滾
@Transactional(timeout = 3)
3.2 readOnly
- 設置事務是否爲只讀事務
- 如果配置了只讀事務
- 配置了只讀事務的方法中,不允許對數據進行修改,修改就會拋出異常
- 其他方法即使修改了數據,在只讀事務的方法中,多次查詢到的結果依然一致
- 如果你一次執行單條查詢語句,則沒有必要啓用事務支持,但如果你一次執行多條查詢語句,例如統計查詢,報表查詢,在這種場景下,多條查詢SQL必須保證整體的讀一致性,否則,在前條SQL查詢之後,後條SQL查詢之前,數據被其他用戶改變,則該次整體的統計查詢將會出現讀數據不一致的狀態,此時,應該啓用事務支持
- Spring 設置的readOnly事務屬性對oracle來說完全無效
- Oracle 數據庫本身也是支持只讀事務的,也就是隻讀模式,但是文檔很明確的說明了只讀模式只能通過oracle數據庫本身進行設置
- 由於只讀事務不存在數據的修改,因此數據庫還會爲只讀事務提供一些優化手段
//
@Transactional(readOnly = true)
3.3 noRollbackFor
- 遇到異常時,事務不回滾
- Spring默認情況下,遇到RunTimeException纔會進行事務回滾,如果遇到checked異常,不會回滾
- 可以通過noRollbackFor、noRollbackForClassName、rollbackFor、rollbackForClassName等屬性改變對異常的默認處理
//讓unchecked例外不回滾
@Transactional(notRollbackFor=RunTimeException.class)
3.4 noRollbackForClassName
- 遇到異常時,事務不回滾,值必須爲全限定類名
- BookService.java
package com.mashibing.service;
import com.mashibing.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookService {
@Autowired
BookDao bookDao;
/**
* 結賬:傳入哪個用戶買了哪本書
* @param username
* @param id
*/
@Transactional(timeout = 3,noRollbackFor = {ArithmeticException.class,NullPointerException.class})
public void checkout(String username,int id){
bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
int i = 1/0;
}
@Transactional(timeout = 3,noRollbackForClassName = {"java.lang.ArithmeticException"})
public void checkout(String username,int id){
bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
int i = 1/0;
}
}
3.5 rollbackFor
- 設置哪些異常事務需要回滾
//讓checked例外也回滾
@Transactional(rollbackFor=Exception.class)
3.6 rollbackForClassName
- 遇到異常時,事務回滾,值必須爲全限定類名
- BookService.java
package com.mashibing.service;
import com.mashibing.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@Service
public class BookService {
@Autowired
BookDao bookDao;
/**
* 結賬:傳入哪個用戶買了哪本書
* @param username
* @param id
*/
@Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class})
public void checkout(String username,int id) throws FileNotFoundException {
bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
// int i = 1/0;
new FileInputStream("aaa.txt");
}
}
3.7 isolation
-
設置事務的隔離級別
-
從上往下,隔離級別越來越高,也就是數據越來越安全
- 讀未提交:Isolation.READ_UNCOMMITTED,A事務修改數據後,即使不提交,B事務也能看到該數據,稱爲B產生髒讀,即讀到了不存在的數據
- 讀已提交:Isolation.READ_COMMITTED,在B事務第一次查詢後,A事務修改數據後提交,此時B事務再次查詢,發現與第一次自己讀取到的數據不一致,稱B不可重複讀。讀已提交爲Oracle默認隔離級別
- 可重複讀:Isolation.REPEATABLE_READ,在B事務第一次查詢後,A事務新增或刪除數據後提交,此時B事務再次查詢,發現與第一次查詢時,獲得記錄數不同(對Oracle),或B事務查詢確實查不到,但想要插入相同記錄,又會報錯違反唯一約束(mysql),稱爲B幻讀。可重複讀爲mysql默認隔離級別
- 序列化:Isolation.SERIALIZABLE
-
幻讀與不可重複讀區別
- 不可重複讀的重點在於修改,同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了
- 幻讀的重點在於新增或者刪除,同樣的條件, 第1次和第2次讀出來的記錄數不一樣
-
各隔離級別下的數據問題
隔離級別\數據問題 髒讀 不可重複讀 幻讀 讀未提交 √ √ √ 讀已提交 √ √ 可重複讀 √ 序列化
3.8 propagation
- 設置事務的傳播特性
- 事務傳播特性用來描述由某一個事務傳播行爲修飾的方法被嵌套進另一個方法的時事務如何傳播
- Spring中七種事務傳播特性
傳播特性 | 描述 | 補充 |
---|---|---|
Propagation.REQUIRED | 如果有事務在運行,當前方法就在這個事務內運行,否則就啓動一個新事務,並在自己的事務內運行 | 如果A中調用B、C,而B使用了A的事務,那麼如果B拋出異常,即使在A中捕獲了該異常,A中的C仍然回滾 |
Propagation.NESTED | 如果有事務在運行,當前方法就在這個事務的嵌套事務內運行,否則就啓動一個新事務,並在自己的事務內運行 | 和REQUIRED差距就在於,同樣案例中,C不會回滾。因爲NESTED修飾的方法,一進去就會加入一個savepoint,當該方法拋出異常時,事務會回滾到該方法最初,並繼續執行 |
Propagation.REQUIRES_NEW | 當前的方法必須啓動新事務,並在它自己的事務內運行,如果有事務正在運行,應該將它掛起 | 就和Oracle中獨立事務的邏輯完全相同 |
Propagation.SUPPORTS | 如果有事務在運行,當前方法就在這個事務內運行,否則它可以不運行在事務中 | |
Propagation.NOT_SUPPORTED | 當前的方法不應該運行在事務中,如果有運行的事務,將它掛起 ,並使用新的數據庫連接。新的數據庫連接不使用事務 | |
Propagation.MANDATORY | 當前的方法必須運行在事務內部,如果沒有正在運行的事務,拋出異常 | |
Propagation.NEVER | 當前的方法不應該運行在事務中,如果有運行的事務,就拋出異常 |
3.8.1 測試事務的傳播特性
- BookDao.java
package com.mashibing.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 減去某個用戶的餘額
* @param userName
* @param price
*/
public void updateBalance(String userName,int price){
String sql = "update account set balance=balance-? where username=?";
jdbcTemplate.update(sql,price,userName);
}
/**
* 按照圖書的id來獲取圖書的價格
* @param id
* @return
*/
public int getPrice(int id){
String sql = "select price from book where id=?";
return jdbcTemplate.queryForObject(sql,Integer.class,id);
}
/**
* 減庫存,減去某本書的庫存
* @param id
*/
public void updateStock(int id){
String sql = "update book_stock set stock=stock-1 where id=?";
jdbcTemplate.update(sql,id);
}
/**
* 修改圖書價格
* @param id
* @param price
*/
public void updatePrice(int id,int price){
String sql = "update book set price=? where id =?";
jdbcTemplate.update(sql,price,id);
}
}
- BookService.java
package com.mashibing.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@Service
public class BookService {
@Autowired
BookDao bookDao;
/**
* 結賬:傳入哪個用戶買了哪本書
* @param username
* @param id
*/
@Transactional(propagation = Propagation.REQUIRED)
public void checkout(String username,int id) {
bookDao.updateStock(id);
int price = bookDao.getPrice(id);
bookDao.updateBalance(username,price);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(int id,int price){
bookDao.updatePrice(id,price);
int i = 1/0;
}
}
- MulService.java
package com.mashibing.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MulService {
@Autowired
private BookService bookService;
@Transactional
public void mulTx(){
bookService.checkout("zhangsan",1);
bookService.updatePrice(1,1000);
}
}
- MyTest.java
import com.mashibing.service.MulService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");
MulService mulService = context.getBean("mulService", MulService.class);
mulService.mulTx();
}
}
- 如果在BookService類中定義方法mulTx,也就是說mulTx方法和設置了事務傳播特性的checkout、updatePrice在同一個類中,那麼事務傳播特性會失效,它們整體遵循mulTx上的事務
4 基於xml的事務配置
jdbcTemplate.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:component-scan base-package="com.mashibing"></context:component-scan>
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--事務控制-->
<!--配置事務管理器的bean-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
基於xml配置的事務:依賴tx名稱空間和aop名稱空間
1、spring中提供事務管理器(切面),配置這個事務管理器
2、配置出事務方法
3、告訴spring哪些方法是事務方法(事務切面按照我們的切入點表達式去切入事務方法)
-->
<bean id="bookService" class="com.mashibing.service.BookService"></bean>
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.mashibing.service.*.*(..))"/>
<!--事務建議:advice-ref:指向事務管理器的配置-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"></aop:advisor>
</aop:config>
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事務屬性-->
<tx:attributes>
<!--指明哪些方法是事務方法-->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
</beans>