Taogen's Blog

Stay hungry stay foolish.

Background

I call APIs of OSS (object storage services) to upload my local file with Java Input Stream.

Error Info

The uploaded file is 0 bytes.

Solutions

  • Check your accessed file exists and the file is not 0 byte.
  • Before the inputStream upload, using Java InputStream available() method to check out whether the remaining number of bytes that can be read from the file input stream is 0.
  • Make sure the remaining number of bytes that can be read from the file input stream is right.

Reasons

The reason of the size of my uploaded file is 0 byte is I read the input stream two times.

Most of input stream classes in Java API are not support read more than once. Because some Input Stream classes of Java API not support mark() and reset() method.

Although the FilterInputStream child classes can support read more than once by its mark(), reset() methods, and internal cache array byte buf[], you still need to call it manually. The reset() method is for repositions this stream to the position at the time the mark method was last called on this input stream.

Receive from Request URL Query String

Integer Array

Frontend Pass with HTTP request

  • url?ids=1,2,3
  • url?ids=1&ids=2&ids=3

Backend receive in controller methods

  • Integer[] ids
  • @RequestParam Integer[] ids
  • @RequestParam List<Integer> ids

String Array

Frontend Pass with HTTP request

  • url?ids=a,b,c
  • url?ids=a&ids=b&ids=c

Backend receive in controller methods

  • String[] ids
  • @RequestParam String[] ids
  • @RequestParam List<String> ids

Receive from Form Data

Integer Array

Frontend Pass with HTTP request

  • ids=1,2,3
  • ids=1, ids=2, ids=3

Backend receive in controller methods

  • Integer[] ids
  • @RequestParam Integer[] ids
  • @RequestParam List<Integer> ids

String Array

Frontend Pass with HTTP request

  • ids=a,b,c
  • ids=a, ids=b, ids=c

Backend receive in controller methods

  • String[] ids
  • @RequestParam String[] ids
  • @RequestParam List<String> ids

Receive from request body JSON

Integer Array

Frontend Pass with HTTP request

  • [1,2,3]

Backend receive in controller methods

  • @RequestBody Integer[] ids
  • @RequestBody List<Integer> ids

String Array

Frontend Pass with HTTP request

  • [“a”,”b”,”c”]

Backend receive in controller methods

  • @RequestBody String[] ids
  • @RequestBody List<String> ids

Download file streams with Axios

download.js

const axios = require('axios').default; // or import axios from 'axios'
const baseUrl = process.env.VUE_APP_BASE_API

/**
* Download file stream
*
* @param uri e.g. /web/article/exportExcel
* @param params e.g. {id: 1}
*/
export function download(uri, params) {
var url = baseUrl + uri
return axios.get(url, {
params:params,
responseType: 'blob',
}).then((response) => {
// response.data: file stream or error message defined by developers such as {"msg":"something went wrong...","code":500}
resolveBlob(response);
}).catch(error => {
// error.response.data: spring framework ResponseEntity object
alert("Fail to download file")
})
}

function resolveBlob(response) {
const headerval = response.headers['content-disposition'];
if (headerval != null) {
let filename = headerval.split(';')[1].split('=')[1].replace('"', '').replace('"', '');
filename = decodeURI(filename);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(url);
link.remove();
} else {
handleKnownException(response);
}
}

function handleKnownException(response) {
var reader = new FileReader();
reader.onload = function() {
if (reader.result != null) {
const responseData = JSON.parse(reader.result);
if (responseData.code == 500) {
alert(responseData.msg);
}
}
}
reader.readAsText(response.data);
}

Notice:

  • responseType: 'blob'
  • filename = decodeURI(filename)

article.vue

<template>
...
</template>

<script>
import { downLoadFile } from '@/utils/download';

export default {
...
methods: {
handleExportExcel() {
this.fullscreenLoading = true;
downLoadFile("/web/article/exportExcel", this.searchParams)
.then(() => {
this.fullscreenLoading = false;
});
}
}
}
</script>

Here are several ways to access parent context inside child context.

  1. Use ES6 Arrow functions.

An arrow function does not have its own bindings to this or super, this is inherited from the scope. In regular function, this is the function itself (it has its own scope).

getData() {
ajaxRequest(query).then(response => {
this.data = response.data;
});
}
  1. Store reference to context/this inside another variable, If you can’t use ES6 syntax.
getData() {
let self = this;
ajaxRequest(query).then(function(response) {
self.data = response.data;
});
}

References

[1] Arrow function expressions

[2] How to access the correct this inside a callback?

  1. Put files into /public directory

  2. Write your download link

Download /public/pdf/instruction.pdf

<a href="/pdf/instruction.pdf" download>download instruction</a>

