大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的一块垫脚石,我们一起精进。

EF Core集成

EF Core是微软的ORM,可以使用它与主流的数据库提供商合作,如SQL Server、Oracle、MySQL、PostgreSQL和Cosmos DB。当您使用ABP命令行界面(CLI)创建新的ABP解决方案时,它是默认的数据库提供程序。

默认情况下,启动模板使用SQL Server。如果您更喜欢其他的数据库管理系统(DBMS),可以在创建新解决方案时指定-DBMS参数,如下所示:

abp new DemoApp -dbms MySQL

您可以参考ABP的文档,了解最新支持的数据库选项,以及如何切换到其他现成数据库提供程序。

在接下来您将了解到:

  • 如何配置DBMS;
  • 如何定义DbContext类;
  • 如何注册到依赖注入(DI)系统;
  • 如何将实体映射到数据库表;
  • 如何使用Code First和为实体创建自定义存储库;
  • 如何为实体加载相关数据的不同方式。

3.1 配置 DBMS

我们使用AbpDbContextOptions在模块的ConfigureServices方法中配置DBMS。以下示例使用SQL Server作为DBMS进行配置:

Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer();
});

当然,如果希望配置不同的DBMS,那么UseSqlServer()方法调用将有所不同。我们不需要设置连接字符串,因为它是从ConnectionString:Default配置自动获得的。你可以查看appsettings.json文件,以查看和更改连接字符串。

配置了DBMS,但还没有定义DbContext对象,这是在EF Core中使用数据库所必需的,我接下来看看如何配置:

3.2 定义 DbContext

DbContext是EF Core中与数据库交互的主要对象。通常创建一个继承自DbContext的类来创建自己的DbContext。使用ABP框架,我们将继承AbpDbContext。

下面是一个使用ABP框架的DbContext类定义示例:

using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace FormsApp
{
public class FormsAppDbContext : AbpDbContext<FormsAppDbContext>
{
public DbSet<Form> Forms { get; set; }
public FormsAppDbContext(DbContextOptions<FormsAppDbContext> options)
: base(options)
{
}
}
}

FormsAppDbContext继承自AbpDbContext<FormsAppDbContext>AbpDbContext是一个泛型类,将DbContext类型作为泛型参数。它还迫使我们创建一个构造函数。然后,我们就可以为实体添加DbSet属性。

一旦定义了DbContext,我们就应该向DI系统注册它,以便在应用程序中使用它。

3.3 向 DI 注册 DbContext

AddAbpDbContext扩展方法用于向DI系统注册DbContext类。您可以在模块的ConfigureServices方法中使用此方法(它位于启动解决方案的EntityFrameworkCore项目中),如以下代码块所示:

public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<FormsAppDbContext> (options =>
{
//启用默认通用存储库,DDD应始终通过聚合根访问子实体
options.AddDefaultRepositories(); //开启后,非聚合根实体也支持IRepository注入
//options.AddDefaultRepositories(includeAllEntities: true);
});
}

AddDefaultRepositories()用于为与DbContext相关的实体启用默认通用存储库。默认情况下,它仅为聚合根实体启用通用存储库,因为在域驱动设计(DDD)中,子实体应始终通过聚合根进行访问。如果还想将存储库用于其他实体类型,可以将可选的includealentities参数设置为true

options.AddDefaultRepositories(includeAllEntities: true);

使用此选项,意味着您可以为应用程序的任何实体注入IRepository服务。

注意:因为从事关系数据库的开发人员习惯于从所有数据库表中查询,如果要严格应用 DDD 原则,则应始终使用聚合根来访问子实体。

我们已经了解了如何注册DbContext类,我们可以为DbContext类中的所有实体注入和使用IRepository接口。接下来,我们应该首先为实体配置EF Core映射。

3.4 配置实体映射

EF Core是一个对象到关系的映射器,它将实体映射到数据库表。我们可以通过以下两种方式配置这些映射的详细信息:

  • 在实体类上使用数据注释属性
  • 通过重写OnModelCreating方法在内部使用 Fluent API(推荐)

使用数据注释属性会领域层依赖于EF Core,如果这对您来说不是问题,您可以遵循EF Core的文档使用这些属性。为了解脱依赖,同时也为了保持实体类的纯洁度,推荐使用Fluent API方法。

要使用Fluent API方法,可以在DbContext类中重写OnModelCreating方法,如以下代码块所示:

