Java: Spring Boot自定义控制台日志输出的颜色

简述

偶然发现,在Spring Boot中,可以自定义输出日志的颜色(之前其实是有的,但只有日志级别那个地方有颜色).

image-2874

主要改动的就是,日志消息的颜色跟日志级别的颜色统一了.

但还是有几点要说明:

  1. 仅支持Logback日志系统
  2. 控制台输出格式的默认配置在: org.springframework.boot.logging.logback.DefaultLogbackConfiguration.java的CONSOLE_LOG_PATTERN属性;
  3. 在application.properties中配置logging.pattern.console即可覆盖默认配置
  4. 更多配置可以参考: Spring Boot官方文档

源码

java标志
image-2875

完整源码: Gitee仓库

源码很简单,启动类如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@SpringBootApplication
@RestController
public class SpringBootLoggingColorCustomDemoApplication {


    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @RequestMapping("/")
    public Map<String,String> hello(){
        log.trace("test trace");
        log.debug("test debug");
        log.warn("test warn");
        log.info("test info");
        log.error("test error");
        return Map.of("Hello","Java");
    }


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

}

application.properties配置如下:

spring.output.ansi.enabled=ALWAYS # 启用ANSI输出
logging.level.root=TRACE # 配置日志级别,输出会比较多.默认级别是INFO

# 配置输出格式.
logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(—){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){red} %clr(%m){%5p} %n

完整源码: Gitee仓库

Java: 动态更新Log4J2日志级别

Log4J的日志级别

下表示Log4J内置的日志级别.

标准级别数字级别
OFF0
FATAL100
ERROR200
WARN300
INFO400
DEBUG500
TRACE600
ALLInteger.MAX_VALUE

java标志
image-2871

使用Spring Boot

完整源码地址: Gitee仓库

例子中使用了Spring Boot,而在Spring Boot中,支持的日志级别为: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF .

在Spring Boot中,配置Log4J2很简单,只需要简单几步就可以完成.

当然,Spring Boot还支持其它日志系统,例如: Commons Logging(Spring Boot默认的日志系统),Logback,Log4J2,Java Util Logging.并且针对Logback,Log4J2,Java Util Logging提供了默认自动配置.

日志级别与颜色映射

在Spring Boot中,提供了日志级别与颜色映射,如下:

级别颜色
FATAL红色
ERROR红色
WARN黄色
INFO绿色
DEBUG绿色
TRACE绿色

源码

完整源码地址: Gitee仓库

如果要在Spring Boot中配置Log4J2,只需要在pom.xml中配置相关依赖即可:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.8</version>
</dependency>

这里需要排除spring-boot-starter-logging,因为默认的spring-boot-starter-logging和Log4J2会冲突.

在这里引入Lombok是因为可以自动生成Log4J2的实例,简化使用.

引入actuator是因为我们需要动态修改日志级别.

使用:

import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@SpringBootApplication
@RestController
@Log4j2
public class Log4jlevelupdatedemoApplication {


    @RequestMapping("/")
    public Map<String,String> hello(){
        log.trace("test trace");
        log.debug("test debug");
        log.warn("test warn");
        log.info("test info");
        log.error("test error");
        return Map.of("Hello","Java");
    }

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

}

需要注意: 每次重启服务器,日志级别都会被重置为默认级别.

操作步骤

1 . 浏览器访问: http://localhost:8080/ ,会在【控制台】输出测试日志:

2019-08-02 10:10:11.489 WARN 15340 — [nio-8080-exec-1] x.s.l.Log4jlevelupdatedemoApplication : test warn
2019-08-02 10:10:11.490 INFO 15340 — [nio-8080-exec-1] x.s.l.Log4jlevelupdatedemoApplication : test info
2019-08-02 10:10:11.490 ERROR 15340 — [nio-8080-exec-1] x.s.l.Log4jlevelupdatedemoApplication : test error

