BigDecimal返回前端数据超过16位导致精度丢失,比如6916750604278803063 变成 6916750604278803000

本文介绍了如何在Java中处理SQL查询返回的16位限制,通过两种方法:字符串化和JDBC ResultSet处理,确保大数据类型的正确展示。涉及单元格数据转换、不同类型数据的处理和GridData类的使用。

由于JS最多支持16位的数字型数据,所以必须对返回数据进行处理。

方法一:简单粗暴,强行转为字符串返回

方法二:在JDBC返回的resultset进行处理

package smartbix;

import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import smartbix.dataprocess.ClickHouseNullValue;
import smartbix.util.CommonErrorCode;
import smartbix.util.DBType;
import smartbix.util.DbUtil;
import smartbixlibs.org.apache.logging.log4j.LogManager;

/**
 * 工具类:GridData
 *
 * @since 2017-07-13
 */
public class GridDataUtil {
	private static BigDecimal MIN_INT = new BigDecimal(Integer.MIN_VALUE);
	private static BigDecimal MAX_INT = new BigDecimal(Integer.MAX_VALUE);
	private static BigDecimal MIN_LONG = new BigDecimal(Long.MIN_VALUE);
	private static BigDecimal MAX_LONG = new BigDecimal(Long.MAX_VALUE);

	/**
	 * 根据SQL获取GridData
	 * 
	 * @param conn
	 *            conn
	 * @param cellDataConvertor
	 *            单元格数据转换类
	 * @param sql
	 *            sql
	 * @param params
	 *            params
	 * @return GridData
	 */
	public static GridData executeSql(Connection conn, ICellDataConvertor cellDataConvertor, String sql, DBType dbType,
			Object... params) {
		GridData result = new GridData();
		PreparedStatement prep = null;
		ResultSet rs = null;
		try {
			LogManager.getLogger().debug(sql);
			prep = conn.prepareStatement(sql);
			rs = getResultSet(prep, params);
			result = getResultSet(rs, cellDataConvertor, Integer.MAX_VALUE, dbType);
		} catch (SQLException e) {
			throw SmartbiXException.create(CommonErrorCode.SQL_ERROR, e).setDetail(sql);
		} finally {
			DbUtil.closeDBObject(rs, prep, null);
		}
		return result;
	}

	/**
	 * 根据SQL获取IDataIterator
	 * 
	 * @param conn
	 *            conn
	 * @param cellDataConvertor
	 *            数据格式转换类
	 * @param sql
	 *            sql
	 * @param dbType
	 *            数据库类型
	 * @param params
	 *            params
	 * @return DataIterator
	 */
	public static DataIterator executeIterator(Connection conn, ICellDataConvertor cellDataConvertor, String sql,
			DBType dbType, Object... params) {
		try {
			ResultSet rs = null;
			LogManager.getLogger().debug(sql);
			if (dbType == DBType.PRESTO) {
				Statement stat = conn.createStatement();
				rs = stat.executeQuery(sql);
			} else {
				PreparedStatement prep = conn.prepareStatement(sql);
				rs = getResultSet(prep, params);
			}
			return getDataIterator(rs, cellDataConvertor, dbType);
		} catch (SQLException e) {
			throw SmartbiXException.create(CommonErrorCode.SQL_ERROR, e).setDetail(sql);
		}
	}

