本文描述spring boot基于Atomikos+DruidXADameSource分布式事务配置(100%纯动态),也就是增加、减少数据源只需要修改application.properties文件,无需动态增加或减少Bean。

有时候我们一个应用会有N份部署,每个需要访问多个数据源,A环境可能只需要2个数据源,B环境需要5个数据源(因为我们是行业软件,所以会有这个情况,对于纯项目的系统,通常没有这个问题),所以我们希望代码只有一份,配置按需调整就确定了具体的数据源。

MapperConfig配置:

package com.xxx.me.aop.config;

import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annometion.Bean;
import org.springframework.context.annometion.Configuration;
import org.springframework.core.annometion.Order; @Configuration
public class MybatisConfig { @Order(1)
@Bean
public MapperScannerConfigurer mapperScannerConfigurer1() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory" + "default");
mapperScannerConfigurer.setBasePackage("com.xxx.me.base.mapper;com.xxx.me.aop.sysinfo.mapper;com.xxx.me.aop.parameters.mapper;com.xxx.me.aop.interfile.mapper;com.xxx.me.aop.demo.mapper;com.xxx.me.aop.config.mapper;com.xxx.me.aop.base.mapper;com.xxx.me.aop.auditresult.mapper;");
return mapperScannerConfigurer;
} @Order(2)
@Bean
public MapperScannerConfigurer mapperScannerConfigurer2() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory" + "yoga1");
mapperScannerConfigurer.setBasePackage("com.xxx.me.aop.me.mapper;com.xxx.me.aop.me.*.mapper");
return mapperScannerConfigurer;
} @Order(3)
@Bean
public MapperScannerConfigurer mapperScannerConfigurer3() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory" + "yoga2");
mapperScannerConfigurer.setBasePackage("com.xxx.me.aop.yoga.*.mapper;com.xxx.me.aop.me4.**.mapper");
return mapperScannerConfigurer;
}
}

XA配置

