翻译 自 http://www.baeldung.com/jedis-java-redis-client-library
Intro to Jedis – the Java Redis Client Library

介绍 关于jedis - java redis客户端库

1. Overview
This article is an introduction to Jedis, a client library in Java for Redis – the popular in-memory data structure
store that can persist on disk as well. It is driven by a keystore-based data structure to persist data and can be
used as a database, cache, message broker, etc.
First, we are going to explain in which kind of situations Jedis is useful and what it is about.
In the subsequent sections we are elaborating on the various data structures and explaining transactions, pipelining
and the publish/subscribe feature. We conclude with connection pooling and Redis Cluster.

1 概述
这是一遍介绍接jedis的文章,一个redis客户端库操作
redis,这是目前最受欢迎的内存数据存储结构同时也
可以存储在磁盘中。它的驱动是根据键值存储数据结构
而且在使用的时候就想数据库,缓存,信息代理等
首先我们将要描述多种关于jedis的使用情况
在后来的章节,我们尽力在多种数据结构上进行解释 传输,
管道,和发布订阅一些特色。我们包括连接池和redis 集群

2. Why Jedis?
Redis lists the most well-known client libraries on their official site. There are multiple alternatives to Jedis,
but only two more are currently worthy of their recommendation star, lettuce, and Redisson.
These two clients do have some unique features like thread safety, transparent reconnection handling and an asynchronous API,
all features of which Jedis lacks.
However, it is small and considerably faster than the other two. Besides, it is the client library of choice of the Spring
Framework developers and it has the biggest community of all three.

2 为什么使用jedis
jedis是目前最流行的redis客户端库在他们的官方主页上
这里有多种替代选择jedis,但是仅仅只有两个目前值得推荐使用
这两种分别是lettuce和Redissson
这两种客户端有独特之处例如线程安全,透明重连接处理和
一些异步的api,所有的这些都是jedis所缺少的

3. Maven Dependencies
Let’s start by declaring the only dependency we will need in the pom.xml:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
If you’re looking for the latest version of the library, check out this page.

3 使用Maven Dependencies
让我们开始声明一个独一无二的的dependency ,我们需要在pom.xml中
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
如果你想查找最先版的redis库,请点击这里

4. Redis Installation
You will need to install and fire up one of the latest versions of Redis. We are running the latest stable version at this
moment (3.2.1), but any post 3.x version should be okay.
Find here more information about Redis for Linux and Macintosh, they have very similar basic installation steps. Windows is
not officially supported but this port is well maintained.
Thereafter we can directly dive in and connect to it from our Java code:
Jedis jedis = new Jedis();
The default constructor will work just fine unless you have started the service on a non-default port or on a remote machine,
in which case you can configure it correctly by passing
the correct values as parameters into the constructor.

4 Redis 的安装
你应该需要安装和启动一个目前最新版的redis,
我们目前运行的是最先稳定版3.2.1在目前时间范围内。
但是任何发布的3.X版本都是可以的
点击这里获取更多关于redis Linux版和mac的信息。
他们都是一些非常相似基础的安装步骤。Windows不是官方
的支持,但是这个网站是可以的获取到安装windows版本的
在这之后我们直接使用客户端驱动直接连接通过我们的Java代码
Jedis jedis = new Jedis();
这是一个默认的构造方法将会正常启动,除非你将要开始一个服务
在一个非默认端口的在一个远程机器,在这种情况下,
您可以将正确的值作为参数传递给构造函数,
从而正确地配置它。

5. Redis Data Structures
Most of the native operation commands are supported and, conveniently enough, they normally share the same method name.

5 Redis 数据结构
绝大数本地操作命令是支持的,足够的遍历。通常情况先
它们都是共享相同的方法名

