Spring(10)深入Spring 資料庫事務管理(二)
一、Spring事務配置
1.程式設計式事務
程式設計時事務以程式碼方式管理事務,就是說事務由開發者通過程式碼方式實現,這裡需要使用一個事務定類介面TransactionDefinition,我們使用預設實現類DefaultTransactionDefinition就可以。這裡不做重點介紹。
2.宣告式事務
程式設計式事務是一種約定型的事務,在大部分情況下用資料庫事務時,大部分的場景是在程式碼中發生了異常時,需要回滾事務,而不發生異常時則是提交事務,從而保證資料庫資料的一致性。從這點出發,Spring 給了一個約定 (AOP 開發也給了我們一個約定),
如果使用的是宣告式事務,當業務方法不發生異常(或者發生異常 但該異常也被配置資訊允許提交事務)時, Spring 就會讓事務管理器提交事務。而發生異常(並且該異常不被你的配置資訊所允許提交事務)時,則讓事務管理器回滾事務。
3.基於XML的宣告式事務控制(配置方式)重點
(1)環境搭建
1》引入依賴包
<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.xhbjava</groupId> <artifactId>Spring02</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties><dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> </dependencies> </project>
2》建立Spring配置檔案並引入約束
需要匯入 aop 和 tx 兩個名稱空間<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns="http://www.springframework.org/schema/beans" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
3》準備實體類和資料庫表
這裡我們還是以以前建立好的account表和類進行。
4》業務介面和實現層編寫
package com.xhbjava.service; import com.xhbjava.pojo.Account; /** * 賬戶的業務層介面 * * @author mr.wang * */ public interface IAccountService { /** * 根據id查詢賬戶資訊 * @param id * @return */ Account findAccountById(Integer id); /** * 轉賬 * * @param sourceName * @param targeName * @param money */ void transfer(String sourceName,String targeName,Float money); }
package com.xhbjava.service.impl; import com.xhbjava.dao.IAccountDao; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountService; /** * 賬戶業務層介面實現類 * * @author mr.wang * */ public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public Account findAccountById(Integer id) { // TODO Auto-generated method stub return accountDao.findAccountById(id); } @Override public void transfer(String sourceName, String targeName, Float money) { // TODO Auto-generated method stub // 1.根據名稱查詢兩個賬戶 Account source = accountDao.findAccountByName(sourceName); Account target = accountDao.findAccountByName(targeName); // 2.修改兩個賬戶的金額 source.setMoney(source.getMoney() - money);// 轉出賬戶減錢 target.setMoney(target.getMoney() + money);// 轉入賬戶加錢 // 3.更新兩個賬戶 accountDao.updateAccount(source); int i = 1 / 0; accountDao.updateAccount(target); } }
5》Dao層介面和實現類類
package com.xhbjava.dao; import com.xhbjava.pojo.Account; /** * 賬戶持久層介面 * * @author mr.wang * */ public interface IAccountDao { /** * 根據id查詢賬戶 * * @param id * @return */ Account findAccountById(Integer id); /** * 根據名字查詢賬戶 * * @param sourceName * @return */ Account findAccountByName(String sourceName); /** * 更新賬戶 * * @param source */ void updateAccount(Account source); }
package com.xhbjava.dao.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import com.xhbjava.dao.IAccountDao; import com.xhbjava.pojo.Account; import com.xhbjava.pojo.AccountRowMapper; /** * 使用者持久層介面實現類 * * @author mr.wang * */ public class AccountDaoImpl implements IAccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Account findAccountById(Integer id) { List<Account> list = jdbcTemplate.query("select * from account where id = ? ", new AccountRowMapper(), id); return list.isEmpty() ? null : list.get(0); } @Override public Account findAccountByName(String sourceName) { List<Account> list = jdbcTemplate.query("select * from account where name = ? ", new AccountRowMapper(), sourceName); if (list.isEmpty()) { return null; } if (list.size() > 1) { throw new RuntimeException("結果集不唯一,不是隻有一個賬戶物件"); } return list.get(0); } @Override public void updateAccount(Account source) { // TODO Auto-generated method stub jdbcTemplate.update("update account set money = ? where id = ? ", source.getMoney(), source.getId()); } }
package com.xhbjava.pojo; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.jdbc.core.RowMapper; /** * 賬戶的封裝類 RowMapper 的實現類 * @author mr.wang * */ public 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; } }
6》Spring檔案中配置業務層和持久層以及資料等
<!-- 配置service --> <bean id="accountService" class="com.xhbjava.service.impl.AccountServiceImpl"> </bean> <!-- 配置dao --> <bean id="accountDao" class="com.xhbjava.dao.impl.AccountDaoImpl"> </bean> <!-- 資料庫模板配置 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </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/ssm?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean>
(2)事務配置
1》配置事務管理器
在Spring配置檔案中進行配置:
<!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean>
2》配置事務通知引用事務管理器
<!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean>
3》配置事務的屬性
<!-- 配置事務的通知引用事務管理器 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" read-only="false" propagation="REQUIRED" /> <tx:method name="find*" read-only="true" propagation="SUPPORTS" /> </tx:attributes> </tx:advice>指定方法名稱:是業務核心方法 read-only:是否是隻讀事務。預設 false,不只讀。 isolation:指定事務的隔離級別。預設值是使用資料庫的預設隔離級別。 propagation:指定事務的傳播行為。 timeout:指定超時時間。預設值為:-1。永不超時。 rollback-for:用於指定一個異常,當執行產生該異常時,事務回滾。產生其他異常,事務不回滾。沒有預設值,任何異常都回滾。 no-rollback-for:用於指定一個異常,當產生該異常時,事務不回滾,產生其他異常時,事務回滾。沒有預設值,任何異常都回滾。 4》配置 AOP 切入點表示式
<!--配置aop --> <aop:config> <!-- 配置切入點表示式 --> <aop:pointcut expression="execution(* com.xhbjava.service.impl.*.*(..))" id="pt1"/> <!-- 在 aop:config 標籤內部:建立事務的通知和切入點表示式的關係 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/> </aop:config>
5》配置切入點表示式和事務通知的對應關係
<!-- 在 aop:config 標籤內部:建立事務的通知和切入點表示式的關係 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
(3)測試
測試之前資料庫:
呼叫的方法:
執行測試方法:
測試後資料庫:
4.基於註解方式宣告式事務控制
(1)環境搭建
1》引入依賴包
略。
2》配置Spring配置檔案
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns="http://www.springframework.org/schema/beans" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置spring建立容器時要掃描的包 --> <context:component-scan base-package="com.xhbjava"></context:component-scan> <!-- 資料庫模板配置 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 資料庫配置 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> </beans>
3》建立資料庫表和實體類
略,參照上面即可。
4》建立業務層和Dao層介面和實現類並使用註解讓 spring 管理
package com.xhbjava.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.xhbjava.dao.IAccountDao; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountService; /** * 賬戶業務層介面實現類 * * @author mr.wang * */ @Service("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public Account findAccountById(Integer id) { // TODO Auto-generated method stub return accountDao.findAccountById(id); } @Override public void transfer(String sourceName, String targeName, Float money) { // TODO Auto-generated method stub // 1.根據名稱查詢兩個賬戶 Account source = accountDao.findAccountByName(sourceName); Account target = accountDao.findAccountByName(targeName); // 2.修改兩個賬戶的金額 source.setMoney(source.getMoney() - money);// 轉出賬戶減錢 target.setMoney(target.getMoney() + money);// 轉入賬戶加錢 // 3.更新兩個賬戶 accountDao.updateAccount(source); int i = 1 / 0; accountDao.updateAccount(target); } }
package com.xhbjava.dao.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import com.xhbjava.dao.IAccountDao; import com.xhbjava.pojo.Account; import com.xhbjava.pojo.AccountRowMapper; /** * 使用者持久層介面實現類 * * @author mr.wang * */ @Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Account findAccountById(Integer id) { List<Account> list = jdbcTemplate.query("select * from account where id = ? ", new AccountRowMapper(), id); return list.isEmpty() ? null : list.get(0); } @Override public Account findAccountByName(String sourceName) { List<Account> list = jdbcTemplate.query("select * from account where name = ? ", new AccountRowMapper(), sourceName); if (list.isEmpty()) { return null; } if (list.size() > 1) { throw new RuntimeException("結果集不唯一,不是隻有一個賬戶物件"); } return list.get(0); } @Override public void updateAccount(Account source) { // TODO Auto-generated method stub jdbcTemplate.update("update account set money = ? where id = ? ", source.getMoney(), source.getId()); } }
(2)事務配置
1》配置事務管理器並注入資料來源
<!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean>
2》在業務層使用@Transactional 註解
@Service("accountService") @Transactional(readOnly=true,propagation=Propagation.SUPPORTS) public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } ....... }該註解的屬性和 xml 中的屬性含義一致。該註解可以出現在介面上,類上和方法上。 出現介面上,表示該介面的所有實現類都有事務支援。 出現在類上,表示類中所有方法有事務支援 出現在方法上,表示方法有事務支援。 以上三個位置的優先順序:方法>類>介面 3》在配置檔案中開啟 spring 對註解事務的支援
<!-- 開啟 spring 對註解事務的支援 --> <tx:annotation-driven transaction-manager="transactionManager"/>
不使用XML方式:(之前寫過)
@Configuration @EnableTransactionManagement public class SpringTxConfiguration { //裡面配置資料來源,配置 JdbcTemplate,配置事務管理器。在之前的步驟已經寫過了。 }
4》測試
略。
5.Transactional 配置項
在程式設計式事務允許自定義事務介面一一TransactionDefinition 它可以由 XML 或者註解@Transactional 進行配置,在上面我基於註解方式用到Transactional 配置項。
下面是它的介面原始碼:
package org.springframework.transaction.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; import org.springframework.transaction.TransactionDefinition; / @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
通過程式碼發現他們的配置項不是很多,下面是他的具體配置項說明:
配置項 | 含義 | 備註 |
value | 定義事務管理器 | 它是 Spring JoC 容器裡的 Bean id ,這個 Bean 1/li 要實現 介面 Pl atformTransactionManager |
transactionManager | 同上 | 同上 |
isolation | 隔離級別 | 當一個數據庫在多個事務同時存在時出現,預設值取資料庫預設隔離級別 |
propagation | 傳播行為 | 傳播行為是方法之間呼叫的問題。預設值為 Propagation .REQUIRED |
timeout | 超時時間 | 單位為秒,當超時時,會引發異常,預設會導致事務囚滾 |
readOnly | 是否開啟只讀事務 | 預設值為 false |
rollbackFor | 回滾事務的異常類定義 | 也就是隻有當方法產生所定義異常時,才回滾事務,否則就提交事務 |
rollbackForClassName | 回滾事務的異常類名定義 | 同rolbackFor ,只是使用類名稱定義 |
noRollbackFor | 當產生哪些異常不回滾事務 | 當產生所定義異常時, Sprin 將繼續提交事務 |
noRollbackForClassName | 同noRollbackFor | 同noRollbackFor ,只是使用類的名稱定義 |
6.事務定義器
從註解@Transactional 或者 XML 中我 看到了事務定義器的身影,因此我們有必要討下事務定義器 TransactionDefinition。介面原始碼如下:
package org.springframework.transaction; import java.sql.Connection; import org.springframework.lang.Nullable; public interface TransactionDefinition {
//傳播行為7個常亮定義 int PROPAGATION_REQUIRED = 0;//預設傳播級別 int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; //隔離級別 5個定義 int ISOLATION_DEFAULT = -1;//預設級別 int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
//-1代表永不超時 int TIMEOUT_DEFAULT = -1; //獲取傳播行為 int getPropagationBehavior(); //獲取隔離級別 int getIsolationLevel(); //事務超時時間 int getTimeout(); //是否只讀事務 boolean isReadOnly(); //獲取事務定義器的名稱 @Nullable String getName(); }
7.宣告式事務的約定流程
@Transaction註解可以使用在方法或者類上面,在SpringIoC容器初始化時,Spring會讀入這個註解或者XML配置的事務資訊,並且儲存到一個事務定義類裡面( TransactionDefinition 介面的子類),以備將來使用。當執行時會讓 Spring 攔截註解標註的某 一個方法或者類的所有方法,Spring將編寫的程式碼織入到 AOP 的流程中,然後給出它的約定。
首先 Spring 通過事務管理器(PlatformTransactionManager 子類)建立事務,與此同時會把事務定義中的隔離級別、超時時間等屬性根據配置內容往事務上設定。而根據傳播行為配置採取一種特定的策略,Spring 根據配置完,無須編碼。啟動業務程式碼,我們知道Spring會通過反射的方式排程開發者的業務程式碼,但是反射的結果可能是正常返回或者產生異常,那麼它給的約定是隻要發生異常,井且符合事務定義類回滾條件的,Spring 就會將資料庫事務回滾,否則將資料庫事務提交,這也是 Spring 自己完成的。在編碼過程中發現,我們只 需要編寫業務程式碼和對事務屬性進行配置即可,無需程式碼干預,程式碼邏輯也更為清晰,更有利於維護。
宣告式事務的流程如下:@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,timeout=3) public void transfer(String sourceName, String targeName, Float money) { // TODO Auto-generated method stub // 1.根據名稱查詢兩個賬戶 Account source = accountDao.findAccountByName(sourceName); Account target = accountDao.findAccountByName(targeName); // 2.修改兩個賬戶的金額 source.setMoney(source.getMoney() - money);// 轉出賬戶減錢 target.setMoney(target.getMoney() + money);// 轉入賬戶加錢 // 3.更新兩個賬戶 accountDao.updateAccount(source); int i = 1 / 0; accountDao.updateAccount(target); }
二、Spring+MyBatis 組合中使用事務
完成專案結構:
1.建立maven工程
2.建立資料庫表
略,沿用之前的表account。
3.環境搭建
(1)pom檔案配置,新增依賴
<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.xhbjava</groupId> <artifactId>Spring02</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> </project>
(2)配置Spring中bean.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns="http://www.springframework.org/schema/beans" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 啟用掃描機制,並指定掃描對應的包 --> <context:annotation-config /> <context:component-scan base-package="com.xhbjava.**" /> <!-- 資料庫連線池配置 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 整合MyBatis --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" /> </bean> <!-- 事務管理器配置資料來源事務 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 使用註解定義事務 --> <tx:annotation-driven transaction-manager="transactionManager" /> <!-- 使用自動掃碼物件關係對映 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定會話工廠,如果當前上下文中只定義了一個則該屬性可省去 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> <!-- 指定要自動掃描介面的基礎包,實現介面 --> <property name="basePackage" value="com.xhbjava.mapper"></property> </bean> </beans>
(3)建立Account實體類
沿用之前,略。
(4)搭建 MyBatis 的對映檔案 建立 SQL POJO 的關係。
package com.xhbjava.mapper; import org.springframework.stereotype.Repository; import com.xhbjava.pojo.Account; @Repository public interface AccountMapper { public int insertAccount(Account account); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xhbjava.mapper.AccountMapper"> <insert id="insertAccount" parameterType="com.xhbjava.pojo.Account"> insert into account(name,money)value(#{name},#{money}) </insert> </mapper>
(5)為了引入這 對映器,配置MyBatis 配置檔案。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <mappers> <mapper resource="com/xhbjava/mapper/AccountMapper.xml" /> </mappers> </configuration>
(6)新增介面類和實現類
package com.xhbjava.service; import com.xhbjava.pojo.Account; /** * 賬戶的業務層介面 * * @author mr.wang * */ public interface IAccountService { public int insertAccount(Account account); }
package com.xhbjava.service.impl; 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 com.xhbjava.mapper.AccountMapper; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountService; @Service public class AccountServiceImpl implements IAccountService { @Autowired private AccountMapper accountMapper =null; @Override @Transactional(propagation = Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED) public int insertAccount(Account account) { return accountMapper.insertAccount(account); } }
package com.xhbjava.service; import java.util.List; import com.xhbjava.pojo.Account; /** * 賬戶的業務層介面 * * @author mr.wang * */ public interface IAccountListService { public int insertListAccount(List<Account> accountList); }
package com.xhbjava.service.impl; import java.util.List; import org.apache.log4j.Logger; 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 com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountListService; import com.xhbjava.service.IAccountService; @Service public class AccountListServiceImpl implements IAccountListService { @Autowired private IAccountService accountService; Logger logger = Logger.getLogger(AccountListServiceImpl.class); @Override @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED) public int insertListAccount(List<Account> accountList) { int count = 0; for(Account account:accountList) { try { count +=accountService.insertAccount(account); }catch(Exception e) { logger.info(e); } } return count; } }
(7)log4j配置
### set log levels ### log4j.rootLogger = DEBUG,stdout ### \u8F93\u51FA\u5230\u63A7\u5236\u53F0 ### log4j.logger.org.springframework=DEBUG log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p %d %C:%m%n log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.Target=System.out log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=[%d{yy/MM/dd HH:mm:ss:SSS}]-%l:%m%n ### \u8F93\u51FA\u5230\u65E5\u5FD7\u6587\u4EF6 ### log4j.appender.File=org.apache.log4j.RollingFileAppender log4j.appender.File.File=${project}src\\main\\resources\\app.log log4j.appender.File.MaxFileSize=10MB log4j.appender.File.Threshold=ALL log4j.appender.File.layout=org.apache.log4j.PatternLayout log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n
4.測試
package com.xhbjava.test; import java.util.ArrayList; import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountListService; public class TestSpring { public static void main(String args[]) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); IAccountListService accountListService = ctx.getBean(IAccountListService.class); List<Account> accountList = new ArrayList<Account>(); for(int i=0;i<=2;i++) { Account account = new Account(); account.setName("張三"+i); account.setMoney(100f+i); accountList.add(account); } int count = accountListService.insertListAccount(accountList); System.out.println(count); } }
5.一些注意問題
(1)錯誤使用service
package com.xhbjava.controller; import org.springframework.beans.factory.annotation.Autowired; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountService; public class AccountController { @Autowired private IAccountService accountService = null; public void errorUseServices() { Account account = new Account(); account.setName("李四"); account.setMoney(12f); accountService.insertAccount(account); Account account1 = new Account(); account1.setName("李四1"); account1.setMoney(12f); accountService.insertAccount(account1); } }
在上面程式碼中,Controller 使用 Service 方法時,如果這個 Service 標註有@Transactional ,那麼它就會啟用一個事務,而 一個Service 方法完成後,它就會釋放該事務,所以前後兩個insertRole 方法是在兩個不同的事務中完成的。這樣容易造成第一個插入成功,第二個插入失敗,造成資料不一致,這是我們需要注意的。