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配置文件并引入约束
<?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>
<!--配置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; } ....... }
<!-- 开启 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 方法是在两个不同的事务中完成的。这样容易造成第一个插入成功,第二个插入失败,造成数据不一致,这是我们需要注意的。