在开发中,遇到了sql语句报错,但是并没有回滚的情况。
经过几天的排查,终于找到了事务没有回滚的原因。
原来的项目用的是informix的数据库,原来针对事务回滚的机制都是好用的。我本地用的是mysql数据库。
先将程序代码与spring-mybatis.xml配置文件拿过来:
1、程序代码:
这个问题是在验证增删改查返回值时发现的。
两个操作,删除时,因为关联了外键,所以会报错,此时正常情况更新的语句也会回滚,但是并没有。
/** *@Author: Administrator on 2020/3/12 15:15 *@param: *@return: *@Description:查询同步情况 */ @Override public PageInfo getSyncstatusPages(Syncstatus vo, int pageNo, int pageSize) { PageHelper.startPage(pageNo, pageSize); /* //查看增删改查的返回值 //1新增:返回值自己定义,可以是void,int //1-1新增一条数据:插入成功,返回值为1 int insert_success1 = yylfHttpServletMapper.insert("8", "2", "1"); //1-2新增多条数据:插入成功,返回值为插入的数据条数,当有一条数据错误时,所有数据都会插入失败 int insert_success2 = yylfHttpServletMapper.insert_duotiao("7"); String insert_success3 = yylfHttpServletMapper.insert_duotiao_String("7");//不支持返回值为String类型 //1-3新增一条数据:插入失败:主键冲突,会直接报异常 int insert_failed = yylfHttpServletMapper.insert("1", "2", "1"); //1-4插入null:属性为null,如果表中所有字段允许为null,插入一条所有值均为null的数据 Syncstatus syncstatus1 = null; yylfHttpServletMapper.insertSyncstatus(syncstatus1); //1-5插入一个没有赋值的对象:属性为null,如果表中所有字段允许为null,插入一条所有值均为null的数据 Syncstatus syncstatus2 = new Syncstatus(); yylfHttpServletMapper.insertSyncstatus(syncstatus2);*/ /*//2删除:返回值自己定义,可以是void,int //2-1删除成功:没有数据:返回值为0 int delete_success1 = yylfHttpServletMapper.delete("0"); //2-2删除成功:有多条数据:返回值为删除的数据条数 int delete_success2 = yylfHttpServletMapper.delete_systemcode("2");*/ //2-3删除失败:例如有外键:报异常 //3更新:返回值自己定义,可以是void,int //3-1更新成功:没有数据,返回值为0 //int update_no = yylfHttpServletMapper.update_no("0"); //3-2更新成功:有多条数据,返回更新的数据条数 int update_duotiao = yylfHttpServletMapper.update_duotiao_systemcode("2"); int delete_fail = yylfHttpServletMapper.delete("1"); //3-3更新失败:例如有外键,报异常 //int update_fail = yylfHttpServletMapper.update_fail("1"); //4查询 //4-1 没数:String 类型返回null //Object object = yylfHttpServletMapper.select("0"); //4-1 没数:集合 类型返回[]空集合 //Syncstatus syncstatus3 = new Syncstatus(); //syncstatus3.setStatus("7"); //List<Syncstatus> page0 = yylfHttpServletMapper.getSyncstatusList(syncstatus3); //4-1 没数:int 类型返回null,如果定义为int会报错。因为没数时返回null,可以将返回类型改为String //String i = yylfHttpServletMapper.select_int(0); //4-1:当返回值为对象时,若返回值为空,则返回null //4-2 有数 List<Syncstatus> pages = yylfHttpServletMapper.getSyncstatusList(vo); return new PageInfo<Syncstatus>(pages); }
2、对数据库的操作:
<update id="update_duotiao_systemcode"> UPDATE aaa SET systemcode = '3' WHERE systemcode = #{systemcode,jdbcType=VARCHAR} </update> <delete id="delete"> delete from aaa where uuid = #{uuid,jdbcType=VARCHAR} </delete>
3、配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean id="dataSource" class="com.p6spy.engine.spy.P6DataSource"> <constructor-arg ref="dataSourceTarget"/> </bean> <!-- 定义使用dbcp2连接池的数据源 此处使用自定义的数据源,将用户名与密码解密处理 --> <bean id="dataSourceTarget" class="com.asd.common.jdbc.MyBasicDataSource" > <property name="url" value="${jdbc.url}"> </property> <property name="username" value="${jdbc.username}"> </property> <property name="password" value="${jdbc.password}"> </property> <property name="driverClassName" value="${jdbc.driverClassName}"> </property> <!-- informix--> <!--<property name="validationQuery" value="select count(*) from systables"> </property>--> <!-- mysql检测方式 --> <property name="validationQuery" value="select 1"> </property> <!-- oracle检测方式 <property name="validationQuery" value="select 1 from dual"> </property> --> </bean> <!-- 配置SqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据源 相关信息看源码 --> <property name="dataSource" ref="dataSource" /> <!-- 扫描的实体所在的包--> <property name="configLocation" value="classpath:mybatis.xml"/> <!-- mapper和resultmap配置路径 --> <property name="mapperLocations"> <list> <value>classpath:mybatis/*Mapper.xml</value> </list> </property> </bean> <!-- 自动扫描mapper接口,注入sqlsessionfactory --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.asd.modules.dao"/> </bean> <!-- 启用类扫描机制,通过元数据配置Service --> <context:component-scan base-package="com.asd"> <context:include-filter type="regex" expression="com/.asd/.modules/.sevice/.impl/.*ServiceImpl" /> </context:component-scan> <!-- mybatis事物配置 --> <context:annotation-config /> <!-- ================================事务相关控制================================================= --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="reinsAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="delete*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException" /> <tx:method name="insert*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.RuntimeException" /> <tx:method name="save*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.RuntimeException" /> <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" /> <tx:method name="find*" propagation="SUPPORTS" /> <tx:method name="get*" propagation="SUPPORTS" /> <tx:method name="select*" propagation="SUPPORTS" /> <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> </tx:attributes> </tx:advice> <aop:config> <!-- 把事务控制在Service层 --> <aop:pointcut id="reinsPointCut" expression="execution(* com.asd.modules.service.impl.*ServiceImpl.*(..))" /> <aop:advisor pointcut-ref="reinsPointCut" advice-ref="reinsAdvice" /> </aop:config> </beans>
4、数据库语句:
-- 创建aaa表用来验证增删改查的返回值 CREATE TABLE `reserve`.`aaa` ( `uuid` char(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `systemcode` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `status` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, PRIMARY KEY (`uuid`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- 创建bbb表用来关联aaa的uuid作外键 CREATE TABLE `reserve`.`bbb` ( `uuid` char(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `systemcode` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `status` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, PRIMARY KEY (`uuid`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; alter table bbb add constraint FK_T_POSITI_REFERENCE_T_COMPAN foreign key (uuid)references aaa (uuid); insert into bbb (uuid,systemcode,status)value ('1','2','2'); -- 验证事支持 DELETE from aaa where uuid != '1'; insert into aaa (uuid,systemcode,status)value ('2','2','2'); SELECT * FROM aaa;
排查过程共查找了下述方面:
1、排除数据库原因:
查看mysql数据库是支持事务的;而且用informix数据库进行了验证,同样没有回滚。
2、验证了impl的类型等均为问题。
3、查看了事务的配置信息也正确好用。
4、验证了系统其它的一些方法,发现是支持事务的。
5、将这两个语句放到其它方法里也好用。
6、事务是在service层处理的,在控制层也加了异常捕获(这个操作并不会影响事务回滚,即使不catch,也会回滚的)
最终锁定问题原因:是因为方法名称的问题。
当将方法名改成其它的,不以get开头,不报错。
这个问题很坑,因为本以为为配置文件中的get*,会使这个方法的事务起作用,谁知道恰恰get*的这个配置虽然起作用了,但是结果却是事务不回滚,在将该配置改为
<tx:method name="get*" propagation="SUPPORTS" rollback-for="java.lang.Exception"/>
也没有用,最后将其注释掉,事务回滚。走了下面的配置:
<tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
需要注意的是tx:method 的name属性指的是方法名。
将SUPPORTS改为REQUIRED后,事务也进行回滚。最终得到原因:是因为propagation的配置信息不正确。
拓展:
一、在声明式的事务处理中,要配置一个切面,其中就用到了propagation,表示打算对这些方法怎么使用事务,是用还是不用,其中propagation有七种配置,REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。默认是REQUIRED。
二、Spring中七种Propagation类的事务属性详解:
三、注意.
这个配置将影响数据存储,必须根据情况选择。
问题往往出现在你忽略的地方。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
时间:2020-05-19
JUnit是Java中最有名的单元测试框架,用于编写和运行可重复的测试,多数Java的开发环境都已经集成了JUnit作为单元测试的工具.好的单元测试能极大的提高开发效率和代码质量. 使用SpringJunit单元测试,通过@ContextConfiguration加载配置文件后,只会在src/test/resources目录下寻找配置文件,不会加载src/main/resources中的. 这样就导致了项目可以正常启动,但是单元测试时会提示找不到注入的类. 可以通过pom.xml配置来解决该问题
这篇文章主要介绍了spring基于xml文件配置Bean过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 通过全类名来配置: class:bean的全类名,通过反射的方式在IOC容器中创建Bean,所以要求bean中必须有一个无参的构造器. <bean id="helloWorld" class="com.gong.spring.beans.HelloWorld"> <property na
声明: 此处需求是修改封装的clickhouseUtil数据查询引擎连接工具类.由于此类中的方法都是静态方法.连接地址等参数需要根据不同环境改变.例如开发下地址,测试下地址,生产地址等,所有通过配置文件来获取不同环境下的配置参数,但是使用的方法是静态的,所有不能使用一般情况下的@value直接给变量赋值,需要用到spring 属性的set方法来给静态变量赋值,然后静态方法使用静态变量即可 方法: 第一步:在yml文件中配置需要的参数 clickhouse: address: jdbc:click
这篇文章主要介绍了SpringBoot如何读取配置文件参数并全局使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言: 读取配置文件参数的方法:@Value("${xx}")注解.但是@Value不能为static变量赋值,而且很多时候我们需要将参数放在一个地方统一管理,而不是每个类都赋值一次. 正文: 注意:一定要给类加上@Component 注解 application.xml test: app_id: 12345 app_
这篇文章主要介绍了基于SPRINGBOOT配置文件占位符过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.配置文件占位符 1.application.properties server.port=8088 debug=false product.id=ID:${random.uuid} product.name=da mao mao product.weight=${random.int} product.fristLinePrice
在Spring boot开发中,需要在application.yml文件里配置数据库的连接信息,或者在启动时传入数据库密码,如果不加密,传明文,数据库就直接暴露了,相当于"裸奔"了,因此需要进行加密处理才行. 使用@SpringBootApplication注解启动的项目,只需增加maven依赖 我们对信息加解密是使用这个jar包的: 编写加解密测试类: package cn.linjk.ehome; import org.jasypt.encryption.pbe.StandardP
这种属性应用方式是 field_name=@field_value@. 两个@符号是springboot为替代${}属性占位符产生,原因是${}会被maven处理,所以应该是起不到引用变量的作用. @@方式可以引用springboot非默认配置文件(即其他配置文件)中的变量: springboot默认配置文件是 src/main/resources/application.properties 补充知识:springboot项目使用@Value注解获取配置文件中的配置信息 application
这篇文章主要介绍了springboot配置文件的加载顺序解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 springboot启动时会扫描一下位置的application.properties或者application.yml文件作为默认配置文件: file:./config/ file:./ classpath:/config/ classpath:/ 以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置会覆盖低优先级配置
springboot采纳了建立生产就绪spring应用程序的观点. Spring Boot优先于配置的惯例,旨在让您尽快启动和运行.在一般情况下,我们不需要做太多的配置就能够让spring boot正常运行.在一些特殊的情况下,我们需要做修改一些配置,或者需要有自己的配置属性. SpringBoot启动会扫描以下位置的application.yml或者 application.properties文件作为SpringBoot的默认配置文件. -file:./config/ -file:./
一. 1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<context-param>和<listener>两个结点. 2.紧急着,容创建一个ServletContext(servlet上下文),这个web项目的所有部分都将共享这个上下文. 3.容器将<context-param>转换为键值对,并交给servletContext. 4.容器创建<listener>中的类实例,创建监听器. 二. load-on-startup 元
本文实例讲述了Python实现加载及解析properties配置文件的方法.分享给大家供大家参考,具体如下: 这里参考前面一篇:http://www.jb51.net/article/137393.htm 我们都是在java里面遇到要解析properties文件,在python中基本没有遇到这中情况,今天用python跑深度学习的时候,发现有些参数可以放在一个global.properties全局文件中,这样使用的时候更加方便.原理都是加载文件,然后用line方法进行解析判断"=",自
Java中代码的加载顺序所能了解的知识点 类的依赖关系 static代码块的加载时间 继承类中构造器的隐式调用 非static的成员变量初始化时间 main方法和static的加载顺序 测试代码如下: public class App { private static App d = new App(); private SubClass t = new SubClass(); static{ System.out.println("App static");//6 } public
先给大家介绍下spring boot 配置加载顺序,具体内容如下所示: 使用 Spring Boot 会涉及到各种各样的配置,如开发.测试.线上就至少 3 套配置信息了.Spring Boot 可以轻松的帮助我们使用相同的代码就能使开发.测试.线上环境使用不同的配置. 在 Spring Boot 里面,可以使用以下几种方式来加载配置.本章内容基于 Spring Boot 2.0 进行详解. 1.properties文件: 2.YAML文件: 3.系统环境变量: 4.命令行参数: 等等-- 我们可
前一阵子横扫了javascript,当时自我感觉良好.现在一想,又觉得没什么.今天的任务是把asp.net ajax中客户端页面生命周期那一章研究完.然而,因为这一章的内容使我产生了一些迷惑.这些疑惑在书中都没有只字提及. 一.html页面的详细加载过程是什么呢?页面元素在加载时的优先级是什么? 二.javascript的作用域.变量的作用域.不同脚本段之间的关系? 三.html页面的生命周期? 这些问题真的打中了我的死穴.不了解这些,我就无法透过asp.net ajax的框架看到其底层原理.只
需求说明 做项目时,为了省事,起初把初始化的配置都放在每个类中 static加载,初始化配置一多,就想把它给整理一下,这里使用servlet中的init方法初始化. web.xml说明 首先了解下web.xml中元素的加载顺序: 启动web项目后,web容器首先回去找web.xml文件,读取这个文件 容器会创建一个 ServletContext ( servlet 上下文),整个 web 项目的所有部分都将共享这个上下文 容器将 转换为键值对,并交给 servletContext 容器创建 中的
在Vuejs 1.0版本中,如果父子组件进行配合,它们的生命周期执行具有如下特点: 1. created总是先父后子 生命周期函数created总是按照从父到子的顺序依次执行,但是兄弟之间没有严格按照这样的顺序执行,估计是采用了异步函数,不仅如此,子组件在父组件中的插入顺序也是随机的,并没有什么特别的规律.假定子组件的引用顺序如下: <div class="container"> <child-c1 v-ref:child1></child-c1>
具体代码如下所示: public class Parent { public static int a = parentStaticMethod2(); { System.out.println("父类非静态初始化块"); } static { System.out.println("父类静态初始化块"); } public Parent() { System.out.println("父类的构造方法"); } public static int