背景
  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; }
  光从名字上很难发现这是个写业务~
  在没有做读写分离之前这个问题不大,设想如果做了读写分离这个业务被路由到了从库上【只读】那么整块业务都会报错!
   思考
  既然出现了这种问题那么我们处理方式
     -  增加新的配置比如增加一个指定名称的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>
 
-  通过注解的方式 tx:annotation-driven 
回答
     -  我们来看一下如何计算出事务的属性呢?
 当指定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; }
   
-  这段代码很明显是在找最合适的attrbute【如果不找到的话那么很有可能*会匹配上所有的方法名称】===》根据key的长短来区别 只要找到不小于上一个匹配的key的长度的key那么就进行更换
 这样保证*不会匹配上所有方法【不过通常泛化的方法都是比较短的~但是一旦出现了count和count*方法 事实上count都会被count*短路】
 
-  是否直接加上对应的注解就可以了呢?查看如下类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即可
     - 我们不要使用tx:advice的xml,自己选择初始化 
- 继续使用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共存~