GVKun编程网logo

JpaSpecificationExecutor规范中的JOIN + ORDER BY(jpa criteriaquery)

18

想了解JpaSpecificationExecutor规范中的JOIN+ORDERBY的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于jpacriteriaquery的相关问题,此外,我们还

想了解JpaSpecificationExecutor规范中的JOIN + ORDER BY的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于jpa criteriaquery的相关问题,此外,我们还将为您介绍关于ASP.NET Core 中的规约模式(Specification Pattern )—— 增强泛型仓储模式、Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting resul...、declaration of ''void* operator new [](size_t)'' has a different exception specifier、Executor框架(一)Callable、Future、Executor和ExecutorService的新知识。

本文目录一览:

JpaSpecificationExecutor规范中的JOIN + ORDER BY(jpa criteriaquery)

JpaSpecificationExecutor规范中的JOIN + ORDER BY(jpa criteriaquery)

我有一个使用JOIN和ORDER BY的查询,并想使用Criteria Api在我的存储库中使用它。

在这里,我发现了如何将这样的查询包装到CriteriaQuery(Link)中。

CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);Root<Pet> pet = cq.from(Pet.class);Join<Pet, Owner> owner = cq.join(Pet_.owners);cq.select(pet);cq.orderBy(cb.asc(owner.get(Owner_.lastName),owner.get(Owner_.firstName)));

另一方面,我发现了一些将Criteria
Api与JpaRepository结合使用的示例(example)。

问题在于存储库中的所有方法都需要规范:

T findOne(Specification<T> spec);

总是这样构建的:

public static Specification<PerfTest> statusSetEqual(final Status... statuses) {    return new Specification<PerfTest>() {        @Override        public Predicate toPredicate(Root<PerfTest> root, CriteriaQuery<?> query, CriteriaBuilder cb) {            return cb.not(root.get("status").in((Object[]) statuses));        }    };}

因此,一方面,我知道如何创建CriteriaQuery;另一方面,我需要从谓词构建的规范,而我不知道如何将CriteriaQuery解析为规范/谓词。

答案1

小编典典

尝试这样的事情(我假设宠物有很多主人):