	/**
	 * 按ResultSet生成IDataIterator
	 * 
	 * @param rs
	 *            rs
	 * @param cellDataConvertor
	 *            单元格数据转换类
	 * @param dbType
	 *            dbType
	 * @return IDataIterator
	 */
	public static DataIterator getDataIterator(final ResultSet rs, final ICellDataConvertor cellDataConvertor,
			final DBType dbType) {
		ResultSetMetaData meta;
		int colCount;
		try {
			meta = rs == null ? null : rs.getMetaData();
			colCount = meta == null ? 0 : meta.getColumnCount();
		} catch (SQLException e) {
			throw SmartbiXException.create(CommonErrorCode.SQL_ERROR, e);
		}
		final int columnCount = colCount;
		//
		IDataIterator iterator = new IDataIterator() {
			private boolean closed;
			private long currentRow = 0L;

			@Override
			public long getMaxRow() {
				if (dbType == DBType.SYBASE) {
					return 10000L;
				} else {
					return -1L;
				}
			}

			@Override
			public void close() {
				if (closed || rs == null) {
					return;
				}
				try {
					Statement stat = rs.getStatement();
					Connection conn = stat.getConnection();
					DbUtil.closeDBObject(rs, stat, conn);
				} catch (SQLException e) {
					LogManager.getLogger().error(e.getMessage(), e);
				} finally {
					closed = true;
				}
			}

			@Override
			public boolean hasNext() {
				long maxRow = getMaxRow();
				if (maxRow != -1 && currentRow >= maxRow) {
					return false;
				}
				try {
					return rs == null ? false : rs.next();
				} catch (SQLException e) {
					close();
					return false;
				}
			}

			@Override
			public IRow next() {
				try {
					List<ICell> cells = new ArrayList<ICell>();
					for (int i = 1; i <= columnCount; i++) {
						CellData cell = getCell(rs, i, cellDataConvertor, dbType);
						cells.add(cell);
					}
					Row row = new Row();
					row.setCells(cells);
					currentRow++;
					return row;
				} catch (SQLException e) {
					close();
					return null;
				}
			}

		};
		//
		DataIterator result = new DataIterator();
		result.setIterator(iterator);
		result.setColumnLabels(getColumnLabels(meta));
		return result;
	}

	/**
	 * 获取SQL结果集中元数据的列头信息
	 * 
	 * @param meta
	 *            meta
	 * @return column labels
	 * @throws SQLException
	 *             SQLException
	 */
	public static List<String> getColumnLabels(ResultSetMetaData meta) {
		List<String> columnLabels = new ArrayList<String>();
		if (meta != null) {
			try {
				int columnCount = meta.getColumnCount();
				for (int i = 1; i <= columnCount; i++) {
					String columnLabel = meta.getColumnLabel(i);
					if (columnLabel == null || columnLabel.isEmpty()) {
						columnLabel = meta.getColumnName(i);
					}
					columnLabels.add(columnLabel);
				}
			} catch (SQLException e) {
				throw SmartbiXException.create(CommonErrorCode.SQL_ERROR, e);
			}
		}
		return columnLabels;
	}

	/**
	 * GridData转换为IDataIterator
	 * 
	 * @param gridData
	 *            gridData
	 * @return IDataIterator
	 */
	public static IDataIterator toDataIterator(final GridData gridData) {
		if (gridData == null || gridData.getData() == null) {
			return null;
		}
		return new IDataIterator() {
			Iterator<List<CellData>> datas = gridData.getData().iterator();
			private long currentRow = 0L;

			@Override
			public boolean hasNext() {
				long maxRow = getMaxRow();
				if (maxRow != -1 && currentRow >= maxRow) {
					return false;
				}
				return datas.hasNext();
			}

			@Override
			public long getMaxRow() {
				return -1L;
			}

			@Override
			public IRow next() {
				currentRow++;
				return new IRow() {
					private List<? extends ICell> cells;
					{
						cells = datas.next();
					}

					@Override
					public List<? extends ICell> getCells() {
						return cells;
					}
				};
			}

			@Override
			public void close() {
				//
			}
		};
	}

	/**
	 * IDataIterator转换为 GridData
	 * 
	 * @param dataIterator
	 *            dataIterator
	 * @return GridData
	 */
	public static GridData toGridData(IDataIterator dataIterator) {
		if (dataIterator == null) {
			return null;
		}
		GridData result = new GridData();
		List<List<CellData>> datas = new ArrayList<List<CellData>>();
		while (dataIterator.hasNext()) {
			List<CellData> cellDatas = new ArrayList<CellData>();
			IRow row = dataIterator.next();
			List<? extends ICell> cells = row.getCells();
			for (ICell cell : cells) {
				CellData cellData = new CellData();
				cellData.setType(cell.getType());
				cellData.setValue(cell.getValue());
				cellData.setDisplayValue(cell.getDisplayValue());
				cellDatas.add(cellData);
			}
			datas.add(cellDatas);
		}
		dataIterator.close();
		result.setData(datas);
		result.setTotalRowsCount(datas.size());
		return result;
	}

