検索
ホームページバックエンド開発PHPチュートリアルSpring Boot、Mybatis、Redis は最新の Web プロジェクトを迅速に構築します

SpringBoot は、すぐに使用できるさまざまなプラグインを提供するため、現在最も主流の Java Web 開発フレームワークの 1 つとなっています。 Mybatis は、非常に軽量で使いやすい ORM フレームワークです。 Redis は、現在非常に主流の分散キー/値データベースであり、Web 開発ではデータベース クエリの結果をキャッシュするためによく使用されます。この記事では、Spring Boot、Mybatis、Redis を使用して最新の Web プロジェクトを迅速に構築する方法を紹介し、コードの品質を保証するために Spring Boot で単体テストをエレガントに作成する方法も紹介します。

このブログでは、SpringBoot を使用して Web アプリケーションを迅速に構築し、ORM フレームワークとして Mybatis を使用する方法を紹介します。パフォーマンスを向上させるために、Mybatis の 2 次キャッシュとして Redis を使用します。コードをテストするために、単体テストを作成し、H2 インメモリ データベースを使用してテスト データを生成しました。このプロジェクトを通じて、読者が最新の Java Web 開発のスキルとベスト プラクティスをすぐに習得できることを願っています。

この記事のサンプルコードはGithubでダウンロードできます: https://github.com/Lovelcp/spring-boot-mybatis-with-redis/tree/master

開発環境: mac 10.11
ide: Intellij 2017.1
jdk: 1.8
Spring-Boot: 1.5.3.RELEASE
Redis: 3.2.9
Mysql: 5.7

Spring-Boot

新しいプロジェクト

まず、Springを初期化する必要がありますプロジェクトを起動します。 Intellij の Spring Initializer を使用すると、新しい Spring-Boot プロジェクトを作成することが非常に簡単になります。まず、Intellij で New Project を選択します:

次に、選択インターフェイスで、Web、Mybatis、Redis、Mysql、H2 をチェックします:

新しいプロジェクトが成功すると、プロジェクトが表示されます。構造は以下に示すとおりです。

Spring Initializer は、スタートアップ クラス SpringBootMybatisWithRedisApplication を自動的に生成しました。このクラスのコードは非常に単純です:

@SpringBootApplication
public class SpringBootMybatisWithRedisApplication {
 public static void main(String[] args) {
 SpringApplication.run(SpringBootMybatisWithRedisApplication.class, args);
 }
}

@SpringBootApplication アノテーションは、Spring Boot の自動構成機能を有効にすることを示します。これで、プロジェクトのスケルトンが正常に構築されました。興味のある読者は、Intellij を通じてそれを開始して、その効果を確認できます。

新しい API インターフェースを作成します

次に、Web API を作成する必要があります。 Web プロジェクトが販売者の製品 (Product) の処理を​​担当していると仮定します。製品 ID に基づいて製品情報を返す get インターフェイスと、製品情報を更新する put インターフェイスを提供する必要があります。まず、製品 ID、製品名、価格を含む Product クラスを定義します。

public class Product implements Serializable {
 private static final long serialVersionUID = 1435515995276255188L;
 private long id;
 private String name;
 private long price;
 // getters setters
}

次に、Controller クラスを定義する必要があります。 Spring Boot は Spring MVC を Web コンポーネントとして内部的に使用するため、アノテーションを通じてインターフェイス クラスを迅速に開発できます:

@RestController
@RequestMapping("/product")
public class ProductController {
 @GetMapping("/{id}")
 public Product getProductInfo(
   @PathVariable("id")
     Long productId) {
  // TODO
  return null;
 }
 @PutMapping("/{id}")
 public Product updateProductInfo(
   @PathVariable("id")
     Long productId,
   @RequestBody
     Product newProduct) {
  // TODO
  return null;
 }
}

上記のコードで使用されているアノテーションの役割を簡単に紹介しましょう:

@RestController:クラスはコントローラーであり、Rest インターフェイスを提供します。つまり、すべてのインターフェイス値は Json 形式で返されます。このアノテーションは実際には @Controller と @ResponseBody を組み合わせたアノテーションであり、Rest API の開発が容易になります。

@RequestMapping、@GetMapping、@PutMapping: インターフェイスの URL アドレスを表します。クラスにマークされた @RequestMapping アノテーションは、クラスの下のすべてのインターフェイスの URL が /product で始まることを示します。 @GetMapping は、これが Get HTTP インターフェイスであることを示し、@PutMapping は、これが Put HTTP インターフェイスであることを示します。