public static Specification<Pet> ownerNameEqual(String ownerName) {        return new Specification<Pet>() {            @Override            public Predicate toPredicate(Root<Pet> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {                Join<Pet, Owner> owners = root.join("owners");                criteriaQuery.orderBy(criteriaBuilder.desc(root.get("id")));                return criteriaBuilder.equal(owners.get("name"), ownerName);            }        };    }

这只是搜索至少一个拥有者名称等于 ownerName的 所有宠物的示例 __

但是您可以List<Pet> findByOwnersNameOrderByIdDesc(StringownerName);改为在存储库中添加一个方法(等同于Specification)。

ASP.NET Core 中的规约模式(Specification Pattern )—— 增强泛型仓储模式

ASP.NET Core 中的规约模式(Specification Pattern )—— 增强泛型仓储模式

原文链接:https://codewithmukesh.com/blog/specification-pattern-in-aspnet-core/

在本文中,我们将讨论在 ASP.NET Core 应用程序中实现规约模式以及它如何增强现有的泛型仓储模式。我们将从头开始构建具有泛型仓储模式、Entity Framework Core 的 ASP.NET Core WebAPI,并最终实现规约模式模式。您可以在此处找到此实现的完整源代码 [1]。让我们开始吧。

理解规约模式:为什么?

让我们通过一个简单的示例来了解使用规约模式的必要性。下面是 Developer 类的代码片段,它具有 Name、Email、Experience 等所需的属性。

public class Developer
{
    public int Id { getset; }
    public string Name { getset; }
    public string Email { getset; }
    public int YearsOfExperience {get;set;}
    public decimal EstimatedIncome {get;set;}
    public int Followers { getset; }
}

现在,我们可能会有一个服务层,它通过像 Entity Framework Core 这样的抽象从 DB 返回数据集。这是它的样子。

public class DeveloperService : IDeveloperService
{
    private readonly ApplicationDbContext _context;
    public DeveloperService(ApplicationDbContext context)
    {
        _context = context;
    }
    public async Task<IEnumerable<Developer>> GetDeveloperCount()
    {
        // return a count of all developers in the database
    }
}

虽然您将获得所有开发人员的数量,但更实际和合乎逻辑的要求是使用某种过滤器获得开发人员的数量,同意吗?例如,获取估计收入为 100,000 美元或以上的开发人员的数量,或具有 5 年或以上经验的开发人员的数量。可能性是无限的。

但是,这最终会让您拥有大量的服务层函数,例如 GetDeveloperCountWithSalariesGreaterThan (decimal minSalary)、GetDeveloperCountWithExperienceMoreThan (int minExp) 等等。需求越多,您最终拥有的功能数量就越多。如果您需要薪水高于 x 且经验高于 y 年的开发人员数量怎么办?  这是另一个可能导致额外方法的挑战。

您可能会争辩说您可以将这些过滤器直接应用于 Entity Framework Core 实体,例如

await _context.Developers.Where(a=>a.Salary > 10000 && a.Experience > 6).ToListAsync()

但是,不,这与您需要的干净的应用程序代码库相去甚远。这种方法最终会很快破坏应用程序的可伸缩性,相信我,这根本无法维护。小提示,您的应用程序中始终需要一个位于应用程序和数据库之间的服务层,并全权负责处理业务逻辑。

这是您的应用程序需要使用规约模式的地方。注意,泛型仓储模式有一些限制,这些限制是通过使用规约模式解决的。我们将建立一个项目,然后使用规约。

我们将建造什么

为了演示 ASP.NET Core 中的规约模式,我们将构建一个具有 2 个端点的简单 Web API 应用程序:

  • 返回特定的开发人员详细信息
  • 返回开发人员列表

但是,我们将添加泛型仓储模式和工作单元的组合,使这个实现更加合乎逻辑和实用。我们将在这里专门识别和实现规约模式的用例。这几乎是您使用 ASP.NET Core 5.0 构建完整应用程序时所需的一切。让我们开始吧。

PS,你可以在这里找到这个实现的完整源代码。

设置项目

首先,让我们打开 Visual Studio 2019+ 并创建一个新的解决方案和一个 WebAPI 项目。请注意,我们也将在此实现中遵循六边形架构,以保持解决方案的良好组织。

添加 API 项目后,让我们再向此解决方案添加 2 个类库项目。我们称之为 Data 和 Core。

  • Data 是与数据库和上下文相关的所有实现所在的地方。
  • Core 是我们将添加接口和域实体的地方。

这就是现阶段解决方案的样子。

添加所需的模型

如前所述,在 Core 项目中,创建一个名为 Entities 的新文件夹并向其中添加 2 个类,即 DeveloperAddress

public class Address
{
    public int Id { getset; }
    public string City { getset; }
    public string Street { getset; }
}
public class Developer
{
    public int Id { getset; }
    public string Name { getset; }
    public string Email { getset; }
    public int YearsOfExperience { getset; }
    public decimal EstimatedIncome { getset; }
    public Address Address { getset; }
}

添加 DBContext 、Migrations 和必需的包

现在,让我们将所需的 NuGet 包安装到相应的项目中。

打开包管理器控制台并从下拉列表中将 Data 项目设置为默认项目。 运行以下命令以安装所需的软件包。

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

接下来,将 API 项目设置为默认项目,并运行以下命令。

Install-Package Microsoft.EntityFrameworkCore.Design

在设置应用程序上下文类之前,让我们添加连接字符串。为此,从 API 项目打开 appsettings.json 并添加以下内容。

请注意,我们目前正在使用 SQLServer Local DB 进行此演示。

"ConnectionStrings": {
  "DefaultConnection""Data Source=(localdb)\\mssqllocaldb;Initial Catalog=specification-pattern-demo;Integrated Security=True;MultipleActiveResultSets=True"
},

完成后,让我们创建所需的上下文类,以帮助我们访问数据库。为此,在数据项目下,添加一个新类并将其命名为 ApplicationDbContext。

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions options) : base(options)
    {
    }
    public DbSet<Developer> Developers { getset; }
    public DbSet<Address> Addresses { getset; }
}

在这里,您可以看到我们提到了要包含在 Application Db Context 中的 Developer 和 Address 类。