5.1. Strings
Strings are the most basic kind of Redis value, useful for when you need to persist simple key-value data types:
jedis.set("events/city/rome", "32,15,223,828");
String cachedResponse = jedis.get("events/city/rome");
The variable cachedResponse will hold the value 32,15,223,828. Coupled with expiration support, discussed later,
it can work as a lightning fast
and simple to use cache layer for HTTP requests received at your web application and other caching requirements.

5.1 Strings
stringshi最基本的类型在Redis的值中,常用于持久化单一的键值数据类型
jedis.set("events/city/rome", "32,15,223,828");
String cachedResponse = jedis.get("events/city/rome");
这个变量cachedResponse 将会获得这个值32,15,223,828
于此相关的还有过期支持机制我们稍后再谈论。它可以工作很轻量级很快很方便的
去使用缓存层为HTTP请求获取在你的网络应用层和其他的缓存需求

5.2. Lists
Redis Lists are simply lists of strings, sorted by insertion order and make it an ideal tool to implement, for instance,
message queues:

jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");

String task = jedis.rpop("queue#tasks");
The variable task will hold the value firstTask. Remember that you can serialize any object and persist it as a string,
so messages in the queue can carry more complex data when required.

5.2Lists
redis Lists 是一个简单字符串链表,根据插入的顺序使他更好的运行在一些方面
例如消息队列

jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");

String task = jedis.rpop("queue#tasks");
这个变量测试将会获取的fistTask的值。记住你可以序列化任何对象
和持久化这个对象作为一个字符串。所以信息在一个队列中
可以携带更复杂的数据当获取时

5.3. Sets

Redis Sets are an unordered collection of Strings that come in handy when you want to exclude repeated members:

jedis.sadd("nicknames", "nickname#1");
jedis.sadd("nicknames", "nickname#2");
jedis.sadd("nicknames", "nickname#1");

Set<String> nicknames = jedis.smembers("nicknames");
boolean exists = jedis.sismember("nicknames", "nickname#1");
The Java Set nicknames will have a size of 2, the second addition of nickname#1 was ignored. Also,
the exists variable will have a value of true, the method sismember
enables you to quickly check for the existence of a particular member.

5.3Sets
Redis Sets是一个无序的String类型集合,当你使用它你可以排除重复对象
jedis.sadd("nicknames", "nickname#1");
jedis.sadd("nicknames", "nickname#2");
jedis.sadd("nicknames", "nickname#1");

Set<String> nicknames = jedis.smembers("nicknames");
boolean exists = jedis.sismember("nicknames", "nickname#1");
这个java Set nicknames 的长度将会是2 在第二次加入
nickname#1时被忽略掉了,所以这个exists变量的值将会是true
这个方法sismember使你能够快速的检查某个变量会员是否存在

5.4. Hashes
Redis Hashes are mapping between String fields and String values:

jedis.hset("user#1", "name", "Peter");
jedis.hset("user#1", "job", "politician");
String name = jedis.hget("user#1", "name");

Map<String, String> fields = jedis.hgetAll("user#1");
String job = fields.get("job");

As you can see, hashes are a very convenient data type when you want to access object’s properties individually
since you do not need to retrieve the whole object.

5.4 Hashes
redis Hashes 是一个映射关系,在String键和String值类型
jedis.hset("user#1", "name", "Peter");
jedis.hset("user#1", "job", "politician");

String name = jedis.hget("user#1", "name");

Map<String, String> fields = jedis.hgetAll("user#1");
String job = fields.get("job");
正如你所看到一样,hashes 是一个 数据类型当它想通过一个对象的单独属性
而不需要遍历整个对象

5.5. Sorted Sets

Sorted Sets are like a Set where each member has an associated ranking, that is used for sorting them:

Map<String, Double> scores = new HashMap<>();

scores.put("PlayerOne", 3000.0);
scores.put("PlayerTwo", 1500.0);
scores.put("PlayerThree", 8200.0);

scores.keySet().forEach(player -> {
jedis.zadd("ranking", scores.get(player), player);
});