@PathVariable、@RequestBody: パラメータのマッピング関係を示します。 /product/123 にアクセスする Get リクエストがあるとします。このリクエストは getProductInfo メソッドによって処理され、URL の 123 が productId にマップされます。同様に、Put リクエストの場合、リクエスト本文は newProduct オブジェクトにマップされます。

ここではインターフェイスを定義しただけで、製品情報はデータベースに保存されているため、実際の処理ロジックはまだ完成していません。次に、mybatis をプロジェクトに統合し、データベースと対話します。

Mybatisを統合する

データソースを設定する

まず、設定ファイルでデータソースを設定する必要があります。データベースとして mysql を使用します。ここでは、構成ファイルの形式として yaml を使用します。新しい application.yml ファイルをリソースディレクトリに作成します:

spring:
# 数据库配置
 datasource:
 url: jdbc:mysql://{your_host}/{your_db}
 username: {your_username}
 password: {your_password}
 driver-class-name: org.gjt.mm.mysql.Driver

Spring Boot には自動構成の機能があるため、新しい DataSource 構成クラスを作成する必要はありません。 Spring Boot は構成ファイルを自動的に読み込みます。データベース接続プールは、構成ファイルの情報に基づいて作成するのに非常に便利です。

著者は、設定ファイル形式として yaml を使用することをお勧めします。 XML は冗長に見えますが、YAML には階層構造がありません。これら 2 つの欠点を補うだけです。これが、Spring Boot がデフォルトで yaml 形式をサポートする理由です。

Mybatis を設定する

我们已经通过Spring Initializer在pom.xml中引入了mybatis-spring-boot-starte库,该库会自动帮我们初始化mybatis。首先我们在application.yml中填写mybatis的相关配置:

# mybatis配置
mybatis:
 # 配置映射类所在包名
 type-aliases-package: com.wooyoo.learning.dao.domain
 # 配置mapper xml文件所在路径,这里是一个数组
 mapper-locations:
 - mappers/ProductMapper.xml

然后,再在代码中定义ProductMapper类:

@Mapper
public interface ProductMapper {
 Product select(
   @Param("id")
     long id);
 void update(Product product);
}

这里,只要我们加上了@Mapper注解,Spring Boot在初始化mybatis时会自动加载该mapper类。

Spring Boot之所以这么流行,最大的原因是它自动配置的特性。开发者只需要关注组件的配置(比如数据库的连接信息),而无需关心如何初始化各个组件,这使得我们可以集中精力专注于业务的实现,简化开发流程。

访问数据库

完成了Mybatis的配置之后,我们就可以在我们的接口中访问数据库了。我们在ProductController下通过@Autowired引入mapper类,并且调用对应的方法实现对product的查询和更新操作,这里我们以查询接口为例:

@RestController
@RequestMapping("/product")
public class ProductController {
 @Autowired
 private ProductMapper productMapper;
 @GetMapping("/{id}")
 public Product getProductInfo(
   @PathVariable("id")
     Long productId) {
  return productMapper.select(productId);
 }
 // 避免篇幅过长,省略updateProductInfo的代码
}

然后在你的mysql中插入几条product的信息,就可以运行该项目看看是否能够查询成功了。

至此,我们已经成功地在项目中集成了Mybatis,增添了与数据库交互的能力。但是这还不够,一个现代化的Web项目,肯定会上缓存加速我们的数据库查询。接下来,将介绍如何科学地将Redis集成到Mybatis的二级缓存中,实现数据库查询的自动缓存。

集成Redis

配置Redis

同访问数据库一样,我们需要配置Redis的连接信息。在application.yml文件中增加如下配置:

spring:
 redis:
 # redis数据库索引(默认为0),我们使用索引为3的数据库,避免和其他数据库冲突
 database: 3
 # redis服务器地址(默认为localhost)
 host: localhost
 # redis端口(默认为6379)
 port: 6379
 # redis访问密码(默认为空)
 password:
 # redis连接超时时间(单位为毫秒)
 timeout: 0
 # redis连接池配置
 pool:
  # 最大可用连接数(默认为8,负数表示无限)
  max-active: 8
  # 最大空闲连接数(默认为8,负数表示无限)
  max-idle: 8
  # 最小空闲连接数(默认为0,该值只有为正数才有作用)
  min-idle: 0
  # 从连接池中获取连接最大等待时间(默认为-1,单位为毫秒,负数表示无限)
  max-wait: -1

上述列出的都为常用配置,读者可以通过注释信息了解每个配置项的具体作用。由于我们在pom.xml中已经引入了spring-boot-starter-data-redis库,所以Spring Boot会帮我们自动加载Redis的连接,具体的配置类
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration。通过该配置类,我们可以发现底层默认使用Jedis库,并且提供了开箱即用的redisTemplate和stringTemplate。