接下来,我们需要将此上下文添加到我们的 ASP.NET Core 应用程序的服务容器并配置连接详细信息。在 API 工程中打开 Startup.cs,在 ConfigureServices 方法下添加如下内容。

services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

最后,我们准备添加迁移并更新数据库。再次打开包管理器控制台并将 Data 项目设置为默认项目。运行以下命令:

add-migration initial
update-database

这是演示相同内容的屏幕截图。请注意,您可能会收到有关上述小数属性精度的警告。我们暂时可以忽略它。

完成后,我们的数据库现在应该准备好了所需的表和相应的字段。出于演示目的,我使用 Visual Studio 2019 IDE 的 SQL Server 对象资源管理器工具将一些示例数据直接添加到数据库中。

实现泛型仓储模式

由于我们的需求是返回开发人员的结果集,所以我们创建一个泛型仓储模式,以便它可以使用 ApplicationDbContext 从数据库中查询数据。使用泛型仓储模式的重要性在于,此代码也可以重用于多个其他实体。

例如,我们稍后添加一个名为 Product 的新实体,您不一定需要添加用于从数据库访问 Product 数据的新类,但您可以在大多数用例中使用现有的泛型仓储库实现。请注意,我们将在本文后面的部分讨论和解决泛型仓储库模式的一些限制。

在 Core 项目下,添加一个新文件夹并将其命名为 Interfaces。在这里,添加一个新接口 IGenericRepository

public interface IGenericRepository<T> where T: class
{
    Task<T> GetByIdAsync(int id);
    Task<List<T>> GetAllAsync();
}

创建泛型仓储实现

现在,让我们实现上面创建的接口。由于我们遵循六边形 / 洋葱架构,我们将不得不在应用程序核心之外添加实现。这意味着,所有与数据相关的实现都将添加到数据项目中。

在这里,添加一个新类 GenericRepository

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    protected readonly ApplicationDbContext _context;
    public GenericRepository(ApplicationDbContext context)
    {
        _context = context;
    }
    public async Task<List<T>> GetAllAsync()
    {
        return await _context.Set<T>().ToListAsync();
    }
    public async Task<T> GetByIdAsync(int id)
    {
        return await _context.Set<T>().FindAsync();
    }
}

可以看到我们正在将 ApplicationDbContext 的实例注入到这个仓储实现的构造函数中。此实例进一步用于从数据库读取数据。

最后在 API 工程的 Startup.cs 中添加如下内容,将 IGenericRepository 接口注册到应用的服务容器中。

services.AddScoped(typeof(IGenericRepository<>), (typeof(GenericRepository<>)));

泛型仓储模式的问题:反模式?

一些开发人员认为泛型仓储是一种反模式。如果使用不当,是的,任何模式都会弄乱您的代码。对泛型仓储的主要抱怨是单个方法可能会将整个数据库访问代码暴露给用户。这也可能意味着需要针对每种需求组合使用多种方法(如本文开头所述)。例如,看下面的接口声明:

List<T> FindAsync(Expression<Func<T, bool>> query);

此方法可以作为泛型仓储模式的一部分来解决我们遇到的问题。但是由于该方法过于笼统,泛型仓储不可能知道我们传递给它的表达式。另一个想法可能是从 IGenericRepository 接口中删除此方法并在新接口中使用它,例如,从 IGenericRepository 派生的 IDeveloperRepository。这可能会奏效,但考虑到未来实体的添加和需求的变化,这种变化不是一个明智的选择。

想象一下有 20-30 个新实体并且必须创建大量新仓储?不是个好主意,是吗?考虑在 IDevloperRepository 及其实现中具有多种方法,例如 GetDevelopersWithSalariesGreaterThan (decimal salary) 和 GetDevelopersWithExperienceLessThan (int years),不简洁,是吗?

如果有更简洁的方法来解决这个需求呢?这正是规约模式派上用场的地方。

在 ASP.NET Core 中使用规约模式增强仓储模式

规约模式乍一看可能会觉得很复杂。我也感觉到了。但是,一旦您添加了某些基类和评估器,您所要做的就是创建规约类,根据您的要求,这些类通常为 2 到 10 行。让我们开始使用 ASP.NET Core 中的规约模式。

