背景
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共存~