String player = jedis.zrevrange("ranking", 0, 1).iterator().next();
long rank = jedis.zrevrank("ranking", "PlayerOne");
The variable player will hold the value PlayerThree because we are retrieving the top 1 player and he
is the one with the highest score. The rank variable will have a value of
1 because PlayerOne is the second in the ranking and the ranking is zero-based.

5.5 Sorted Set
Sorted Sets是像Set一样但是他的每个元素hi向关联有序的。
通常在有序的时候使用它们
Map<String, Double> scores = new HashMap<>();

scores.put("PlayerOne", 3000.0);
scores.put("PlayerTwo", 1500.0);
scores.put("PlayerThree", 8200.0);

scores.keySet().forEach(player -> {
jedis.zadd("ranking", scores.get(player), player);
});

String player = jedis.zrevrange("ranking", 0, 1).iterator().next();
long rank = jedis.zrevrank("ranking", "PlayerOne");

变量player 将持有值player3,因为我们检索的是前1名player ,
他是得分最高的player 。rank变量的值为1,
因为PlayerOne是排名的第二名,排名为零。

6. Transactions
Transactions guarantee atomicity and thread safety operations, which means that requests
from other clients will never be handled concurrently during Redis transactions:

String friendsPrefix = "friends#";
String userOneId = "4352523";
String userTwoId = "5552321";

Transaction t = jedis.multi();
t.sadd(friendsPrefix + userOneId, userTwoId);
t.sadd(friendsPrefix + userTwoId, userOneId);
t.exec();
You can even make a transaction success dependent on a specific key by “watching” it right before you instantiate your Transaction:

jedis.watch("friends#deleted#" + userOneId);
If the value of that key changes before the transaction is executed, the transaction will not be completed successfully.

6 Transactions
事务保证了原子性和线程的安全操作。这意味着来自其他客户端的请求
在redis的事务里请求绝不会同时请求操作。
String friendsPrefix = "friends#";
String userOneId = "4352523";
String userTwoId = "5552321";

Transaction t = jedis.multi();
t.sadd(friendsPrefix + userOneId, userTwoId);
t.sadd(friendsPrefix + userTwoId, userOneId);
t.exec();

你也可以使事务成功取决于一个特定的键
通过“watching” 它在实例化你的事务之前
jedis.watch("friends#deleted#" + userOneId);
如果这个键改变在事务执行之前,那么那么这个事务将完全的失败

7. Pipelining

When we have to send multiple commands, we can pack them together in one request and save connection
overhead by using pipelines, it is essentially a network optimization. As long as the operations are mutually independent,
we can take advantage of this technique:

String userOneId = "4352523";
String userTwoId = "4849888";

Pipeline p = jedis.pipelined();
p.sadd("searched#" + userOneId, "paris");
p.zadd("ranking", 126, userOneId);
p.zadd("ranking", 325, userTwoId);
Response<Boolean> pipeExists = p.sismember("searched#" + userOneId, "paris");
Response<Set<String>> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();

String exists = pipeExists.get();
Set<String> ranking = pipeRanking.get();
Notice we do not get direct access to the command responses, instead we are given a Response
instance from which we can request the underlying response after the pipeline has been synced.

7 Pinelining(管道)
当我们需要去发送多个命令。我们可以将它们打包在一起通过一个request
节约了连接费用通过使用pipeline。这是对于网络优化是很有必要的
当这些操作相互独立,我们可以从中获取有利是,使用这个技术
String userOneId = "4352523";
String userTwoId = "4849888";

Pipeline p = jedis.pipelined();
p.sadd("searched#" + userOneId, "paris");
p.zadd("ranking", 126, userOneId);
p.zadd("ranking", 325, userTwoId);
Response<Boolean> pipeExists = p.sismember("searched#" + userOneId, "paris");
Response<Set<String>> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();

String exists = pipeExists.get();
Set<String> ranking = pipeRanking.get();
注意 我们无法直接访问命令响应 在管道被同步之后,
取而代之的是 我们得到了一个响应实例
我们可以请求底层响应。