public class FormsAppDbContext : AbpDbContext<FormsAppDbContext>
{
...
//1.override覆盖后,依然会调用父类的base.OnModelCreating(),因为内置审计日志和数据过滤
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder); 2.Fluent API,这里可以继续封装(TODO)
builder.Entity<Form>(b =>
{
b.ToTable("Forms");
b.ConfigureByConvention(); //3.重要,默认配置预定义的Entity或AggregateRoot,无需再配置,剩下的配置就显得整洁而规范了。
b.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
b.HasIndex(x => x.Name);
}); //4.一对多的配置
builder.Entity<Question>(b =>
{
b.ToTable("FormQuestions");
b.ConfigureByConvention();
b.Property(x => x.Title)
.HasMaxLength(200)
.IsRequired();
b.HasOne<Form>() //5.一个问题对应一个表单,一个表单有多个问题。
.WithMany(x => x.Questions)
.HasForeignKey(x => x.FormId)
.IsRequired();
});
}
}

重写OnModelCreating方法时,始终调用base.OnModelCreating(),因为该方法内执行默认配置(如审核日志和数据过滤器)。然后,使用builder对象执行配置。

例如,我们可以为本章中定义的表单类配置映射,如下所示:

builder.Entity<Form>(b => {
b.ToTable("Forms");
b.ConfigureByConvention();
b.Property(x => x.Name).HasMaxLength(100) .IsRequired();
b.HasIndex(x => x.Name);
});

在这里调用b.ConfigureByConvention方法很重要。如果实体派生自ABP的预定义实体或AggregateRoot类,它将配置实体的基本属性。剩下的配置代码非常干净和标准,您可以从EF Core的文档中了解所有细节。

下面是另一个配置实体之间关系的示例:

builder.Entity<Question>(b => {
b.ToTable("FormQuestions");
b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(200).IsRequired();
b.HasOne<Form>().WithMany(x => x.Questions).HasForeignKey(x => x.FormId).IsRequired();
});

在这个例子中,我们定义了表单和问题实体之间的关系:一个表单可以有许多问题,而一个问题属于一个表单。

EF的 Code First Migrations系统提供了一种高效的方法来增量更新数据库,使其与实体保持同步。

Code First相比较传统迁移的好处:

  • 高效快速
  • 增量更新
  • 版本管理

3.5 实现自定义存储库

我们在“自定义存储库”部分创建了一个IFormRepository接口。现在,是时候使用EF Core实现这个存储库接口了。

在解决方案的EF Core集成项目中实现存储库,如下所示:

//1.集成自EfCoreRepository,传入三个泛型参数,继承了所有标准存储库的方法。
public class FormRepository : EfCoreRepository<FormsAppDbContext, Form, Guid>,IFormRepository
{
public FormRepository(IDbContextProvider<FormsAppDbContext> dbContextProvider)
: base(dbContextProvider){ } public async Task<List<Form>> GetListAsync(string name, bool includeDrafts = false)
{
var dbContext = await GetDbContextAsync();
var query = dbContext.Forms.Where(f => f.Name.Contains(name));
if (!includeDrafts)
{
query = query.Where(f => !f.IsDraft);
}
return await query.ToListAsync();
}
}

该类派生自ABP的EfCoreRepository类。通过这种方式,我们继承了所有标准的存储库方法。EfCoreRepository类获得三个通用参数:DbContext类型、实体类型和实体类的PK类型。

FormRepository还实现了IFormRepository,它定义了一个GetListAsync方法,DbContext实例在这个方法中可以使用EF Core API的所有功能。

关于WhereIf的提示:

条件过滤是一种广泛使用的模式,ABP提供了一种很好的WhereIf扩展方法,可以简化我们的代码。

我们可以重写GetListAsync方法,如下代码块所示:

var dbContext = await GetDbContextAsync();
return await dbContext.Forms
.Where(f => f.Name.Contains(name))
.WhereIf(!includeDrafts, f => !f.IsDraft)
.ToListAsync();

因为我们有DbContext实例,所以可以使用它执行结构化查询语言(SQL)命令或存储过程。下面是执行“删除所有表单”命令:

public async Task DeleteAllDraftsAsync()
{
var dbContext = await GetDbContextAsync();
//执行SQL查询
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM Forms WHERE IsDraft = 1");
}

执行存储过程和函数,请参考EF的核心文档学习如何执行存储过程和函数。