2 . 浏览器访问: http://localhost:8080/actuator/loggers ,可以看到目前的日志级别配置;

3 . 在Postman中新建一个POST请求,地址为: http://localhost:8080/actuator/loggers/ROOT , 参数为: Body -> raw -> JSON(application/json) ,内容为:

TRACE为日志级别,可选值为: TRACE, DEBUG, INFO, WARN, ERROR, FATAL,OFF.

{
“configuredLevel”: “TRACE”
}

请求之后,不会返回任何数据.再次发起[1]请求,之后即可在控制台看到修改后的数据.

实现原理

  1. 请求会抵达 [1]org.springframework.boot.actuate.logging.LoggersEndpoint 该类为loggers端点的处理类;
  2. 调用[1]类的configureLogLevel方法,在此方法中会调用当前日志系统的抽象父类(org.springframework.boot.logging.LoggingSystem),该类有多个子类,其中一个子类为:[2]org.springframework.boot.logging.log4j2.Log4J2LoggingSystem,就会取到该类;
  3. 调用[2]类中的setLogLevel方法,在此方法中,会将我们传递给Spring Boot的日志级别转换为Log4J2对应的日志级别;
  4. 如果配置对象为空的话,则会新建一个配置,反之则直接设置给当前配置;
  5. 最后调用org.apache.logging.log4j.core.LoggerContext的updateLoggers方法刷新配置.

完整源码地址: Gitee仓库

Java: 操作Redis

推荐使用Docker来搭建Redis环境.

连接的两种方式

会使用以下方式来操作Redis:

  1. Lettuce:Lettuce是基于Netty和Reactor的可伸缩线程安全的Redis客户端。Lettuce提供了同步,异步和反应的API来与Redis进行交互。
  2. Spring Data Redis:可以与Lettuce及其它Redis客户端整合.(暂未提供,待补充)

注意: 在Lettuce中,目前只提供了同步/异步的方式与Redis交互,暂无提供反应式方式与Redis交互的计划.

版本

  1. Redis: 4.*
  2. Lettuce:5.*
  3. Java:1.8 +(未在Java 1.9 中测试,不保证不会出现问题 – 2018/01/17)
  4. Maven:3.5

完整源码地址在: Gitee仓库

java标志
image-2863

单独使用Lettuce

请注意,该示例为一个Maven项目.

异步请求

