.NET仓储模式高级用例

主要结论

  • 如果需要执行基本CURD之外的其他操作,此时就有必要使用仓储(Repository)。

  • 为了促进测试工作并改善可靠性,应将仓储视作可重复使用的库(Library)。

  • 将安全和审计功能放入仓储中可减少Bug并简化应用程序。

  • 对ORM的选择不会限制仓储的用途,只会影响仓储承担的工作量。

在之前发布的文章使用实体框架、Dapper和Chain的仓储模式实现策略中,我们介绍了实现仓储所需的基本模式。很多情况下,这些模式只是围绕底层数据访问技术,本质上并非完全必要的薄层。然而通过构建这样的仓储将获得很多新的机会。

在设计仓储时,需要从“必须发生的事”这个角度来思考。例如,假设制订了一条规则,每当一条记录被更新后,其“LastModifiedBy”列必须设置为当前用户。但我们并不需要在每次保存前更新应用程序代码中的LastModifiedBy,可以直接将相关函数放在仓储中。

通过将数据访问层视作管理所有“必须发生的事情”细节的独立库,即可大幅减少实现过程中的错误数量。与此同时可以简化基于仓储构建的代码,因为已经不再需要考虑“记账”之类的任务。

注意:本文会尽量提供适用于实体框架(Entity Framework)、Dapper和/或Tortuga Chain的代码范例,然而大部分仓储功能均可通过不依赖具体ORM的方式实现。

审计列

大部分应用程序最终需要追踪谁在什么时间更改了数据库。对于简单的数据库,这是通过审计列(Audit column)的形式实现的。虽然名称可能各不相同,但审计列通常主要承担下列四个角色:

  • 创建者的User Key

  • 创建日期/时间

  • 最后修改者的User Key

  • 最后修改日期/时间

取决于应用程序的安全需求,可能还存在其他审计列,例如:

  • 删除者的User Key

  • 删除日期/时间

  • [创建 | 最后修改 | 删除] 者的Application Key

  • [创建 | 最后修改 | 删除] 者的IP地址

从技术角度来看日期列很容易处理,但User Key的处理就需要费些功夫了,这里需要的是“可感知上下文的仓储”。

常规的仓储是无法感知上下文的,这意味着除了连接数据库时绝对必要的信息,仓储无法获知其他任何信息。如果能正确地设计,仓储可以是彻底无状态(Stateless)的,这样即可在整个应用程序中共享一个实例。

可感知上下文的仓储略微复杂。除非了解上下文,否则无法创建这种仓储,而上下文至少要包含当前活跃用户的ID和Key。对于某些应用程序这就够了,但对于其他应用程序,我们可能还需要传递整个用户对象和/或代表运行中应用程序的对象。

Chain

Chain通过一种名为审计规则(Audit rule)的功能为此提供了内建的支持。审计规则可供我们根据列名指定要覆盖(Override)的值。该功能包含了拆箱即用的基于日期的规则,以及从用户对象将属性复制到列的规则。范例:

dataSource = dataSource.WithRules(new UserDataRule("CreatedByKey", "UserKey", OperationType.Insert),new UserDataRule("UpdatedByKey", "UserKey", OperationType.InsertOrUpdate),new DateTimeRule("CreatedDate", DateTimeKind.Local, OperationType.Insert),
new DateTimeRule("UpdatedDate", DateTimeKind.Local, OperationType.InsertOrUpdate));

如上所述为了实现这一点我们需要一种可感知上下文的仓储。从下列构造函数中可以看到如何将上下文传递给不可变数据源,并使用必要信息新建数据源。

public EmployeeRepository(DataSource dataSource, User user)
{m_DataSource = dataSource.WithUser(user);
}

借此即可使用自行选择的DI框架针对每个请求自动创建并填写仓储。

实体框架

为了在实体框架中实现审计列的全局应用,我们需要利用ObjectStateManager并创建一个专用接口。该接口(如果愿意也可以称之为“基类(Base class)”)看起来类似这样:

public interface IAuditableEntity 
{DateTime CreatedDate {get; set;}DateTime UpdatedDate {get; set;}DateTime CreatedDate {get; set;}DateTime CreatedDate {get; set;}
}

