使用实体框架、Dapper和Chain的仓储模式实现策略

关键要点:

  • Dapper这类微ORM(Micro-ORM)虽然提供了最好的性能,但也需要去做最多的工作。

  • 在无需复杂对象图时,Chain这类Fluent ORM更易于使用。

  • 对实体框架(Entity Framework)做大量的工作后,其性能可显著提高。

  • 为获得数据库的最大性能,需要采用可能会有些繁琐的投影(Projection)操作。

  • ORM整体上的局部更新可能会存在问题。

在现代企业开发中,可采用多种方法构建数据存取层(data access layer ,DAL)。使用C#做开发时,DAL的最底层几乎总是使用ADO.NET。但这时常会形成一个笨重的库,所以通常会在DAL的底层之上再部署一个ORM层。为允许模拟和隐藏ORM的细节,整个DAL包装在存储内。

在这一系列的文章中,我们将审视三种使用不同类型ORM构建仓储模式的方法,分别是:

  • 实体框架:一种传统的“全特性”或“OOP”类型的ORM。

  • Dapper:一种主要专注结果集映射的轻量级微ORM。

  • Tortuga Chain:一种基于函数式编程理念的Fluent ORM。

本文将侧重于开发人员可在典型仓储中用到的那些基本功能。在本系列文章的第二部分,我们将着眼于那些开发人员基于实际情况而实现的高级技术。

插入(Insert)操作

对于任何CRUD操作集,通常会首先实现基本的插入操作,进而可用插入操作对其它的操作进行测试。

Chain

Chain使用列名和属性名间的运行时匹配。对于在数据库中并不存在的对象,除非启用了严格模式(strict model),否则将忽略该对象上的属性。类似地,没有匹配属性的列不能成为生成SQL的组成部分。

public int Insert(Employee employee)
{return m_DataSource.Insert("HR.Employee", employee).ToInt32().Execute();
}

Dapper

