上一篇写了spring框架中自定义事务管理器的xml配置和注解配置的两种方式,这篇主要说一下spring框架中自己封装的事务管理器的使用以及xml和注解两种配置方式。下面所涉及的理解均是通过b站上的学习视频所获得,链接先附上:https://www.bilibili.com/video/BV1mE411X7yp。
接下来,正式开始:
首先准备一下pom文件:
springAnnoTx_withoutXML_demo1
com.myself
1.0-SNAPSHOT
4.0.0
jar
org.springframework
spring-context
5.0.2.RELEASE
org.springframework
spring-tx
5.0.2.RELEASE
org.springframework
spring-jdbc
5.0.2.RELEASE
org.springframework
spring-test
5.0.2.RELEASE
mysql
mysql-connector-java
5.1.6
junit
junit
4.12
org.aspectj
aspectjweaver
1.8.7
在这里说明,前面也一直忘了说这件事,为了方便测试类方便测试,这里讲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 accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper(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的配置:
- 接下来,重点说一下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文件中开启注解对事务的支持。
这里我有两个疑问,1、开启注解对事务的支持后,就不用开启注解了:aop:aspectj-autoproxy/是不是tx:annotation-driven标签中已经开启了注解开关;2、使用注解的方式就不用配置切入点表达式了,我猜想是因为@Transactional(propagation注解加在了类上,是不是就代表将该类中的方法作为切入点了。
到此,注解的方式就配置完了,总体一看注解的方式会简单很多,但是也存在一个问题,比如@Transactional(propagation注解的类中的方法只读操作和读写操作各占一半,这种情况就需要将一半的方法上重新加上@Transactional(propagation注解,并修改其属性,从这一点考虑,没有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 {
/**