8. Publish/Subscribe

We can use the Redis messaging broker functionality to send messages between the different
components of our system. Make sure the subscriber
and publisher threads do not share the same Jedis connection.
8. 发布/订阅

8.1. Subscriber
Subscribe and listen to messages sent to a channel:

Jedis jSubscriber = new Jedis();
jSubscriber.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// handle message
}
}, "channel");
Subscribe is a blocking method, you will need to unsubscribe from the JedisPubSub explicitly. We have overridden the
onMessage method but there are many more useful methods available to override.

8.1 Subscriber
订阅者收听信息来自于一个管道
Jedis jSubscriber = new Jedis();
jSubscriber.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// handle message
}
}, "channel");
订阅是一个阻塞方法,你要显示的取消订阅来自JedisPubSub的
我们已经重载了onMessage犯法,但是更多的方法需要去覆盖

8.2. Publisher

Then simply send messages to that same channel from the publisher’s thread:

Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");
8.2 Publisher
就是仅仅是发送一个信息在同一个管道中通过发布者线程
Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");

9. Connection Pooling
It is important to know that the way we have been dealing with our Jedis instance is naive.
In a real world scenario, you do not want to use a single instance in a multi-threaded environment
as a single instance is not thread safe.

Luckily enough we can easily create a pool of connections to Redis for us to reuse on demand,
a pool that is thread safe and reliable as long as you return the
resource to the pool when you are done with it.
Let’s create the JedisPool:

final JedisPoolConfig poolConfig = buildPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "localhost");

private JedisPoolConfig buildPoolConfig() {
final JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(128);
poolConfig.setMaxIdle(128);
poolConfig.setMinIdle(16);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis());
poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis());
poolConfig.setNumTestsPerEvictionRun(3);
poolConfig.setBlockWhenExhausted(true);
return poolConfig;
}
Since the pool instance is thread safe, you can store it somewhere statically but you should
take care of destroying the pool to avoid leaks when the application is being shutdown.

Now we can make use of our pool from anywhere in the application when needed:

try (Jedis jedis = jedisPool.getResource()) {
// do operations with jedis resource
}
We used the Java try-with-resources statement to avoid having to manually close the Jedis resource,
but if you cannot use this statement you can also close the resource manually in the finally clause.

Make sure you use a pool like we have described in your application if you do not want to face nasty
multi-threading issues. You can obviously play with the pool configuration
parameters to adapt it to the best setup in your system.

9 Connnectiong Pooling 连接池
我们都知道使用刚才的方法实例jedishis幼稚的。
在一个真实的方案中,你不想使用一个单一的实例在一个多线程环境下面
因为单一实例在多线程是线程不安全的

幸运的是我们能足够的简单创造一个连接池能使我们重用redis
线程池是线程安全的和足够信赖,只要你返回资源到池中当你运行完它

让我们开始创建一个JedisPool
final JedisPoolConfig poolConfig = buildPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "localhost");

private JedisPoolConfig buildPoolConfig() {
final JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(128);
poolConfig.setMaxIdle(128);
poolConfig.setMinIdle(16);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis());
poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis());
poolConfig.setNumTestsPerEvictionRun(3);
poolConfig.setBlockWhenExhausted(true);
return poolConfig;
}
当池实例化是线程安全的,你可以存储它在任何静态的地方。
但是你需要注意的是销毁一个连接池避免泄露,当一个程序关闭

现在我们可以确保我们的池,当应用程序在任何地方需要连接的时候
try (Jedis jedis = jedisPool.getResource()) {
// do operations with jedis resource
}

我们使用Java的try with resource 声明避免去手动的关闭jedis资源
但是当你不能使用这样声明的时候,你也可以关闭资源通过手动的方式在finally语句中

确保我们使用池像我们使用described 在我们的应用程序中当我们
不想去面对令人不愉快的多线程问题。你可以显示的配置
池配置文件,参数去适应你的最新的设置在你的系统中