异步使用的部分代码如下:

	/**
     * Redis事务使用异步执行
     * <p>
     * 方法会删除在本方法内添加的部分数据,具体看方法内部.
     */
    public static void asyncTransactionsSetAndGetData() throws ExecutionException, InterruptedException {
        // 设置Redis连接信息
        RedisURI redisURI = RedisURI.Builder.redis(AppConfig.REDIS_HOST, AppConfig.REDIS_PORT)
                .withPassword(AppConfig.REDIS_PASSWORD).withDatabase(AppConfig.REDIS_DATABASE).build();
        // 用连接信息,创建Redis客户端
        RedisClient redisClient = RedisClient.create(redisURI);

        // 获取异步执行命令(这一步很关键)
        RedisAsyncCommands<String, String> asyncCommands = redisClient.connect().async();

        // 开启事务
        RedisFuture<String> stringRedisFuture = asyncCommands.multi();
        System.out.println("开启事务的执行反馈:" + stringRedisFuture.get());

        RedisFuture<String> rf = asyncCommands.set("MUL-" + AppConfig.DEFAULT_KEY, AppConfig.DEFAULT_VALUE);
        int count = 20;
        for (int i = 0; i < count; i++) {
            asyncCommands.incr("MUL-COUNT");
            asyncCommands.set(i + "-MUL-" + AppConfig.DEFAULT_KEY, i + "," + AppConfig.DEFAULT_VALUE);
        }

        // 删除刚才添加的数据
        for (int i = 0; i < count; i++) {
            asyncCommands.del(i + "-MUL-" + AppConfig.DEFAULT_KEY);
        }

        // 执行事务
        RedisFuture<TransactionResult> result = asyncCommands.exec();

        System.out.println("事务执行结果:" + result.get());
        System.out.println("命令执行结果:" + rf.get());

        redisClient.shutdownAsync();
    }


    /**
     * 异步执行批量设置及获取数据
     * <p>
     * <p>
     * 未删除本方法内添加的数据.
     */
    public static void asyncBatchSetAndGetData() {

        // 设置Redis连接信息
        RedisURI redisURI = RedisURI.Builder.redis(AppConfig.REDIS_HOST, AppConfig.REDIS_PORT)
                .withPassword(AppConfig.REDIS_PASSWORD).withDatabase(AppConfig.REDIS_DATABASE).build();
        // 用连接信息,创建Redis客户端
        RedisClient redisClient = RedisClient.create(redisURI);

        // 获取异步执行命令(这一步很关键)
        RedisAsyncCommands<String, String> asyncCommands = redisClient.connect().async();
        int count = 10;
        List<RedisFuture<String>> futures = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            futures.add(asyncCommands.set(AppConfig.DEFAULT_KEY + "-" + i, AppConfig.DEFAULT_VALUE + i));
        }

        System.out.println(LettuceFutures.awaitAll(1, TimeUnit.MINUTES, futures.toArray(new RedisFuture[futures.size()])));

        List<RedisFuture<String>> lists = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            lists.add(asyncCommands.get(AppConfig.DEFAULT_KEY + "-" + i));
        }
        lists.stream().filter(x -> !"".equals(x.getError())).forEach(y -> {
            try {
                System.out.println(y.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

        redisClient.shutdownAsync();

    }

    /**
     * 异步设置与获取数据的基本用法
     * <p>
     * <p>
     * 方法内部会删除本方法添加的数据.
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void asyncSetAndGetData() throws ExecutionException, InterruptedException {
        // 设置Redis连接信息
        RedisURI redisURI = RedisURI.Builder.redis(AppConfig.REDIS_HOST, AppConfig.REDIS_PORT)
                .withPassword(AppConfig.REDIS_PASSWORD).withDatabase(AppConfig.REDIS_DATABASE).build();
        // 用连接信息,创建Redis客户端
        RedisClient redisClient = RedisClient.create(redisURI);

        // 获取异步执行命令(这一步很关键)
        RedisAsyncCommands<String, String> asyncCommands = redisClient.connect().async();

        // 设置数据
        Future<String> setResult = asyncCommands.set(AppConfig.DEFAULT_KEY, AppConfig.DEFAULT_VALUE);
        System.out.println(setResult.get());


        // 读取数据
        Future<String> getResult = asyncCommands.get(AppConfig.DEFAULT_KEY);
        System.out.println(getResult.get());

        // 删除数据,可以传递多个key.
        asyncCommands.del(AppConfig.DEFAULT_KEY);

        /*
         * 关闭此客户端并异步关闭所有打开的连接。
         * 调用此方法后客户端的操作被丢弃。
         * 关机时间为2秒,超时时间为15秒。
         */
        redisClient.shutdownAsync();
    }


    /**
     * 异步设置与获取数据的高级用法.
     * <p>
     * 方法内部会删除本方法添加的数据.
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void asyncAdvancedSetAndGetData() throws ExecutionException, InterruptedException {
        // 设置Redis连接信息
        RedisURI redisURI = RedisURI.Builder.redis(AppConfig.REDIS_HOST, AppConfig.REDIS_PORT)
                .withPassword(AppConfig.REDIS_PASSWORD).withDatabase(AppConfig.REDIS_DATABASE).build();
        // 用连接信息,创建Redis客户端
        RedisClient redisClient = RedisClient.create(redisURI);
        // 获取异步执行命令(这一步很关键)
        RedisAsyncCommands<String, String> asyncCommands = redisClient.connect().async();

        // 编解码器
        StringCodec codec = StringCodec.UTF8;
        // 状态消息输出
        StatusOutput sot = new StatusOutput<>(codec);

        // 添加命令
        RedisFuture<String> response = asyncCommands.dispatch
                (CommandType.SET, sot,
                        new CommandArgs<>(codec).addKey(AppConfig.DEFAULT_KEY)
                                .addValue(AppConfig.DEFAULT_VALUE));


        // 可以检查是否已经执行完成(执行完成不表示执行成功与否)
        if (response.isDone()) {
            // 会返回执行结果
            System.out.println(response.get());
        }

        // 获取刚才设置的结果
        RedisFuture<String> getInfo = asyncCommands.dispatch(CommandType.GET, sot,
                new CommandArgs<>(codec)
                        .addKey(AppConfig.DEFAULT_KEY));

        // 下面会显示上面设置的值:
        System.out.println(getInfo.get());

        // 删除键值
        System.out.println("删除结果(已删除的键数量):"+asyncCommands.dispatch(CommandType.DEL, new IntegerOutput<>(codec),
                new CommandArgs<>(codec)
                        .addKey(AppConfig.DEFAULT_KEY)).get());

        /*
         * 关闭这个客户端并关闭所有打开的连接。 调用关机后应该丢弃客户端。 关机没有安静的时间和2秒的超时。
         */
        redisClient.shutdown();
    }

image-2864

同步请求

同步请求处理的部分代码:

	/**
     * Redis事务使用同步执行
     * <p>
     * 方法会删除在本方法内添加的部分数据,具体看方法内部.
     */
    public static void syncTransactionsSetAndGetData() {
        // 设置Redis连接信息
        RedisURI redisURI = RedisURI.Builder.redis(AppConfig.REDIS_HOST, AppConfig.REDIS_PORT)
                .withPassword(AppConfig.REDIS_PASSWORD).withDatabase(AppConfig.REDIS_DATABASE).build();
        // 用连接信息,创建Redis客户端
        RedisClient redisClient = RedisClient.create(redisURI);

        // 获取同步执行命令(这一步很关键)
        RedisCommands<String, String> redisCommands = redisClient.connect().sync();

        // 开启事务
        String stringRedisFuture = redisCommands.multi();
        System.out.println("开启事务的执行反馈:" + stringRedisFuture);

        String rf = redisCommands.set("MUL-" + AppConfig.DEFAULT_KEY, AppConfig.DEFAULT_VALUE);
        int count = 20;
        for (int i = 0; i < count; i++) {
            redisCommands.incr("MUL-COUNT");
            redisCommands.set(i + "-MUL-" + AppConfig.DEFAULT_KEY, i + "," + AppConfig.DEFAULT_VALUE);
        }


        // 删除刚才添加的数据
        for (int i = 0; i < count; i++) {
            redisCommands.del(i + "-MUL-" + AppConfig.DEFAULT_KEY);
        }


        // 执行事务
        TransactionResult result = redisCommands.exec();

        System.out.println("事务执行结果:" + result.get(0));
        System.out.println("命令执行结果:" + rf);

        redisClient.shutdown();
    }


    /**
     * 同步设置与获取数据的基本用法
     * <p>
     * <p>
     * 方法内部会删除本方法添加的数据.
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void syncSetAndGetData(){
        // 设置Redis连接信息
        RedisURI redisURI = RedisURI.Builder.redis(AppConfig.REDIS_HOST, AppConfig.REDIS_PORT)
                .withPassword(AppConfig.REDIS_PASSWORD).withDatabase(AppConfig.REDIS_DATABASE).build();
        // 用连接信息,创建Redis客户端
        RedisClient redisClient = RedisClient.create(redisURI);

        // 获取同步执行命令(这一步很关键)
        RedisCommands<String, String> redisCommands = redisClient.connect().sync();

        // 设置数据
        String setResult = redisCommands.set(AppConfig.DEFAULT_KEY, AppConfig.DEFAULT_VALUE);
        System.out.println(setResult);


        // 读取数据
        String getResult = redisCommands.get(AppConfig.DEFAULT_KEY);
        System.out.println(getResult);

        // 删除数据,可以传递多个key.
        redisCommands.del(AppConfig.DEFAULT_KEY);

        /*
         * 关闭此客户端并同步关闭所有打开的连接。
         * 调用此方法后客户端的操作被丢弃。
         * 关机时间为2秒,超时时间为15秒。
         */
        redisClient.shutdown();
    }


    /**
     * 同步设置与获取数据的高级用法.
     * <p>
     * 方法内部会删除本方法添加的数据.
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void syncAdvancedSetAndGetData() {
        // 设置Redis连接信息
        RedisURI redisURI = RedisURI.Builder.redis(AppConfig.REDIS_HOST, AppConfig.REDIS_PORT)
                .withPassword(AppConfig.REDIS_PASSWORD).withDatabase(AppConfig.REDIS_DATABASE).build();
        // 用连接信息,创建Redis客户端
        RedisClient redisClient = RedisClient.create(redisURI);
        // 获取同步执行命令(这一步很关键)
        RedisCommands<String, String> redisCommands = redisClient.connect().sync();

        // 编解码器
        StringCodec codec = StringCodec.UTF8;
        // 状态消息输出
        StatusOutput sot = new StatusOutput<>(codec);

        // 添加命令

        Object response = redisCommands.dispatch
                (CommandType.SET, sot,
                        new CommandArgs<>(codec).addKey(AppConfig.DEFAULT_KEY)
                                .addValue(AppConfig.DEFAULT_VALUE));


        System.out.println(response);

        ///

        // 获取刚才设置的结果
        Object getInfo = redisCommands.dispatch(CommandType.GET, sot,
                new CommandArgs<>(codec)
                        .addKey(AppConfig.DEFAULT_KEY));

        // 下面会显示上面设置的值:
        System.out.println(getInfo);

        // 删除键值
        System.out.println("删除结果(已删除的键数量): " + redisCommands.dispatch(CommandType.DEL, new IntegerOutput(codec),
                new CommandArgs<>(codec)
                        .addKey(AppConfig.DEFAULT_KEY)).toString());

        /*
         * 关闭这个客户端并关闭所有打开的连接。 调用关机后应该丢弃客户端。 关机没有安静的时间和2秒的超时。
         */
        redisClient.shutdown();
    }