在 Core 项目下,添加一个新文件夹并将其命名为 Specifications。这是所有与规约相关的接口都要去的地方。

创建一个新接口并将其命名为 ISpecification.cs

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    List<Expression<Func<T, object>>> Includes { get; }
    Expression<Func<T, object>> OrderBy { get; }
    Expression<Func<T, object>> OrderByDescending { get; }
}

这只是一个最小的实现。让我解释每个声明的方法定义。

  • Criteria - 您可以在此处添加基于实体的表达式。
  • Includes – 如果要包含外键表数据,可以使用此方法添加它。
  • OrderBy 和 OrderByDescending 是不言自明的。

接下来,在同一文件夹中,添加一个新类 BaseSpecifcation。这将是 ISpecification 接口的实现。

public class BaseSpecifcation<T> : ISpecification<T>
{
    public BaseSpecifcation()
    {
    }
    public BaseSpecifcation(Expression<Func<T, bool>> criteria)
    {
        Criteria = criteria;
    }
    public Expression<Func<T, bool>> Criteria { get; }
    public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
    public Expression<Func<T, object>> OrderBy { getprivate set; }
    public Expression<Func<T, object>> OrderByDescending { getprivate set; }
    protected void AddInclude(Expression<Func<T, object>> includeExpression)
    {
        Includes.Add(includeExpression);
    }
    protected void AddOrderBy(Expression<Func<T, object>> orderByExpression)
    {
        OrderBy = orderByExpression;
    }
    protected void AddOrderByDescending(Expression<Func<T, object>> orderByDescExpression)
    {
        OrderByDescending = orderByDescExpression;
    }
}

在这里,我们将添加 3 个基本方法和一个构造函数。

  • 将表达式添加到 Includes 属性
  • 将表达式添加到 OrderBy 属性
  • 将表达式添加到 OrderByDescending 属性
  • 您可以注意到我们还有一个接受条件的构造函数。Criteria 可以是 (x=>x.Salary > 100 )  等。你明白了,是吗?

升级泛型仓储

首先,让我们在 IGenericRepository 接口中添加一个方法。

IEnumerable<T> FindWithSpecificationPattern(ISpecification<T> specification = null);

接下来,让我们在 GenericRepository 类中实现新方法。

public IEnumerable<T> FindWithSpecificationPattern(ISpecification<T> specification = null)
{
    return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), specification);
}

现在,设置所有这些背后的想法是创建可以返回特定结果集的单独规约类。这些新规约类中的每一个都将从 BaseSpecification 类继承。明白了吗?现在让我们创建这些规约类,以便它有意义

因此,让我们得出 2 个要求 / 规约:

1. 按薪水降序返回开发人员列表的规约。
2. 另一个规约返回具有 N 或以上经验的开发人员列表及其地址。

在 Core 项目的同一个 Specification 文件夹下,添加我们的第一个规约类 DeveloperByIncomeSpecification

public class DeveloperByIncomeSpecification : BaseSpecifcation<Developer>
{
    public DeveloperByIncomeSpecification()
    {            
        AddOrderByDescending(x => x.EstimatedIncome);
    }
}

在这里,您可以看到我们从 BaseSpecification 类派生并在构造函数中使用 AddOrderByDescending 方法。理想情况下,此规约将返回一个按收入递减顺序排列的开发人员列表。

接下来,让我们添加另一个类,DeveloperWithAddressSpecification

public class DeveloperWithAddressSpecification : BaseSpecifcation<Developer>
{
    public DeveloperWithAddressSpecification(int years) : base(x=>x.EstimatedIncome > years)
    {
        AddInclude(x => x.Address);
    }
}

因此,这里我们将查询表达式传递给 Specification Class 的基类,它是 BaseSpecification 的构造函数,然后将其添加到我们之前创建的 Criteria 属性中。其实很简单。

现在,随着我们的规约类准备就绪,让我们添加 api 端点。

在 API 项目下,在 Controllers 文件夹下添加一个新的 API Controller,并将其命名为 DevelopersController。