随后该接口(或基类)会应用给数据库中与审计列匹配的每个实体。

随后需要通过下列方式对DataContext类的Save方法进行覆盖(Override)。

public override int SaveChanges()
{// Get added entriesIEnumerable<ObjectStateEntry> addedEntryCollection = Context.ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(m => m != null && m.Entity != null);// Get modified entriesIEnumerable<ObjectStateEntry> modifiedEntryCollection = Context.ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(m => m != null && m.Entity != null);// Set audit fields of added entriesforeach (ObjectStateEntry entry in addedEntryCollection){                var addedEntity = entry.Entity as IAuditableEntity;if (addedEntity != null){addedEntity.CreatedDate = DateTime.Now;addedEntity.CreatedByKey = m_User.UserKey;addedEntity.UpdatedDate = DateTime.Now;addedEntity.UpdatedByKey = m_User.UserKey;}}// Set audit fields of modified entriesforeach (ObjectStateEntry entry in modifiedEntryCollection){var modifiedEntity = entry.Entity as IAuditableEntity;if (modifiedEntity != null){modifiedEntity.UpdatedDate = DateTime.Now;modifiedEntity.UpdatedByKey = m_User.UserKey;}}return SaveChanges();
}

如果需要大量使用实体框架(EF),则有必要非常熟悉ObjectStateManager及其能力。因为有关进行中事务的大部分有用元数据都包含在ObjectStateManager中。

最后还需要修改数据上下文(可能还有仓储)的构造函数以使其接受用户对象。

虽然看似这要编写大量代码,但每个EF数据上下文只需要编写一次。与上文的范例类似,数据上下文和仓储的实际创建工作可由DI框架负责进行。

历史表

很多地方性的法规和制度要求对记录的改动进行追踪,此外这样做也可以简化诊断工作。

对此的常规建议是直接让数据库自行处理。一些数据库内建包含了类似的功能,这类功能通常叫做时间表(Temporal table)。其他数据库则可使用触发器模拟出类似的功能。无论哪种情况,应用程序都不会发现额外的日志操作,因此这种技术出错的概率也得以大幅降低。

如果出于某些原因无法使用时间表或触发器,那么仓储需要能明确写入历史表。

无论将维持历史表的代码放在哪里,都有两个基本惯例需要遵循。一致性在这里真的很重要,如果一些表遵循一个惯例,其他表遵循另一个管理,最终只能造成混乱。

写入前复制:在这个惯例中,需要在实际执行更新或删除操作前将老的记录从活动(Live)表复制到历史表。这意味着历史表绝对不会包含当前记录。因此需要将活动表和历史表联接在一起才能看到完整的变更历史。

复制前写入:或者可以首先更新活动表,随后将该行复制到历史表。这种做法的优势在于历史表中包含完整的记录,无需进行上文提到的联接。不足之处在于,由于这种做法需要复制数据,因此会耗费更多空间。

无论哪种惯例,都可以使用软删除了解是谁实际删除了行。如果需要使用硬删除,也只能在执行软删除之后再进行硬删除。

软删除

使用仓储可获得的另一个优势在于可以在应用程序无法察觉的情况下从硬删除切换为软删除。软删除可用能被应用程序察觉的方式删除记录,但删除的记录可继续保留在数据库中,以便用于审计等用途。此外在必要时应用程序还可以恢复被软删除的记录。

为避免数据丢失,不应针对为软删除提供支持的表为应用程序分配DELETE特权。如果应用程序无意中试图执行硬删除,权限检查功能会显示错误信息,而不会直接删除行。

Chain

Chain通过自己的审计规则基础架构提供了隐式的软删除支持。在配置软删除规则后,按照习惯还需要配置匹配审计(Matching audit)列:

var dataSource = dataSource.WithRules(new SoftDeleteRule("DeletedFlag", true, OperationTypes.SelectOrDelete),new UserDataRule("DeletedByKey", "EmployeeKey", OperationTypes.Delete),new DateTimeRule("DeletedDate", DateTimeKind.Local, OperationTypes.Delete));

