事务注解与tx:attributes共存


声明:本文转载自https://my.oschina.net/qixiaobo025/blog/1607785,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

背景

Spring的声明式事务用多了之后确实神清气爽啊~

我们通常使用tx:attribute各种方法名称来做处理

比较典型的包括get query list find count 等为只读事务

其他的包括update edit modify save insert add 等等为正常的写操作。虽说如此 但是总会出现部分同学不按照要求【事实上也很难避免】

比如如下

@Override public TmPrintCount getPrintCountIfNull(TmPrintCount printCount) {     String idOwnOrg = WxbStatic.getOrg();     TmPrintCount tmPrintCount = this.getPrintCountByBillId(printCount);     if (null == tmPrintCount){         printCount.setPkId(baseService.getUUid());         printCount.setPrintCount(1);         printCount.setIdOwnOrg(idOwnOrg);         this.addPrintCount(printCount);         return printCount;     }     return tmPrintCount; }

光从名字上很难发现这是个写业务~

在没有做读写分离之前这个问题不大,设想如果做了读写分离这个业务被路由到了从库上【只读】那么整块业务都会报错!

思考

既然出现了这种问题那么我们处理方式

  1. 增加新的配置比如增加一个指定名称的getPrintCountIfNull那么此处问个问题?是否tx:method的位置越靠前那么匹配的越高呢?比如声明count*为 涉及到优先级的问题不得马虎

    <tx:advice id="txAdvice" transaction-manager="transactionManager">     <tx:attributes>                  <tx:method name="load*" read-only="true" propagation="SUPPORTS"                    timeout="20"/>         <tx:method name="find*" read-only="true" propagation="SUPPORTS"                    timeout="20"/>         <tx:method name="get*" read-only="true" propagation="SUPPORTS"                    timeout="20"/>         <tx:method name="select*" read-only="true" propagation="SUPPORTS"                    timeout="20"/>         <tx:method name="list*" read-only="true" propagation="SUPPORTS"                    timeout="50"/>         <tx:method name="loginCheck" read-only="true" propagation="SUPPORTS"                    timeout="20"/>         <tx:method name="selectDiff*" read-only="true" propagation="SUPPORTS"                    timeout="120"/>         <tx:method name="importBatch*" propagation="REQUIRED" rollback-for="java.lang.Exception"                    timeout="180"/>         <tx:method name="fastImport*" propagation="REQUIRED" rollback-for="java.lang.Exception"                    timeout="180"/>         <tx:method name="createMonitor*" propagation="REQUIRED" rollback-for="java.lang.Exception"                    timeout="500"/>         <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception"                    timeout="50"/>     </tx:attributes> </tx:advice>
  2. 通过注解的方式 tx:annotation-driven