public class DevelopersController : ControllerBase
{
    public readonly IGenericRepository<Developer> _repository;
    public DevelopersController(IGenericRepository<Developer> repository)
    {
        _repository = repository;
    }
    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var developers = await _repository.GetAllAsync();
        return Ok(developers);
    }
    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(int id)
    {
        var developer = await _repository.GetByIdAsync(id);
        return Ok(developer);
    }
    [HttpGet("specify")]
    public async Task<IActionResult> Specify()
    {
        var specification = new DeveloperWithAddressSpecification(3);
        //var specification = new DeveloperByIncomeSpecification();
        var developers = _repository.FindWithSpecificationPattern(specification);
        return Ok(developers);
    }
}

第 3 – 7 行:将 IGenericRepository 注入到 Controller 的构造函数中。第 8 – 19 行:使用仓储实例返回所有开发人员和具有特定 Id 的开发人员的标准端点。

第 20 – 27 行:这是控制器最有趣的部分。这里的第 23 行和第 24 行是我们之前创建的 2 个规约类。这只是为了证明可以在控制器或使用 GenericRepository 的任何地方创建任何此类规约实例。我们将使用 DeveloperWithAddressSpecification (3) 进行演示。

现在让我们运行应用程序并检查指定端点的结果。

可以看到还返回了地址数据。现在,回到控制器,注释掉第 24 行,让我们暂时使用 DeveloperByIncomeSpecification。再次运行应用程序。

现在您可以注意到没有返回地址数据。为什么?很简单,因为我们使用了不同的规约,没有提到添加 Address 实体。相反,该规约按收入的递减顺序返回开发人员的集合。简单,但整洁对吗?这可能是 ASP.NET Core 应用程序中最酷的设计模式之一。

很奇怪,但这实际上是您可以理解规约模式是什么的时候 根据维基百科 - 在计算机编程中,规约模式是一种特定的软件设计模式,其中可以通过使用布尔逻辑将业务规则链接在一起来重新组合业务规则。该模式经常用于领域驱动设计的上下文中。

现在更有意义了,是吗?业务规则(我们要求返回具有一定经验水平或更高级别的开发人员)通过链接标准(这发生在 DeveloperWithAddressSpecification 类中)组合在一起,这是一个布尔逻辑。很简单,但是太强大了

展望未来,这种模式的可能性是无穷无尽的,并且非常有助于扩展应用程序。这种模式也可能支持 Data-Shaping 和分页。非常强大的模式,学习曲线很小,是吗?这是这篇文章的总结。

总结

在本文中,我们介绍了 ASP.NET Core 应用程序中的规约模式,以及它如何通过占上风来增强泛型仓储模式。我们还构建了一个完整的 Web API 应用程序,该应用程序遵循洋葱架构以进行干净的代码管理。你也可以在我的 Github 上找到完整的源代码。有任何建议或问题吗?请随时将它们留在下面的评论部分。Thanks and Happy Coding!

欢迎关注我的个人公众号”My IO“

参考资料

[1]

完整源代码: https://github.com/iammukeshm/specification-pattern-asp-net-core


本文分享自微信公众号 - dotNET 跨平台(opendotnet)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与 “OSC 源创计划”,欢迎正在阅读的你也加入,一起分享。

Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting resul...

Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting resul...

mybatis 插入数据时报错:

Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.sql.SQLException: 不支持的特性
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:8)

原因:加了如下标红的设置(useGeneratedKeys="true" 把新增加的主键赋值到自己定义的 keyProperty(id)中)

<insert id="insert" parameterType="com.vimtech.bms.business.domain.monitor.finan.AssetsVisitReportWithBLOBs" useGeneratedKeys="true" keyProperty="serialid">

解决方法一:没什么用的话,删除标红的即可;

解决方法二:用 selectKey 先查下自增的主键 ID 值然后赋给相应的主键 ID 即可

oracle 的写法 (查序列的下一个值然后赋值):

<selectKey resultType="java.lang.Long" order="BEFORE" keyProperty="###">
  SELECT SEQ_ASSETS_VISIT_REPORT.nextval AS ### FROM dual
</selectKey>

SQLServer 的写法

<selectKey resultType="java.lang.Integer" keyProperty="timelineConfigId">
  SELECT @@IDENTITY AS TIMELINE_CONFIG_ID
</selectKey>

 

declaration of ''void* operator new [](size_t)'' has a different exception specifier