Spring Data Redis和Lettuce搭配使用

请注意,该示例为一个Spring Boot项目.

部分示例代码如下:

   private static final String DEFAULT_KEY = "SUANCAIYU.XYZ-SPRINGBOOT";


    @Autowired
    private StringRedisTemplate stringRedisTemplate;



    @RequestMapping("writeSync")
    private String writeSync(){
        int length=5000;
        for (int i = 0; i <length ; i++) {
            stringRedisTemplate.opsForValue().set(DEFAULT_KEY+i,i+DEFAULT_KEY+i);
        }
        System.out.println("abc");
        return "sync";
    }



    @RequestMapping("asyncRead")
    private String read() {
        return stringRedisTemplate.execute((RedisCallback<String>) redisConnection -> {
            System.out.println(redisConnection.setCommands().sMembers(DEFAULT_KEY.getBytes()).stream().map(x->new String(x)).collect(Collectors.joining(",")));
            return "success";
        });
    }

    @RequestMapping("asyncWrite")
    private String write() {
        stringRedisTemplate.execute((RedisCallback<String>) redisConnection -> {
            Long add = redisConnection.setCommands().sAdd(DEFAULT_KEY.getBytes(), "test123456".getBytes());
            if (add > 0) {
                return "success";
            }
            return "faild";
        });
        return "write";
    }

