上一篇写了spring框架中自定义事务管理器的xml配置和注解配置的两种方式,这篇主要说一下spring框架中自己封装的事务管理器的使用以及xml和注解两种配置方式。下面所涉及的理解均是通过b站上的学习视频所获得,链接先附上:https://www.bilibili.com/video/BV1mE411X7yp。
接下来,正式开始:
首先准备一下pom文件:
<?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">
<artifactId>springAnnoTx_withoutXML_demo1</artifactId>
<groupId>com.myself</groupId>
<version>1.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
</project>
在这里说明,前面也一直忘了说这件事,为了方便测试类方便测试,这里讲spring整合junit的jar包也导入了进来,在没有整合之前,junit中的main方法每次执行的时候,如果没有加载spring的bean,xml文件,是不会创建容器的,整合完之后,junit的main方法执行时会先去加载spring容器。
话不多说,其他的环境也先附上:
实体类:
package com.myself.domain;
import java.io.Serializable;
/**
* 账户实体类
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
dao层:
package com.myself.dao;
import com.myself.domain.Account;
/**
* 持久层接口
*/
public interface IAccountDao {
Account findByName(String name);
void update(Account account);
}
package com.myself.dao.impl;
import com.myself.dao.IAccountDao;
import com.myself.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class AccountDaoImpl implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findByName(String name) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
if (accounts == null){
return null;
}
if (accounts.size() > 1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void update(Account account) {
jdbcTemplate.update("update account set name = ? ,money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
}
}
service层:
package com.myself.service;
/**
* 业务层接口
*/
public interface IAccountService {
void transfer(String sourceName,String targetName,Float money);
}
package com.myself.service.impl;
import com.myself.dao.IAccountDao;
import com.myself.domain.Account;
import com.myself.service.IAccountService;
/**
* 业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
accountDao.update(source);
// int i=1/0;
accountDao.update(target);
}
}
一切准备完毕,就到了最关键的bean.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountDao" class="com.myself.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dateSource"></property>
</bean>
<bean id="dateSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mystudy"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="accountService" class="com.myself.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dateSource"></property>
</bean>
<!-- 配置事务的通知类型 -->
<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:method>
</tx:attributes>
</tx:advice>
<!-- 配置切入点和事务的对应关系 -->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.myself.service.impl.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
- 接下来,重点说一下bean.xml文件中事务的配置方式。
1、首先,需要配置spring自己的事务管理器:DataSourceTransactionManager,并且注入数据源
2、然后配置事务的通知类型,使用 tx:advice标签已经将提交和回滚事务的通知类型给封装好了,不用再繁琐的配置前置、后置、异常、最终通知了。在tx:advice标签中还有一个子标签tx:attributes,该标签下可以配置多个tx:method标签,这里配置的是事务的传播行为。tx:method标签的name属性指明业务层中被增强方法的方法名,可以用*代表所有方法(一般不这么写)。接下来还有事务的几个重要属性:
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认级别
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚,没有默认值,表示任何异常都回滚
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚,没有默认值,表示任何异常都回滚
propagation:用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务,增删改的选择,查询方法可以选择SUPPORTS
read-only:用于指定事务是否只读只有查询方法才能设置为true,默认值是false,表示读写
timeout:用于指定事务的超时时间,默认值是-1.表示永不超时,以秒为单位
这里常用的两个属性就是read-only和propagation,用于区分是只读操作还是读写操作。
3、事务管理器有了,事务的通知类型有了,还需要切入点与事务的对应关系就可以啦。在aop:config标签下,首先配置切入点表达式: aop:pointcut;完事之后再讲切入点与事务关联上就可以了:aop:advisor标签中advice-ref属性指明用哪个通知类型的事务,pointcut-ref指明该事务与哪个切入点关联。
到此,spring自己封装的事务管理器的xml配置方式就完事了,接下来使用测试类测试:
package com.itheima.test;
import com.itheima.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void transfer(){
accountService.transfer("aaa","bbb",100f);
}
}
以上就是spring种事务管理器xml的配置方式。
- 接下来,说一下注解的方式:
dao层的依赖注入,这里就不再阐述了,上一篇也提到过。主要说一下service层实现类怎么使用注解的方式实现事务的控制。
service层实现类需要添加如下注解:
package com.myself.service.impl;
import com.myself.dao.IAccountDao;
import com.myself.domain.Account;
import com.myself.service.IAccountServiceDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只读
public class AccountServiceImpl implements IAccountServiceDao {
@Autowired
private IAccountDao accountDao;
@Override
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)//读写
public void transfer(String sourceName, String targetName, Float money) {
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
accountDao.update(source);
// int i= 1/0;
accountDao.update(target);
}
}
首先,在类的上方加一个@Transactional(propagation注解,里面依然会有事务传播行为的几个属性,我这里配置了只读的几个属性
接下来,如果碰到有读写操作的方法,可以在方法上再加@Transactional(propagation注解,修改属性值。
这么配置完就剩最后一步了,在bean.xml文件中开启注解对事务的支持。<tx:annotation-driven transaction-manager=“transactionManager”></tx:annotation-driven>,指定事务管理器。
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启扫描-->
<context:component-scan base-package="com.myself"></context:component-scan>
<!--开启注解-->
<!--<aop:aspectj-autoproxy/>-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mystudy"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--spring中基于xml的声明式事务配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启注解对事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
这里我有两个疑问,1、开启注解对事务的支持后,就不用开启注解了:aop:aspectj-autoproxy/是不是tx:annotation-driven标签中已经开启了注解开关;2、使用注解的方式就不用配置切入点表达式了,我猜想是因为@Transactional(propagation注解加在了类上,是不是就代表将该类中的方法作为切入点了。
到此,注解的方式就配置完了,总体一看注解的方式会简单很多,但是也存在一个问题,比如@Transactional(propagation注解的类中的方法只读操作和读写操作各占一半,这种情况就需要将一半的方法上重新加上@Transactional(propagation注解,并修改其属性,从这一点考虑,没有xml一劳永逸。
- 最后,说一下纯注解的方式(没有bean.xml)
首先,需要准备出一个配置类:
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* spring的配置类
*/
@Configuration
@ComponentScan("com.myself")//开启扫描
@Import({JdbcConfig.class,TransactionManager.class})//导入其他配置类
@PropertySource("jdbcConfig.properites")//加载资源文件
@EnableTransactionManagement//开启注解对事务的支持
public class SpringConfguration {
}
说明一下几个注解:
1、@Configuration:表明该类是一个配置类;
2、@ComponentScan(“com.myself”):开启扫描,扫描com.myself包下的所有注解;
3、@Import({JdbcConfig.class,TransactionManager.class}):@Import这个注解,说明该类是一个主配置类,里面可以有可变的其他配置类。比如:JdbcConfig、TransactionManager;
4、@PropertySource(“jdbcConfig.properites”)加载类路径下的配置文件,这里的配置文件写的是连接数据库的相关信息;
5、@EnableTransactionManagement:开启注解对事务的支持
说完主配置类,重点关注一下JdbcConfig、TransactionManager以及jdbcConfig.properites资源文件
jdbcConfig.properites:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mystudy
jdbc.username=root
jdbc.password=root
JdbcConfig:
package config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
/**
* 数据库配置相关
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建jdbcTemplate对象
* @param dataSource
* @return
*/
@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean("dataSource")
public DataSource createDriverManagerDataSource(){
DriverManagerDataSource dms = new DriverManagerDataSource();
dms.setDriverClassName(driver);
dms.setUrl(url);
dms.setUsername(username);
dms.setPassword(password);
return dms;
}
}
这个配置类中,首先是创建jdbcTemplate对象,并用@Bean注解加入到spring容器中。创建数据源对象,并用@Bean注解加入到spring容器中,涉及到的四个变量通过 @Value注解从资源文件中获取。
TransactionManager:
package config;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
-
配置事务相关类
*/
public class TransactionManager {/**
- 创建事务管理器对象
- @param dataSource
- @return
*/
@Bean(“transactionManager”)
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
这个配置类中穿件事务管理器对象并用@Bean注解加入到spring容器中。
至此,使用纯注解的方式也就完事啦,bean.xml也可以删除了。我的spring事务的学习也暂时告一段落了~
再次说明,资源来自b站的学习视频,已在文章开头标明出处,有兴趣的同学可以去看看,如有理解有误的地方,望指出。