	/**
	 * 设置查询参数并返回ResultSet
	 * 
	 * @param prep
	 *            prep
	 * @param params
	 *            params
	 * @return ResultSet
	 * @throws SQLException
	 *             SQLException
	 */
	private static ResultSet getResultSet(PreparedStatement prep, Object... params) throws SQLException {
		long t1 = System.currentTimeMillis();
		for (int i = 0; i < params.length; i++) {
			Object param = params[i];
			if (param == null) {
				prep.setNull(i + 1, Types.VARCHAR);
			} else if (param instanceof String) {
				prep.setString(i + 1, param.toString());
			} else if (param instanceof Double) {
				prep.setDouble(i + 1, ((Double) param).doubleValue());
			} else if (param instanceof Integer) {
				prep.setInt(i + 1, ((Integer) param).intValue());
			} else if (param instanceof java.util.Date) {
				if (param instanceof Date) {
					prep.setDate(i + 1, (Date) param);
				} else if (param instanceof Time) {
					prep.setTime(i + 1, (Time) param);
				} else if (param instanceof Timestamp) {
					prep.setTimestamp(i + 1, (Timestamp) param);
				} else {
					prep.setTimestamp(i + 1, new Timestamp(((java.util.Date) param).getTime()));
				}
			}
		}
		ResultSet rs = prep.executeQuery();
		long t2 = System.currentTimeMillis();
		LogManager.getLogger().debug(String.format("[executeSql]: %d%s.", t2 - t1, "ms"));
		return rs;
	}

	/**
	 * 根据ResultSet获取 GridData
	 * 
	 * @param rs
	 *            rs
	 * @param cellDataConvertor
	 *            单元格数据转换类
	 * @return GridData
	 * @throws SQLException
	 *             SQLException
	 */
	private static GridData getResultSet(ResultSet rs, ICellDataConvertor cellDataConvertor, int maxRows, DBType dbType)
			throws SQLException {
		GridData gridData = new GridData();
		List<String> columnLabels = new ArrayList<String>();
		List<List<CellData>> data = new ArrayList<List<CellData>>();
		if (rs != null) {
			ResultSetMetaData meta = rs.getMetaData();
			int columnCount = meta.getColumnCount();
			columnLabels.addAll(getColumnLabels(meta));
			int rowCount = 0;
			while (rs.next() && ++rowCount <= maxRows) {
				List<CellData> row = new ArrayList<CellData>();
				for (int i = 1; i <= columnCount; i++) {
					CellData cell = getCell(rs, i, cellDataConvertor, dbType);
					row.add(cell);
				}
				data.add(row);
			}
		}
		gridData.setStringHeaders(columnLabels);
		gridData.setData(data);
		gridData.setTotalRowsCount(data.size());
		return gridData;
	}