在发现表包含软删除列(例如本例中的DeletedFlag)后,会自动发生两件事:

  • 所有查询的WHERE子句可暗中添加“AND DeletedFlag = 0”。

  • 所有对DataSource.Delete的调用将变成更新(Update)语句以设置 deleted flag。

实体框架

在实体框架中,可以在读取为软删除提供支持的表的每个查询中包含一个额外的Where子句。此外还需要将任何删除操作手工转换为更新操作,使用对象图(Object graph)时这一点可能较难办到。

另一种方法的过程较繁琐,但可能更不易出错。首先在DataContext.OnModelCreating覆盖中明确列出每个支持软删除的表。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{modelBuilder.Entity<Employee>().
Map(m => m.Requires("IsDeleted").HasValue(false)); }

随后需要覆盖Save方法以确保删除操作可变成更新操作。Stackoverflow上的Colin提供了这种模式。

public override int SaveChanges()
{foreach (var entry in ChangeTracker.Entries().Where(p => p.State == EntityState.Deleted && p.Entity is ModelBase))SoftDelete(entry);return base.SaveChanges();
}private void SoftDelete(DbEntityEntry entry)
{var e = (ModelBase)entry.Entity;string tableName = GetTableName(e.GetType());Database.ExecuteSqlCommand(String.Format("UPDATE {0} SET IsDeleted = 1 WHERE ID = @id", tableName), new SqlParameter("id", e.ID));//Marking it Detached prevents the hard deleteentry.State = EntityState.Detached;
}

建议阅读Colin回答中的剩余内容,这些回答解决了很多边界案例问题。

访问日志记录

虽然审计列、历史表,以及软删除均适用于写入操作场景,但有时候可能还要用日志记录读取操作。例如美国医疗健康行业中,医护人员需要能够在紧急情况下访问病患的医疗记录。但在正常业务中他们只有在为病患提供治疗的过程中可以合法访问这些记录。

由于记录不能彻底锁定,因此作为权宜之计只能追踪读取过每条记录的人的身份。在仓储层面上,只需要对每个涉及敏感数据的查询进行日志记录即可轻松实现。最简单的方法是在相关仓储方法的基础上手工实现。

性能日志

用户体验已成为一项功能,因此我们有必要了解每个查询到底要花费多长时间。单纯追踪每页面性能还不够,因为一个页面可能涉及多个查询。对于实体框架这一点尤为重要,因为延迟加载(Lazy-loading)可能会将数据库调用隐藏起来。

仓储中的显式日志记录

虽然很枯燥并且很容易漏掉某个查询,但可将每个查询封装到“即抛型”计时器中。具体模式如下:

public class OperationTimer : IDisposable
{readonly object m_Context;readonly Stopwatch m_Timer;public OperationTimer(object context){m_Context = context;m_Timer = Stopwatch.StartNew();}public void Dispose(){//Write to log here using timer and context}
}

具体用法为:

using(new OperationTimer("Load employees"))
{//execute query here
} 
Chain

Chain在数据源层面上暴露了一系列事件。本例需要的是DataSource.ExecutionFinished。范例如下:

static void DefaultDispatcher_ExecutionFinished(object sender, ExecutionEventArgs e)
{Debug.WriteLine($"Execution finished: {e.ExecutionDetails.OperationName}. Duration: {e.Duration.Value.TotalSeconds.ToString("N3")} sec. Rows affected: {(e.RowsAffected != null ? e.RowsAffected.Value.ToString("N0") : "<NULL>")}.");
}

此外还可将句柄附加到DataSource.GlobalExecutionFinished,借此侦听来自所有数据源的事件。

实体框架

实体框架内建的日志能力无法衡量每个查询所需的时间。为了消除这种局限,我们可以使用自定义的IDbCommandInterceptor。

public class EFLoggerForTesting : IDbCommandInterceptor
{static readonly ConcurrentDictionary<DbCommand, DateTime> m_StartTime = new ConcurrentDictionary<DbCommand, DateTime>();public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext){Log(command, interceptionContext);}public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext){Log(command, interceptionContext);}public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext){Log(command, interceptionContext);}private static void Log<T>(DbCommand command, DbCommandInterceptionContext<T> interceptionContext){DateTime startTime;TimeSpan duration;m_StartTime.TryRemove(command, out startTime);if (startTime != default(DateTime)){duration = DateTime.Now - startTime;}
elseduration = TimeSpan.Zero;string message;var parameters = new StringBuilder();foreach (DbParameter param in command.Parameters){parameters.AppendLine(param.ParameterName + " " + param.DbType + " = " + param.Value);}if (interceptionContext.Exception == null){message = string.Format("Database call took {0} sec. RequestId {1} \r\nCommand:\r\n{2}", duration.TotalSeconds.ToString("N3"), requestId, parameters.ToString() + command.CommandText);}else{message = string.Format("EF Database call failed after {0} sec. RequestId {1} \r\nCommand:\r\n{2}\r\nError:{3} ", duration.TotalSeconds.ToString("N3"), requestId, parameters.ToString() + command.CommandText, interceptionContext.Exception);}Debug.WriteLine(message);}public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext){OnStart(command);}public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext){OnStart(command);}public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext){OnStart(command);}private static void OnStart(DbCommand command){m_StartTime.TryAdd(command, DateTime.Now);}
}