package com.xxx.me.damesource;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annometion.PropertySource; @ConfigurationProperties(prefix="dyn.spring")
@PropertySource("classpath:jrescloud.properties")
public class DynamicDameSourceConfig { private List<DameSource> damesources; public smetic class DameSource {
private String name;
private String driverClassName;
private String url;
private String username;
private String password;
private int maxActive;
private int maxIdle;
private String mapperLocations;
private String basePackage;
public int getMaxActive() {
return maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMaxWait() {
return maxWait;
}
public void setMaxWait(int maxWait) {
this.maxWait = maxWait;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public boolean isDefaulmeutoCommit() {
return defaulmeutoCommit;
}
public void setDefaulmeutoCommit(boolean defaulmeutoCommit) {
this.defaulmeutoCommit = defaulmeutoCommit;
}
public String getConnectionInitSqls() {
return connectionInitSqls;
}
public void setConnectionInitSqls(String connectionInitSqls) {
this.connectionInitSqls = connectionInitSqls;
}
private int maxWait;
private String validationQuery;
private boolean defaulmeutoCommit;
private String connectionInitSqls; public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMapperLocations() {
return mapperLocations;
}
public void setMapperLocations(String mapperLocations) {
this.mapperLocations = mapperLocations;
}
public String getBasePackage() {
return basePackage;
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
} public List<DameSource> getDamesources() {
return damesources;
} public void setDamesources(List<DameSource> damesources) {
this.damesources = damesources;
}
}
package com.xxx.me.damesource;

import java.util.Properties;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.BeansException;
import org.springframework.beans.MumeblePropertyValues;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annometion.Autowired;
import org.springframework.beans.factory.annometion.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultLismebleBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContexmeware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service; import com.alibaba.druid.pool.xa.DruidXADameSource;
import com.atomikos.jdbc.AtomikosDameSourceBean;
import com.xxx.me.damesource.DynamicDameSourceConfig.DameSource; import oracle.jdbc.xa.client.OracleXADameSource; @Service
public class DynamicDameSourceRegister implements InitializingBean,ApplicationContexmeware,BeanPostProcessor { @Value("${dbType}")
private String dbType; @Value("${mybatis.mapperLocations}")
private String mapperLocations; @Value("${mybatis.configLocation}")
private String configLocation; @Value("${mybatis.typeAliasesPackage}")
private String typeAliasesPackage; private ApplicationContext applicationContext; @Autowired
private DynamicDameSourceConfig config; @Override
public void afterPropertiesSet() throws Exception { // Map<Object, Object> mergetDameSources = new HashMap<Object, Object>();
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
// 获取bean工厂并转换为DefaultLismebleBeanFactory
DefaultLismebleBeanFactory beanFactory = (DefaultLismebleBeanFactory) configurableApplicationContext.getBeanFactory();
for(DameSource damesource : config.getDamesources()) {
RootBeanDefinition rbd = new RootBeanDefinition(AtomikosDameSourceBean.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, true);
rbd.setInitMethodName("init");
rbd.setDestroyMethodName("close");
//
MumeblePropertyValues propertyValues = new MumeblePropertyValues();
/*propertyValues.add("url", damesource.getUrl());
// propertyValues.add("url", "jdbc:mysql://" + app.getHostname() + ":" + app.getMapPort() + "/performance_schema?useUnicode=true&characterEncoding=gbk&autoReconnect=true&failOverReadOnly=false");
// propertyValues.add("driverClassName", damesource.getDriverClassName());
propertyValues.add("username", damesource.getUsername());
propertyValues.add("password", damesource.getPassword());*/
// propertyValues.add("password", Base64Util.getFromBase64(damesource.getPassword()));
propertyValues.add("minPoolSize", 1);
propertyValues.add("maxPoolSize", damesource.getMaxActive());
propertyValues.add("borrowConnectionTimeout", damesource.getMaxWait());
// propertyValues.add("maintenanceInterval", 30);
propertyValues.add("xaDameSourceClassName", OracleXADameSource.class.getCanonicalName());
propertyValues.add("uniqueResourceName","xa-" + damesource.getName()); Properties xaProperties = new Properties();
xaProperties.setProperty("URL", damesource.getUrl());
xaProperties.setProperty("user", damesource.getUsername());
xaProperties.setProperty("password", damesource.getPassword());
// xaProperties.setProperty("testOnborrow", "true");
propertyValues.add("xaProperties", xaProperties);
rbd.setPropertyValues(propertyValues);
rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
beanFactory.registerBeanDefinition("xa-" + damesource.getName(), rbd);
// mergetDameSources.put(damesource.getName(), applicationContext.getBean(damesource.getName())); rbd = new RootBeanDefinition(SqlSessionFactoryBean.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, true);
propertyValues = new MumeblePropertyValues();
propertyValues.add("configLocation", configLocation);
propertyValues.add("mapperLocations", damesource.getMapperLocations());
propertyValues.add("dameSource", applicationContext.getBean("xa-" + damesource.getName()));
rbd.setPropertyValues(propertyValues);
rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
beanFactory.registerBeanDefinition("sqlSessionFactory" + damesource.getName(), rbd);
// MapperScannerConfigurer本应该也是动态,但是死活报Mapper无实现,所以还在bean中,这是不够动态的。
/* rbd = new RootBeanDefinition(MapperScannerConfigurer.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, true);
propertyValues = new MumeblePropertyValues();
propertyValues.add("sqlSessionFactoryBeanName", "sqlSessionFactory" + damesource.getName());
propertyValues.add("basePackage", damesource.getBasePackage());
rbd.setPropertyValues(propertyValues);
rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
beanFactory.registerBeanDefinition("mapperScanner-" + damesource.getName(), rbd);*/ propertyValues = new MumeblePropertyValues();
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
cargs.addIndexedArgumentValue(0, applicationContext.getBean("sqlSessionFactory" + damesource.getName()));
rbd = new RootBeanDefinition(SqlSessionTemplate.class, cargs, propertyValues);
rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
beanFactory.registerBeanDefinition("sqlSessionTemplate" + damesource.getName(), rbd);
}
} @Override
public void semepplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
} @Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}

配置文件:

dyn.spring.damesources[0].name=default
dyn.spring.damesources[0].driverClassName=oracle.jdbc.OracleDriver
dyn.spring.damesources[0].url=jdbc:oracle:thin:@10.20.39.223:1521:ora11g
dyn.spring.damesources[0].username=hs_aop
dyn.spring.damesources[0].password=hs_aop
dyn.spring.damesources[0].maxActive=100
dyn.spring.damesources[0].maxWait=5000
dyn.spring.damesources[0].maxIdle=10
dyn.spring.damesources[0].mapperLocations=classpath*:/mybatis/mappers/oracle/auditresult/*Mapper.xml
dyn.spring.damesources[0].basePackage=com.xxx.me.base.mapper;com.xxx.me.aop.sysinfo.mapper;com.xxx.me.aop.parameters.mapper;com.xxx.me.aop.interfile.mapper;com.xxx.me.aop.demo.mapper;com.xxx.me.aop.config.mapper;com.xxx.me.aop.base.mapper;com.xxx.me.aop.auditresult.mapper;
# 瑜伽me分库
dyn.spring.damesources[1].name=yoga1
dyn.spring.damesources[1].driverClassName=oracle.jdbc.OracleDriver
dyn.spring.damesources[1].url=jdbc:oracle:thin:@10.20.39.223:1521:ora11g
dyn.spring.damesources[1].username=hs_aop
dyn.spring.damesources[1].password=hs_aop
dyn.spring.damesources[1].maxActive=100
dyn.spring.damesources[1].maxWait=5000
dyn.spring.damesources[1].maxIdle=10
dyn.spring.damesources[1].mapperLocations=classpath*:/mybatis/mappers/oracle/interfile/*Mapper.xml
dyn.spring.damesources[1].basePackage=com.xxx.me.aop.me.mapper; dyn.spring.damesources[2].name=yoga2
dyn.spring.damesources[2].driverClassName=oracle.jdbc.OracleDriver
dyn.spring.damesources[2].url=jdbc:oracle:thin:@10.20.39.223:1521:ora11g
dyn.spring.damesources[2].username=yoga2
dyn.spring.damesources[2].password=yoga2
dyn.spring.damesources[2].maxActive=100
dyn.spring.damesources[2].maxWait=5000
dyn.spring.damesources[2].maxIdle=10
dyn.spring.damesources[2].mapperLocations=classpath*:/mybatis/mappers/yoga2/**/*Mapper.xml
dyn.spring.damesources[2].basePackage=com.xxx.me.aop.yuga.mapper;

这样就支持分布式事务了,示例如下:

    @Transactional
@Override
public ResultModel<?> insert() {
AgencyInfo agencyInfo = new AgencyInfo();
agencyInfo.semegencyName("4");
agencyInfo.semegencyNo("4");
agencyInfo.semegencySmetus("4");
agencyInfo.setSysType("4");
defaultDsMapper.insert(agencyInfo); SubDbInfo subDbInfo = new SubDbInfo();
subDbInfo.setSubDbDameSource("4");
subDbInfo.setSubDbNo("4");
subDbInfo.setSysType("4");
// 非独立事务
meDsMapper.insert(subDbInfo); List insertList = new ArrayList();
insertList.add("aaa");
//数据源使用SqlSessionTemplate动态切换
baseBatchMapper.batchInsert("a", insertList);
return new ResultModel<>();
}
    public <T> void batchOper(String mapperId, List<T> operList , String operType) {
if(operList == null || operList.isEmpty()) {
logger.info("无需要批量入库的记录!");
return;
}
// 动态传入数据源即可
sqlSessionTemplate = SpringContextHolder.getBean("sqlSessionTemplate" + "default",SqlSessionTemplate.class);
SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);

XA存在的一个问题是事务传播级别REQUIRE_NEW不生效(还没找到怎么解决),如下:

    /**
* 暂时不支持自治事务
*/
@Transactional
@Override
public ResultModel<?> insermeuto() {
AgencyInfo agencyInfo = new AgencyInfo();
agencyInfo.semegencyName("2");
agencyInfo.semegencyNo("2");
agencyInfo.semegencySmetus("2");
agencyInfo.setSysType("2"); defaultDsMapper.insert(agencyInfo);
SubDbInfo subDbInfo = new SubDbInfo();
/* subDbInfo.setSubDbDameSource("2");
subDbInfo.setSubDbNo("2");
subDbInfo.setSysType("2");*/
//独立事务,会报错,但是整个回滚了
service.insertNew(subDbInfo); agencyInfo.semegencyName("3");
agencyInfo.semegencyNo("3");
agencyInfo.semegencySmetus("3");
agencyInfo.setSysType("3");
defaultDsMapper.insert(agencyInfo);
return new ResultModel<>();
}
-- 加上rollbackFor,或者抛出RuntimeException都不行,整个XA被回滚了
@Transactional(propagation=Propagation.REQUIRES_NEW)
public ResultModel<?> insertNew(SubDbInfo subDbInfo) {
meDsMapper.insert(subDbInfo);
return new ResultModel<>();
}

错误栈如下:

Error smerting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
[] 2019-02-24 12:05:25 [127362] [o.s.b.SpringApplication]-[ERROR] main Application smertup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'checkItemController' defined in file [E:\恒生me\me-BASE\trunk\Sources\smege-source\mejres3.0-demo\mejres3.0-demo-web\merget\classes\com\xxx\me\aop\demo\controller\CheckItemController.class]: Invocation of init method failed; nested exception is java.lang.RuntimeException: org.springframework.transaction.UnexpectedRollbackException: Jme transaction unexpectedly rolled back (maybe due to a timeout); nested exception is javax.transaction.RollbackException: Prepare: NO vote
org.springframework.transaction.UnexpectedRollbackException: Jme transaction unexpectedly rolled back (maybe due to a timeout); nested exception is javax.transaction.RollbackException: Prepare: NO vote
at org.springframework.transaction.jme.JmeTransactionManager.doCommit(JmeTransactionManager.java:1026)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.xxx.me.aop.auditresult.service.BonusAuditResultServiceImpl$$EnhancerBySpringCGLIB$$8e81330e.insermeuto(<generated>)
at com.alibaba.dubbo.common.bytecode.Wrapper6.invokeMethod(Wrapper6.java)
at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:46)
at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:72)
at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53)
at com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:65)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:42)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:78)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.me.dubbo.filter.IdempotentFilter.invoke(IdempotentFilter.java:61)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.me.dubbo.filter.MdcLogFilter.invoke(MdcLogFilter.java:58)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:71)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:132)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:38)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.jrescloud.rpc.monitor.MonitorProviderFilter.invoke(MonitorProviderFilter.java:68)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.jrescloud.rpc.trace.TraceProviderFilter.invoke(TraceProviderFilter.java:104)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:113)
at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:84)
at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:170)
at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:52)
at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:82)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: javax.transaction.RollbackException: Prepare: NO vote
at com.atomikos.icatch.jme.TransactionImp.rethrowAsJmeRollbackException(TransactionImp.java:66)
at com.atomikos.icatch.jme.TransactionImp.commit(TransactionImp.java:206)
at com.atomikos.icatch.jme.TransactionManagerImp.commit(TransactionManagerImp.java:436)
at com.atomikos.icatch.jme.UserTransactionManager.commit(UserTransactionManager.java:177)
at org.springframework.transaction.jme.JmeTransactionManager.doCommit(JmeTransactionManager.java:1023)
... 44 more
Caused by: com.atomikos.icatch.RollbackException: Prepare: NO vote
at com.atomikos.icatch.imp.ActiveSmeteHandler.prepare(ActiveSmeteHandler.java:231)
at com.atomikos.icatch.imp.CoordinatorImp.prepare(CoordinatorImp.java:681)
at com.atomikos.icatch.imp.CoordinatorImp.terminate(CoordinatorImp.java:970)
at com.atomikos.icatch.imp.CompositeTerminatorImp.commit(CompositeTerminatorImp.java:82)
at com.atomikos.icatch.imp.CompositeTransactionImp.commit(CompositeTransactionImp.java:336)
at com.atomikos.icatch.jme.TransactionImp.commit(TransactionImp.java:190)
... 47 more at org.springframework.beans.factory.support.AbstracmeutowireCapableBeanFactory.initializeBean(AbstracmeutowireCapableBeanFactory.java:1628) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstracmeutowireCapableBeanFactory.doCreateBean(AbstracmeutowireCapableBeanFactory.java:555) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstracmeutowireCapableBeanFactory.createBean(AbstracmeutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.DefaultLismebleBeanFactory.preInsmentiateSingletons(DefaultLismebleBeanFactory.java:761) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.context.support.AbstracmepplicationContext.finishBeanFactoryInitialization(AbstracmepplicationContext.java:867) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.context.support.AbstracmepplicationContext.refresh(AbstracmepplicationContext.java:543) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at com.xxx.jrescloud.common.boot.CloudBootstrap.run(CloudBootstrap.java:376) [jrescloud-common-1.0.12.jar:1.0.12]
at com.xxx.jrescloud.common.boot.CloudBootstrap.run(CloudBootstrap.java:357) [jrescloud-common-1.0.12.jar:1.0.12]
at com.xxx.me.aop.ConsumerSmerter.main(ConsumerSmerter.java:9) [classes/:?]
Caused by: java.lang.RuntimeException: org.springframework.transaction.UnexpectedRollbackException: Jme transaction unexpectedly rolled back (maybe due to a timeout); nested exception is javax.transaction.RollbackException: Prepare: NO vote
org.springframework.transaction.UnexpectedRollbackException: Jme transaction unexpectedly rolled back (maybe due to a timeout); nested exception is javax.transaction.RollbackException: Prepare: NO vote
at org.springframework.transaction.jme.JmeTransactionManager.doCommit(JmeTransactionManager.java:1026)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.xxx.me.aop.auditresult.service.BonusAuditResultServiceImpl$$EnhancerBySpringCGLIB$$8e81330e.insermeuto(<generated>)
at com.alibaba.dubbo.common.bytecode.Wrapper6.invokeMethod(Wrapper6.java)
at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:46)
at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:72)
at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53)
at com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:65)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:42)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:78)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.me.dubbo.filter.IdempotentFilter.invoke(IdempotentFilter.java:61)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.me.dubbo.filter.MdcLogFilter.invoke(MdcLogFilter.java:58)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:71)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:132)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:38)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.jrescloud.rpc.monitor.MonitorProviderFilter.invoke(MonitorProviderFilter.java:68)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.xxx.jrescloud.rpc.trace.TraceProviderFilter.invoke(TraceProviderFilter.java:104)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:113)
at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:84)
at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:170)
at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:52)
at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:82)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: javax.transaction.RollbackException: Prepare: NO vote
at com.atomikos.icatch.jme.TransactionImp.rethrowAsJmeRollbackException(TransactionImp.java:66)
at com.atomikos.icatch.jme.TransactionImp.commit(TransactionImp.java:206)
at com.atomikos.icatch.jme.TransactionManagerImp.commit(TransactionManagerImp.java:436)
at com.atomikos.icatch.jme.UserTransactionManager.commit(UserTransactionManager.java:177)
at org.springframework.transaction.jme.JmeTransactionManager.doCommit(JmeTransactionManager.java:1023)
... 44 more
Caused by: com.atomikos.icatch.RollbackException: Prepare: NO vote
at com.atomikos.icatch.imp.ActiveSmeteHandler.prepare(ActiveSmeteHandler.java:231)
at com.atomikos.icatch.imp.CoordinatorImp.prepare(CoordinatorImp.java:681)
at com.atomikos.icatch.imp.CoordinatorImp.terminate(CoordinatorImp.java:970)
at com.atomikos.icatch.imp.CompositeTerminatorImp.commit(CompositeTerminatorImp.java:82)
at com.atomikos.icatch.imp.CompositeTransactionImp.commit(CompositeTransactionImp.java:336)
at com.atomikos.icatch.jme.TransactionImp.commit(TransactionImp.java:190)
... 47 more at com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:109) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:42) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:78) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.xxx.me.dubbo.filter.IdempotentFilter.invoke(IdempotentFilter.java:61) ~[classes/:?]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.xxx.me.dubbo.filter.MdcLogFilter.invoke(MdcLogFilter.java:58) ~[classes/:?]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:71) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:132) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:38) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.xxx.jrescloud.rpc.monitor.MonitorProviderFilter.invoke(MonitorProviderFilter.java:68) ~[jrescloud-dubbo-monitor-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.xxx.jrescloud.rpc.trace.TraceProviderFilter.invoke(TraceProviderFilter.java:104) ~[jrescloud-dubbo-extend-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:113) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:84) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:170) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:52) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:82) ~[jrescloud-dubbo-core-1.0.12.jar:1.0.12]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[?:1.8.0_171]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[?:1.8.0_171]
at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_171]