回答

  1. 我们来看一下如何计算出事务的属性呢?
    当指定tx:attributtes使用的是NameMatchTransactionAttributeSource 那么该AttributeSource如何计算呢?是否有序呢?

    public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable {      /**     * Logger available to subclasses.     * <p>Static for optimal serialization.     */    protected static final Log logger = LogFactory.getLog(NameMatchTransactionAttributeSource.class);      /** Keys are method names; values are TransactionAttributes */    private Map<String, TransactionAttribute> nameMap = new HashMap<String, TransactionAttribute>();        /**     * Set a name/attribute map, consisting of method names     * (e.g. "myMethod") and TransactionAttribute instances     * (or Strings to be converted to TransactionAttribute instances).     * @see TransactionAttribute     * @see TransactionAttributeEditor     */    public void setNameMap(Map<String, TransactionAttribute> nameMap) {       for (Map.Entry<String, TransactionAttribute> entry : nameMap.entrySet()) {          addTransactionalMethod(entry.getKey(), entry.getValue());       }    }      /**     * Parses the given properties into a name/attribute map.     * Expects method names as keys and String attributes definitions as values,     * parsable into TransactionAttribute instances via TransactionAttributeEditor.     * @see #setNameMap     * @see TransactionAttributeEditor     */    public void setProperties(Properties transactionAttributes) {       TransactionAttributeEditor tae = new TransactionAttributeEditor();       Enumeration propNames = transactionAttributes.propertyNames();       while (propNames.hasMoreElements()) {          String methodName = (String) propNames.nextElement();          String value = transactionAttributes.getProperty(methodName);          tae.setAsText(value);          TransactionAttribute attr = (TransactionAttribute) tae.getValue();          addTransactionalMethod(methodName, attr);       }    }      /**     * Add an attribute for a transactional method.     * <p>Method names can be exact matches, or of the pattern "xxx*",     * "*xxx" or "*xxx*" for matching multiple methods.     * @param methodName the name of the method     * @param attr attribute associated with the method     */    public void addTransactionalMethod(String methodName, TransactionAttribute attr) {       if (logger.isDebugEnabled()) {          logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]");       }       this.nameMap.put(methodName, attr);    }        public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {       // look for direct name match       String methodName = method.getName();       TransactionAttribute attr = this.nameMap.get(methodName);         if (attr == null) {          // Look for most specific name match.          String bestNameMatch = null;          for (String mappedName : this.nameMap.keySet()) {             if (isMatch(methodName, mappedName) &&                   (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {                attr = this.nameMap.get(mappedName);                bestNameMatch = mappedName;             }          }       }         return attr;    }      /**     * Return if the given method name matches the mapped name.     * <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,     * as well as direct equality. Can be overridden in subclasses.     * @param methodName the method name of the class     * @param mappedName the name in the descriptor     * @return if the names match     * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)     */    protected boolean isMatch(String methodName, String mappedName) {       return PatternMatchUtils.simpleMatch(mappedName, methodName);    }        @Override    public boolean equals(Object other) {       if (this == other) {          return true;       }       if (!(other instanceof NameMatchTransactionAttributeSource)) {          return false;       }       NameMatchTransactionAttributeSource otherTas = (NameMatchTransactionAttributeSource) other;       return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap);    }      @Override    public int hashCode() {       return NameMatchTransactionAttributeSource.class.hashCode();    }      @Override    public String toString() {       return getClass().getName() + ": " + this.nameMap;    }   }

    大失所望 这是一个hashMap并不是LinkedHashMap因此是无序的,那么我们在注册的时候也无从找到优先级

    public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {    // look for direct name match    String methodName = method.getName();    TransactionAttribute attr = this.nameMap.get(methodName);      if (attr == null) {       // Look for most specific name match.       String bestNameMatch = null;       for (String mappedName : this.nameMap.keySet()) {          if (isMatch(methodName, mappedName) &&                (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {             attr = this.nameMap.get(mappedName);             bestNameMatch = mappedName;          }       }    }      return attr; }

     

  2. 这段代码很明显是在找最合适的attrbute【如果不找到的话那么很有可能*会匹配上所有的方法名称】===》根据key的长短来区别 只要找到不小于上一个匹配的key的长度的key那么就进行更换
    这样保证*不会匹配上所有方法【不过通常泛化的方法都是比较短的~但是一旦出现了count和count*方法 事实上count都会被count*短路】

  3. 是否直接加上对应的注解就可以了呢?查看如下类TxAdviceBeanDefinitionParser

    class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {      private static final String METHOD_ELEMENT = "method";      private static final String METHOD_NAME_ATTRIBUTE = "name";      private static final String ATTRIBUTES_ELEMENT = "attributes";      private static final String TIMEOUT_ATTRIBUTE = "timeout";      private static final String READ_ONLY_ATTRIBUTE = "read-only";      private static final String PROPAGATION_ATTRIBUTE = "propagation";      private static final String ISOLATION_ATTRIBUTE = "isolation";      private static final String ROLLBACK_FOR_ATTRIBUTE = "rollback-for";      private static final String NO_ROLLBACK_FOR_ATTRIBUTE = "no-rollback-for";        @Override    protected Class<?> getBeanClass(Element element) {       return TransactionInterceptor.class;    }      @Override    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {       builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));         List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);       if (txAttributes.size() > 1) {          parserContext.getReaderContext().error(                "Element <attributes> is allowed at most once inside element <advice>", element);       }       else if (txAttributes.size() == 1) {          // Using attributes source.          Element attributeSourceElement = txAttributes.get(0);          RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);          builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);       }       else {          // Assume annotations source.          builder.addPropertyValue("transactionAttributeSource",                new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));       }    }      private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {       List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);       ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =          new ManagedMap<TypedStringValue, RuleBasedTransactionAttribute>(methods.size());       transactionAttributeMap.setSource(parserContext.extractSource(attrEle));         for (Element methodEle : methods) {          String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);          TypedStringValue nameHolder = new TypedStringValue(name);          nameHolder.setSource(parserContext.extractSource(methodEle));            RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();          String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);          String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);          String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);          String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);          if (StringUtils.hasText(propagation)) {             attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);          }          if (StringUtils.hasText(isolation)) {             attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);          }          if (StringUtils.hasText(timeout)) {             try {                attribute.setTimeout(Integer.parseInt(timeout));             }             catch (NumberFormatException ex) {                parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle);             }          }          if (StringUtils.hasText(readOnly)) {             attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));          }            List<RollbackRuleAttribute> rollbackRules = new LinkedList<RollbackRuleAttribute>();          if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {             String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);             addRollbackRuleAttributesTo(rollbackRules,rollbackForValue);          }          if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {             String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);             addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue);          }          attribute.setRollbackRules(rollbackRules);            transactionAttributeMap.put(nameHolder, attribute);       }         RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);       attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));       attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);       return attributeSourceDefinition;    }      private void addRollbackRuleAttributesTo(List<RollbackRuleAttribute> rollbackRules, String rollbackForValue) {       String[] exceptionTypeNames = StringUtils.commaDelimitedListToStringArray(rollbackForValue);       for (String typeName : exceptionTypeNames) {          rollbackRules.add(new RollbackRuleAttribute(StringUtils.trimWhitespace(typeName)));       }    }      private void addNoRollbackRuleAttributesTo(List<RollbackRuleAttribute> rollbackRules, String noRollbackForValue) {       String[] exceptionTypeNames = StringUtils.commaDelimitedListToStringArray(noRollbackForValue);       for (String typeName : exceptionTypeNames) {          rollbackRules.add(new NoRollbackRuleAttribute(StringUtils.trimWhitespace(typeName)));       }    }   } 中这一段   else if (txAttributes.size() == 1) {    // Using attributes source.    Element attributeSourceElement = txAttributes.get(0);    RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);    builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition); } else {    // Assume annotations source.    builder.addPropertyValue("transactionAttributeSource",          new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource")); }

    中这一段 

    else if (txAttributes.size() == 1) {    // Using attributes source.    Element attributeSourceElement = txAttributes.get(0);    RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);    builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition); } else {    // Assume annotations source.    builder.addPropertyValue("transactionAttributeSource",          new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource")); }

    很明显Annotation和attribute在默认情况下是二选一的~难道就没有办法了么?

方案

 

CompositeTransactionAttributeSource 顾名思义就是用来组合的,我们可以利用该类

public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {    for (TransactionAttributeSource tas : this.transactionAttributeSources) {       TransactionAttribute ta = tas.getTransactionAttribute(method, targetClass);       if (ta != null) {          return ta;       }    }    return null; }

冲着可以知道我们如果想同时利用注解和attribute那么一注解优先的情况下将transactionAttributeSources即可

  1. 我们不要使用tx:advice的xml,自己选择初始化 
  2. 继续使用tx:advice的注解在Spring初始化之后立刻重新设置transactionAttributeSource

在这我们选择第二种方案

@Component public class CompositeTransactionListener implements ApplicationListener<ContextRefreshedEvent> {     @Override     public void onApplicationEvent(ContextRefreshedEvent event) {         ApplicationContext applicationContext = event.getApplicationContext();         if (applicationContext.getParent() == null) {             TransactionInterceptor interceptor = (TransactionInterceptor) applicationContext.getBean(TransactionInterceptor.class);             interceptor.setTransactionAttributeSources(new TransactionAttributeSource[]{new AnnotationTransactionAttributeSource(), interceptor.getTransactionAttributeSource()});         }     } }

这样就完成了注解和attribute共存~

本文发表于2018年01月16日 16:32
(c)注:本文转载自https://my.oschina.net/qixiaobo025/blog/1607785,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2466 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1