一旦实现了IFormRepository,就可以注入并使用它,而不是IRepository<Form,Guid>,如下所示:

1)自定义存储库的调用

public class FormService : ITransientDependency
{
private readonly IFormRepository _formRepository;//自定义仓储库
public FormService(IFormRepository formRepository)
{
_formRepository = formRepository;
} public async Task<List<Form>> GetFormsAsync(string name)
{
return await _formRepository.GetListAsync(name, includeDrafts: true);
}
}

FormService类使用IFormRepository的自定义GetListAsync方法。即使为表单实现了自定义存储库类,仍然可以为该实体注入并使用默认的通用存储库(例如,IRepository<Form,Guid>),尤其是刚开始不熟悉,可以从通用存储库上手,等熟悉后就可以使用自定义存储库。

2)自定义存储库的配置

如果重写EfCoreRepository类中的基方法并,可能会出现一个潜在问题:使用通用存储库的服务将继续使用非重写方法。要防止这种情况,请在向DI注册DbContext时使用AddRepository方法,如下所示:

context.Services.AddAbpDbContext<FormsAppDbContext>(options =>
{
options.AddDefaultRepositories();
//实现仓储库后,建议进行注入
options.AddRepository<Form, FormRepository>();
});

通过这种配置,AddRepository方法将通用存储库重定向到自定义存储库类。

3.7 数据加载

如果您的实体具有指向其他实体的导航属性或具有其他实体的集合,则在使用主实体时,您经常需要访问这些相关实体。例如,前面介绍的表单实体有一组问题实体,您可能需要在使用表单对象时访问这些问题集。

访问相关实体有多种方式,包括:

  • 显式加载
  • 延迟加载
  • 即时加载

1)显式加载

存储库提供了EnsureRepropertyLoadedAsyncEnsureRecollectionLoadedAsync扩展方法,以显式加载导航属性或子集合。

例如,我们可以显式加载表单的问题,如以下代码块所示:

public async Task<IEnumerable<Question>> GetQuestionsAsync(Form form)
{
//
await _formRepository.EnsureCollectionLoadedAsync(form, f => f.Questions);
return form.Questions;
}

如果不用EnsureCollectionLoadedAsyncQuestions可能是空的,如果已经加载过,不会重复加载,所以多次调用对性能没有影响。

2)延迟加载

延迟加载是EF Core的一项功能,它在您首次访问相关属性和集合时加载它们。默认情况下不启用延迟加载。如果要为DbContext启用它,请执行以下步骤:

  1. 在 EF Core 层中安装Microsoft.EntityFrameworkCore.Proxies
  2. 配置时使用 UseLazyLoadingProxies方法
Configure<AbpDbContextOptions>(options =>
{
options.PreConfigure<FormsAppDbContext>(opts =>
{
opts.DbContextOptions.UseLazyLoadingProxies();
});
options.UseSqlServer();
});
  • 确保导航属性和集合属性在实体中是virtual
public class Form : BasicAggregateRoot<Guid>
{
...
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<FormManager> Owners { get; set; }
}

当您启用延迟加载时,您无需再使用显式加载。

延迟加载是一个被讨论过的ORM概念。一些开发人员发现它很实用,而其他人则建议不要使用它。我之所以不使用它,是因为它有一些潜在的问题,比如:

  • 无法使用异步

延迟加载不能使用异步编程,无法使用async/await模式访问属性。因此,它会阻止调用线程,这对于吞吐量和可伸缩性来说是一种糟糕的做法。

  • 1+N性能问题

如果在使用foreach循环之前没有预先加载相关数据,则可能会出现1+N加载问题。1+N加载意味着通过单个数据库操作1次(比如,从数据库中查询实体列表),然后执行一个循环来访问这些实体的导航属性(或集合)。在这种情况下,它会延迟加载每个循环内的相关属性(N=第一次数据库操作中查询的实体数)。因此,进行1+N数据库调用,会显著降低应用程序性能。

  • 断言和代码优化问题

因为您可能不容易看到相关数据何时从数据库加载。我建议采用一种更可控的方法,尽可能使用即时加载

3)即时加载

顾名思义,即时加载是在首先查询主实体时加载相关数据的一种方式。假设您已经创建了一个自定义存储库,以便在从数据库获取表单对象时加载相关问题,如下所示:

  • EF Core层,在自定义仓储库中使用EF Core API