完整源码地址在: Gitee仓库

C# : 异步更新UI

源于

提示:该程序为C#的Winform程序,可在Windows上直接运行.

之前写了一个Winform程序,需要异步更新UI(例如,在Android中,是明确禁止同步更新UI的.).于是请教了一些网友,得出下面的程序.

其实原理都比较简单,就是把网络请求放到另外一个线程里面去执行.等网络请求线程执行完成之后,通过回调或其他方式执行UI线程更新.

image-2857

运行截图

程序运行截图如下,图示为已经加载完成数据的效果:

image-2858

源码

完整源码在: Gitee仓库

请求的网络地址为: http://www.weather.com.cn/data/sk/101270101.html,有时候网络会比较慢..

发送请求封装部分:

// allDone 属性包含 ManualResetEvent 类的实例,它指示请求完成。
        public ManualResetEvent allDone = new ManualResetEvent(false);

        // 它创建 WebRequest wreq 和 RequestState rs,调用 BeginGetResponse 开始处理请求,然后调用 allDone.WaitOne() 方法,
        // 以便应用程序不会在回调完成前退出。 在从 Internet 资源读取响应后,Main() 将该响应写入到控制台,应用程序结束。
        public void SendGet(string url,  HttpResultGet resultGet, FormInterface formTransInterface, WebHeaderCollection whcl = null)
        {
            try
            {
                WebRequest wreq = WebRequest.Create(url);
                if (whcl != null)
                {
                    ((HttpWebRequest)wreq).Headers = whcl;
                }
                // Create the state object.  
                ASyncRequestState rs = new ASyncRequestState
                {
                    // Put the request into the state object so it can be passed around.  
                    Request = wreq
                };

                // Issue the async request.  
                IAsyncResult r = wreq.BeginGetResponse(
                   new AsyncCallback(RespCallback), rs);

                // Wait until the ManualResetEvent is set so that the application   
                // does not exit until after the callback is called.  
                allDone.WaitOne();
                if (rs.RequestData.ToString().Length > 0)
                {
                    resultGet.ResultSet(rs.RequestData.ToString(), formTransInterface);
                }
            }
            catch (WebException)
            {
                resultGet.ResultSet("[请求错误]网络请求发生未知错误,请稍候重试!", formTransInterface);
            }
            catch (Exception)
            {
                resultGet.ResultSet("[请求错误]网络请求发生未知错误,请稍候重试!", formTransInterface);
            }

        }