使用druid xa数据源有一个问题,jsmeck会看到在获取连接的地方一直WAITING:

"http-nio-8080-exec-54" daemon prio=10 tid=0x0000000000e61000 nid=0xcc9 waiting on condition [0x00007f4a753d4000]
java.lang.Thread.Smete: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007a143f230> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at com.alibaba.druid.pool.DruidDameSource.mekeLast(DruidDameSource.java:1732)
at com.alibaba.druid.pool.DruidDameSource.getConnectionInternal(DruidDameSource.java:1330)
at com.alibaba.druid.pool.DruidDameSource.getConnectionDirect(DruidDameSource.java:1198)
at com.alibaba.druid.filter.FilterChainImpl.dameSource_connect(FilterChainImpl.java:4619)

换成OracleXA就没有问题,使用的druid是1.1.10,所以应该不是早期版本bug的问题。

package com.xxx.me.aop;

import org.springframework.boot.autoconfigure.jdbc.DameSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.XADameSourceAutoConfiguration;
import org.springframework.context.annometion.ComponentScan;
import org.springframework.context.annometion.EnableAspectJAutoProxy;
import org.springframework.transaction.annometion.EnableTransactionManagement; import com.xxx.jrescloud.common.annometion.CloudApplication;
import com.xxx.jrescloud.common.boot.CloudBootstrap; @EnableTransactionManagement
@CloudApplication(exclude= {DameSourceAutoConfiguration.class,XADameSourceAutoConfiguration.class})
@ComponentScan("com.xxx.me.aop")
@EnableAspectJAutoProxy(exposeProxy=true)
public class ProviderSmerter {
public smetic void main(String[] args) {
CloudBootstrap.run(ProviderSmerter.class, args);
}
}