declaration of ''void* operator new [](size_t)'' has a different exception specifier

其实就是c++11和c++98的定义的坑

https://stackoverflow.com/questions/39188919/different-exception-specifier-with-g-6-2

Are you using C++11 or later?

The original operator new() declarations in C++98

throwing:   
void* operator new (std::size_t size) throw (std::bad_alloc);

nothrow:
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();

placement:
void* operator new (std::size_t size, void* ptr) throw();

Have been changed in C++11 to use noexcept keyword:

throwing:   
void* operator new (std::size_t size);

nothrow:    
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;

placement:  
void* operator new (std::size_t size, void* ptr) noexcept;

Executor框架(一)Callable、Future、Executor和ExecutorService

Executor框架(一)Callable、Future、Executor和ExecutorService

本文已同步至个人博客liaosi''s blog-Executor框架(一)Callable、Future、Executor和ExecutorService

引言

Executor框架是指JDK 1.5中引入的一系列并发库中与Executor相关的功能类,包括Executor、Executors、ExecutorService、Future、Callable等。

为什么要引入Executor框架?

如果使用new Thread(...).start()的方法处理多线程,有如下缺点:

  • 开销大。对于JVM来说,每次新建线程和销毁线程都会有很大的开销。
  • 线程缺乏管理。没有一个池来限制线程的数量,如果并发量很高,会创建很多的线程,而且线程之间可能会有相互竞争,这将会过多得占用系统资源,增加系统资源的消耗量。而且线程数量超过系统负荷,容易导致系统不稳定。

使用线程池的方式,有如下优点:

  • 复用线程。通过复用创建的了的线程,减少了线程的创建、消亡的开销。
  • 有效控制并发线程数。
  • 提供了更简单灵活的线程管理。可以提供定时执行、定期执行、单线程、可变线程数等多种线程使用功能。

Executor框架的UML图

图片描述

下面开始分析一下Executor框架中几个比较重要的接口和类。

Callable

Callable 位于java.util.concurrent包下,它是一个接口,只声明了一个叫做call()的方法。

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable 接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。和 Runnable 接口中的run()方法类似,Callable 提供一个call()方法作为线程的执行体。但是call()方法比run()方法更加强大,这要体现在:

1)call 方法可以有返回值。返回值的类型即是 Callable 接口传递进来的V类型。
2)call 方法可以声明抛出异常。

Future

Future 接口位于java.util.concurrent包下,是Java 1.5中引入的接口.
Future主要用来对具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get()方法获取执行结果,get()方法会阻塞直到任务返回结果。

public interface Future<V> {


    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

当你提交一个Callable对象给线程池时,将得到一个Future对象,并且它和你传入的Callable示例有相同泛型。

Future 接口中的5个方法:

  • cancel方法:用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
    参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。
    如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;
    如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;
    如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  • isCancelled方法:表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  • isDone方法:表示任务是否已经完成,若任务完成,则返回true;
  • get()方法:用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  • get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,会抛出TimeoutException异常。

也就是说Future提供了三种功能:

1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果

Executor

Executor是一个接口,它将任务的提交与任务的执行分离开来,定义了一个接收Runnable对象的方法execute。Executor是Executor框架中最基础的一个接口,类似于集合中的Collection接口。

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

ExecutorService

ExecutorService继承了Executor,是一个比Executor使用更广泛的子类接口。定义了终止任务、提交任务、跟踪任务返回结果等方法。
一个ExecutorService是可以关闭的,关闭之后它将不能再接收任何任务。对于不再使用的ExecutorService,应该将其关闭以释放资源。

ExecutorService方法介绍

package java.util.concurrent;

import java.util.List;
import java.util.Collection;

public interface ExecutorService extends Executor {

    /**
     * 平滑地关闭线程池,已经提交到线程池中的任务会继续执行完。
     */
    void shutdown();

    /**
     * 立即关闭线程池,返回还没有开始执行的任务列表。
     * 会尝试中断正在执行的任务(每个线程调用 interruput方法),但这个行为不一定会成功。
     */
    List<Runnable> shutdownNow();

    /**
     * 判断线程池是否已经关闭
     */
    boolean isShutdown();