没有第三方扩展时,Dapper需要编程人员手工指定所需的SQL,其中包括了特定于数据库的逻辑,用于返回新创建的主键。

 public int Insert(Employee employee){const string sql = @"INSERT INTO HR.Employee(FirstName,MiddleName,LastName,Title,ManagerKey,OfficePhone,CellPhone)
VALUES  (@FirstName,@MiddleName,@LastName,@Title,@ManagerKey,@OfficePhone,@CellPhone);SELECT SCOPE_IDENTITY()
";using (var con = new SqlConnection(m_ConnectionString)){con.Open();return con.ExecuteScalar<int>(sql, employee);}}

实体框架

实体框架使用编译阶段映射在运行时生成SQL。需将任何没有匹配列的属性标记为NotMapped,否则将会产生错误。

public int Insert(Employee employee){using (var context = new CodeFirstModels()){context.Employees.Add(employee);context.SaveChanges();return employee.EmployeeKey;}}

更新(Update)操作

Chain

Chain缺省使用数据库中所定义的主键。但是在设置了适当的插入选项后,它将在模型中使用Key属性。

public void Update(Employee employee){m_DataSource.Update("HR.Employee", employee).Execute();}

Dapper

与插入操作一样,纯Dapper需用户手工编写必要的SQL语句。

public void Update(Employee employee){const string sql = @"UPDATE HR.EmployeeSET     FirstName = @FirstName,MiddleName = @MiddleName,LastName = @LastName,Title = @Title,ManagerKey = @ManagerKey,OfficePhone = @OfficePhone,CellPhone = @CellPhoneWHERE   EmployeeKey = @EmployeeKey";using (var con = new SqlConnection(m_ConnectionString)){con.Open();con.Execute(sql, employee);}}

实体框架(初学者)

实体框架为UPDATE语句查找Key属性,以生成WHERE语句。

public void Update(Employee employee){using (var context = new CodeFirstModels()){var entity = context.Employees.
Where(e => e.EmployeeKey == employee.EmployeeKey).First();entity.CellPhone = employee.CellPhone;entity.FirstName = employee.FirstName;entity.LastName = employee.LastName;entity.ManagerKey = employee.ManagerKey;entity.MiddleName = employee.MiddleName;entity.OfficePhone = employee.OfficePhone;entity.Title = employee.Title;context.SaveChanges();}}

实体框架(中级用户)

使用实体框架时,初学者常会在执行更新操作上犯错误。将实体添加到上下文中很容易就能实现它,而这种模式应成为中级使用者的常识。这里给出使用实体状态“Modified”修正后的例子。

public void Update(Employee employee){using (var context = new CodeFirstModels()){context.Entry(employee).State = EntityState.Modified;context.SaveChanges();}}

读取全部(Read All)操作

读取全部操作在实体框架和Chain中是十分相似的,不同之处在于在实体框架中实现需要编写更多行的代码,而在Chain中实现需要编写更长的代码行。

Dapper当然是最为繁琐的,因为它需要未经加工的SQL语句。即使如此,仍可以通过使用SELECT *语句替代手工地指定列名而在一定程度上降低Dapper的开销。这在存在返回额外数据的风险的情况下,降低了出现类与SQL语句不匹配的可能性。

Chain

在Chain中,ToObject连接生成一系列所需的列。通过匹配所需列表与可用列的列表,From连接生成SQL语句。

public IList<Employee> GetAll()
{return m_DataSource.From("HR.Employee").ToCollection<Employee>().Execute();
}

Dapper

Dapper是最为繁琐的,因为它需要原始未经加工的SQL语句。虽然这令人皱眉头,但仍可以通过使用SELECT *语句替代手工地指定列名而在一定程度上降低Dapper的开销,这样是不太可能漏掉列的,虽然存在返回额外数据的风险。

 public IList<Employee> GetAll(){using (var con = new SqlConnection(m_ConnectionString)){con.Open();return con.Query<Employee>(
"SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName,
e.Title, e.ManagerKey, e.OfficePhone, e.CellPhone, e.CreatedDate
FROM HR.Employee e").AsList();}}

实体框架

像以前一样,实体框架使用编译期信息确定如何生成SQL语句。

public IList<Employee> GetAll(){using (var context = new CodeFirstModels()){return context.Employees.ToList();}}

按标识符获取(Get by Id)操作

需要注意的是,随每个例子的语法稍作修改就可表明只返回一个对象。同样的基本过滤技术可用于返回多个对象。

Chain

Chain严重依赖于“过滤对象”。这些对象直接被转义成参数化的WHERE语句,语句中的每个属性间具有“AND”操作符。

public Employee Get(int employeeKey){return m_DataSource.From("HR.Employee", 
new { @EmployeeKey = employeeKey }).ToObject<Employee>().Execute();}

Chain也允许用参数化的字符串表示WHERE语句,虽然这个功能很少被用到。

如果主键是标量,即主键中只有一列,那么可使用简化的语法。

public Employee Get(int employeeKey){return m_DataSource.GetByKey("HR.Employee", employeeKey)
.ToObject<Employee>().Execute();}

Dapper

下例中,可以看到Dapper手工指定了SQL语句。该语句与Chain和实体框架所生成的SQL语句在本质上是一致的。

using (var con = new SqlConnection(m_ConnectionString)){con.Open();return con.Query<Employee>("SELECT e.EmployeeKey, e.FirstName,
e.MiddleName, e.LastName, e.Title, e.ManagerKey, e.OfficePhone,
e.CellPhone, e.CreatedDate FROM HR.Employee e
WHERE e.EmployeeKey = @EmployeeKey",
new { @EmployeeKey = employeeKey }).First();}

实体框架

实体框架将表名和首个ToList或First操作间的所有内容看作为一个表达式树。在运行时评估该树以生成SQL语句。

public Employee Get(int employeeKey){using (var context = new CodeFirstModels()){return context.Employees.Where(e => e.EmployeeKey == employeeKey).First();}}

删除(Delete)操作

Chain

Chain期待包括主键的参数对象。而参数对象中的其它特性将被忽略(该语法不支持批量删除)。

public void Delete(int employeeKey){m_DataSource.Delete("HR.Employee",
new { @EmployeeKey = employeeKey }).Execute();}

如果有标量主键,可使用简化的语法。

 public void Delete(int employeeKey){m_DataSource.DeleteByKey("HR.Employee", employeeKey).Execute();}

Dapper

public void Delete(int employeeKey){using (var con = new SqlConnection(m_ConnectionString)){con.Open();con.Execute("DELETE FROM HR.Employee 
WHERE EmployeeKey = @EmployeeKey",
new { @EmployeeKey = employeeKey });}}

实体框架(初学者)

初学者一般会取回一个记录然后迅速删除,丢弃所有返回的信息。

public void Delete(int employeeKey){using (var context = new CodeFirstModels()){var employee = context.Employees.
Where(e => e.EmployeeKey == employeeKey).First();context.Employees.Remove(employee);context.SaveChanges();}}

实体框架(中级用户)

可使用内嵌SQL避免数据库的往返交互操作。

public void Delete(int employeeKey){using (var context = new CodeFirstModels()){context.Database.ExecuteSqlCommand(
"DELETE FROM HR.Employee WHERE EmployeeKey = @p0", employeeKey);}}

投影(Projection)操作

投影是中间层开发中的一个重要部分。在取回了比实际所需更多的数据时,数据库常会完全失去使用覆盖索引或索引的能力,这将导致严重的性能影响。

Chain

同上,Chain将仅选取指定对象类型所需的所有列。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers(){return m_DataSource.From("HR.Employee").
ToCollection<EmployeeOfficePhone>().Execute();}

Dapper

鉴于Dapper是显式的,所以是由开发人员确保只选取必需的列。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers(){using (var con = new SqlConnection(m_ConnectionString)){con.Open();return con.Query<EmployeeOfficePhone>(
"SELECT e.EmployeeKey, e.FirstName, e.LastName, e.OfficePhone
FROM HR.Employee e").AsList();}}

实体框架

实体框架需要额外的操作步骤,这些步骤常因为有些繁琐而被忽视。

通过在调用ToList前就包括了额外的选择语句,实体架构可生成正确的SQL语句,并避免从数据库返回过多的信息。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers(){using (var context = new CodeFirstModels()){return context.Employees.Select(e => new EmployeeOfficePhone(){EmployeeKey = e.EmployeeKey,FirstName = e.FirstName,LastName = e.LastName,OfficePhone = e.OfficePhone}).ToList();}}

使用投影做更新操作

固然,在存在投影对象时直接从投影对象更新数据库是一种好的方法。该方法在Chain和Dapper的基本模式中是天然存在的。而在实体框架中,则必须要在手工拷贝属性和编写Dapper风格的内嵌SQL这两种方法间做出选择。

Chain

注意,任何未在投影类上具有匹配属性的列将不受到影响。

public void Update(EmployeeOfficePhone employee){return m_DataSource.Update("HR.Employee", employee).Execute();}

Dapper

public void Update(EmployeeOfficePhone employee){const string sql = @"UPDATE HR.Employee
SET     FirstName = @FirstName,LastName = @LastName,OfficePhone = @OfficePhone
WHERE   EmployeeKey = @EmployeeKey
";using (var con = new SqlConnection(m_ConnectionString)){con.Open();con.Execute(sql, employee);}}

实体框架

public void Update(EmployeeOfficePhone employee){using (var context = new CodeFirstModels()){var entity = context.Employees.
Where(e => e.EmployeeKey == employee.EmployeeKey).First();entity.FirstName = employee.FirstName;entity.LastName = employee.LastName;entity.OfficePhone = employee.OfficePhone;context.SaveChanges();}}

反射插入(Reflexive Insert)

现在我们来看一些更有意思的用例。反射插入意味着返回被插入的对象。做反射插入通常是为了获得默认的和计算的域。

模型

注意,实体框架和Chain需要对属性进行注释,这样库才会知道该域将由数据库予以设置。

[DatabaseGenerated(DatabaseGeneratedOption.Computed)] //Needed by EF[IgnoreOnInsert, IgnoreOnUpdate] //Needed by Chainpublic DateTime? CreatedDate { get; set; }

Chain

Chain允许将ToObject附加到任何插入或更新操作上。

public Employee InsertAndReturn(Employee employee){return m_DataSource.Insert("HR.Employee", employee)
.ToObject<Employee>().Execute();}

Dapper

使用Dapper的反射插入,可以使用特定于数据库的功能实现,例如OUTPUT语句。

public Employee InsertAndReturn(Employee employee){const string sql = @"INSERT INTO HR.Employee(FirstName,MiddleName,LastName,Title,ManagerKey,OfficePhone,CellPhone)OUTPUT Inserted.EmployeeKey,Inserted.FirstName,Inserted.MiddleName,Inserted.LastName,Inserted.Title,Inserted.ManagerKey,Inserted.OfficePhone,Inserted.CellPhone,Inserted.CreatedDate
VALUES  (@FirstName,@MiddleName,@LastName,@Title,@ManagerKey,@OfficePhone,@CellPhone);";using (var con = new SqlConnection(m_ConnectionString)){con.Open();return con.Query<Employee>(sql, employee).First();}}

如果一并考虑初学者级别模式,更典型的做法是仅在Get方法之后调用Insert方法。

public Employee InsertAndReturn_Novice(Employee employee){return Get(Insert(employee));}

实体框架

使用前面提及的DatabaseGenerated属性,你可以插入一个新的实体并读回它的计算的和/或默认的列。

public Employee InsertAndReturn(Employee employee){using (var context = new CodeFirstModels()){context.Employees.Add(employee);context.SaveChanges();return employee;}}

受限更新/局部更新

有时应用并没有打算对每个列做更新,尤其是当模型是直接源自于UI并可能混合了可更新域和不可更新域时。

Chain

在Chain中,使用IgnoreOnInsert和IgnoreOnUpdate属性去限制插入和更新操作。为允许用数据库作为默认取值,典型的做法是将这两个属性都置于CreatedDate类型的列中。为避免更新操作过程中的意外改变,通常将IgnoreOnUpdate属性置于CreatedBy之类的列上。

Dapper

就显式编写的插入和更新语句而言,Dapper最具灵活性。

实体框架

除了计算列(列值为表达式),实体框架并未给出一种简单的方法可声明某一列不参与插入或删除操作,但可使用更新操作的“读-拷贝-写”(read-copy-write)模式模拟该行为。

更新或插入(Upsert)操作

经常需要作为一个单一操作完成记录的插入或者更新,尤其是在使用自然主键(natural key)时。

Chain

在Chain中,Upsert操作的实现使用了与插入和删除相同的设计。所生成的SQL随数据库引擎不同而各异(例如:SQL Server使用了MERGE,SQLit使用了一系列语句)。

public int Upsert(Employee employee){return m_DataSource.Upsert("HR.Employee", employee).
ToInt32().Execute();}

Dapper

在Dapper中,Upsert操作的实现需要多轮的来回交互,或是需要比较复杂的特定于数据库的SQL语句。本文对此不作阐述。

实体框架

在实体框架中,这(过程?函数?都可以用“这”指代)仅作为被改进的更新操作的一个变体。

public int Upsert(Employee employee){using (var context = new CodeFirstModels()){if(employee.EmployeeKey == 0)context.Entry(employee).State = EntityState.Added;elsecontext.Entry(employee).State = EntityState.Modified;context.SaveChanges();return employee.EmployeeKey;}}

性能

虽然本文所采用的主要基准测试是代码量和易用性,但是对实际性能的考虑也是非常有用的。

所有的性能基准测试中都包括了预热过程,其后是对主循环做1000次迭代操作。每次测试中都使用了同样的模型,模型使用实体框架的代码优先(Code First)技术从数据库代码生成器产生。所有迭代都相当于共计13个基本CRUD操作,其中包括创建、读取、更新和删除操作。

我要澄清的是,这里所做的仅是一些粗略的测试,使用了任何人在刚开始接触这些库时通常就会看到的代码类型。当然一些高级技术可以改进每个测试的性能,有时甚至是极大地改进。

BenchmarkDotNet计时

  • Chain:平均3.4160毫秒,标准偏差为0.2764毫秒;

  • 未使用经编译的物化器(Compiled Materializers)的Chain:平均3.0955毫秒,标准偏差0.1391毫秒;

  • Dapper:平均2.7250毫秒,标准偏差0.1840毫秒;

  • 实体框架(初学者):平均13.1078毫秒,标准偏差0.4649毫秒;

  • 实体框架(中级用户):平均10.11498毫秒,标准偏差0.1952毫秒;

  • 实体框架(未使用AsNoTracking的中级用户):平均9.7290毫秒,标准偏差0.3281毫秒。

结论

虽然可使用任何ORM框架去实现基本的仓储模式,但是各种实现的性能和所需的代码量具有显著的差异。选取实现方式时需要对这些因素进行平衡,此外还需考虑数据库可移植性、跨平台支持和开发人员经验等。

在该系列文章的第二部分,我们将着眼于那些不仅将仓储模式作为瘦抽象层的高级用例。

你可以在GitHub上获取本文的代码。

关于作者

Jonathan Allen的首份工作是在上世纪九十年代末做诊所的MIS项目,Allen将项目逐步由Access和Excel升级到企业级的解决方法。在从事为财政部门编写自动交易系统代码的工作五年之后,他成为项目顾问,参与了包括机器人仓库UI、癌症研究软件中间层、主要房地产保险企业的大数据需求等在内的各种行业项目。在闲暇时间,他喜欢研究源于16世纪的武术,并为其撰写文章。

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


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

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

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

相关文章

JVM-对象的存活与死亡

转载自 JVM-对象的存活与死亡 当Java虚拟机进行垃圾收集的时候&#xff0c;那么它必须要先判断对象&#xff0c;是否还存活&#xff0c;如果存活就不能对它进行回收。所以判断一个对象是否存活是Java虚拟机必须要实现的。1.对象是否存活  1&#xff09;引用计数器&#xff1…

SpringBoot+MyBatis搭建迷你小程序

简介&#xff1a;用Spring Boot框架大大简化了新Spring应用的初始搭建以及开发过程&#xff0c;在开发人员中越来越受到欢迎。微信小程序作为目前炙手可热的应用&#xff0c;很有可能在未来占据轻应用的市场。本门课程的主要目的是将两者结合起来&#xff0c;同时希望作为入门翔…

蓝桥杯JAVA省赛2013-----B------3(振兴中华)

【解析】&#xff1a;将格子中的字存放到一个二维数组中&#xff0c;使用回溯法依次进行遍历&#xff0c; 符合条件的1&#xff0c;最后求出总和 【答案】&#xff1a;35 从我做起振 (0, 0) (0, 1) (0, 2) (0, 3) (0, 4) 我做起振兴 (1, 0) (1, 1) (1, 2) (1, 3) (1, 4) 做起…

python变量后面加星号_Python基础找茬系列20--python函数的秘密

一、小试牛刀二、函数的定义def 函数名(参数列表): 函数体【1】函数的关键词&#xff1a;是def&#xff0c;不是del&#xff0c;也不是function【2】函数的名称&#xff1a;不能使用关键词作为函数的名称&#xff0c;允许使用内置函数名作为函数名称&#xff0c;这会覆盖内置函…

基于Quartz.net 的开源任务管理平台

最近&#xff0c;又重新整理&#xff0c;开发出了一套基于Quartz.net 的任务管理平台。将Quartz.net 的任务调度&#xff0c;管理等功能统一整合&#xff0c;形成了一套比较完整的任务调度平台。主要是&#xff1a;任务调度服务&#xff0c;后台任务管理 等功能。 github地址&a…

Java中的垃圾回收与对象生命周期

转载自 Java中的垃圾回收与对象生命周期1. 垃圾回收 垃圾回收是Java程序设计中内存管理的核心概念&#xff0c;JVM的内存管理机制被称为垃圾回收机制。 一个对象创建后被放置在JVM的堆内存中&#xff0c;当永远不再引用这个对象时&#xff0c;它将被JVM在堆内存中回收。…

页面复杂对象传递参数 开发中遇到的问题

左边是我发送的数据 转换成右边的就可以接受到数据了 我发送的数据接收回来 这样发送 服务器就可以接收到数据了

hibernate正向生成数据库表以及配置——hibernate.cfg.xml

<?xml version1.0 encodingUTF-8?> <!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"><!-- Generated by MyEclipse H…

蓝桥杯JAVA省赛2013-----B------4(黄金连分数)

【答案】&#xff1a;0.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911375 识别问题 --> 斐波那契的第n项与第n1项的比值n要多少才够 --> 精度处理大整数、大浮点数 &#xff1a;add()-&#xff1a;subtract()*&…

.NET仓储模式高级用例

主要结论 如果需要执行基本CURD之外的其他操作&#xff0c;此时就有必要使用仓储&#xff08;Repository&#xff09;。为了促进测试工作并改善可靠性&#xff0c;应将仓储视作可重复使用的库&#xff08;Library&#xff09;。将安全和审计功能放入仓储中可减少Bug并简化应用程…

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

目前市面上有众多做大屏的可视化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;同时我们也可以定义一个自定义的异常类来处理一些特殊的…