当前位置: 首页 > news >正文

好的手机网站国内搜索引擎

好的手机网站,国内搜索引擎,旅游网站建设的好处,公司网站是否必须做可信认证前言 对于从事java开发工作的同学来说#xff0c;spring的事务肯定再熟悉不过了。 在某些业务场景下#xff0c;如果一个请求中#xff0c;需要同时写入多张表的数据。为了保证操作的原子性#xff08;要么同时成功#xff0c;要么同时失败#xff09;#xff0c;避免数… 前言 对于从事java开发工作的同学来说spring的事务肯定再熟悉不过了。 在某些业务场景下如果一个请求中需要同时写入多张表的数据。为了保证操作的原子性要么同时成功要么同时失败避免数据不一致的情况我们一般都会用到spring事务。 确实spring事务用起来贼爽就用一个简单的注解Transactional就能轻松搞定事务。我猜大部分小伙伴也是这样用的而且一直用一直爽。 但如果你使用不当它也会坑你于无形。 今天我们就一起聊聊事务失效的一些场景说不定你已经中招了。不信让我们一起看看。 一 事务不生效 1.访问权限问题 众所周知java的访问权限主要有四种private、default、protected、public它们的权限从左到右依次变大。 但如果我们在开发过程中把有某些事务方法定义了错误的访问权限就会导致事务功能出问题例如 Service public class UserService {Transactionalprivate void add(UserModel userModel) {saveData(userModel);updateData(userModel);} }我们可以看到add方法的访问权限被定义成了private这样会导致事务失效spring要求被代理方法必须是public的。 说白了在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断如果目标方法不是public则TransactionAttribute返回null即不支持事务。 protected TransactionAttribute computeTransactionAttribute(Method method, Nullable Class? targetClass) {// Dont allow no-public methods as required.if (allowPublicMethodsOnly()  !Modifier.isPublic(method.getModifiers())) {return null;}// The method may be on an interface, but we need attributes from the target class.// If the target class is null, the method will be unchanged.Method specificMethod  AopUtils.getMostSpecificMethod(method, targetClass);// First try is the method in the target class.TransactionAttribute txAttr  findTransactionAttribute(specificMethod);if (txAttr ! null) {return txAttr;}// Second try is the transaction attribute on the target class.txAttr  findTransactionAttribute(specificMethod.getDeclaringClass());if (txAttr ! null  ClassUtils.isUserLevelMethod(method)) {return txAttr;}if (specificMethod ! method) {// Fallback is to look at the original method.txAttr  findTransactionAttribute(method);if (txAttr ! null) {return txAttr;}// Last fallback is the class of the original method.txAttr  findTransactionAttribute(method.getDeclaringClass());if (txAttr ! null  ClassUtils.isUserLevelMethod(method)) {return txAttr;}}return null;}也就是说如果我们自定义的事务方法即目标方法它的访问权限不是public而是private、default或protected的话spring则不会提供事务功能。 2. 方法用final修饰 有时候某个方法不想被子类重新这时可以将该方法定义成final的。普通方法这样定义是没问题的但如果将事务方法定义成final例如 Service public class UserService {Transactionalpublic final void add(UserModel userModel){saveData(userModel);updateData(userModel);} }我们可以看到add方法被定义成了final的这样会导致事务失效。 为什么 如果你看过spring事务的源码可能会知道spring事务底层使用了aop也就是通过jdk动态代理或者cglib帮我们生成了代理类在代理类中实现的事务功能。 但如果某个方法用final修饰了那么在它的代理类中就无法重写该方法而添加事务功能。 注意如果某个方法是static的同样无法通过动态代理变成事务方法。 3.方法内部调用 有时候我们需要在某个Service类的某个方法中调用另外一个事务方法比如 Service public class UserService {Autowiredprivate UserMapper userMapper;public void add(UserModel userModel) {userMapper.insertUser(userModel);updateStatus(userModel);}Transactionalpublic void updateStatus(UserModel userModel) {doSameThing();} }我们看到在事务方法add中直接调用事务方法updateStatus。从前面介绍的内容可以知道updateStatus方法拥有事务的能力是因为spring aop生成代理了对象但是这种方法直接调用了this对象的方法所以updateStatus方法不会生成事务。 由此可见在同一个类中的方法直接内部调用会导致事务失效。 那么问题来了如果有些场景确实想在同一个类的某个方法中调用它自己的另外一个方法该怎么办呢 3.1 新加一个Service方法 这个方法非常简单只需要新加一个Service方法把Transactional注解加到新Service方法上把需要事务执行的代码移到新方法中。具体代码如下 Servcie public class ServiceA {Autowiredprvate ServiceB serviceB;public void save(User user) {queryData1();queryData2();serviceB.doSave(user);}}Servciepublic class ServiceB {Transactional(rollbackForException.class)public void doSave(User user) {addData1();updateData2();}}3.2 在该Service类中注入自己 如果不想再新加一个Service类在该Service类中注入自己也是一种选择。具体代码如下 Servcie public class ServiceA {Autowiredprvate ServiceA serviceA;public void save(User user) {queryData1();queryData2();serviceA.doSave(user);}Transactional(rollbackForException.class)public void doSave(User user) {addData1();updateData2();}}可能有些人可能会有这样的疑问这种做法会不会出现循环依赖问题 答案不会。 其实spring ioc内部的三级缓存保证了它不会出现循环依赖问题。但有些坑。 3.3 通过AopContent类 在该Service类中使用AopContext.currentProxy()获取代理对象 上面的方法2确实可以解决问题但是代码看起来并不直观还可以通过在该Service类中使用AOPProxy获取代理对象实现相同的功能。具体代码如下 Servcie public class ServiceA {public void save(User user) {queryData1();queryData2();((ServiceA)AopContext.currentProxy()).doSave(user);}Transactional(rollbackForException.class)public void doSave(User user) {addData1();updateData2();}}4.未被spring管理 在我们平时开发过程中有个细节很容易被忽略。即使用spring事务的前提是对象要被spring管理需要创建bean实例。 通常情况下我们通过Controller、Service、Component、Repository等注解可以自动实现bean实例化和依赖注入的功能。 如果有一天你匆匆忙忙的开发了一个Service类但忘了加Service注解比如 //Service public class UserService {Transactionalpublic void add(UserModel userModel) {saveData(userModel);updateData(userModel);}     }从上面的例子我们可以看到UserService类没有加Service注解那么该类不会交给spring管理所以它的add方法也不会生成事务。 5.多线程调用 在实际项目开发中多线程的使用场景还是挺多的。如果spring事务用在多线程场景中会有问题吗 Slf4j Service public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() - {roleService.doOtherThing();}).start();} }Service public class RoleService {Transactionalpublic void doOtherThing() {System.out.println(保存role表数据);} }从上面的例子中我们可以看到事务方法add中调用了事务方法doOtherThing但是事务方法doOtherThing是在另外一个线程中调用的。 这样会导致两个方法不在同一个线程中获取到的数据库连接不一样从而是两个不同的事务。如果想doOtherThing方法中抛了异常add方法也回滚是不可能的。 如果看过spring事务源码的朋友可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个mapkey是数据源value是数据库连接。 private static final ThreadLocalMapObject, Object resources new NamedThreadLocal(Transactional resources);我们说的同一个事务其实是指同一个数据库连接只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程拿到的数据库连接肯定是不一样的所以是不同的事务。 6.表不支持事务 周所周知在mysql5之前默认的数据库引擎是myisam。 它的好处就不用多说了索引文件和数据文件是分开存储的对于查多写少的单表操作性能比innodb更好。 有些老项目中可能还在用它。 在创建表的时候只需要把ENGINE参数设置成MyISAM即可 CREATE TABLE category (id bigint NOT NULL AUTO_INCREMENT,one_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,two_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,three_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,four_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,PRIMARY KEY (id) ) ENGINEMyISAM AUTO_INCREMENT4 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_binmyisam好用但有个很致命的问题是不支持事务。 如果只是单表操作还好不会出现太大的问题。但如果需要跨多张表操作由于其不支持事务数据极有可能会出现不完整的情况。 此外myisam还不支持行锁和外键。 所以在实际业务场景中myisam使用的并不多。在mysql5以后myisam已经逐渐退出了历史的舞台取而代之的是innodb。 有时候我们在开发的过程中发现某张表的事务一直都没有生效那不一定是spring事务的锅最好确认一下你使用的那张表是否支持事务。 7.未开启事务 有时候事务没有生效的根本原因是没有开启事务。 你看到这句话可能会觉得好笑。 开启事务不是一个项目中最最最基本的功能吗 为什么还会没有开启事务 没错如果项目已经搭建好了事务功能肯定是有的。 但如果你是在搭建项目demo的时候只有一张表而这张表的事务没有生效。那么会是什么原因造成的呢 当然原因有很多但没有开启事务这个原因极其容易被忽略。 如果你使用的是springboot项目那么你很幸运。因为springboot通过DataSourceTransactionManagerAutoConfiguration类已经默默的帮你开启了事务。 你所要做的事情很简单只需要配置spring.datasource相关参数即可。 但如果你使用的还是传统的spring项目则需要在applicationContext.xml文件中手动配置事务相关参数。如果忘了配置事务肯定是不会生效的。 具体配置如下信息 !-- 配置事务管理器 --  bean classorg.springframework.jdbc.datasource.DataSourceTransactionManager idtransactionManager property namedataSource refdataSource/property  /bean  tx:advice idadvice transaction-managertransactionManager tx:attributes tx:method name* propagationREQUIRED//tx:attributes  /tx:advice  !-- 用切点把事务切进去 --  aop:config aop:pointcut expressionexecution(* com.susan.*.*(..)) idpointcut/ aop:advisor advice-refadvice pointcut-refpointcut/  /aop:config 默默的说一句如果在pointcut标签中的切入点匹配规则配错了的话有些类的事务也不会生效。 二 事务不回滚 1.错误的传播特性 其实我们在使用Transactional注解时是可以指定propagation参数的。 该参数的作用是指定事务的传播特性spring目前支持7种传播特性 REQUIRED 如果当前上下文中存在事务那么加入该事务如果不存在事务创建一个事务这是默认的传播属性值。 SUPPORTS 如果当前上下文存在事务则支持事务加入事务如果不存在事务则使用非事务的方式执行。 MANDATORY 如果当前上下文中存在事务否则抛出异常。 REQUIRES_NEW 每次都会新建一个事务并且同时将上下文中的事务挂起执行当前新建事务完成以后上下文事务恢复再执行。 NOT_SUPPORTED 如果当前上下文中存在事务则挂起当前事务然后新的方法在没有事务的环境中执行。 NEVER 如果当前上下文中存在事务则抛出异常否则在无事务环境上执行代码。 NESTED 如果当前上下文中存在事务则嵌套事务执行如果不存在事务则新建事务。 如果我们在手动设置propagation参数的时候把传播特性设置错了比如 Service public class UserService {Transactional(propagation  Propagation.NEVER)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);} }我们可以看到add方法的事务传播特性定义成了Propagation.NEVER这种类型的传播特性不支持事务如果有事务则会抛异常。 目前只有这三种传播特性才会创建新事务REQUIREDREQUIRES_NEWNESTED。 2.自己吞了异常 事务不会回滚最常见的问题是开发者在代码中手动try...catch了异常。比如 Slf4j Service public class UserService {Transactionalpublic void add(UserModel userModel) {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);}} }这种情况下spring事务当然不会回滚因为开发者自己捕获了异常又没有手动抛出换句话说就是把异常吞掉了。 如果想要spring事务能够正常回滚必须抛出它能够处理的异常。如果没有抛异常则spring认为程序是正常的。 3.手动抛了别的异常 即使开发者没有手动捕获异常但如果抛的异常不正确spring事务也不会回滚。 Slf4j Service public class UserService {Transactionalpublic void add(UserModel userModel) throws Exception {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);throw new Exception(e);}} }上面的这种情况开发人员自己捕获了异常又手动抛出了异常Exception事务同样不会回滚。 因为spring事务默认情况下只会回滚RuntimeException运行时异常和Error错误对于普通的Exception非运行时异常它不会回滚。 4.自定义了回滚异常 在使用Transactional注解声明事务时有时我们想自定义回滚的异常spring也是支持的。可以通过设置rollbackFor参数来完成这个功能。 但如果这个参数的值设置错了就会引出一些莫名其妙的问题例如 Slf4j Service public class UserService {Transactional(rollbackFor  BusinessException.class)public void add(UserModel userModel) throws Exception {saveData(userModel);updateData(userModel);} }如果在执行上面这段代码保存和更新数据时程序报错了抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常报错的异常不属于BusinessException所以事务也不会回滚。 即使rollbackFor有默认值但阿里巴巴开发者规范中还是要求开发者重新指定该参数。 这是为什么呢 因为如果使用默认值一旦程序抛出了Exception事务不会回滚这会出现很大的bug。所以建议一般情况下将该参数设置成Exception或Throwable。 5.嵌套事务回滚多了 public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);roleService.doOtherThing();} }Service public class RoleService {Transactional(propagation  Propagation.NESTED)public void doOtherThing() {System.out.println(保存role表数据);} }这种情况使用了嵌套的内部事务原本是希望调用roleService.doOtherThing方法时如果出现了异常只回滚doOtherThing方法里的内容不回滚 userMapper.insertUser里的内容即回滚保存点。。但事实是insertUser也回滚了。 why? 因为doOtherThing方法出现了异常没有手动捕获会继续往上抛到外层add方法的代理方法中捕获了异常。所以这种情况是直接回滚了整个事务不只回滚单个保存点。 怎么样才能只回滚保存点呢 Slf4j Service public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);try {roleService.doOtherThing();} catch (Exception e) {log.error(e.getMessage(), e);}} }可以将内部嵌套事务放在try/catch中并且不继续往上抛异常。这样就能保证如果内部嵌套事务中出现异常只回滚内部事务而不影响外部事务。 三 其他 1 大事务问题 在使用spring事务时有个让人非常头疼的问题就是大事务问题。 通常情况下我们会在方法上Transactional注解填加事务功能比如 Service public class UserService {Autowired private RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {query1();query2();query3();roleService.save(userModel);update(userModel);} }Service public class RoleService {Autowired private RoleService roleService;Transactionalpublic void save(UserModel userModel) throws Exception {query4();query5();query6();saveData(userModel);} }但Transactional注解如果被加到方法上有个缺点就是整个方法都包含在事务当中了。 上面的这个例子中在UserService类中其实只有这两行才需要事务 roleService.save(userModel); update(userModel);在RoleService类中只有这一行需要事务 saveData(userModel);现在的这种写法会导致所有的query方法也被包含在同一个事务当中。 如果query方法非常多调用层级很深而且有部分查询方法比较耗时的话会造成整个事务非常耗时而从造成大事务问题。 2.编程式事务 上面聊的这些内容都是基于Transactional注解的主要说的是它的事务问题我们把这种事务叫做声明式事务。 其实spring还提供了另外一种创建事务的方式即通过手动编写代码实现的事务我们把这种事务叫做编程式事务。例如 Autowiredprivate TransactionTemplate transactionTemplate;...public void save(final User user) {queryData1();queryData2();transactionTemplate.execute((status)  {addData1();updateData2();return Boolean.TRUE;})}在spring中为了支持编程式事务专门提供了一个类TransactionTemplate在它的execute方法中就实现了事务的功能。 相较于Transactional注解声明式事务我更建议大家使用基于TransactionTemplate的编程式事务。主要原因如下 避免由于spring aop问题导致事务失效的问题。 能够更小粒度的控制事务的范围更直观。 建议在项目中少使用Transactional注解开启事务。但并不是说一定不能用它如果项目中有些业务逻辑比较简单而且不经常变动使用Transactional注解开启事务开启事务也无妨因为它更简单开发效率更高但是千万要小心事务失效的问题。
http://www.ho-use.cn/article/10816222.html