    /**
     * 判断线程池的任务是否已经执行完毕。
     * 注意此方法调用之前需要先调用shutdown()方法或者shutdownNow()方法,否则总是会返回false
     */
    boolean isTerminated();

    /**
     * 判断线程池的任务是否都执行完。
     * 如果没有任务没有执行完毕则阻塞,直至任务完成或者达到了指定的timeout时间就会返回
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 提交带有一个返回值的任务到线程池中去执行(回调),返回的 Future 表示任务的待定结果。
     * 当任务成功完成后,通过 Future 实例的 get() 方法可以获取该任务的结果。
     * Future 的 get() 方法是会阻塞的。
     */
    <T> Future<T> submit(Callable<T> task);

    /**
     *提交一个Runnable的任务,当任务完成后,可以通过Future.get()获取的是提交时传递的参数T result
     * 
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 提交一个Runnable的人无语,它的Future.get()得不到任何内容,它返回值总是Null。
     * 为什么有这个方法?为什么不直接设计成void submit(Runnable task)这种方式?
     * 这是因为Future除了get这种获取任务信息外,还可以控制任务,
     具体体现在 Future的这个方法上:boolean cancel(boolean mayInterruptIfRunning)
     这个方法能够去取消提交的Rannable任务。
     */
    Future<?> submit(Runnable task);

