网站开发kxhtml行业门户网站有什么作用

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

网站开发kxhtml,行业门户网站有什么作用,jsp网站开发详解书,wordpress注册跳过邮箱验证在某些业务场景下#xff0c;如果一个请求中#xff0c;需要同事写入多张表的数据#xff0c;但为了保证操作的原子性#xff08;要么同事插入数据成功#xff0c;要么同事插入失败#xff09;#xff0c;例如#xff0c;当我们创建用户的时候#xff0c;往往会给用户…在某些业务场景下如果一个请求中需要同事写入多张表的数据但为了保证操作的原子性要么同事插入数据成功要么同事插入失败例如当我们创建用户的时候往往会给用户赋予角色信息以及菜单权限信息此时就需要一个接口插入多张表的数据我们一般会用spring事务避免数据不一致的情况。 spring事务用起来也比较简单一个简单的注解Transactional就能轻松搞定事务 但是在一些特殊场景下事务会失效具体哪些场景让我们接下来分析一下。 一、失效场景集结 二、事务不生效场景 1.访问权限问题 Java的访问权限主要有四种private、default、protected、public他们的权限从左到右依次变大。 但如果我们在开发过程中把有某些事务方法定义了错误的访问权限就会导致事务功能出问题。 比如设置方法的访问权限为private的了。 Service public class UserService {Transactionalprivate void add(User user) {saveData(user);saveRole(user);} } 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、protected、default的话spring则不会提供事务功能。、 2.方法用final修饰 某个方法不想被子类重载这时可以将方法定义成final的普通方法这样定义是没问题的但如果将事务方法定义成final。例如 Service public class UserService {Transactionalpublic final void add(User user){saveData(user);saveRoleData(user);} } add()方法被定义成了final的会导致事务失效。 why? 因为spring事务底层使用了aop也就是通过jdk动态代理或者cglib帮我们生成了代理类在代理类中实现的事务功能。 但如果某个方法用final修饰了那么在他的代理类中就无法重写该方法而添加事务功能。 注意如果某个方法是static的同样无法通过动态代理变成事务方法。 3.方法内部调用 有时候我们需要在某个业务方法Service类调用另外一个事务方法比如 Service public class UserService {Autowiredprivate UserMapper userMapper;public void add(User user) {userMapper.insertUser(user);updateStatus(user);}Transactionalpublic void updateStatus(User user) {doSameThing();} } 在代码中事务方法add中直接调用事务方法updateStatusupdateStatus方法拥有事务的能力是因为spring aop生成代理了对象但是这种方法直接调用了this对象的方法所以updateStatus方法不会生成事务。 因此在同一个类中的方法直接内部调用会导致事务失效。 那如果有些场景确实需要在同一个类的某个方法中调用它自己的另外一个方法How to do? 3.1新加一个Service方法 这个方法比较简单只需要新加一个Service方法把Transactional注解加到新Service方法上把需要事务执行的代码移到新方法中。具体代码如下 Servcie public class ServiceA {Autowiredprivate 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 {Autowiredprivate 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()获取代理对象 3.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的注解了。 5.多线程调用 在实际项目开发中多线程的使用场景还是挺多的如果spring事务用在多线程场景中会出现什么问题呢 Slf4j Service public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(User user) throws Exception {userMapper.insertUser(user);new Thread(() - {roleService.doOtherThing();}).start();} }Service public class RoleService {Transactionalpublic void doOtherThing() {System.out.println(保存role表数据);} } 从代码中我们可以看到事务方法add中调用了事务方法doOtherThing但是事务方法doOtherThing是在另外一个线程中调用的。 这样会导致两个方法不在同一个线程中获取到的数据库连接不一样从而是两个不同的事务如果想doOtherThing方法中跑了异常add方法也回滚是不可能的。 spring的事务是通过数据库连接来实现的当前线程中保存了一个mapkey是数据源value是数据库连接。 private static final ThreadLocalMapObject, Object resources new NamedThreadLocal(Transactional resources); 我们说的同一个事务其实是指同一个数据库连接只有拥有同一个数据库连接才能同事提交和回滚如果在不同的线程拿到的数据库连接肯定是不一样所以是同的事务。 6.表不支持事务 在mysql5之前默认的数据库引擎是myisam。 它的好处是索引文件和数据库文件时分开存储的对于查多写少的单表操作性能比innodb更好。 但是它有一个很致命的问题不支持事务。 如果只是单表操作还好不会出现太大的问题但如果需要联合查询因其不支持事务数据极有可能出现不完整的情况。 此外myisam还不支持行锁和外键。 有时候我们在开发过程中发现某张表的事务一直没有生效那不一定时spring事务的问题可能时数据库不支持事务。 7.未开启事务 有时候事务没有生效的根本原因时没有开启事务。 如果时spring boot项目那么你不用担心因为spring boot通过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(User user) {saveData(user);updateData(user);} } add方法的事务传播特性定义成了Propagation.NEVER这种类型的传播特性不支持事务如果有事务则会抛异常。 目前只有这三种传播特性才会创建新事务REQUIREDREQUIRES_NEWNESTED。 2.自己吞了异常 事务不会回滚最常见的问题是开发者在代码中手动try…catch了异常。比如 Slf4j Service public class UserService {Transactionalpublic void add(User user) {try {saveData(user);updateData(user);} catch (Exception e) {log.error(e.getMessage(), e);}} } 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(User user) throws Exception {saveData(user);updateData(user);} } 如果在执行这段代码的时候保存和更新数据时程序报错了抛出了SqlException、DuplicateKeyException等异常。而BusinessException是自定义的异常报错的异常不属于BusinessException所以事务也不会回滚。 即使rollbackFor有默认值但alibaba开发者规范中还是要求开发人员重新指定该参数。 why? 因为如果使用默认值一旦程序抛出了Exception事务不会回滚这会出现很大的bug所以建议一般情况下将该参数设置成Exception或Throwable。 5.嵌套事务回滚太多 public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(User user) throws Exception {userMapper.insertUser(user);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(User user) throws Exception {userMapper.insertUser(user);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(User user) throws Exception {query1();query2();query3();roleService.save(user);update(user);} }Service public class RoleService {Autowired private RoleService roleService;Transactionalpublic void save(User user) throws Exception {query4();query5();query6();saveData(user);} } 但Transaction注解如果被加到方法上缺点就是整个方法都包含在事务当中了。 上面这个例子中在UserService类中只有这两行需要事务 roleService.save(user); update(user); 在RoleService类中只有一行需要事务 saveData(user); 这种写法会导致所有的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注解开启事务是可以的因为简单开发更快注意事务失效的问题就行。