相关文章:

  • 网站营销策划公司dw制作wap网站怎么做
  • 2017年做那个网站致富做网站编辑累吗
  • 为什么网站建设价格不一徐州专业网站制作公司
  • 餐饮网站源码百度推广费用多少
  • 个人可以备案网站的内容海外推广解决方案
  • 静态网站建设规划wordpress免费的吗
  • 微网站怎么注册账号青海省建设厅网站
  • 福州企业建站程序如何做网站的优化和推广
  • 网站开发方案论文wordpress 分页无效
  • 拖拽建站 wordpressfifa世界排名最新
  • html下载网站模板旅游新闻热点
  • 做网站公司流程蓝色大气网站模板
  • 网站开发 问题 关键技术为什么 要建设网站
  • 成都网站建设公司多少钱叫别人做网站需要注意什么
  • jsp网站建设项目实战wordpress最简洁主题
  • 合肥专业网站优化哪家好wordpress 欢迎插件
  • 西安网站建设推荐q479185700上墙免费空间最大的网盘
  • 网站大多用源码来做吗有免费的接码平台吗
  • 静态手机网站怎么开通微信公众号
  • 伦教网站建设骨科医院网站模板
  • 网站建设内部下单流程图网站开发及流行框架
  • 珠海商城网站wordpress标签加入文章列表
  • 做网站虚拟主机多少钱申请微信公众号
  • 网站图片上传功能怎么做的网站负面信息
  • 给别人做网站用做假酒验证将一个网站拉入黑名单怎么做
  • 网站重新建设的申请怎么建立自己网站
  • 搜索推广网站哪家做的最好凡科网做网站能达到什么效果
  • 网站设计 联系网站空间知识
  • 泽成杭州seo网站推广排名网站建设工种
  • 免费erp系统网站建优化