Repository: Zh1Cheung/tyloo Branch: master Commit: 69c934eb4d6a Files: 256 Total size: 431.2 KB Directory structure: gitextract_mxpupjhz/ ├── .gitignore ├── README.md ├── pom.xml ├── tyloo-api/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── io/ │ └── tyloo/ │ └── api/ │ ├── Propagation.java │ ├── TransactionContext.java │ ├── TransactionContextEditor.java │ ├── TransactionStatus.java │ ├── TransactionXid.java │ ├── Tyloo.java │ ├── UniqueIdentity.java │ └── UuidUtils.java ├── tyloo-core/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── io/ │ └── tyloo/ │ ├── CancellingException.java │ ├── ConcurrentTransactionException.java │ ├── ConfirmingException.java │ ├── InvocationContext.java │ ├── NoExistedTransactionException.java │ ├── OptimisticLockException.java │ ├── Participant.java │ ├── SystemException.java │ ├── Terminator.java │ ├── Transaction.java │ ├── TransactionManager.java │ ├── TransactionRepository.java │ ├── common/ │ │ ├── MethodRole.java │ │ └── TransactionType.java │ ├── context/ │ │ └── MethodTransactionContextEditor.java │ ├── interceptor/ │ │ ├── TylooCoordinatorAspect.java │ │ ├── TylooCoordinatorInterceptor.java │ │ ├── TylooMethodContext.java │ │ ├── TylooTransactionAspect.java │ │ └── TylooTransactionInterceptor.java │ ├── recover/ │ │ ├── TylooRecoverConfiguration.java │ │ └── TylooTransactionRecovery.java │ ├── repository/ │ │ ├── CachableTransactionRepository.java │ │ ├── FileSystemTransactionRepository.java │ │ ├── JdbcTransactionRepository.java │ │ ├── RedisTransactionRepository.java │ │ ├── TransactionIOException.java │ │ └── helper/ │ │ ├── ExpandTransactionSerializer.java │ │ ├── JedisCallback.java │ │ ├── RedisHelper.java │ │ └── TransactionSerializer.java │ ├── serializer/ │ │ ├── JacksonJsonSerializer.java │ │ ├── JdkSerializationSerializer.java │ │ ├── KryoPoolSerializer.java │ │ └── ObjectSerializer.java │ ├── support/ │ │ ├── BeanFactory.java │ │ ├── FactoryBuilder.java │ │ └── TransactionConfigurator.java │ └── utils/ │ ├── ByteUtils.java │ ├── CollectionUtils.java │ ├── ReflectionUtils.java │ ├── StringUtils.java │ ├── TransactionUtils.java │ └── TylooMethodUtils.java ├── tyloo-dubbo/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── io/ │ │ └── tyloo/ │ │ └── dubbo/ │ │ ├── constants/ │ │ │ └── TransactionContextConstants.java │ │ ├── context/ │ │ │ └── DubboTransactionContextEditor.java │ │ └── proxy/ │ │ ├── javassist/ │ │ │ ├── TylooClassGenerator.java │ │ │ ├── TylooJavassistProxyFactory.java │ │ │ └── TylooProxy.java │ │ └── jdk/ │ │ ├── MethodProceedingJoinPoint.java │ │ ├── TylooInvokerInvocationHandler.java │ │ └── TylooJdkProxyFactory.java │ └── resources/ │ ├── META-INF/ │ │ └── dubbo/ │ │ └── org.apache.dubbo.rpc.ProxyFactory │ └── tyloo-dubbo.xml ├── tyloo-spring/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── dbscripts/ │ │ └── db.sql │ ├── java/ │ │ └── io/ │ │ └── tyloo/ │ │ └── spring/ │ │ ├── ConfigurableCoordinatorAspect.java │ │ ├── ConfigurableTransactionAspect.java │ │ ├── recover/ │ │ │ ├── DefaultTylooRecoverConfiguration.java │ │ │ └── RecoverScheduledJob.java │ │ ├── repository/ │ │ │ └── SpringJdbcTransactionRepository.java │ │ └── support/ │ │ ├── SpringBeanFactory.java │ │ ├── SpringPostProcessor.java │ │ └── SpringTransactionConfigurator.java │ └── resources/ │ └── tyloo.xml ├── tyloo-tutorial-sample/ │ ├── pom.xml │ ├── src/ │ │ └── tylooSampledb/ │ │ ├── create_db_cap.sql │ │ ├── create_db_ord.sql │ │ ├── create_db_red.sql │ │ └── create_db_tcc.sql │ ├── tyloo-dubbo-sample/ │ │ ├── pom.xml │ │ ├── src/ │ │ │ └── main/ │ │ │ └── tylooSampledb/ │ │ │ ├── create_db_cap.sql │ │ │ ├── create_db_ord.sql │ │ │ ├── create_db_red.sql │ │ │ └── create_db_tcc.sql │ │ ├── tyloo-dubbo-capital/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── tyloo/ │ │ │ │ └── sample/ │ │ │ │ └── dubbo/ │ │ │ │ └── capital/ │ │ │ │ └── service/ │ │ │ │ ├── CapitalAccountServiceImpl.java │ │ │ │ └── CapitalTradeOrderServiceImpl.java │ │ │ ├── resources/ │ │ │ │ ├── config/ │ │ │ │ │ └── spring/ │ │ │ │ │ └── local/ │ │ │ │ │ ├── appcontext-service-provider.xml │ │ │ │ │ ├── appcontext-service-tcc.xml │ │ │ │ │ └── appcontext-web-servlet.xml │ │ │ │ ├── log/ │ │ │ │ │ └── log4j.xml │ │ │ │ ├── sample-dubbo-capital.properties │ │ │ │ └── tccjdbc.properties │ │ │ └── webapp/ │ │ │ ├── WEB-INF/ │ │ │ │ └── web.xml │ │ │ └── index.jsp │ │ ├── tyloo-dubbo-capital-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── io/ │ │ │ └── tyloo/ │ │ │ └── sample/ │ │ │ └── dubbo/ │ │ │ └── capital/ │ │ │ └── api/ │ │ │ ├── CapitalAccountService.java │ │ │ ├── CapitalTradeOrderService.java │ │ │ └── dto/ │ │ │ └── CapitalTradeOrderDto.java │ │ ├── tyloo-dubbo-order/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── tyloo/ │ │ │ │ └── sample/ │ │ │ │ └── dubbo/ │ │ │ │ └── order/ │ │ │ │ ├── service/ │ │ │ │ │ ├── AccountServiceImpl.java │ │ │ │ │ ├── PaymentServiceImpl.java │ │ │ │ │ └── PlaceOrderServiceImpl.java │ │ │ │ └── web/ │ │ │ │ └── controller/ │ │ │ │ ├── OrderController.java │ │ │ │ └── vo/ │ │ │ │ └── PlaceOrderRequest.java │ │ │ ├── resources/ │ │ │ │ ├── config/ │ │ │ │ │ └── spring/ │ │ │ │ │ └── local/ │ │ │ │ │ ├── appcontext-service-dubbo.xml │ │ │ │ │ ├── appcontext-service-tcc.xml │ │ │ │ │ └── appcontext-web-servlet.xml │ │ │ │ ├── log/ │ │ │ │ │ └── log4j.xml │ │ │ │ ├── sample-dubbo-order.properties │ │ │ │ └── tccjdbc.properties │ │ │ └── webapp/ │ │ │ └── WEB-INF/ │ │ │ ├── ftl/ │ │ │ │ ├── index.ftl │ │ │ │ ├── pay_success.ftl │ │ │ │ ├── product_detail.ftl │ │ │ │ └── shop.ftl │ │ │ └── web.xml │ │ ├── tyloo-dubbo-redpacket/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── tyloo/ │ │ │ │ └── sample/ │ │ │ │ └── dubbo/ │ │ │ │ └── redpacket/ │ │ │ │ └── service/ │ │ │ │ ├── RedPacketAccountServiceImpl.java │ │ │ │ └── RedPacketTradeOrderServiceImpl.java │ │ │ ├── resources/ │ │ │ │ ├── config/ │ │ │ │ │ └── spring/ │ │ │ │ │ └── local/ │ │ │ │ │ ├── appcontext-service-provider.xml │ │ │ │ │ ├── appcontext-service-tcc.xml │ │ │ │ │ └── appcontext-web-servlet.xml │ │ │ │ ├── log/ │ │ │ │ │ └── log4j.xml │ │ │ │ ├── sample-dubbo-redpacket.properties │ │ │ │ └── tccjdbc.properties │ │ │ └── webapp/ │ │ │ ├── WEB-INF/ │ │ │ │ └── web.xml │ │ │ └── index.jsp │ │ └── tyloo-dubbo-redpacket-api/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── io/ │ │ └── tyloo/ │ │ └── sample/ │ │ └── dubbo/ │ │ └── redpacket/ │ │ └── api/ │ │ ├── RedPacketAccountService.java │ │ ├── RedPacketTradeOrderService.java │ │ └── dto/ │ │ └── RedPacketTradeOrderDto.java │ ├── tyloo-http-sample/ │ │ ├── pom.xml │ │ ├── tyloo-http-capital/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── tyloo/ │ │ │ │ └── sample/ │ │ │ │ └── http/ │ │ │ │ └── capital/ │ │ │ │ └── service/ │ │ │ │ ├── CapitalAccountServiceImpl.java │ │ │ │ └── CapitalTradeOrderServiceImpl.java │ │ │ ├── resources/ │ │ │ │ ├── config/ │ │ │ │ │ └── spring/ │ │ │ │ │ └── local/ │ │ │ │ │ ├── appcontext-service-provider.xml │ │ │ │ │ ├── appcontext-service-tcc.xml │ │ │ │ │ └── appcontext-web-servlet.xml │ │ │ │ ├── log/ │ │ │ │ │ └── log4j.xml │ │ │ │ └── tccjdbc.properties │ │ │ └── webapp/ │ │ │ ├── WEB-INF/ │ │ │ │ └── web.xml │ │ │ └── index.jsp │ │ ├── tyloo-http-capital-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── io/ │ │ │ └── tyloo/ │ │ │ └── sample/ │ │ │ └── http/ │ │ │ └── capital/ │ │ │ └── api/ │ │ │ ├── CapitalAccountService.java │ │ │ ├── CapitalTradeOrderService.java │ │ │ └── dto/ │ │ │ └── CapitalTradeOrderDto.java │ │ ├── tyloo-http-order/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── tyloo/ │ │ │ │ └── sample/ │ │ │ │ └── http/ │ │ │ │ └── order/ │ │ │ │ ├── service/ │ │ │ │ │ ├── AccountServiceImpl.java │ │ │ │ │ ├── PaymentServiceImpl.java │ │ │ │ │ ├── PlaceOrderServiceImpl.java │ │ │ │ │ └── TradeOrderServiceProxy.java │ │ │ │ └── web/ │ │ │ │ └── controller/ │ │ │ │ ├── OrderController.java │ │ │ │ └── vo/ │ │ │ │ └── PlaceOrderRequest.java │ │ │ ├── resources/ │ │ │ │ ├── config/ │ │ │ │ │ └── spring/ │ │ │ │ │ └── local/ │ │ │ │ │ ├── appcontext-service-consumer.xml │ │ │ │ │ ├── appcontext-service-tcc.xml │ │ │ │ │ └── appcontext-web-servlet.xml │ │ │ │ ├── log/ │ │ │ │ │ └── log4j.xml │ │ │ │ ├── sample-dubbo-order.properties │ │ │ │ └── tccjdbc.properties │ │ │ └── webapp/ │ │ │ └── WEB-INF/ │ │ │ ├── ftl/ │ │ │ │ ├── index.ftl │ │ │ │ ├── pay_success.ftl │ │ │ │ ├── product_detail.ftl │ │ │ │ └── shop.ftl │ │ │ └── web.xml │ │ ├── tyloo-http-redpacket/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── tyloo/ │ │ │ │ └── sample/ │ │ │ │ └── http/ │ │ │ │ └── redpacket/ │ │ │ │ └── service/ │ │ │ │ ├── RedPacketAccountServiceImpl.java │ │ │ │ └── RedPacketTradeOrderServiceImpl.java │ │ │ ├── resources/ │ │ │ │ ├── config/ │ │ │ │ │ └── spring/ │ │ │ │ │ └── local/ │ │ │ │ │ ├── appcontext-service-provider.xml │ │ │ │ │ ├── appcontext-service-tcc.xml │ │ │ │ │ └── appcontext-web-servlet.xml │ │ │ │ ├── log/ │ │ │ │ │ └── log4j.xml │ │ │ │ └── tccjdbc.properties │ │ │ └── webapp/ │ │ │ ├── WEB-INF/ │ │ │ │ └── web.xml │ │ │ └── index.jsp │ │ └── tyloo-http-redpacket-api/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── io/ │ │ └── tyloo/ │ │ └── sample/ │ │ └── http/ │ │ └── redpacket/ │ │ └── api/ │ │ ├── RedPacketAccountService.java │ │ ├── RedPacketTradeOrderService.java │ │ └── dto/ │ │ └── RedPacketTradeOrderDto.java │ └── tyloo-sample-domain/ │ ├── pom.xml │ ├── tyloo-sample-captial/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── tyloo/ │ │ │ └── sample/ │ │ │ └── capital/ │ │ │ ├── domain/ │ │ │ │ ├── entity/ │ │ │ │ │ ├── CapitalAccount.java │ │ │ │ │ └── TradeOrder.java │ │ │ │ └── repository/ │ │ │ │ ├── CapitalAccountRepository.java │ │ │ │ └── TradeOrderRepository.java │ │ │ └── infrastructure/ │ │ │ └── dao/ │ │ │ ├── CapitalAccountDao.java │ │ │ └── TradeOrderDao.java │ │ └── resources/ │ │ ├── config/ │ │ │ ├── spring/ │ │ │ │ └── local/ │ │ │ │ ├── appcontext-service-dao.xml │ │ │ │ └── appcontext-service-datasource.xml │ │ │ └── sqlmap/ │ │ │ └── main/ │ │ │ ├── sample-capitalaccount.xml │ │ │ └── sample-tradeorder.xml │ │ ├── jdbc.properties │ │ └── log/ │ │ └── log4j.xml │ ├── tyloo-sample-common/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── io/ │ │ └── tyloo/ │ │ └── sample/ │ │ └── exception/ │ │ └── InsufficientBalanceException.java │ ├── tyloo-sample-order/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── tyloo/ │ │ │ └── sample/ │ │ │ └── order/ │ │ │ ├── domain/ │ │ │ │ ├── entity/ │ │ │ │ │ ├── Order.java │ │ │ │ │ ├── OrderLine.java │ │ │ │ │ ├── Product.java │ │ │ │ │ └── Shop.java │ │ │ │ ├── factory/ │ │ │ │ │ └── OrderFactory.java │ │ │ │ ├── repository/ │ │ │ │ │ ├── OrderRepository.java │ │ │ │ │ ├── ProductRepository.java │ │ │ │ │ └── ShopRepository.java │ │ │ │ └── service/ │ │ │ │ └── OrderServiceImpl.java │ │ │ └── infrastructure/ │ │ │ └── dao/ │ │ │ ├── OrderDao.java │ │ │ ├── OrderLineDao.java │ │ │ ├── ProductDao.java │ │ │ └── ShopDao.java │ │ └── resources/ │ │ ├── config/ │ │ │ ├── spring/ │ │ │ │ └── local/ │ │ │ │ ├── appcontext-service-dao.xml │ │ │ │ └── appcontext-service-datasource.xml │ │ │ └── sqlmap/ │ │ │ └── main/ │ │ │ ├── sample-order.xml │ │ │ ├── sample-orderline.xml │ │ │ ├── sample-product.xml │ │ │ └── sample-shop.xml │ │ ├── jdbc.properties │ │ ├── log/ │ │ │ └── log4j.xml │ │ └── sample-order.properties │ └── tyloo-sample-redpacket/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── io/ │ │ └── tyloo/ │ │ └── sample/ │ │ └── redpacket/ │ │ ├── domain/ │ │ │ ├── entity/ │ │ │ │ ├── RedPacketAccount.java │ │ │ │ └── TradeOrder.java │ │ │ └── repository/ │ │ │ ├── RedPacketAccountRepository.java │ │ │ └── TradeOrderRepository.java │ │ └── infrastructure/ │ │ └── dao/ │ │ ├── RedPacketAccountDao.java │ │ └── TradeOrderDao.java │ └── resources/ │ ├── config/ │ │ ├── spring/ │ │ │ └── local/ │ │ │ ├── appcontext-service-dao.xml │ │ │ └── appcontext-service-datasource.xml │ │ └── sqlmap/ │ │ └── main/ │ │ ├── sample-redpacketaccount.xml │ │ └── sample-tradeorder.xml │ ├── jdbc.properties │ └── log/ │ └── log4j.xml └── tyloo-unit-test/ ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── io/ │ │ └── tyloo/ │ │ └── unittest/ │ │ ├── client/ │ │ │ ├── AccountRecordServiceProxy.java │ │ │ ├── AccountServiceProxy.java │ │ │ └── TransferService.java │ │ ├── entity/ │ │ │ ├── AccountRecord.java │ │ │ ├── AccountStatus.java │ │ │ ├── SubAccount.java │ │ │ └── UserShardingId.java │ │ ├── repository/ │ │ │ ├── AccountRecordRepository.java │ │ │ └── SubAccountRepository.java │ │ ├── service/ │ │ │ ├── AccountService.java │ │ │ └── AccountServiceImpl.java │ │ ├── thirdservice/ │ │ │ ├── AccountRecordService.java │ │ │ └── AccountRecordServiceImpl.java │ │ └── utils/ │ │ └── UnitTest.java │ └── resources/ │ └── tyloo-unit-test.xml └── test/ ├── java/ │ └── io/ │ └── tyloo/ │ └── unit/ │ └── test/ │ ├── AbstractTestCase.java │ ├── PerformanceTest.java │ ├── ReflectionTest.java │ └── TransferServiceTest.java └── resources/ └── log4j.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # maven ignore target/ *.jar *.war *.zip *.tar *.tar.gz # eclipse ignore .settings/ .project .classpath # idea ignore .idea/ *.ipr *.iml *.iws # temp ignore *.log *.cache *.diff *.patch *.tmp # system ignore .DS_Store Thumbs.db ================================================ FILE: README.md ================================================ # tyloo Distributed transaction framework——TCC ## 概念 - Try: 尝试执行业务 - 完成所有业务检查(一致性) - 预留必须业务资源(准隔离性) - Confirm: 确认执行业务 - 真正执行业务 - 不作任何业务检查 - 只使用Try阶段预留的业务资源 - Confirm操作满足幂等性 - Cancel: 取消执行业务 - 释放Try阶段预留的业务资源 - Cancel操作满足幂等性 ## 环境 - Java - Maven - Git - MySQL - Redis - Zookeeper - Intellij IDEA ## 功能 - 基于 Spring AOP 切面思想实现对分布式事务注解的拦截。 - 基于Dubbo的ProxyFactory代理机制为服务接口生成代理对象。 - 基于Mysql、Redis乐观锁进行事务版本控制以及基于基于Quartz进行事务恢复。 - 支持多种事务日志序列化以及事务存储器实现。 - 调用方式(版本):Dubbo、HTTP ## 业务场景 - https://www.cnblogs.com/jajian/p/10014145.html - **我们有必要使用TCC分布式事务机制来保证各个服务形成一个整体性的事务** ## 运行 1. 导入数据库脚本`tyloo/tyloo-tutorial-sample/src/tylooSampledb` 2. 修改三个配置文件`tccjdbc.properties` 3. 修改三个子项目(capital、redpacket、order)的启动配置:HTTP port ## 模块 - **两个拦截器** - 通过对 @Tyloo AOP 切面( 参与者 try 方法 )进行拦截,透明化对参与者confirm / cancel 方法调用,从而实现 TCC - **事务与参与者** - TCC 通过多个参与者的 try / confirm / cancel 方法,实现事务的最终一致性 - Tyloo 将每个业务操作抽象成事务参与者 - **事务管理器** - 提供事务的获取、发起、提交、回滚,参与者的新增等等方法。 - **事务存储器** - 提供对事务对象的持久化 - **事务注解** - 传播级别 - 确认/取消执行业务方法 - 事务上下文编辑 - **事务恢复** - 事务信息被持久化到外部的存储器中。事务存储是事务恢复的基础。通过读取外部存储器中的异常事务,定时任务会按照一定频率对事务进行重试,直到事务完成或超过最大重试次数。 ## 流程 - 因为对远程业务的调用需要用到代理对象,代理对象由dubbo service生成,TRY阶段在进行远程调用前需要调用代理对象(代理对象的confrimmethod和cancelmethod均和try方法名字相同),此时拦截器会进行拦截代理对象,拦截后调用远程业务,远程业务(远程业务本地有个本地事务保证执行成功)也会被拦截,然后远程业务执行。COMMIT阶段时主业务执行完comfirm方法后代理对象执行comfirm方法(confirm方法均为反射调用),但是代理对象的confirm方法还是代理对象的try方法,此时再被拦截器拦截,因为此时代理对象的事务状态已经改为CONFIRMING,由于事务类型为defalut(因为代理对象的传播级别默认为SUPPORT),所以直接过了。此时再调用远程业务的try方法,走拦截,invoke反射的时候反射远程业务的confrim方法,因为try方法做了幂等所以直接过了,此时远程业务的commit阶段完成,然后继续下一个远程业务。最后根事务提交,完成。 ================================================ FILE: pom.xml ================================================ 4.0.0 io.tyloo tyloo pom 1.1.0 tyloo-api tyloo-core tyloo-spring tyloo-unit-test tyloo-tutorial-sample tyloo-server tyloo-dubbo tyloo-bom 1.8 1.8 GBK 4.3.18.RELEASE 1.9.2 1.7.9 2.7.4.1 org.springframework spring-framework-bom ${springframework.version} pom import com.fasterxml.jackson jackson-bom 2.10.1 pom import org.apache.dubbo dubbo ${dubbo.version} org.apache.dubbo dubbo-dependencies-zookeeper ${dubbo.version} pom com.mchange c3p0 0.9.5.4 org.quartz-scheduler quartz 2.3.2 com.esotericsoftware kryo 4.0.2 com.google.guava guava 19.0 org.apache.httpcomponents httpclient 4.5.10 com.alibaba fastjson 1.2.62 org.aspectj aspectjrt ${aspectj.version} org.aspectj aspectjweaver ${aspectj.version} org.apache.commons commons-lang3 3.9 org.slf4j slf4j-api ${slf4j.version} org.slf4j jcl-over-slf4j ${slf4j.version} org.slf4j slf4j-log4j12 ${slf4j.version} log4j log4j 1.2.17 mysql mysql-connector-java 5.1.48 junit junit 4.12 test org.mybatis mybatis 3.2.8 org.mybatis mybatis-spring 1.2.2 redis.clients jedis 2.9.0 org.freemarker freemarker 2.3.29 commons-codec commons-codec 1.11 io.tyloo tyloo-api ${project.version} io.tyloo tyloo-core ${project.version} src/main/resources true src/test/resources true org.apache.maven.plugins maven-resources-plugin 2.4 ${project.encoding} org.apache.maven.plugins maven-compiler-plugin 2.0.2 ${java.src.version} ${java.target.version} ${project.encoding} org.apache.maven.plugins maven-eclipse-plugin 2.6 ${basedir} http://svn.apache.org/repos/asf/maven/plugins/trunk/maven-eclipse-plugin/src/optional/eclipse-config/maven-styles.xml true false org.apache.maven.plugins maven-source-plugin 2.2.1 attach-sources jar org.apache.maven.plugins maven-deploy-plugin 2.7 true ================================================ FILE: tyloo-api/pom.xml ================================================ tyloo io.tyloo 1.1.0 4.0.0 tyloo-api cn.hutool hutool-all 5.2.5 ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/Propagation.java ================================================ package io.tyloo.api; /* * * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 11:49 2019/4/4 * */ public enum Propagation { /** * ֵ֧ǰǰû񣬾½һ */ REQUIRED(0), /** * ֵ֧ǰǰû񣬾Էʽִ */ SUPPORTS(1), /** * ֵ֧ǰǰû񣬾׳쳣 */ MANDATORY(2), /** * ½ǰ񣬰ѵǰ */ REQUIRES_NEW(3); private final int value; Propagation(int value) { this.value = value; } public int value() { return this.value; } } ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/TransactionContext.java ================================================ package io.tyloo.api; import java.io.Serializable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /* * * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 12:01 2019/4/6 * */ public class TransactionContext implements Serializable { private static final long serialVersionUID = -8199390103169700387L; private TransactionXid xid; private int status; private Map attachments = new ConcurrentHashMap(); public TransactionContext() { } public TransactionContext(TransactionXid xid, int status) { this.xid = xid; this.status = status; } public void setXid(TransactionXid xid) { this.xid = xid; } public TransactionXid getXid() throws CloneNotSupportedException { return xid.clone(); } public void setAttachments(Map attachments) { if (attachments != null && !attachments.isEmpty()) { this.attachments.putAll(attachments); } } public Map getAttachments() { return attachments; } public void setStatus(int status) { this.status = status; } public int getStatus() { return status; } } ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/TransactionContextEditor.java ================================================ package io.tyloo.api; import java.lang.reflect.Method; /* * * ı༭úͻ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 11:35 2019/4/7 * */ public interface TransactionContextEditor { /** * Ӳл * * @param target * @param method * @param args * @return */ TransactionContext get(Object target, Method method, Object[] args); /** * ĵ * * @param transactionContext * @param target * @param method * @param args */ void set(TransactionContext transactionContext, Object target, Method method, Object[] args); } ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/TransactionStatus.java ================================================ package io.tyloo.api; /* * * ״̬ö * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 12:05 2019/4/5 * */ public enum TransactionStatus { /** * try׶ */ TRYING(1), /** * confirm׶ */ CONFIRMING(2), /** * cancel׶ */ CANCELLING(3); private int id; TransactionStatus(int id) { this.id = id; } public int getId() { return id; } public static TransactionStatus valueOf(int id) { switch (id) { case 1: return TRYING; case 2: return CONFIRMING; default: return CANCELLING; } } } ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/TransactionXid.java ================================================ package io.tyloo.api; import javax.transaction.xa.Xid; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.Arrays; import cn.hutool.core.lang.UUID; /* * xidţΨһʶһʹ UUID 㷨ɣ֤Ψһԡ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 14:57 2019/4/10 * */ public class TransactionXid implements Xid, Serializable { private static final long serialVersionUID = -6817267250789142043L; /** * XID ĸʽʶ * һ֣ڱʶglobalTransactionIdbranchQualifierֵʹõĸʽĬֵ1 */ private int formatId = 1; /** * ȫID. * ͬķֲʽӦʹͬglobalTransactionIdȷ֪XAĸֲʽ */ private byte[] globalTransactionId; /** * ֧޶. * Ĭֵǿմһֲʽеÿ֧bqualֵΨһ */ private byte[] branchQualifier; private static byte[] CUSTOMIZED_TRANSACTION_ID = "UniqueIdentity".getBytes(); public TransactionXid() { globalTransactionId = uuidToByteArray(UUID.randomUUID()); branchQualifier = uuidToByteArray(UUID.randomUUID()); } public void setGlobalTransactionId(byte[] globalTransactionId) { this.globalTransactionId = globalTransactionId; } public void setBranchQualifier(byte[] branchQualifier) { this.branchQualifier = branchQualifier; } public TransactionXid(Object uniqueIdentity) { if (uniqueIdentity == null) { globalTransactionId = uuidToByteArray(UUID.randomUUID()); branchQualifier = uuidToByteArray(UUID.randomUUID()); } else { this.globalTransactionId = CUSTOMIZED_TRANSACTION_ID; this.branchQualifier = uniqueIdentity.toString().getBytes(); } } public TransactionXid(byte[] globalTransactionId) { this.globalTransactionId = globalTransactionId; this.branchQualifier = uuidToByteArray(UUID.randomUUID()); } public TransactionXid(byte[] globalTransactionId, byte[] branchQualifier) { this.globalTransactionId = globalTransactionId; this.branchQualifier = branchQualifier; } /** * ȡ XID ĸʽʶ֡ */ @Override public int getFormatId() { return formatId; } /** * ȡ XID ȫʶΪֽ顣 */ @Override public byte[] getGlobalTransactionId() { return globalTransactionId; } /** * ȡ XID ֧ʶΪֽ顣 */ @Override public byte[] getBranchQualifier() { return branchQualifier; } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); if (Arrays.equals(CUSTOMIZED_TRANSACTION_ID, globalTransactionId)) { stringBuilder.append(new String(globalTransactionId)); stringBuilder.append(":").append(new String(branchQualifier)); } else { stringBuilder.append(UUID.nameUUIDFromBytes(globalTransactionId).toString()); stringBuilder.append(":").append(UUID.nameUUIDFromBytes(branchQualifier).toString()); } return stringBuilder.toString(); } @Override public TransactionXid clone() throws CloneNotSupportedException { TransactionXid clone = (TransactionXid) super.clone(); byte[] cloneGlobalTransactionId = null; byte[] cloneBranchQualifier = null; if (globalTransactionId != null) { cloneGlobalTransactionId = new byte[globalTransactionId.length]; System.arraycopy(globalTransactionId, 0, cloneGlobalTransactionId, 0, globalTransactionId.length); } if (branchQualifier != null) { cloneBranchQualifier = new byte[branchQualifier.length]; System.arraycopy(branchQualifier, 0, cloneBranchQualifier, 0, branchQualifier.length); } return new TransactionXid(cloneGlobalTransactionId, cloneBranchQualifier); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.getFormatId(); result = prime * result + Arrays.hashCode(branchQualifier); result = prime * result + Arrays.hashCode(globalTransactionId); return result; } @Override public boolean equals(Object obj) { return this == obj; } private static byte[] uuidToByteArray(UUID uuid) { ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()); return bb.array(); } } ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/Tyloo.java ================================================ package io.tyloo.api; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; /* * * ע * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 11:32 2019/4/1 * * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Tyloo { /** * */ Propagation propagation() default Propagation.REQUIRED; /** * ȷִҵ񷽷 */ String confirmMethod() default ""; /** * ȡִҵ񷽷 */ String cancelMethod() default ""; /** * ı༭ */ public Class transactionContextEditor() default DefaultTransactionContextEditor.class; /** * ʱ쳣 * delayCancelExceptions()ʾϵͳõ쳣ʱrollbacɻָjobִָk * ͨҪʱ쳣ΪdelayCancelExceptionsԱΪʱ˳ʱ쳣rollback, Ǵûִ꣬Ӷrollbackʧ * * @return */ public Class[] delayCancelExceptions() default {}; public boolean asyncConfirm() default false; public boolean asyncCancel() default false; /** * Ĭı༭ʵ */ class DefaultTransactionContextEditor implements TransactionContextEditor { @Override public TransactionContext get(Object target, Method method, Object[] args) { int position = getTransactionContextParamPosition(method.getParameterTypes()); if (position >= 0) { return (TransactionContext) args[position]; } return null; } @Override public void set(TransactionContext transactionContext, Object target, Method method, Object[] args) { int position = getTransactionContextParamPosition(method.getParameterTypes()); if (position >= 0) { args[position] = transactionContext; } } /** * ڷλ * * @param parameterTypes ͼ * @return λ */ static int getTransactionContextParamPosition(Class[] parameterTypes) { int position = -1; for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i].equals(TransactionContext.class)) { position = i; break; } } return position; } /** * @param args б * @return ȡTransactionContext */ public static TransactionContext getTransactionContextFromArgs(Object[] args) { TransactionContext transactionContext = null; for (Object arg : args) { if (arg != null && TransactionContext.class.isAssignableFrom(arg.getClass())) { transactionContext = (TransactionContext) arg; } } return transactionContext; } } } ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/UniqueIdentity.java ================================================ package io.tyloo.api; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 16:40 2019/4/6 * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) public @interface UniqueIdentity { } ================================================ FILE: tyloo-api/src/main/java/io/tyloo/api/UuidUtils.java ================================================ package io.tyloo.api; import java.nio.ByteBuffer; import cn.hutool.core.lang.UUID; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:06 2019/4/4 * */ public class UuidUtils { public static byte[] uuidToByteArray(UUID uuid) { ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); return bb.array(); } public static UUID byteArrayToUUID(byte[] bytes) { ByteBuffer bb = ByteBuffer.wrap(bytes); long firstLong = bb.getLong(); long secondLong = bb.getLong(); return new UUID(firstLong, secondLong); } } ================================================ FILE: tyloo-core/pom.xml ================================================ tyloo io.tyloo 1.1.0 4.0.0 tyloo-core io.tyloo tyloo-api com.esotericsoftware kryo com.fasterxml.jackson.core jackson-databind log4j log4j com.google.guava guava org.aspectj aspectjweaver redis.clients jedis org.apache.commons commons-lang3 com.alibaba fastjson ================================================ FILE: tyloo-core/src/main/java/io/tyloo/CancellingException.java ================================================ package io.tyloo; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:55 2019/6/1 * */ public class CancellingException extends RuntimeException { public CancellingException(Throwable cause) { super(cause); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/ConcurrentTransactionException.java ================================================ package io.tyloo; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:55 2019/6/1 * */ public class ConcurrentTransactionException extends RuntimeException { private static final long serialVersionUID = 4099060614687283527L; public ConcurrentTransactionException() { } public ConcurrentTransactionException(String message) { super(message); } public ConcurrentTransactionException(String message, Throwable e) { super(message, e); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/ConfirmingException.java ================================================ package io.tyloo; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:55 2019/6/1 * */ public class ConfirmingException extends RuntimeException { public ConfirmingException(Throwable cause) { super(cause); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/InvocationContext.java ================================================ package io.tyloo; import java.io.Serializable; /* * * ִз * ¼ࡢ顢顣 * ͨЩԣִύ / ع * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:48 2019/6/3 * */ public class InvocationContext implements Serializable { private static final long serialVersionUID = -7969140711432461165L; /** * */ private Class targetClass; /** * */ private String methodName; /** * */ private Class[] parameterTypes; /** * */ private Object[] args; public InvocationContext() { } public InvocationContext(Class targetClass, String methodName, Class[] parameterTypes, Object... args) { this.methodName = methodName; this.parameterTypes = parameterTypes; this.targetClass = targetClass; this.args = args; } public Object[] getArgs() { return args; } public Class getTargetClass() { return targetClass; } public String getMethodName() { return methodName; } public Class[] getParameterTypes() { return parameterTypes; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/NoExistedTransactionException.java ================================================ package io.tyloo; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:55 2019/6/1 * */ public class NoExistedTransactionException extends Exception { private static final long serialVersionUID = 1031919168789207713L; } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/OptimisticLockException.java ================================================ package io.tyloo; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:55 2019/6/1 * */ public class OptimisticLockException extends RuntimeException { private static final long serialVersionUID = -4250747002159020605L; } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/Participant.java ================================================ package io.tyloo; import io.tyloo.api.TransactionContext; import io.tyloo.api.TransactionContextEditor; import io.tyloo.api.TransactionStatus; import io.tyloo.api.TransactionXid; import java.io.Serializable; /* * * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:00 2019/6/5 * */ public class Participant implements Serializable { private static final long serialVersionUID = 4127729421281425247L; private TransactionXid xid; private InvocationContext confirmInvocationContext; private InvocationContext cancelInvocationContext; Class transactionContextEditorClass; public Participant() { } public Participant(TransactionXid xid, InvocationContext confirmInvocationContext, InvocationContext cancelInvocationContext, Class transactionContextEditorClass) { this.xid = xid; this.confirmInvocationContext = confirmInvocationContext; this.cancelInvocationContext = cancelInvocationContext; this.transactionContextEditorClass = transactionContextEditorClass; } public Participant(InvocationContext confirmInvocationContext, InvocationContext cancelInvocationContext, Class transactionContextEditorClass) { this.confirmInvocationContext = confirmInvocationContext; this.cancelInvocationContext = cancelInvocationContext; this.transactionContextEditorClass = transactionContextEditorClass; } public void setXid(TransactionXid xid) { this.xid = xid; } public void rollback() { Terminator.invoke(new TransactionContext(xid, TransactionStatus.CANCELLING.getId()), cancelInvocationContext, transactionContextEditorClass); } public void commit() { Terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass); } public TransactionXid getXid() { return xid; } public InvocationContext getConfirmInvocationContext() { return confirmInvocationContext; } public InvocationContext getCancelInvocationContext() { return cancelInvocationContext; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/SystemException.java ================================================ package io.tyloo; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:55 2019/6/1 * */ public class SystemException extends RuntimeException { public SystemException(String message) { super(message); } public SystemException(Throwable e) { super(e); } public SystemException(String message, Throwable e) { super(message, e); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/Terminator.java ================================================ package io.tyloo; import io.tyloo.api.TransactionContext; import io.tyloo.api.TransactionContextEditor; import io.tyloo.support.FactoryBuilder; import io.tyloo.utils.StringUtils; import java.lang.reflect.Method; /* * * ִ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:01 2019/6/6 * */ public final class Terminator { public Terminator() { } /** * ݵģȡĿ귽ִз. * * @param invocationContext */ public static void invoke(TransactionContext transactionContext, InvocationContext invocationContext, Class transactionContextEditorClass) { if (StringUtils.isNotEmpty(invocationContext.getMethodName())) { try { Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance(); Method method = null; //ע method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes()); FactoryBuilder.factoryOf(transactionContextEditorClass).getInstance().set(transactionContext, target, method, invocationContext.getArgs()); // ÷񷽷ٴαTylooAspectTylooCoordinatorAspectأΪ״̬ѾTRYINGˣֱִԶ̷ method.invoke(target, invocationContext.getArgs()); } catch (Exception e) { throw new SystemException(e); } } } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/Transaction.java ================================================ package io.tyloo; import io.tyloo.api.TransactionContext; import io.tyloo.api.TransactionStatus; import io.tyloo.api.TransactionXid; import io.tyloo.common.TransactionType; import javax.transaction.xa.Xid; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 18:04 2019/4/30 * */ public class Transaction implements Serializable { private static final long serialVersionUID = 7291423944314337931L; private TransactionXid xid; private TransactionStatus status; private TransactionType transactionType; private volatile int retriedCount = 0; private Date createTime = new Date(); private Date lastUpdateTime = new Date(); private long version = 1; private List participants = new ArrayList(); private Map attachments = new ConcurrentHashMap(); public Transaction() { } public Transaction(TransactionContext transactionContext) throws CloneNotSupportedException { this.xid = transactionContext.getXid(); this.status = TransactionStatus.TRYING; this.transactionType = TransactionType.BRANCH; } public Transaction(TransactionType transactionType) { this.xid = new TransactionXid(); this.status = TransactionStatus.TRYING; this.transactionType = transactionType; } public Transaction(Object uniqueIdentity,TransactionType transactionType) { this.xid = new TransactionXid(uniqueIdentity); this.status = TransactionStatus.TRYING; this.transactionType = transactionType; } public void enlistParticipant(Participant participant) { participants.add(participant); } public Xid getXid() { try { return xid.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public TransactionStatus getStatus() { return status; } public List getParticipants() { return participants; } public TransactionType getTransactionType() { return transactionType; } public void changeStatus(TransactionStatus status) { this.status = status; } public void commit() { for (Participant participant : participants) { participant.commit(); } } public void rollback() { for (Participant participant : participants) { participant.rollback(); } } public int getRetriedCount() { return retriedCount; } public void addRetriedCount() { this.retriedCount++; } public void resetRetriedCount(int retriedCount) { this.retriedCount = retriedCount; } public Map getAttachments() { return attachments; } public long getVersion() { return version; } public void updateVersion() { this.version++; } public void setVersion(long version) { this.version = version; } public Date getLastUpdateTime() { return lastUpdateTime; } public void setLastUpdateTime(Date date) { this.lastUpdateTime = date; } public Date getCreateTime() { return createTime; } public void updateTime() { this.lastUpdateTime = new Date(); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/TransactionManager.java ================================================ package io.tyloo; import io.tyloo.api.TransactionContext; import io.tyloo.api.TransactionStatus; import io.tyloo.common.TransactionType; import org.apache.log4j.Logger; import java.util.Deque; import java.util.LinkedList; import java.util.concurrent.ExecutorService; /* * * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:25 2019/6/7 * */ public class TransactionManager { static final Logger logger = Logger.getLogger(TransactionManager.class.getSimpleName()); private TransactionRepository transactionRepository; /** * ǰ߳ */ private static final ThreadLocal> CURRENT = new ThreadLocal>(); private ExecutorService executorService; public void setTransactionRepository(TransactionRepository transactionRepository) { this.transactionRepository = transactionRepository; } public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } public TransactionManager() { } /** * * * begin ֮ʵҲʹһȫʵorder * Żص rootMethodProceed ִ * * @param uniqueIdentify * @return */ public Transaction begin(Object uniqueIdentify) throws CloneNotSupportedException { Transaction transaction = new Transaction(uniqueIdentify, TransactionType.ROOT); transactionRepository.create(transaction); registerTransaction(transaction); return transaction; } /** * ֧ * * @param transactionContext * @return ֧ */ public Transaction propagationNewBegin(TransactionContext transactionContext) throws CloneNotSupportedException { Transaction transaction = new Transaction(transactionContext); transactionRepository.create(transaction); registerTransaction(transaction); return transaction; } /** * ȡ֧ * * @param transactionContext * @return ֧ * @throws NoExistedTransactionException 񲻴ʱ */ public Transaction propagationExistBegin(TransactionContext transactionContext) throws NoExistedTransactionException { Transaction transaction = null; try { transaction = transactionRepository.findByXid(transactionContext.getXid()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } if (transaction != null) { transaction.changeStatus(TransactionStatus.valueOf(transactionContext.getStatus())); registerTransaction(transaction); return transaction; } else { throw new NoExistedTransactionException(); } } public void commit(boolean asyncCommit) { final Transaction transaction = getCurrentTransaction(); transaction.changeStatus(TransactionStatus.CONFIRMING); transactionRepository.update(transaction); if (asyncCommit) { try { Long statTime = System.currentTimeMillis(); executorService.submit(new Runnable() { @Override public void run() { commitTransaction(transaction); } }); logger.debug("async submit cost time:" + (System.currentTimeMillis() - statTime)); } catch (Throwable commitException) { logger.warn("tyloo transaction async submit confirm failed, recovery job will try to confirm later.", commitException); throw new ConfirmingException(commitException); } } else { commitTransaction(transaction); } } public void rollback(boolean asyncRollback) { final Transaction transaction = getCurrentTransaction(); transaction.changeStatus(TransactionStatus.CANCELLING); transactionRepository.update(transaction); if (asyncRollback) { try { executorService.submit(new Runnable() { @Override public void run() { rollbackTransaction(transaction); } }); } catch (Throwable rollbackException) { logger.warn("tyloo transaction async rollback failed, recovery job will try to rollback later.", rollbackException); throw new CancellingException(rollbackException); } } else { rollbackTransaction(transaction); } } private void commitTransaction(Transaction transaction) { try { transaction.commit(); transactionRepository.delete(transaction); } catch (Throwable commitException) { logger.warn("tyloo transaction confirm failed, recovery job will try to confirm later.", commitException); throw new ConfirmingException(commitException); } } private void rollbackTransaction(Transaction transaction) { try { transaction.rollback(); transactionRepository.delete(transaction); } catch (Throwable rollbackException) { logger.warn("tyloo transaction rollback failed, recovery job will try to rollback later.", rollbackException); throw new CancellingException(rollbackException); } } /** * ȡǰ߳һ(ͷ)Ԫ * * @return */ public Transaction getCurrentTransaction() { if (isTransactionActive()) { return CURRENT.get().peek(); } return null; } public boolean isTransactionActive() { Deque transactions = CURRENT.get(); return transactions != null && !transactions.isEmpty(); } /** * ע񵽵ǰ߳ * * @param transaction */ private void registerTransaction(Transaction transaction) { if (CURRENT.get() == null) { CURRENT.set(new LinkedList()); } CURRENT.get().push(transaction); } /** * ӵǰ߳Ƴ * * @param transaction */ public void cleanAfterCompletion(Transaction transaction) { if (isTransactionActive() && transaction != null) { Transaction currentTransaction = getCurrentTransaction(); if (currentTransaction == transaction) { CURRENT.get().pop(); if (CURRENT.get().size() == 0) { CURRENT.remove(); } } else { throw new SystemException("Illegal transaction when clean after completion"); } } } /** * Ӳߵ * * @param participant */ public void enlistParticipant(Participant participant) { Transaction transaction = this.getCurrentTransaction(); transaction.enlistParticipant(participant); transactionRepository.update(transaction); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/TransactionRepository.java ================================================ package io.tyloo; import io.tyloo.api.TransactionXid; import java.util.Date; import java.util.List; /* * * (tccݿdao) * ʵڱĿdubbo-orderеconfig.spring.localappcontext-service-dao.xmlã * ʵSpringJdbcTransactionRepository̳JdbcTransactionRepositoryΪordercapitalredpacket3ķ * ÿһģжappcontext-service-dao.xmlӳ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:00 2019/6/12 * */ public interface TransactionRepository { int create(Transaction transaction) throws CloneNotSupportedException; int update(Transaction transaction); int delete(Transaction transaction); Transaction findByXid(TransactionXid xid); List findAllUnmodifiedSince(Date date); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/common/MethodRole.java ================================================ package io.tyloo.common; /* * ɫ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:27 2019/4/15 * */ public enum MethodRole { /** * */ ROOT, /** * ṩ */ PROVIDER, /** * */ NORMAL } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/common/TransactionType.java ================================================ package io.tyloo.common; /* * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:28 2019/4/17 * */ public enum TransactionType { /** * :1. */ ROOT(1), /** * ֧:2. */ BRANCH(2); int id; TransactionType(int id) { this.id = id; } public static TransactionType valueOf(int id) { switch (id) { case 1: return ROOT; case 2: return BRANCH; default: return null; } } public int getId() { return id; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/context/MethodTransactionContextEditor.java ================================================ package io.tyloo.context; import io.tyloo.api.TransactionContext; import io.tyloo.api.TransactionContextEditor; import io.tyloo.utils.TylooMethodUtils; import java.lang.reflect.Method; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 16:42 2019/4/16 * */ @Deprecated public class MethodTransactionContextEditor implements TransactionContextEditor { @Override public TransactionContext get(Object target, Method method, Object[] args) { int position = TylooMethodUtils.getTransactionContextParamPosition(method.getParameterTypes()); if (position >= 0) { return (TransactionContext) args[position]; } return null; } @Override public void set(TransactionContext transactionContext, Object target, Method method, Object[] args) { int position = TylooMethodUtils.getTransactionContextParamPosition(method.getParameterTypes()); if (position >= 0) { args[position] = transactionContext; } } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/interceptor/TylooCoordinatorAspect.java ================================================ package io.tyloo.interceptor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; /* * ԴЭӦ * ͨ@Pointcut + @Around ע⣬ö @Tyloo עķ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 18:48 2019/4/22 * */ @Aspect public abstract class TylooCoordinatorAspect { private TylooCoordinatorInterceptor tylooCoordinatorInterceptor; @Pointcut("@annotation(io.tyloo.api.Tyloo)") public void transactionContextCall() { } @Around("transactionContextCall()") public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) throws Throwable { return tylooCoordinatorInterceptor.interceptTransactionContextMethod(pjp); } public void setTylooCoordinatorInterceptor(TylooCoordinatorInterceptor tylooCoordinatorInterceptor) { this.tylooCoordinatorInterceptor = tylooCoordinatorInterceptor; } public abstract int getOrder(); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/interceptor/TylooCoordinatorInterceptor.java ================================================ package io.tyloo.interceptor; import io.tyloo.InvocationContext; import io.tyloo.Participant; import io.tyloo.Transaction; import io.tyloo.TransactionManager; import io.tyloo.api.TransactionContext; import io.tyloo.api.TransactionStatus; import io.tyloo.api.TransactionXid; import io.tyloo.api.Tyloo; import io.tyloo.support.FactoryBuilder; import io.tyloo.utils.ReflectionUtils; import io.tyloo.utils.TylooMethodUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; /* * ԴЭ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:35 2019/4/23 * */ public class TylooCoordinatorInterceptor { private TransactionManager transactionManager; public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) throws Throwable { Transaction transaction = transactionManager.getCurrentTransaction(); if (transaction != null) { switch (transaction.getStatus()) { case TRYING: enlistParticipant(pjp); break; case CONFIRMING: break; case CANCELLING: break; default: throw new IllegalStateException("Unexpected value: " + transaction.getStatus()); } } return pjp.proceed(pjp.getArgs()); } /** * ߣ Try ׶α * * @param pjp * @throws IllegalAccessException * @throws InstantiationException */ private void enlistParticipant(ProceedingJoinPoint pjp) throws IllegalAccessException, InstantiationException, CloneNotSupportedException { Method method = TylooMethodUtils.getTylooMethod(pjp); if (method == null) { throw new RuntimeException(String.format("join point not found method, point is : %s", pjp.getSignature().getName())); } Tyloo tyloo = method.getAnnotation(Tyloo.class); String confirmMethodName = tyloo.confirmMethod(); String cancelMethodName = tyloo.cancelMethod(); Transaction transaction = transactionManager.getCurrentTransaction(); TransactionXid xid = new TransactionXid(transaction.getXid().getGlobalTransactionId()); if (FactoryBuilder.factoryOf(tyloo.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()) == null) { FactoryBuilder.factoryOf(tyloo.transactionContextEditor()).getInstance().set(new TransactionContext(xid, TransactionStatus.TRYING.getId()), pjp.getTarget(), ((MethodSignature) pjp.getSignature()).getMethod(), pjp.getArgs()); } Class targetClass = ReflectionUtils.getDeclaringType(pjp.getTarget().getClass(), method.getName(), method.getParameterTypes()); InvocationContext confirmInvocation = new InvocationContext(targetClass, confirmMethodName, method.getParameterTypes(), pjp.getArgs()); InvocationContext cancelInvocation = new InvocationContext(targetClass, cancelMethodName, method.getParameterTypes(), pjp.getArgs()); Participant participant = new Participant( xid, confirmInvocation, cancelInvocation, tyloo.transactionContextEditor()); transactionManager.enlistParticipant(participant); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/interceptor/TylooMethodContext.java ================================================ package io.tyloo.interceptor; import io.tyloo.api.Propagation; import io.tyloo.api.TransactionContext; import io.tyloo.api.Tyloo; import io.tyloo.api.UniqueIdentity; import io.tyloo.common.MethodRole; import io.tyloo.support.FactoryBuilder; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /* * עⷽ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:33 2019/4/24 * */ public class TylooMethodContext { /** * */ private ProceedingJoinPoint pjp = null; /** * עⷽ */ private Method method = null; /** * ע */ private Tyloo tyloo = null; /** * */ private Propagation propagation = null; /** * */ private TransactionContext transactionContext = null; TylooMethodContext(ProceedingJoinPoint pjp) { this.pjp = pjp; this.method = getTylooMethod(); assert method != null; this.tyloo = method.getAnnotation(Tyloo.class); this.propagation = tyloo.propagation(); this.transactionContext = FactoryBuilder.factoryOf(tyloo.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()); } public Tyloo getAnnotation() { return tyloo; } public Propagation getPropagation() { return propagation; } public TransactionContext getTransactionContext() { return transactionContext; } public Method getMethod() { return method; } /** * ȡΨһʶ * * @return */ public Object getUniqueIdentity() { Annotation[][] annotations = this.getMethod().getParameterAnnotations(); for (int i = 0; i < annotations.length; i++) { for (Annotation annotation : annotations[i]) { if (annotation.annotationType().equals(UniqueIdentity.class)) { Object[] params = pjp.getArgs(); return params[i]; } } } return null; } /** * ȡעⷽ * * @return */ private Method getTylooMethod() { Method method = ((MethodSignature) (pjp.getSignature())).getMethod(); if (method.getAnnotation(Tyloo.class) == null) { try { method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { return null; } } return method; } /** * ͨ÷񴫲ȡ * * @param isTransactionActive * @return */ public MethodRole getMethodRole(boolean isTransactionActive) { if ((propagation.equals(Propagation.REQUIRED) && !isTransactionActive && transactionContext == null) || propagation.equals(Propagation.REQUIRES_NEW)) { return MethodRole.ROOT; } else if ((propagation.equals(Propagation.REQUIRED) || propagation.equals(Propagation.MANDATORY)) && !isTransactionActive && transactionContext != null) { return MethodRole.PROVIDER; } else { return MethodRole.NORMAL; } } public Object proceed() throws Throwable { return this.pjp.proceed(); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/interceptor/TylooTransactionAspect.java ================================================ package io.tyloo.interceptor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; /* * ɲӦ * ͨ@Pointcut + @Around ע⣬ö @Tyloo עķ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:14 2019/4/26 * */ @Aspect public abstract class TylooTransactionAspect { private TylooTransactionInterceptor tylooTransactionInterceptor; public void setTylooTransactionInterceptor(TylooTransactionInterceptor tylooTransactionInterceptor) { this.tylooTransactionInterceptor = tylooTransactionInterceptor; } @Pointcut("@annotation(io.tyloo.api.Tyloo)") public void tylooService() { } @Around("tylooService()") public Object interceptTylooMethod(ProceedingJoinPoint pjp) throws Throwable { return tylooTransactionInterceptor.interceptTylooMethod(pjp); } public abstract int getOrder(); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/interceptor/TylooTransactionInterceptor.java ================================================ package io.tyloo.interceptor; import com.alibaba.fastjson.JSON; import io.tyloo.NoExistedTransactionException; import io.tyloo.SystemException; import io.tyloo.Transaction; import io.tyloo.TransactionManager; import io.tyloo.api.TransactionStatus; import io.tyloo.utils.ReflectionUtils; import io.tyloo.utils.TransactionUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.log4j.Logger; import org.aspectj.lang.ProceedingJoinPoint; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /* * ɲ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:18 2019/4/23 * */ public class TylooTransactionInterceptor { static final Logger logger = Logger.getLogger(TylooTransactionInterceptor.class.getSimpleName()); private final Set> delayCancelExceptions = new HashSet<>(); private TransactionManager transactionManager; /** * . * * @param transactionManager */ public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } public void setDelayCancelExceptions(Set> delayCancelExceptions) { this.delayCancelExceptions.addAll(delayCancelExceptions); } /** * ز. * * @param pjp * @throws Throwable */ public Object interceptTylooMethod(ProceedingJoinPoint pjp) throws Throwable { TylooMethodContext tylooMethodContext = new TylooMethodContext(pjp); boolean isTransactionActive = transactionManager.isTransactionActive(); if (!TransactionUtils.isLegalTransactionContext(isTransactionActive, tylooMethodContext)) { throw new SystemException("no active tyloo transaction while propagation is mandatory for method " + tylooMethodContext.getMethod().getName()); } switch (tylooMethodContext.getMethodRole(isTransactionActive)) { case ROOT: return rootMethodProceed(tylooMethodContext); case PROVIDER: return providerMethodProceed(tylooMethodContext); default: return pjp.proceed(); } } /** * 񷽷Ĵ. * * @param tylooMethodContext * @throws Throwable */ private Object rootMethodProceed(TylooMethodContext tylooMethodContext) throws Throwable { Object returnValue = null; Transaction transaction = null; boolean asyncConfirm = tylooMethodContext.getAnnotation().asyncConfirm(); boolean asyncCancel = tylooMethodContext.getAnnotation().asyncCancel(); Set> allDelayCancelExceptions = new HashSet<>(); allDelayCancelExceptions.addAll(this.delayCancelExceptions); allDelayCancelExceptions.addAll(Arrays.asList(tylooMethodContext.getAnnotation().delayCancelExceptions())); try { transaction = transactionManager.begin(tylooMethodContext.getUniqueIdentity()); try { returnValue = tylooMethodContext.proceed(); } catch (Throwable tryingException) { if (!isDelayCancelException(tryingException, allDelayCancelExceptions)) { logger.warn(String.format("tyloo transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException); transactionManager.rollback(asyncCancel); } throw tryingException; } transactionManager.commit(asyncConfirm); } finally { transactionManager.cleanAfterCompletion(transaction); } return returnValue; } /** * ṩ񷽷. * ״̬ CONFIRMING / CANCELLING öӦ * * @param tylooMethodContext * @throws Throwable */ private Object providerMethodProceed(TylooMethodContext tylooMethodContext) throws Throwable { Transaction transaction = null; boolean asyncConfirm = tylooMethodContext.getAnnotation().asyncConfirm(); boolean asyncCancel = tylooMethodContext.getAnnotation().asyncCancel(); try { switch (TransactionStatus.valueOf(tylooMethodContext.getTransactionContext().getStatus())) { case TRYING: transaction = transactionManager.propagationNewBegin(tylooMethodContext.getTransactionContext()); return tylooMethodContext.proceed(); case CONFIRMING: try { transaction = transactionManager.propagationExistBegin(tylooMethodContext.getTransactionContext()); transactionManager.commit(asyncConfirm); } catch (NoExistedTransactionException excepton) { //the transaction has been commit,ignore it. } break; case CANCELLING: try { transaction = transactionManager.propagationExistBegin(tylooMethodContext.getTransactionContext()); transactionManager.rollback(asyncCancel); } catch (NoExistedTransactionException exception) { //the transaction has been rollback,ignore it. } break; default: throw new IllegalStateException("Unexpected value: " + TransactionStatus.valueOf(tylooMethodContext.getTransactionContext().getStatus())); } } finally { transactionManager.cleanAfterCompletion(transaction); } Method method = tylooMethodContext.getMethod(); return ReflectionUtils.getNullValue(method.getReturnType()); } private boolean isDelayCancelException(Throwable throwable, Set> delayCancelExceptions) { if (delayCancelExceptions != null) { for (Class delayCancelException : delayCancelExceptions) { Throwable rootCause = ExceptionUtils.getRootCause(throwable); if (!throwable.getClass().isAssignableFrom(delayCancelException)) { if ((rootCause != null) && rootCause.getClass().isAssignableFrom(delayCancelException)) { return true; } } else { return true; } } } return false; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/recover/TylooRecoverConfiguration.java ================================================ package io.tyloo.recover; import java.util.Set; /* * ָýӿ. * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 18:50 2019/5/1 * */ public interface TylooRecoverConfiguration { /** * ȡԴ * * @return */ int getMaxRetryCount(); /** * ȡҪִָijʱ. * * @return */ int getRecoverDuration(); /** * ȡʱʽ. * * @return */ String getCronExpression(); /** * ӳȡ쳣 * * @return */ Set> getDelayCancelExceptions(); void setDelayCancelExceptions(Set> delayRecoverExceptions); int getAsyncTerminateThreadCorePoolSize(); int getAsyncTerminateThreadMaxPoolSize(); int getAsyncTerminateThreadWorkQueueSize(); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/recover/TylooTransactionRecovery.java ================================================ package io.tyloo.recover; import com.alibaba.fastjson.JSON; import io.tyloo.OptimisticLockException; import io.tyloo.Transaction; import io.tyloo.TransactionRepository; import io.tyloo.api.TransactionStatus; import io.tyloo.common.TransactionType; import io.tyloo.support.TransactionConfigurator; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.log4j.Logger; import java.util.Calendar; import java.util.Date; import java.util.List; /* * * ָ * * Ϣ־ûⲿĴ洢⣩С洢ָĻͨȡⲿ洢е쳣񣬶ʱᰴһƵʶԣֱɻ򳬹Դ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 18:51 2019/5/2 * */ public class TylooTransactionRecovery { static final Logger logger = Logger.getLogger(TylooTransactionRecovery.class.getSimpleName()); /** * TCC. */ private TransactionConfigurator transactionConfigurator; /** * ָ(RecoverScheduledJobʱ). */ public void startRecover() throws CloneNotSupportedException { List transactions = loadErrorTransactions(); recoverErrorTransactions(transactions); } /** * ҳִдϢ * * @return */ private List loadErrorTransactions() { long currentTimeInMillis = Calendar.getInstance().getTimeInMillis(); TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository(); TylooRecoverConfiguration tylooRecoverConfiguration = transactionConfigurator.getTylooRecoverConfiguration(); return transactionRepository.findAllUnmodifiedSince(new Date(currentTimeInMillis - tylooRecoverConfiguration.getRecoverDuration() * 1000)); } /** * ָ. * * @param transactions */ private void recoverErrorTransactions(List transactions) throws CloneNotSupportedException { for (Transaction transaction : transactions) { //ȽԴ if (transaction.getRetriedCount() > transactionConfigurator.getTylooRecoverConfiguration().getMaxRetryCount()) { logger.error(String.format("recover failed with max retry count,will not try again. txid:%s, status:%s,retried count:%d,transaction content:%s", transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction))); continue; } //ǰǷ֧ʱ if (transaction.getTransactionType().equals(TransactionType.BRANCH) && (transaction.getCreateTime().getTime() + transactionConfigurator.getTylooRecoverConfiguration().getMaxRetryCount() * transactionConfigurator.getTylooRecoverConfiguration().getRecoverDuration() * 1000 > System.currentTimeMillis())) { continue; } try { transaction.addRetriedCount(); // CONFIRMING(2)״̬ǰִ if (transaction.getStatus().equals(TransactionStatus.CONFIRMING)) { transaction.changeStatus(TransactionStatus.CONFIRMING); transactionConfigurator.getTransactionRepository().update(transaction); transaction.commit(); transactionConfigurator.getTransactionRepository().delete(transaction); } else if (transaction.getStatus().equals(TransactionStatus.CANCELLING) || transaction.getTransactionType().equals(TransactionType.ROOT)) { // ״̬ΪCANCELLING(3)Ȼִлع transaction.changeStatus(TransactionStatus.CANCELLING); transactionConfigurator.getTransactionRepository().update(transaction); transaction.rollback(); // £ʱû־ֱɾ transactionConfigurator.getTransactionRepository().delete(transaction); } } catch (Throwable throwable) { if (throwable instanceof OptimisticLockException || ExceptionUtils.getRootCause(throwable) instanceof OptimisticLockException) { logger.warn(String.format("optimisticLockException happened while recover. txid:%s, status:%s,retried count:%d,transaction content:%s", transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)), throwable); } else { logger.error(String.format("recover failed, txid:%s, status:%s,retried count:%d,transaction content:%s", transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)), throwable); } } } } public void setTransactionConfigurator(TransactionConfigurator transactionConfigurator) { this.transactionConfigurator = transactionConfigurator; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/CachableTransactionRepository.java ================================================ package io.tyloo.repository; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import io.tyloo.ConcurrentTransactionException; import io.tyloo.OptimisticLockException; import io.tyloo.Transaction; import io.tyloo.TransactionRepository; import io.tyloo.api.TransactionXid; import javax.transaction.xa.Xid; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; /* * * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:08 2019/5/12 * */ public abstract class CachableTransactionRepository implements TransactionRepository { /** * ʱ(Ϊλ) */ private int expireDuration = 120; /** * ־¼ */ private final Cache transactionXidTylooTransactionCache; public CachableTransactionRepository() { transactionXidTylooTransactionCache = CacheBuilder.newBuilder().expireAfterAccess(expireDuration, TimeUnit.SECONDS).maximumSize(1000).build(); } /** * ־¼ */ @Override public int create(Transaction transaction) throws CloneNotSupportedException { int result = doCreate(transaction); if (result > 0) { putToCache(transaction); } else { throw new ConcurrentTransactionException("transaction xid duplicated. xid:" + transaction.getXid().toString()); } return result; } /** * ־¼ */ @Override public int update(Transaction transaction) { int result = 0; try { result = doUpdate(transaction); if (result > 0) { putToCache(transaction); } else { throw new OptimisticLockException(); } } catch (CloneNotSupportedException e) { e.printStackTrace(); } finally { if (result <= 0) { removeFromCache(transaction); } } return result; } /** * ɾ־¼ */ @Override public int delete(Transaction transaction) { int result = 0; try { result = doDelete(transaction); } catch (CloneNotSupportedException e) { e.printStackTrace(); } finally { removeFromCache(transaction); } return result; } /** * xid־¼. * * @param transactionXid * @return */ @Override public Transaction findByXid(TransactionXid transactionXid) { Transaction transaction = findFromCache(transactionXid); if (transaction == null) { transaction = doFindOne(transactionXid); if (transaction != null) { putToCache(transaction); } } return transaction; } /** * ҳδ־ijһʱ㿪ʼ. * * @return */ @Override public List findAllUnmodifiedSince(Date date) { List transactions = doFindAllUnmodifiedSince(date); for (Transaction transaction : transactions) { putToCache(transaction); } return transactions; } /** * 뻺. * * @param transaction */ protected void putToCache(Transaction transaction) { transactionXidTylooTransactionCache.put(transaction.getXid(), transaction); } /** * ӻɾ. * * @param transaction */ protected void removeFromCache(Transaction transaction) { transactionXidTylooTransactionCache.invalidate(transaction.getXid()); } /** * ӻв. * * @param transactionXid * @return */ protected Transaction findFromCache(TransactionXid transactionXid) { return transactionXidTylooTransactionCache.getIfPresent(transactionXid); } public void setExpireDuration(int durationInSeconds) { this.expireDuration = durationInSeconds; } /** * ־¼ * * @param transaction * @return */ protected abstract int doCreate(Transaction transaction) throws CloneNotSupportedException; protected abstract int doUpdate(Transaction transaction) throws CloneNotSupportedException; protected abstract int doDelete(Transaction transaction) throws CloneNotSupportedException; protected abstract Transaction doFindOne(Xid xid); protected abstract List doFindAllUnmodifiedSince(Date date); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/FileSystemTransactionRepository.java ================================================ package io.tyloo.repository; import io.tyloo.Transaction; import io.tyloo.repository.helper.TransactionSerializer; import io.tyloo.serializer.KryoPoolSerializer; import io.tyloo.serializer.ObjectSerializer; import javax.transaction.xa.Xid; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Date; import java.util.List; /* * ļϵͳ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:22 2019/5/14 * */ public class FileSystemTransactionRepository extends CachableTransactionRepository { private String rootPath = "/tcc"; private volatile boolean initialized; private ObjectSerializer serializer = new KryoPoolSerializer(); public void setSerializer(ObjectSerializer serializer) { this.serializer = serializer; } public void setRootPath(String rootPath) { this.rootPath = rootPath; } @Override protected int doCreate(Transaction transaction) throws CloneNotSupportedException { return createFile(transaction); } @Override protected int doUpdate(Transaction transaction) throws CloneNotSupportedException { transaction.updateVersion(); transaction.updateTime(); writeFile(transaction); return 1; } @Override protected int doDelete(Transaction transaction) throws CloneNotSupportedException { String fullFileName = getFullFileName(transaction.getXid()); File file = new File(fullFileName); if (file.exists()) { return file.delete() ? 1 : 0; } return 1; } @Override protected Transaction doFindOne(Xid xid) { String fullFileName = getFullFileName(xid); File file = new File(fullFileName); if (file.exists()) { return readTransaction(file); } return null; } @Override protected List doFindAllUnmodifiedSince(Date date) { List allTransactions = doFindAll(); List allUnmodifiedSince = new ArrayList(); for (Transaction transaction : allTransactions) { if (transaction.getLastUpdateTime().compareTo(date) < 0) { allUnmodifiedSince.add(transaction); } } return allUnmodifiedSince; } protected List doFindAll() { List transactions = new ArrayList(); File path = new File(rootPath); File[] files = path.listFiles(); for (File file : files) { Transaction transaction = readTransaction(file); transactions.add(transaction); } return transactions; } private String getFullFileName(Xid xid) { return String.format("%s/%s", rootPath, xid); } private void makeDirIfNecessary() { if (!initialized) { synchronized (FileSystemTransactionRepository.class) { if (!initialized) { File rootPathFile = new File(rootPath); if (!rootPathFile.exists()) { boolean result = rootPathFile.mkdir(); if (!result) { throw new TransactionIOException("cannot create root path, the path to create is:" + rootPath); } initialized = true; } else if (!rootPathFile.isDirectory()) { throw new TransactionIOException("rootPath is not directory"); } } } } } private int createFile(Transaction transaction) throws CloneNotSupportedException { makeDirIfNecessary(); String filePath = getFullFileName(transaction.getXid()); FileChannel channel = null; RandomAccessFile raf = null; File file = null; byte[] content = TransactionSerializer.serialize(serializer, transaction); try { file = new File(filePath); boolean result = file.createNewFile(); if (!result) { return 0; } raf = new RandomAccessFile(file, "rw"); channel = raf.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(content.length); buffer.put(content); buffer.flip(); while (buffer.hasRemaining()) { channel.write(buffer); } channel.force(true); return 1; } catch (FileNotFoundException e) { throw new TransactionIOException(e); } catch (IOException e) { throw new TransactionIOException(e); } finally { if (channel != null && channel.isOpen()) { try { channel.close(); } catch (IOException e) { throw new TransactionIOException(e); } } } } private void writeFile(Transaction transaction) throws CloneNotSupportedException { makeDirIfNecessary(); String filePath = getFullFileName(transaction.getXid()); FileChannel channel = null; RandomAccessFile raf = null; byte[] content = TransactionSerializer.serialize(serializer, transaction); try { raf = new RandomAccessFile(filePath, "rw"); channel = raf.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(content.length); buffer.put(content); buffer.flip(); while (buffer.hasRemaining()) { channel.write(buffer); } channel.force(true); } catch (Exception e) { throw new TransactionIOException(e); } finally { if (channel != null && channel.isOpen()) { try { channel.close(); } catch (IOException e) { throw new TransactionIOException(e); } } } } private Transaction readTransaction(File file) { FileInputStream fis = null; try { fis = new FileInputStream(file); byte[] content = new byte[(int) file.length()]; fis.read(content); if (content != null) { return TransactionSerializer.deserialize(serializer, content); } } catch (Exception e) { throw new TransactionIOException(e); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { throw new TransactionIOException(e); } } } return null; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/JdbcTransactionRepository.java ================================================ package io.tyloo.repository; import io.tyloo.Transaction; import io.tyloo.api.TransactionStatus; import io.tyloo.serializer.KryoPoolSerializer; import io.tyloo.serializer.ObjectSerializer; import io.tyloo.utils.CollectionUtils; import io.tyloo.utils.StringUtils; import javax.sql.DataSource; import javax.transaction.xa.Xid; import java.sql.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /* * * JDBC⣨Ӧ÷ʵעԴ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:26 2019/5/13 * */ public class JdbcTransactionRepository extends CachableTransactionRepository { private String domain; private String tbSuffix; private DataSource dataSource; private ObjectSerializer serializer = new KryoPoolSerializer(); public String getDomain() { return domain; } public void setDomain(String domain) { this.domain = domain; } public String getTbSuffix() { return tbSuffix; } public void setTbSuffix(String tbSuffix) { this.tbSuffix = tbSuffix; } public void setSerializer(ObjectSerializer serializer) { this.serializer = serializer; } public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override protected int doCreate(Transaction transaction) { Connection connection = null; PreparedStatement stmt = null; try { connection = this.getConnection(); StringBuilder builder = new StringBuilder(); builder.append("INSERT INTO " + getTableName() + "(GLOBAL_TX_ID,BRANCH_QUALIFIER,TRANSACTION_TYPE,CONTENT,STATUS,RETRIED_COUNT,CREATE_TIME,LAST_UPDATE_TIME,VERSION"); builder.append(StringUtils.isNotEmpty(domain) ? ",DOMAIN ) VALUES (?,?,?,?,?,?,?,?,?,?)" : ") VALUES (?,?,?,?,?,?,?,?,?)"); stmt = connection.prepareStatement(builder.toString()); stmt.setBytes(1, transaction.getXid().getGlobalTransactionId()); stmt.setBytes(2, transaction.getXid().getBranchQualifier()); stmt.setInt(3, transaction.getTransactionType().getId()); stmt.setBytes(4, serializer.serialize(transaction)); stmt.setInt(5, transaction.getStatus().getId()); stmt.setInt(6, transaction.getRetriedCount()); stmt.setTimestamp(7, new java.sql.Timestamp(transaction.getCreateTime().getTime())); stmt.setTimestamp(8, new java.sql.Timestamp(transaction.getLastUpdateTime().getTime())); stmt.setLong(9, transaction.getVersion()); if (StringUtils.isNotEmpty(domain)) { stmt.setString(10, domain); } stmt.executeUpdate(); return 1; } catch (SQLException e) { if (e instanceof SQLIntegrityConstraintViolationException) { return 0; } else { throw new TransactionIOException(e); } } catch (Throwable throwable) { throw new TransactionIOException(throwable); } finally { closeStatement(stmt); this.releaseConnection(connection); } } @Override protected int doUpdate(Transaction transaction) { Connection connection = null; PreparedStatement stmt = null; java.util.Date lastUpdateTime = transaction.getLastUpdateTime(); long currentVersion = transaction.getVersion(); transaction.updateTime(); transaction.updateVersion(); try { connection = this.getConnection(); StringBuilder builder = new StringBuilder(); builder.append("UPDATE " + getTableName() + " SET " + "CONTENT = ?,STATUS = ?,LAST_UPDATE_TIME = ?, RETRIED_COUNT = ?,VERSION = VERSION+1 WHERE GLOBAL_TX_ID = ? AND BRANCH_QUALIFIER = ? AND VERSION = ?"); builder.append(StringUtils.isNotEmpty(domain) ? " AND DOMAIN = ?" : ""); stmt = connection.prepareStatement(builder.toString()); stmt.setBytes(1, serializer.serialize(transaction)); stmt.setInt(2, transaction.getStatus().getId()); stmt.setTimestamp(3, new Timestamp(transaction.getLastUpdateTime().getTime())); stmt.setInt(4, transaction.getRetriedCount()); stmt.setBytes(5, transaction.getXid().getGlobalTransactionId()); stmt.setBytes(6, transaction.getXid().getBranchQualifier()); stmt.setLong(7, currentVersion); if (StringUtils.isNotEmpty(domain)) { stmt.setString(8, domain); } int result = stmt.executeUpdate(); return result; } catch (Throwable e) { transaction.setLastUpdateTime(lastUpdateTime); transaction.setVersion(currentVersion); throw new TransactionIOException(e); } finally { closeStatement(stmt); this.releaseConnection(connection); } } @Override protected int doDelete(Transaction transaction) { Connection connection = null; PreparedStatement stmt = null; try { connection = this.getConnection(); String builder = "DELETE FROM " + getTableName() + " WHERE GLOBAL_TX_ID = ? AND BRANCH_QUALIFIER = ?" + (StringUtils.isNotEmpty(domain) ? " AND DOMAIN = ?" : ""); stmt = connection.prepareStatement(builder); stmt.setBytes(1, transaction.getXid().getGlobalTransactionId()); stmt.setBytes(2, transaction.getXid().getBranchQualifier()); if (StringUtils.isNotEmpty(domain)) { stmt.setString(3, domain); } return stmt.executeUpdate(); } catch (SQLException e) { throw new TransactionIOException(e); } finally { closeStatement(stmt); this.releaseConnection(connection); } } @Override protected Transaction doFindOne(Xid xid) { List transactions = doFind(Arrays.asList(xid)); if (!CollectionUtils.isEmpty(transactions)) { return transactions.get(0); } return null; } @Override protected List doFindAllUnmodifiedSince(java.util.Date date) { List transactions = new ArrayList(); Connection connection = null; PreparedStatement stmt = null; try { connection = this.getConnection(); StringBuilder builder = new StringBuilder(); builder.append("SELECT GLOBAL_TX_ID, BRANCH_QUALIFIER, CONTENT,STATUS,TRANSACTION_TYPE,CREATE_TIME,LAST_UPDATE_TIME,RETRIED_COUNT,VERSION"); builder.append(StringUtils.isNotEmpty(domain) ? ",DOMAIN" : ""); builder.append(" FROM " + getTableName() + " WHERE LAST_UPDATE_TIME < ?"); builder.append(" AND IS_DELETE = 0 "); builder.append(StringUtils.isNotEmpty(domain) ? " AND DOMAIN = ?" : ""); stmt = connection.prepareStatement(builder.toString()); stmt.setTimestamp(1, new Timestamp(date.getTime())); if (StringUtils.isNotEmpty(domain)) { stmt.setString(2, domain); } ResultSet resultSet = stmt.executeQuery(); this.constructTransactions(resultSet, transactions); } catch (Throwable e) { throw new TransactionIOException(e); } finally { closeStatement(stmt); this.releaseConnection(connection); } return transactions; } protected List doFind(List xids) { List transactions = new ArrayList(); if (CollectionUtils.isEmpty(xids)) { return transactions; } Connection connection = null; PreparedStatement stmt = null; try { connection = this.getConnection(); StringBuilder builder = new StringBuilder(); builder.append("SELECT GLOBAL_TX_ID, BRANCH_QUALIFIER, CONTENT,STATUS,TRANSACTION_TYPE,CREATE_TIME,LAST_UPDATE_TIME,RETRIED_COUNT,VERSION"); builder.append(StringUtils.isNotEmpty(domain) ? ",DOMAIN" : ""); builder.append(" FROM " + getTableName() + " WHERE"); if (!CollectionUtils.isEmpty(xids)) { for (Xid xid : xids) { builder.append(" ( GLOBAL_TX_ID = ? AND BRANCH_QUALIFIER = ? ) OR"); } builder.delete(builder.length() - 2, builder.length()); } builder.append(StringUtils.isNotEmpty(domain) ? " AND DOMAIN = ?" : ""); stmt = connection.prepareStatement(builder.toString()); int i = 0; for (Xid xid : xids) { stmt.setBytes(++i, xid.getGlobalTransactionId()); stmt.setBytes(++i, xid.getBranchQualifier()); } if (StringUtils.isNotEmpty(domain)) { stmt.setString(++i, domain); } ResultSet resultSet = stmt.executeQuery(); this.constructTransactions(resultSet, transactions); } catch (Throwable e) { throw new TransactionIOException(e); } finally { closeStatement(stmt); this.releaseConnection(connection); } return transactions; } protected void constructTransactions(ResultSet resultSet, List transactions) throws SQLException { while (resultSet.next()) { byte[] transactionBytes = resultSet.getBytes(3); Transaction transaction = serializer.deserialize(transactionBytes); transaction.changeStatus(TransactionStatus.valueOf(resultSet.getInt(4))); transaction.setLastUpdateTime(resultSet.getDate(7)); transaction.setVersion(resultSet.getLong(9)); transaction.resetRetriedCount(resultSet.getInt(8)); transactions.add(transaction); } } protected Connection getConnection() { try { return this.dataSource.getConnection(); } catch (SQLException e) { throw new TransactionIOException(e); } } protected void releaseConnection(Connection con) { try { if (con != null && !con.isClosed()) { con.close(); } } catch (SQLException e) { throw new TransactionIOException(e); } } private void closeStatement(Statement stmt) { try { if (stmt != null && !stmt.isClosed()) { stmt.close(); } } catch (Exception ex) { throw new TransactionIOException(ex); } } private String getTableName() { return StringUtils.isNotEmpty(tbSuffix) ? "TCC_TRANSACTION" + tbSuffix : "TCC_TRANSACTION"; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/RedisTransactionRepository.java ================================================ package io.tyloo.repository; import io.tyloo.Transaction; import io.tyloo.repository.helper.ExpandTransactionSerializer; import io.tyloo.repository.helper.JedisCallback; import io.tyloo.repository.helper.RedisHelper; import io.tyloo.serializer.KryoPoolSerializer; import io.tyloo.serializer.ObjectSerializer; import org.apache.log4j.Logger; import redis.clients.jedis.*; import javax.transaction.xa.Xid; import java.util.*; /* * * Redis * JedisRedisٷƼJavaӿ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:27 2019/5/17 * */ public class RedisTransactionRepository extends CachableTransactionRepository { private static final Logger logger = Logger.getLogger(RedisTransactionRepository.class.getSimpleName()); private JedisPool jedisPool; private String keyPrefix = "TCC:"; private int fetchKeySize = 1000; private boolean isSupportScan = true; private boolean isForbiddenKeys = false; private ObjectSerializer serializer = new KryoPoolSerializer(); public void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; } public void setSerializer(ObjectSerializer serializer) { this.serializer = serializer; } public int getFetchKeySize() { return fetchKeySize; } public void setFetchKeySize(int fetchKeySize) { this.fetchKeySize = fetchKeySize; } public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; isSupportScan = RedisHelper.isSupportScanCommand(jedisPool.getResource()); if (!isSupportScan && isForbiddenKeys) { throw new RuntimeException("Redis not support 'scan' command, " + "and 'keys' command is forbidden, " + "try update redis version higher than 2.8.0 " + "or set 'isForbiddenKeys' to false"); } } public void setSupportScan(boolean isSupportScan) { this.isSupportScan = isSupportScan; } public void setForbiddenKeys(boolean forbiddenKeys) { isForbiddenKeys = forbiddenKeys; } @Override protected int doCreate(final Transaction transaction) { try { Long statusCode = RedisHelper.execute(jedisPool, jedis -> { List params = new ArrayList<>(); for (Map.Entry entry : ExpandTransactionSerializer.serialize(serializer, transaction).entrySet()) { params.add(entry.getKey()); params.add(entry.getValue()); } Object result = jedis.eval("if redis.call('exists', KEYS[1]) == 0 then redis.call('hmset', KEYS[1], unpack(ARGV)); return 1; end; return 0;".getBytes(), Arrays.asList(RedisHelper.getRedisKey(keyPrefix, transaction.getXid())), params); return (Long) result; }); return statusCode.intValue(); } catch (Exception e) { throw new TransactionIOException(e); } } @Override protected int doUpdate(final Transaction transaction) { try { Long statusCode = RedisHelper.execute(jedisPool, jedis -> { transaction.updateTime(); transaction.updateVersion(); List params = new ArrayList<>(); for (Map.Entry entry : ExpandTransactionSerializer.serialize(serializer, transaction).entrySet()) { params.add(entry.getKey()); params.add(entry.getValue()); } Object result = jedis.eval(String.format("if redis.call('hget',KEYS[1],'VERSION') == '%s' then redis.call('hmset', KEYS[1], unpack(ARGV)); return 1; end; return 0;", transaction.getVersion() - 1).getBytes(), Arrays.asList(RedisHelper.getRedisKey(keyPrefix, transaction.getXid())), params); return (Long) result; }); return statusCode.intValue(); } catch (Exception e) { throw new TransactionIOException(e); } } @Override protected int doDelete(final Transaction transaction) { try { Long result = RedisHelper.execute(jedisPool, jedis -> jedis.del(RedisHelper.getRedisKey(keyPrefix, transaction.getXid()))); return result.intValue(); } catch (Exception e) { throw new TransactionIOException(e); } } @Override protected Transaction doFindOne(final Xid xid) { try { Long startTime = System.currentTimeMillis(); Map content = RedisHelper.execute(jedisPool, jedis -> jedis.hgetAll(RedisHelper.getRedisKey(keyPrefix, xid))); logger.info("redis find cost time :" + (System.currentTimeMillis() - startTime)); if (content != null && content.size() > 0) { return ExpandTransactionSerializer.deserialize(serializer, content); } return null; } catch (Exception e) { throw new TransactionIOException(e); } } @Override protected List doFindAllUnmodifiedSince(Date date) { List allTransactions = doFindAll(); List allUnmodifiedSince = new ArrayList<>(); for (Transaction transaction : allTransactions) { if (transaction.getLastUpdateTime().compareTo(date) < 0) { allUnmodifiedSince.add(transaction); } } return allUnmodifiedSince; } // @Override protected List doFindAll() { try { final Set keys = RedisHelper.execute(jedisPool, jedis -> { if (isSupportScan) { List allKeys = new ArrayList<>(); String cursor = RedisHelper.SCAN_INIT_CURSOR; ScanParams scanParams = RedisHelper.buildDefaultScanParams(keyPrefix + "*", fetchKeySize); do { ScanResult scanResult = jedis.scan(cursor, scanParams); allKeys.addAll(scanResult.getResult()); cursor = scanResult.getStringCursor(); } while (!cursor.equals(RedisHelper.SCAN_INIT_CURSOR)); Set allKeySet = new HashSet<>(); for (String key : allKeys) { allKeySet.add(key.getBytes()); } logger.info(String.format("find all key by scan command with pattern:%s allKeySet.size()=%d", keyPrefix + "*", allKeySet.size())); return allKeySet; } else { return jedis.keys((keyPrefix + "*").getBytes()); } }); return RedisHelper.execute(jedisPool, jedis -> { Pipeline pipeline = jedis.pipelined(); for (final byte[] key : keys) { pipeline.hgetAll(key); } List result = pipeline.syncAndReturnAll(); List list = new ArrayList<>(); for (Object data : result) { if (data != null && ((Map) data).size() > 0) { list.add(ExpandTransactionSerializer.deserialize(serializer, (Map) data)); } } return list; }); } catch (Exception e) { throw new TransactionIOException(e); } } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/TransactionIOException.java ================================================ package io.tyloo.repository; /* * * IO쳣 * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:27 2019/5/16 * */ public class TransactionIOException extends RuntimeException { private static final long serialVersionUID = 6508064607297986329L; public TransactionIOException(String message) { super(message); } public TransactionIOException(Throwable e) { super(e); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/helper/ExpandTransactionSerializer.java ================================================ package io.tyloo.repository.helper; import com.alibaba.fastjson.JSON; import io.tyloo.SystemException; import io.tyloo.Transaction; import io.tyloo.api.TransactionStatus; import io.tyloo.serializer.ObjectSerializer; import io.tyloo.utils.ByteUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; import java.text.ParseException; import java.util.HashMap; import java.util.Map; /* * * л * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:07 2019/5/4 * */ public class ExpandTransactionSerializer { public static Map serialize(ObjectSerializer serializer, Transaction transaction) throws CloneNotSupportedException { Map map = new HashMap<>(); map.put("GLOBAL_TX_ID".getBytes(), transaction.getXid().getGlobalTransactionId()); map.put("BRANCH_QUALIFIER".getBytes(), transaction.getXid().getBranchQualifier()); map.put("STATUS".getBytes(), ByteUtils.intToBytes(transaction.getStatus().getId())); map.put("TRANSACTION_TYPE".getBytes(), ByteUtils.intToBytes(transaction.getTransactionType().getId())); map.put("RETRIED_COUNT".getBytes(), ByteUtils.intToBytes(transaction.getRetriedCount())); map.put("CREATE_TIME".getBytes(), DateFormatUtils.format(transaction.getCreateTime(), "yyyy-MM-dd HH:mm:ss").getBytes()); map.put("LAST_UPDATE_TIME".getBytes(), DateFormatUtils.format(transaction.getLastUpdateTime(), "yyyy-MM-dd HH:mm:ss").getBytes()); map.put("VERSION".getBytes(), ByteUtils.longToBytes(transaction.getVersion())); map.put("CONTENT".getBytes(), serializer.serialize(transaction)); map.put("CONTENT_VIEW".getBytes(), JSON.toJSONString(transaction).getBytes()); return map; } public static Transaction deserialize(ObjectSerializer serializer, Map map1) { Map propertyMap = new HashMap<>(); for (Map.Entry entry : map1.entrySet()) { propertyMap.put(new String(entry.getKey()), entry.getValue()); } byte[] content = propertyMap.get("CONTENT"); Transaction transaction = (Transaction) serializer.deserialize(content); transaction.changeStatus(TransactionStatus.valueOf(ByteUtils.bytesToInt(propertyMap.get("STATUS")))); transaction.resetRetriedCount(ByteUtils.bytesToInt(propertyMap.get("RETRIED_COUNT"))); try { transaction.setLastUpdateTime(DateUtils.parseDate(new String(propertyMap.get("LAST_UPDATE_TIME")), "yyyy-MM-dd HH:mm:ss")); } catch (ParseException e) { throw new SystemException(e); } transaction.setVersion(ByteUtils.bytesToLong(propertyMap.get("VERSION"))); return transaction; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/helper/JedisCallback.java ================================================ package io.tyloo.repository.helper; import redis.clients.jedis.Jedis; /* * Jedisصӿ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:07 2019/5/4 * */ public interface JedisCallback { /** * doInJedisΪṩһδװ jedis 󣬿ʹԭ jedis ĸַ * @param jedis * @return */ public T doInJedis(Jedis jedis) throws CloneNotSupportedException; } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/helper/RedisHelper.java ================================================ package io.tyloo.repository.helper; import org.apache.log4j.Logger; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.ScanParams; import redis.clients.jedis.exceptions.JedisDataException; import javax.transaction.xa.Xid; /* * * Redis * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:07 2019/5/6 * */ public class RedisHelper { public static int SCAN_COUNT = 30; public static String SCAN_TEST_PATTERN = "*"; public static String SCAN_INIT_CURSOR = "0"; private static final Logger logger = Logger.getLogger(RedisHelper.class); public static byte[] getRedisKey(String keyPrefix, Xid xid) { return (keyPrefix + xid.toString()).getBytes(); } public static byte[] getRedisKey(String keyPrefix, String globalTransactionId, String branchQualifier) { return (keyPrefix + globalTransactionId + ":" + branchQualifier).getBytes(); } /** * JedisPoolÿJedis * @param * @param jedisPool * @param callback * @return */ public static T execute(JedisPool jedisPool, JedisCallback callback) { try (Jedis jedis = jedisPool.getResource()) { return callback.doInJedis(jedis); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } /** * SCAN cursor [MATCH pattern] [COUNT count] * $redis-cli scan 0 match key99* count 1000 * 1) "13912" * 2) 1) "key997" * 2) "key9906" * 3) "key9957" * 4) "key9902" * 5) "key9971" * 6) "key9935" * 7) "key9958" * 8) "key9928" * 9) "key9931" * 10) "key9961" * 11) "key9948" * 12) "key9965" * 13) "key9937" *