将Redis作为二级缓存

Mybatis的二级缓存原理本文不再赘述,读者只要知道,Mybatis的二级缓存可以自动地对数据库的查询做缓存,并且可以在更新数据时同时自动地更新缓存。

实现Mybatis的二级缓存很简单,只需要新建一个类实现org.apache.ibatis.cache.Cache接口即可。

该接口共有以下五个方法:

String getId():mybatis缓存操作对象的标识符。一个mapper对应一个mybatis的缓存操作对象。

void putObject(Object key, Object value):将查询结果塞入缓存。

Object getObject(Object key):从缓存中获取被缓存的查询结果。

Object removeObject(Object key):从缓存中删除对应的key、value。只有在回滚时触发。一般我们也可以不用实现,具体使用方式请参考:org.apache.ibatis.cache.decorators.TransactionalCache。

void clear():发生更新时,清除缓存。

int getSize():可选实现。返回缓存的数量。

ReadWriteLock getReadWriteLock():可选实现。用于实现原子性的缓存操作。

接下来,我们新建RedisCache类,实现Cache接口:

public class RedisCache implements Cache {
 private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 private final String id; // cache instance id
 private RedisTemplate redisTemplate;
 private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
 public RedisCache(String id) {
  if (id == null) {
   throw new IllegalArgumentException("Cache instances require an ID");
  }
  this.id = id;
 }
 @Override
 public String getId() {
  return id;
 }
 /**
  * Put query result to redis
  *
  * @param key
  * @param value
  */
 @Override
 @SuppressWarnings("unchecked")
 public void putObject(Object key, Object value) {
  RedisTemplate redisTemplate = getRedisTemplate();
  ValueOperations opsForValue = redisTemplate.opsForValue();
  opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
  logger.debug("Put query result to redis");
 }
 /**
  * Get cached query result from redis
  *
  * @param key
  * @return
  */
 @Override
 public Object getObject(Object key) {
  RedisTemplate redisTemplate = getRedisTemplate();
  ValueOperations opsForValue = redisTemplate.opsForValue();
  logger.debug("Get cached query result from redis");
  return opsForValue.get(key);
 }
 /**
  * Remove cached query result from redis
  *
  * @param key
  * @return
  */
 @Override
 @SuppressWarnings("unchecked")
 public Object removeObject(Object key) {
  RedisTemplate redisTemplate = getRedisTemplate();
  redisTemplate.delete(key);
  logger.debug("Remove cached query result from redis");
  return null;
 }
 /**
  * Clears this cache instance
  */
 @Override
 public void clear() {
  RedisTemplate redisTemplate = getRedisTemplate();
  redisTemplate.execute((RedisCallback) connection -> {
   connection.flushDb();
   return null;
  });
  logger.debug("Clear all the cached query result from redis");
 }
 @Override
 public int getSize() {
  return 0;
 }
 @Override
 public ReadWriteLock getReadWriteLock() {
  return readWriteLock;
 }
 private RedisTemplate getRedisTemplate() {
  if (redisTemplate == null) {
   redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
  }
  return redisTemplate;
 }
}

讲解一下上述代码中一些关键点:

自己实现的二级缓存,必须要有一个带id的构造函数,否则会报错。

我们使用Spring封装的redisTemplate来操作Redis。网上所有介绍redis做二级缓存的文章都是直接用jedis库,但是笔者认为这样不够Spring Style,而且,redisTemplate封装了底层的实现,未来如果我们不用jedis了,我们可以直接更换底层的库,而不用修改上层的代码。更方便的是,使用redisTemplate,我们不用关心redis连接的释放问题,否则新手很容易忘记释放连接而导致应用卡死。

需要注意的是,这里不能通过autowire的方式引用redisTemplate,因为RedisCache并不是Spring容器里的bean。所以我们需要手动地去调用容器的getBean方法来拿到这个bean,具体的实现方式请参考Github中的代码。

我们采用的redis序列化方式是默认的jdk序列化。所以数据库的查询对象(比如Product类)需要实现Serializable接口。
这样,我们就实现了一个优雅的、科学的并且具有Spring Style的Redis缓存类。

开启二级缓存

接下来,我们需要在ProductMapper.xml中开启二级缓存:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wooyoo.learning.dao.mapper.ProductMapper">
 <!-- 开启基于redis的二级缓存 -->
 <cache type="com.wooyoo.learning.util.RedisCache"/>
 <select id="select" resultType="Product">
  SELECT * FROM products WHERE id = #{id} LIMIT 1
 </select>
 <update id="update" parameterType="Product" flushCache="true">
  UPDATE products SET name = #{name}, price = #{price} WHERE id = #{id} LIMIT 1
 </update>