Preview and download /public/pdf/instruction.pdf

<a href="/pdf/instruction.pdf" target="_blank">download instruction</a>

In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events…

Pass Data Between Parent and Child Components

props and $emit

  • Using props to share data from parent to child
  • Emitting custom events to share data from child to parent

parent.vue

<template>
<div style="padding: 10px 10px 10px 10px">
<h2>This is the parent page</h2>
<div>
<button @click="onSendClick">Send to Child</button>
</div>
<div>
Received from child by emit: {{receiveFromChild}}
</div>
<Child :name="childName" @receiveFromChild="onReceiveFromChild"/>
</div>
</template>

<script>
import Child from "./Child";
export default {
name: "Parent",
components: {
Child,
},
methods: {
onSendClick() {
this.childName = Math.random().toString(36).substring(7);
},
onReceiveFromChild(value) {
this.receiveFromChild = value;
}
},
data() {
return {
receiveFromChild: undefined,
childName: undefined
}
},
}
</script>

Child.vue

<template>
<div style="background-color: lightgray; padding: 10px 10px 10px 10px">
<h2>I'm the child page</h2>
<p>Receive from parent by props: {{name}}</p>
<button @click="onSendButtonClick">Send to parent</button>
</div>
</template>

<script>
export default {
name: "Child",
props: {
name: {
type: String,
default: "",
},
},
methods: {
onSendButtonClick() {
let sendValue = Math.random().toString(36).substring(7) + " " + new Date().toISOString().replace("T", " ").replace("Z", "");
this.$emit("receiveFromChild", sendValue);
}
},
}
</script>

Using Vuex to create an app-level shared state.

Call Methods between Parent and Child Components

Call child methods in Parent Components

Call child methods by ref of Vue 2

parent.vue

<div class="form">                          
<child-component ref="childComponentRef" />
</div>
this.$refs.childComponentRef.doSomething();

Call parent methods in Child Components

Call parent methods by props

parent.vue

<div class="form">                          
<child-component :doSomething="doSomething" />
</div>

child.vue

props: {
doSomething: {
type: Function
}
},

Summary

Update child components status

  • Using props to share data from parent to child. (control child status in parent)
  • Call child methods by ref. (control child status directly)

Update parent components status

  • Emitting custom events to share data from child to parent. (control parent status in child)
  • Call parent methods by props. (control parent status directly)

类型改变,如 Integer 改为 String 或 Integer 改为 Long。

if (xxx.equals(Object obj))

当成员变量的数据类型改变后,直接量与成员变量比较时,比较结果可能和之前的结果不同。如:

new Integer(1).equals(new Long(1)) // 返回 false
new Integer(1).equals("1") // 返回 false
new Long(1).equals(1) // 返回 false

发生错误的场景,如:

Integer type1 = 1;
if (type1.equals(type)) {
...
}

解决方法:与该成员变量的比较的值,数据类型要保持一致。

map.get(Object key)

若该变量作为 map 的 key 时,可能出现新的数据类型在 map 中找不到对应的 key。

HashMap put(key, value) 方法有指定数据类型的约束,而 get(Object key) 方法没有对应的数据类型约束。字段类型改变后,put 方法有错误提示,而 get 方法没有提示。在 map 对象中不同的数据类型的 key 是不同的 key,从而会出现改完数据类型后找不到对应的 key。

Map<Integer, String> map = new HashMap()

解决方法:put(key, value) 和 get(Object key) 的 key 的数据类型要保持一致。

方法的重载

若该变量作为方法的参数时,存在多个重载方法,改完类型后,可能调用的不是之前的方法。如存在重载方法 getById(Integer id) 和 getById(Serializable id)。

解决方法:检查该成员变量作为参数调用方法的地方,跳转的方法是不是之前的方法。

JSON Serialization

Custom Serializing Enumeration

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.taogen.demo.modules.task.enumeration.NameValueEnum;

import java.io.IOException;

/**
* Serializing Enums to JSON
*/
public class NameValueEnumSerializer extends StdSerializer<NameValueEnum> {

protected NameValueEnumSerializer() {
super(NameValueEnum.class);
}

protected NameValueEnumSerializer(Class t) {
super(t);
}

@Override
public void serialize(NameValueEnum nameValueEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("name");
jsonGenerator.writeString(nameValueEnum.getName());
jsonGenerator.writeFieldName("value");
jsonGenerator.writeString(nameValueEnum.getValue());
jsonGenerator.writeEndObject();
}
}
public interface NameValueEnum {

public String getName();

public void setName(String name);

public String getValue();

public void setValue(String value);
}
@JsonSerialize(using = NameValueEnumSerializer.class)
public enum TaskPriority implements NameValueEnum {
EMERGENCY("emergency", "紧急"),
HIGH("high", "高"),
GENERAL("general", "普通"),
LOW("low", "低")
;

private String name;

private String value;

@Override
@JsonValue
public String getName() {
return name;
}

@Override
public void setName(String name) {
this.name = name;
}

@Override
public String getValue() {
return value;
}

@Override
public void setValue(String value) {
this.value = value;
}
}
public class Task extends BaseEntity {
private TaskPriority priority;
}