10. Redis Cluster

This Redis implementation provides easy scalability and high availability, we encourage you to read
their official specification if you are not familiar with it. We will not cover Redis cluster setup since that is a bit out of the scope for
this article, but you should have no problems in doing so when you are done with its documentation.

Once we have that ready, we can start using it from our application:

try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) {
// use the jedisCluster resource as if it was a normal Jedis resource
} catch (IOException e) {}
We only need to provide the host and port details from one of our master instances, it will auto-discover
the rest of the instances in the cluster.

This is certainly a very powerful feature but it is not a silver bullet. When using Redis Cluster you cannot
perform transactions nor use pipelines, two important features on which many applications rely for ensuring data integrity.

Transactions are disabled because, in a clustered environment, keys will be persisted across multiple instances.
Operation atomicity and thread safety cannot be guaranteed for operations that involve command execution in different instances.

Some advanced key creation strategies will ensure that data that is interesting for you to be persisted in the same
instance will get persisted that way. In theory, that should enable you to perform transactions successfully using
one of the underlying Jedis instances of the Redis Cluster.

Unfortunately, currently you cannot find out in which Redis instance a particular key is saved using Jedis
(which is actually supported natively by Redis), so you do not know which of the instances you must perform
the transaction operation. If you are interested about this, you can find more information here.

10 Redis Cluster
这是一个实现提供一个简单可伸缩和高可用性。如果你对它不熟悉,我们鼓励去
去阅读这篇官方文章。我们不会说关于Redis cluster的配置因为这仅仅是那篇文章的很小一部分。
当你阅读完那篇文章,你应该不会出现什么问题在做这些事情的时候
一旦我们将下面的准备好,我们将开始使用它在我们的应用程序中
try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) {
// use the jedisCluster resource as if it was a normal Jedis resource
} catch (IOException e) {

}

我们需要提供一个ip和一个端口信息给我们一个主节点实例之一。
它会自己自动的发现剩余的实例在我们的集群中

这是一个很重要的功能,但是它不是一个银弹。当使用Redis Cluster
你不能使用事务,管道这两个在Redis中很重要的功能。这两个公共在其他许多的应用程序中
确保数据的一致性

事务是残废的,因为在集群中一个键值将会持久化在多个实例中
操作的原子性和线程的安全性不能保证操作在多个命令的执行在不同的实例机器上
在一些先进的key的创建策略将会确保数据是有效的
当你想持久化到同一个机器上或从同一个机器上获取持久化的内容。
理论上说能够使帮你用事务的成功通过使用底层jedis的实例关于Redis cluster的

翻译一篇关于jedis的文章的更多相关文章

  1. 翻译一篇SpiderMonkey GC的文章

    前言 这篇文章包含了对SpiderMonkey中Rooted<T>, Handle<T>的解释. 翻译自 https://developer.mozilla.org/en-US ...

  2. 翻译一篇文章:It's Difficult to Grow a Test Developer(成为测试开发工程师的艰辛)

    翻译一篇文章:It's Difficult to Grow a Test Developer(成为测试开发工程师的艰辛)   以下文章是送给来poptest学习测试开发工程师的学员们,很多人想测试工程 ...

  3. Expo大作战(三十一)--expo sdk api之Payments(expo中的支付),翻译这篇文章傻逼了,完全不符合国内用户,我只负责翻译大家可以略过!

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  4. 一篇RPO漏洞挖掘文章翻译加深理解。

    这是我第一次尝试翻译一篇漏洞挖掘文章,翻译它也是为了加深理解它.这是一篇很有意思的漏洞挖掘文章. 前几天在看fd的博客,偶然看到了这篇文章,虽然有点老了.但是思路真的牛皮.我决定花费时间和精力研究它们 ...

  5. (转)干货|这篇TensorFlow实例教程文章告诉你GANs为何引爆机器学习?(附源码)

    干货|这篇TensorFlow实例教程文章告诉你GANs为何引爆机器学习?(附源码) 该博客来源自:https://mp.weixin.qq.com/s?__biz=MzA4NzE1NzYyMw==& ...

  6. 《转载-两篇很好的文章整合》Android中自定义控件

    两篇很好的文章,有相互借鉴的地方,整合到一起收藏 分别转载自:http://blog.csdn.net/xu_fu/article/details/7829721 http://www.cnblogs ...

  7. 给B公司的一些建议(又一篇烂尾的文章)

    感慨:太多太多的悲伤故事,发生在自己身上,发生在自己的身边.因此,为了避免总是走"弯路",走"错误"的道路,最近一直在完善自己的理论模型. 烂尾说明:本文是一篇 ...

  8. 小鹏汽车技术中台实践 :微服务篇 InfoQ 今天 以下文章来源于InfoQ Pro

    小鹏汽车技术中台实践 :微服务篇 InfoQ  今天 以下文章来源于InfoQ Pro

  9. 翻译一篇英文文章,主要是给自己看的——在ASP.NET Core Web Api中如何刷新token

    原文地址 :https://www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/ 先申明,本人英语太菜,每次 ...

