开发原因

在Java后端开发过程中事务控制非常重要,而Spring为我们提供了方便的声明式事务方法@transactional。但是默认的Spring事务只支持单数据源,而实际上一个系统往往需要写多个数据源,这个时候我们就需要考虑如何通过Spring实现对分布式事务的支持。

开发思路

对于数据库层面的分布式事务而言,JTA(Java Transaction API,XA的JAVA实现方案)是一个不错的解决方案,通常JTA需要应用服务器的支持,但在查阅SpringBoot的文档时发现,它推荐了AtomikosBitronix两种无需服务器支持的分布式事务组件,在这两个组件中,Atomikos更受大家的好评,所以我选择使用Atomikos

Atomikos实践

pom.xml文件引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

application文件配置两个数据源

#account数据库
spring.datasource.account.url = jdbc:mysql://192.168.190.131:3307/seata-account?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
spring.datasource.account.username = root
spring.datasource.account.password = 123456
#storage数据库
spring.datasource.storage.url = jdbc:mysql://192.168.190.131:3308/seata-storage?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
spring.datasource.storage.username = root
spring.datasource.storage.password = 123456

MybatisConfig中配置这两个数据源

package com.yj.atomikos.config;import java.util.Properties;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.atomikos.jdbc.AtomikosDataSourceBean;@Configuration
public class MybatisConfig {@Value("${spring.datasource.account.url}")private String accountDbUrl;@Value("${spring.datasource.account.username}")private String accountUsername;@Value("${spring.datasource.account.password}")private String accountPassword;@Value("${spring.datasource.storage.url}")private String storageDbUrl;@Value("${spring.datasource.storage.username}")private String storageUsername;@Value("${spring.datasource.storage.password}")private String storagePassword;final static Logger logger = LoggerFactory.getLogger(MybatisConfig.class);/*** 配置druid显示监控统计信息 开启Druid的监控平台 http://localhost:8080/druid** @return servlet registration bean*/@Beanpublic ServletRegistrationBean druidServlet() {logger.info("Init Druid Servlet Configuration ");ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");// IP白名单,不设默认都可以// servletRegistrationBean.addInitParameter("allow",// "192.168.2.25,127.0.0.1");// IP黑名单(共同存在时,deny优先于allow)servletRegistrationBean.addInitParameter("deny", "192.168.190.131");// 控制台管理用户servletRegistrationBean.addInitParameter("loginUsername", "root");servletRegistrationBean.addInitParameter("loginPassword", "123456");// 是否能够重置数据 禁用HTML页面上的“Reset All”功能servletRegistrationBean.addInitParameter("resetEnable", "false");return servletRegistrationBean;}@Beanpublic FilterRegistrationBean filterRegistrationBean() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());filterRegistrationBean.addUrlPatterns("/*");filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");return filterRegistrationBean;}@Bean(name = "accountDataSource")@Primarypublic DataSource accountDataSource() {AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();atomikosDataSourceBean.setUniqueResourceName("accountDataSource");atomikosDataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");Properties properties = new Properties();properties.put("URL", accountDbUrl);properties.put("user", accountUsername);properties.put("password", accountPassword);atomikosDataSourceBean.setXaProperties(properties);return atomikosDataSourceBean;}@Bean(name = "storageDataSource")public DataSource storageDataSource() {AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();atomikosDataSourceBean.setUniqueResourceName("storageDataSource");atomikosDataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");Properties properties = new Properties();properties.put("URL", storageDbUrl);properties.put("user", storageUsername);properties.put("password", storagePassword);atomikosDataSourceBean.setXaProperties(properties);return atomikosDataSourceBean;}
}

AccountDataSourceConfig

package com.yj.atomikos.config;import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;@Configuration
@MapperScan(basePackages = { "com.yj.atomikos.dao.account" }, sqlSessionFactoryRef = "accountSqlSessionFactory")
public class AccountDataSourceConfig {public static final String MAPPER_XML_LOCATION = "classpath:mapper/account/*.xml";@Autowired@Qualifier("accountDataSource")private DataSource accountDataSource;@Beanpublic SqlSessionTemplate accountSqlSessionTemplate() throws Exception {return new SqlSessionTemplate(accountSqlSessionFactory());}@Beanpublic SqlSessionFactory accountSqlSessionFactory() throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(accountDataSource);factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_XML_LOCATION));return factoryBean.getObject();}
}

StorageDataSourceConfig

package com.yj.atomikos.config;import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;@Configuration
@MapperScan(basePackages = { "com.yj.atomikos.dao.storage" }, sqlSessionFactoryRef = "storageSqlSessionFactory")
public class StorageDataSourceConfig {public static final String MAPPER_XML_LOCATION = "classpath:mapper/storage/*.xml";@Autowired@Qualifier("storageDataSource")private DataSource storageDataSource;@Beanpublic SqlSessionTemplate storageSqlSessionTemplate() throws Exception {return new SqlSessionTemplate(storageSqlSessionFactory());}@Beanpublic SqlSessionFactory storageSqlSessionFactory() throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(storageDataSource);factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_XML_LOCATION));return factoryBean.getObject();}
}

BusinessService模拟事务

package com.yj.atomikos.service;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.yj.atomikos.dao.account.AccountDao;
import com.yj.atomikos.dao.storage.StorageDao;@Service
public class BusinessService {private static final Logger log = LoggerFactory.getLogger(BusinessService.class);@Autowiredprivate AccountDao accountDao;@Autowiredprivate StorageDao storageDao;@Transactionalpublic String transaction(long userId, long productId, String flag) {bisiness(userId, productId, flag);return "操作成功";}private void bisiness(long userId, long productId, String flag) {log.info("==业务开始==");accountDao.updateAccount(userId);if ("1".equals(flag)) {int i = 1 / 0;}storageDao.updateStorage(productId);log.info("==业务结束==");}
}

开始验证

启动项目,观察日志输出

2020-01-27 15:44:24.471  WARN 10732 --- [           main] c.a.i.config.UserTransactionServiceImp   : No properties path set - looking for transactions.properties in classpath...
2020-01-27 15:44:24.472  WARN 10732 --- [           main] c.a.i.config.UserTransactionServiceImp   : transactions.properties not found - looking for jta.properties in classpath...
2020-01-27 15:44:24.473  WARN 10732 --- [           main] c.a.i.config.UserTransactionServiceImp   : Failed to open transactions properties file - using default values
2020-01-27 15:44:24.506  INFO 10732 --- [           main] c.a.p.imp.StateRecoveryManagerImp        : com.atomikos.persistence.imp.WriteAheadObjectLog instantiation failed - falling back to default
2020-01-27 15:44:24.509  INFO 10732 --- [           main] c.a.persistence.imp.FileLogStream        : Starting read of logfile D:\eclipse\ws\DistributedTransaction\Atomikos\transaction-logs\tmlog25.log
2020-01-27 15:44:24.509  INFO 10732 --- [           main] c.a.persistence.imp.FileLogStream        : Done read of logfile
2020-01-27 15:44:24.509  INFO 10732 --- [           main] c.a.persistence.imp.AbstractLogStream    : Logfile closed: D:\eclipse\ws\DistributedTransaction\Atomikos\transaction-logs\tmlog25.log
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING core version: 3.9.3
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.automatic_resource_registration = true
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.client_demarcation = false
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.threaded_2pc = false
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.serial_jta_transactions = true
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.serializable_logging = true
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.log_base_dir = D:\eclipse\ws\DistributedTransaction\Atomikos\transaction-logs
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.max_actives = 50
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.checkpoint_interval = 500
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.enable_logging = true
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.output_dir = .\
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.log_base_name = tmlog
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.max_timeout = 300000
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.tm_unique_name = 192.168.190.1.tm
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING java.naming.factory.initial = com.sun.jndi.rmi.registry.RegistryContextFactory
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING java.naming.provider.url = rmi://localhost:1099
2020-01-27 15:44:24.530  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.service = com.atomikos.icatch.standalone.UserTransactionServiceFactory
2020-01-27 15:44:24.531  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.force_shutdown_on_vm_exit = false
2020-01-27 15:44:24.531  INFO 10732 --- [           main] c.a.i.c.i.AbstractUserTransactionService : USING com.atomikos.icatch.default_jta_timeout = 10000
2020-01-27 15:44:24.541  INFO 10732 --- [           main] o.s.t.jta.JtaTransactionManager          : Using JTA UserTransaction: com.atomikos.icatch.jta.UserTransactionManager@32b9bd12
2020-01-27 15:44:24.542  INFO 10732 --- [           main] o.s.t.jta.JtaTransactionManager          : Using JTA TransactionManager: com.atomikos.icatch.jta.UserTransactionManager@32b9bd12
2020-01-27 15:44:24.649  INFO 10732 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-01-27 15:44:24.657  INFO 10732 --- [           main] o.a.coyote.http11.Http11NioProtocol      : Starting ProtocolHandler ["http-nio-8001"]
2020-01-27 15:44:24.666  INFO 10732 --- [           main] o.a.tomcat.util.net.NioSelectorPool      : Using a shared selector for servlet write/read
2020-01-27 15:44:24.675  INFO 10732 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8001 (http)

调用接口

http://127.0.0.1:8001/transaction?user_id=1&product_id=1&flag=1

观察日志

2020-01-27 15:45:24.012  INFO 10732 --- [nio-8001-exec-3] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2020-01-27 15:45:24.013  INFO 10732 --- [nio-8001-exec-3] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2020-01-27 15:45:24.028  INFO 10732 --- [nio-8001-exec-3] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
2020-01-27 15:45:24.075  INFO 10732 --- [nio-8001-exec-3] c.a.icatch.imp.thread.TaskManager        : THREADS: using JDK thread pooling...
2020-01-27 15:45:24.081  INFO 10732 --- [nio-8001-exec-3] c.a.icatch.imp.BaseTransactionManager    : createCompositeTransaction ( 10000 ): created new ROOT transaction with id 192.168.190.1.tm0000100027
2020-01-27 15:45:24.089  INFO 10732 --- [nio-8001-exec-3] com.yj.atomikos.service.BusinessService  : ==业务开始==
2020-01-27 15:45:24.108  INFO 10732 --- [nio-8001-exec-3] c.atomikos.jdbc.AbstractDataSourceBean   : AtomikosDataSoureBean 'accountDataSource': getConnection ( null )...
2020-01-27 15:45:24.108  INFO 10732 --- [nio-8001-exec-3] c.atomikos.jdbc.AbstractDataSourceBean   : AtomikosDataSoureBean 'accountDataSource': init...
2020-01-27 15:45:24.108  WARN 10732 --- [nio-8001-exec-3] c.atomikos.jdbc.AbstractDataSourceBean   : AtomikosDataSoureBean 'accountDataSource': poolSize equals default - this may cause performance problems!
2020-01-27 15:45:24.115  INFO 10732 --- [nio-8001-exec-3] c.atomikos.jdbc.AtomikosDataSourceBean   : AtomikosDataSoureBean 'accountDataSource': initializing with [ xaDataSourceClassName=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource, uniqueResourceName=accountDataSource, maxPoolSize=1, minPoolSize=1, borrowConnectionTimeout=30, maxIdleTime=60, reapTimeout=0, maintenanceInterval=60, testQuery=null, xaProperties=[URL=jdbc:mysql://192.168.190.131:3307/seata-account?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true,user=root,password=123456], loginTimeout=0, maxLifetime=0]
2020-01-27 15:45:24.314  INFO 10732 --- [nio-8001-exec-3] c.a.d.xa.XATransactionalResource         : accountDataSource: refreshed XAResource
2020-01-27 15:45:24.353  INFO 10732 --- [nio-8001-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.mysql.jdbc.jdbc2.optional.JDBC4ConnectionWrapper@3dc5b19: calling getAutoCommit...
2020-01-27 15:45:24.356 DEBUG 10732 --- [nio-8001-exec-3] c.y.a.d.a.AccountDao.updateAccount       : ==>  Preparing: update account set money = money-10 where user_id = ?
2020-01-27 15:45:24.359  INFO 10732 --- [nio-8001-exec-3] c.a.icatch.imp.CompositeTransactionImp   : addParticipant ( XAResourceTransaction: 3139322E3136382E3139302E312E746D30303030313030303237:3139322E3136382E3139302E312E746D31 ) for transaction 192.168.190.1.tm0000100027
2020-01-27 15:45:24.359  INFO 10732 --- [nio-8001-exec-3] c.a.datasource.xa.XAResourceTransaction  : XAResource.start ( 3139322E3136382E3139302E312E746D30303030313030303237:3139322E3136382E3139302E312E746D31 , XAResource.TMNOFLAGS ) on resource accountDataSource represented by XAResource instance com.mysql.jdbc.jdbc2.optional.JDBC4MysqlXAConnection@1a063f06
2020-01-27 15:45:24.361  INFO 10732 --- [nio-8001-exec-3] c.a.icatch.imp.CompositeTransactionImp   : registerSynchronization ( com.atomikos.jdbc.AtomikosConnectionProxy$JdbcRequeueSynchronization@b42ba2df ) for transaction 192.168.190.1.tm0000100027
2020-01-27 15:45:24.361  INFO 10732 --- [nio-8001-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.mysql.jdbc.jdbc2.optional.JDBC4ConnectionWrapper@3dc5b19: calling prepareStatement(update account set money = money-10 where user_id = ?)...
2020-01-27 15:45:24.385 DEBUG 10732 --- [nio-8001-exec-3] c.y.a.d.a.AccountDao.updateAccount       : ==> Parameters: 1(Long)
2020-01-27 15:45:24.389 DEBUG 10732 --- [nio-8001-exec-3] c.y.a.d.a.AccountDao.updateAccount       : <==    Updates: 1
2020-01-27 15:45:24.390  INFO 10732 --- [nio-8001-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.mysql.jdbc.jdbc2.optional.JDBC4ConnectionWrapper@3dc5b19: close()...
2020-01-27 15:45:24.391  INFO 10732 --- [nio-8001-exec-3] c.a.datasource.xa.XAResourceTransaction  : XAResource.end ( 3139322E3136382E3139302E312E746D30303030313030303237:3139322E3136382E3139302E312E746D31 , XAResource.TMSUCCESS ) on resource accountDataSource represented by XAResource instance com.mysql.jdbc.jdbc2.optional.JDBC4MysqlXAConnection@1a063f06
2020-01-27 15:45:24.395  INFO 10732 --- [nio-8001-exec-3] c.a.datasource.xa.XAResourceTransaction  : XAResource.rollback ( 3139322E3136382E3139302E312E746D30303030313030303237:3139322E3136382E3139302E312E746D31 ) on resource accountDataSource represented by XAResource instance com.mysql.jdbc.jdbc2.optional.JDBC4MysqlXAConnection@1a063f06
2020-01-27 15:45:24.400  INFO 10732 --- [nio-8001-exec-3] c.a.icatch.imp.CompositeTransactionImp   : rollback() done of transaction 192.168.190.1.tm0000100027
2020-01-27 15:45:24.409 ERROR 10732 --- [nio-8001-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root causejava.lang.ArithmeticException: / by zero

同时观察两个数据源中的数据,正常。到这里Atomikos对于一个项目中多数据源的事务控制生效了。

Atomikos简介相关推荐

  1. Atomikos的使用

    第一步:添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId> ...

  2. 分布式事务解决方案——基于Atomikos的实现

    声明:以下关于"JTA规范事务模型"."Spring JTA分布式事务的实现"等内容均来源于其他大佬的博客内容,并已经表明出处. 1.JTA规范事务模型   J ...

  3. SpringCloud从入门到精通(超详细文档)

    前言:认识 Spring Cloud 及应用现状 Spring Cloud 是什么? 在学习本课程之前,读者有必要先了解一下 Spring Cloud. Spring Cloud 是一系列框架的有序集 ...

  4. SpringCloud从入门到精通(超详细文档二)

    上一篇文档(SpringCloud从入门到精通之超详细文档一)已经对Springboot/SpringCloud做了简单的介绍以及应用讲解,下面将继续为大家介绍SpringCloud后续应用. 第12 ...

  5. SpringBoot入门简介

    一.Spring Boot入门 1.Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特 ...

  6. etcd 笔记(01)— etcd 简介、特点、应用场景、常用术语、分布式 CAP 理论、分布式原理

    1. etcd 简介 etcd 官网定义: A highly-available key value store for shared configuration and service discov ...

  7. Docker学习(一)-----Docker简介与安装

    一.Docker介绍 1.1什么是docker Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源 Docker可以让开发者打包他们的应用以及依赖包到一个轻量级,可移植 ...

  8. 【Spring】框架简介

    [Spring]框架简介 Spring是什么 Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制)和AOP(Asp ...

  9. TensorRT简介

    TensorRT 介绍 引用:https://arleyzhang.github.io/articles/7f4b25ce/ 1 简介 TensorRT是一个高性能的深度学习推理(Inference) ...

最新文章

  1. WinAPI——Windows 消息
  2. Excel表格从指定部分重新分页打印的两种方法
  3. 如何使用Mybatis-plus
  4. 渗透测试之Nmap命令(二)
  5. Django运行项目时候出现DisallowedHost at / Invalid HTTP_HOST header:
  6. SDN 前提知识:关于东西南北向接口
  7. web端功能自动化定位元素(暂不更新)
  8. windows系统文件和dll文件
  9. gcf,gca,gco的区别
  10. Sevlet相关 摘抄笔记
  11. 一款强大的红队资产测绘工具
  12. 参考文献名称怎么复制_论文格式之注释:脚注、尾注、参考文献怎么弄?
  13. 简单好用的图片取色器【可取RGB数值】
  14. 使用CNNs网络,基于caltech 101数据集实现分类
  15. 阿里云 mysql 修改root密码修改_设置及修改MySQL root用户密码 - MySQL中文参考手册...
  16. 代码详解设计模式--观察者模式
  17. MBR与GPT分区扫盲,希捷2T、3T硬盘测评(多图杀猫)
  18. 深度学习之格式转换笔记(三):keras(.hdf5)模型转TensorFlow(.pb) 转TensorRT(.uff)格式
  19. STM32——NFC门禁模块(RC522)
  20. 核心代码是整个程序吗_你认为3D建模是像程序员一样敲代码吗?你太out了

热门文章

  1. 中台与组织 | 白话中台战略
  2. 物联网学习 - 实验课程方案
  3. latex_3_中文会议论文模板以及修改过程中遇到的问题的解决方案
  4. matlab超出所有矩阵维度,matlab中索引超出矩阵维度
  5. 操作系统<OS>学习习题——第二、三章:进程与处理机
  6. linux go服务器吗,Linux之CentOS上部署安装goproxy服务端
  7. 《Long-Term Temporal Convolutions for Action Recognition》 论文翻译
  8. P2P中的UDP穿透方法以及PYTHON实现
  9. 三对角线型行列式的求法
  10. php 无限查找下级业绩_php无限查询下级,php递归统计下级总数,php 获取无限子级...