虽然这种方式无法获取上下文数据,但可酌情将上下文推出(Shove)至ThreadLocal或AsyncLocal以绕过这一局限。

权限检查 – 表级

虽然可执行应用程序级别的权限检查,但同时强制进行仓储级的检查也能提供一定的好处。这种做法可以避免忘了对新创建的Screen/页面进行权限检查。

仓储强制执行

实现这一切最简单的方法是在每个相关函数开始时执行角色检查。例如:

public int Insert(Employee employee){if (!m_User.IsAdmin)throw new SecurityException("Only admins may add employees");
数据库强制执行

更成熟的做法是创建多个连接字符串。在创建仓储时,可根据用户角色选择连接字符串。在本例中非管理员用户的连接字符串针对employee表不具备INSERT特权。

由于复杂度和繁琐的维护,除非需要多层防御机制,对安全性要求极高的环境,否则不建议使用这种方法。就算在这种情况下,也需要通过大量的自动化测试确保每个连接字符串只包含自己需要的全部权限。

权限检查 – 列级

有时候可能需要进行列级的权限检查。例如我们可能需要防止用户为自己分配管理员特权,或可能希望阻止经理之外的其他用户查看员工的薪资数据。

Chain

Chain可以利用自带的审计规则功能实现列级权限检查。此时会将匿名函数与列名称,以及受限制操作列表一起传递至RestrictColumn构造函数。(并可选指定表名称。)

var IsAdminCheck = user => ((User)user).IsAdmin;dataSource = dataSource.WithRules(new RestrictColumn("Users", "IsAdmin", 
OperationTypes.Insert|OperationTypes.Update, IsAdminCheck));

为防止读取受限制的列,可将其传递至OperationTypes.Select flag

Dapper

在Dapper中实现这一目标的最简单方法是使用多个SQL语句。如果用户缺乏某一特权,只需要选择忽略对应列的SQL语句即可。

实体框架

查询可使用下列几个选项:

  1. 根据用户角色手工创建不同的投影(例如Select子句)。

  2. 正常执行查询,随后如果权限检查失败,对结果集进行循环,并将受限制的属性设置为null/0。

对于插入,按照上述方法将受限制属性留空即可。

更新操作较为复杂。当写入特定列的操作受限时将无法附加实体。此时需要重新获取原始记录,对允许的值进行复制并保存该对象,而不要保存应用程序代码传递来的对象。(基本上这就是上一篇文章提到的“新手”模式。)

将一个模型映射至多个表

数据架构方面有一个很重要的概念,即:无需在表和类之间创建一对一映射。为了让数据库的运转更高效或满足特定业务规则的需求,通常可能需要将一个类映射至多个表。

假设需要记录有关棒球队的数据,可能会用到这些表:

主键

Team

Team

TeamSeasonMap

TeamKey+SeasonKey

如果应用程序只能在有关赛季(Season)的上下文中理解团队(Team)的概念,那么可以用一个Team对象涵盖所有表。

Chain

Chain中的类和表之间不具备强关系,这意味着对于更新操作,应该这样写代码:

dataSource.Update("Team", myTeam).Execute();
dataSource.Update("TeamSeasonMap", myTeam).Execute();

代码运行时会判断哪些表适用哪些属性,并酌情生成SQL语句。

通过这种方式,即可从所有表的联接视图中获取Team对象。(Chain不支持直接联接,假设始终通过视图实现。)

实体框架

实体框架会认为映射至同一实体的多个表严格共享相同的主键。这意味着将无法支持该场景。

  • 对于读取操作,可以使用EF的常规LINQ语法执行联接和投影。

  • 对于更新操作,需要将每个表的模型复制到单独的实体中。

缓存

一般来说,仓储都需要考虑缓存问题。由于仓储知道数据的修改时间,因此可充当处理缓存失效问题的最佳方法。

Chain

Chain支持缓存,但必须通过Appender分别应用给每个查询。Appender可附加至实际执行之前的操作中,在本例中我们需要关注四个Appender:

  • .Cache(...)

  • .CacheAllItems(...)

  • .InvalidateCache(...)

  • .ReadOrCache(...)

也许通过仓储范例可以更好地说明这些Appender的作用。在下面的例子中可以看到对特定记录创建缓存,以及使用CacheAllItems对集合创建缓存这两种做法之间的相互作用。

public class EmployeeCachingRepository
{private const string TableName = "HR.Employee";private const string AllCacheKey = "HR.Employee ALL";public IClass1DataSource Source { get; private set; }public CachePolicy Policy { get; private set; }public EmployeeCachingRepository(IClass1DataSource source
, CachePolicy policy = null){Source = source;Policy = policy;}protected string CacheKey(int id){return $"HR.Employee EmployeeKey={id}";}protected string CacheKey(Employee entity){return CacheKey(entity.EmployeeKey.Value);}public Employee Get(int id){return Source.GetByKey(TableName, id)
.ToObject<Employee>()
.ReadOrCache(CacheKey(id), policy: Policy).Execute();}public IList<Employee> GetAll(){return Source.From(TableName)
.ToCollection<Employee>().CacheAllItems((Employee x) => CacheKey(x),
policy: Policy).ReadOrCache(AllCacheKey, policy: Policy).Execute();}public Employee Insert(Employee entity){return Source.Insert(TableName, entity)
.ToObject<Employee>()
.InvalidateCache(AllCacheKey)
.Cache((Employee x) => CacheKey(x), policy: Policy)
.Execute();}public Employee Update(Employee entity){return Source.Update(TableName, entity)
.ToObject<Employee>()
.Cache(CacheKey(entity))
.InvalidateCache(AllCacheKey).Execute();}public void Delete(int id){Source.DeleteByKey(TableName, id).
InvalidateCache(CacheKey(id))
.InvalidateCache(AllCacheKey).Execute();} }

从这个例子中可以发现,Chain为失效逻辑提供了丰富的控制能力,但作为代价我们必须慎重地指定各种选项。

实体框架

实体框架提供了两种级别的缓存。第一级仅限数据上下文,主要可用于确保对象图不包含代表同一条物理数据库记录的重复实体。由于该缓存会与数据上下文一起销毁,因此大部分缓存场景并不使用这种缓存。

在EF的术语中,我们需要的是名为“二级缓存”的缓存。虽然该功能已包含在EF 5中,但第6版实体框架并未提供任何拆箱即用的缓存功能。因此我们需要使用第三方库,例如EntityFramework.Cache或EFSecondLevelCache。从列举的这些库可以知道,为EF增加二级缓存并没有什么标准的模式。

关于本文作者

Jonathan Allen的第一份工作是在九十年代末期为一家诊所开发MIS项目,借此帮助这家诊所逐渐由Access和Excel转向真正的企业级解决方案。在用五年时间为财政部开发自动化交易系统后,他开始担任各种项目的顾问,并从事了仓储机器人UI、癌症研究软件中间层,以及大型房地产保险企业大数据需求等各类项目。闲暇时他喜欢研究并撰文介绍16世纪的格斗术。

原文地址:http://www.infoq.com/cn/articles/repository-advanced


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

可视化大屏设计尺寸_可视化大屏设计_酷炫不是最高效的大屏展示的唯一标准...

目前市面上有众多做大屏的可视化BI工具&#xff0c;有的部分企业为了要实现其功能效果而令人感到枯燥乏味&#xff0c;或者是为了看上去绚丽多彩而显得极端复杂&#xff0c;从而实现对于相当复杂而又冗余数据的深入分析&#xff0c;让企业决策者难以理解数据的价值。这也导致了…

Java的GC机制及算法

转载自 Java的GC机制及算法 GC的阶段 对每个对象而言&#xff0c;垃圾回收分为两个阶段&#xff1a;finalization和reclamation。 finalization: 指运行这个对象的finalize的方法。reclamation: 回收被这个对象使用的内存。 GC的过程的基本步骤首先确认对象是不可达的&#…

19年8月 字母哥 第一章 spring boot 2.x基础及概念入门 这里全部看完了 热部署没出来 第二章在前面2页 用热点公司网不行

http://springboot.zimug.com/1233100 文档 http://www.zimug.com/page/5 字母哥个人博客 11111 第一章 spring boot 2.x基础及概念入门 1.1.spring boot 产生的背景及其优势 1.2.spring boot 2.x 新特性说明 1.3.helloworld及项目结构介绍 1.4.IDEA结合spring b…

dell 恢复介质_戴尔官方WIN10恢复介质镜像下载与安装教程 | Dell 中国

前提&#xff1a;准备一个8G以上的空U盘相关视频请点击如何制作并使用win10 USB安装镜像目录:一、制作U盘安装介质1.打开链接&#xff0c;点击"下载OS Recovery Tool"&#xff0c;2.运行下载好的 OS Recovery Tool &#xff0c;点击INSTALL&#xff0c;3.安装完成&am…

CSS3(笔记)

CSS简介 CSS是什么 Cascading Style Sheet层叠样式表 CSS&#xff1a;表现&#xff08;美化网页&#xff09; 字体&#xff0c;颜色&#xff0c;边距&#xff0c;高度&#xff0c;宽度&#xff0c;背景图片&#xff0c;网页定位&#xff0c;网页浮动 CSS怎么用&#xff08;快…

19年8月 字母哥 第三章 spring boot 配置原理实战 用热点公司网不行

第三章 spring boot 配置原理实战 3.1.结合配置加载讲解bean自动装配原理 3.2.详解YAML语法及占位符语法 3.3.获取自定义配置的两种实现方法 3.4.配置文件注入值数据校验 3.5.加载旧项目配置文件的两种方式 这节课就是适配老的项目而已所以要新建分支 因为不是很常用 3.6.profi…

.NET Core跨平台图形处理库ImageSharp

ImageSharp 是支持.NET Core跨平台图形处理库&#xff0c;ImageSharp是ImageProcessor 的.NET Core跨平台实现。 ImageSharp 支持如下操作&#xff1a; 调整大小&#xff0c;裁剪&#xff0c;翻转&#xff0c;旋转&#xff0c;边缘检测等。 支持BMP&#xff0c;PNG&#xff0c;…

JVM GC参数以及GC算法的应用

转载自 JVM GC参数以及GC算法的应用1. 串行收集器 串行收集器是最古老&#xff0c;最稳定以及效率高的收集器可能会产生较长的停顿&#xff0c;只使用一个线程去回收-XX:UseSerialGC新生代、老年代使用串行回收新生代复制算法老年代标记-压缩串行收集器的日志输出&#xff1a…

hibernate正向生成数据库表以及配置——Student.java

package cn.bdqn.studentInfo.entity;import java.util.HashSet; import java.util.Set;/*** 学生表的实体类* author Administrator**/ public class Student {private Integer id;private String name;private Set<Teacher>teachersnew HashSet<Teacher>();publi…

全局配置_配置全局异常处理,结果没有想到,spring boot实践(3)

01 spring boot读取配置信息02 多环境配置这个功能详细大家在项目中也经常遇到&#xff0c;通常我们在写controller的时候会定义一个全局的异常处理。任何的controller出现异常都会进入到这个全局异常统一抛出&#xff0c;同时我们也可以定义一个自定义的异常类来处理一些特殊的…

蓝桥杯JAVA省赛2013-----B------5(有理数类)

五、有理数类 【答案】&#xff1a;Rational(this.rax.rb this.rbx.ra, this.rb*x.rb) public class Test01 {static class Rational //内部类 【main调用内部类 &#xff1a; 内部类前 要加 static】{private long ra;private long rb;private long gcd(long a, long b…

19年8月 字母哥 第四章 常用web开发数据库框架 不要用公司网络加载不出来 用热点!!!

第四章 常用web开发数据库框架 4.1.整合Spring JDBC操作数据 4.2 Spring JDBC多数据源的实现 4.3.Spring JDBC JTA实现分布式事务 4.4.ORM主流框架选型 4.5.bean转换Dozer的快速上手 4.6.整合Spring Data JPA操作数据 4.7.Sp…

分库分表的几种常见形式以及可能遇到的难

在谈论数据库架构和数据库优化的时候&#xff0c;我们经常会听到“分库分表”、“分片”、“Sharding”…这样的关键词。让人感到高兴的是&#xff0c;这些朋友所服务的公司业务量正在&#xff08;或者即将面临&#xff09;高速增长&#xff0c;技术方面也面临着一些挑战。让人…

JVM 调优和垃圾回收器说明

转载自 JVM 调优和垃圾回收器说明JVM垃圾收集算法JVM垃圾收集算法有四种&#xff1a;标记-清除算法、复制算法、标记-整理算法、分代收集算法标记-清除算法&#xff1a;该算法如同它的名字一样&#xff0c;分为两个阶段&#xff1a;标记、清除。首先标记出所有需要回收的对象…

python中的数组按顺序切片_python切片(获取一个子列表(数组))详解

切片&#xff1a;切片指从现有列表中&#xff0c;获取一个子列表返回一个新列表&#xff0c;不影响原列表。下标以 0 开始&#xff1a;list [红,绿,蓝,白,黑,黄,青]# 下标 0 1 2 3 4 5 6取单个值语法&#xff1a;列表[n]n为下标&#xff0c;n0表示第一个 &#xff0c; n1表示第…

.net core快速上手

2014年11月12日的Connect ();开发者活动上宣布将.NET堆栈基于MIT协议开源&#xff0c;并且提供开源保证&#xff0c;托管在Github上。当时的版本与最终目标相距甚远&#xff0c;然而有一点可以肯定的是&#xff0c;这是一个与.NET Framework 4.x完全不同的框架。 这在社区引发了…

hibernate正向生成数据库表以及配置——Teacher.java

package cn.bdqn.studentInfo.entity;import java.util.HashSet; import java.util.Set;/*** 老师的实体类* author Administrator**/ public class Teacher {private Integer id;private String name;private Set<Student> students new HashSet<Student>();publi…

19年8月 字母哥 第五章 静态资源与模板引擎的整合 用热点公司网不行

第五章 静态资源与模板引擎的整合 5.1.webjars与静态资源 5.2.模板引擎选型与未来趋势 5.3.web应用开发之整合jsp 5.4.web应用开发之整合freemarker 5.5.web应用开发之整合thymeleaf 5.6.thymeleaf基础语法讲解 5.7.thymeleaf内置对象与工具类 5.8.公共片段(标签)与内联…

python 随机排序_Python 如何随机打乱列表(List)排序

场景&#xff1a;现在有一个list:[1,2,3,4,5,6]&#xff0c;我需要把这个list在输出的时候&#xff0c;是以一种随机打乱的形式输出。专业点的术语&#xff1a;将一个容器中的数据每次随机逐个遍历一遍。注意&#xff1a;不是生成一个随机的list集。环境&#xff1a;Python 3.6…

深入JVM虚拟机(四) Java GC收集器

转载自 深入JVM虚拟机(四) Java GC收集器1 GC收集器 1.1 Serial串行收集器 串行收集器主要有两个特点&#xff1a;第一&#xff0c;它仅仅使用单线程进行垃圾回收&#xff1b;第二&#xff0c;它独占式的垃圾回收。 在串行收集器进行垃圾回收时&#xff0c;Java 应用程序中的线…