	/**
	 * 获取单行ResultSet中指定列的数据
	 * 
	 * <pre>
	 * wasNull只有在rs.getXXX返回值的类型为原始类型时(比如:int、long、double等)才需要调用,
	 * 像其它非原始类型(比如:String等)可以直接判断是否等于null就不需要调用wasNull方法了,
	 * 否则oracle中对于非原始类型返回值为null时调用wasNull可能会抛错。
	 * </pre>
	 * 
	 * @param rs
	 *            rs
	 * @param columnIndex
	 *            the first column is 1, the second is 2, ...
	 * @param cellDataConvertor
	 *            数据格数据转换类
	 * @return CellData
	 */
	private static CellData getCell(ResultSet rs, int columnIndex, ICellDataConvertor cellDataConvertor, DBType dbType)
			throws SQLException {
		CellData cell = new CellData();
		if (rs != null) {
			ResultSetMetaData meta = rs.getMetaData();
			int columnType = meta.getColumnType(columnIndex);
			switch (columnType) {
				case java.sql.Types.BIT:
				case java.sql.Types.TINYINT:
				case java.sql.Types.SMALLINT:
				case java.sql.Types.INTEGER:
					if (columnType == java.sql.Types.BIT
							&& (dbType == DBType.GREENPLUM || dbType == DBType.POSTGRESQL)) {
						int b = rs.getBoolean(columnIndex) ? 1 : 0;
						cell.setIntValue(b);
					} else if (dbType == DBType.CLICK_HOUSE || dbType == DBType.SMARTBI_MPP) {
						// 抽数到ClickHouse时,null被置为默认值,所以查询时需要把默认值还原为null
						int val = rs.getInt(columnIndex);
						if (ClickHouseNullValue.NULL_INTEGER == val) {
							cell.setType(ValueType.INTEGER);
							cell.setNullValue();
						} else {
							cell.setIntValue(val);
						}
					} else {
						cell.setIntValue(rs.getInt(columnIndex));
					}
					if (rs.wasNull()) {
						cell.setNullValue();
					}
					break;
				case java.sql.Types.BIGINT:
					BigDecimal bd = rs.getBigDecimal(columnIndex);
					if (bd == null) {
						break;
					}
					// 抽数到ClickHouse时,null被置为默认值,所以查询时需要把默认值还原为null
					if (dbType == DBType.CLICK_HOUSE || dbType == DBType.SMARTBI_MPP) {
						if (ClickHouseNullValue.NULL_BITINT_OBJ.compareTo(bd) == 0) {
							cell.setType(ValueType.BIGINT);
							cell.setNullValue();
						} else {
							cell.setBigIntValue(bd.toBigInteger());
						}
					} else {
						cell.setBigIntValue(bd.toBigInteger());
					}
					if (rs.wasNull()) {
						cell.setNullValue();
					}
					break;
				case java.sql.Types.FLOAT:
				case java.sql.Types.DOUBLE:
				case java.sql.Types.REAL:
					// 抽数到ClickHouse时,null被置为默认值,所以查询时需要把默认值还原为null
					if (dbType == DBType.CLICK_HOUSE || dbType == DBType.SMARTBI_MPP) {
						double val = rs.getDouble(columnIndex);
						if (ClickHouseNullValue.NULL_DOUBLE == val) {
							cell.setType(ValueType.DOUBLE);
							cell.setNullValue();
						} else {
							cell.setDoubleValue(val);
						}
					} else {
						cell.setDoubleValue(rs.getDouble(columnIndex));
					}
					if (rs.wasNull()) {
						cell.setNullValue();
					}
					break;
				case java.sql.Types.NUMERIC:
				case java.sql.Types.DECIMAL:
					BigDecimal bd2 = rs.getBigDecimal(columnIndex);
					if (bd2 == null) {
						cell.setType(ValueType.DOUBLE);
						cell.setNullValue();
						break;
					}

					// 抽数到ClickHouse时,null被置为默认值,所以查询时需要把默认值还原为null
					if ((dbType == DBType.CLICK_HOUSE || dbType == DBType.SMARTBI_MPP)
							&& (ClickHouseNullValue.NULL_INTEGER_OBJ.compareTo(bd2) == 0
									|| ClickHouseNullValue.NULL_BITINT_OBJ.compareTo(bd2) == 0
									|| ClickHouseNullValue.NULL_DOUBLE_OBJ.compareTo(bd2) == 0)) {
						cell.setType(ValueType.DOUBLE);
						cell.setNullValue();
						break;
					}

					// 整数
					if (bd2.scale() == 0) {
						if (bd2.compareTo(MIN_INT) >= 0 && bd2.compareTo(MAX_INT) <= 0) {
							cell.setIntValue(bd2.intValue());
						} else if (bd2.compareTo(MIN_LONG) >= 0 && bd2.compareTo(MAX_LONG) <= 0) {
							cell.setLongValue(bd2.longValue());
						} else {
							cell.setBigIntValue(bd2.toBigInteger());
						}
						break;
					}

					// 浮点数
					cell.setBigDecimalValue(bd2);
					break;
				case java.sql.Types.CHAR:
				case java.sql.Types.VARCHAR:
				case java.sql.Types.LONGVARCHAR:
				case java.sql.Types.NCHAR:
				case java.sql.Types.NVARCHAR:
				case java.sql.Types.LONGNVARCHAR:
					String strValue = rs.getString(columnIndex);
					cell.setStringValue(strValue);
					break;
				case java.sql.Types.DATE:
					Date date = rs.getDate(columnIndex);
					// 抽数到ClickHouse时,null被置为默认值,所以查询时需要把默认值还原为null
					if (date != null && ClickHouseNullValue.NULL_DATE.compareTo(date) == 0) {
						cell.setType(ValueType.DATE);
						cell.setNullValue();
					} else {
						cell.setDateValue(ValueType.DATE, date);
					}
					break;
				case java.sql.Types.TIME:
					cell.setDateValue(ValueType.TIME, rs.getTime(columnIndex));
					break;
				case java.sql.Types.TIMESTAMP:
					Timestamp timestamp = rs.getTimestamp(columnIndex);
					// 抽数到ClickHouse时,null被置为默认值,所以查询时需要把默认值还原为null
					if (timestamp != null && ClickHouseNullValue.NULL_DATETIME.compareTo(timestamp) == 0) {
						cell.setType(ValueType.DATETIME);
						cell.setNullValue();
					} else {
						cell.setDateValue(ValueType.DATETIME, timestamp);
					}
					break;
				case java.sql.Types.CLOB:
				case java.sql.Types.NCLOB:
					String value = null;
					Reader reader = rs.getCharacterStream(columnIndex);
					if (reader != null) {
						StringBuilder sb = new StringBuilder(4096);
						try {
							char[] charbuf = new char[4096];
							for (int j = reader.read(charbuf); j > 0; j = reader.read(charbuf)) {
								sb.append(charbuf, 0, j);
							}
						} catch (IOException e) {
							throw new SQLException(e.getMessage());
						}
						value = sb.toString();
					} else {
						if (!rs.wasNull()) {
							value = "";
						}
					}
					cell.setStringValue(value);
					break;
				case java.sql.Types.LONGVARBINARY:
					String lsValue = rs.getString(columnIndex);
					cell.setType(ValueType.BINARY);
					cell.setValue(lsValue);
					break;
				case java.sql.Types.BLOB:
				case java.sql.Types.BINARY:
					String defaultValue = "<BINARY>";
					cell.setType(ValueType.BINARY);
					cell.setValue(defaultValue);
					break;
				default:
					cell.setType(ValueType.UNKNOWN);
					String svalue = rs.getString(columnIndex);
					cell.setValue(svalue);
					break;
			}
			cellDataConvertor.convert(cell, columnIndex);
		}
		return cell;
	}