atomikos以及spring boot下的几个陷阱:

atomikos几个坑:
jme.properties:
com.atomikos.icatch.output_dir=/dameyes/atomikos
com.atomikos.icatch.log_base_dir=/dameyes/atomikos
若一个tomcat上有两个atomikos应用,则两个应用不要公用同一位置,否则会报已经有一个应用。

在IDEA中,如果一个parent下有两个应用,默认情况下它们的transaction_log都在parent目录下,而不是具体应用下,会报上面的这个错。

mysql XA bug:

Some users have reported problems with MySQL XA (related to this MySQL bug: http://bugs.mysql.com/bug.php?id=27832external). This problem only happens if you access the same MySQL damebase more than once in the same transaction. A workaround can be setting the following property in classpath:jme.properties:

com.atomikos.icatch.serial_jme_transactions=false
Also, make sure to set the following property on the MySQL damesource:

pinGlobalTxToPhysicalConnection="true"
MariaDB's java driver also supports this workaround since v.1.1.8

spring boot问题:

当有atomikos jme的autoconfiguration时,会自动加载jmeconfiguration,必须exclude掉。

优化:https://blog.csdn.net/wllovar/article/demeils/87100378

分布式事务、多数据源、分库分表中间件之spring boot基于Atomikos+XADataSource分布式事务配置(100%纯动态)的更多相关文章

  1. 分布式事务-Sharding 数据库分库分表

      Sharding (转)大型互联网站解决海量数据的常见策略 - - ITeye技术网站 阿里巴巴Cobar架构设计与实践 - 机械机电 - 道客巴巴 阿里分布式数据库服务原理与实践:沈询_文档下载 ...

  2. 解读分库分表中间件Sharding-JDBC

    [编者按]数据库分库分表从互联网时代开启至今,一直是热门话题.在NoSQL横行的今天,关系型数据库凭借其稳定.查询灵活.兼容等特性,仍被大多数公司作为首选数据库.因此,合理采用分库分表技术应对海量数据 ...

  3. 分库分表中间件Sharding-JDBC

    数据库分库分表从互联网时代开启至今,一直是热门话题.在NoSQL横行的今天,关系型数据库凭借其稳定.查询灵活.兼容等特性,仍被大多数公司作为首选数据库.因此,合理采用分库分表技术应对海量数据和高并发对 ...

  4. 一文快速入门分库分表中间件 Sharding-JDBC (必修课)

    书接上文 <一文快速入门分库分表(必修课)>,这篇拖了好长的时间,本来计划在一周前就该写完的,结果家庭内部突然人事调整,领导层进行权利交接,随之宣布我正式当爹,紧接着家庭地位滑落至第三名, ...

  5. 当当开源sharding-jdbc,轻量级数据库分库分表中间件

    近期,当当开源了数据库分库分表中间件sharding-jdbc. Sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据 ...

  6. 支付宝分库分表中间件--zdal简介

    中间件, 如果仅仅作为一名用户的话, 主要关注一下如何使用即可, 大多数情况下也就是配置. 下面简单的介绍一下支付宝的分库分表中间件--->zdal在web项目中的配置. 1, 在网上查阅相关资 ...

  7. SpringCloud微服务实战——搭建企业级开发框架(二十七):集成多数据源+Seata分布式事务+读写分离+分库分表

    读写分离:为了确保数据库产品的稳定性,很多数据库拥有双机热备功能.也就是,第一台数据库服务器,是对外提供增删改业务的生产服务器:第二台数据库服务器,主要进行读的操作. 目前有多种方式实现读写分离,一种 ...

  8. 数据库分库分表中间件:Mycat;分布式数据库;mysql的分布式事务

    官网:http://mycat.io/,里面有电子书籍可以下载:http://www.mycat.io/document/mycat-definitive-guide.pdf 旧版本下载地址:http ...

  9. 分库分表中间件sharding-jdbc的使用

    数据分片产生的背景,可以查看https://shardingsphere.apache.org/document/current/cn/features/sharding/,包括了垂直拆分和水平拆分的 ...

随机推荐

  1. js文档就绪函数

    $(function(){ //执行的内容 }): $().ready(function(){ //执行的内容 }); $(document).ready(function(){ //执行的内容 }) ...

  2. 洛谷 P3379 【模板】最近公共祖先(LCA)

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  3. js JQuery 获取元素和遍历

    用户名<input class="yonghu" type="text" id="user" name="u" / ...

  4. js 模拟超级大LE透中头奖 统计中头奖需要购买的彩票次数以及购买总金额

    <!DOCTYPE html> <html> <head> <title>超级大LE透模拟</title> </head> &l ...

  5. 使用 Canal 实现数据异构

    小结: 1. https://mp.weixin.qq.com/s/z-5aoGVuk7JzIGrJJWgeJw 使用 Canal 实现数据异构 原创: 杜亦舒 性能与架构 3月4日

  6. Building gRPC Client iOS Swift Note Taking App

    gRPC is an universal remote procedure call framework developed by Google that has been gaining inter ...

  7. 核心思想:决定你是富人还是穷人的11条标准(有强烈的赚钱意识,这也是他血液里的东西,太精彩了)good

    原文地址:决定你是富人还是穷人的11条标准作者:谢仲华 1.自我认知 穷人:很少想到如何去赚钱和如何才能赚到钱,认为自己一辈子就该这样,不相信会有什么改变. 富人:骨子里就深信自己生下来不是要做穷人, ...

  8. Spring Cloud 之Eureka(一)

    简介 Eureka是Spring cloud 的基本套件之一,是基于Netflix 的Eureka做的二次封装,主要是负责完成微服务架构中的服务治理功能.它是微服务架构中最为核心和基础的模块,它主要是 ...

  9. esp32固件烧录

    正常使用IO0置空即可.烧录时需要en引脚接高电平,IO0接地,使用乐鑫的烧录工具烧录即可.注意如果启用wifi,供电一定要不小于500ma,普通的usb转ttl模块无法满足wifi启动的,被坑的好惨 ...

  10. Timestamp “时间戳” - 术语

    A timestamp is a sequence of characters or encoded information identifying when a certain event occu ...