异步回调封装部分:

// RespCallBack() 方法实现 Internet 请求的异步回调方法。 
        // 该方法创建包含来自 Internet 资源的响应的 WebResponse 实例,
        // 获取响应流,然后开始从该流异步读取数据。
        private void RespCallback(IAsyncResult ar)
        {
            // Get the ASyncRequestState object from the async result.  
            ASyncRequestState rs = (ASyncRequestState)ar.AsyncState;

            // Get the WebRequest from AsyncRequestState.  
            WebRequest req = rs.Request;

            // Call EndGetResponse, which produces the WebResponse object  
            //  that came from the request issued above.  
            WebResponse resp = req.EndGetResponse(ar);

            //  Start reading data from the response stream.  
            Stream ResponseStream = resp.GetResponseStream();

            // Store the response stream in AsyncRequestState to read   
            // the stream asynchronously.  
            rs.ResponseStream = ResponseStream;

            // Open the stream using a StreamReader for easy access.  
            StreamReader reader = new StreamReader(ResponseStream);
            // Read the content.  
            string responseFromServer = reader.ReadToEnd();

            rs.RequestData.Append(
                   responseFromServer);

            // Close down the response stream.  
            reader.Close();
            // Set the ManualResetEvent so the main thread can exit.  
            allDone.Set();
        }