	/**
	 * 将GridData转化为字符串(可供测试使用)
	 * 
	 * @param gridData
	 *            GridData
	 */
	protected static String toString(GridData gridData) {
		StringBuilder buff = new StringBuilder();
		if (gridData != null) {
			List<String> headers = gridData.getStringHeaders();
			if (headers != null && headers.size() > 0) {
				for (int i = 0; i < headers.size(); i++) {
					String header = headers.get(i);
					if (i > 0) {
						buff.append(", ");
					}
					buff.append(header);
				}
				buff.append("\n");
			}
			List<List<CellData>> datas = gridData.getData();
			if (datas != null) {
				for (List<CellData> rowData : datas) {
					if (rowData == null || rowData.size() == 0) {
						continue;
					}
					for (int i = 0; i < rowData.size(); i++) {
						CellData cell = rowData.get(i);
						if (i > 0) {
							buff.append(", ");
						}
						buff.append(cell.getStringValue());
					}
					buff.append("\n");
				}
			}
		}
		return buff.toString();
	}

}
package smartbix;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;

import smartbixlibs.com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * 单元格数据
 *
 * @since 2017-06-20
 */
public class CellData implements ICell, Serializable {
	private ValueType type;
	private String displayValue;
	private Object value;

	public void setType(ValueType type) {
		this.type = type;
	}

	public ValueType getType() {
		return type;
	}

	public void setValue(Object value) {
		this.value = value;
	}

	public Object getValue() {
		return value;
	}

	public void setDisplayValue(String displayValue) {
		this.displayValue = displayValue;
	}

	public String getDisplayValue() {
		return displayValue;
	}

	@JsonIgnore
	public byte[] getByteArrayValue() {
		return (byte[]) value;
	}

	public void setByteArrayValue(byte[] byteArrayValue) {
		this.value = byteArrayValue;
	}

	@JsonIgnore
	public Date getDateValue() {
		return (Date) value;
	}

	public void setDateValue(ValueType type, Date dateValue) {
		this.type = type;
		this.value = dateValue;
	}