    /**
     * 执行一组给定的Callable任务,返回对应的Future列表。列表中每一个Future都将持有该任务的结果和状态。
     * 当所有任务执行完毕后,方法返回,此时并且每一个Future的isDone()方法都是true。
     * 完成的任务可能是正常结束,也可以是异常结束
     * 如果当任务执行过程中,tasks集合被修改了,那么方法的返回结果将是不确定的,
       即不能确定执行的是修改前的任务,还是修改后的任务
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    /**
     * 执行一组给定的Callable任务,返回对应的Future列表。列表中每一个Future都将持有该任务的结果和状态。
     * 当所有任务执行完毕后或者超时后,方法将返回,此时并且每一个Future的isDone()方法都是true。
     * 一旦方法返回,未执行完成的任务被取消,而完成的任务可能正常结束或者异常结束, 
     * 完成的任务可以是正常结束,也可以是异常结束
     * 如果当任务执行过程中,tasks集合被修改了,那么方法的返回结果将是不确定的
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 执行一组给定的Callable任务,当成功执行完(没抛异常)一个任务后此方法便返回,返回的是该任务的结果
     * 一旦此正常返回或者异常结束,未执行的任务都会被取消。 
     * 如果当任务执行过程中,tasks集合被修改了,那么方法的返回结果将是不确定的
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    /**
     * 执行一组给定的Callable任务,当在timeout(超时)之前成功执行完(没抛异常)一个任务后此方法便返回,返回的是该任务的结果
     * 一旦此正常返回或者异常结束,未执行的任务都会被取消。 
     * 如果当任务执行过程中,tasks集合被修改了,那么方法的返回结果将是不确定的
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

shutdown() 和 shutdownNow() 是用来关闭连接池的两个方法,而且这两个方法都是在当前线程立即返回,不会阻塞至线程池中的方法执行结束。调用这两个方法之后,连接池将不能再接受任务。
下面给写几个示例来加深ExecutorService的方法的理解。
先写两个任务类:ShortTask和LongTask,这两个类都继承了Runnable接口,ShortTask的run()方法执行很快,LongTask的run()方法执行时间为10s。

public class LongTask implements Runnable {


    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(10L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("complete a long task");
    }

}
public class ShortTask implements Runnable {

    @Override
    public void run() {
        System.out.println("complete a short task...");
    }
    
}

测试shutdown()方法

    @Test
    public void testShutdown() throws InterruptedException {
        ExecutorService threadpool = Executors.newFixedThreadPool(4);
        //将4个任务提交到有4个线程的线程池
        threadpool.submit(new ShortTask());
        threadpool.submit(new ShortTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new ShortTask());

        //关闭线程池
        threadpool.shutdown();

        boolean isShutdown = threadpool.isShutdown();
        System.out.println("线程池是否已经关闭:" + isShutdown);

        final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        while (!threadpool.awaitTermination(1L, TimeUnit.SECONDS)) {
            System.out.println("线程池中还有任务在执行,当前时间:" + sdf.format(new Date()));
        }

        System.out.println("线程池中已经没有在执行的任务,线程池已完全关闭!");
    }

控制台输出的结果:

complete a short task...
线程池是否已经关闭:true
complete a short task...
complete a short task...
线程池中还有任务在执行,当前时间:19:53:08
线程池中还有任务在执行,当前时间:19:53:09
线程池中还有任务在执行,当前时间:19:53:10
线程池中还有任务在执行,当前时间:19:53:11
线程池中还有任务在执行,当前时间:19:53:12
线程池中还有任务在执行,当前时间:19:53:13
线程池中还有任务在执行,当前时间:19:53:14
线程池中还有任务在执行,当前时间:19:53:15
线程池中还有任务在执行,当前时间:19:53:16
complete a long task
线程池中已经没有在执行的任务,线程池已完全关闭!

测试shutdownNow()方法

    @Test
    public void testShutdownNow() throws InterruptedException {
        ExecutorService threadpool = Executors.newFixedThreadPool(3);
        //将5个任务提交到有3个线程的线程池
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());
        threadpool.submit(new LongTask());

        //主线程睡眠2秒钟,让3个线程池的任务都开始执行
        TimeUnit.SECONDS.sleep(1L);

        //关闭线程池
        List<Runnable> waiteRunnables = threadpool.shutdownNow();
        System.out.println("还没有执行的任务数:" + waiteRunnables.size());

        boolean isShutdown = threadpool.isShutdown();
        System.out.println("线程池是否已经关闭:" + isShutdown);

        final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        while (!threadpool.awaitTermination(1L, TimeUnit.SECONDS)) {
            System.out.println("线程池中还有任务在执行,当前时间:" + sdf.format(new Date()));
        }

        System.out.println("线程池中已经没有在执行的任务,线程池已完全关闭!");
    }

控制台输出:

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.lzumetal.multithread.threadpooltest.LongTask.run(LongTask.java:11)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
还没有执行的任务数:2
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
线程池是否已经关闭:true
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
complete a long task
    at java.lang.Thread.run(Thread.java:748)
complete a long task
java.lang.InterruptedException: sleep interrupted
complete a long task
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.lzumetal.multithread.threadpooltest.LongTask.run(LongTask.java:11)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.lzumetal.multithread.threadpooltest.LongTask.run(LongTask.java:11)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
线程池中已经没有在执行的任务,线程池已完全关闭!

从上面我们看到当调用了试图shutdownNow()后,那三个执行的任务都被interrupt了。而且awaitTermination(long timeout, TimeUnit unit)方法返回的是true。

测试submit(Callable<T> task)方法

Callabel任务类:

package com.lzumetal.multithread.threadpooltest;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class CallableTask implements Callable<String> {

    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(5L);
        return "success";
    }


}

测试:

    @Test
    public void testSubmitCallable() {

        ExecutorService threadpool = null;
        try {

            threadpool = Executors.newFixedThreadPool(3);

            final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            System.out.println("提交一个callable任务到线程池,现在时间是:" + sdf.format(new Date()));

            Future<String> future = threadpool.submit(new CallableTask());

            System.out.println("获取callable任务的结果:" + future.get() + ",现在时间是:" + sdf.format(new Date()));
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            if (threadpool != null) {
                threadpool.shutdown();
            }
        }

    }

控制台输出:

提交一个callable任务到线程池,现在时间是:20:25:27
获取callable任务的结果:success,现在时间是:20:25:32

我们今天的关于JpaSpecificationExecutor规范中的JOIN + ORDER BYjpa criteriaquery的分享就到这里,谢谢您的阅读,如果想了解更多关于ASP.NET Core 中的规约模式(Specification Pattern )—— 增强泛型仓储模式、Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting resul...、declaration of ''void* operator new [](size_t)'' has a different exception specifier、Executor框架(一)Callable、Future、Executor和ExecutorService的相关信息,可以在本站进行搜索。

本文标签:

上一篇Swift 中的 `let` 和 `var` 有什么区别?(let跟var的区别)

下一篇Git 扩展:Win32 错误 487:无法为 cygwin 的堆预留空间,Win32 错误 0(无法为tom clancy执行启动程序)