JSON Deserialization

Deserializing Comma Separated String to List

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CommaSeparatedStrToListJsonDeserializer
extends JsonDeserializer<List<String>> {

@Override
public List<String> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
if ("{specifiedField}".equals(jsonParser.currentName())) {
String fieldStr = jsonParser.getValueAsString();
if (fieldStr != null && !fieldStr.trim().isEmpty()) {
return Arrays.stream(fieldStr.split(",")).collect(Collectors.toList());
}
}
return null;
}
}
@JsonDeserialize(using = CommaSeparatedStrToListJsonDeserializer.class)
private List<String> {specifiedField};

Deserializing JSON Array to String

public class JsonArrayToStringDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (jsonParser.currentToken() == JsonToken.START_ARRAY) {
List<String> list = new ArrayList<>();
jsonParser.nextToken();

while (jsonParser.hasCurrentToken() && jsonParser.currentToken() != JsonToken.END_ARRAY) {
list.add(jsonParser.getValueAsString());
jsonParser.nextToken();
}
return String.join(",", list);
}
return null;
}
}
@JsonDeserialize(using = JsonArrayToStringDeserializer.class)
private String {specifiedField};

References

[1] How To Serialize and Deserialize Enums with Jackson

Common Use

Character Encoding

useUnicode=true
characterEncoding=utf8

Data Processing

zeroDateTimeBehavior=convertToNull
tinyInt1isBit=false

Time Zone

serverTimezone=GMT%2B8

Connection

useSSL

For 8.0.12 and earlier: Use SSL when communicating with the server (true/false), default is ‘true’ when connecting to MySQL 5.5.45+, 5.6.26+ or 5.7.6+, otherwise default is ‘false’.

For 8.0.13 and later: Default is ‘true’. DEPRECATED. See sslMode property description for details.

  • Default Value: true
  • Since Version: 3.0.2

autoReconnect

Should the driver try to re-establish stale and/or dead connections

  • Default Value: false
  • Since Version: 1.1

maxReconnects

Maximum number of reconnects to attempt if autoReconnect is true, default is ‘3’.

useSSL=true
autoReconnect=true

Others

Timeout

initialTimeout

If autoReconnect is enabled, the initial time to wait between re-connect attempts (in seconds, defaults to ‘2’).

  • Default Value: 2
  • Since Version: 1.1

connectTimeout

Timeout for socket connect (in milliseconds), with 0 being no timeout. Only works on JDK-1.4 or newer. Defaults to ‘0’.

  • Default Value: 0
  • Since Version: 3.0.1

socketTimeout

Timeout (in milliseconds) on network socket operations (0, the default means no timeout).

  • Default Value: 0
  • Since Version: 3.0.1

References

[1] MySQL Connector/J 8.0 Configuration Properties

Background

Using MyBatis to query DATETIME type column data from MySQL table, if the column value is 0000-00-00 00:00:00, the program will throw exception Java.sql.SQLException. The following are the column properties.

pubtime DATETIME NULL DEFAULT NULL

Error Info

Error attempting to get column 'pubtime' from result set.  Cause: java.sql.SQLException: Zero date value prohibited
; Zero date value prohibited; nested exception is java.sql.SQLException: Zero date value prohibited
org.springframework.dao.TransientDataAccessResourceException: Error attempting to get column 'pubtime' from result set. Cause: java.sql.SQLException: Zero date value prohibited
; Zero date value prohibited; nested exception is java.sql.SQLException: Zero date value prohibited

Solutions

To set up zeroDateTimeBehavior=convertToNull in JdbcUrl. zeroDateTimeBehavior values can be EXCEPTION, ROUND, and CONVERT_TO_NULL. The default value of zeroDateTimeBehavior is EXCEPTION.

  1. Zero date will be converted to null
driver-url=jdbc:mysql://127.0.0.1/test?zeroDateTimeBehavior=convertToNull
  1. Zero date will be converted to 0001-01-01 00:00:00.0, equivalent to one year
driver-url=jdbc:mysql://127.0.0.1/test?zeroDateTimeBehavior=round

Reasons

When MySQL database is in the face of 0000-00-00 00:00:00 date processing, if not set corresponding countermeasures, it will produce an exception.

References

0%