代码调用:

        /**
         * 发送异步网络请求.
         *
         *
         */
        public void SendGet(string url, FormInterface formInterface)
        {

            // 随便显示的一个天气地址.
            AsyncHttpUtil asyncHttpUtil = new AsyncHttpUtil();
            asyncHttpUtil.SendGet(url, new SendHTTP(), formInterface);
        }

        // 异步回调
        void HttpResultGet.ResultSet(string result, FormInterface formInterface)
        {
            formInterface.SendResult(result);
        }

程序入口调用:

/**
         * 接收异步返回结果.
         * 
         */
        void FormInterface.SendResult(string text)
        {
            MessageBox.Show(text);
            this.targetText.Text = text;
        }
        /**
         * 模拟发送异步请求.
         *    
         */
        private void send_Click(object sender, EventArgs e)
        {
            SendHTTP send = new SendHTTP();
            send.SendGet("http://www.weather.com.cn/data/sk/101270101.html", this);
        }

完整源码在: Gitee仓库

Java: 写入100万数据到MySQL

数据量大的测试

忘了之前在哪遇到过这个问题,就是插入100万的数据到MySQL.

想了好几个办法,但发现还是用Java比较简单.

这里使用了Spring Data JdbcTemplate和Spring Boot,最低Java版本是Java 8.

解决方案1

第一个办法是直接用Spring Data JdbcTemplate的批量插入功能.完成100万数据插入到MySQL,大约需要5分钟.速度太慢了.

思路是:生成100万条随机数据,用batchUpdate批量插入进去.

代码如下:

public void run1(){
        System.out.println("开始.............................");
        beginTransaction();
        System.out.println(LocalDateTime.now().toString());
        int size=1000000;
        List<int[]> listArr = IntStream.rangeClosed(1,size).parallel().boxed().map(x->new int[]{x}).collect(Collectors.toList());
        System.out.println("data generator success.......................");
        jdbcTemplate.batchUpdate(" INSERT INTO `user_gender` VALUES (?,?,0) ",new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                int[] args = listArr.get(i);
                ps.setInt(1,args[0]);
                ps.setString(2, "student_"+args[0]);
            }

            @Override
            public int getBatchSize() {
                return size;
            }
        });
        endTransaction();
        System.out.println(LocalDateTime.now().toString());
}

java标志
image-2849

解决方案2

经历第一种慢之后,思考了一下.似乎有一种更好的方法:

方案2插入100万数据,只需要约40秒.没错,就是40秒!

该方案部分参考了: https://www.cnblogs.com/cxxjohnson/p/9155411.html

方案2的思路是:

一下生成100万条数据,然后使用insert into ‘user_gender’ VALUES 的方式,一起加入.但会遇到个问题,在插入数据的时候可能会遇到问题:

om.mysql.jdbc.PacketTooBigException: Packet for query is too large (4232009 > 4194304). You can change this value on the server by setting the max_allowed_packet’ variable.

出现上面的错误是因为数据库表的 max_allowed_packet这个配置没配置足够大,因为默认的为4M的,后来我调为100M就没报错了

set global max_allowed_packet = 100*1024*1024;

记住,设置好后重新登录数据库才能看的设置后的值

show VARIABLES like ‘%max_allowed_packet%’;

代码如下:

public void run2(){
        System.out.println("start.............................");
        beginTransaction();
        System.out.println(LocalDateTime.now().toString());
        int size=1000000;
        String listArr = IntStream.rangeClosed(1,size).parallel().boxed().map(x->" (\""+x+"\",\"student_"+x+"\",0) ").collect(Collectors.joining(","));
        StringBuilder sql = new StringBuilder();
        sql.append("  INSERT INTO `user_gender` VALUES  ");
        sql.append(listArr);
        System.out.println("data generator success.......................");
        System.out.println(LocalDateTime.now().toString());
        System.out.println("start insert.................................");
        jdbcTemplate.batchUpdate(sql.toString());
        endTransaction();
        System.out.println(LocalDateTime.now().toString());
}

这样的速度,基本也就达到目标了.

完整源码: Gitee仓库地址