</mapper>

表示开启基于redis的二级缓存,并且在update语句中,我们设置flushCache为true,这样在更新product信息时,能够自动失效缓存(本质上调用的是clear方法)。

测试

配置H2内存数据库

至此我们已经完成了所有代码的开发,接下来我们需要书写单元测试代码来测试我们代码的质量。我们刚才开发的过程中采用的是mysql数据库,而一般我们在测试时经常采用的是内存数据库。这里我们使用H2作为我们测试场景中使用的数据库。

要使用H2也很简单,只需要跟使用mysql时配置一下即可。在application.yml文件中:

---
spring:
 profiles: test
 # 数据库配置
 datasource:
 url: jdbc:h2:mem:test
 username: root
 password: 123456
 driver-class-name: org.h2.Driver
 schema: classpath:schema.sql
 data: classpath:data.sql

为了避免和默认的配置冲突,我们用---另起一段,并且用profiles: test表明这是test环境下的配置。然后只要在我们的测试类中加上@ActiveProfiles(profiles = "test")注解来启用test环境下的配置,这样就能一键从mysql数据库切换到h2数据库。

在上述配置中,schema.sql用于存放我们的建表语句,data.sql用于存放insert的数据。这样当我们测试时,h2就会读取这两个文件,初始化我们所需要的表结构以及数据,然后在测试结束时销毁,不会对我们的mysql数据库产生任何影响。这就是内存数据库的好处。另外,别忘了在pom.xml中将h2的依赖的scope设置为test。

使用Spring Boot就是这么简单,无需修改任何代码,轻松完成数据库在不同环境下的切换。

编写测试代码

因为我们是通过Spring Initializer初始化的项目,所以已经有了一个测试类——SpringBootMybatisWithRedisApplicationTests。

Spring Boot提供了一些方便我们进行Web接口测试的工具类,比如TestRestTemplate。然后在配置文件中我们将log等级调成DEBUG,方便观察调试日志。具体的测试代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(profiles = "test")
public class SpringBootMybatisWithRedisApplicationTests {
 @LocalServerPort
 private int port;
 @Autowired
 private TestRestTemplate restTemplate;
 @Test
 public void test() {
  long productId = 1;
  Product product = restTemplate.getForObject("http://localhost:" + port + "/product/" + productId, Product.class);
  assertThat(product.getPrice()).isEqualTo(200);
  Product newProduct = new Product();
  long newPrice = new Random().nextLong();
  newProduct.setName("new name");
  newProduct.setPrice(newPrice);
  restTemplate.put("http://localhost:" + port + "/product/" + productId, newProduct);
  Product testProduct = restTemplate.getForObject("http://localhost:" + port + "/product/" + productId, Product.class);
  assertThat(testProduct.getPrice()).isEqualTo(newPrice);
 }
}

在上述测试代码中:

我们首先调用get接口,通过assert语句判断是否得到了预期的对象。此时该product对象会存入redis中。

然后我们调用put接口更新该product对象,此时redis缓存会失效。

最后我们再次调用get接口,判断是否获取到了新的product对象。如果获取到老的对象,说明缓存失效的代码执行失败,代码存在错误,反之则说明我们代码是OK的。

书写单元测试是一个良好的编程习惯。虽然会占用你一定的时间,但是当你日后需要做一些重构工作时,你就会感激过去写过单元测试的自己。

查看测试结果

我们在Intellij中点击执行测试用例,测试结果如下:

显示的是绿色,说明测试用例执行成功了。

本篇文章介绍了如何通过Spring Boot、Mybatis以及Redis快速搭建一个现代化的Web项目,并且同时介绍了如何在Spring Boot下优雅地书写单元测试来保证我们的代码质量。当然这个项目还存在一个问题,那就是mybatis的二级缓存只能通过flush整个DB来实现缓存失效,这个时候可能会把一些不需要失效的缓存也给失效了,所以具有一定的局限性。希望本文能帮助到大家。

相关推荐:

Tomcat部署Web项目该如何实现?

微信开发之Maven仓库管理及新建WEB项目的步骤详解

VS中新建网站和新建WEB项目的区别

以上がSpring Boot、Mybatis、Redis は最新の Web プロジェクトを迅速に構築しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
PHP対Python:違いを理解しますPHP対Python:違いを理解しますApr 11, 2025 am 12:15 AM

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHPは、シンプルな構文と高い実行効率を備えたWeb開発に適しています。 2。Pythonは、簡潔な構文とリッチライブラリを備えたデータサイエンスと機械学習に適しています。

