Spring系列笔记
Spring(一)Spring 入门&IOC介绍.
Spring(二)基于注解的IOC配置及实现crud.
Spring(三)AOP及基于XML和注解的AOP配置.
Spring(四)JdbcTemplate及Spring事务控制.
1.Spring 中的 JdbcTemplate
1.1.JdbcTemplate 概述
- 是什么?
- 它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装
- 需要两个jar包:
- spring-jdbc-5.0.2.RELEASE.jar(JdbcTemplate在此内)
- spring-tx-5.0.2.RELEASE.jar(与事务相关)
- 作用(为什么学):
- 它是用于和数据库交互的,实现对表的crud
1.2.JdbcTemplate 对象的创建
- JdbcTemplatede最基本用法
/**
* @author liuyongjun
* @date 2020-06-02-15:53
* JdbcTemplatede最基本用法
*/
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//准备数据源:spring的内置数据源(除了cp30和dbcp)
//配置数据库连接四要素
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring");
ds.setUsername("root");
ds.setPassword("123456");
//1.创建JdbcTemplate对象
JdbcTemplate jt = new JdbcTemplate();
//给对象jt设置数据源
jt.setDataSource(ds);
//2.执行操作
jt.execute("insert into account(name,money)values('ccc',1000)");
}
}
通过上面JdbcTemplate的简单用法,通过创建对象,不管是设置数据源还是准备数据源都用到了有set方法,依据我们之前学过的依赖注入,我们可以在配置文件中配置这些对象。因此引出IOC
1.3.JdbcTemplate在spring的IOC中使用
- 首先在resources目录下创建xml文件,并进行配置导包
<packaging>jar</packaging>
<dependencies>
<!-- 与IOC相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 与JdbcTemplate相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
- 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 除了cp30和dbcp以外,使用spring的内置数据源DriverManager-->
<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/spring"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
- 然后进行基本用法测试:
public class JdbcTemplateDemo2 {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
jt.execute("insert into account(name,money)values('ddd',2222)");
}
}
结果:
1.4.JdbcTemplate 的CRUD操作
- 查询所需的方法:
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
//保存
// jt.update("insert into account(name,money)values(?,?)","eee",3333f);
//更新
// jt.update("update account set name=?,money=? where id=?","test",4567,5);
//删除
// jt.update("delete from account where id=?",5);
//查询所有
//此查询方式需要自己写实现类,下面我们定义了Account的封装策略,这样就能查询了
// List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);
//此查询方式无需自己写实现类,用BeanPropertyRowMapper这样spring会给提供
// List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
// for(Account account : accounts){
// System.out.println(account);
// }
//查询一个
// List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
// System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));
//查询返回一行一列(使用聚合函数,但不加group by子句)
Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f);
System.out.println(count);
}
}
/**
* 定义Account的封装策略
* 上面查询操作用到了RowMapper
*/
class AccountRowMapper implements RowMapper<Account>{
/**
* 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
* @param rs
* @param rowNum
* @return Account类型
* @throws SQLException
*/
@Override
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;
}
}
这样我们就使用main函数测试了JdbcTemplate的crud,但是在实际开发中,JdbcTemplate不是这样用的,那是怎样用呢?我们通常都是在Dao中使用,请看下面介绍
1.5.JdbcTemplate 在Dao 中使用
1.5.1.准备实体类
package com.xuexi.domain;
import java.io.Serializable;
/**
* @author liuyongjun
* @date 2020-06-02-15:38
* 账户的实体类
*/
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 +
'}';
}
}
1.5.2. 第一种方式:在 dao 中定义 JdbcTemplate
- 定义账户持久层接口
/**
* @author liuyongjun
* @date 2020-06-02-21:34
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 根据Id查询账户
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
- 账户的持久层实现类
/**
* 账户的持久层实现类
* 需要给 dao 注入 JdbcTemplate
*/
@Repository
public class AccountDaoImpl2 implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
- 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl2">
<!--注入JdbcTemplate-->
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置JdbcTemplate-->
<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/spring"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
- 测试类
public class JdbcTemplateDemo4 {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountDao accountDao = ac.getBean("accountDao",IAccountDao.class);
//3.执行操作
Account account = accountDao.findAccountById(1);
System.out.println(account);
account.setMoney(30000f);
accountDao.updateAccount(account);
}
此种方式有一个小问题:
就是我们的 dao 有很多时,每个 dao 都有一些重复性的代码。下面就是重复代码:
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
能不能把它抽取出来呢? 请看下一小节。
1.5.3. 第二种方式:让 dao 继承 JdbcDaoSupport
- JdbcDaoSupport 是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源:具体源码如下:
/**
* 此类用于抽取dao中的重复代码
*/
public class JdbcDaoSupport {
//定义对象
private JdbcTemplate jdbcTemplate;
//set 方法注入数据源,判断是否注入了,注入了就创建 JdbcTemplate
//在配置文件中,配置账户的持久层中可选择注入dataSource,从而间接注入jdbcTemplate,而不必直接注入jdbcTemplate
public void setDataSource(DataSource dataSource) {
if(jdbcTemplate == null){
jdbcTemplate = createJdbcTemplate(dataSource);
}
}
//使用数据源创建 JdcbTemplate
private JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
//当然,我们也可以通过注入 JdbcTemplate 对象
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//使用 getJdbcTmeplate 方法获取操作模板对象
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}
当然,我们想到了父类,spring自然也想到了,因此spring早就为我们写好了父类,我们不需要自己写,只需要导包即可:
import org.springframework.jdbc.core.support.JdbcDaoSupport;
- 定义持久层接口和实现类
/**
* @author liuyongjun
* @date 2020-06-02-21:34
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 根据Id查询账户
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
- 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl">
<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/spring"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
- 测试类:
public class JdbcTemplateDemo4 {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountDao accountDao = ac.getBean("accountDao",IAccountDao.class);
Account account = accountDao.findAccountById(1);
System.out.println(account);
account.setMoney(30000f);
accountDao.updateAccount(account);
}
}
- 这两种方式的区别:
- 第一种在 Dao 类中定义 JdbcTemplate 的方式,适用于所有配置方式(xml 和注解都可以)。
- 第二种让 Dao 继承 JdbcDaoSupport 的方式,只能用于基于 XML 的方式,注解用不了。
2.上一个笔记基于XML和注解的AOP实现事务控制
- 上一个笔记中通知类用了一个日志类,没有用事务控制实现,这次当作业补充,分别基于xml和注解进行AOP实现事务控制。
2.1.基于xml的AOP实现事务控制
- 开始pom.xml文件配置,定义坐标,导包
<packaging>jar</packaging>
<dependencies>
<!-- 与IOC相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- spring中对Junit框架的整合-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--开源 JDBC工具类库-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--配置 C3P0 数据源使用-->
<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>
<!-- aspectj框架支持的包 进行aop操作使用-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!-- 与事务相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
- 配置xml文件
- 此配置前一部分是xml、aop的命名空间及约束
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置Service -->
<bean id="accountService" class="com.xuexi.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></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/spring"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.xuexi.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="com.xuexi.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!-- 配置AOP-->
<aop:config>
<!-- 配置通用的切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.xuexi.service.impl.*.*(..))"></aop:pointcut>
<!-- 配置切面-->
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置前置通知:开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!-- 配置后置通知:提交事务-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:释放连接-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
- 上面的配置是去掉了代理类工厂配置,加入了AOP配置,如图
- 将事务管理器类 TransactionManager 作为通知类用于增强方法
/**
* @author liuyongjun
* @date 2020-06-01-14:12
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
//将连接还回连接池中
connectionUtils.getThreadConnection().close();
//将连接与线程解绑,防止下次使用报错。因为连接被关闭后不能再调用
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 环绕通知,一般单独使用
*/
public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint) {
// 用于接收返回值
Object returnValue = null;
try {
// 获取切入点方法参数
Object[] args = proceedingJoinPoint.getArgs();
// 前置通知:开始事务
this.beginTransaction();
// 调用切入点方法
returnValue = proceedingJoinPoint.proceed(args);
// 后置通知:提交事务
this.commit();
} catch (Throwable throwable) {
// 异常通知:回滚事务
this.rollback();
throwable.printStackTrace();
} finally {
// 最终通知:释放连接
this.release();
}
return returnValue;
}
}
- service实现类
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer acccountId) {
accountDao.deleteAccount(acccountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
- 测试
/*
使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
- 结果
- 同样,若出现异常时,也会进行事务管理,回滚事务,不会进行转账。
- 环绕通知
- 首先在配置文件中使用 aop:around 来配置环绕通知
<!-- 配置aop -->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.xuexi.service.impl.*.*(..))"/>
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置环绕通知 -->
<aop:around method="aroundTransaction" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
- 在通知类中编写环绕通知的具体实现
/**
* 环绕通知,一般单独使用
*/
public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint) {
// 用于接收返回值
Object returnValue = null;
try {
// 获取切入点方法参数
Object[] args = proceedingJoinPoint.getArgs();
// 前置通知:开始事务
this.beginTransaction();
// 调用切入点方法
returnValue = proceedingJoinPoint.proceed(args);
// 后置通知:提交事务
this.commit();
} catch (Throwable throwable) {
// 异常通知:回滚事务
this.rollback();
throwable.printStackTrace();
} finally {
// 最终通知:释放连接
this.release();
}
return returnValue;
}
2.2.基于注解的AOP实现事务控制
2.2.1.半注解(XML + 注解)方式
- 首先配置pom.xml文件,确定依赖关系,这和基于xml一样
- 然后配置xml文件,在基于xml配置中添加context命名和约束
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
- 我们在基于xml的基础上进行修改(针对四大通知,环绕通知后面介绍)
- 开始前配置spring创建容器时要扫描的包
<context:component-scan base-package=“com.xuexi”></context:component-scan> - 首先配置service对象在业务层实现类中用@Service(“accountService”)取代,其内的注入dao配置用自动类型注入@Autowired取代,这样就不需要set方法了。
- 接着,配置dao对象在持久层实现类中用@Repository(“accountDao”)取代,其内的注入QueryRunner和ConnectionUtils都用属性上加自动类型注入@Autowired取代,这样也就不需要set方法了。
- 然后,配置Connection的工具类 ConnectionUtils在ConnectionUtils类上用@Component(“connectionUtils”)取代,其内的注入数据源配置用自动类型注入@Autowired取代,这样就不需要set方法了。
- 然后,配置事务管理器在TransactionManager类上用@Component(“txManager”)取代,其内的注入ConnectionUtils配置用自动类型注入@Autowired取代,这样就不需要set方法了。
- 最后,也是在TransactionManager类中,我们针对于AOP配置:
- 第一,将< aop:config></aop:config>开启spring对注解AOP的支持换成了< aop:aspectj-autoproxy></aop:aspectj-autoproxy>配置
- 第二,配置切面用@Aspect表明当前类是个切面类。
- 第三,对于切入点表达式用
@Pointcut(“execution(* com.xuexi.service.impl..(…))”)
private void pt1(){}表示。 - 第四,配置四个通知:
- 前置通知用@Before(“pt1()”)取代,
- 后置通知用@AfterReturning(“pt1()”)取代
- 异常通知用@AfterThrowing(“pt1()”)取代
- 最终通知用@After(“pt1()”)取代
- 开始前配置spring创建容器时要扫描的包
- 完整配置
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.xuexi"></context:component-scan>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></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/spring"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 业务层实现类
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
//方法和之前的相似
}
- 持久层实现类
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
//方法相似就先忽略
}
- 工具类 ConnectionUtils
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
//方法相似就先忽略
}
- TransactionManager类
/* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("txManager")
//切面
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
//切入点表达式
@Pointcut("execution(* com.xuexi.service.impl.*.*(..))")
private void pt1(){}
/**
* 开启事务
*/
@Before("pt1()")
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
@AfterReturning("pt1()")
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
@AfterThrowing("pt1()")
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
@After("pt1()")
public void release(){
try {
//将连接还回连接池中
connectionUtils.getThreadConnection().close();
//将连接与线程解绑,防止下次使用报错。因为连接被关闭后不能再调用
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
- 测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
-结果
- 分析原因:原本正常的调用顺序应该为:前置通知 --> 后置通知/异常通知 --> 最终通知,而此时的调用顺序变成了 前置通知 --> 最终通知 --> 后置通知/异常通知,如果是在事务管理中,一旦先执行了最终通知,连接被释放,那么再执行事务的提交或者回滚就行不通了
- 图解
我们上次笔记也提到了,上面的代码有一点小问题,当使用注解来配置前置通知、后置通知、异常通知以及环绕通知时,由于 Spring 的 bug,导致了通知被调用的顺序有误,想解决这个问题,那么只需要使用环绕通知即可,因为在环绕通知中每种通知的调用顺序是由我们自己决定的,不会受 Spring 的影响。所以,使用注解推荐使用环绕通知!!!
环绕通知(基于注解)
- 将四大通知的注解注释,打开环绕注解注释
- TransactionManager类更改通知注解
/**
* 环绕通知,一般单独使用
*/
@Around("pt1()")
public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint) {
// 用于接收返回值
Object returnValue = null;
try {
// 获取切入点方法参数
Object[] args = proceedingJoinPoint.getArgs();
// 前置通知:开始事务
this.beginTransaction();
// 调用切入点方法
returnValue = proceedingJoinPoint.proceed(args);
// 后置通知:提交事务
this.commit();
} catch (Throwable throwable) {
// 异常通知:回滚事务
this.rollback();
throwable.printStackTrace();
} finally {
// 最终通知:释放连接
this.release();
}
return returnValue;
}
2.2.3.纯注解
- 如果要使用纯注解的方式,那么只需要在配置上用 @EnableAspectJAutoProxy 注解开启对 AOP 的支持即可
@Configuration
@ComponentScan("com.xuexi")
@EnableAspectJAutoProxy// 开启aop支持
public class SpringConfiguration {
}
- 具体操作:
- 我们在半注解的基础上进行操作,配置文件中还有如下内容:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.xuexi"></context:component-scan>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></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/spring"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 因此我们需要用注解的方式将配置文件的内容替换掉:
- 首先,定义一个注解类,用@Configuration注解,用于指定当前类是一个 Spring 配置类SpringConfiguration,当创建容器时会从该类上加载注解
- 接着,@ComponentScan,用于指定 Spring 在初始化容器时要扫描的包。相当于<context:component-scan base-package=“com.xuexi”/>
- 然后新建一个和spring连接数据库相关的配置类JdbcConfig,用@Bean表明使用此方法创建一个对象,并且放入 Spring 容器,可指明QueryRunner配置的id为“runner”,在该类中创建一个QueryRunner对象
- 在JdbcConfig类中创建一个数据源,并用@Bean(“dataSource”)注解,同时书写配置文件(内含四要素),该配置文件在SpringConfiguration中用@PropertySource(“classpath:jdbcConfig.properties”)注解,表示调用该文件
- 在SpringConfiguration中使用@Import(JdbcConfig.class),使两个配置类建立他们的关系
- 在SpringConfiguration中使用@EnableAspectJAutoProxy// 开启aop支持
- 最后在测试类中设@ContextConfiguration(classes = SpringConfiguration.class)
- SpringConfiguration配置类
@Configuration
@ComponentScan(basePackages = "com.xuexi")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
@EnableAspectJAutoProxy// 开启aop支持
public class SpringConfiguration {
}
- JdbcConfig配置类
/**
* 和spring连接数据库相关的配置类
*/
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;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean("dataSource")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
- 配置文件JdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=123456
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
3. Spring 中的事务控制
3.1.基本介绍
- 在上一篇笔记中,为了实现业务层的事务管理,我们自己创建了连接工具类 ConnectionUtils 和事务管理器 TransactionManager。但是其实 Spring 已经给我们提供了事务管理相关的接口,我们只需要在配置文件中声明使用即可。
- avaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案。
- Spring 框架为我们提供了一组事务控制的接口,这组接口是在 spring-tx-5.0.2.RELEASE.jar 中,因此我们需要导入相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
- Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。由于编程式事务比较繁琐并且耦合度高,所以这里不作介绍,只介绍声明式事务。
3.2.Spring 中事务控制的 API 介绍
3.2.1 PlatformTransactionManager 接口
- PlatformTransactionManager 接口是 Spring 的事务管理器,它里面提供了我们常用的操作事务的方法:
- TransactionStatus getTransaction(@Nullable TransactionDefinition definition):获取事务状态信息
- void commit(TransactionStatus status):提交事务
- void rollback(TransactionStatus status):回滚事务
- 在开发我们一般使用以下实现类:
- org.springframework.jdbc.datasource.DataSourceTransactionManager:使用 Spring
JDBC 或 Mybatis 进行持久化数据时使用 - org.springframework.orm.hibernate5.HibernateTransactionManag:使用 HIbernate 进行持久化数据时使用
- org.springframework.jdbc.datasource.DataSourceTransactionManager:使用 Spring
3.2.2 TransactionDefinition 接口
- TransactionDefinition 接口是事务的定义信息对象,提供以下方法:
- String getName(): 获取事务的名称
- int getIsolationLevel(): 获取事务的隔离级别
- int getPropagationBehavior: 获取事务的传播行为
- int getTimeout() : 获取事务的超时时间
- boolean isReadOnly() : 获取事务是否只读
- 事务的隔离级别反映了事务提交并发访问时的处理态度,在 TransactionDefinition 中定义了以下几种级别:
- ISOLATION_DEFAULT: 使用数据库默认的隔离级别,为以下四种的其中之一
- ISOLATION_READ_UNCOMMITTED: 可以读取未提交数据
- ISOLATION_READ_COMMITTED : 只能读取已提交数据,解决脏读问题 (Oracle 默认级别)
- ISOLATION_REPEATABLE_READ : 只有当前事务提交后才可以看到其他事务提交的修改 (MySQL 默认级别)
- ISOLATION_SERIALIZABLE : 事务串行执行,一个时刻只能有一个事务执行
- 事务的传播行为反映了事务的控制范围,在 TransactionDefinition 中定义了以下几种级别:
- REQUIRED: 默认的 Spring 事务传播行为,能满足大部分应用场景。如果当前没有事务,就新建一个事务;如果已经存在一个事务,那么加入到这个事务中。
- SUPPORTS:支持当前事务,如果没有事务,就以非事务方式执行
- MANDATORY:使用当前的事务,如果没有事务就抛出异常
- REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
- 事务的超时时间
- 默认值是 -1 ,没有超时限制。设置的时候以秒为单位进行设置
- 事务是否为只读事务
- 一般把查询操作设置为只读事务
3.2.3 TransactionStatus 接口
- TransactionStatus 接口描述了某个时间点上事务对象具体的运行状态,提供以下方法:
- void flush():刷新事务
- boolean hasSavepoint():获取是否存在存储点
存储点:一旦设置了存储点表明事务是按步提交
-
- boolean isCompleted() :获取事务是否完成
- boolean isNewTransaction() :获取事务是否为新的事务
- boolean isRollbackOnly(): 获取事务是否回滚
- void setRollbackOnly(): 设置回滚
3.3.基于 XML 的声明式事务控制(配置方式)
- 我们还是用账户案例:
- 配置pom.xml文件,导入相关依赖
<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-jdbc</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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
- 声明式事务的配置步骤
- 1.使用 < bean > 标签配置一个事务管理器 DataSourceTransactionManager,注入dataSource
- 2.使用 < tx:advice > 标签配置事务的通知并且引用事务管理器,此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的。
- 属性id:给事务通知起一个唯一标识
- 属性transaction-manager:给事务通知提供一个事务管理器引用
- 3.在 < tx:advice> 标签内部使用 < tx:attributes> 标签和 < tx:method> 标签配置事务的属性
- < tx:method> 标签用于配置事务的属性,有以下属性:
- name 属性:指定要控制的方法,可以使用通配符 *
- isolation 属性:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
- propagation 属性:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
- read-only 属性:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
- timeout 属性:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
- **rollback-for **属性:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
- **no-rollback-for **属性:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-
- 4.使用 < aop:config> 标签和 < aop:pointcut> 标签配置 AOP 切入点表达式
- 5.在 < aop:config> 标签内部使用 < aop:advisor> 标签配置切入点表达式和事务通知的关系
- advice-ref 属性用于指定通知的引用
- pointcut-ref 属性用于指定切入点表达式的引用
- 编写配置文件,注意要导入 aop 和 tx 的名称空间
<?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
http://www.springframework.org/schema/beans/spring-beans.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">
<!-- 配置业务层-->
<bean id="accountService" class="com.xuexi.service.impl.AccountServiceImpl">
<!-- 注入dao-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl">
<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/spring"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.xuexi.service.impl.*.*(..))"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
- 基本的业务层接口和实现类,持久层接口和实现类,账户的实体类以及测试类很简单就不写了
3.4.基于半注解(XML + 注解) 的声明式事务控制
- 首先pom.xml文件依赖关系和上面一样
- 其次配置xml文件
- 第一,注解需要加入context的命名空间和约束
- 第二,编写配置文件,注意在配置文件中一样要配置事务管理器,同时还要开启对注解事务的支持
- 第三,因为是注解了,所以dao不能继承jdbctemplate了,而是要在其中定义,同时别忘了在配置文件中配置jdbctemplate
<?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
http://www.springframework.org/schema/beans/spring-beans.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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.xuexi"></context:component-scan>
<!-- 配置数据源-->
<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/spring"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- spring中基于注解 的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
-->
<!-- 配置事务管理器 -->
<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>
- 修改业务层和持久层代码,并且在需要事务支持的地方使用@Transactional注解
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
//需要的是读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//....
}
- 关于 @Transactional 注解:
- 该注解的属性和 XML 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
- 出现接口上,表示该接口的所有实现类都有事务支持。
- 出现在类上,表示类中所有方法有事务支持
- 出现在方法上,表示方法有事务支持。
- 以上三个位置的优先级:方法>类>接口
- 使用注解不需要配置 AOP 的原因是在业务层配置了 @Transactional 注解的地方就相当于是切入点
3.5. 基于纯注解的声明式事务控制
- 要想使用纯注解,需要创建两个配置类
- 注解类
/**
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.xuexi")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement//开启注解事务的支持
public class SpringConfiguration {
}
- jdbc配置类
/**
* 和连接数据库相关的配置类
*/
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(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
- 数据对应的配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=123456
- 同时,为了替换xml文件中的事务管理器,创建事务配置类
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
- 测试类
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}