* $redis-cli scan 13912 match key99* count 1000 * 1) "5292" * 2) 1) "key996" * 2) "key9960" * 3) "key9973" * 4) "key9978" * 5) "key9927" * 6) "key995" * 7) "key9992" * 8) "key9993" * 9) "key9964" * 10) "key9934" * ؽΪ֣һּ 1) һεα꣬ڶּ 2) DZε * * @param pattern * @param count * @return */ public static ScanParams buildDefaultScanParams(String pattern, int count) { return new ScanParams().match(pattern).count(count); } /** * Ƿ֧scanJedis * * @param jedis * @return */ public static Boolean isSupportScanCommand(Jedis jedis) { try { ScanParams scanParams = buildDefaultScanParams(SCAN_TEST_PATTERN, SCAN_COUNT); jedis.scan(SCAN_INIT_CURSOR, scanParams); } catch (JedisDataException e) { logger.error(e.getMessage(), e); logger.info("Redis **NOT** support scan command"); return false; } logger.info("Redis support scan command"); return true; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/repository/helper/TransactionSerializer.java ================================================ package io.tyloo.repository.helper; import io.tyloo.Transaction; import io.tyloo.serializer.ObjectSerializer; import java.util.Date; import java.util.HashMap; import java.util.Map; /* * * л * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:08 2019/5/8 * */ public class TransactionSerializer { public static byte[] serialize(ObjectSerializer serializer, Transaction transaction) throws CloneNotSupportedException { Map map = new HashMap<>(); map.put("GLOBAL_TX_ID", transaction.getXid().getGlobalTransactionId()); map.put("BRANCH_QUALIFIER", transaction.getXid().getBranchQualifier()); map.put("STATUS", transaction.getStatus().getId()); map.put("TRANSACTION_TYPE", transaction.getTransactionType().getId()); map.put("RETRIED_COUNT", transaction.getRetriedCount()); map.put("CREATE_TIME", transaction.getCreateTime()); map.put("LAST_UPDATE_TIME", transaction.getLastUpdateTime()); map.put("VERSION", transaction.getVersion()); map.put("CONTENT", serializer.serialize(transaction)); return serializer.serialize(map); } public static Transaction deserialize(ObjectSerializer serializer, byte[] value) { Map map = (Map) serializer.deserialize(value); byte[] content = (byte[]) map.get("CONTENT"); Transaction transaction = (Transaction) serializer.deserialize(content); transaction.resetRetriedCount((Integer) map.get("RETRIED_COUNT")); transaction.setLastUpdateTime((Date) map.get("LAST_UPDATE_TIME")); transaction.setVersion((Long) map.get("VERSION")); return transaction; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/serializer/JacksonJsonSerializer.java ================================================ package io.tyloo.serializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import io.tyloo.SystemException; import io.tyloo.Transaction; import java.io.IOException; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:29 2019/12/4 * */ public class JacksonJsonSerializer implements ObjectSerializer { private static ObjectMapper objectMapper = new ObjectMapper(); static { // objectMapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.NON_FINAL,"@class"); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); } @Override public byte[] serialize(Transaction transaction) { try { return objectMapper.writeValueAsBytes(transaction); } catch (JsonProcessingException e) { throw new SystemException(String.format("serialize object failed. object:%s", transaction), e); } } @Override public Transaction deserialize(byte[] bytes) { try { return objectMapper.readValue(bytes, Transaction.class); } catch (IOException e) { throw new SystemException(String.format("deserialize object failed."), e); } } @Override public Transaction clone(Transaction object) { return deserialize(serialize(object)); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/serializer/JdkSerializationSerializer.java ================================================ package io.tyloo.serializer; import io.tyloo.Transaction; import org.apache.commons.lang3.SerializationUtils; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:29 2019/5/22 * */ public class JdkSerializationSerializer implements ObjectSerializer { @Override public byte[] serialize(Transaction object) { return SerializationUtils.serialize(object); } @Override public Transaction deserialize(byte[] bytes) { if (bytes == null) { return null; } else { return (Transaction) SerializationUtils.deserialize(bytes); } } @Override public Transaction clone(Transaction object) { return SerializationUtils.clone(object); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/serializer/KryoPoolSerializer.java ================================================ package io.tyloo.serializer; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.pool.KryoCallback; import com.esotericsoftware.kryo.pool.KryoFactory; import com.esotericsoftware.kryo.pool.KryoPool; import io.tyloo.Transaction; import org.objenesis.strategy.StdInstantiatorStrategy; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:29 2019/5/22 * */ public class KryoPoolSerializer implements ObjectSerializer { static KryoFactory factory = () -> { Kryo kryo = new Kryo(); kryo.setReferences(true); kryo.setRegistrationRequired(false); //Fix the NPE bug when deserializing Collections. ((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()) .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy()); return kryo; }; KryoPool pool = new KryoPool.Builder(factory).softReferences().build(); private int initPoolSize = 300; public KryoPoolSerializer() { init(); } public KryoPoolSerializer(int initPoolSize) { this.initPoolSize = initPoolSize; init(); } private void init() { for (int i = 0; i < initPoolSize; i++) { Kryo kryo = pool.borrow(); pool.release(kryo); } } @Override public byte[] serialize(final Transaction object) { return pool.run(kryo -> { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Output output = new Output(byteArrayOutputStream); kryo.writeClassAndObject(output, object); output.flush(); return byteArrayOutputStream.toByteArray(); }); } @Override public Transaction deserialize(final byte[] bytes) { return pool.run(kryo -> { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); Input input = new Input(byteArrayInputStream); return (Transaction) kryo.readClassAndObject(input); }); } @Override public Transaction clone(final Transaction object) { return pool.run(kryo -> kryo.copy(object)); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/serializer/ObjectSerializer.java ================================================ package io.tyloo.serializer; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:29 2019/5/22 * */ public interface ObjectSerializer { /** * Serialize the given object to binary data. * * @param t object to serialize * @return the equivalent binary data */ byte[] serialize(T t); /** * Deserialize an object from the given binary data. * * @param bytes object binary representation * @return the equivalent object instance */ T deserialize(byte[] bytes); T clone(T object); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/support/BeanFactory.java ================================================ package io.tyloo.support; /* * * Bean * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:34 2019/5/27 * */ public interface BeanFactory { T getBean(Class var1); boolean isFactoryOf(Class clazz); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/support/FactoryBuilder.java ================================================ package io.tyloo.support; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /* * * Bean * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:35 2019/5/27 * */ public final class FactoryBuilder { private FactoryBuilder() { } /** * Bean */ private static List beanFactories = new ArrayList(); /** * Bean ӳ */ private static ConcurrentHashMap classFactoryMap = new ConcurrentHashMap<>(); /** * ָ൥ * * @param * @param clazz ָ * @return */ public static SingeltonFactory factoryOf(Class clazz) { if (!classFactoryMap.containsKey(clazz)) { for (BeanFactory beanFactory : beanFactories) { if (beanFactory.isFactoryOf(clazz)) { classFactoryMap.putIfAbsent(clazz, new SingeltonFactory(clazz, beanFactory.getBean(clazz))); } } if (!classFactoryMap.containsKey(clazz)) { classFactoryMap.putIfAbsent(clazz, new SingeltonFactory(clazz)); } } return classFactoryMap.get(clazz); } /** * BeanעᵽǰBuilder * * @param beanFactory Bean */ public static void registerBeanFactory(BeanFactory beanFactory) { beanFactories.add(beanFactory); } /** * * * @param */ public static class SingeltonFactory { private volatile T instance = null; private String className; public SingeltonFactory(Class clazz, T instance) { this.className = clazz.getName(); this.instance = instance; } public SingeltonFactory(Class clazz) { this.className = clazz.getName(); } /** * õ * * @return */ public T getInstance() { if (instance == null) { synchronized (SingeltonFactory.class) { if (instance == null) { try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class clazz = loader.loadClass(className); instance = (T) clazz.newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to create an instance of " + className, e); } } } } return instance; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (other == null || getClass() != other.getClass()) { return false; } SingeltonFactory that = (SingeltonFactory) other; return className.equals(that.className); } @Override public int hashCode() { return className.hashCode(); } } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/support/TransactionConfigurator.java ================================================ package io.tyloo.support; import io.tyloo.TransactionManager; import io.tyloo.TransactionRepository; import io.tyloo.recover.TylooRecoverConfiguration; /* * * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:34 2019/5/28 * */ public interface TransactionConfigurator { /** * ȡ. * * @return */ TransactionManager getTransactionManager(); /** * ȡ. * * @return */ TransactionRepository getTransactionRepository(); /** * ȡָ. * * @return */ TylooRecoverConfiguration getTylooRecoverConfiguration(); } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/utils/ByteUtils.java ================================================ package io.tyloo.utils; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:49 2019/5/30 * */ public class ByteUtils { public static byte[] longToBytes(long num) { return String.valueOf(num).getBytes(); } public static long bytesToLong(byte[] bytes) { return Long.valueOf(new String(bytes)); } public static byte[] intToBytes(int num) { return String.valueOf(num).getBytes(); } public static int bytesToInt(byte[] bytes) { return Integer.valueOf(new String(bytes)); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/utils/CollectionUtils.java ================================================ package io.tyloo.utils; import java.util.Collection; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:49 2019/5/30 * */ public final class CollectionUtils { private CollectionUtils() { } public static boolean isEmpty(Collection collection) { return (collection == null || collection.isEmpty()); } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/utils/ReflectionUtils.java ================================================ package io.tyloo.utils; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.Map; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 18:07 2019/5/19 * */ public class ReflectionUtils { public static void makeAccessible(Method method) { if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { method.setAccessible(true); } } public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Object handler = Proxy.getInvocationHandler(annotation); Field f; f = handler.getClass().getDeclaredField("memberValues"); f.setAccessible(true); Map memberValues; memberValues = (Map) f.get(handler); Object oldValue = memberValues.get(key); if (oldValue == null || oldValue.getClass() != newValue.getClass()) { throw new IllegalArgumentException(); } memberValues.put(key, newValue); return oldValue; } public static Class getDeclaringType(Class aClass, String methodName, Class[] parameterTypes) { Method method = null; Class findClass = aClass; do { Class[] clazzes = findClass.getInterfaces(); for (Class clazz : clazzes) { try { method = clazz.getDeclaredMethod(methodName, parameterTypes); } catch (NoSuchMethodException e) { method = null; } if (method != null) { return clazz; } } findClass = findClass.getSuperclass(); } while (!findClass.equals(Object.class)); return aClass; } public static Object getNullValue(Class type) { if (boolean.class.equals(type)) { return false; } else if (byte.class.equals(type)) { return 0; } else if (short.class.equals(type)) { return 0; } else if (int.class.equals(type)) { return 0; } else if (long.class.equals(type)) { return 0; } else if (float.class.equals(type)) { return 0; } else if (double.class.equals(type)) { return 0; } else if (char.class.equals(type)) { return ' '; } return null; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/utils/StringUtils.java ================================================ package io.tyloo.utils; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:49 2019/5/30 * */ public class StringUtils { public static boolean isNotEmpty(String value) { if(value == null) { return false; } if(value.equals("")) { return false; } return true; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/utils/TransactionUtils.java ================================================ package io.tyloo.utils; import io.tyloo.api.Propagation; import io.tyloo.interceptor.TylooMethodContext; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:49 2019/5/30 * */ public class TransactionUtils { public static boolean isLegalTransactionContext(boolean isTransactionActive, TylooMethodContext tylooMethodContext) { return !tylooMethodContext.getPropagation().equals(Propagation.MANDATORY) || isTransactionActive || tylooMethodContext.getTransactionContext() != null; } } ================================================ FILE: tyloo-core/src/main/java/io/tyloo/utils/TylooMethodUtils.java ================================================ package io.tyloo.utils; import io.tyloo.api.Propagation; import io.tyloo.api.TransactionContext; import io.tyloo.api.Tyloo; import io.tyloo.common.MethodRole; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 19:49 2019/5/30 * */ public class TylooMethodUtils { public static Method getTylooMethod(ProceedingJoinPoint pjp) { Method method = ((MethodSignature) (pjp.getSignature())).getMethod(); if (method.getAnnotation(Tyloo.class) == null) { try { method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { return null; } } return method; } public static MethodRole calculateMethodType(Propagation propagation, boolean isTransactionActive, TransactionContext transactionContext) { if ((propagation.equals(Propagation.REQUIRED) && !isTransactionActive && transactionContext == null) || propagation.equals(Propagation.REQUIRES_NEW)) { return MethodRole.ROOT; } else if ((propagation.equals(Propagation.REQUIRED) || propagation.equals(Propagation.MANDATORY)) && !isTransactionActive && transactionContext != null) { return MethodRole.PROVIDER; } else { return MethodRole.NORMAL; } } public static int getTransactionContextParamPosition(Class[] parameterTypes) { int position = -1; for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i].equals(TransactionContext.class)) { position = i; break; } } return position; } } ================================================ FILE: tyloo-dubbo/pom.xml ================================================ tyloo io.tyloo 1.1.0 4.0.0 tyloo-dubbo io.tyloo tyloo-api io.tyloo tyloo-core org.apache.dubbo dubbo org.apache.dubbo dubbo-dependencies-zookeeper pom org.aspectj aspectjweaver com.alibaba fastjson ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/constants/TransactionContextConstants.java ================================================ package io.tyloo.dubbo.constants; /* * * ij * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 9:33 2019/7/5 * */ public class TransactionContextConstants { public static final String TRANSACTION_CONTEXT = "TRANSACTION_CONTEXT"; } ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/context/DubboTransactionContextEditor.java ================================================ package io.tyloo.dubbo.context; import com.alibaba.fastjson.JSON; import io.tyloo.api.TransactionContext; import io.tyloo.api.TransactionContextEditor; import io.tyloo.dubbo.constants.TransactionContextConstants; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.RpcContext; import java.lang.reflect.Method; /* * Dubbo ı༭ * * ײʹõdubboTransactionContextEditorΪDubboTransactionContextEditor.classʹdubboʽηʽ * ͨ Dubbo ʽεķʽ Dubbo Service ӿ TransactionContext Խӿڲһ * tyloo ͨ Dubbo Proxy Ļƣʵ `@Tyloo` Զ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 15:32 2019/7/17 * */ public class DubboTransactionContextEditor implements TransactionContextEditor { @Override public TransactionContext get(Object target, Method method, Object[] args) { String context = RpcContext.getContext().getAttachment(TransactionContextConstants.TRANSACTION_CONTEXT); if (StringUtils.isNotEmpty(context)) { return JSON.parseObject(context, TransactionContext.class); } return null; } @Override public void set(TransactionContext transactionContext, Object target, Method method, Object[] args) { RpcContext.getContext().setAttachment(TransactionContextConstants.TRANSACTION_CONTEXT, JSON.toJSONString(transactionContext)); } } ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/proxy/javassist/TylooClassGenerator.java ================================================ package io.tyloo.dubbo.proxy.javassist; import javassist.*; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.ClassMemberValue; import javassist.bytecode.annotation.EnumMemberValue; import javassist.bytecode.annotation.StringMemberValue; import org.apache.dubbo.common.utils.ReflectUtils; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /* * * TCC Javassist ʵ֡ * Javassist һԴķ༭ʹ Java ֽ⡣ * һ Dubbo ServiceTccProxy ᶯ̬ࣺ - Dubbo Service Proxy - Dubbo Service ProxyFactoryɶӦ Dubbo Service Proxy * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 9:33 2019/8/1 * */ public final class TylooClassGenerator { private static final AtomicLong CLASS_NAME_COUNTER = new AtomicLong(0); private static final String SIMPLE_NAME_TAG = ""; private static final Map POOL_MAP = new ConcurrentHashMap(); //ClassLoader - ClassPool private final Set tylooMethods = new HashSet(); private ClassPool mPool; private CtClass mCtc; private String mClassName, mSuperClass; private Set mInterfaces; private List mFields, mConstructors, mMethods; private Map mCopyMethods; // private Map> mCopyConstructors; // private boolean mDefaultConstructor = false; private TylooClassGenerator() { } private TylooClassGenerator(ClassPool pool) { mPool = pool; } public static TylooClassGenerator newInstance() { return new TylooClassGenerator(getClassPool(Thread.currentThread().getContextClassLoader())); } public static TylooClassGenerator newInstance(ClassLoader loader) { return new TylooClassGenerator(getClassPool(loader)); } public static boolean isDynamicClass(Class cl) { return TylooClassGenerator.DC.class.isAssignableFrom(cl); } public static ClassPool getClassPool(ClassLoader loader) { if (loader == null) { return ClassPool.getDefault(); } ClassPool pool = POOL_MAP.get(loader); if (pool == null) { pool = new ClassPool(true); pool.appendClassPath(new LoaderClassPath(loader)); POOL_MAP.put(loader, pool); } return pool; } private static String modifier(int mod) { if (java.lang.reflect.Modifier.isPublic(mod)) { return "public"; } if (java.lang.reflect.Modifier.isProtected(mod)) { return "protected"; } if (java.lang.reflect.Modifier.isPrivate(mod)) { return "private"; } return ""; } public String getClassName() { return mClassName; } public TylooClassGenerator setClassName(String name) { mClassName = name; return this; } public TylooClassGenerator addInterface(String cn) { if (mInterfaces == null) { mInterfaces = new HashSet(); } mInterfaces.add(cn); return this; } public TylooClassGenerator addInterface(Class cl) { return addInterface(cl.getName()); } public TylooClassGenerator setSuperClass(String cn) { mSuperClass = cn; return this; } public TylooClassGenerator setSuperClass(Class cl) { mSuperClass = cl.getName(); return this; } public TylooClassGenerator addField(String code) { if (mFields == null) { mFields = new ArrayList(); } mFields.add(code); return this; } public TylooClassGenerator addField(String name, int mod, Class type) { return addField(name, mod, type, null); } public TylooClassGenerator addField(String name, int mod, Class type, String def) { StringBuilder sb = new StringBuilder(); sb.append(modifier(mod)).append(' ').append(ReflectUtils.getName(type)).append(' '); sb.append(name); if (def != null && def.length() > 0) { sb.append('='); sb.append(def); } sb.append(';'); return addField(sb.toString()); } public TylooClassGenerator addMethod(String code) { if (mMethods == null) { mMethods = new ArrayList(); } mMethods.add(code); return this; } public TylooClassGenerator addMethod(String name, int mod, Class rt, Class[] pts, String body) { return addMethod(false, name, mod, rt, pts, null, body); } public TylooClassGenerator addMethod(boolean isTylooMethod, String name, int mod, Class rt, Class[] pts, Class[] ets, String body) { StringBuilder sb = new StringBuilder(); sb.append(modifier(mod)).append(' ').append(ReflectUtils.getName(rt)).append(' ').append(name); sb.append('('); for (int i = 0; i < pts.length; i++) { if (i > 0) { sb.append(','); } sb.append(ReflectUtils.getName(pts[i])); sb.append(" arg").append(i); } sb.append(')'); if (ets != null && ets.length > 0) { sb.append(" throws "); for (int i = 0; i < ets.length; i++) { if (i > 0) { sb.append(','); } sb.append(ReflectUtils.getName(ets[i])); } } sb.append('{').append(body).append('}'); if (isTylooMethod) { tylooMethods.add(sb.toString()); } return addMethod(sb.toString()); } public TylooClassGenerator addMethod(Method m) { addMethod(m.getName(), m); return this; } public TylooClassGenerator addMethod(String name, Method m) { String desc = name + ReflectUtils.getDescWithoutMethodName(m); addMethod(':' + desc); if (mCopyMethods == null) { mCopyMethods = new ConcurrentHashMap(8); } mCopyMethods.put(desc, m); return this; } public TylooClassGenerator addConstructor(String code) { if (mConstructors == null) { mConstructors = new LinkedList(); } mConstructors.add(code); return this; } public TylooClassGenerator addConstructor(int mod, Class[] pts, String body) { return addConstructor(mod, pts, null, body); } public TylooClassGenerator addConstructor(int mod, Class[] pts, Class[] ets, String body) { StringBuilder sb = new StringBuilder(); sb.append(modifier(mod)).append(' ').append(SIMPLE_NAME_TAG); sb.append('('); for (int i = 0; i < pts.length; i++) { if (i > 0) { sb.append(','); } sb.append(ReflectUtils.getName(pts[i])); sb.append(" arg").append(i); } sb.append(')'); if (ets != null && ets.length > 0) { sb.append(" throws "); for (int i = 0; i < ets.length; i++) { if (i > 0) { sb.append(','); } sb.append(ReflectUtils.getName(ets[i])); } } sb.append('{').append(body).append('}'); return addConstructor(sb.toString()); } public TylooClassGenerator addConstructor(Constructor c) { String desc = ReflectUtils.getDesc(c); addConstructor(":" + desc); if (mCopyConstructors == null) { mCopyConstructors = new ConcurrentHashMap>(4); } mCopyConstructors.put(desc, c); return this; } public TylooClassGenerator addDefaultConstructor() { mDefaultConstructor = true; return this; } public ClassPool getClassPool() { return mPool; } public Class toClass() { if (mCtc != null) { mCtc.detach(); } long id = CLASS_NAME_COUNTER.getAndIncrement(); try { CtClass ctcs = mSuperClass == null ? null : mPool.get(mSuperClass); if (mClassName == null) { mClassName = (mSuperClass == null || Modifier.isPublic(ctcs.getModifiers()) ? TylooClassGenerator.class.getName() : mSuperClass + "$sc") + id; } mCtc = mPool.makeClass(mClassName); if (mSuperClass != null) { mCtc.setSuperclass(ctcs); } mCtc.addInterface(mPool.get(DC.class.getName())); // add dynamic class tag. if (mInterfaces != null) { for (String cl : mInterfaces) { mCtc.addInterface(mPool.get(cl)); } } if (mFields != null) { for (String code : mFields) { mCtc.addField(CtField.make(code, mCtc)); } } if (mMethods != null) { for (String code : mMethods) { if (code.charAt(0) == ':') { mCtc.addMethod(CtNewMethod.copy(getCtMethod(mCopyMethods.get(code.substring(1))), code.substring(1, code.indexOf('(')), mCtc, null)); } else { CtMethod ctMethod = CtNewMethod.make(code, mCtc); // עⷽ if (tylooMethods.contains(code)) { ConstPool constpool = mCtc.getClassFile().getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); Annotation annot = new Annotation("Tyloo", constpool); EnumMemberValue enumMemberValue = new EnumMemberValue(constpool); enumMemberValue.setType("Propagation"); enumMemberValue.setValue("SUPPORTS"); annot.addMemberValue("propagation", enumMemberValue); annot.addMemberValue("confirmMethod", new StringMemberValue(ctMethod.getName(), constpool)); annot.addMemberValue("cancelMethod", new StringMemberValue(ctMethod.getName(), constpool)); ClassMemberValue classMemberValue = new ClassMemberValue("DubboTransactionContextEditor", constpool); annot.addMemberValue("transactionContextEditor", classMemberValue); attr.addAnnotation(annot); ctMethod.getMethodInfo().addAttribute(attr); } mCtc.addMethod(ctMethod); } } } if (mDefaultConstructor) { mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc)); } if (mConstructors != null) { for (String code : mConstructors) { if (code.charAt(0) == ':') { mCtc.addConstructor(CtNewConstructor.copy(getCtConstructor(mCopyConstructors.get(code.substring(1))), mCtc, null)); } else { String[] sn = mCtc.getSimpleName().split("\\$+"); // inner class name include $. mCtc.addConstructor(CtNewConstructor.make(code.replaceFirst(SIMPLE_NAME_TAG, sn[sn.length - 1]), mCtc)); } } } return mCtc.toClass(); } catch (RuntimeException e) { throw e; } catch (NotFoundException e) { throw new RuntimeException(e.getMessage(), e); } catch (CannotCompileException e) { throw new RuntimeException(e.getMessage(), e); } } public void release() { if (mCtc != null) { mCtc.detach(); } if (mInterfaces != null) { mInterfaces.clear(); } if (mFields != null) { mFields.clear(); } if (mMethods != null) { mMethods.clear(); } if (mConstructors != null) { mConstructors.clear(); } if (mCopyMethods != null) { mCopyMethods.clear(); } if (mCopyConstructors != null) { mCopyConstructors.clear(); } } private CtClass getCtClass(Class c) throws NotFoundException { return mPool.get(c.getName()); } private CtMethod getCtMethod(Method m) throws NotFoundException { return getCtClass(m.getDeclaringClass()).getMethod(m.getName(), ReflectUtils.getDescWithoutMethodName(m)); } private CtConstructor getCtConstructor(Constructor c) throws NotFoundException { return getCtClass(c.getDeclaringClass()).getConstructor(ReflectUtils.getDesc(c)); } public interface DC { } // dynamic class tag interface. } ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/proxy/javassist/TylooJavassistProxyFactory.java ================================================ package io.tyloo.dubbo.proxy.javassist; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler; import org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory; /* * * * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 9:33 2019/8/16 * */ public class TylooJavassistProxyFactory extends JavassistProxyFactory { /** * ĿʱgetProxy(...)` Dubbo Service Proxy * com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler`Dubbo ô * * @param invoker * @param interfaces * @param * @return */ @Override @SuppressWarnings("unchecked") public T getProxy(Invoker invoker, Class[] interfaces) { return (T) TylooProxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } } ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/proxy/javassist/TylooProxy.java ================================================ /* * Copyright 1999-2011 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.tyloo.dubbo.proxy.javassist; import io.tyloo.api.Tyloo; import org.apache.dubbo.common.utils.ClassHelper; import org.apache.dubbo.common.utils.ReflectUtils; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.atomic.AtomicLong; /* * * TCC Proxy Dubbo Service Proxy * * ο Dubbo Դʵ֣ * com.alibaba.dubbo.Enums.bytecode.Proxy * com.alibaba.dubbo.Enums.bytecode.ClassGenerator * com.alibaba.dubbo.Enums.bytecode.Wrapper * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 9:34 2019/8/27 * */ public abstract class TylooProxy { public static final InvocationHandler RETURN_NULL_INVOKER = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) { return null; } }; public static final InvocationHandler THROW_UNSUPPORTED_INVOKER = (proxy, method, args) -> { throw new UnsupportedOperationException("Method [" + ReflectUtils.getName(method) + "] unimplemented."); }; private static final AtomicLong PROXY_CLASS_COUNTER = new AtomicLong(0); private static final String PACKAGE_NAME = TylooProxy.class.getPackage().getName(); private static final Map> ProxyCacheMap = new WeakHashMap>(); private static final Object PendingGenerationMarker = new Object(); protected TylooProxy() { } /** * TCC Proxy * * @param ics interface class array. * @return TccProxy instance. */ public static TylooProxy getProxy(Class... ics) { return getProxy(ClassHelper.getCallerClassLoader(TylooProxy.class), ics); } /** * 1. Уӿ * 2. ʹýӿڼ `;` ָƴӣΪ Proxy Ψһʶ * 3. Proxy Ӧ ClassLoader * 4. һֱ Tyloo Proxy ֱɹ * 5. Dubbo Service ProxyFactoryProxy Ĵ * 6. Dubbo Service Proxy Ĵ롣 * * @param cl * @param ics * @return */ public static TylooProxy getProxy(ClassLoader cl, Class... ics) { if (ics.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < ics.length; i++) { String itf = ics[i].getName(); if (!ics[i].isInterface()) { throw new RuntimeException(itf + " is not a interface."); } Class tmp = null; try { tmp = Class.forName(itf, false, cl); } catch (ClassNotFoundException e) { } if (tmp != ics[i]) { throw new IllegalArgumentException(ics[i] + " is not visible from class loader"); } sb.append(itf).append(';'); } // use interface class name list as key. String key = sb.toString(); // get cache by class loader. Map cache; synchronized (ProxyCacheMap) { cache = ProxyCacheMap.get(cl); if (cache == null) { cache = new HashMap(); ProxyCacheMap.put(cl, cache); } } TylooProxy proxy = null; synchronized (cache) { do { Object value = cache.get(key); if (value instanceof Reference) { proxy = (TylooProxy) ((Reference) value).get(); if (proxy != null) { return proxy; } } if (value == PendingGenerationMarker) { try { cache.wait(); } catch (InterruptedException e) { } } else { cache.put(key, PendingGenerationMarker); break; } } while (true); } /** * Dubbo Service Proxy Ĵ */ long id = PROXY_CLASS_COUNTER.getAndIncrement(); String pkg = null; TylooClassGenerator ccp = null, ccm = null; try { ccp = TylooClassGenerator.newInstance(cl); Set worked = new HashSet(); List methods = new ArrayList(); for (int i = 0; i < ics.length; i++) { if (!Modifier.isPublic(ics[i].getModifiers())) { String npkg = ics[i].getPackage().getName(); if (pkg == null) { pkg = npkg; } else { if (pkg.equals(npkg)) { } else { throw new IllegalArgumentException("non-public interfaces from different packages"); } } } ccp.addInterface(ics[i]); for (Method method : ics[i].getMethods()) { String desc = ReflectUtils.getDesc(method); if (worked.contains(desc)) { continue; } worked.add(desc); int ix = methods.size(); Class rt = method.getReturnType(); Class[] pts = method.getParameterTypes(); StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];"); for (int j = 0; j < pts.length; j++) { code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";"); } code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);"); if (!Void.TYPE.equals(rt)) { code.append(" return ").append(asArgument(rt, "ret")).append(";"); } methods.add(method); StringBuilder tylooDesc = new StringBuilder(); Tyloo tyloo = method.getAnnotation(Tyloo.class); ccp.addMethod(tyloo != null, method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString()); } } if (pkg == null) { pkg = PACKAGE_NAME; } // create ProxyInstance class. String pcn = pkg + ".proxy" + id; ccp.setClassName(pcn); ccp.addField("public static java.lang.reflect.Method[] methods;"); ccp.addField("private " + InvocationHandler.class.getName() + " handler;"); ccp.addConstructor(Modifier.PUBLIC, new Class[]{InvocationHandler.class}, new Class[0], "handler=$1;"); ccp.addDefaultConstructor(); Class clazz = ccp.toClass(); clazz.getField("methods").set(null, methods.toArray(new Method[0])); // create TccProxy class. String fcn = TylooProxy.class.getName() + id; ccm = TylooClassGenerator.newInstance(cl); ccm.setClassName(fcn); ccm.addDefaultConstructor(); ccm.setSuperClass(TylooProxy.class); ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }"); Class pc = ccm.toClass(); proxy = (TylooProxy) pc.newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { // release TccClassGenerator if (ccp != null) { ccp.release(); } if (ccm != null) { ccm.release(); } synchronized (cache) { if (proxy == null) { cache.remove(key); } else { cache.put(key, new WeakReference(proxy)); } cache.notifyAll(); } } return proxy; } private static String asArgument(Class cl, String name) { if (cl.isPrimitive()) { if (Boolean.TYPE == cl) { return name + "==null?false:((Boolean)" + name + ").booleanValue()"; } if (Byte.TYPE == cl) { return name + "==null?(byte)0:((Byte)" + name + ").byteValue()"; } if (Character.TYPE == cl) { return name + "==null?(char)0:((Character)" + name + ").charValue()"; } if (Double.TYPE == cl) { return name + "==null?(double)0:((Double)" + name + ").doubleValue()"; } if (Float.TYPE == cl) { return name + "==null?(float)0:((Float)" + name + ").floatValue()"; } if (Integer.TYPE == cl) { return name + "==null?(int)0:((Integer)" + name + ").intValue()"; } if (Long.TYPE == cl) { return name + "==null?(long)0:((Long)" + name + ").longValue()"; } if (Short.TYPE == cl) { return name + "==null?(short)0:((Short)" + name + ").shortValue()"; } throw new RuntimeException(name + " is unknown primitive type."); } return "(" + ReflectUtils.getName(cl) + ")" + name; } /** * get instance with default handler. * * @return instance. */ public Object newInstance() { return newInstance(THROW_UNSUPPORTED_INVOKER); } /** * get instance with special handler. * * @return instance. */ abstract public Object newInstance(InvocationHandler handler); } ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/proxy/jdk/MethodProceedingJoinPoint.java ================================================ package io.tyloo.dubbo.proxy.jdk; import io.tyloo.SystemException; import io.tyloo.utils.ReflectionUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.MethodSignature; import org.aspectj.lang.reflect.SourceLocation; import org.aspectj.runtime.internal.AroundClosure; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /* * * ɷ * * ο [`org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint`](https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java) ʵ֡ * 洦ɺ󣬵 `#proceed(...)` Զ Dubbo Service á * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 10:56 2019/9/12 * */ public class MethodProceedingJoinPoint implements ProceedingJoinPoint, JoinPoint.StaticPart { private final Object proxy; private final Object target; private final Method method; private final Object[] args; private Signature signature; /** * Lazily initialized source location object */ private SourceLocation sourceLocation; /** * @param proxy * @param target Ŀ * @param method * @param args */ public MethodProceedingJoinPoint(Object proxy, Object target, Method method, Object[] args) { this.proxy = proxy; this.target = target; this.method = method; this.args = args; } /** * Զ Dubbo Service * * @return * @throws Throwable */ @Override public void set$AroundClosure(AroundClosure aroundClosure) { throw new UnsupportedOperationException(); } @Override public Object proceed() throws Throwable { // Use reflection to invoke the method. try { ReflectionUtils.makeAccessible(method); return method.invoke(target, args); } catch (InvocationTargetException ex) { // Invoked method threw a checked exception. // We must rethrow it. The client won't see the interceptor. throw ex.getTargetException(); } catch (IllegalArgumentException ex) { throw new SystemException("Tried calling method [" + method + "] on target [" + target + "] failed", ex); } catch (IllegalAccessException ex) { throw new SystemException("Could not access method [" + method + "]", ex); } } @Override public Object proceed(Object[] objects) throws Throwable { // Use reflection to invoke the method. try { ReflectionUtils.makeAccessible(method); return method.invoke(target, objects); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } catch (IllegalArgumentException ex) { throw new SystemException("Tried calling method [" + method + "] on target [" + target + "] failed", ex); } catch (IllegalAccessException ex) { throw new SystemException("Could not access method [" + method + "]", ex); } } @Override public String toShortString() { return "execution(" + getSignature().toShortString() + ")"; } @Override public String toLongString() { return "execution(" + getSignature().toLongString() + ")"; } @Override public String toString() { return "execution(" + getSignature().toString() + ")"; } @Override public Object getThis() { return this.proxy; } @Override public Object getTarget() { return this.target; } @Override public Object[] getArgs() { return this.args; } @Override public Signature getSignature() { if (this.signature == null) { this.signature = new MethodSignatureImpl(); } return signature; } @Override public SourceLocation getSourceLocation() { if (this.sourceLocation == null) { this.sourceLocation = new SourceLocationImpl(); } return this.sourceLocation; } @Override public String getKind() { return ProceedingJoinPoint.METHOD_EXECUTION; } @Override public int getId() { return 0; } @Override public StaticPart getStaticPart() { return this; } /** * Lazily initialized MethodSignature. */ private class MethodSignatureImpl implements MethodSignature { private volatile String[] parameterNames; @Override public String getName() { return method.getName(); } @Override public int getModifiers() { return method.getModifiers(); } @Override public Class getDeclaringType() { return method.getDeclaringClass(); } @Override public String getDeclaringTypeName() { return method.getDeclaringClass().getName(); } @Override public Class getReturnType() { return method.getReturnType(); } @Override public Method getMethod() { return method; } @Override public Class[] getParameterTypes() { return method.getParameterTypes(); } @Override public String[] getParameterNames() { throw new UnsupportedOperationException(); } @Override public Class[] getExceptionTypes() { return method.getExceptionTypes(); } @Override public String toShortString() { return toString(false, false, false, false); } @Override public String toLongString() { return toString(true, true, true, true); } @Override public String toString() { return toString(false, true, false, true); } private String toString(boolean includeModifier, boolean includeReturnTypeAndArgs, boolean useLongReturnAndArgumentTypeName, boolean useLongTypeName) { StringBuilder sb = new StringBuilder(); if (includeModifier) { sb.append(Modifier.toString(getModifiers())); sb.append(" "); } if (includeReturnTypeAndArgs) { appendType(sb, getReturnType(), useLongReturnAndArgumentTypeName); sb.append(" "); } appendType(sb, getDeclaringType(), useLongTypeName); sb.append("."); sb.append(getMethod().getName()); sb.append("("); Class[] parametersTypes = getParameterTypes(); appendTypes(sb, parametersTypes, includeReturnTypeAndArgs, useLongReturnAndArgumentTypeName); sb.append(")"); return sb.toString(); } private void appendTypes(StringBuilder sb, Class[] types, boolean includeArgs, boolean useLongReturnAndArgumentTypeName) { if (includeArgs) { for (int size = types.length, i = 0; i < size; i++) { appendType(sb, types[i], useLongReturnAndArgumentTypeName); if (i < size - 1) { sb.append(","); } } } else { if (types.length != 0) { sb.append(".."); } } } private void appendType(StringBuilder sb, Class type, boolean useLongTypeName) { if (type.isArray()) { appendType(sb, type.getComponentType(), useLongTypeName); sb.append("[]"); } else { sb.append(useLongTypeName ? type.getName() : type.getSimpleName()); } } } /** * Lazily initialized SourceLocation. */ private class SourceLocationImpl implements SourceLocation { @Override public Class getWithinType() { if (proxy == null) { throw new UnsupportedOperationException("No source location joinpoint available: target is null"); } return proxy.getClass(); } @Override public String getFileName() { throw new UnsupportedOperationException(); } @Override public int getLine() { throw new UnsupportedOperationException(); } @Override public int getColumn() { throw new UnsupportedOperationException(); } } } ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/proxy/jdk/TylooInvokerInvocationHandler.java ================================================ package io.tyloo.dubbo.proxy.jdk; import io.tyloo.api.Propagation; import io.tyloo.api.Tyloo; import io.tyloo.dubbo.context.DubboTransactionContextEditor; import io.tyloo.interceptor.TylooCoordinatorAspect; import io.tyloo.support.FactoryBuilder; import io.tyloo.utils.ReflectionUtils; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler; import org.aspectj.lang.ProceedingJoinPoint; import java.lang.reflect.Method; /* * * TCC ô * ڵ Dubbo Service ʱʹ TylooCoordinatorAspect ش * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 10:55 2019/9/23 * */ public class TylooInvokerInvocationHandler extends InvokerInvocationHandler { private Object target; public TylooInvokerInvocationHandler(Invoker handler) { super(handler); } public TylooInvokerInvocationHandler(T target, Invoker invoker) { super(invoker); this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Tyloo tyloo = method.getAnnotation(Tyloo.class); if (tyloo != null) { if (StringUtils.isEmpty(tyloo.confirmMethod())) { ReflectionUtils.changeAnnotationValue(tyloo, "confirmMethod", method.getName()); ReflectionUtils.changeAnnotationValue(tyloo, "cancelMethod", method.getName()); ReflectionUtils.changeAnnotationValue(tyloo, "transactionContextEditor", DubboTransactionContextEditor.class); ReflectionUtils.changeAnnotationValue(tyloo, "propagation", Propagation.SUPPORTS); } /** * ɷ * TylooCoordinatorAspect#interceptTransactionContextMethod Էش * Ϊʲô TylooTransactionAspect 棿 * ΪΪ Propagation.SUPPORTSᷢ */ ProceedingJoinPoint pjp = new MethodProceedingJoinPoint(proxy, target, method, args); return FactoryBuilder.factoryOf(TylooCoordinatorAspect.class).getInstance().interceptTransactionContextMethod(pjp); } else { return super.invoke(target, method, args); } } } ================================================ FILE: tyloo-dubbo/src/main/java/io/tyloo/dubbo/proxy/jdk/TylooJdkProxyFactory.java ================================================ package io.tyloo.dubbo.proxy.jdk; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler; import org.apache.dubbo.rpc.proxy.jdk.JdkProxyFactory; import java.lang.reflect.Proxy; /* * * TCC JDK * JDK ̬ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 10:52 2019/9/29 * */ public class TylooJdkProxyFactory extends JdkProxyFactory { /** * - Ŀʱ `TccJavassistProxyFactory#getProxy(...)` Dubbo Service Proxy * - һε `Proxy#newProxyInstance(...)` Dubbo Service Proxy * - ڶε `Proxy#newProxyInstance(...)` Ե Dubbo Service Proxy Proxy * * @param invoker * @param interfaces * @param * @return */ @Override @SuppressWarnings("unchecked") public T getProxy(Invoker invoker, Class[] interfaces) { T proxy = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker)); T tccProxy = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new TylooInvokerInvocationHandler(proxy, invoker)); return tccProxy; } } ================================================ FILE: tyloo-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.ProxyFactory ================================================ tylooJavassist=TylooJavassistProxyFactory tylooJdk=TylooJdkProxyFactory ================================================ FILE: tyloo-dubbo/src/main/resources/tyloo-dubbo.xml ================================================ ================================================ FILE: tyloo-spring/pom.xml ================================================ tyloo io.tyloo 1.1.0 4.0.0 tyloo-spring io.tyloo tyloo-core io.tyloo tyloo-api org.springframework spring-context org.springframework spring-jdbc org.springframework spring-aop org.springframework spring-context-support org.quartz-scheduler quartz org.aspectj aspectjweaver log4j log4j org.projectlombok lombok 1.18.12 ================================================ FILE: tyloo-spring/src/main/dbscripts/db.sql ================================================ CREATE TABLE `TCC_TRANSACTION` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/ConfigurableCoordinatorAspect.java ================================================ package io.tyloo.spring; import io.tyloo.interceptor.TylooCoordinatorAspect; import io.tyloo.interceptor.TylooCoordinatorInterceptor; import io.tyloo.support.TransactionConfigurator; import org.aspectj.lang.annotation.Aspect; import org.springframework.core.Ordered; /* * * õԴЭ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:21 2019/10/4 * */ @Aspect public class ConfigurableCoordinatorAspect extends TylooCoordinatorAspect implements Ordered { private TransactionConfigurator transactionConfigurator; /** * ʼ * ע TylooTransactionManager */ public void init() { TylooCoordinatorInterceptor tylooCoordinatorInterceptor = new TylooCoordinatorInterceptor(); tylooCoordinatorInterceptor.setTransactionManager(transactionConfigurator.getTransactionManager()); this.setTylooCoordinatorInterceptor(tylooCoordinatorInterceptor); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 1; } public void setTransactionConfigurator(TransactionConfigurator transactionConfigurator) { this.transactionConfigurator = transactionConfigurator; } } ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/ConfigurableTransactionAspect.java ================================================ package io.tyloo.spring; import io.tyloo.TransactionManager; import io.tyloo.interceptor.TylooTransactionAspect; import io.tyloo.interceptor.TylooTransactionInterceptor; import io.tyloo.support.TransactionConfigurator; import org.aspectj.lang.annotation.Aspect; import org.springframework.core.Ordered; /* * * õĿɲ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:13 2019/10/7 * */ @Aspect public class ConfigurableTransactionAspect extends TylooTransactionAspect implements Ordered { private TransactionConfigurator transactionConfigurator; /** * ʼ * ע DelayCancelExceptions setTylooTransactionManager */ public void init() { TransactionManager transactionManager = transactionConfigurator.getTransactionManager(); TylooTransactionInterceptor tylooTransactionInterceptor = new TylooTransactionInterceptor(); tylooTransactionInterceptor.setTransactionManager(transactionManager); tylooTransactionInterceptor.setDelayCancelExceptions(transactionConfigurator.getTylooRecoverConfiguration().getDelayCancelExceptions()); this.setTylooTransactionInterceptor(tylooTransactionInterceptor); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } public void setTransactionConfigurator(TransactionConfigurator transactionConfigurator) { this.transactionConfigurator = transactionConfigurator; } } ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/recover/DefaultTylooRecoverConfiguration.java ================================================ package io.tyloo.spring.recover; import io.tyloo.OptimisticLockException; import io.tyloo.recover.TylooRecoverConfiguration; import lombok.*; import java.net.SocketTimeoutException; import java.util.HashSet; import java.util.Set; /* * * Ĭָ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:14 2019/10/10 * */ @AllArgsConstructor @Data @Builder public class DefaultTylooRecoverConfiguration implements TylooRecoverConfiguration { public static final TylooRecoverConfiguration INSTANCE = new DefaultTylooRecoverConfiguration(); /** * һೢԻָԶָҪ˹ԤĬ30Σ */ private int maxRetryCount = 30; /** * һ־һʱûи¾ͻᱻΪǷ쳣Ҫָ * ָJobɨ賬ʱûиµ־Щлָʱ䵥λ룬Ĭ120 */ private int recoverDuration = 120; //120 seconds /** * ָJobãĬ(ÿ) * cron ʽ * 0/30 * * * * ?ÿ 30 ִһΡ */ private String cronExpression = "0 */1 * * * ?"; private int asyncTerminateThreadCorePoolSize = 512; private int asyncTerminateThreadMaxPoolSize = 1024; private int asyncTerminateThreadWorkQueueSize = 512; /** * ӳȡ쳣 */ private Set> delayCancelExceptions = new HashSet>(); public DefaultTylooRecoverConfiguration() { /** * SocketTimeoutException ָʱС Socket ʱʱ䣬ʱָԶ̲ȡع * Զ̲´θʱΪֹʧܣ׳ OptimisticLockException TylooInterceptor ʱȡع * ܻͶʱȡعͻͳһʱ * */ delayCancelExceptions.add(OptimisticLockException.class); /** * try ׶ΣزߵԶ̲( Զ̷ DubboHttp )Զ̲ try ׶εķ߼ִʱϳ Socket ȴʱ SocketTimeoutException * ִعԶ̲ try ķδִɣܵ cancel ķʵδִ( try ķδִɣݿ񡾷 TCC δύ * cancel ķȡʱδ·ʵδִУ try ķִύݿ񡾷 TCC 񡿣Ϊ )ݲһ¡ * ָʱȡعʱԶ̲ߵ try ķδǿܷݲһ¡ * */ delayCancelExceptions.add(SocketTimeoutException.class); } @Override public void setDelayCancelExceptions(Set> delayCancelExceptions) { this.delayCancelExceptions.addAll(delayCancelExceptions); } } ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/recover/RecoverScheduledJob.java ================================================ package io.tyloo.spring.recover; import io.tyloo.SystemException; import io.tyloo.recover.TylooTransactionRecovery; import io.tyloo.support.TransactionConfigurator; import org.quartz.Scheduler; import org.springframework.scheduling.quartz.CronTriggerFactoryBean; import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean; /* * * ָʱ * Quartz ʵֵȣִָ * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:15 2019/10/29 * */ public class RecoverScheduledJob { /** * ָ */ private TylooTransactionRecovery tylooTransactionRecovery; /** * עTCC. */ private TransactionConfigurator transactionConfigurator; /** * ָ(עorg.springframework.scheduling.quartz.SchedulerFactoryBeanʵ) */ private Scheduler scheduler; /** * ʼSpringʱִ. */ public void init() { try { // MethodInvokingJobDetailFactoryBean ɾֻҪָijijڴʱָָ MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean(); // ָӦĵöʵκνӿ jobDetail.setTargetObject(tylooTransactionRecovery); // ָtargetObjectijķ(˴TransactionRecoveryеstartRecover) jobDetail.setTargetMethod("startRecover"); // jobDetail.setName("transactionRecoveryJob"); // Ƿ񲢷ִУĬDzִеģʱáconcurrentΪfalseܴܿ⣬ҼʽС׸, // Ϊfalseʾһִٿµ jobDetail.setConcurrent(false); jobDetail.afterPropertiesSet(); // ࣬ñָĵָĴ // ฺspringдһIDӦSchedulerFactoryBeanԵListбãܱ֤ijָ CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean(); // ô cronTrigger.setBeanName("transactionRecoveryCronTrigger"); // ͨȡָʱ cronTrigger.setCronExpression(transactionConfigurator.getTylooRecoverConfiguration().getCronExpression()); cronTrigger.setJobDetail(jobDetail.getObject()); cronTrigger.afterPropertiesSet(); // õ scheduler.scheduleJob(jobDetail.getObject(), cronTrigger.getObject()); // scheduler.start(); } catch (Exception e) { throw new SystemException(e); } } public void setTylooTransactionRecovery(TylooTransactionRecovery tylooTransactionRecovery) { this.tylooTransactionRecovery = tylooTransactionRecovery; } public Scheduler getScheduler() { return scheduler; } public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; } public void setTransactionConfigurator(TransactionConfigurator transactionConfigurator) { this.transactionConfigurator = transactionConfigurator; } } ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/repository/SpringJdbcTransactionRepository.java ================================================ package io.tyloo.spring.repository; import io.tyloo.repository.JdbcTransactionRepository; import org.springframework.jdbc.datasource.DataSourceUtils; import java.sql.Connection; /* * * SpringJdbc * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:18 2019/10/30 * */ public class SpringJdbcTransactionRepository extends JdbcTransactionRepository { @Override protected Connection getConnection() { return DataSourceUtils.getConnection(this.getDataSource()); } @Override protected void releaseConnection(Connection con) { DataSourceUtils.releaseConnection(con, this.getDataSource()); } } ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/support/SpringBeanFactory.java ================================================ package io.tyloo.spring.support; import io.tyloo.support.BeanFactory; import io.tyloo.support.FactoryBuilder; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import java.util.Map; /* * * Spring Bean * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:12 2019/11/11 * */ public class SpringBeanFactory implements BeanFactory, ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; FactoryBuilder.registerBeanFactory(this); } @Override public boolean isFactoryOf(Class clazz) { Map map = this.applicationContext.getBeansOfType(clazz); return map.size() > 0; } @Override public T getBean(Class var1) { return this.applicationContext.getBean(var1); } } ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/support/SpringPostProcessor.java ================================================ package io.tyloo.spring.support; import io.tyloo.support.BeanFactory; import io.tyloo.support.FactoryBuilder; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; /* * * Spring ô * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:13 2019/11/18 * */ public class SpringPostProcessor implements ApplicationListener { /** * Springʱ. */ @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext(); if (applicationContext.getParent() == null) { FactoryBuilder.registerBeanFactory(applicationContext.getBean(BeanFactory.class)); } } } ================================================ FILE: tyloo-spring/src/main/java/io/tyloo/spring/support/SpringTransactionConfigurator.java ================================================ package io.tyloo.spring.support; import io.tyloo.TransactionManager; import io.tyloo.TransactionRepository; import io.tyloo.recover.TylooRecoverConfiguration; import io.tyloo.repository.CachableTransactionRepository; import io.tyloo.spring.recover.DefaultTylooRecoverConfiguration; import io.tyloo.support.TransactionConfigurator; import org.springframework.beans.factory.annotation.Autowired; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /* * * Spring * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 20:20 2019/12/4 * */ public class SpringTransactionConfigurator implements TransactionConfigurator { private static volatile ExecutorService executorService = null; @Autowired private TransactionRepository transactionRepository; @Autowired(required = false) private TylooRecoverConfiguration tylooRecoverConfiguration = DefaultTylooRecoverConfiguration.INSTANCE; private TransactionManager transactionManager; public void init() { transactionManager = new TransactionManager(); transactionManager.setTransactionRepository(transactionRepository); if (executorService == null) { Executors.defaultThreadFactory(); synchronized (SpringTransactionConfigurator.class) { if (executorService == null) { executorService = new ThreadPoolExecutor( tylooRecoverConfiguration.getAsyncTerminateThreadCorePoolSize(), tylooRecoverConfiguration.getAsyncTerminateThreadMaxPoolSize(), 5L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(tylooRecoverConfiguration.getAsyncTerminateThreadWorkQueueSize()), new ThreadFactory() { final AtomicInteger poolNumber = new AtomicInteger(1); final ThreadGroup group; final AtomicInteger threadNumber = new AtomicInteger(1); final String namePrefix; { SecurityManager securityManager = System.getSecurityManager(); this.group = securityManager != null ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup(); this.namePrefix = "tcc-async-terminate-pool-" + poolNumber.getAndIncrement() + "-thread-"; } @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(this.group, runnable, this.namePrefix + this.threadNumber.getAndIncrement(), 0L); if (thread.isDaemon()) { thread.setDaemon(false); } if (thread.getPriority() != 5) { thread.setPriority(5); } return thread; } }, new ThreadPoolExecutor.CallerRunsPolicy()); } } } transactionManager.setExecutorService(executorService); if (transactionRepository instanceof CachableTransactionRepository) { ((CachableTransactionRepository) transactionRepository).setExpireDuration(tylooRecoverConfiguration.getRecoverDuration()); } } @Override public TransactionManager getTransactionManager() { return transactionManager; } @Override public TransactionRepository getTransactionRepository() { return transactionRepository; } @Override public TylooRecoverConfiguration getTylooRecoverConfiguration() { return tylooRecoverConfiguration; } } ================================================ FILE: tyloo-spring/src/main/resources/tyloo.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/pom.xml ================================================ tyloo io.tyloo 1.1.0 4.0.0 tyloo-tutorial-sample pom tyloo-dubbo-sample tyloo-http-sample tyloo-server-sample tyloo-sample-domain ================================================ FILE: tyloo-tutorial-sample/src/tylooSampledb/create_db_cap.sql ================================================ CREATE DATABASE `TCC_CAP` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC_CAP; CREATE TABLE `CAP_CAPITAL_ACCOUNT` ( `CAPITAL_ACCOUNT_ID` int(11) NOT NULL AUTO_INCREMENT, `BALANCE_AMOUNT` decimal(10,0) DEFAULT NULL, `USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`CAPITAL_ACCOUNT_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE `CAP_TRADE_ORDER` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `SELF_USER_ID` bigint(11) DEFAULT NULL, `OPPOSITE_USER_ID` bigint(11) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) NOT NULL, `AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `UX_MERCHANT_ORDER_NO` (`MERCHANT_ORDER_NO`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `CAP_CAPITAL_ACCOUNT`(CAPITAL_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID) VALUE (1,10000,1000); INSERT INTO `CAP_CAPITAL_ACCOUNT`(CAPITAL_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID) VALUE (2,10000,2000); ================================================ FILE: tyloo-tutorial-sample/src/tylooSampledb/create_db_ord.sql ================================================ CREATE DATABASE `TCC_ORD` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC_ORD; CREATE TABLE `ORD_ORDER` ( `ORDER_ID` int(11) NOT NULL AUTO_INCREMENT, `PAYER_USER_ID` int(11) DEFAULT NULL, `PAYEE_USER_ID` int(11) DEFAULT NULL, `RED_PACKET_PAY_AMOUNT` decimal(10,0) DEFAULT NULL, `CAPITAL_PAY_AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) NOT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`ORDER_ID`), UNIQUE KEY `MERCHANT_ORDER_NO_UNIQUE` (`MERCHANT_ORDER_NO`) ) ENGINE=InnoDB AUTO_INCREMENT=1188 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_ORDER_LINE` ( `ORDER_LINE_ID` int(11) NOT NULL AUTO_INCREMENT, `PRODUCT_ID` int(11) DEFAULT NULL, `QUANTITY` decimal(10,0) DEFAULT NULL, `UNIT_PRICE` decimal(10,0) DEFAULT NULL, PRIMARY KEY (`ORDER_LINE_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_SHOP` ( `SHOP_ID` int(11) NOT NULL, `OWNER_USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`SHOP_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_PRODUCT`( `PRODUCT_ID` int(11) NOT NULL, `SHOP_ID` int(11) NOT NULL, `PRODUCT_NAME` VARCHAR(64) DEFAULT NULL , `PRICE` DECIMAL(10,0) DEFAULT NULL, PRIMARY KEY (`PRODUCT_ID`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `ORD_SHOP` (`SHOP_ID`,`OWNER_USER_ID`) VALUES (1,1000); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (1,1,'IPhone6S',5288); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (2,1,'MAC Pro',10288); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (3,1,'IWatch',2288); ================================================ FILE: tyloo-tutorial-sample/src/tylooSampledb/create_db_red.sql ================================================ CREATE DATABASE `TCC_RED` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC_RED; CREATE TABLE `RED_RED_PACKET_ACCOUNT` ( `RED_PACKET_ACCOUNT_ID` int(11) NOT NULL, `BALANCE_AMOUNT` decimal(10,0) DEFAULT NULL, `USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`RED_PACKET_ACCOUNT_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `RED_TRADE_ORDER` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `SELF_USER_ID` bigint(11) DEFAULT NULL, `OPPOSITE_USER_ID` bigint(11) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) NOT NULL, `AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `MERCHANT_ORDER_NO_UNIQUE` (`MERCHANT_ORDER_NO`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `RED_RED_PACKET_ACCOUNT` (`RED_PACKET_ACCOUNT_ID`,`BALANCE_AMOUNT`,`USER_ID`) VALUES (1,950,1000); INSERT INTO `RED_RED_PACKET_ACCOUNT` (`RED_PACKET_ACCOUNT_ID`,`BALANCE_AMOUNT`,`USER_ID`) VALUES (2,500,2000); ================================================ FILE: tyloo-tutorial-sample/src/tylooSampledb/create_db_tcc.sql ================================================ CREATE DATABASE `TCC` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC; CREATE TABLE `TCC_TRANSACTION_CAP` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_CAP` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; CREATE TABLE `TCC_TRANSACTION_ORD` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_ORD` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; CREATE TABLE `TCC_TRANSACTION_RED` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_RED` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; CREATE TABLE `TCC_TRANSACTION_UT` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_UT` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/pom.xml ================================================ tyloo-tutorial-sample io.tyloo 1.1.0 4.0.0 tyloo-dubbo-sample pom tyloo-dubbo-capital tyloo-dubbo-capital-api tyloo-dubbo-order tyloo-dubbo-redpacket tyloo-dubbo-redpacket-api ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/src/main/tylooSampledb/create_db_cap.sql ================================================ CREATE DATABASE `tcc_cap` /*!40100 DEFAULT CHARACTER SET utf8 */; use tcc_cap; CREATE TABLE `CAP_CAPITAL_ACCOUNT` ( `CAPITAL_ACCOUNT_ID` int(11) NOT NULL AUTO_INCREMENT, `BALANCE_AMOUNT` decimal(10,0) DEFAULT NULL, `USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`CAPITAL_ACCOUNT_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE `CAP_TRADE_ORDER` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `SELF_USER_ID` bigint(11) DEFAULT NULL, `OPPOSITE_USER_ID` bigint(11) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) DEFAULT NULL, `AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `CAP_CAPITAL_ACCOUNT`(CAPITAL_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID) VALUE (1,10000,1000); INSERT INTO `CAP_CAPITAL_ACCOUNT`(CAPITAL_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID) VALUE (2,10000,2000); ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/src/main/tylooSampledb/create_db_ord.sql ================================================ CREATE DATABASE `TCC_ORD` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC_ORD; CREATE TABLE `ORD_ORDER` ( `ORDER_ID` int(11) NOT NULL AUTO_INCREMENT, `PAYER_USER_ID` int(11) DEFAULT NULL, `PAYEE_USER_ID` int(11) DEFAULT NULL, `RED_PACKET_PAY_AMOUNT` decimal(10,0) DEFAULT NULL, `CAPITAL_PAY_AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) DEFAULT NULL, PRIMARY KEY (`ORDER_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_ORDER_LINE` ( `ORDER_LINE_ID` int(11) NOT NULL AUTO_INCREMENT, `PRODUCT_ID` int(11) DEFAULT NULL, `QUANTITY` decimal(10,0) DEFAULT NULL, `UNIT_PRICE` decimal(10,0) DEFAULT NULL, PRIMARY KEY (`ORDER_LINE_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_SHOP` ( `SHOP_ID` int(11) NOT NULL, `OWNER_USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`SHOP_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_PRODUCT`( `PRODUCT_ID` int(11) NOT NULL, `SHOP_ID` int(11) NOT NULL, `PRODUCT_NAME` VARCHAR(64) DEFAULT NULL , `PRICE` DECIMAL(10,0) DEFAULT NULL, PRIMARY KEY (`PRODUCT_ID`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `ORD_SHOP` (`SHOP_ID`,`OWNER_USER_ID`) VALUES (1,1000); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (1,1,'IPhone6S',5288); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (2,1,'MAC Pro',10288); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (3,1,'IWatch',2288); ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/src/main/tylooSampledb/create_db_red.sql ================================================ CREATE DATABASE `tcc_red` /*!40100 DEFAULT CHARACTER SET utf8 */; use tcc_red; CREATE TABLE `RED_RED_PACKET_ACCOUNT` ( `RED_PACKET_ACCOUNT_ID` int(11) NOT NULL, `BALANCE_AMOUNT` decimal(10,0) DEFAULT NULL, `USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`RED_PACKET_ACCOUNT_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `RED_TRADE_ORDER` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `SELF_USER_ID` BIGINT(11) DEFAULT NULL, `OPPOSITE_USER_ID` BIGINT(11) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) DEFAULT NULL, `AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `RED_RED_PACKET_ACCOUNT` (`RED_PACKET_ACCOUNT_ID`,`BALANCE_AMOUNT`,`USER_ID`) VALUES (1,950,1000); INSERT INTO `RED_RED_PACKET_ACCOUNT` (`RED_PACKET_ACCOUNT_ID`,`BALANCE_AMOUNT`,`USER_ID`) VALUES (2,500,2000); ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/src/main/tylooSampledb/create_db_tcc.sql ================================================ CREATE DATABASE `TCC` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC; CREATE TABLE `TCC_TRANSACTION_CAP` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `TCC_TRANSACTION_ORD` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `TCC_TRANSACTION_RED` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `TCC_TRANSACTION_UT` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/pom.xml ================================================ tyloo-dubbo-sample io.tyloo 1.1.0 4.0.0 tyloo-dubbo-capital war io.tyloo tyloo-dubbo-capital-api ${project.version} io.tyloo tyloo-sample-captial ${project.version} io.tyloo tyloo-dubbo ${project.version} io.tyloo tyloo-spring ${project.version} org.springframework spring-web org.springframework spring-webmvc org.springframework spring-context ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/java/io/tyloo/sample/dubbo/capital/service/CapitalAccountServiceImpl.java ================================================ package io.tyloo.sample.dubbo.capital.service; import io.tyloo.sample.capital.domain.repository.CapitalAccountRepository; import io.tyloo.sample.dubbo.capital.api.CapitalAccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; @Service("capitalAccountService") public class CapitalAccountServiceImpl implements CapitalAccountService { @Autowired CapitalAccountRepository capitalAccountRepository; @Override public BigDecimal getCapitalAccountByUserId(long userId) { return capitalAccountRepository.findByUserId(userId).getBalanceAmount(); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/java/io/tyloo/sample/dubbo/capital/service/CapitalTradeOrderServiceImpl.java ================================================ package io.tyloo.sample.dubbo.capital.service; import io.tyloo.sample.capital.domain.entity.CapitalAccount; import io.tyloo.sample.capital.domain.entity.TradeOrder; import io.tyloo.sample.capital.domain.repository.CapitalAccountRepository; import io.tyloo.sample.capital.domain.repository.TradeOrderRepository; import io.tyloo.sample.dubbo.capital.api.CapitalTradeOrderService; import io.tyloo.sample.dubbo.capital.api.dto.CapitalTradeOrderDto; import org.apache.commons.lang3.time.DateFormatUtils; import io.tyloo.api.Tyloo; import io.tyloo.dubbo.context.DubboTransactionContextEditor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Calendar; @Service("capitalTradeOrderService") public class CapitalTradeOrderServiceImpl implements CapitalTradeOrderService { @Autowired CapitalAccountRepository capitalAccountRepository; @Autowired TradeOrderRepository tradeOrderRepository; @Override @Tyloo(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = DubboTransactionContextEditor.class) @Transactional public String record(CapitalTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000L); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("capital try record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder foundTradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if trade order has been recorded, if yes, return success directly. if (foundTradeOrder == null) { TradeOrder tradeOrder = new TradeOrder( tradeOrderDto.getSelfUserId(), tradeOrderDto.getOppositeUserId(), tradeOrderDto.getMerchantOrderNo(), tradeOrderDto.getAmount() ); try { tradeOrderRepository.insert(tradeOrder); CapitalAccount transferFromAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); transferFromAccount.transferFrom(tradeOrderDto.getAmount()); capitalAccountRepository.save(transferFromAccount); } catch (DataIntegrityViolationException e) { //this exception may happen when insert trade order concurrently, if happened, ignore this insert operation. } } return "success"; } @Transactional public void confirmRecord(CapitalTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000L); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("capital confirm record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency. if (tradeOrder != null && tradeOrder.getStatus().equals("DRAFT")) { tradeOrder.confirm(); tradeOrderRepository.update(tradeOrder); CapitalAccount transferToAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getOppositeUserId()); transferToAccount.transferTo(tradeOrderDto.getAmount()); capitalAccountRepository.save(transferToAccount); } } @Transactional public void cancelRecord(CapitalTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("capital cancel record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency. if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) { tradeOrder.cancel(); tradeOrderRepository.update(tradeOrder); CapitalAccount capitalAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); capitalAccount.cancelTransfer(tradeOrderDto.getAmount()); capitalAccountRepository.save(capitalAccount); } } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/resources/config/spring/local/appcontext-service-provider.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/resources/config/spring/local/appcontext-service-tcc.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/resources/config/spring/local/appcontext-web-servlet.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/resources/sample-dubbo-capital.properties ================================================ dubbo.port=2881 zookeeper.address=127.0.0.1:2181 zookeeper.session.timeout=8000 zookeeper.connect.timeout=2000 ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/resources/tccjdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/webapp/WEB-INF/web.xml ================================================ Sample Dubbo Capital contextConfigLocation classpath*:config/spring/local/appcontext-*.xml,classpath:tyloo.xml,classpath:tyloo-dubbo.xml log4jConfigLocation classpath:log/log4j.xml log4jRefreshInterval 60000 org.springframework.web.util.Log4jConfigListener org.springframework.web.context.ContextLoaderListener characterEncodingFilter Character Encoding Filter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:config/spring/local/appcontext-web-servlet.xml 1 springmvc / ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital/src/main/webapp/index.jsp ================================================ hello tcc transacton dubbo sample capital ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital-api/pom.xml ================================================ tyloo-dubbo-sample io.tyloo 1.1.0 4.0.0 tyloo-dubbo-capital-api io.tyloo tyloo-api ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital-api/src/main/java/io/tyloo/sample/dubbo/capital/api/CapitalAccountService.java ================================================ package io.tyloo.sample.dubbo.capital.api; import java.math.BigDecimal; public interface CapitalAccountService { BigDecimal getCapitalAccountByUserId(long userId); } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital-api/src/main/java/io/tyloo/sample/dubbo/capital/api/CapitalTradeOrderService.java ================================================ package io.tyloo.sample.dubbo.capital.api; import io.tyloo.sample.dubbo.capital.api.dto.CapitalTradeOrderDto; import io.tyloo.api.Tyloo; public interface CapitalTradeOrderService { @Tyloo public String record(CapitalTradeOrderDto tradeOrderDto); } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-capital-api/src/main/java/io/tyloo/sample/dubbo/capital/api/dto/CapitalTradeOrderDto.java ================================================ package io.tyloo.sample.dubbo.capital.api.dto; import java.io.Serializable; import java.math.BigDecimal; public class CapitalTradeOrderDto implements Serializable { private static final long serialVersionUID = 6627401903410124642L; private long selfUserId; private long oppositeUserId; private String orderTitle; private String merchantOrderNo; private BigDecimal amount; public long getSelfUserId() { return selfUserId; } public void setSelfUserId(long selfUserId) { this.selfUserId = selfUserId; } public long getOppositeUserId() { return oppositeUserId; } public void setOppositeUserId(long oppositeUserId) { this.oppositeUserId = oppositeUserId; } public String getOrderTitle() { return orderTitle; } public void setOrderTitle(String orderTitle) { this.orderTitle = orderTitle; } public String getMerchantOrderNo() { return merchantOrderNo; } public void setMerchantOrderNo(String merchantOrderNo) { this.merchantOrderNo = merchantOrderNo; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/pom.xml ================================================ tyloo-dubbo-sample io.tyloo 1.1.0 4.0.0 tyloo-dubbo-order war log4j log4j io.tyloo tyloo-dubbo ${project.version} io.tyloo tyloo-dubbo-capital-api ${project.version} io.tyloo tyloo-dubbo-redpacket-api ${project.version} io.tyloo tyloo-sample-order ${project.version} io.tyloo tyloo-spring ${project.version} com.mchange c3p0 mysql mysql-connector-java org.mybatis mybatis-spring org.mybatis mybatis org.aspectj aspectjrt org.aspectj aspectjweaver org.springframework spring-web org.springframework spring-webmvc org.springframework spring-context org.springframework spring-beans org.springframework spring-jdbc org.apache.commons commons-lang3 org.freemarker freemarker ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/java/io/tyloo/sample/dubbo/order/service/AccountServiceImpl.java ================================================ package io.tyloo.sample.dubbo.order.service; import io.tyloo.sample.dubbo.capital.api.CapitalAccountService; import io.tyloo.sample.dubbo.redpacket.api.RedPacketAccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; @Service("accountService") public class AccountServiceImpl { @Autowired RedPacketAccountService redPacketAccountService; @Autowired CapitalAccountService capitalAccountService; public BigDecimal getRedPacketAccountByUserId(long userId){ return redPacketAccountService.getRedPacketAccountByUserId(userId); } public BigDecimal getCapitalAccountByUserId(long userId){ return capitalAccountService.getCapitalAccountByUserId(userId); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/java/io/tyloo/sample/dubbo/order/service/PaymentServiceImpl.java ================================================ package io.tyloo.sample.dubbo.order.service; import io.tyloo.sample.dubbo.capital.api.CapitalTradeOrderService; import io.tyloo.sample.dubbo.capital.api.dto.CapitalTradeOrderDto; import io.tyloo.sample.dubbo.redpacket.api.RedPacketTradeOrderService; import io.tyloo.sample.dubbo.redpacket.api.dto.RedPacketTradeOrderDto; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.repository.OrderRepository; import org.apache.commons.lang3.time.DateFormatUtils; import io.tyloo.api.Tyloo; import io.tyloo.api.UniqueIdentity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.net.SocketTimeoutException; import java.util.Calendar; @Service public class PaymentServiceImpl { @Autowired CapitalTradeOrderService capitalTradeOrderService; @Autowired RedPacketTradeOrderService redPacketTradeOrderService; @Autowired OrderRepository orderRepository; @Tyloo(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment", asyncConfirm = false, delayCancelExceptions = {SocketTimeoutException.class, org.apache.dubbo.remoting.TimeoutException.class}) public void makePayment(@UniqueIdentity String orderNo, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) { System.out.println("order try make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); Order order = orderRepository.findByMerchantOrderNo(orderNo); //check if the order status is DRAFT, if no, means that another call makePayment for the same order happened, ignore this call makePayment. if (order.getStatus().equals("DRAFT")) { order.pay(redPacketPayAmount, capitalPayAmount); try { orderRepository.updateOrder(order); } catch (OptimisticLockingFailureException e) { //ignore the concurrently update order exception, ensure idempotency. } } String result = capitalTradeOrderService.record(buildCapitalTradeOrderDto(order)); String result2 = redPacketTradeOrderService.record(buildRedPacketTradeOrderDto(order)); } public void confirmMakePayment(String orderNo, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("order confirm make payment called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); Order foundOrder = orderRepository.findByMerchantOrderNo(orderNo); //check if the trade order status is PAYING, if no, means another call confirmMakePayment happened, return directly, ensure idempotency. if (foundOrder != null && foundOrder.getStatus().equals("PAYING")) { foundOrder.confirm(); orderRepository.updateOrder(foundOrder); } } public void cancelMakePayment(String orderNo, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("order cancel make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); Order foundOrder = orderRepository.findByMerchantOrderNo(orderNo); //check if the trade order status is PAYING, if no, means another call cancelMakePayment happened, return directly, ensure idempotency. if (foundOrder != null && foundOrder.getStatus().equals("PAYING")) { foundOrder.cancelPayment(); orderRepository.updateOrder(foundOrder); } } private CapitalTradeOrderDto buildCapitalTradeOrderDto(Order order) { CapitalTradeOrderDto tradeOrderDto = new CapitalTradeOrderDto(); tradeOrderDto.setAmount(order.getCapitalPayAmount()); tradeOrderDto.setMerchantOrderNo(order.getMerchantOrderNo()); tradeOrderDto.setSelfUserId(order.getPayerUserId()); tradeOrderDto.setOppositeUserId(order.getPayeeUserId()); tradeOrderDto.setOrderTitle(String.format("order no:%s", order.getMerchantOrderNo())); return tradeOrderDto; } private RedPacketTradeOrderDto buildRedPacketTradeOrderDto(Order order) { RedPacketTradeOrderDto tradeOrderDto = new RedPacketTradeOrderDto(); tradeOrderDto.setAmount(order.getRedPacketPayAmount()); tradeOrderDto.setMerchantOrderNo(order.getMerchantOrderNo()); tradeOrderDto.setSelfUserId(order.getPayerUserId()); tradeOrderDto.setOppositeUserId(order.getPayeeUserId()); tradeOrderDto.setOrderTitle(String.format("order no:%s", order.getMerchantOrderNo())); return tradeOrderDto; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/java/io/tyloo/sample/dubbo/order/service/PlaceOrderServiceImpl.java ================================================ package io.tyloo.sample.dubbo.order.service; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.entity.Shop; import io.tyloo.sample.order.domain.repository.ShopRepository; import io.tyloo.sample.order.domain.service.OrderServiceImpl; import org.apache.commons.lang3.tuple.Pair; import io.tyloo.CancellingException; import io.tyloo.ConfirmingException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; @Service public class PlaceOrderServiceImpl { @Autowired ShopRepository shopRepository; @Autowired OrderServiceImpl orderService; @Autowired PaymentServiceImpl paymentService; public String placeOrder(long payerUserId, long shopId, List> productQuantities, final BigDecimal redPacketPayAmount) { Shop shop = shopRepository.findById(shopId); final Order order = orderService.createOrder(payerUserId, shop.getOwnerUserId(), productQuantities); Boolean result = false; try { // ExecutorService executorService = Executors.newFixedThreadPool(2); // Future future1 = executorService.submit(new Runnable() { // @Override // public void run() { paymentService.makePayment(order.getMerchantOrderNo(), redPacketPayAmount, order.getTotalAmount().subtract(redPacketPayAmount)); // } // }); // Future future2 = executorService.submit(new Runnable() { // @Override // public void run() { // paymentService.makePayment(order.getMerchantOrderNo(), order, redPacketPayAmount, order.getTotalAmount().subtract(redPacketPayAmount)); // } // }); // // future1.get(); // future2.get(); } catch (ConfirmingException confirmingException) { //exception throws with the tcc transaction status is CONFIRMING, //when tcc transaction is confirming status, // the tcc transaction recovery will try to confirm the whole transaction to ensure eventually consistent. result = true; } catch (CancellingException cancellingException) { //exception throws with the tcc transaction status is CANCELLING, //when tcc transaction is under CANCELLING status, // the tcc transaction recovery will try to cancel the whole transaction to ensure eventually consistent. } catch (Throwable e) { //other exceptions throws at TRYING stage. //you can retry or cancel the operation. e.printStackTrace(); } return order.getMerchantOrderNo(); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/java/io/tyloo/sample/dubbo/order/web/controller/OrderController.java ================================================ package io.tyloo.sample.dubbo.order.web.controller; import io.tyloo.sample.dubbo.order.service.AccountServiceImpl; import io.tyloo.sample.dubbo.order.service.PlaceOrderServiceImpl; import io.tyloo.sample.dubbo.order.web.controller.vo.PlaceOrderRequest; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.entity.Product; import io.tyloo.sample.order.domain.repository.ProductRepository; import io.tyloo.sample.order.domain.service.OrderServiceImpl; import org.apache.commons.lang3.tuple.ImmutablePair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.RedirectView; import java.math.BigDecimal; import java.security.InvalidParameterException; import java.util.List; @Controller @RequestMapping("") public class OrderController { @Autowired PlaceOrderServiceImpl placeOrderService; @Autowired ProductRepository productRepository; @Autowired AccountServiceImpl accountService; @Autowired OrderServiceImpl orderService; @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView index() { ModelAndView mv = new ModelAndView("/index"); return mv; } @RequestMapping(value = "/user/{userId}/shop/{shopId}", method = RequestMethod.GET) public ModelAndView getProductsInShop(@PathVariable long userId, @PathVariable long shopId) { List products = productRepository.findByShopId(shopId); ModelAndView mv = new ModelAndView("/shop"); mv.addObject("products", products); mv.addObject("userId", userId); mv.addObject("shopId", shopId); return mv; } @RequestMapping(value = "/user/{userId}/shop/{shopId}/product/{productId}/confirm", method = RequestMethod.GET) public ModelAndView productDetail(@PathVariable long userId, @PathVariable long shopId, @PathVariable long productId) { ModelAndView mv = new ModelAndView("product_detail"); mv.addObject("capitalAmount", accountService.getCapitalAccountByUserId(userId)); mv.addObject("redPacketAmount", accountService.getRedPacketAccountByUserId(userId)); mv.addObject("product", productRepository.findById(productId)); mv.addObject("userId", userId); mv.addObject("shopId", shopId); return mv; } @RequestMapping(value = "/placeorder", method = RequestMethod.POST) public RedirectView placeOrder(@RequestParam String redPacketPayAmount, @RequestParam long shopId, @RequestParam long payerUserId, @RequestParam long productId) { PlaceOrderRequest request = buildRequest(redPacketPayAmount, shopId, payerUserId, productId); String merchantOrderNo = placeOrderService.placeOrder(request.getPayerUserId(), request.getShopId(), request.getProductQuantities(), request.getRedPacketPayAmount()); return new RedirectView("/payresult/" + merchantOrderNo); } @RequestMapping(value = "/payresult/{merchantOrderNo}", method = RequestMethod.GET) public ModelAndView getPayResult(@PathVariable String merchantOrderNo) { ModelAndView mv = new ModelAndView("pay_success"); String payResultTip = null; Order foundOrder = orderService.findOrderByMerchantOrderNo(merchantOrderNo); payResultTip = foundOrder.getStatus(); mv.addObject("payResult", payResultTip); mv.addObject("capitalAmount", accountService.getCapitalAccountByUserId(foundOrder.getPayerUserId())); mv.addObject("redPacketAmount", accountService.getRedPacketAccountByUserId(foundOrder.getPayerUserId())); return mv; } private PlaceOrderRequest buildRequest(String redPacketPayAmount, long shopId, long payerUserId, long productId) { BigDecimal redPacketPayAmountInBigDecimal = new BigDecimal(redPacketPayAmount); if (redPacketPayAmountInBigDecimal.compareTo(BigDecimal.ZERO) < 0) { throw new InvalidParameterException("invalid red packet amount :" + redPacketPayAmount); } PlaceOrderRequest request = new PlaceOrderRequest(); request.setPayerUserId(payerUserId); request.setShopId(shopId); request.setRedPacketPayAmount(new BigDecimal(redPacketPayAmount)); request.getProductQuantities().add(new ImmutablePair(productId, 1)); return request; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/java/io/tyloo/sample/dubbo/order/web/controller/vo/PlaceOrderRequest.java ================================================ package io.tyloo.sample.dubbo.order.web.controller.vo; import org.apache.commons.lang3.tuple.Pair; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; public class PlaceOrderRequest { private long payerUserId; private long shopId; private BigDecimal redPacketPayAmount; private List> productQuantities = new ArrayList>(); public long getPayerUserId() { return payerUserId; } public void setPayerUserId(long payerUserId) { this.payerUserId = payerUserId; } public long getShopId() { return shopId; } public void setShopId(long shopId) { this.shopId = shopId; } public BigDecimal getRedPacketPayAmount() { return redPacketPayAmount; } public void setRedPacketPayAmount(BigDecimal redPacketPayAmount) { this.redPacketPayAmount = redPacketPayAmount; } public List> getProductQuantities() { return productQuantities; } public void setProductQuantities(List> productQuantities) { this.productQuantities = productQuantities; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/resources/config/spring/local/appcontext-service-dubbo.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/resources/config/spring/local/appcontext-service-tcc.xml ================================================ org.apache.dubbo.remoting.TimeoutException ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/resources/config/spring/local/appcontext-web-servlet.xml ================================================ json=application/json html=text/html org.springframework.web.servlet.view.freemarker.FreeMarkerView /WEB-INF/ftl/ 5 UTF-8 UTF-8 yyyy-MM-dd HH:mm:ss HH:mm:ss 0.#### true,false true auto_detect UTF-8 true ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/resources/sample-dubbo-order.properties ================================================ zookeeper.address=127.0.0.1:2181 zookeeper.session.timeout=8000 zookeeper.connect.timeout=2000 ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/resources/tccjdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=2000 ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/webapp/WEB-INF/ftl/index.ftl ================================================ [#ftl ] tcc transacton dubbo sample order

sample说明:
打开下面商品列表链接,选择一个商品购买,输入红包支付金额,进行支付,系统将使用红包+资金账户转账支付。
支付成功后,各个project会打印如下日志:

   sample-dubbo-order:
     order try make payment called
     order confirm make payment called

   sample-dubbo-capital:
     capital try record called
     capital confirm record called

   sample-dubbo-redpacket:
     red packet try record called
     red packet confirm record called

商品列表链接

================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/webapp/WEB-INF/ftl/pay_success.ftl ================================================ [#ftl ] 支付结果

支付状态:${payResult}

剩余可用账户余额: ${capitalAmount?string("0.00")}元

剩余可用红包余额: ${redPacketAmount?string("0.00")}元

================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/webapp/WEB-INF/ftl/product_detail.ftl ================================================ [#ftl ] 订单详情

名称: ${product.productName}

价格: ${product.price?string("0.00")}元

可用账户余额: ${capitalAmount?string("0.00")}元

可用红包余额: ${redPacketAmount?string("0.00")}元

红包金额:   
================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/webapp/WEB-INF/ftl/shop.ftl ================================================ [#ftl ] 商品列表
    [#if products?size > 0] [#list products as product]
  • ${product.productName}(${product.price?string("0.00")})    购买

  • [/#list] [/#if]
================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-order/src/main/webapp/WEB-INF/web.xml ================================================ Sample Dubbo Order contextConfigLocation classpath*:config/spring/local/appcontext-*.xml,classpath:tyloo.xml,classpath:tyloo-dubbo.xml log4jConfigLocation classpath:log/log4j.xml log4jRefreshInterval 60000 org.springframework.web.util.Log4jConfigListener org.springframework.web.context.ContextLoaderListener characterEncodingFilter Character Encoding Filter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:config/spring/local/appcontext-web-servlet.xml 1 springmvc /* ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/pom.xml ================================================ tyloo-dubbo-sample io.tyloo 1.1.0 4.0.0 tyloo-dubbo-redpacket war io.tyloo tyloo-dubbo-redpacket-api ${project.version} io.tyloo tyloo-dubbo ${project.version} io.tyloo tyloo-sample-redpacket ${project.version} io.tyloo tyloo-spring ${project.version} org.springframework spring-context log4j log4j com.mchange c3p0 mysql mysql-connector-java org.mybatis mybatis-spring org.mybatis mybatis org.aspectj aspectjrt org.aspectj aspectjweaver org.springframework spring-web org.springframework spring-webmvc org.springframework spring-context org.springframework spring-beans org.springframework spring-jdbc org.apache.commons commons-lang3 ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/java/io/tyloo/sample/dubbo/redpacket/service/RedPacketAccountServiceImpl.java ================================================ package io.tyloo.sample.dubbo.redpacket.service; import io.tyloo.sample.dubbo.redpacket.api.RedPacketAccountService; import io.tyloo.sample.redpacket.domain.repository.RedPacketAccountRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; @Service("redPacketAccountService") public class RedPacketAccountServiceImpl implements RedPacketAccountService { @Autowired RedPacketAccountRepository redPacketAccountRepository; @Override public BigDecimal getRedPacketAccountByUserId(long userId) { return redPacketAccountRepository.findByUserId(userId).getBalanceAmount(); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/java/io/tyloo/sample/dubbo/redpacket/service/RedPacketTradeOrderServiceImpl.java ================================================ package io.tyloo.sample.dubbo.redpacket.service; import io.tyloo.sample.dubbo.redpacket.api.RedPacketTradeOrderService; import io.tyloo.sample.dubbo.redpacket.api.dto.RedPacketTradeOrderDto; import io.tyloo.sample.redpacket.domain.entity.RedPacketAccount; import io.tyloo.sample.redpacket.domain.entity.TradeOrder; import io.tyloo.sample.redpacket.domain.repository.RedPacketAccountRepository; import io.tyloo.sample.redpacket.domain.repository.TradeOrderRepository; import org.apache.commons.lang3.time.DateFormatUtils; import io.tyloo.api.Tyloo; import io.tyloo.dubbo.context.DubboTransactionContextEditor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Calendar; @Service("redPacketTradeOrderService") public class RedPacketTradeOrderServiceImpl implements RedPacketTradeOrderService { @Autowired RedPacketAccountRepository redPacketAccountRepository; @Autowired TradeOrderRepository tradeOrderRepository; @Override @Tyloo(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = DubboTransactionContextEditor.class) @Transactional public String record(RedPacketTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("red packet try record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder foundTradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if trade order has been recorded, if yes, return success directly. if (foundTradeOrder == null) { TradeOrder tradeOrder = new TradeOrder( tradeOrderDto.getSelfUserId(), tradeOrderDto.getOppositeUserId(), tradeOrderDto.getMerchantOrderNo(), tradeOrderDto.getAmount() ); try { tradeOrderRepository.insert(tradeOrder); RedPacketAccount transferFromAccount = redPacketAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); transferFromAccount.transferFrom(tradeOrderDto.getAmount()); redPacketAccountRepository.save(transferFromAccount); } catch (DataIntegrityViolationException e) { //this exception may happen when insert trade order concurrently, if happened, ignore this insert operation. } } return "success"; } @Transactional public void confirmRecord(RedPacketTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("red packet confirm record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency. if (tradeOrder != null && tradeOrder.getStatus().equals("DRAFT")) { tradeOrder.confirm(); tradeOrderRepository.update(tradeOrder); RedPacketAccount transferToAccount = redPacketAccountRepository.findByUserId(tradeOrderDto.getOppositeUserId()); transferToAccount.transferTo(tradeOrderDto.getAmount()); redPacketAccountRepository.save(transferToAccount); } } @Transactional public void cancelRecord(RedPacketTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("red packet cancel record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency. if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) { tradeOrder.cancel(); tradeOrderRepository.update(tradeOrder); RedPacketAccount capitalAccount = redPacketAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); capitalAccount.cancelTransfer(tradeOrderDto.getAmount()); redPacketAccountRepository.save(capitalAccount); } } } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/resources/config/spring/local/appcontext-service-provider.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/resources/config/spring/local/appcontext-service-tcc.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/resources/config/spring/local/appcontext-web-servlet.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/resources/sample-dubbo-redpacket.properties ================================================ dubbo.port=2880 zookeeper.address=127.0.0.1:2181 zookeeper.session.timeout=30000 zookeeper.connect.timeout=2000 ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/resources/tccjdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/webapp/WEB-INF/web.xml ================================================ Sample Dubbo Redpacket contextConfigLocation classpath*:config/spring/local/appcontext-*.xml,classpath:tyloo.xml,classpath:tyloo-dubbo.xml log4jConfigLocation classpath:log/log4j.xml log4jRefreshInterval 60000 org.springframework.web.util.Log4jConfigListener org.springframework.web.context.ContextLoaderListener characterEncodingFilter Character Encoding Filter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:config/spring/local/appcontext-web-servlet.xml 1 springmvc / ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket/src/main/webapp/index.jsp ================================================ hello tcc transacton dubbo sample red packet ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket-api/pom.xml ================================================ tyloo-dubbo-sample io.tyloo 1.1.0 4.0.0 tyloo-dubbo-redpacket-api io.tyloo tyloo-api ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket-api/src/main/java/io/tyloo/sample/dubbo/redpacket/api/RedPacketAccountService.java ================================================ package io.tyloo.sample.dubbo.redpacket.api; import java.math.BigDecimal; public interface RedPacketAccountService { BigDecimal getRedPacketAccountByUserId(long userId); } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket-api/src/main/java/io/tyloo/sample/dubbo/redpacket/api/RedPacketTradeOrderService.java ================================================ package io.tyloo.sample.dubbo.redpacket.api; import io.tyloo.sample.dubbo.redpacket.api.dto.RedPacketTradeOrderDto; import io.tyloo.api.Tyloo; public interface RedPacketTradeOrderService { @Tyloo public String record(RedPacketTradeOrderDto tradeOrderDto); } ================================================ FILE: tyloo-tutorial-sample/tyloo-dubbo-sample/tyloo-dubbo-redpacket-api/src/main/java/io/tyloo/sample/dubbo/redpacket/api/dto/RedPacketTradeOrderDto.java ================================================ package io.tyloo.sample.dubbo.redpacket.api.dto; import java.io.Serializable; import java.math.BigDecimal; public class RedPacketTradeOrderDto implements Serializable { private static final long serialVersionUID = 4747014387277477558L; private long selfUserId; private long oppositeUserId; private String orderTitle; private String merchantOrderNo; private BigDecimal amount; public long getSelfUserId() { return selfUserId; } public void setSelfUserId(long selfUserId) { this.selfUserId = selfUserId; } public long getOppositeUserId() { return oppositeUserId; } public void setOppositeUserId(long oppositeUserId) { this.oppositeUserId = oppositeUserId; } public String getOrderTitle() { return orderTitle; } public void setOrderTitle(String orderTitle) { this.orderTitle = orderTitle; } public String getMerchantOrderNo() { return merchantOrderNo; } public void setMerchantOrderNo(String merchantOrderNo) { this.merchantOrderNo = merchantOrderNo; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/pom.xml ================================================ tyloo-tutorial-sample io.tyloo 1.1.0 4.0.0 tyloo-http-sample pom tyloo-http-capital tyloo-http-capital-api tyloo-http-order tyloo-http-redpacket tyloo-http-redpacket-api ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/pom.xml ================================================ tyloo-http-sample io.tyloo 1.1.0 4.0.0 tyloo-http-capital war io.tyloo tyloo-http-capital-api ${project.version} io.tyloo tyloo-spring ${project.version} io.tyloo tyloo-sample-captial ${project.version} org.springframework spring-web org.springframework spring-webmvc org.springframework spring-context ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/java/io/tyloo/sample/http/capital/service/CapitalAccountServiceImpl.java ================================================ package io.tyloo.sample.http.capital.service; import io.tyloo.sample.capital.domain.repository.CapitalAccountRepository; import io.tyloo.sample.http.capital.api.CapitalAccountService; import org.springframework.beans.factory.annotation.Autowired; import java.math.BigDecimal; public class CapitalAccountServiceImpl implements CapitalAccountService { @Autowired CapitalAccountRepository capitalAccountRepository; @Override public BigDecimal getCapitalAccountByUserId(long userId) { return capitalAccountRepository.findByUserId(userId).getBalanceAmount(); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/java/io/tyloo/sample/http/capital/service/CapitalTradeOrderServiceImpl.java ================================================ package io.tyloo.sample.http.capital.service; import io.tyloo.sample.capital.domain.entity.CapitalAccount; import io.tyloo.sample.capital.domain.entity.TradeOrder; import io.tyloo.sample.capital.domain.repository.CapitalAccountRepository; import io.tyloo.sample.capital.domain.repository.TradeOrderRepository; import io.tyloo.sample.http.capital.api.CapitalTradeOrderService; import io.tyloo.sample.http.capital.api.dto.CapitalTradeOrderDto; import org.apache.commons.lang3.time.DateFormatUtils; import io.tyloo.api.Tyloo; import io.tyloo.api.TransactionContext; import io.tyloo.context.MethodTransactionContextEditor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.transaction.annotation.Transactional; import java.util.Calendar; public class CapitalTradeOrderServiceImpl implements CapitalTradeOrderService { @Autowired CapitalAccountRepository capitalAccountRepository; @Autowired TradeOrderRepository tradeOrderRepository; @Override @Tyloo(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = MethodTransactionContextEditor.class) @Transactional public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("capital try record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder foundTradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if trade order has been recorded, if yes, return success directly. if (foundTradeOrder == null) { TradeOrder tradeOrder = new TradeOrder( tradeOrderDto.getSelfUserId(), tradeOrderDto.getOppositeUserId(), tradeOrderDto.getMerchantOrderNo(), tradeOrderDto.getAmount() ); try { tradeOrderRepository.insert(tradeOrder); CapitalAccount transferFromAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); transferFromAccount.transferFrom(tradeOrderDto.getAmount()); capitalAccountRepository.save(transferFromAccount); } catch (DataIntegrityViolationException e) { //this exception may happen when insert trade order concurrently, if happened, ignore this insert operation. } } return "success"; } @Transactional public void confirmRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("capital confirm record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency. if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) { tradeOrder.confirm(); tradeOrderRepository.update(tradeOrder); CapitalAccount transferToAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getOppositeUserId()); transferToAccount.transferTo(tradeOrderDto.getAmount()); capitalAccountRepository.save(transferToAccount); } } @Transactional public void cancelRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("capital cancel record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency. if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) { tradeOrder.cancel(); tradeOrderRepository.update(tradeOrder); CapitalAccount capitalAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); capitalAccount.cancelTransfer(tradeOrderDto.getAmount()); capitalAccountRepository.save(capitalAccount); } } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/resources/config/spring/local/appcontext-service-provider.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/resources/config/spring/local/appcontext-service-tcc.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/resources/config/spring/local/appcontext-web-servlet.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/resources/tccjdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC_CAP?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/webapp/WEB-INF/web.xml ================================================ Sample Http Capital webAppRootKey tyloo-http-capital contextConfigLocation classpath*:config/spring/local/appcontext-*.xml,classpath:tyloo.xml log4jConfigLocation classpath:log/log4j.xml log4jRefreshInterval 60000 org.springframework.web.util.Log4jConfigListener org.springframework.web.context.ContextLoaderListener characterEncodingFilter Character Encoding Filter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:config/spring/local/appcontext-web-servlet.xml 1 springmvc / ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital/src/main/webapp/index.jsp ================================================ hello tcc transacton http sample capital ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital-api/pom.xml ================================================ tyloo-http-sample io.tyloo 1.1.0 4.0.0 tyloo-http-capital-api io.tyloo tyloo-api ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital-api/src/main/java/io/tyloo/sample/http/capital/api/CapitalAccountService.java ================================================ package io.tyloo.sample.http.capital.api; import java.math.BigDecimal; public interface CapitalAccountService { BigDecimal getCapitalAccountByUserId(long userId); } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital-api/src/main/java/io/tyloo/sample/http/capital/api/CapitalTradeOrderService.java ================================================ package io.tyloo.sample.http.capital.api; import io.tyloo.sample.http.capital.api.dto.CapitalTradeOrderDto; import io.tyloo.api.TransactionContext; public interface CapitalTradeOrderService { public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto); } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-capital-api/src/main/java/io/tyloo/sample/http/capital/api/dto/CapitalTradeOrderDto.java ================================================ package io.tyloo.sample.http.capital.api.dto; import java.io.Serializable; import java.math.BigDecimal; public class CapitalTradeOrderDto implements Serializable { private static final long serialVersionUID = 6627401903410124642L; private long selfUserId; private long oppositeUserId; private String orderTitle; private String merchantOrderNo; private BigDecimal amount; public long getSelfUserId() { return selfUserId; } public void setSelfUserId(long selfUserId) { this.selfUserId = selfUserId; } public long getOppositeUserId() { return oppositeUserId; } public void setOppositeUserId(long oppositeUserId) { this.oppositeUserId = oppositeUserId; } public String getOrderTitle() { return orderTitle; } public void setOrderTitle(String orderTitle) { this.orderTitle = orderTitle; } public String getMerchantOrderNo() { return merchantOrderNo; } public void setMerchantOrderNo(String merchantOrderNo) { this.merchantOrderNo = merchantOrderNo; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/pom.xml ================================================ tyloo-http-sample io.tyloo 1.1.0 4.0.0 tyloo-http-order war log4j log4j io.tyloo tyloo-http-capital-api ${project.version} io.tyloo tyloo-http-redpacket-api ${project.version} io.tyloo tyloo-sample-order ${project.version} io.tyloo tyloo-spring ${project.version} org.apache.httpcomponents httpclient com.mchange c3p0 org.apache.httpcomponents httpclient commons-codec commons-codec mysql mysql-connector-java org.mybatis mybatis-spring org.mybatis mybatis org.aspectj aspectjrt org.aspectj aspectjweaver org.springframework spring-web org.springframework spring-webmvc org.springframework spring-context org.springframework spring-beans org.springframework spring-jdbc org.apache.commons commons-lang3 org.freemarker freemarker ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/java/io/tyloo/sample/http/order/service/AccountServiceImpl.java ================================================ package io.tyloo.sample.http.order.service; import io.tyloo.sample.http.capital.api.CapitalAccountService; import io.tyloo.sample.http.redpacket.api.RedPacketAccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; @Service("accountService") public class AccountServiceImpl { @Autowired RedPacketAccountService redPacketAccountService; @Autowired CapitalAccountService capitalAccountService; public BigDecimal getRedPacketAccountByUserId(long userId){ return redPacketAccountService.getRedPacketAccountByUserId(userId); } public BigDecimal getCapitalAccountByUserId(long userId){ return capitalAccountService.getCapitalAccountByUserId(userId); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/java/io/tyloo/sample/http/order/service/PaymentServiceImpl.java ================================================ package io.tyloo.sample.http.order.service; import io.tyloo.sample.http.capital.api.dto.CapitalTradeOrderDto; import io.tyloo.sample.http.redpacket.api.dto.RedPacketTradeOrderDto; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.repository.OrderRepository; import org.apache.commons.lang3.time.DateFormatUtils; import io.tyloo.api.Tyloo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.Calendar; @Service public class PaymentServiceImpl { @Autowired TradeOrderServiceProxy tradeOrderServiceProxy; @Autowired OrderRepository orderRepository; @Tyloo(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment", asyncConfirm = true) @Transactional public void makePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) { System.out.println("order try make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); //check if the order status is DRAFT, if no, means that another call makePayment for the same order happened, ignore this call makePayment. if (order.getStatus().equals("DRAFT")) { order.pay(redPacketPayAmount, capitalPayAmount); try { orderRepository.updateOrder(order); } catch (OptimisticLockingFailureException e) { //ignore the concurrently update order exception, ensure idempotency. } } String result = tradeOrderServiceProxy.record(null, buildCapitalTradeOrderDto(order)); String result2 = tradeOrderServiceProxy.record(null, buildRedPacketTradeOrderDto(order)); } public void confirmMakePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("order confirm make payment called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); Order foundOrder = orderRepository.findByMerchantOrderNo(order.getMerchantOrderNo()); //check order status, only if the status equals DRAFT, then confirm order if (foundOrder != null && foundOrder.getStatus().equals("PAYING")) { order.confirm(); orderRepository.updateOrder(order); } } public void cancelMakePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("order cancel make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); Order foundOrder = orderRepository.findByMerchantOrderNo(order.getMerchantOrderNo()); if (foundOrder != null && foundOrder.getStatus().equals("PAYING")) { order.cancelPayment(); orderRepository.updateOrder(order); } } private CapitalTradeOrderDto buildCapitalTradeOrderDto(Order order) { CapitalTradeOrderDto tradeOrderDto = new CapitalTradeOrderDto(); tradeOrderDto.setAmount(order.getCapitalPayAmount()); tradeOrderDto.setMerchantOrderNo(order.getMerchantOrderNo()); tradeOrderDto.setSelfUserId(order.getPayerUserId()); tradeOrderDto.setOppositeUserId(order.getPayeeUserId()); tradeOrderDto.setOrderTitle(String.format("order no:%s", order.getMerchantOrderNo())); return tradeOrderDto; } private RedPacketTradeOrderDto buildRedPacketTradeOrderDto(Order order) { RedPacketTradeOrderDto tradeOrderDto = new RedPacketTradeOrderDto(); tradeOrderDto.setAmount(order.getRedPacketPayAmount()); tradeOrderDto.setMerchantOrderNo(order.getMerchantOrderNo()); tradeOrderDto.setSelfUserId(order.getPayerUserId()); tradeOrderDto.setOppositeUserId(order.getPayeeUserId()); tradeOrderDto.setOrderTitle(String.format("order no:%s", order.getMerchantOrderNo())); return tradeOrderDto; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/java/io/tyloo/sample/http/order/service/PlaceOrderServiceImpl.java ================================================ package io.tyloo.sample.http.order.service; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.entity.Shop; import io.tyloo.sample.order.domain.repository.ShopRepository; import io.tyloo.sample.order.domain.service.OrderServiceImpl; import org.apache.commons.lang3.tuple.Pair; import io.tyloo.CancellingException; import io.tyloo.ConfirmingException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; @Service public class PlaceOrderServiceImpl { @Autowired ShopRepository shopRepository; @Autowired OrderServiceImpl orderService; @Autowired PaymentServiceImpl paymentService; public String placeOrder(long payerUserId, long shopId, List> productQuantities, BigDecimal redPacketPayAmount) { Shop shop = shopRepository.findById(shopId); Order order = orderService.createOrder(payerUserId, shop.getOwnerUserId(), productQuantities); Boolean result = false; try { paymentService.makePayment(order, redPacketPayAmount, order.getTotalAmount().subtract(redPacketPayAmount)); } catch (ConfirmingException confirmingException) { //exception throws with the tcc transaction status is CONFIRMING, //when tcc transaction is confirming status, // the tcc transaction recovery will try to confirm the whole transaction to ensure eventually consistent. result = true; } catch (CancellingException cancellingException) { //exception throws with the tcc transaction status is CANCELLING, //when tcc transaction is under CANCELLING status, // the tcc transaction recovery will try to cancel the whole transaction to ensure eventually consistent. } catch (Throwable e) { //other exceptions throws at TRYING stage. //you can retry or cancel the operation. e.printStackTrace(); } return order.getMerchantOrderNo(); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/java/io/tyloo/sample/http/order/service/TradeOrderServiceProxy.java ================================================ package io.tyloo.sample.http.order.service; import io.tyloo.sample.http.capital.api.CapitalTradeOrderService; import io.tyloo.sample.http.capital.api.dto.CapitalTradeOrderDto; import io.tyloo.sample.http.redpacket.api.RedPacketTradeOrderService; import io.tyloo.sample.http.redpacket.api.dto.RedPacketTradeOrderDto; import io.tyloo.api.Tyloo; import io.tyloo.api.Propagation; import io.tyloo.api.TransactionContext; import io.tyloo.context.MethodTransactionContextEditor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class TradeOrderServiceProxy { @Autowired CapitalTradeOrderService capitalTradeOrderService; @Autowired RedPacketTradeOrderService redPacketTradeOrderService; /*the propagation need set Propagation.SUPPORTS,otherwise the recover doesn't work, The default value is Propagation.REQUIRED, which means will begin new transaction when recover. */ @Tyloo(propagation = Propagation.SUPPORTS, confirmMethod = "record", cancelMethod = "record", transactionContextEditor = MethodTransactionContextEditor.class) public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) { return capitalTradeOrderService.record(transactionContext, tradeOrderDto); } @Tyloo(propagation = Propagation.SUPPORTS, confirmMethod = "record", cancelMethod = "record", transactionContextEditor = MethodTransactionContextEditor.class) public String record(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto) { return redPacketTradeOrderService.record(transactionContext, tradeOrderDto); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/java/io/tyloo/sample/http/order/web/controller/OrderController.java ================================================ package io.tyloo.sample.http.order.web.controller; import io.tyloo.sample.http.order.service.AccountServiceImpl; import io.tyloo.sample.http.order.service.PlaceOrderServiceImpl; import io.tyloo.sample.http.order.web.controller.vo.PlaceOrderRequest; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.entity.Product; import io.tyloo.sample.order.domain.repository.ProductRepository; import io.tyloo.sample.order.domain.service.OrderServiceImpl; import org.apache.commons.lang3.tuple.ImmutablePair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.RedirectView; import java.math.BigDecimal; import java.security.InvalidParameterException; import java.util.List; @Controller @RequestMapping("") public class OrderController { @Autowired PlaceOrderServiceImpl placeOrderService; @Autowired ProductRepository productRepository; @Autowired AccountServiceImpl accountService; @Autowired OrderServiceImpl orderService; @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView index() { ModelAndView mv = new ModelAndView("/index"); return mv; } @RequestMapping(value = "/user/{userId}/shop/{shopId}", method = RequestMethod.GET) public ModelAndView getProductsInShop(@PathVariable long userId, @PathVariable long shopId) { List products = productRepository.findByShopId(shopId); ModelAndView mv = new ModelAndView("/shop"); mv.addObject("products", products); mv.addObject("userId", userId); mv.addObject("shopId", shopId); return mv; } @RequestMapping(value = "/user/{userId}/shop/{shopId}/product/{productId}/confirm", method = RequestMethod.GET) public ModelAndView productDetail(@PathVariable long userId, @PathVariable long shopId, @PathVariable long productId) { ModelAndView mv = new ModelAndView("product_detail"); mv.addObject("capitalAmount", accountService.getCapitalAccountByUserId(userId)); mv.addObject("redPacketAmount", accountService.getRedPacketAccountByUserId(userId)); mv.addObject("product", productRepository.findById(productId)); mv.addObject("userId", userId); mv.addObject("shopId", shopId); return mv; } @RequestMapping(value = "/placeorder", method = RequestMethod.POST) public RedirectView placeOrder(@RequestParam String redPacketPayAmount, @RequestParam long shopId, @RequestParam long payerUserId, @RequestParam long productId) { PlaceOrderRequest request = buildRequest(redPacketPayAmount, shopId, payerUserId, productId); String merchantOrderNo = placeOrderService.placeOrder(request.getPayerUserId(), request.getShopId(), request.getProductQuantities(), request.getRedPacketPayAmount()); return new RedirectView("payresult/" + merchantOrderNo); } @RequestMapping(value = "/payresult/{merchantOrderNo}", method = RequestMethod.GET) public ModelAndView getPayResult(@PathVariable String merchantOrderNo) { ModelAndView mv = new ModelAndView("pay_success"); String payResultTip = null; Order foundOrder = orderService.findOrderByMerchantOrderNo(merchantOrderNo); payResultTip = foundOrder.getStatus(); mv.addObject("payResult", payResultTip); mv.addObject("capitalAmount", accountService.getCapitalAccountByUserId(foundOrder.getPayerUserId())); mv.addObject("redPacketAmount", accountService.getRedPacketAccountByUserId(foundOrder.getPayerUserId())); return mv; } private PlaceOrderRequest buildRequest(String redPacketPayAmount, long shopId, long payerUserId, long productId) { BigDecimal redPacketPayAmountInBigDecimal = new BigDecimal(redPacketPayAmount); if (redPacketPayAmountInBigDecimal.compareTo(BigDecimal.ZERO) < 0) { throw new InvalidParameterException("invalid red packet amount :" + redPacketPayAmount); } PlaceOrderRequest request = new PlaceOrderRequest(); request.setPayerUserId(payerUserId); request.setShopId(shopId); request.setRedPacketPayAmount(new BigDecimal(redPacketPayAmount)); request.getProductQuantities().add(new ImmutablePair(productId, 1)); return request; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/java/io/tyloo/sample/http/order/web/controller/vo/PlaceOrderRequest.java ================================================ package io.tyloo.sample.http.order.web.controller.vo; import org.apache.commons.lang3.tuple.Pair; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; public class PlaceOrderRequest { private long payerUserId; private long shopId; private BigDecimal redPacketPayAmount; private List> productQuantities = new ArrayList>(); public long getPayerUserId() { return payerUserId; } public void setPayerUserId(long payerUserId) { this.payerUserId = payerUserId; } public long getShopId() { return shopId; } public void setShopId(long shopId) { this.shopId = shopId; } public BigDecimal getRedPacketPayAmount() { return redPacketPayAmount; } public void setRedPacketPayAmount(BigDecimal redPacketPayAmount) { this.redPacketPayAmount = redPacketPayAmount; } public List> getProductQuantities() { return productQuantities; } public void setProductQuantities(List> productQuantities) { this.productQuantities = productQuantities; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/resources/config/spring/local/appcontext-service-consumer.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/resources/config/spring/local/appcontext-service-tcc.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/resources/config/spring/local/appcontext-web-servlet.xml ================================================ json=application/json html=text/html org.springframework.web.servlet.view.freemarker.FreeMarkerView /WEB-INF/ftl/ 5 UTF-8 UTF-8 yyyy-MM-dd HH:mm:ss HH:mm:ss 0.#### true,false true auto_detect UTF-8 true ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/resources/sample-dubbo-order.properties ================================================ zookeeper.address=127.0.0.1:2181 zookeeper.session.timeout=1800000 zookeeper.connect.timeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/resources/tccjdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC_ORD?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=300000 ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/webapp/WEB-INF/ftl/index.ftl ================================================ [#ftl ] tcc transacton dubbo sample order
sample说明:
打开下面商品列表链接,选择一个商品购买,输入红包支付金额,进行支付,系统将使用红包+资金账户转账支付。
支付成功后,各个project会打印如下日志:

   sample-dubbo-order:
     order try make payment called
     order confirm make payment called

   sample-dubbo-capital:
     capital try record called
     capital confirm record called

   sample-dubbo-redpacket:
     red packet try record called
     red packet confirm record called

商品列表链接

================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/webapp/WEB-INF/ftl/pay_success.ftl ================================================ [#ftl ] 支付结果

支付状态:${payResult}

剩余可用账户余额: ${capitalAmount?string("0.00")}元

剩余可用红包余额: ${redPacketAmount?string("0.00")}元

================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/webapp/WEB-INF/ftl/product_detail.ftl ================================================ [#ftl ] 订单详情

名称: ${product.productName}

价格: ${product.price?string("0.00")}元

可用账户余额: ${capitalAmount?string("0.00")}元

可用红包余额: ${redPacketAmount?string("0.00")}元

红包金额:   
================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/webapp/WEB-INF/ftl/shop.ftl ================================================ [#ftl ] 商品列表
    [#if products?size > 0] [#list products as product]
  • ${product.productName}(${product.price?string("0.00")})    购买

  • [/#list] [/#if]
================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-order/src/main/webapp/WEB-INF/web.xml ================================================ Sample Http Order webAppRootKey tyloo-http-order contextConfigLocation classpath*:config/spring/local/appcontext-*.xml,classpath:tyloo.xml log4jConfigLocation classpath:log/log4j.xml log4jRefreshInterval 60000 org.springframework.web.util.Log4jConfigListener org.springframework.web.context.ContextLoaderListener characterEncodingFilter Character Encoding Filter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:config/spring/local/appcontext-web-servlet.xml 1 springmvc / ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/pom.xml ================================================ tyloo-http-sample io.tyloo 1.1.0 4.0.0 tyloo-http-redpacket war io.tyloo tyloo-http-redpacket-api ${project.version} io.tyloo tyloo-sample-redpacket ${project.version} io.tyloo tyloo-spring ${project.version} org.springframework spring-context log4j log4j com.mchange c3p0 mysql mysql-connector-java org.mybatis mybatis-spring org.mybatis mybatis org.aspectj aspectjrt org.aspectj aspectjweaver org.springframework spring-web org.springframework spring-webmvc org.springframework spring-context org.springframework spring-beans org.springframework spring-jdbc org.apache.commons commons-lang3 ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/java/io/tyloo/sample/http/redpacket/service/RedPacketAccountServiceImpl.java ================================================ package io.tyloo.sample.http.redpacket.service; import io.tyloo.sample.http.redpacket.api.RedPacketAccountService; import io.tyloo.sample.redpacket.domain.repository.RedPacketAccountRepository; import org.springframework.beans.factory.annotation.Autowired; import java.math.BigDecimal; public class RedPacketAccountServiceImpl implements RedPacketAccountService { @Autowired RedPacketAccountRepository redPacketAccountRepository; @Override public BigDecimal getRedPacketAccountByUserId(long userId) { return redPacketAccountRepository.findByUserId(userId).getBalanceAmount(); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/java/io/tyloo/sample/http/redpacket/service/RedPacketTradeOrderServiceImpl.java ================================================ package io.tyloo.sample.http.redpacket.service; import io.tyloo.sample.http.redpacket.api.RedPacketTradeOrderService; import io.tyloo.sample.http.redpacket.api.dto.RedPacketTradeOrderDto; import io.tyloo.sample.redpacket.domain.entity.RedPacketAccount; import io.tyloo.sample.redpacket.domain.entity.TradeOrder; import io.tyloo.sample.redpacket.domain.repository.RedPacketAccountRepository; import io.tyloo.sample.redpacket.domain.repository.TradeOrderRepository; import org.apache.commons.lang3.time.DateFormatUtils; import io.tyloo.api.Tyloo; import io.tyloo.api.TransactionContext; import io.tyloo.context.MethodTransactionContextEditor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.transaction.annotation.Transactional; import java.util.Calendar; public class RedPacketTradeOrderServiceImpl implements RedPacketTradeOrderService { @Autowired RedPacketAccountRepository redPacketAccountRepository; @Autowired TradeOrderRepository tradeOrderRepository; @Override @Tyloo(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = MethodTransactionContextEditor.class) @Transactional public String record(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("red packet try record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder foundTradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); //check if the trade order has need recorded. //if record, then this method call return success directly. if (foundTradeOrder == null) { TradeOrder tradeOrder = new TradeOrder( tradeOrderDto.getSelfUserId(), tradeOrderDto.getOppositeUserId(), tradeOrderDto.getMerchantOrderNo(), tradeOrderDto.getAmount() ); try { tradeOrderRepository.insert(tradeOrder); RedPacketAccount transferFromAccount = redPacketAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); transferFromAccount.transferFrom(tradeOrderDto.getAmount()); redPacketAccountRepository.save(transferFromAccount); } catch (DataIntegrityViolationException e) { } } return "success"; } @Transactional public void confirmRecord(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("red packet confirm record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) { tradeOrder.confirm(); tradeOrderRepository.update(tradeOrder); RedPacketAccount transferToAccount = redPacketAccountRepository.findByUserId(tradeOrderDto.getOppositeUserId()); transferToAccount.transferTo(tradeOrderDto.getAmount()); redPacketAccountRepository.save(transferToAccount); } } @Transactional public void cancelRecord(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto) { try { Thread.sleep(1000l); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("red packet cancel record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss")); TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo()); if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) { tradeOrder.cancel(); tradeOrderRepository.update(tradeOrder); RedPacketAccount capitalAccount = redPacketAccountRepository.findByUserId(tradeOrderDto.getSelfUserId()); capitalAccount.cancelTransfer(tradeOrderDto.getAmount()); redPacketAccountRepository.save(capitalAccount); } } } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/resources/config/spring/local/appcontext-service-provider.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/resources/config/spring/local/appcontext-service-tcc.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/resources/config/spring/local/appcontext-web-servlet.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/resources/tccjdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC_RED?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/webapp/WEB-INF/web.xml ================================================ Sample Dubbo Redpacket webAppRootKey tyloo-http-redpacket contextConfigLocation classpath*:config/spring/local/appcontext-*.xml,classpath:tyloo.xml log4jConfigLocation classpath:log/log4j.xml log4jRefreshInterval 60000 org.springframework.web.util.Log4jConfigListener org.springframework.web.context.ContextLoaderListener characterEncodingFilter Character Encoding Filter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:config/spring/local/appcontext-web-servlet.xml 1 springmvc / ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket/src/main/webapp/index.jsp ================================================ hello tcc transacton http sample red packet ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket-api/pom.xml ================================================ tyloo-http-sample io.tyloo 1.1.0 4.0.0 tyloo-http-redpacket-api io.tyloo tyloo-api ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket-api/src/main/java/io/tyloo/sample/http/redpacket/api/RedPacketAccountService.java ================================================ package io.tyloo.sample.http.redpacket.api; import java.math.BigDecimal; public interface RedPacketAccountService { BigDecimal getRedPacketAccountByUserId(long userId); } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket-api/src/main/java/io/tyloo/sample/http/redpacket/api/RedPacketTradeOrderService.java ================================================ package io.tyloo.sample.http.redpacket.api; import io.tyloo.sample.http.redpacket.api.dto.RedPacketTradeOrderDto; import io.tyloo.api.TransactionContext; public interface RedPacketTradeOrderService { public String record(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto); } ================================================ FILE: tyloo-tutorial-sample/tyloo-http-sample/tyloo-http-redpacket-api/src/main/java/io/tyloo/sample/http/redpacket/api/dto/RedPacketTradeOrderDto.java ================================================ package io.tyloo.sample.http.redpacket.api.dto; import java.io.Serializable; import java.math.BigDecimal; public class RedPacketTradeOrderDto implements Serializable { private static final long serialVersionUID = 4747014387277477558L; private long selfUserId; private long oppositeUserId; private String orderTitle; private String merchantOrderNo; private BigDecimal amount; public long getSelfUserId() { return selfUserId; } public void setSelfUserId(long selfUserId) { this.selfUserId = selfUserId; } public long getOppositeUserId() { return oppositeUserId; } public void setOppositeUserId(long oppositeUserId) { this.oppositeUserId = oppositeUserId; } public String getOrderTitle() { return orderTitle; } public void setOrderTitle(String orderTitle) { this.orderTitle = orderTitle; } public String getMerchantOrderNo() { return merchantOrderNo; } public void setMerchantOrderNo(String merchantOrderNo) { this.merchantOrderNo = merchantOrderNo; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/pom.xml ================================================ tyloo-tutorial-sample io.tyloo 1.1.0 4.0.0 tyloo-sample-domain pom tyloo-sample-common tyloo-sample-order tyloo-sample-captial tyloo-sample-redpacket ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/pom.xml ================================================ tyloo-sample-domain io.tyloo 1.1.0 4.0.0 tyloo-sample-captial io.tyloo tyloo-sample-common ${project.version} org.springframework spring-context log4j log4j com.mchange c3p0 mysql mysql-connector-java org.mybatis mybatis-spring org.mybatis mybatis org.aspectj aspectjrt org.aspectj aspectjweaver org.springframework spring-jdbc org.springframework spring-beans org.apache.commons commons-lang3 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/java/io/tyloo/sample/capital/domain/entity/CapitalAccount.java ================================================ package io.tyloo.sample.capital.domain.entity; import io.tyloo.sample.exception.InsufficientBalanceException; import java.math.BigDecimal; public class CapitalAccount { private long id; private long userId; private BigDecimal balanceAmount; private BigDecimal transferAmount = BigDecimal.ZERO; public long getUserId() { return userId; } public BigDecimal getBalanceAmount() { return balanceAmount; } public long getId() { return id; } public void setId(long id) { this.id = id; } public void transferFrom(BigDecimal amount) { this.balanceAmount = this.balanceAmount.subtract(amount); if (BigDecimal.ZERO.compareTo(this.balanceAmount) > 0) { throw new InsufficientBalanceException(); } transferAmount = transferAmount.add(amount.negate()); } public void transferTo(BigDecimal amount) { this.balanceAmount = this.balanceAmount.add(amount); transferAmount = transferAmount.add(amount); } public void cancelTransfer(BigDecimal amount) { transferTo(amount); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/java/io/tyloo/sample/capital/domain/entity/TradeOrder.java ================================================ package io.tyloo.sample.capital.domain.entity; import java.math.BigDecimal; public class TradeOrder { private long id; private long selfUserId; private long oppositeUserId; private String merchantOrderNo; private BigDecimal amount; private String status = "DRAFT"; private long version = 1l; public TradeOrder() { } public TradeOrder(long selfUserId, long oppositeUserId, String merchantOrderNo, BigDecimal amount) { this.selfUserId = selfUserId; this.oppositeUserId = oppositeUserId; this.merchantOrderNo = merchantOrderNo; this.amount = amount; } public long getId() { return id; } public long getSelfUserId() { return selfUserId; } public long getOppositeUserId() { return oppositeUserId; } public String getMerchantOrderNo() { return merchantOrderNo; } public BigDecimal getAmount() { return amount; } public String getStatus() { return status; } public void confirm() { this.status = "CONFIRM"; } public void cancel() { this.status = "CANCEL"; } public long getVersion() { return version; } public void updateVersion() { this.version = version + 1; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/java/io/tyloo/sample/capital/domain/repository/CapitalAccountRepository.java ================================================ package io.tyloo.sample.capital.domain.repository; import io.tyloo.sample.capital.domain.entity.CapitalAccount; import io.tyloo.sample.capital.infrastructure.dao.CapitalAccountDao; import io.tyloo.sample.exception.InsufficientBalanceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository public class CapitalAccountRepository { @Autowired CapitalAccountDao capitalAccountDao; public CapitalAccount findByUserId(long userId) { return capitalAccountDao.findByUserId(userId); } public void save(CapitalAccount capitalAccount) { int effectCount = capitalAccountDao.update(capitalAccount); if (effectCount < 1) { throw new InsufficientBalanceException(); } } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/java/io/tyloo/sample/capital/domain/repository/TradeOrderRepository.java ================================================ package io.tyloo.sample.capital.domain.repository; import io.tyloo.sample.capital.domain.entity.TradeOrder; import io.tyloo.sample.capital.infrastructure.dao.TradeOrderDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.stereotype.Repository; @Repository public class TradeOrderRepository { @Autowired TradeOrderDao tradeOrderDao; public void insert(TradeOrder tradeOrder) { tradeOrderDao.insert(tradeOrder); } public void update(TradeOrder tradeOrder) { tradeOrder.updateVersion(); int effectCount = tradeOrderDao.update(tradeOrder); if (effectCount < 1) { throw new OptimisticLockingFailureException("update trade order failed"); } } public TradeOrder findByMerchantOrderNo(String merchantOrderNo) { return tradeOrderDao.findByMerchantOrderNo(merchantOrderNo); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/java/io/tyloo/sample/capital/infrastructure/dao/CapitalAccountDao.java ================================================ package io.tyloo.sample.capital.infrastructure.dao; import io.tyloo.sample.capital.domain.entity.CapitalAccount; public interface CapitalAccountDao { CapitalAccount findByUserId(long userId); int update(CapitalAccount capitalAccount); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/java/io/tyloo/sample/capital/infrastructure/dao/TradeOrderDao.java ================================================ package io.tyloo.sample.capital.infrastructure.dao; import io.tyloo.sample.capital.domain.entity.TradeOrder; public interface TradeOrderDao { int insert(TradeOrder tradeOrder); int update(TradeOrder tradeOrder); TradeOrder findByMerchantOrderNo(String merchantOrderNo); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/resources/config/spring/local/appcontext-service-dao.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/resources/config/spring/local/appcontext-service-datasource.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/resources/config/sqlmap/main/sample-capitalaccount.xml ================================================ SELECT CAPITAL_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID FROM CAP_CAPITAL_ACCOUNT INSERT INTO CAP_CAPITAL_ACCOUNT ( BALANCE_AMOUNT, USER_ID ) VALUES ( #{balanceAmount}, #{userId} ) UPDATE CAP_CAPITAL_ACCOUNT SET BALANCE_AMOUNT = BALANCE_AMOUNT+#{transferAmount} WHERE CAPITAL_ACCOUNT_ID = #{id} AND BALANCE_AMOUNT+#{transferAmount}>=0 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/resources/config/sqlmap/main/sample-tradeorder.xml ================================================ SELECT ID, SELF_USER_ID, OPPOSITE_USER_ID, MERCHANT_ORDER_NO, AMOUNT, STATUS, VERSION FROM CAP_TRADE_ORDER INSERT INTO CAP_TRADE_ORDER ( SELF_USER_ID, OPPOSITE_USER_ID, MERCHANT_ORDER_NO, AMOUNT, STATUS, VERSION ) VALUES ( #{selfUserId}, #{oppositeUserId}, #{merchantOrderNo}, #{amount}, #{status}, #{version} ) UPDATE CAP_TRADE_ORDER SET STATUS = #{status}, VERSION = #{version} WHERE ID = #{id} AND VERSION = #{version}-1 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/resources/jdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC_CAP?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-captial/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-common/pom.xml ================================================ tyloo-sample-domain io.tyloo 1.1.0 4.0.0 tyloo-sample-common jar ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-common/src/main/java/io/tyloo/sample/exception/InsufficientBalanceException.java ================================================ package io.tyloo.sample.exception; public class InsufficientBalanceException extends RuntimeException { private static final long serialVersionUID = 6689953065473521009L; public InsufficientBalanceException() { } public InsufficientBalanceException(String message) { super(message); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/pom.xml ================================================ tyloo-sample-domain io.tyloo 1.1.0 4.0.0 tyloo-sample-order io.tyloo tyloo-sample-common ${project.version} org.springframework spring-context log4j log4j com.mchange c3p0 mysql mysql-connector-java org.mybatis mybatis-spring org.mybatis mybatis org.aspectj aspectjrt org.aspectj aspectjweaver org.springframework spring-jdbc org.springframework spring-beans org.apache.commons commons-lang3 cn.hutool hutool-all 5.2.5 compile ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/entity/Order.java ================================================ package io.tyloo.sample.order.domain.entity; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; import cn.hutool.core.lang.UUID; public class Order implements Serializable { private static final long serialVersionUID = -5908730245224893590L; private long id; private long payerUserId; private long payeeUserId; private BigDecimal redPacketPayAmount; private BigDecimal capitalPayAmount; private String status = "DRAFT"; private String merchantOrderNo; private long version = 1l; private List orderLines = new ArrayList(); public Order() { } public Order(long payerUserId, long payeeUserId) { this.payerUserId = payerUserId; this.payeeUserId = payeeUserId; this.merchantOrderNo = UUID.randomUUID().toString().replace("-",""); } public long getPayerUserId() { return payerUserId; } public long getPayeeUserId() { return payeeUserId; } public BigDecimal getTotalAmount() { BigDecimal totalAmount = BigDecimal.ZERO; for (OrderLine orderLine : orderLines) { totalAmount = totalAmount.add(orderLine.getTotalAmount()); } return totalAmount; } public void addOrderLine(OrderLine orderLine) { this.orderLines.add(orderLine); } public List getOrderLines() { return Collections.unmodifiableList(this.orderLines); } public void pay(BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) { this.redPacketPayAmount = redPacketPayAmount; this.capitalPayAmount = capitalPayAmount; this.status = "PAYING"; } public BigDecimal getRedPacketPayAmount() { return redPacketPayAmount; } public BigDecimal getCapitalPayAmount() { return capitalPayAmount; } public String getMerchantOrderNo() { return merchantOrderNo; } public void setMerchantOrderNo(String merchantOrderNo) { this.merchantOrderNo = merchantOrderNo; } public long getId() { return id; } public String getStatus() { return status; } public void confirm() { this.status = "CONFIRMED"; } public void cancelPayment() { this.status = "PAY_FAILED"; } public long getVersion() { return version; } public void updateVersion() { version++; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/entity/OrderLine.java ================================================ package io.tyloo.sample.order.domain.entity; import java.io.Serializable; import java.math.BigDecimal; public class OrderLine implements Serializable { private static final long serialVersionUID = 2300754647209250837L; private long id; private long productId; private int quantity; private BigDecimal unitPrice; public OrderLine() { } public OrderLine(Long productId, Integer quantity,BigDecimal unitPrice) { this.productId = productId; this.quantity = quantity; this.unitPrice = unitPrice; } public long getProductId() { return productId; } public int getQuantity() { return quantity; } public BigDecimal getUnitPrice() { return unitPrice; } public BigDecimal getTotalAmount() { return unitPrice.multiply(BigDecimal.valueOf(quantity)); } public long getId() { return id; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/entity/Product.java ================================================ package io.tyloo.sample.order.domain.entity; import java.io.Serializable; import java.math.BigDecimal; public class Product implements Serializable{ private long productId; private long shopId; private String productName; private BigDecimal price; public Product() { } public Product(long productId, long shopId, String productName, BigDecimal price) { this.productId = productId; this.shopId = shopId; this.productName = productName; this.price = price; } public long getProductId() { return productId; } public long getShopId() { return shopId; } public String getProductName() { return productName; } public BigDecimal getPrice() { return price; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/entity/Shop.java ================================================ package io.tyloo.sample.order.domain.entity; public class Shop { private long id; private long ownerUserId; public long getOwnerUserId() { return ownerUserId; } public long getId() { return id; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/factory/OrderFactory.java ================================================ package io.tyloo.sample.order.domain.factory; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.entity.OrderLine; import io.tyloo.sample.order.domain.repository.ProductRepository; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; @Component public class OrderFactory { @Autowired ProductRepository productRepository; public Order buildOrder(long payerUserId, long payeeUserId, List> productQuantities) { Order order = new Order(payerUserId, payeeUserId); for (Pair pair : productQuantities) { long productId = pair.getLeft(); order.addOrderLine(new OrderLine(productId, pair.getRight(), productRepository.findById(productId).getPrice())); } return order; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/repository/OrderRepository.java ================================================ package io.tyloo.sample.order.domain.repository; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.entity.OrderLine; import io.tyloo.sample.order.infrastructure.dao.OrderDao; import io.tyloo.sample.order.infrastructure.dao.OrderLineDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.stereotype.Repository; /* * * @Author:Zh1Cheung zh1cheunglq@gmail.com * @Date: 18:04 2019/4/19 * */ @Repository public class OrderRepository { @Autowired OrderDao orderDao; @Autowired OrderLineDao orderLineDao; public void createOrder(Order order) { orderDao.insert(order); for (OrderLine orderLine : order.getOrderLines()) { orderLineDao.insert(orderLine); } } public void updateOrder(Order order) { order.updateVersion(); int effectCount = orderDao.update(order); if (effectCount < 1) { throw new OptimisticLockingFailureException("update order failed"); } } public Order findByMerchantOrderNo(String merchantOrderNo) { return orderDao.findByMerchantOrderNo(merchantOrderNo); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/repository/ProductRepository.java ================================================ package io.tyloo.sample.order.domain.repository; import io.tyloo.sample.order.domain.entity.Product; import io.tyloo.sample.order.infrastructure.dao.ProductDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.List; @Repository public class ProductRepository { @Autowired ProductDao productDao; public Product findById(long productId){ return productDao.findById(productId); } public List findByShopId(long shopId){ return productDao.findByShopId(shopId); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/repository/ShopRepository.java ================================================ package io.tyloo.sample.order.domain.repository; import io.tyloo.sample.order.domain.entity.Shop; import io.tyloo.sample.order.infrastructure.dao.ShopDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository public class ShopRepository { @Autowired ShopDao shopDao; public Shop findById(long id) { return shopDao.findById(id); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/domain/service/OrderServiceImpl.java ================================================ package io.tyloo.sample.order.domain.service; import io.tyloo.sample.order.domain.entity.Order; import io.tyloo.sample.order.domain.factory.OrderFactory; import io.tyloo.sample.order.domain.repository.OrderRepository; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class OrderServiceImpl { @Autowired OrderRepository orderRepository; @Autowired OrderFactory orderFactory; @Transactional public Order createOrder(long payerUserId, long payeeUserId, List> productQuantities) { Order order = orderFactory.buildOrder(payerUserId, payeeUserId, productQuantities); orderRepository.createOrder(order); return order; } public Order findOrderByMerchantOrderNo(String orderNo) { return orderRepository.findByMerchantOrderNo(orderNo); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/infrastructure/dao/OrderDao.java ================================================ package io.tyloo.sample.order.infrastructure.dao; import io.tyloo.sample.order.domain.entity.Order; public interface OrderDao { public int insert(Order order); public int update(Order order); Order findByMerchantOrderNo(String merchantOrderNo); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/infrastructure/dao/OrderLineDao.java ================================================ package io.tyloo.sample.order.infrastructure.dao; import io.tyloo.sample.order.domain.entity.OrderLine; public interface OrderLineDao { void insert(OrderLine orderLine); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/infrastructure/dao/ProductDao.java ================================================ package io.tyloo.sample.order.infrastructure.dao; import io.tyloo.sample.order.domain.entity.Product; import java.util.List; public interface ProductDao { Product findById(long productId); List findByShopId(long shopId); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/java/io/tyloo/sample/order/infrastructure/dao/ShopDao.java ================================================ package io.tyloo.sample.order.infrastructure.dao; import io.tyloo.sample.order.domain.entity.Shop; public interface ShopDao { Shop findById(long id); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/config/spring/local/appcontext-service-dao.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/config/spring/local/appcontext-service-datasource.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/config/sqlmap/main/sample-order.xml ================================================ SELECT ORDER_ID, PAYER_USER_ID, PAYEE_USER_ID, RED_PACKET_PAY_AMOUNT, CAPITAL_PAY_AMOUNT, STATUS, MERCHANT_ORDER_NO, VERSION FROM ORD_ORDER INSERT INTO ORD_ORDER ( PAYER_USER_ID, PAYEE_USER_ID, RED_PACKET_PAY_AMOUNT, CAPITAL_PAY_AMOUNT, STATUS, MERCHANT_ORDER_NO, VERSION ) VALUES ( #{payerUserId}, #{payeeUserId}, #{redPacketPayAmount}, #{capitalPayAmount}, #{status}, #{merchantOrderNo}, #{version} ) UPDATE ORD_ORDER SET STATUS = #{status}, RED_PACKET_PAY_AMOUNT = #{redPacketPayAmount}, CAPITAL_PAY_AMOUNT = #{capitalPayAmount}, VERSION = #{version} WHERE ORDER_ID = #{id} AND VERSION=#{version}-1 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/config/sqlmap/main/sample-orderline.xml ================================================ SELECT ORDER_LINE_ID, PRODUCT_ID, QUANTITY, UNIT_PRICE FROM TCC_ORDER_LINE INSERT INTO ORD_ORDER_LINE ( PRODUCT_ID, QUANTITY, UNIT_PRICE ) VALUES ( #{productId}, #{quantity}, #{unitPrice} ) ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/config/sqlmap/main/sample-product.xml ================================================ SELECT PRODUCT_ID, SHOP_ID, PRODUCT_NAME, PRICE FROM ORD_PRODUCT ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/config/sqlmap/main/sample-shop.xml ================================================ SELECT SHOP_ID, OWNER_USER_ID FROM ORD_SHOP ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/jdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/TCC_ORD?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=3000 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-order/src/main/resources/sample-order.properties ================================================ zookeeper.address=127.0.0.1:2181 zookeeper.session.timeout=1800000 zookeeper.connect.timeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/pom.xml ================================================ tyloo-sample-domain io.tyloo 1.1.0 4.0.0 tyloo-sample-redpacket io.tyloo tyloo-sample-common ${project.version} org.springframework spring-context log4j log4j com.mchange c3p0 mysql mysql-connector-java org.mybatis mybatis-spring org.mybatis mybatis org.aspectj aspectjrt org.aspectj aspectjweaver org.springframework spring-jdbc org.springframework spring-beans org.apache.commons commons-lang3 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/java/io/tyloo/sample/redpacket/domain/entity/RedPacketAccount.java ================================================ package io.tyloo.sample.redpacket.domain.entity; import io.tyloo.sample.exception.InsufficientBalanceException; import java.math.BigDecimal; public class RedPacketAccount { private long id; private long userId; private BigDecimal balanceAmount; public long getUserId() { return userId; } public BigDecimal getBalanceAmount() { return balanceAmount; } public long getId() { return id; } public void setId(long id) { this.id = id; } public void transferFrom(BigDecimal amount) { this.balanceAmount = this.balanceAmount.subtract(amount); if (BigDecimal.ZERO.compareTo(this.balanceAmount) > 0) { throw new InsufficientBalanceException(); } } public void transferTo(BigDecimal amount) { this.balanceAmount = this.balanceAmount.add(amount); } public void cancelTransfer(BigDecimal amount) { transferTo(amount); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/java/io/tyloo/sample/redpacket/domain/entity/TradeOrder.java ================================================ package io.tyloo.sample.redpacket.domain.entity; import java.math.BigDecimal; public class TradeOrder { private long id; private long selfUserId; private long oppositeUserId; private String merchantOrderNo; private BigDecimal amount; private String status = "DRAFT"; private long version = 1l; public TradeOrder() { } public TradeOrder(long selfUserId, long oppositeUserId, String merchantOrderNo, BigDecimal amount) { this.selfUserId = selfUserId; this.oppositeUserId = oppositeUserId; this.merchantOrderNo = merchantOrderNo; this.amount = amount; } public long getId() { return id; } public long getSelfUserId() { return selfUserId; } public long getOppositeUserId() { return oppositeUserId; } public String getMerchantOrderNo() { return merchantOrderNo; } public BigDecimal getAmount() { return amount; } public String getStatus() { return status; } public void confirm() { this.status = "CONFIRM"; } public void cancel() { this.status = "CANCEL"; } public long getVersion() { return version; } public void updateVersion() { version = version + 1; } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/java/io/tyloo/sample/redpacket/domain/repository/RedPacketAccountRepository.java ================================================ package io.tyloo.sample.redpacket.domain.repository; import io.tyloo.sample.exception.InsufficientBalanceException; import io.tyloo.sample.redpacket.domain.entity.RedPacketAccount; import io.tyloo.sample.redpacket.infrastructure.dao.RedPacketAccountDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository public class RedPacketAccountRepository { @Autowired RedPacketAccountDao redPacketAccountDao; public RedPacketAccount findByUserId(long userId) { return redPacketAccountDao.findByUserId(userId); } public void save(RedPacketAccount redPacketAccount) { int effectCount = redPacketAccountDao.update(redPacketAccount); if (effectCount < 1) { throw new InsufficientBalanceException(); } } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/java/io/tyloo/sample/redpacket/domain/repository/TradeOrderRepository.java ================================================ package io.tyloo.sample.redpacket.domain.repository; import io.tyloo.sample.redpacket.domain.entity.TradeOrder; import io.tyloo.sample.redpacket.infrastructure.dao.TradeOrderDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.stereotype.Repository; @Repository public class TradeOrderRepository { @Autowired TradeOrderDao tradeOrderDao; public void insert(TradeOrder tradeOrder) { tradeOrderDao.insert(tradeOrder); } public void update(TradeOrder tradeOrder) { tradeOrder.updateVersion(); int effectCount = tradeOrderDao.update(tradeOrder); if (effectCount < 1) { throw new OptimisticLockingFailureException("update trade order failed"); } } public TradeOrder findByMerchantOrderNo(String merchantOrderNo) { return tradeOrderDao.findByMerchantOrderNo(merchantOrderNo); } } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/java/io/tyloo/sample/redpacket/infrastructure/dao/RedPacketAccountDao.java ================================================ package io.tyloo.sample.redpacket.infrastructure.dao; import io.tyloo.sample.redpacket.domain.entity.RedPacketAccount; public interface RedPacketAccountDao { RedPacketAccount findByUserId(long userId); int update(RedPacketAccount redPacketAccount); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/java/io/tyloo/sample/redpacket/infrastructure/dao/TradeOrderDao.java ================================================ package io.tyloo.sample.redpacket.infrastructure.dao; import io.tyloo.sample.redpacket.domain.entity.TradeOrder; public interface TradeOrderDao { void insert(TradeOrder tradeOrder); int update(TradeOrder tradeOrder); TradeOrder findByMerchantOrderNo(String merchantOrderNo); } ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/resources/config/spring/local/appcontext-service-dao.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/resources/config/spring/local/appcontext-service-datasource.xml ================================================ ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/resources/config/sqlmap/main/sample-redpacketaccount.xml ================================================ SELECT RED_PACKET_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID FROM RED_RED_PACKET_ACCOUNT INSERT INTO RED_RED_PACKET_ACCOUNT ( BALANCE_AMOUNT, USER_ID ) VALUES ( #{balanceAmount}, #{userId} ) UPDATE RED_RED_PACKET_ACCOUNT SET BALANCE_AMOUNT = #{balanceAmount} WHERE RED_PACKET_ACCOUNT_ID = #{id} ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/resources/config/sqlmap/main/sample-tradeorder.xml ================================================ SELECT ID, SELF_USER_ID, OPPOSITE_USER_ID, MERCHANT_ORDER_NO, AMOUNT, STATUS, VERSION FROM RED_TRADE_ORDER INSERT INTO RED_TRADE_ORDER ( SELF_USER_ID, OPPOSITE_USER_ID, MERCHANT_ORDER_NO, AMOUNT, STATUS, VERSION ) VALUES ( #{selfUserId}, #{oppositeUserId}, #{merchantOrderNo}, #{amount}, #{status}, #{version} ) UPDATE RED_TRADE_ORDER SET STATUS = #{status}, VERSION = #{version} WHERE ID = #{id} AND VERSION = #{version}-1 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/resources/jdbc.properties ================================================ jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC_RED?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000 ================================================ FILE: tyloo-tutorial-sample/tyloo-sample-domain/tyloo-sample-redpacket/src/main/resources/log/log4j.xml ================================================ ================================================ FILE: tyloo-unit-test/pom.xml ================================================ io.tyloo tyloo 1.1.0 4.0.0 tyloo-unit-test jar io.tyloo tyloo-spring ${project.version} junit junit org.springframework spring-context org.springframework spring-test ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/client/AccountRecordServiceProxy.java ================================================ package io.tyloo.unittest.client; import io.tyloo.api.TransactionContext; import io.tyloo.unittest.thirdservice.AccountRecordService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import static java.util.concurrent.Executors.*; @Service public class AccountRecordServiceProxy { @Autowired private AccountRecordService accountRecordService; private ExecutorService executorService = newFixedThreadPool(100); public void record(final TransactionContext transactionContext, final long accountId, final int amount) { // Future future = this.executorService // .submit(new Callable() { // @Override // public Boolean call() throws Exception { // accountRecordService.record(transactionContext, accountId, amount); // return true; // } // }); // // handleResult(future); accountRecordService.record(transactionContext, accountId, amount); } private void handleResult(Future future) { while (!future.isDone()) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } try { future.get(); } catch (InterruptedException | ExecutionException e) { throw new Error(e); } } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/client/AccountServiceProxy.java ================================================ package io.tyloo.unittest.client; import io.tyloo.api.TransactionContext; import io.tyloo.unittest.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.*; @Service public class AccountServiceProxy { @Autowired AccountService accountService; private ExecutorService executorService = Executors.newFixedThreadPool(100); public void transferFromWithMultipleTier(final TransactionContext transactionContext, final long accountId, final int amount) { Future future = this.executorService .submit(new Callable() { @Override public Boolean call() throws Exception { accountService.transferFromWithMultipleTier(transactionContext, accountId, amount); return true; } }); handleResult(future); } public void transferToWithMultipleTier(final TransactionContext transactionContext, final long accountId, final int amount) { // Future future = this.executorService // .submit(new Callable() { // @Override // public Boolean call() throws Exception { // accountService.transferToWithMultipleTier(transactionContext, accountId, amount); // return true; // } // }); // // handleResult(future); accountService.transferToWithMultipleTier(transactionContext, accountId, amount); } public void performanceTuningTransferTo(TransactionContext transactionContext) { } public void transferTo(final TransactionContext transactionContext, final long accountId, final int amount) { // Future future = this.executorService // .submit(new Callable() { // @Override // public Boolean call() throws Exception { // accountService.transferTo(transactionContext, accountId, amount); // return true; // } // }); // // handleResult(future); accountService.transferTo(transactionContext, accountId, amount); } public void transferTo(final long accountId, final int amount) { // Future future = this.executorService // .submit(new Callable() { // @Override // public Boolean call() throws Exception { // accountService.transferToWithNoTransactionContext(accountId, amount); // return true; // } // }); // // handleResult(future); accountService.transferToWithNoTransactionContext(accountId, amount); } public void transferFrom(final TransactionContext transactionContext, final long accountId, final int amount) { // Future future = this.executorService // .submit(new Callable() { // @Override // public Boolean call() throws Exception { // accountService.transferFrom(transactionContext, accountId, amount); // return true; // } // }); // // handleResult(future); accountService.transferFrom(transactionContext, accountId, amount); } private void handleResult(Future future) { while (!future.isDone()) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } try { future.get(); } catch (InterruptedException e) { throw new Error(e); } catch (ExecutionException e) { throw new Error(e); } } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/client/TransferService.java ================================================ package io.tyloo.unittest.client; import io.tyloo.api.Tyloo; import io.tyloo.api.Propagation; import io.tyloo.unittest.entity.AccountStatus; import io.tyloo.unittest.entity.SubAccount; import io.tyloo.unittest.repository.SubAccountRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class TransferService { @Autowired AccountServiceProxy accountService; @Autowired SubAccountRepository subAccountRepository; public TransferService() { } @Tyloo @Transactional public void performenceTuningTransfer() { accountService.performanceTuningTransferTo(null); } @Tyloo(propagation = Propagation.MANDATORY) public void transferWithMandatoryPropagation(long fromAccountId, long toAccountId, int amount) { System.out.println("transfer called"); SubAccount subAccount = subAccountRepository.findById(fromAccountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() - amount); accountService.transferTo(null, toAccountId, amount); } @Tyloo(confirmMethod = "transferConfirm", cancelMethod = "transferCancel") @Transactional public void transfer(long fromAccountId, long toAccountId, int amount) { System.out.println("transfer called"); SubAccount subAccount = subAccountRepository.findById(fromAccountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() - amount); accountService.transferTo(null, toAccountId, amount); } @Tyloo(confirmMethod = "transferConfirm", cancelMethod = "transferCancel") public void transferWithMultipleTier(long fromAccountId, long toAccountId, int amount) { System.out.println("transferWithMultipleTier called"); SubAccount subAccount = subAccountRepository.findById(fromAccountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() - amount); accountService.transferToWithMultipleTier(null, toAccountId, amount); } @Tyloo(confirmMethod = "transferWithMultipleConsumerConfirm", cancelMethod = "transferWithMultipleConsumerCancel") @Transactional public void transferWithMultipleConsumer(long fromAccountId, long toAccountId, int amount) { System.out.println("transferWithMultipleConsumer called"); accountService.transferFrom(null, fromAccountId, amount); accountService.transferTo(null, toAccountId, amount); } @Tyloo public void transferWithOnlyTryAndMultipleConsumer(long fromAccountId, long toAccountId, int amount) { System.out.println("transferWithOnlyTryAndMultipleConsumer called"); accountService.transferFrom(null, fromAccountId, amount); accountService.transferTo(null, toAccountId, amount); } @Tyloo public void transferWithNoTransactionContext(long fromAccountId, long toAccountId, int amount) { System.out.println("transferWithNoTransactionContext called"); accountService.transferTo(toAccountId, amount); } public void transferConfirm(long fromAccountId, long toAccountId, int amount) { System.out.println("transferConfirm called"); SubAccount subAccount = subAccountRepository.findById(fromAccountId); subAccount.setStatus(AccountStatus.NORMAL.getId()); } public void transferCancel(long fromAccountId, long toAccountId, int amount) { System.out.println("transferCancel called"); SubAccount subAccount = subAccountRepository.findById(fromAccountId); if (subAccount.getStatus() == AccountStatus.TRANSFERING.getId()) { subAccount.setBalanceAmount(subAccount.getBalanceAmount() + amount); } subAccount.setStatus(AccountStatus.NORMAL.getId()); } public void transferWithMultipleConsumerConfirm(long fromAccountId, long toAccountId, int amount) { System.out.println("transferWithMultipleConsumerConfirm called"); } public void transferWithMultipleConsumerCancel(long fromAccountId, long toAccountId, int amount) { System.out.println("transferWithMultipleConsumerCancel called"); } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/entity/AccountRecord.java ================================================ package io.tyloo.unittest.entity; public class AccountRecord { private long accountId; private volatile int balanceAmount; private volatile int statusId = AccountStatus.NORMAL.getId(); public AccountRecord(long accountId, int balanceAmount) { this.accountId = accountId; this.balanceAmount = balanceAmount; } public long getAccountId() { return accountId; } public int getBalanceAmount() { return balanceAmount; } public void setStatusId(int statusId) { this.statusId = statusId; } public void setBalanceAmount(int balanceAmount) { this.balanceAmount = balanceAmount; } public int getStatusId() { return statusId; } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/entity/AccountStatus.java ================================================ package io.tyloo.unittest.entity; public enum AccountStatus { NORMAL(1), TRANSFERING(2); private int id; AccountStatus(int id) { this.id = id; } public int getId() { return id; } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/entity/SubAccount.java ================================================ package io.tyloo.unittest.entity; public class SubAccount { private long id; private volatile int balanceAmount; private volatile int status = AccountStatus.NORMAL.getId(); public long getId() { return id; } public void setId(long id) { this.id = id; } public int getBalanceAmount() { return balanceAmount; } public void setBalanceAmount(int balanceAmount) { this.balanceAmount = balanceAmount; } public SubAccount() { } public SubAccount(long id, int balanceAmount) { this.id = id; this.balanceAmount = balanceAmount; } public void setStatus(int status) { this.status = status; } public int getStatus() { return status; } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/entity/UserShardingId.java ================================================ package io.tyloo.unittest.entity; import java.io.Serializable; public abstract class UserShardingId implements Serializable { private static final long serialVersionUID = -8923642703284688507L; private Long id; private Long userId; public UserShardingId() { } public UserShardingId(Long userId) { this.userId = userId; } public UserShardingId(Long id, Long userId) { this.id = id; this.userId = userId; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } @Override public boolean equals(Object obj) { if (null == obj) { return false; } if (this == obj) { return true; } if (!getClass().equals(obj.getClass())) { return false; } UserShardingId that = (UserShardingId) obj; boolean isIdEquals = false; boolean isUserIdEquals = false; if ((this.id == null && that.id == null) || (this.id != null && this.id.equals(that.id))) { isIdEquals = true; } if ((this.userId == null && that.userId == null) || (this.userId != null && this.userId.equals(that.userId))) { isUserIdEquals = true; } return isIdEquals && isUserIdEquals; } @Override public int hashCode() { int hashCode = 17; hashCode += this.id == null ? 0 : this.id * 31; hashCode += this.userId == null ? 0 : this.userId * 31; return hashCode; } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/repository/AccountRecordRepository.java ================================================ package io.tyloo.unittest.repository; import io.tyloo.unittest.entity.AccountRecord; import org.springframework.stereotype.Repository; import java.util.HashMap; import java.util.Map; @Repository public class AccountRecordRepository { private Map accountRecordMap = new HashMap(); { accountRecordMap.put(1L, new AccountRecord(1, 0)); accountRecordMap.put(2L, new AccountRecord(2, 0)); accountRecordMap.put(3L, new AccountRecord(3, 0)); } public AccountRecord findById(Long id) { return accountRecordMap.get(id); } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/repository/SubAccountRepository.java ================================================ package io.tyloo.unittest.repository; import io.tyloo.unittest.entity.SubAccount; import org.springframework.stereotype.Repository; import java.util.HashMap; import java.util.Map; @Repository public class SubAccountRepository { private Map subAccountMap = new HashMap(); { subAccountMap.put(1L, new SubAccount(1, 100)); subAccountMap.put(2L, new SubAccount(2, 200)); subAccountMap.put(3L, new SubAccount(3, 300)); } public SubAccount findById(Long id) { return subAccountMap.get(id); } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/service/AccountService.java ================================================ package io.tyloo.unittest.service; import io.tyloo.api.TransactionContext; public interface AccountService { void transferTo(TransactionContext transactionContext, long accountId, int amount); void transferToConfirm(TransactionContext transactionContext, long accountId, int amount); void transferToCancel(TransactionContext transactionContext, long accountId, int amount); void transferToWithNoTransactionContext(long accountId, int amount); void transferToConfirmWithNoTransactionContext(long accountId, int amount); void transferToCancelWithNoTransactionContext(long accountId, int amount); void transferFrom(TransactionContext transactionContext, long accountId, int amount); void transferFromConfirm(TransactionContext transactionContext, long accountId, int amount); void transferFromCancel(TransactionContext transactionContext, long accountId, int amount); void transferToWithMultipleTier(TransactionContext transactionContext, long accountId, int amount); void transferFromWithMultipleTier(TransactionContext transactionContext, long accountId, int amount); } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/service/AccountServiceImpl.java ================================================ package io.tyloo.unittest.service; import io.tyloo.api.Tyloo; import io.tyloo.api.Propagation; import io.tyloo.api.TransactionContext; import io.tyloo.unittest.client.AccountRecordServiceProxy; import io.tyloo.unittest.entity.AccountStatus; import io.tyloo.unittest.entity.SubAccount; import io.tyloo.unittest.repository.SubAccountRepository; import io.tyloo.unittest.utils.UnitTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountServiceImpl implements AccountService { @Autowired AccountRecordServiceProxy accountRecordServiceProxy; @Autowired SubAccountRepository subAccountRepository; @Override @Tyloo(confirmMethod = "transferFromConfirm", cancelMethod = "transferFromCancel") public void transferFrom(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferFrom called"); SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() - amount); accountRecordServiceProxy.record(null, accountId, amount); } @Override @Tyloo(propagation = Propagation.REQUIRED, confirmMethod = "transferToConfirm", cancelMethod = "transferToCancel") public void transferTo(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferTo called"); SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() + amount); } @Override @Tyloo(confirmMethod = "transferFromConfirm", cancelMethod = "transferFromCancel") public void transferFromWithMultipleTier(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferFromWithMultipleTier called"); SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() + amount); accountRecordServiceProxy.record(null, accountId, amount); } @Override @Tyloo(confirmMethod = "transferToConfirm", cancelMethod = "transferToCancel") public void transferToWithMultipleTier(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferToWithMultipleTier called"); SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() + amount); accountRecordServiceProxy.record(null, accountId, amount); } @Override @Tyloo(propagation = Propagation.REQUIRES_NEW, confirmMethod = "transferToConfirmWithNoTransactionContext", cancelMethod = "transferToCancelWithNoTransactionContext") public void transferToWithNoTransactionContext(long accountId, int amount) { System.out.println("transferToWithNoTransactionContext called"); SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.TRANSFERING.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() + amount); accountRecordServiceProxy.record(null, accountId, amount); } public void transferFromConfirm(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferFromConfirm called"); SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.NORMAL.getId()); } public void transferFromCancel(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferFromCancel called"); SubAccount subAccount = subAccountRepository.findById(accountId); if (subAccount.getStatus() == AccountStatus.TRANSFERING.getId()) { subAccount.setStatus(AccountStatus.NORMAL.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() + amount); } } @Override public void transferToConfirm(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferToConfirm called"); if (UnitTest.CONFIRMING_EXCEPTION) { throw new RuntimeException("transferToConfirm confirm failed."); } SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.NORMAL.getId()); } @Override public void transferToCancel(TransactionContext transactionContext, long accountId, int amount) { System.out.println("transferToCancel called"); SubAccount subAccount = subAccountRepository.findById(accountId); if (subAccount.getStatus() == AccountStatus.TRANSFERING.getId()) { subAccount.setStatus(AccountStatus.NORMAL.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() - amount); } } public void transferToConfirmWithNoTransactionContext(long accountId, int amount) { System.out.println("transferToConfirmWithNoTransactionContext called"); SubAccount subAccount = subAccountRepository.findById(accountId); subAccount.setStatus(AccountStatus.NORMAL.getId()); } public void transferToCancelWithNoTransactionContext(long accountId, int amount) { System.out.println("transferToCancelWithNoTransactionContext called"); SubAccount subAccount = subAccountRepository.findById(accountId); if (subAccount.getStatus() == AccountStatus.TRANSFERING.getId()) { subAccount.setStatus(AccountStatus.NORMAL.getId()); subAccount.setBalanceAmount(subAccount.getBalanceAmount() - amount); } } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/thirdservice/AccountRecordService.java ================================================ package io.tyloo.unittest.thirdservice; import io.tyloo.api.TransactionContext; public interface AccountRecordService { public void record(TransactionContext transactionContext, long accountId, int amount); void recordConfirm(TransactionContext transactionContext, long accountId, int amount); void recordCancel(TransactionContext transactionContext, long accountId, int amount); } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/thirdservice/AccountRecordServiceImpl.java ================================================ package io.tyloo.unittest.thirdservice; import io.tyloo.api.Tyloo; import io.tyloo.api.TransactionContext; import io.tyloo.unittest.entity.AccountRecord; import io.tyloo.unittest.entity.AccountStatus; import io.tyloo.unittest.repository.AccountRecordRepository; import io.tyloo.unittest.utils.UnitTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountRecordServiceImpl implements AccountRecordService { @Autowired AccountRecordRepository accountRecordRepository; @Override @Tyloo(confirmMethod = "recordConfirm", cancelMethod = "recordCancel") public void record(TransactionContext transactionContext, long accountId, int amount) { System.out.println("record"); AccountRecord accountRecord = accountRecordRepository.findById(accountId); accountRecord.setBalanceAmount(amount); accountRecord.setStatusId(AccountStatus.TRANSFERING.getId()); if (UnitTest.TRYING_EXCEPTION) { throw new RuntimeException("record try failed."); } } @Override public void recordConfirm(TransactionContext transactionContext, long accountId, int amount) { System.out.println("recordConfirm"); AccountRecord accountRecord = accountRecordRepository.findById(accountId); accountRecord.setStatusId(AccountStatus.NORMAL.getId()); } @Override public void recordCancel(TransactionContext transactionContext, long accountId, int amount) { System.out.println("recordCancel"); if (UnitTest.TRYING_EXCEPTION) { throw new RuntimeException("record cancel failed."); } AccountRecord accountRecord = accountRecordRepository.findById(accountId); accountRecord.setBalanceAmount(accountRecord.getBalanceAmount() - amount); accountRecord.setStatusId(AccountStatus.NORMAL.getId()); } } ================================================ FILE: tyloo-unit-test/src/main/java/io/tyloo/unittest/utils/UnitTest.java ================================================ package io.tyloo.unittest.utils; public class UnitTest { public static volatile boolean CONFIRMING_EXCEPTION = false; public static volatile boolean TRYING_EXCEPTION = false; } ================================================ FILE: tyloo-unit-test/src/main/resources/tyloo-unit-test.xml ================================================ ================================================ FILE: tyloo-unit-test/src/test/java/io/tyloo/unit/test/AbstractTestCase.java ================================================ package io.tyloo.unit.test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:/tyloo-unit-test.xml","classpath:/tyloo.xml"}) public abstract class AbstractTestCase { } ================================================ FILE: tyloo-unit-test/src/test/java/io/tyloo/unit/test/PerformanceTest.java ================================================ package io.tyloo.unit.test; import io.tyloo.Participant; import io.tyloo.Transaction; import io.tyloo.common.TransactionType; import io.tyloo.serializer.KryoPoolSerializer; import io.tyloo.serializer.ObjectSerializer; import io.tyloo.unittest.client.TransferService; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class PerformanceTest extends AbstractTestCase { @Autowired private TransferService transferService; @Test public void performanceTest() { long currentTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { transferService.performenceTuningTransfer(); } long thenTime = System.currentTimeMillis(); System.out.println(thenTime - currentTime); } @Test public void serializeTest() { ObjectSerializer objectSerializer = new KryoPoolSerializer(); long currentTime = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { // Transaction transaction = new Transaction(TransactionType.ROOT); transaction.getAttachments().put("abc", new Participant()); byte[] bytes = objectSerializer.serialize(transaction); Transaction transaction1 = (Transaction) objectSerializer.deserialize(bytes); if (transaction.getVersion() != transaction1.getVersion()) { throw new Error(); } } long thenTime = System.currentTimeMillis(); System.out.println(thenTime - currentTime); } @Test public void testThreadPool() throws ExecutionException, InterruptedException { ExecutorService executorService = new ThreadPoolExecutor(1, 2, 30L, TimeUnit.MILLISECONDS, new SynchronousQueue()); Long startTime = System.currentTimeMillis(); List futures = new ArrayList(); for (int i = 0; i <= 1; i++) { futures.add(executorService.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName() + " done"); } })); } for (Future future : futures) { future.get(); } System.out.println("cost time:" + (System.currentTimeMillis() - startTime)); } } ================================================ FILE: tyloo-unit-test/src/test/java/io/tyloo/unit/test/ReflectionTest.java ================================================ package io.tyloo.unit.test; import io.tyloo.Transaction; import io.tyloo.serializer.JacksonJsonSerializer; import io.tyloo.unittest.client.TransferService; import io.tyloo.unittest.entity.SubAccount; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Method; public class ReflectionTest extends AbstractTestCase { @Autowired private TransferService transferService; @Test public void test1() throws NoSuchMethodException { transferService.performenceTuningTransfer(); Method transferMethod = TransferService.class.getMethod("transfer", long.class, long.class, int.class); } @Test public void testJacksonSerializer() { String json = "[\"Transaction\",{\"xid\":[\"TransactionXid\",{\"formatId\":1,\"globalTransactionId\":\"TQ5/1W2USTWZHjwV7JajxA==\",\"branchQualifier\":\"Kcjo0GaHTc6hIPpf3B7ARA==\"}],\"status\":\"TRYING\",\"transactionType\":\"ROOT\",\"retriedCount\":0,\"createTime\":[\"java.util.Date\",1575457808693],\"lastUpdateTime\":[\"java.util.Date\",1575457822864],\"version\":2,\"participants\":[\"java.util.ArrayList\",[[\"Participant\",{\"xid\":[\"TransactionXid\",{\"formatId\":1,\"globalTransactionId\":\"TQ5/1W2USTWZHjwV7JajxA==\",\"branchQualifier\":\"H1KzapA5SL+L2mu3D5tyRw==\"}],\"confirmInvocationContext\":[\"InvocationContext\",{\"targetClass\":\"TransferService\",\"methodName\":\"transferConfirm\",\"parameterTypes\":[\"long\",\"long\",\"int\"],\"args\":[\"[Ljava.lang.Object;\",[[\"java.lang.Long\",1],[\"java.lang.Long\",2],50]]}],\"cancelInvocationContext\":[\"InvocationContext\",{\"targetClass\":\"TransferService\",\"methodName\":\"transferCancel\",\"parameterTypes\":[\"long\",\"long\",\"int\"],\"args\":[\"[Ljava.lang.Object;\",[[\"java.lang.Long\",1],[\"java.lang.Long\",2],50]]}]}]]],\"attachments\":[\"java.util.concurrent.ConcurrentHashMap\",{}]}]"; JacksonJsonSerializer jacksonJsonSerializer = new JacksonJsonSerializer(); Transaction transaction = jacksonJsonSerializer.deserialize(json.getBytes()); SubAccount subAccount = new SubAccount(1l, 10); byte[] bytes = jacksonJsonSerializer.serialize(transaction); json = new String(bytes); transaction = jacksonJsonSerializer.deserialize(bytes); Assert.assertTrue(transaction != null); } } ================================================ FILE: tyloo-unit-test/src/test/java/io/tyloo/unit/test/TransferServiceTest.java ================================================ package io.tyloo.unit.test; import io.tyloo.SystemException; import io.tyloo.recover.TylooTransactionRecovery; import io.tyloo.unittest.client.TransferService; import io.tyloo.unittest.entity.AccountRecord; import io.tyloo.unittest.entity.AccountStatus; import io.tyloo.unittest.entity.SubAccount; import io.tyloo.unittest.repository.AccountRecordRepository; import io.tyloo.unittest.repository.SubAccountRepository; import io.tyloo.unittest.utils.UnitTest; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; public class TransferServiceTest extends AbstractTestCase { @Autowired private TransferService transferService; @Autowired SubAccountRepository subAccountRepository; @Autowired AccountRecordRepository accountRecordRepository; @Autowired TylooTransactionRecovery tylooTransactionRecovery; @Test public void testTransfer() throws InterruptedException { //given buildAccount(); //when transferService.transfer(1, 2, 50); //then SubAccount subAccountFrom = subAccountRepository.findById(1L); SubAccount subAccountTo = subAccountRepository.findById(2L); Assert.assertTrue(subAccountFrom.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountTo.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountFrom.getBalanceAmount() == 50); Assert.assertTrue(subAccountTo.getBalanceAmount() == 250); } @Test public void testTransferWithMandatoryPropagtion() throws InterruptedException { //given buildAccount(); //when try { transferService.transferWithMandatoryPropagation(1, 2, 50); } catch (SystemException e) { Assert.assertTrue(e.getMessage().startsWith("no active tyloo transaction while propagation is mandatory for method")); } } @Test public void testTransferWithMultipleTier() { //given buildAccount(); //when transferService.transferWithMultipleTier(1, 2, 50); //then SubAccount subAccountFrom = subAccountRepository.findById(1L); SubAccount subAccountTo = subAccountRepository.findById(2L); Assert.assertTrue(subAccountFrom.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountTo.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountFrom.getBalanceAmount() == 50); Assert.assertTrue(subAccountTo.getBalanceAmount() == 250); AccountRecord accountRecordTo = accountRecordRepository.findById(2L); Assert.assertTrue(accountRecordTo.getBalanceAmount() == 50); Assert.assertTrue(accountRecordTo.getStatusId() == AccountStatus.NORMAL.getId()); } @Test public void testTransferWithMultiplerConsumer() { //given buildAccount(); //when transferService.transferWithMultipleConsumer(1, 2, 70); //then SubAccount subAccountFrom = subAccountRepository.findById(1L); SubAccount subAccountTo = subAccountRepository.findById(2L); Assert.assertTrue(subAccountFrom.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountTo.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountFrom.getBalanceAmount() == 30); Assert.assertTrue(subAccountTo.getBalanceAmount() == 270); } @Test public void testTransferWithOnlyTryAndMultipleConsumer() { //given buildAccount(); //when transferService.transferWithOnlyTryAndMultipleConsumer(1, 2, 70); //then SubAccount subAccountFrom = subAccountRepository.findById(1L); SubAccount subAccountTo = subAccountRepository.findById(2L); Assert.assertTrue(subAccountFrom.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountTo.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountFrom.getBalanceAmount() == 30); Assert.assertTrue(subAccountTo.getBalanceAmount() == 270); } @Test public void testTransferWithNoTransactionContext() { //given buildAccount(); //when transferService.transferWithNoTransactionContext(1, 2, 70); //then SubAccount subAccountTo = subAccountRepository.findById(2L); Assert.assertTrue(subAccountTo.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountTo.getBalanceAmount() == 270); } @Test public void testTryingRecovery() { //given UnitTest.TRYING_EXCEPTION = true; try { //given buildAccount(); //when transferService.transferWithMultipleConsumer(1, 2, 70); } catch (Throwable e) { } System.out.println("begin recovery"); //when UnitTest.TRYING_EXCEPTION = false; //then AccountRecord accountRecord = accountRecordRepository.findById(1L); Assert.assertTrue(accountRecord.getBalanceAmount() == 70); try { //waiting the auto recovery schedule Thread.sleep(1000 * 10); } catch (InterruptedException e) { throw new Error(e); } Assert.assertTrue(accountRecord.getBalanceAmount() == 0); } @Test public void testConfirmingRecovery() { //given UnitTest.CONFIRMING_EXCEPTION = true; try { //given buildAccount(); //when transferService.transferWithMultipleConsumer(1, 2, 70); } catch (Throwable e) { } System.out.println("begin recovery"); //when UnitTest.CONFIRMING_EXCEPTION = false; //then AccountRecord accountRecord = accountRecordRepository.findById(1L); Assert.assertTrue(accountRecord.getBalanceAmount() == 70); try { //waiting the auto recovery schedule Thread.sleep(1000 * 10L); } catch (InterruptedException e) { throw new Error(e); } Assert.assertTrue(accountRecord.getBalanceAmount() == 70); SubAccount subAccountFrom = subAccountRepository.findById(1L); SubAccount subAccountTo = subAccountRepository.findById(2L); Assert.assertTrue(subAccountFrom.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountTo.getStatus() == AccountStatus.NORMAL.getId()); Assert.assertTrue(subAccountFrom.getBalanceAmount() == 30); Assert.assertTrue(subAccountTo.getBalanceAmount() == 270); } private void buildAccount() { SubAccount subAccountFrom = subAccountRepository.findById(1L); subAccountFrom.setBalanceAmount(100); subAccountFrom.setStatus(AccountStatus.NORMAL.getId()); SubAccount subAccountTo = subAccountRepository.findById(2L); subAccountTo.setBalanceAmount(200); subAccountTo.setStatus(AccountStatus.NORMAL.getId()); AccountRecord accountRecordFrom = accountRecordRepository.findById(1L); accountRecordFrom.setBalanceAmount(0); accountRecordFrom.setStatusId(AccountStatus.NORMAL.getId()); AccountRecord accountRecordTo = accountRecordRepository.findById(2L); accountRecordTo.setBalanceAmount(0); accountRecordTo.setStatusId(AccountStatus.NORMAL.getId()); } } ================================================ FILE: tyloo-unit-test/src/test/resources/log4j.properties ================================================ # Set root logger level to DEBUG and its only appender to stdout. log4j.rootLogger=INFO, stdout # stdout is set to be a ConsoleAppender. log4j.appender.stdout=org.apache.log4j.ConsoleAppender # stdout uses PatternLayout. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n #log4j.appender.stdout.Target=System.out log4j.logger.com.ibatis=debug,stdout log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug,stdout log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug,stdout log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug,stdout log4j.logger.java.sql.Connection=debug,stdout log4j.logger.java.sql.Statement=debug,stdout log4j.logger.java.sql.PreparedStatement=debug,stdout