PHP:それは死にかけていますか、それとも単に適応していますか?PHP:それは死にかけていますか、それとも単に適応していますか?Apr 11, 2025 am 12:13 AM

PHPは死にかけていませんが、常に適応して進化しています。 1)PHPは、1994年以来、新しいテクノロジーの傾向に適応するために複数のバージョンの反復を受けています。 2)現在、電子商取引、コンテンツ管理システム、その他の分野で広く使用されています。 3)PHP8は、パフォーマンスと近代化を改善するために、JITコンパイラおよびその他の機能を導入します。 4)Opcacheを使用してPSR-12標準に従って、パフォーマンスとコードの品質を最適化します。

PHPの未来:適応と革新PHPの未来:適応と革新Apr 11, 2025 am 12:01 AM

PHPの将来は、新しいテクノロジーの傾向に適応し、革新的な機能を導入することで達成されます。1)クラウドコンピューティング、コンテナ化、マイクロサービスアーキテクチャに適応し、DockerとKubernetesをサポートします。 2)パフォーマンスとデータ処理の効率を改善するために、JITコンパイラと列挙タイプを導入します。 3)パフォーマンスを継続的に最適化し、ベストプラクティスを促進します。

PHPの抽象クラスまたはインターフェイスに対して、いつ特性を使用しますか?PHPの抽象クラスまたはインターフェイスに対して、いつ特性を使用しますか?Apr 10, 2025 am 09:39 AM

PHPでは、特性は方法が必要な状況に適していますが、継承には適していません。 1)特性により、クラスの多重化方法が複数の継承の複雑さを回避できます。 2)特性を使用する場合、メソッドの競合に注意を払う必要があります。メソッドの競合は、代替およびキーワードとして解決できます。 3)パフォーマンスを最適化し、コードメンテナビリティを改善するために、特性の過剰使用を避け、その単一の責任を維持する必要があります。

依存関係噴射コンテナ(DIC)とは何ですか?また、なぜPHPで使用するのですか?依存関係噴射コンテナ(DIC)とは何ですか?また、なぜPHPで使用するのですか?Apr 10, 2025 am 09:38 AM

依存関係噴射コンテナ(DIC)は、PHPプロジェクトで使用するオブジェクト依存関係を管理および提供するツールです。 DICの主な利点には、次のものが含まれます。1。デカップリング、コンポーネントの独立したもの、およびコードの保守とテストが簡単です。 2。柔軟性、依存関係を交換または変更しやすい。 3.テスト可能性、単体テストのために模擬オブジェクトを注入するのに便利です。

通常のPHPアレイと比較して、SPL SPLFIXEDARRAYとそのパフォーマンス特性を説明してください。通常のPHPアレイと比較して、SPL SPLFIXEDARRAYとそのパフォーマンス特性を説明してください。Apr 10, 2025 am 09:37 AM

SplfixedArrayは、PHPの固定サイズの配列であり、高性能と低いメモリの使用が必要なシナリオに適しています。 1)動的調整によって引き起こされるオーバーヘッドを回避するために、作成時にサイズを指定する必要があります。 2)C言語アレイに基づいて、メモリと高速アクセス速度を直接動作させます。 3)大規模なデータ処理とメモリに敏感な環境に適していますが、サイズが固定されているため、注意して使用する必要があります。

PHPは、ファイルを安全に処理する方法をどのように処理しますか?PHPは、ファイルを安全に処理する方法をどのように処理しますか?Apr 10, 2025 am 09:37 AM

PHPは、$ \ _ファイル変数を介してファイルのアップロードを処理します。セキュリティを確保するための方法には次のものが含まれます。1。アップロードエラー、2。ファイルの種類とサイズを確認する、3。ファイル上書きを防ぐ、4。ファイルを永続的なストレージの場所に移動します。

Null Coulescingオペレーター(??)およびNull Coulescing Assignment Operator(?? =)とは何ですか?Null Coulescingオペレーター(??)およびNull Coulescing Assignment Operator(?? =)とは何ですか?Apr 10, 2025 am 09:33 AM

JavaScriptでは、nullcoalescingoperator(??)およびnullcoalescingsignmentoperator(?? =)を使用できます。 1.??最初の非潜水金または非未定されたオペランドを返します。 2.??これらの演算子は、コードロジックを簡素化し、読みやすさとパフォーマンスを向上させます。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

VSCode Windows 64 ビットのダウンロード

VSCode Windows 64 ビットのダウンロード

Microsoft によって発売された無料で強力な IDE エディター

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!