public async Task<Form> GetWithQuestions(Guid formId)
{
var dbContext = await GetDbContextAsync();
return await dbContext.Forms
.Include(f => f.Questions)
.SingleAsync(f => f.Id == formId);
}

自定义存储库方法,可以使用完整的EF Core API。但是,如果您使用的是ABP的存储库,并且不想在应用程序层依赖EF Core,那么就不能使用EF CoreInclude 扩展方法(用于快速加载相关数据)。

假如你不想在应用层依赖EF Core API该怎么办?

在本例中,您有两个选项:

1)IRepository.WithDetailsAsync

IRepositoryWithDetailsSync方法通过包含给定的属性或集合来返回IQueryable实例,如下所示:

public async Task EagerLoadDemoAsync(Guid formId)
{
var queryable = await _formRepository.WithDetailsAsync(f => f.Questions);
var query = queryable.Where(f => f.Id == formId);
var form = await _asyncExecuter.FirstOrDefaultAsync(query);
foreach (var question in form.Questions)
{
//...
}
}

WithDetailsAsync(f=>f.Questions)返回IQueryable<Form>,其中包含form.Questions,因此我们可以安全地循环表单。IAsyncQueryableExecuter在本章的“通用存储库”部分进行了介绍。如果需要,WithDetailsSync方法可以获取多个表达式以包含多个属性。如果需要嵌套包含(EF Core中的ThenClude扩展方法),则不能使用WithDetailsAsync

2)聚合模式

聚合模式将在第10章DDD——领域层中详细介绍。可以简单地理解:一个聚合被认为是一个单一的单元,它与所有子集合一起作为单个单元进行读取和保存。这意味着您在加载Form时总是加载相关Questions

ABP很好地支持聚合模式,并允许您在全局点为实体配置即时加载。我们可以在模块类的ConfigureServices方法中编写以下配置(在解决方案的EntityFrameworkCore项目中):

Configure<AbpEntityOptions>(options =>
{
options.Entity<Form>(orderOptions =>
{
//全局点为实体配置预加载
orderOptions.DefaultWithDetailsFunc = query => query
.Include(f => f.Questions)
.Include(f => f.Owners);
});
});

建议包括所有子集合。如上所示配置DefaultWithDetailsFunc方法后,将发生以下情况

  • 默认情况下,返回单个实体(如GetAsync)的存储库方法将加载相关实体,除非通过在方法调用中将includeDetails参数指定为false来明确禁用该行为。
  • 返回多个实体(如GetListAsync)的存储库方法将允许相关实体的即时加载,而默认情况下它们不会即时加载。

下面是一些例子,获取包含子集合的单一表单,如下所示:

//获取一个包含子集合的表单
var form = await _formRepository.GetAsync(formId); //获取一个没有子集合的表单
var form = await _formRepository.GetAsync(formId, includeDetails: false); //获取没有子集合的表单列表
var forms = await _formRepository.GetListAsync(f => f.Name.StartsWith("A")); //获取包含子集合的表单列表
var forms = await _formRepository.GetListAsync(f => f.Name.StartsWith("A"), includeDetails: true);

聚合模式在大多数情况下简化了应用程序代码,而在需要性能优化的情况下,您可以进行微调。请注意,如果真正实现聚合模式,则不会使用导航属性(指向其他聚合),我们将在第10章DDD——领域层中再次回到这个主题。

了解UoW

UoW是ABP用来启动、管理和处理数据库连接和事务的主要系统。UoW采用环境上下文模式(Ambient Context pattern)设计。这意味着,当我们创建一个新的UoW时,它会创建一个作用域上下文,该上下文中共享所有数据库操作=。UoW中完成的所有操作都会一起提交(成功时)或回滚(异常时)。

配置UoW选项

ASP.NET Core中,默认设置下,HTTP请求被视为一个UoW。ABP在请求开始时启动UoW,如果请求成功完成,则将更改保存到数据库中。如果请求因异常而失败,它将回滚。

ABP根据HTTP请求类型确定数据库事务使用情况。HTTP GET请求不会创建数据库事务。UoW仍然可以工作,但在这种情况下不使用数据库事务。如果您没有对所有其他HTTP请求类型(POSTPUTDELETE和其他)进行配置,则它们将使用数据库事务

HTTP请求 是否创建事务
GET 不创建事务
PUT 创建事务
POST 创建事务