随机推荐

  1. hadoop系列四:mapreduce的使用(二)

    转载请在页首明显处注明作者与出处 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的一些内容,如hadoop,spark,storm,机器学习等. 当前使用的hadoop版本为2.6 ...

  2. sql unique约束详解

    UNIQUE 约束唯一标识数据库表中的每条记录. UNIQUE 和 PRIMARY KEY 约束均为列或列集合提供了唯一性的保证. PRIMARY KEY 拥有自动定义的 UNIQUE 约束. 请注意 ...

  3. 中间件学习之RMI+JDBC远端数据库的访问

    问题: RMI+JDBC远端数据库的访问.实现简单的成绩查询系统(创建表,录入成绩,查询成绩等).在服务器端,通过JDBC访问数据库.客户端调用服务端提供的各种数据库操作. 环境准备: (1).确保J ...

  4. Centos7安装nginx并设置为HTTP代理服务器(正向代理)

    # wget https://nginx.org/download/nginx-1.9.9.tar.gz # .tar.gz # cd nginx- # ./configure --prefix=/u ...

  5. [2015-11-10]分享一个调用msbuild生成解决方案并打包发布的批处理脚本

    最近工作成果之一,特此记录. 用于打包的批处理脚本 注意设置 path/to/your/solutionfile.sln 指向vs的解决方案文件. setlocal enabledelayedexpa ...

  6. bat调用带参数存储过程

    @bat调用sql文件 sqlplus user/pass@orcl @F:\factory.sql @将所有的存储过程封装在sql中 factory.sql:exec pro_factory(&am ...

  7. HTML5_input_file_打开很慢的问题

    最近项目中有上传附件的功能,只是在chrome浏览器上面测试,发现上传附件,打开选择框比较慢 原文链接:http://www.foreverpx.cn

  8. 谈谈.NET,Java,php

    开通博客后,一直都是转点别的朋友写的有意思的博文,今天我来写我在博客园的第一篇文章,说的不对的地方请你指正.希望本文能为一些准备学习编程的朋友有一点帮助. 开发桌面程序一直都是c语言,c++的天下,因 ...

  9. HTTP协议知多少-关于http1.x、http2、SPDY的相关知识

    作为网站开发的基础协议,我们知道浏览器上都有输出http这四个字母,这意味着什么呢? 这就是最基础的HTTP协议. 逐浪君今天为各位大人准备了一些HTTP技术的知识,来和大家分享. 以下图为例: 这一 ...

  10. C# 导出数据到Excel模板中(转)

    今天做报表的时候遇到了多表头的问题,而且相应的报表的格式都一样.所以就采用了报表模板的方式来进行. 第一步:在开发的当前项目中引入:Microsoft.Office.Interop.Excel:Sys ...