package com.zhu.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import com.alibaba.druid.pool.DruidDataSource;
@Service(“service1”)
public class UserJdbcWithoutTransManagerService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void addScore(String userName,int toAdd){
String sql = “UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?”;
jdbcTemplate.update(sql,toAdd,userName);
}
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext(“file:src/main/resources/applicationContext.xml”);
UserJdbcWithoutTransManagerService service =
(UserJdbcWithoutTransManagerService)ctx.getBean(“service1”);
JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean(“jdbcTemplate”);
DruidDataSource druidDataSource = (DruidDataSource)jdbcTemplate.getDataSource();
//①.检查数据源autoCommit的设置
System.out.println(“autoCommit:”+ druidDataSource.isDefaultAutoCommit());
//②.插入一条记录,初始分数为10
jdbcTemplate.execute(
“INSERT INTO zhu_test VALUES(‘tom’,10)”);
//③.调用工作在无事务环境下的服务类方法,将分数添加20分
service.addScore(“tom”,20);
//④.查看此时用户的分数
@SuppressWarnings(“deprecation”)
int score = jdbcTemplate.queryForInt(“SELECT age FROM zhu_test WHERE name =‘tom’”);
System.out.println(“score:”+score);
jdbcTemplate.execute(“DELETE FROM zhu_test WHERE name=‘tom’”);
}
}
运行结果:
applicationContext.xml中并没有配置事务,但是还是持久化到数据库中去了。DataSource默认设置是自动提交的,也就是说,在执行了CRUD之后,会马上持久化到数据库中。如果设置自动提交为false,那么在jdbcTemplate执行完之后并不会马上持久化到数据库中,除非手动提交。
虽说没有事务管理,程序依然可以进行数据的CRUD操作,但是没有事务管理,在数据安全同步方面会面临很大的挑战。栗子太多,不一一举例,看代码
我们故意提交一条错误的sql语句
package com.zhu.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import com.alibaba.druid.pool.DruidDataSource;
@Service(“service1”)
public class UserJdbcWithoutTransManagerService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void addScore(String userName,int toAdd){
String sql = “UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?”;
jdbcTemplate.update(sql,toAdd,userName);
}
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext(“file:src/main/resources/applicationContext.xml”);
UserJdbcWithoutTransManagerService service =
(UserJdbcWithoutTransManagerService)ctx.getBean(“service1”);
JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean(“jdbcTemplate”);
DruidDataSource druidDataSource = (DruidDataSource)jdbcTemplate.getDataSource();
//①.检查数据源autoCommit的设置
System.err.println(“autoCommit:”+ druidDataSource.isDefaultAutoCommit());
//②.插入一条记录,初始分数为10
jdbcTemplate.execute(
“INSERT INTO zhu_test VALUES(‘tom’,10)”);
//③.执行一条错误sql语句
jdbcTemplate.execute(
“INSERT INTO zhu_test VALUES(‘tom1’,10,00)”);
//④.调用工作在无事务环境下的服务类方法,将分数添加20分(不会执行)
service.addScore(“tom”,20);
}
}
清空数据,然后执行结果是:②成功插入数据tom 10 然后③错误的sql抛出异常 然后④更新操作也不会执行。
正常的逻辑是在同一方法或者类中执行一系列的CRUD操作,其中一条出出现问题会抛出异常,然后已经执行完的语句回滚到发生异常之前的状态。
这种情况在正常的开发环境是最基本的常识性错误,开发过程中一定要避免。
然后是配置事务。
applicationContext.xml文件中添加事务和模型视图的配置
package com.zhu.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class NoTMController {
//②.自动注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//③.通过Spring MVC注解映URL请求
@RequestMapping("/logon")
@ResponseBody
public String logon(String userName,String password){
String sql = “UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?”;
if(isRightUser(userName,password)){
//执行更新操作(年龄加20)
jdbcTemplate.update(sql,20,“tom”);
//执行错误语句
jdbcTemplate.execute(
"INSERT
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
INTO zhu_test VALUES(‘tom1’,10,00)");
return “success”;
}else{
return “fail”;
}
}
private boolean isRightUser(String userName,String password){
//do sth…
return true;
}
}
启动项目 输入网址
影响了一条数据,操作已经完成
检测到错误,回滚
所以结果是数据没有变化。
注释掉错误的sql语句则完成执行所有的sql语句。
事务在进行嵌套调用的时候不会分解为多个事务,比如说一个事务中的方法调用其他事务的方法不会产生多余的事务。
spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:
int getPropagationBehavior():事务的传播行为
int getIsolationLevel():事务的隔离级别
int getTimeout():事务的过期时间
boolean isReadOnly():事务的读写特性。
所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring 支持 7 种事务传播行为:
-
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
-
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
-
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常
-
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
-
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
-
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
事务与线程
由于 Spring 的事务管理器是通过线程相关的 ThreadLocal 来保存数据访问基础设施,再结合 IOC 和 AOP 实现高级声明式事务的功能,所以 Spring 的事务天然地和线程有着千丝万缕的联系。
我们知道 Web 容器本身就是多线程的,Web 容器为一个 Http 请求创建一个独立的线程,所以由此请求所牵涉到的 Spring 容器中的 Bean 也是运行于多线程的环境下。在绝大多数情况下,Spring 的 Bean 都是单实例的(singleton),单实例 Bean 的最大的好处是线程无关性,不存在多线程并发访问的问题,也即是线程安全的。
一个类能够以单实例的方式运行的前提是“无状态”:即一个类不能拥有状态化的成员变量。我们知道,在传统的编程中,DAO 必须执有一个 Connection,而 Connection 即是状态化的对象。所以传统的 DAO 不能做成单实例的,每次要用时都必须 new 一个新的实例。传统的 Service 由于将有状态的 DAO 作为成员变量,所以传统的 Service 本身也是有状态的。
但是在 Spring 中,DAO 和 Service 都以单实例的方式存在。Spring 是通过 ThreadLocal 将有状态的变量(如 Connection 等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring 不遗余力地将状态化的对象无状态化,就是要达到单实例化 Bean 的目的。
由于 Spring 已经通过 ThreadLocal 的设施将 Bean 无状态化,所以 Spring 中单实例 Bean 对线程安全问题拥有了一种天生的免疫能力。不但单实例的 Service 可以成功运行于多线程环境中,Service 本身还可以自由地启动独立线程以执行其它的 Service。
于是我们修改我们的代码
package com.zhu.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class NoTMController {
//②.自动注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//③.通过Spring MVC注解映URL请求
@RequestMapping("/logon")
@ResponseBody
public String logon(String userName,String password){
System.err.println(userName);
if(isRightUser(userName,password)){
Thread h1 = new Thread(){
String sql = “UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?”;