Dapper 简介

Dapper是.NET的一款轻量级ORM工具(GitHub),也可称为简单对象映射器。在速度方面拥有微型ORM之王的称号。

它是半自动的,也就是说实体类和SQL语句都要自己写,但它提供自动对象映射。是通过对IDbConnection接口的扩展来操作数据库的。

优点

  • 轻量,只有一个文件
  • 性能高,Dapper的速度接近与IDataReader,取列表的数据超过了DataTable。
  • 支持多种数据库。Dapper可以在所有Ado.net Providers下工作,包括sqlite, sqlce, firebird, oracle, MySQL, PostgreSQL and SQL Server
  • 使用Dapper可以自动进行对象映射,通过Emit反射IDataReader的序列队列,来快速的得到和产生对象

使用 Dapper

下面简单创建一个Web API应用并通过Dapper访问MySQL数据。

  1. 创建MySQL测试数据

    CREATE SCHEMA `ormdemo` ;
    
    CREATE TABLE `ormdemo`.`category` (
    `Id` INT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(45) NOT NULL,
    PRIMARY KEY (`Id`)); CREATE TABLE `ormdemo`.`product` (
    `Id` INT NOT NULL AUTO_INCREMENT,
    `Name` VARCHAR(45) NOT NULL,
    `Price` DECIMAL(19,2) NULL,
    `Quantity` INT NULL,
    `CategoryId` INT NOT NULL,
    PRIMARY KEY (`Id`),
    INDEX `fk_product_category_idx` (`CategoryId` ASC),
    CONSTRAINT `fk_product_category`
    FOREIGN KEY (`CategoryId`)
    REFERENCES `ormdemo`.`category` (`Id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION); INSERT INTO `ormdemo`.`category` (`Name`) VALUES("Phones");
    INSERT INTO `ormdemo`.`category` (`Name`) VALUES("Computers"); INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("iPhone8",4999.99,10,1);
    INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("iPhone7",2999.99,10,1);
    INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("HP750",6000.00,5,2);
    INSERT INTO `ormdemo`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("HP5000",12000.00,10,2);
  2. 创建Web API应用并添加NuGet引用

    Install-Package MySql.Data
    Install-Package Dapper
  3. 新建一个Product类

    public class Category
    {
    public int Id { get; set; } public string Name { get; set; }
    } public class Product
    {
    public int Id { get; set; } public string Name { get; set; } public int Quantity { get; set; } public decimal Price { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; }
    }
  4. 新建一个DBConfig类用于创建并返回数据库连接

    using MySql.Data.MySqlClient;
    using System.Data;
    using System.Configuration; public class DBConfig
    {
    //ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
    private static string DefaultSqlConnectionString = @"server=127.0.0.1;database=ormdemo;uid=root;pwd=Open0001;SslMode=none;"; public static IDbConnection GetSqlConnection(string sqlConnectionString = null)
    {
    if (string.IsNullOrWhiteSpace(sqlConnectionString))
    {
    sqlConnectionString = DefaultSqlConnectionString;
    }
    IDbConnection conn = new MySqlConnection(sqlConnectionString);
    conn.Open();
    return conn;
    }
    }
  5. 创建简单的仓储接口和类

    public interface IProductRepository
    {
    Task<bool> AddAsync(Product prod);
    Task<IEnumerable<Product>> GetAllAsync();
    Task<Product> GetByIDAsync(int id);
    Task<bool> DeleteAsync(int id);
    Task<bool> UpdateAsync(Product prod);
    }
    public class ProductRepository : IProductRepository
    {
    public async Task<IEnumerable<Product>> GetAllAsync()
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    return await conn.QueryAsync<Product>(@"SELECT Id
    ,Name
    ,Quantity
    ,Price
    ,CategoryId
    FROM Product");
    }
    } public async Task<Product> GetByIDAsync(int id)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    string sql = @"SELECT Id
    ,Name
    ,Quantity
    ,Price
    ,CategoryId
    FROM Product
    WHERE Id = @Id";
    return await conn.QueryFirstOrDefaultAsync<Product>(sql, new { Id = id });
    }
    } public async Task<bool> AddAsync(Product prod)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    string sql = @"INSERT INTO Product
    (Name
    ,Quantity
    ,Price
    ,CategoryId)
    VALUES
    (@Name
    ,@Quantity
    ,@Price
    ,@CategoryId)";
    return await conn.ExecuteAsync(sql, prod) > 0;
    }
    } public async Task<bool> UpdateAsync(Product prod)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    string sql = @"UPDATE Product SET
    Name = @Name
    ,Quantity = @Quantity
    ,Price= @Price
    ,CategoryId= @CategoryId
    WHERE Id = @Id";
    return await conn.ExecuteAsync(sql, prod) > 0;
    }
    } public async Task<bool> DeleteAsync(int id)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    string sql = @"DELETE FROM Product
    WHERE Id = @Id";
    return await conn.ExecuteAsync(sql, new { Id = id }) > 0;
    }
    }
    }

    在Startup ConfigureServices方法里面配置依赖注入

    public void ConfigureServices(IServiceCollection services)
    {
    services.AddTransient<IProductRepository, ProductRepository>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
  6. 在Controller里面调用仓储方法

    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
    private readonly IProductRepository _productRepository;
    public ProductController(IProductRepository productRepository)
    {
    _productRepository = productRepository;
    } [HttpGet]
    public async Task<IActionResult> Get()
    {
    var data = await _productRepository.GetAllAsync();
    return Ok(data);
    } [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
    var data = await _productRepository.GetByIDAsync(id);
    return Ok(data);
    } [HttpPost]
    public async Task<IActionResult> Post([FromBody] Product prod)
    {
    if (!ModelState.IsValid)
    {
    return BadRequest(ModelState);
    } await _productRepository.AddAsync(prod);
    return NoContent();
    } [HttpPut("{id}")]
    public async Task<IActionResult> Put(int id, [FromBody] Product prod)
    {
    if (!ModelState.IsValid)
    {
    return BadRequest(ModelState);
    } var model = await _productRepository.GetByIDAsync(id);
    model.Name = prod.Name;
    model.Quantity = prod.Quantity;
    model.Price = prod.Price;
    await _productRepository.UpdateAsync(model); return NoContent();
    } [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
    await _productRepository.DeleteAsync(id);
    return NoContent();
    }
    }
  7. 测试API是否可以正常工作

  8. Dapper对存储过程和事务的支持

    存储过程

    using (var connection = My.ConnectionFactory())
    {
    connection.Open(); var affectedRows = connection.Execute(sql,
    new {Kind = InvoiceKind.WebInvoice, Code = "Single_Insert_1"},
    commandType: CommandType.StoredProcedure); My.Result.Show(affectedRows);
    }

    事务

    using (var connection = My.ConnectionFactory())
    {
    connection.Open(); using (var transaction = connection.BeginTransaction())
    {
    var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"}, transaction: transaction); transaction.Commit();
    }
    }
  9. Dapper对多表映射的支持

    var selectAllProductWithCategorySQL = @"select * from product p
    inner join category c on c.Id = p.CategoryId
    Order by p.Id";
    var allProductWithCategory = connection.Query<Product, Category, Product>(selectAllProductWithCategorySQL, (prod, cg) => { prod.Category = cg; return prod; });

使用 Dapper Contrib 或其他扩展

Dapper Contrib扩展Dapper提供了CRUD的方法

  • Get
  • GetAll
  • Insert
  • Update
  • Delete
  • DeleteAll
  1. 添加NuGet引用Dapper.Contrib

    Install-Package Dapper.Contrib
  2. 为Product类添加数据注解

    [Table("Product")]
    public class Product
    {
    [Key]
    public int Id { get; set; } public string Name { get; set; } public int Quantity { get; set; } public decimal Price { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; }
    }
  3. 增加一个新的仓储类继承

    public class ContribProductRepository : IProductRepository
    {
    public async Task<bool> AddAsync(Product prod)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    return await conn.InsertAsync(prod) > 0;
    }
    } public async Task<IEnumerable<Product>> GetAllAsync()
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    return await conn.GetAllAsync<Product>();
    }
    } public async Task<Product> GetByIDAsync(int id)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    return await conn.GetAsync<Product>(id);
    }
    } public async Task<bool> DeleteAsync(int id)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    var entity = await conn.GetAsync<Product>(id);
    return await conn.DeleteAsync(entity);
    }
    } public async Task<bool> UpdateAsync(Product prod)
    {
    using (IDbConnection conn = DBConfig.GetSqlConnection())
    {
    return await conn.UpdateAsync(prod);
    }
    }
    }

    修改Startup ConfigureServices方法里面配置依赖注入

    services.AddTransient<IProductRepository, ContribProductRepository>();

    测试,这样可以少写了不少基本的SQL语句。

  4. 其他一些开源的Dapper扩展

    类库 提供的方法
    Dapper.SimpleCRUD Get GetList GetListPaged Insert Update Delete DeleteList RecordCount
    Dapper Plus Bulk Insert Bulk Delete Bulk Update Bulk Merge Bulk Action Async Bulk Also Action Bulk Then Action
    Dapper.FastCRUD Get Find Insert Update BulkUpdate Delete BulkDelete Count
    Dapper.Mapper Multi-mapping

引入工作单元 Unit of Work

仓储模式往往需要工作单元模式的介入来负责一系列仓储对象的持久化,确保数据完整性。网上关于工作单元模式的实现方式有多种,但其本质都是工作单元类通过创建一个所有仓储共享的数据库上下文对象,来组织多个仓储对象。

网上的一些实现方式:

  • Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application

    微软之前给出的一个示例,仓储类做为工作单元的变量,并通过工作单元传入一致的context参数创建。

    public class UnitOfWork : IDisposable
    {
    private SchoolContext context = new SchoolContext();
    private GenericRepository<Department> departmentRepository;
    private GenericRepository<Course> courseRepository; public GenericRepository<Department> DepartmentRepository
    {
    get
    { if (this.departmentRepository == null)
    {
    this.departmentRepository = new GenericRepository<Department>(context);
    }
    return departmentRepository;
    }
    } public GenericRepository<Course> CourseRepository
    {
    get
    { if (this.courseRepository == null)
    {
    this.courseRepository = new GenericRepository<Course>(context);
    }
    return courseRepository;
    }
    } public void Save()
    {
    context.SaveChanges();
    } private bool disposed = false; protected virtual void Dispose(bool disposing)
    {
    if (!this.disposed)
    {
    if (disposing)
    {
    context.Dispose();
    }
    }
    this.disposed = true;
    } public void Dispose()
    {
    Dispose(true);
    GC.SuppressFinalize(this);
    }
    }
  • DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践

    博客园一位大神的总结,最终采用的方案是仓储类负责查询,工作单元类负责增删改等数据持久化操作。

优缺点不作讨论,适合自己的就是最好的,这里采用了另外一种实现方式:

  • 定义DapperDBContext

    public interface IContext : IDisposable
    {
    bool IsTransactionStarted { get; } void BeginTransaction(); void Commit(); void Rollback();
    } public abstract class DapperDBContext : IContext
    {
    private IDbConnection _connection;
    private IDbTransaction _transaction;
    private int? _commandTimeout = null;
    private readonly DapperDBContextOptions _options; public bool IsTransactionStarted { get; private set; } protected abstract IDbConnection CreateConnection(string connectionString); protected DapperDBContext(IOptions<DapperDBContextOptions> optionsAccessor)
    {
    _options = optionsAccessor.Value; _connection = CreateConnection(_options.Configuration);
    _connection.Open(); DebugPrint("Connection started.");
    } #region Transaction public void BeginTransaction()
    {
    if (IsTransactionStarted)
    throw new InvalidOperationException("Transaction is already started."); _transaction = _connection.BeginTransaction();
    IsTransactionStarted = true; DebugPrint("Transaction started.");
    } public void Commit()
    {
    if (!IsTransactionStarted)
    throw new InvalidOperationException("No transaction started."); _transaction.Commit();
    _transaction = null; IsTransactionStarted = false; DebugPrint("Transaction committed.");
    } public void Rollback()
    {
    if (!IsTransactionStarted)
    throw new InvalidOperationException("No transaction started."); _transaction.Rollback();
    _transaction.Dispose();
    _transaction = null; IsTransactionStarted = false; DebugPrint("Transaction rollbacked and disposed.");
    } #endregion Transaction #region Dapper Execute & Query public async Task<int> ExecuteAsync(string sql, object param = null, CommandType commandType = CommandType.Text)
    {
    return await _connection.ExecuteAsync(sql, param, _transaction, _commandTimeout, commandType);
    } public async Task<IEnumerable<T>> QueryAsync<T>(string sql, object param = null, CommandType commandType = CommandType.Text)
    {
    return await _connection.QueryAsync<T>(sql, param, _transaction, _commandTimeout, commandType);
    } public async Task<T> QueryFirstOrDefaultAsync<T>(string sql, object param = null, CommandType commandType = CommandType.Text)
    {
    return await _connection.QueryFirstOrDefaultAsync<T>(sql, param, _transaction, _commandTimeout, commandType);
    } public async Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, object param = null, string splitOn = "Id", CommandType commandType = CommandType.Text)
    {
    return await _connection.QueryAsync(sql, map, param, _transaction, true, splitOn, _commandTimeout, commandType);
    } #endregion Dapper Execute & Query public void Dispose()
    {
    if (IsTransactionStarted)
    Rollback(); _connection.Close();
    _connection.Dispose();
    _connection = null; DebugPrint("Connection closed and disposed.");
    } private void DebugPrint(string message)
    {
    #if DEBUG
    Debug.Print(">>> UnitOfWorkWithDapper - Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, message);
    #endif
    }
    }
  • 定义UnitOfWork

    public interface IUnitOfWork : IDisposable
    {
    void SaveChanges();
    } public class UnitOfWork : IUnitOfWork
    {
    private readonly IContext _context; public UnitOfWork(IContext context)
    {
    _context = context;
    _context.BeginTransaction();
    } public void SaveChanges()
    {
    if (!_context.IsTransactionStarted)
    throw new InvalidOperationException("Transaction have already been commited or disposed."); _context.Commit();
    } public void Dispose()
    {
    if (_context.IsTransactionStarted)
    _context.Rollback();
    }
    }
  • 定义UnitOfWorkFactory

    public interface IUnitOfWorkFactory
    {
    IUnitOfWork Create();
    } public class DapperUnitOfWorkFactory : IUnitOfWorkFactory
    {
    private readonly DapperDBContext _context; public DapperUnitOfWorkFactory(DapperDBContext context)
    {
    _context = context;
    } public IUnitOfWork Create()
    {
    return new UnitOfWork(_context);
    }
    }
  • 定义服务扩展

    public class DapperDBContextOptions : IOptions<DapperDBContextOptions>
    {
    public string Configuration { get; set; } DapperDBContextOptions IOptions<DapperDBContextOptions>.Value
    {
    get { return this; }
    }
    } public static class DapperDBContextServiceCollectionExtensions
    {
    public static IServiceCollection AddDapperDBContext<T>(this IServiceCollection services, Action<DapperDBContextOptions> setupAction) where T : DapperDBContext
    {
    if (services == null)
    {
    throw new ArgumentNullException(nameof(services));
    } if (setupAction == null)
    {
    throw new ArgumentNullException(nameof(setupAction));
    } services.AddOptions();
    services.Configure(setupAction);
    services.AddScoped<DapperDBContext, T>();
    services.AddScoped<IUnitOfWorkFactory, DapperUnitOfWorkFactory>(); return services;
    }
    }
  • 怎么使用

    1. 创建一个自己的Context并继承DapperDBContext。下面测试的TestDBContext是采用MySQL数据库并返回MySqlConnection。

      public class TestDBContext : DapperDBContext
      {
      public TestDBContext(IOptions<DapperDBContextOptions> optionsAccessor) : base(optionsAccessor)
      {
      } protected override IDbConnection CreateConnection(string connectionString)
      {
      IDbConnection conn = new MySqlConnection(connectionString);
      return conn;
      }
      }
    2. 仓储类里面添加DapperDBContext引用

      public class UowProductRepository : IProductRepository
      {
      private readonly DapperDBContext _context;
      public UowProductRepository(DapperDBContext context)
      {
      _context = context;
      } public async Task<Product> GetByIDAsync(int id)
      {
      string sql = @"SELECT Id
      ,Name
      ,Quantity
      ,Price
      ,CategoryId
      FROM Product
      WHERE Id = @Id";
      return await _context.QueryFirstOrDefaultAsync<Product>(sql, new { Id = id });
      } public async Task<bool> AddAsync(Product prod)
      {
      string sql = @"INSERT INTO Product
      (Name
      ,Quantity
      ,Price
      ,CategoryId)
      VALUES
      (@Name
      ,@Quantity
      ,@Price
      ,@CategoryId)";
      return await _context.ExecuteAsync(sql, prod) > 0;
      }
      }
    3. Startup里面注册服务

      public void ConfigureServices(IServiceCollection services)
      {
      services.AddDapperDBContext<TestDBContext>(options => {
      options.Configuration = @"server=127.0.0.1;database=ormdemo;uid=root;pwd=password;SslMode=none;";
      }); services.AddTransient<IProductRepository, UowProductRepository>();
      services.AddTransient<ICategoryRepository, UowCategoryRepository>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
      }
    4. Controller调用

      public class ProductController : ControllerBase
      {
      private readonly IUnitOfWorkFactory _uowFactory;
      private readonly IProductRepository _productRepository;
      private readonly ICategoryRepository _categoryRepository; public ProductController(IUnitOfWorkFactory uowFactory, IProductRepository productRepository, ICategoryRepository categoryRepository)
      {
      _uowFactory = uowFactory;
      _productRepository = productRepository;
      _categoryRepository = categoryRepository;
      } [HttpGet("{id}")]
      public async Task<IActionResult> Get(int id)
      {
      var data = await _productRepository.GetByIDAsync(id);
      return Ok(data);
      } [HttpPost]
      public async Task<IActionResult> Post([FromBody] Product prod)
      {
      if (!ModelState.IsValid)
      {
      return BadRequest(ModelState);
      } //await _productRepository.AddAsync(prod); using (var uow = _uowFactory.Create())
      {
      await _productRepository.AddAsync(prod); uow.SaveChanges();
      } return NoContent();
      }
      }

源代码

Github

参考

ASP.NET Core 中的 ORM 之 Dapper的更多相关文章

  1. ASP.NET Core 中的 ORM 之 Entity Framework

    目录 EF Core 简介 使用 EF Core(Code First) EF Core 中的一些常用知识点 实体建模 实体关系 种子数据 并发管理 执行 SQL 语句和存储过程 延迟加载和预先加载 ...

  2. asp.net core 六 Oracle ORM

         .netcore 中 Oracle ORM      在真正将项目移植到.netcore下,才发现会有很多问题,例如访问Oracle,问题出现的时间在2017年底          参考连接 ...

  3. 如何在ASP.NET Core中编写高效的控制器

    ​通过遵循最佳实践,可以编写更好的控制器.所谓的"瘦"控制器(指代码更少.职责更少的控制器)更容易阅读和维护.而且,一旦你的控制器很瘦,可能就不需要对它们进行太多测试了.相反,你可 ...

  4. ASP.NET Core 中的那些认证中间件及一些重要知识点

    前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...

  5. Asp.net Core中使用Session

    前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Cor ...

  6. 在ASP.NET Core中使用百度在线编辑器UEditor

    在ASP.NET Core中使用百度在线编辑器UEditor 0x00 起因 最近需要一个在线编辑器,之前听人说过百度的UEditor不错,去官网下了一个.不过服务端只有ASP.NET版的,如果是为了 ...

  7. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  8. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

  9. ASP.NET Core中的依赖注入(3): 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

随机推荐

  1. js多维数组转一维数组

    1,使用map方法 var arr = [1,[2,[[3,4],5],6]]; function unid(arr){ var arr1 = (arr + '').split(',');//将数组转 ...

  2. MySQL优化(四) 慢查询的定位及优化

    一.SQL语句优化的一般步骤: (1)通过 show status 命令了解各种 SQL 的执行效率: (2)定位执行效率较低的 SQL 语句(重点是 Select): (3)通过 explain 分 ...

  3. 软件推荐-有限元开发软件FELAC

    首页:http://yuanjisuan.cn/ 有限元语言及其编译器(Finite Element Language And it’s Compiler),以下简称FELAC是中国科学院数学与系统科 ...

  4. ubuntu上vsftpd服务配置

    Ubuntu上提供两种常用的ftp服务应用:vsftpd 和 tftpd,区别如下: 1)vsftpd 支持客户端上下传文件,支持浏览器显示及下载,支持用户名密码认证,支持匿名访问,默认端口TCP:2 ...

  5. Html5与Css3知识点拾遗(八)

    css5新增的元素与属性 表单内元素的属性 1. form属性 之前必须书写在表单内部.而在Html5中,可以放在任何位置,为元素指定一个form属性,属性值为该表单的id,就可以声明该元素属于指定表 ...

  6. 第一周Access课总结

    一.问:这节课学到了什么知识? 答:这周课程迎来新的学习领域,作为初次学Access有了一定的了解,Access是office办公软件中的一个极为重要的组成部分,它可以对大量的数据进行存储,查找,统计 ...

  7. 精选!15个必备的VSCode插件

    Visual Studio Code 是由微软开发的一款免费.跨平台的文本编辑器.由于其卓越的性能和丰富的功能,它很快就受到了大家的喜爱. 就像大多数 IDE 一样,VSCode 也有一个扩展和主题市 ...

  8. Spring集合注入

    1.集合注入 上一篇博客讲了spring得属性注入,通过value属性来配置基本数据类型,通过<property>标签的 ref 属性来配置对象的引用.如果想注入多个数据,那我们就要用到集 ...

  9. Java学习笔记30(集合框架四:List接口)

    List接口继承自Collection接口 具有重要的三大特点: 1.有序集合:存入和取出的顺序一致 2.此接口的用户可以对列表中每个元素插入位置精确的控制:可以通过索引操作 3.可以存储重复元素 L ...

  10. docker系统学习之docker界面管理

    docker可视化界面 dockerUI已废弃,转投Portainer项目 Portainer,轻量级管理界面,基本满足中小单位需求 官方Github https://github.com/porta ...