	public void setDateValue(Date dateValue) {
		this.type = ValueType.DATETIME;
		this.value = dateValue;
	}

	@JsonIgnore
	public double getDoubleValue() {
		return ((Number) value).doubleValue();
	}

	public void setDoubleValue(double doubleValue) {
		type = ValueType.DOUBLE;
		this.value = doubleValue;
	}

	@JsonIgnore
	public BigDecimal getBigDecimalValue() {
		return (BigDecimal) value;
	}

	public void setBigDecimalValue(BigDecimal bigDecimal) {
		type = ValueType.BIGDECIMAL;
		this.value = bigDecimal;
	}

	@JsonIgnore
	public int getIntValue() {
		return ((Number) value).intValue();
	}

	public void setIntValue(int intValue) {
		type = ValueType.INTEGER;
		this.value = intValue;
	}

	@JsonIgnore
	public long getLongValue() {
		return ((Number) value).longValue();
	}

	public void setLongValue(long longValue) {
		type = ValueType.LONG;
		this.value = longValue;
	}

	@JsonIgnore
	public BigInteger getBigIntValue() {
		return ((BigInteger) value);
	}

	public void setBigIntValue(BigInteger longValue) {
		type = ValueType.BIGINT;
		this.value = longValue;
	}

	@JsonIgnore
	public String getStringValue() {
		return (String) value;
	}

	public void setStringValue(String stringValue) {
		type = ValueType.STRING;
		this.value = stringValue;
	}

	@JsonIgnore
	public boolean isNull() {
		return value == null;
	}

	public void setNullValue() {
		this.value = null;
	}

	@Override
	public int hashCode() {
		Object value = getValue();
		return value == null ? 0 : value.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof CellData)) {
			return false;
		}
		CellData tmp = (CellData) obj;
		if (this.type != tmp.type) {
			return false;
		}
		return value == null ? tmp.value == null : value.equals(tmp.value);
	}

}