最好不要在GET请求中更改数据库。如果在一个GET请求中进行了多个写操作,但请求以某种方式失败,那么数据库状态可能会处于不一致的状态,因为ABP不会为GET请求创建数据库事务。在这种情况下,可以使用AbpUnitOfWorkDefaultOptionsGET请求启用事务,也可以手动控制UoW。

为GET启用请求事务的配置:

在模块(在数据库集成项目中)的ConfigureServices方法中使用AbpUnitOfWorkDefaultOptions,如下所示:

public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
options.Timeout = 300000; // 5 minutes
options.IsolationLevel = IsolationLevel.Serializable;
});
}

TransactionBehavior的三个值:

  • Auto(默认):自动使用事务(为非GET HTTP请求启用事务)
  • Enabled:始终使用事务,即使对于HTTP GET请求
  • Disabled: 从不使用事务

Auto是默认值,对于大多数应用推荐使用。IsolationLevel仅对关系数据库有效。如果未指定,ABP将使用基础提供程序的默认值。最后,Timeout选项允许将事务的默认超时值设置为毫秒,如果UoW操作未在给定的超时值内完成,将引发超时异常。

以上,我们学习了如何在全局配置UOW默认选项,也可以为单个UoW手动配置这些值。

手动控制UoW

对于web应用,一般很少需要手动控制UoW。但是,对于后台作业或非web应用程序,您可能需要自己创建UoW作用域。

使用特性

创建UoW作用域的一种方法是在方法上使用[UnitOfWork]属性,如下所示:

[UnitOfWork(isTransactional: true)]
public async Task DoItAsync()
{     
await _formRepository.InsertAsync(new Form() { ... });     
await _formRepository.InsertAsync(new Form() { ... });
}

如果周围的UoW已经就位,那么UnitOfWork特性将被忽略。否则,ABP会在进入DoItAsync方法之前启动一个新的事务UoW,并在不引发异常的情况下提交事务。如果该方法引发异常,事务将回滚。

使用注入服务

如果要精细控制UoW,可以注入并使用IUnitOfWorkManager服务,如以下代码块所示:

public async Task DoItAsync()
{
using (var uow = _unitOfWorkManager.Begin(requiresNew: true,isTransactional: true, timeout: 15000))
{
await _formRepository.InsertAsync(new Form() { });
await _formRepository.InsertAsync(new Form() { });
await uow.CompleteAsync();
}
}

在本例中,我们将启动一个新的事务性UoW作用域,timeout参数的值为15秒。使用这种用法(requiresNew: true),ABP总是启动一个新的UoW,即使周围已经有一个UoW。如果一切正常,会调用uow.CompleteAsync()方法。如果要回滚当前事务,请使用uow.RollbackAsync()方法。

如前所述,UoW使用环境作用域。您可以使用IUnitOfWorkManager.Current访问此范围内的任何位置的当前UoW。如果没有正在进行的UoW,则可以为null

下面的代码段将SaveChangesAsync方法与IUnitOfWorkManager.Current属性一起使用:

await _unitOfWorkManager.Current.SaveChangesAsync();

我们将所有挂起的更改保存到数据库中。但是,如果这是事务性UoW,那么如果回滚UoW或在UoW范围内引发任何异常,这些更改也会回滚。

小结 & 思考

  • 小结:ABP 框架可以与任何数据库系统一起工作,同时它提供了与EF Core和MongoDB的内置集成包。
  • 思考:假如你不想在应用层依赖EF Core API,或者用的是ABP仓储库该怎么办?

