编程式事务:
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn=.........;
try{
conn.setAutoCommit(false);
conn.commit;
}
catch(Exception e){
conn.rollback();
}finally{
conn.close();
}
上述这种编程式的实现方式存在很多缺陷:
细节没有被屏蔽,具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐
代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用
声明式事务:
既然事务控制的代码有规律可循代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作
这样做能够提高开发效率
,消除了冗余的代码
,框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性
,性能等各个方面的优化
基于注解的声明式事务:实现事务功能
数据库准备工作:
创建图书表:
<!
create table t_book(book_id int not null auto_increment comment '主键',book_name varchar(20) default null comment '图书名称',price int default null comment '价格',stock int unsigned default null comment '库存(无符号)',primary key(book_id));
插入数据:
insert into t_book(book_id,book_name,price,stock)values(1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);
创建用户表:
create table t_user(user_id int not null auto_increment comment '主键',username varchar(20) default null comment '用户名',balance int unsigned default null comment '余额(无符号)',primary key(user_id));
插入数据:
insert into t_user(user_id,username,balance) values (1,'admin',50);
通过生活经验可知,图书购买的过程往往会出现余额不足或者库存不足的情况,它就类似于我们在进行事务处理时产生的异常,而需要执行回滚操作,但我们早在学习mysql中就说过,引起事务回滚的原因通常并不是SQL语句出现错误,而是业务的核心逻辑出现问题,但是业务逻辑并不会产生异常,那么要处理出现问题的业务,我们可以在数据库层面对其进行解决,也可以在java代码中对其进行处理,在数据库中进行的处理即为,库存量和余额量不能小于0,为这两个字段设置关键字unsigned,设置当前字段的值是不能为负值的,当然也可以通过在java代码层面自定义异常等,当业务逻辑出现问题时,抛出异常即可
java准备工作:
数据库连接的部分写在外部的jdbc.properties
文件中:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/WJR
name=root
password=你的密码
TXannotation.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=
"http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="spring_txAnnotation"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${name}"></property>
<property name="password" value="${password}"></property>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
通过创建我们之前学习的经典的三层架构模型,实现业务逻辑:
控制层:
package spring_txAnnotation.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import spring_txAnnotation.Service.ServiceBook;
@Controller
public class BookController {
@Autowired
private ServiceBook serviceBook;
public void BuyBook(Integer userId,Integer BookId){
serviceBook.buyBook(userId,BookId);
}
}
持久层:
package spring_txAnnotation.Dao;
public interface BookDao {
Integer getPrice(Integer bookId);
void updateStock(Integer bookId);
void updateBalance(Integer userId, Integer price);
}
持久层的实现类:
package spring_txAnnotation.Dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer getPrice(Integer bookId) {
String sql="select price from t_book where book_id=?";
return jdbcTemplate.queryForObject(sql,Integer.class,bookId);
}
@Override
public void updateStock(Integer bookId) {
String sql="update t_book set stock=stock-1 where book_id=?";
jdbcTemplate.update(sql,bookId);
}
@Override
public void updateBalance(Integer userId,Integer price) {
String sql="update t_user set balance=balance-? where user_id=?";
jdbcTemplate.update(sql,price,userId);
}
}
业务层:
package spring_txAnnotation.Service;
public interface ServiceBook {
void buyBook(Integer userId, Integer bookId);
}
业务层的实现类:
package spring_txAnnotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import spring_txAnnotation.Dao.BookDao;
@Service
public class ServiceBookImpl implements ServiceBook {
@Autowired
private BookDao bookDao;
@Override
public void buyBook(Integer userId, Integer bookId) {
Integer book_price=bookDao.getPrice(bookId);
bookDao.updateStock(bookId);
bookDao.updateBalance(userId,book_price);
}
}
测试类:
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;
import spring_txAnnotation.Controller.BookController;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:TXannotation.xml")
public class Test1 {
@Autowired
private BookController bookController;
@Test
public void testBuyBooks(){
bookController.BuyBook(1,1);
}
}
运行后,报错如下:
org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [update t_user set balance=balance-? where user_id=?]; Data truncation: BIGINT UNSIGNED value is out of range in '(`wjr`.`t_user`.`balance` - 80)'; nested exception is com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: BIGINT UNSIGNED value is out of range in '(`wjr`.`t_user`.`balance` - 80)'
原因如下:属性为无符号的这个字段的值超出了范围
Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: BIGINT UNSIGNED value is out of range in '(`wjr`.`t_user`.`balance` - 80)'
上述的SQL语句对数据实现的是,用当前余额的50元去购买价格为80的图书编号为1的图书,二者相减后的结果为负数,但因为我们将其类型设置为无符号的类型,因此就产生错误
在之前学习mysql中,我们就讲到mysql默认是有提交事务的功能,一个SQL语句对应一个事务提交,那么自动提交事务的功能会带来什么麻烦呢?
如下所示:
下述的这三个方法各自对应单独的事务,也就是说,他们事务提交成功与否只和自身有关,与其他操作并没有关系
首先我们先来查看一下数据库中的数据情况,如下所示:
当我们发生了使用不足的余额去购买图书这个操作后,java控制台报错如上述,数据库中的数据变化,如下所示:
我们发现用户的余额并没有任何的改变,但是1号图书的库存量减少了1,那么也证实了我们上述所说的,不同的操作之间是互不影响的,但这种结果,显然不符合逻辑,下面我们就通过基于注解的声明式事务来对这种情况进行处理
上面在学习声明式事务的概念时,我们提到过,声明式事务不需要我们单独的写切面和通知,只需要在配置文件中进行简单的配置即可完成操作
第一步:在XML文件中,配置事务管理器-->处理数据必须有数据源
,如下所示:
由于事务管理器是一个接口,如果想将接口设置为bean,则必须通过其实现类来完成
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
第二步:在XML文件中,开启事务的注解驱动–>驱动并不是环绕通知,事务管理器才是,我们通过注解驱动中的transaction-manager属性
将其二者关联起来 ,而属性值使用为默认值
<tx:annotation-driven transaction-manager="transactionManager"/>
注意:这里的annotation有多个,一定要使用tx的
第三步:将其注解使用在方法上
使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
,也就是说@Transactional作用于那个方法上,那个方法就是连接点,如果加到类上,则该类上的所有方法都是连接点,transaction-manager用来设置事务管理器的id,如果该id是默认值[transactionManager],可以省略不写
如下所示:将其添加至方法上
报错如下:
org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [update t_user set balance=balance-? where user_id=?]; Data truncation: BIGINT UNSIGNED value is out of range in '(`wjr`.`t_user`.`balance` - 80)'; nested exception is com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: BIGINT UNSIGNED value is out of range in '(`wjr`.`t_user`.`balance` - 80)'
Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: BIGINT UNSIGNED value is out of range in '(`wjr`.`t_user`.`balance` - 80)'
咿?不禁让人产生疑惑,怎么进行事务管理之后的报错内容和上述没有进行管理时完全相同啊,好像事务管理的作用并没有在这里体现,那么我们再去查看数据库部分,是否也和上面完全相同呢?
如下所示:
与上述未进行事务管理不同的地方在于,这里的1号图书的数量并没有减少,用户余额同样也是,因为余额不足的情况下,购买失败,因此整个事务进行了回滚,显然这种才是符合逻辑的
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)