<think>我们正在处理一个关于Jalor6框架中BigDecimal类型返回前端时被自动截断导致精度丢失的问题。 首先,我们需要理解问题:在Jalor6框架中,当我们将一个BigDecimal类型的数据返回前端时,框架可能会自动截断数字的数,导致精度丢失。 这通常是因为框架在序列化过程中对数字进行了处理,可能是为了减少数据传输量或者默认的数字格式设置。 解决思路: 1. 检查框架的序列化配置:Jalor6可能使用了Jackson或默认的JSON序列化工具(如Jackson、Fastjson等),这些工具在序列化BigDecimal时可能有默认的精度设置。 2. 自定义序列化器:我们可以为BigDecimal类型编写一个自定义的序列化器,确保在序列化时保留所有数。 3. 全局配置:修改框架中JSON序列化工具的全局配置,设置BigDecimal的序列化方式为不丢失精度。 具体步骤(以常见的Jackson为例,因为很多Java框架默认使用Jackson): 步骤1:确认Jalor6使用的JSON序列化库。如果是Jackson,我们可以通过配置ObjectMapper来解决。 步骤2:配置ObjectMapper,设置其对于BigDecimal的序列化方式。 假设Jalor6使用Jackson,我们可以这样配置: 在Spring Boot项目中,我们可以通过一个配置类来定制ObjectMapper。 示例代码: ```java import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import java.math.BigDecimal; @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); // 注册自定义序列化器 SimpleModule module = new SimpleModule(); module.addSerializer(BigDecimal.class, new BigDecimalSerializer()); objectMapper.registerModule(module); return objectMapper; } // 自定义BigDecimal序列化器 public static class BigDecimalSerializer extends JsonSerializer<BigDecimal> { @Override public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 直接将BigDecimal转换为字符串,避免科学计数法和精度丢失 gen.writeString(value.toPlainString()); } } } ``` 这样,当序列化BigDecimal时,会将其转换为字符串,前端接收到的就是字符串形式,不会丢失精度前端如果需要计算,可以再转换为高精度数字类型(如JavaScript中可以使用BigInt或者Decimal.js库)。 注意:如果Jalor6使用的是其他JSON库(如Fastjson),配置方式会有所不同。 另外,如果框架本身有提供配置选项,也可以尝试在配置文件中设置。例如,在application.properties中设置Jackson的全局配置: ``` spring.jackson.serialization.write-numbers-as-strings=true ``` 但是这样会将所有数字都写成字符串,可能会影响其他数字类型,不推荐。 推荐使用自定义序列化器,只针对BigDecimal类型进行处理。 如果上述方法不适用,或者Jalor6框架有自己的序列化机制,则需要查看框架文档或源码,找到扩展点。 由于Jalor6是一个国内框架,文档可能较少,如果遇到困难,可以尝试以下方法: - 在框架的配置中寻找关于JSON序列化的配置项 - 查看框架是否提供了自定义序列化的接口 - 在框架的Controller层,使用@ResponseBody注解的方法返回前,将BigDecimal转换为String(不推荐,因为需要大量修改代码) 最后,生成相关问题: §§相关问题§§ 1. 如何在Jalor6中配置Jackson的ObjectMapper? 2. 除了自定义序列化器,还有哪些方法可以保证BigDecimal精度丢失? 3. Jalor6框架默认使用哪种JSON序列化库? 4. 如果前端需要处理大数字,有哪些JavaScript库可以推荐?</think>### 解决 Jalor6 中 BigDecimal 返回前端精度丢失问题 在 Jalor6 框架中,BigDecimal 类型返回前端时被截断的根本原因是**序列化过程中的精度丢失**。以下是完整解决方案: --- #### 原因分析 1. **默认序列化行为** Java 框架(如 Spring Boot)默认使用 Jackson 库序列化对象为 JSON。Jackson 处理 BigDecimal 时: - 默认使用 `BigDecimal#toString()` 方法 - 但某些配置会启用科学计数法或截断尾随零[^1] - 示例:`123.456789012345` → `"123.456789"`(精度丢失) 2. **前端数字类型限制** JavaScript 的 `Number` 类型最大安全整数为 $2^{53}-1$(约 16 ),超过时自动截断[^2]。 --- #### 解决方案(三种方法) ##### ✅ 方法1:自定义序列化器(推荐) ```java // 步骤1:创建自定义序列化器 public class BigDecimalSerializer extends JsonSerializer<BigDecimal> { @Override public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 直接输出完整精度的字符串 gen.writeString(value.toPlainString()); } } // 步骤2:在实体类字段上应用注解 public class OrderDTO { @JsonSerialize(using = BigDecimalSerializer.class) private BigDecimal amount; } ``` **效果**: `{ "amount": "123.456789012345" }`(字符串形式保留完整精度) ##### ✅ 方法2:全局配置 Jackson(Spring Boot) ```java @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); // 禁用科学计数法 mapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); // 禁用数值类型自动检测(避免转为 double) mapper.disable(MapperFeature.USE_ANNOTATIONS); return mapper; } } ``` **配置说明**: - `WRITE_BIGDECIMAL_AS_PLAIN`:强制完整输出小数 - `USE_ANNOTATIONS`:防止 Jackson 自动转换类型 ##### ✅ 方法3:DTO 层转为字符串 ```java public class OrderVO { private String amount; // 改为字符串类型 // 在Service层转换 public static OrderVO fromEntity(Order order) { OrderVO vo = new OrderVO(); vo.setAmount(order.getAmount().toPlainString()); return vo; } } ``` **适用场景**: - 简单项目快速修复 - 需要兼容老版本框架 --- #### 前端处理建议 1. **接收字符串** ```javascript // axios 示例 axios.get('/api/order').then(res => { const amount = new BigNumber(res.data.amount); // 使用 big.js 库 }); ``` 2. **使用高精度计算库** - [big.js](https://github.com/MikeMcl/big.js/) - [decimal.js](https://github.com/MikeMcl/decimal.js/) --- #### 验证结果 | 方案 | 返回值示例 | 精度保持 | |------|------------|----------| | 默认 | `123.456` | ❌ 丢失 | | 方法1 | `"123.456789012345"` | ✅ | | 方法2 | `123.456789012345` | ✅ (纯数字) | | 方法3 | `"123.456789012345"` | ✅ | > ⚠️ 注意:方法2返回的纯数字可能被 JavaScript 截断,**推荐方法1(字符串形式)作为最佳实践**。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值