ABP框架之——数据访问基础架构(下)的更多相关文章

  1. ABP框架之——数据访问基础架构

    大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享阅读心得,希望我的文章能成为你成长路上的一块垫脚石,我们一起精进. 几乎所有的业务应用程序都要适用一种数据库基础架构,用来实现数据访问逻辑,以便从数 ...

  2. 探索ABP基础架构-下

    配置应用程序 ASP.NET Core 的配置系统提供了一个基于键值对的配置方法.它是一个可扩展的系统,可以从各种资源中读取键值对,例如 JSON 设置文件.环境变量.命令行参数等等. 设置配置值 默 ...

  3. PHP数据访问基础知识(20161028)

    数据访问 动态页面的特征:能够读取数据库,网页的内容都是从数据库读出来的,而不是写死的 所有的程序归根结底都是对数据的增删改查 如何用服务器的PHP来操作服务器的MySQL,Apache则是用来管理, ...

  4. .Net ABP 框架 service 无法访问

    最近在看ABP框架,按照文档写好service后,怎么也访问不到,后来发现,忘记把service类设置为public的了! 不写public ABP框架就不能将这个service映射为controll ...

  5. .NET中微软实体框架的数据访问方法

    介绍 本文的目的是解释微软的实体框架提供的三种数据访问方法.网上有好几篇关于这个话题的好文章,但是我想以一个教程的形式更详细地介绍这个话题,这个教程对于开始学习实体框架及其方法的人来说是个入门.我们将 ...

  6. 【ABP框架系列学习】N层架构(3)

    目录 0.引言 1.DDD分层 2.ABP应用构架模型 客户端应用程序(Client Applications) 表现层(Presentation Layer) 分布式服务层(Distributed ...

  7. webapi框架搭建-数据访问ef code first

    webapi框架搭建系列博客 为什么用ef? 我相信很多博友和我一样都有这种“选择困难症”,我曾经有,现在也有,这是技术人的一个通病——总想用“更完美”的方式去实现,导致在技术选择上犹豫不决,或总是推 ...

  8. php数据访问基础

    建一个连接(造一个连接对象) $db = new MySqli("IP地址或者域名,若果是本地则用localhost","用户名","数据库密码&qu ...

  9. EF – 3.EF数据查询基础(下)数据关联

    5.5.1 <关于“数据关联”,你不一定清楚的事> 这讲视频比较全面地介绍了“一对一”.“一对多”和“多对多”三种数据关联类型在关系数据库和Entity Framework数据模型中的实现 ...

随机推荐

  1. 我们如何上传docker到habor上呢

    Docker 打包上传habor认证 首先在 Maven 的配置文件 setting.xml 中增加相关 server 配置,主要配置 Docker registry(远程仓库)用户认证信息. < ...

  2. Python学习报告及后续学习计划

    第一次有学习Python的想法是源于寒假在家的时候,高中同学问我是否学了Python(用于深度学习),当时就到b站收藏了黑马最新的教学视频,但是"收藏过等于我看了",后续就是过完年 ...

  3. OpenHarmony 3.1 Beta版本关键特性解析——OpenHarmony图形框架

    (以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 李煜 华为技术有限公司 崔坤华为技术有限公司 众所周知,动画是系统和应用与用户交互的重要环节.动画效果的好坏会直接影响 ...

  4. JavaScript学习基础1

    ##JavaScript: # 概念:一门运行在客户端的脚本语言 *运行在客户端浏览器中的.属于前端三件套之一,属于静态资源,每一个浏览器都有JavaScript的解析引擎 *脚本语言:不需要编译,直 ...

  5. Ranchar中PostgreSQL容器异常 53100: could not resize shared memory segment ... bytes: No space left on device

    问题: 客户查报表时描述查询一天的报表能出来,查询一个月的报表不能出来 分析原因: 从下图的异常中分析是PostgreSQL 的共享内存过小,容器默认的/dev/shm大小为64M 解决方案:调整ra ...

  6. Java之IO流技术详解

    何为IO? 首先,我们看看百度给出的解释. I/O输入/输出(Input/Output),分为IO设备和IO接口两个部分. i是写入,Input的首字母.o是输出,Output的首字母. IO 也称为 ...

  7. 攻防世界-MISC:base64stego

    这是攻防世界新手练习区的第十一题,题目如下: 点击下载附件一,发现是一个压缩包,点击解压,发现是需要密码才能解密 先用010editor打开这个压缩包,这里需要知道zip压缩包的组成部分,包括压缩源文 ...

  8. pyqt5 重启相同线程错误:QThread: Destroyed while thread is still running

    背景: 把一个基于QObject的类的槽运行在另一个线程,我们可以用moveToThread的方法. 1 新建一个子线程类,编写槽函数和信号,MyClass *m_MyClass=new MyClas ...

  9. Nginx报错收集

    在安装完成ngixn之后,访问页面显示空白,报错信息里面有这一条报错信息: tailf /usr/local/nginx/logs/error.log 2018/10/26 10:58:00 [err ...

  10. MySQL主从配置及haproxy和keepalived搭建

    本篇文章主要介绍如何搭建MySQL主主配置.主从配置.haproxy.keepalived,已经搭建过程中的一些问题.本次运行环境是在docker中,也会介绍一些docker的知识 docker 小知 ...