Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存
接上一篇 Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务。通过本篇阅读,您便可以开始学会添加一个全新的 Claptrap。
Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架。如果您是首次阅读本系列文章。建议可以先从本文末尾的入门文章开始了解。
开篇摘要
本篇,我通过实现 “管理库存” 的需求来了解一下如何在已有的项目样例中定义一个 Claptrap。
结合前一篇的基本步骤,定义 Claptrap 只要而外增加一些步骤就可以了。完整的步骤如下所示,其中标记为 “新内容” 的部分属于本篇的区别于前篇的新内容:
- 定义 ClaptrapTypeCode (新内容)
- 定义 State (新内容)
- 定义 Grain 接口 (新内容)
- 实现 Grain (新内容)
- 注册 Grain (新内容)
- 定义 EventCode
- 定义 Event
- 实现 EventHandler
- 注册 EventHandler
- 实现 IInitialStateDataFactory (新内容)
- 修改 Controller
这是一个从下向上的过程,实际的编码过程中开发也可以有所调整。
本篇实现的业务用例:
- 实现表示库存数据的 SKU(Stock keeping Unit) 对象。
- 能够对 SKU 进行更新和读取。
定义 ClaptrapTypeCode
ClaptrapTypeCode 是一个 Claptrap 的唯一编码。其在 State 的识别,序列化等方面起到了重要的作用。
打开 HelloClaptrap.Models
项目中的 ClaptrapCodes
类。
添加 SKU 的 ClaptrapTypeCode。
namespace HelloClaptrap.Models
{
public static class ClaptrapCodes
{
public const string CartGrain = "cart_claptrap_newbe";
private const string CartEventSuffix = "_e_" + CartGrain;
public const string AddItemToCart = "addItem" + CartEventSuffix;
public const string RemoveItemFromCart = "removeItem" + CartEventSuffix; #region Sku + public const string SkuGrain = "sku_claptrap_newbe";
+ private const string SkuEventSuffix = "_e_" + SkuGrain; #endregion
}
}
定义 State
State 在 Actor 模式中代表了 Actor 对象当前的数据表现。
由于 Claptrap 是基于事件溯源模式的 Actor。因此定义恰好的 State 非常重要。
在该示例当中,我们只需要记录当前 SKU 的库存即可,因此,State 的设计非常的简单。
在 HelloClaptrap.Models
项目添加 Sku
文件夹,并在该文件夹下创建 SkuState
类。
添加如下代码:
+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Models.Sku
+ {
+ public class SkuState : IStateData
+ {
+ public int Inventory { get; set; }
+ }
+ }
Inventory 表示当前 SKU 的库存。
IStateData
接口是框架中表示 State 的空接口,用于在泛型推断时使用。
定义 Grain 接口
定义 Grain 接口的定义,才能够提供外部与 Claptrap 的互操作性。
在 HelloClaptrap.IActors
项目中添加 ISkuGrain
接口。
添加接口以及 Attribute。
+ using System.Threading.Tasks;
+ using HelloClaptrap.Models;
+ using HelloClaptrap.Models.Sku;
+ using Newbe.Claptrap;
+ using Newbe.Claptrap.Orleans;
+
+ namespace HelloClaptrap.IActor
+ {
+ [ClaptrapState(typeof(SkuState), ClaptrapCodes.SkuGrain)]
+ public interface ISkuGrain : IClaptrapGrain
+ {
+ /// <summary>
+ /// Get latest inventory of this sku
+ /// </summary>
+ /// <returns></returns>
+ Task<int> GetInventoryAsync();
+
+ /// <summary>
+ /// Update inventory by add diff, diff could be negative number
+ /// </summary>
+ /// <param name="diff"></param>
+ /// <returns>Inventory after updating</returns>
+ Task<int> UpdateInventoryAsync(int diff);
+ }
+ }
其中增加了以下内容:
- 标记了
ClaptrapState
,使得 State 与 Grain 进行关联。 - 接口继承了
IClaptrapGrain
,这是框架定义的 Grain 接口,这是依托于 Orleans 运行必须继承的接口。 - 增加了 GetInventoryAsync 方法,表示 “获取当前库存”。
- 增加了 UpdateInventoryAsync 方法,表示 “增量更新当前库存”。
diff > 0
表示增加库存,diff < 0
表示减少库存。 - 需要注意的是 Grain 的方法定义有一定限制。详细可以参见《Developing a Grain》。
实现 Grain
定义好 ISkuGrain 之后,便可以添加代码进行实现。
在 HelloClaptrap.Actors
项目新建 Sku
文件夹,并在该文件夹中添加 SkuGrain
类。
+ using System;
+ using System.Threading.Tasks;
+ using HelloClaptrap.IActor;
+ using HelloClaptrap.Models;
+ using HelloClaptrap.Models.Sku;
+ using Newbe.Claptrap;
+ using Newbe.Claptrap.Orleans;
+
+ namespace HelloClaptrap.Actors.Sku
+ {
+ public class SkuGrain : ClaptrapBoxGrain<SkuState>, ISkuGrain
+ {
+ public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService)
+ : base(claptrapGrainCommonService)
+ {
+ }
+
+ public Task<int> GetInventoryAsync()
+ {
+ return Task.FromResult(StateData.Inventory);
+ }
+
+ public async Task<int> UpdateInventoryAsync(int diff)
+ {
+ if (diff == )
+ {
+ throw new BizException("diff can`t be 0");
+ }
+
+ var old = StateData.Inventory;
+ var newInventory = old + diff;
+ if (newInventory < )
+ {
+ throw new BizException(
+ $"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}");
+ }
+
+ throw new NotImplementedException();
+ }
+ }
+ }
其中增加了以下内容:
- 继承
ClaptrapBoxGrain<SkuState>
并实现ISkuGrain
,ClaptrapBoxGrain
是框架定义的 Grain 基类,其中的泛型参数表示对应的 State 类型。 - 实现 GetInventoryAsync 方法,从 StateData 中读取当前的库存。
- 实现 UpdateInventoryAsync 方法,添加业务判断代码,若不满足业务操作的条件则抛出异常。
- UpdateInventoryAsync 的最后我们现在抛出 NotImplementedException ,因为当前事件还没有定义,需要等待后续的代码实现。
- BizException 是一个自定义异常,可以自行添加。实际开发中也可以不使用抛出异常的方式表示业务中断,改用状态码或者其他返回值也是可以的。
注册 Grain
Claptrap 对应的 Grain 需要在应用程序启动时进行注册,这样框架才能扫描发现。
由于示例代码采用的是程序集范围内扫描,因此实际上不需要进行修改。
这里指出发生注册的位置:
打开 HelloClaptrap.BackendServer
项目的 Program
类。
using System;
using Autofac;
using HelloClaptrap.Actors.Cart;
using HelloClaptrap.IActor;
using HelloClaptrap.Repository;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newbe.Claptrap;
using Newbe.Claptrap.Bootstrapper;
using NLog.Web;
using Orleans; namespace HelloClaptrap.BackendServer
{
public class Program
{ public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.UseClaptrap(
builder =>
{
+ builder
+ .ScanClaptrapDesigns(new[]
+ {
+ typeof(ICartGrain).Assembly,
+ typeof(CartGrain).Assembly,
+ });
},
builder => { builder.RegisterModule<RepositoryModule>(); })
.UseOrleansClaptrap()
.UseOrleans(builder => builder.UseDashboard(options => options.Port = ))
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog();
}
}
因为 ISkuGrain 和 SkuGrain 分别于 ICartGrain 和 CartGrain 属于同一程序集,因而此处不需要修改。
定义 EventCode
前面我们已经实现了 Claptrap 的主要部分,但唯独没有完成更新库存的操作。这是因为更新库存是需要对 State 进行更新的。而我们都知道 Claptrap 是基于事件溯源的 Actor 模式,对 State 的更新需要通过事件才能完成。故而由这里开始,我们来通过事件更新库存。
EventCode 是 Claptrap 系统每个事件的唯一编码。其在事件的识别,序列化等方面起到了重要的作用。
打开 HelloClaptrap.Models
项目中的 ClaptrapCodes
类。
添加 “更新库存” 的 EventCode。
namespace HelloClaptrap.Models
{
public static class ClaptrapCodes
{
#region Cart public const string CartGrain = "cart_claptrap_newbe";
private const string CartEventSuffix = "_e_" + CartGrain;
public const string AddItemToCart = "addItem" + CartEventSuffix;
public const string RemoveItemFromCart = "removeItem" + CartEventSuffix;
public const string RemoveAllItemsFromCart = "remoeAllItems" + CartEventSuffix; #endregion #region Sku public const string SkuGrain = "sku_claptrap_newbe";
private const string SkuEventSuffix = "_e_" + SkuGrain;
+ public const string SkuInventoryUpdate = "inventoryUpdate" + SkuEventSuffix; #endregion
}
}
定义 Event
Event 是事件溯源的关键。用于改变 Claptrap 中的 State。并且 Event 会被持久化在持久层。
在 HelloClaptrap.Models
项目的 Sku/Events
文件夹下创建 InventoryUpdateEvent
类。
添加如下代码:
+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Models.Sku.Events
+ {
+ public class InventoryUpdateEvent : IEventData
+ {
+ public int Diff { get; set; }
+ public int NewInventory { get; set; }
+ }
+ }
- Diff 表示此次更新库存的数额,
diff > 0
表示增加库存,diff < 0
表示减少库存。 - NewInventory 表示更新之后的库存。此处,提前给出一个建议,但由于篇幅问题,不展开讨论:建议在事件中包含 State 的更新后数据。
实现 EventHandler
EventHandler
用于将事件更新到 Claptrap 的 State
上。
在 HelloClaptrap.Actors
项目的 Sku/Events
文件夹下创建 InventoryUpdateEventHandler
类。
添加如下代码:
+ using System.Threading.Tasks;
+ using HelloClaptrap.Models.Sku;
+ using HelloClaptrap.Models.Sku.Events;
+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Actors.Sku.Events
+ {
+ public class InventoryUpdateEventHandler
+ : NormalEventHandler<SkuState, InventoryUpdateEvent>
+ {
+ public override ValueTask HandleEvent(SkuState stateData,
+ InventoryUpdateEvent eventData,
+ IEventContext eventContext)
+ {
+ stateData.Inventory = eventData.NewInventory;
+ return new ValueTask();
+ }
+ }
+ }
- 因为事件中已经包含了更新后的库存,故而直接对 StateData 进行赋值即可。
注册 EventHandler
实现并测试完 EventHandler 之后,便可以将 EventHandler 进行注册,以便与 EventCode 以及 Claptrap 进行关联。
打开 HelloClaptrap.Actors
项目的 SkuGrain
类。
使用 Attribute 进行标记,并修改 UpdateInventoryAsync 执行事件。
using System.Threading.Tasks;
+ using HelloClaptrap.Actors.Sku.Events;
using HelloClaptrap.IActor;
using HelloClaptrap.Models;
using HelloClaptrap.Models.Sku;
+ using HelloClaptrap.Models.Sku.Events;
using Newbe.Claptrap;
using Newbe.Claptrap.Orleans; namespace HelloClaptrap.Actors.Sku
{
+ [ClaptrapEventHandler(typeof(InventoryUpdateEventHandler), ClaptrapCodes.SkuInventoryUpdate)]
public class SkuGrain : ClaptrapBoxGrain<SkuState>, ISkuGrain
{
public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService)
: base(claptrapGrainCommonService)
{
} public Task<int> GetInventoryAsync()
{
return Task.FromResult(StateData.Inventory);
} public async Task<int> UpdateInventoryAsync(int diff)
{
if (diff == )
{
throw new BizException("diff can`t be 0");
} var old = StateData.Inventory;
var newInventory = old + diff;
if (newInventory < )
{
throw new BizException(
$"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}");
} - throw new NotImplementedException();
+ var evt = this.CreateEvent(new InventoryUpdateEvent
+ {
+ Diff = diff,
+ NewInventory = newInventory
+ });
+ await Claptrap.HandleEventAsync(evt);
+ return StateData.Inventory;
}
}
}
实现 IInitialStateDataFactory
前面我们已经完成了库存的查询和更新。不过通常来说库存有一个初始数额,我们本节在补充这部分逻辑。
在 HelloClaptrap.Actors
项目的 Sku
文件夹下创建 SkuStateInitHandler
类。
+ using System.Threading.Tasks;
+ using HelloClaptrap.Models.Sku;
+ using HelloClaptrap.Repository;
+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Actors.Sku
+ {
+ public class SkuStateInitHandler : IInitialStateDataFactory
+ {
+ private readonly ISkuRepository _skuRepository;
+
+ public SkuStateInitHandler(
+ ISkuRepository skuRepository)
+ {
+ _skuRepository = skuRepository;
+ }
+
+ public async Task<IStateData> Create(IClaptrapIdentity identity)
+ {
+ var skuId = identity.Id;
+ var inventory = await _skuRepository.GetInitInventoryAsync(skuId);
+ var re = new SkuState
+ {
+ Inventory = inventory
+ };
+ return re;
+ }
+ }
+ }
IInitialStateDataFactory
会在 Claptrap 初次激活时被调用,用来创建 State 的初始值。- 注入
ISkuRepository
从数据库中读取 Sku 对应的库存初始数额,具体的代码此处不进行罗列,读者可以查看样例仓库中的实现。
除了实现代码之外,还需要进行注册才会被调用。
打开 HelloClaptrap.Actors
项目的 SkuGrain
类。
using System.Threading.Tasks;
using HelloClaptrap.Actors.Sku.Events;
using HelloClaptrap.IActor;
using HelloClaptrap.Models;
using HelloClaptrap.Models.Sku;
using HelloClaptrap.Models.Sku.Events;
using Newbe.Claptrap;
using Newbe.Claptrap.Orleans; namespace HelloClaptrap.Actors.Sku
{
+ [ClaptrapStateInitialFactoryHandler(typeof(SkuStateInitHandler))]
[ClaptrapEventHandler(typeof(InventoryUpdateEventHandler), ClaptrapCodes.SkuInventoryUpdate)]
public class SkuGrain : ClaptrapBoxGrain<SkuState>, ISkuGrain
{
public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService)
: base(claptrapGrainCommonService)
{
} public Task<int> GetInventoryAsync()
{
return Task.FromResult(StateData.Inventory);
} public async Task<int> UpdateInventoryAsync(int diff)
{
if (diff == )
{
throw new BizException("diff can`t be 0");
} var old = StateData.Inventory;
var newInventory = old + diff;
if (newInventory < )
{
throw new BizException(
$"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}");
} var evt = this.CreateEvent(new InventoryUpdateEvent
{
Diff = diff,
NewInventory = newInventory
});
await Claptrap.HandleEventAsync(evt);
return StateData.Inventory;
}
}
}
修改 Controller
前面的所有步骤完成之后,就已经完成了 Claptrap 的所有部分。但由于 Claptrap 无法直接提供与外部程序的互操作性。因此,还需要在在 Controller 层增加一个 API 以便外部进行 “读取库存” 的操作。
在 HelloClaptrap.Web
项目的 Controllers
文件夹下新建 SkuController
类。
+ using System.Threading.Tasks;
+ using HelloClaptrap.IActor;
+ using Microsoft.AspNetCore.Mvc;
+ using Orleans;
+
+ namespace HelloClaptrap.Web.Controllers
+ {
+ [Route("api/[controller]")]
+ public class SkuController : Controller
+ {
+ private readonly IGrainFactory _grainFactory;
+
+ public SkuController(
+ IGrainFactory grainFactory)
+ {
+ _grainFactory = grainFactory;
+ }
+
+ [HttpGet("{id}")]
+ public async Task<IActionResult> GetItemsAsync(string id)
+ {
+ var skuGrain = _grainFactory.GetGrain<ISkuGrain>(id);
+ var inventory = await skuGrain.GetInventoryAsync();
+ return Json(new
+ {
+ skuId = id,
+ inventory = inventory,
+ });
+ }
+ }
+ }
- 新增 API 读取特定 SkuId 的库存。按照样例代码的实现,可以传入
yueluo-123
得到库存数额为 666。不存在的 SkuId 将会抛出异常。 - 此处没有创建更新库存的对外 API,因为本示例将在下篇进行下单购物时进行库存操作,此处暂不需要 API。
小结
至此,我们就完成了 “管理商品库存” 这个简单需求的所有内容。
您可以从以下地址来获取本文章对应的源代码:
最后但是最重要!
最近作者正在构建以反应式
、Actor模式
和事件溯源
为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap
本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。
联系方式:
- Github Issue
- Gitee Issue
- 公开邮箱 newbe-claptrap@googlegroups.com (发送到该邮箱的内容将被公开)
- Gitter
您还可以查阅本系列的其他选文:
理论入门篇
术语介绍篇
- Actor 模式
- 事件溯源(Event Sourcing)
- Claptrap
- Minion
- 事件 (Event)
- 状态 (State)
- 状态快照 (State Snapshot)
- Claptrap 设计图 (Claptrap Design)
- Claptrap 工厂 (Claptrap Factory)
- Claptrap Identity
- Claptrap Box
- Claptrap 生命周期(Claptrap Lifetime Scope)
- 序列化(Serialization)
实现入门篇
- Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
- Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
- Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存
样例实践篇
其他番外篇
- 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
- 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
- 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
- docker-mcr 助您全速下载 dotnet 镜像
- 十多位全球技术专家,为你献上近十个小时的.Net 微服务介绍
- 年轻的樵夫哟,你掉的是这个免费 8 核 4G 公网服务器,还是这个随时可用的 Docker 实验平台?
GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap
Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap
您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 claptrap.newbe.pro。
- 本文作者: newbe36524
- 本文链接: https://www.newbe.pro/Newbe.Claptrap/Get-Started-3/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存的更多相关文章
- Newbe.Claptrap 框架入门,第一步 —— 开发环境准备
Newbe.Claptrap 框架依托于一些关键性的基础组件和一些可选的辅助组件.本篇我们来介绍一下如何准备一个开发环境. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如 ...
- Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
让我们来实现一个简单的 “电商购物车” 需求来了解一下如何使用 Newbe.Claptrap 进行开发. 业务需求 实现一个简单的 “电商购物车” 需求,这里实现几个简单的业务: 获取当前购物车中的商 ...
- 轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单
接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开 ...
- Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
接上一篇 Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开始尝试使用 ...
- Newbe.Claptrap 框架入门,第二步 —— 创建项目
接上一篇 Newbe.Claptrap 框架入门,第一步 -- 开发环境准备 ,我们继续了解如何创建一个 Newbe.Claptrap 项目. Newbe.Claptrap 是一个用于轻松应对并发问题 ...
- C#使用Thrift作为RPC框架入门(三)之三层架构
前言 这是我们讲解Thrift框架的第三篇文章,前两篇我们讲了Thrift作为RPC框架的基本用法以及架构的设计.为了我们更好的使用和理解Thrift框架,接下来,我们将来学习一下Thrift框架提供 ...
- Ubuntu14.04 CUDA8.0 CUDN4.0 NVIDIA1080 多种深度框架(懒人三步装) - 从入门到放弃
这是一个懒人快速安装教程,1080卡有点麻烦,因为cuda需要8.0.为了安装方便直接把命令写成三个shell脚本. 代码基本是http://blog.csdn.net/langb2014/artic ...
- Gemini.Workflow 双子工作流入门教程三:定义流程:流程节点、迁移条件参数配置
简介: Gemini.Workflow 双子工作流,是一套功能强大,使用简单的工作流,简称双子流,目前配套集成在Aries框架中. 下面介绍本篇教程:定义流程:流程节点.迁移条件参数配置. 一.普通节 ...
- springboot2.0入门(三)----定义编程风格+jackjson使用+postMan测试
一.RESTFul风格API 1.优点: )看Url就知道要什么资源 )看http method就知道针对资源干什么 )看http status code就知道结果如何 HTTP方法体现对资源的操作: ...
随机推荐
- Java数据结构和算法(2)之稀疏数组
1.定义 稀疏数组可以看做是普通二位数组的压缩,但是这里说的普通数组是值无效数据量远大于有效数据量的数组,关于稀疏数组的运用有五子棋盘,地图等.. *当一个数组中大部分元素为0,或者为同一个值的数组时 ...
- shell脚本报错:-bash: xxx: /bin/sh^M: bad interpreter: No such file or directory
今天执行一个shell脚本,然后在执行的时候报错,脚本内容很简单,仅供测试: #!/bin/sh echo "test shell " 具体报错信息如下 [root@localho ...
- SpringBoot整合Mail发送邮件&发送模板邮件
整合mail发送邮件,其实就是通过代码来操作发送邮件的步骤,编辑收件人.邮件内容.邮件附件等等.通过邮件可以拓展出短信验证码.消息通知等业务. 一.pom文件引入依赖 <dependency&g ...
- PHP filectime() 函数
定义和用法 filectime() 函数返回指定文件的上次修改时间. 该函数将检查文件的日常修改情况和 inode 修改情况.inode 修改情况是指:权限的修改.所有者的修改.用户组的修改或其他元数 ...
- explain关键字使用解释
原文: 58沈剑 架构师之路 https://mp.weixin.qq.com/s/oWNrLHwqM-0ObuYbuGj98A <数据库允许空值,往往是悲剧的开始>一文通过explai ...
- QueryRunner使用总结
使用JDBC技术是一件繁琐的事情,为了使数据库更加高效,有一种简化jdbc技术的操作--DBUtils.DbUtils(org.apache.commons.dbutils.DbUtils)是Apac ...
- Spring Boot 自定义数据源 DruidDataSource
https://blog.csdn.net/wangmx1993328/article/details/81865153 springboot 使用DruidDataSource 数据源 一.添加 ...
- Hive对字段进行urlDecode
最近项目中需要对埋点日志hive表进行分析,并且按一定的规则统计出来满足要求的用户pin.本来以为是一件比较简单的事,结果在查看导出的词表时发现很多带有"%"的明显具有url en ...
- 未来云原生世界的“领头羊”:容器批量计算项目Volcano 1.0版本发布
在刚刚结束的CLOUD NATIVE+ OPEN SOURCE Virtual Summit China 2020上,由华为云云原生团队主导的容器批量计算项目Volcano正式发布1.0版本,标志着V ...
- Java多线程入门及实战
基本概念: 1: 程序 2 进程 3 线程 4 进程和线程的区别 5 进程和程序的区别 Java实现多线程的方法: 1 继承Thread 2 实现Runable 3 实现callable 4 线程池的 ...