package cn.lihu.jh.module.ecg.service.appointment;
|
|
import cn.lihu.jh.framework.common.util.object.BeanUtils;
|
import cn.lihu.jh.module.ecg.controller.admin.appointment.vo.AppointmentSaveReqVO;
|
import cn.lihu.jh.module.ecg.dal.dataobject.appointment.AppointmentDO;
|
import org.junit.Test;
|
|
import java.sql.Connection;
|
import java.sql.DriverManager;
|
import java.sql.PreparedStatement;
|
import java.sql.ResultSet;
|
import java.sql.Statement;
|
import java.text.SimpleDateFormat;
|
|
import static org.junit.Assert.*;
|
|
/**
|
* 锁定 {@code register_date}「时间被截断为 00:00:00」的成因与修复。
|
*
|
* <h3>现象</h3>
|
* 2026-09-27 起 {@code appointment.register_date} 大量变成 {@code yyyy-MM-dd 00:00:00},
|
* 而 09-26 之前是 {@code 2026-09-26 16:58:53} 这样的完整时间。
|
*
|
* <h3>成因(经实验确定)</h3>
|
* {@code java.sql.Date} 是**只表示日期**的 JDBC 类型;把它写入 {@code DATETIME} 列时,
|
* 时间部分必然变成 {@code 00:00:00}。实测(同连接串、同列):
|
* <pre>
|
* setObject(java.util.Date) → 2026-09-26 16:58:53 保留
|
* setObject(java.sql.Date) → 2026-09-26 00:00:00 丢弃
|
* </pre>
|
* 而 {@code AppointmentSaveReqVO}/{@code AppointmentRespVO}/{@code AppointmentPageReqVO}
|
* 原先都把 {@code registerDate}/{@code doctorTime} 声明为 {@code java.sql.Date}
|
* (经 {@code import java.sql.Date;}),对应的 {@code AppointmentDO} 却是
|
* {@code java.util.Date} —— 类型不一致导致往返过程中时间丢失。
|
*
|
* <h3>修复</h3>
|
* 三个 VO 统一改为 {@code java.util.Date}。本测试锁定「类型一致」这一约束。
|
*/
|
public class RegisterDateTruncationTest {
|
|
private static final String SAMPLE = "2026-09-26 16:58:53";
|
|
/** 生产/测试库连接串(与 application-local.yaml 一致) */
|
private static final String JDBC_URL =
|
"jdbc:mysql://47.93.127.38:3308/lihu?useSSL=false&serverTimezone=Asia/Shanghai"
|
+ "&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true";
|
|
/**
|
* 核心约束:VO 与 DO 的 registerDate/doctorTime 必须是**同一种** Date 类型,
|
* 且都不是 {@code java.sql.Date}(否则时间会被丢弃)。
|
*/
|
@Test
|
public void testRegisterDateTypes_shouldBeConsistentAndNotSqlDate() throws Exception {
|
Class<?> voReg = AppointmentSaveReqVO.class.getDeclaredField("registerDate").getType();
|
Class<?> voDoc = AppointmentSaveReqVO.class.getDeclaredField("doctorTime").getType();
|
Class<?> doReg = AppointmentDO.class.getDeclaredField("registerDate").getType();
|
Class<?> doDoc = AppointmentDO.class.getDeclaredField("doctorTime").getType();
|
|
System.out.println("[类型] VO.registerDate = " + voReg.getName());
|
System.out.println("[类型] VO.doctorTime = " + voDoc.getName());
|
System.out.println("[类型] DO.registerDate = " + doReg.getName());
|
System.out.println("[类型] DO.doctorTime = " + doDoc.getName());
|
|
assertNotEquals("VO.registerDate 不得为 java.sql.Date(会丢弃时分秒)",
|
"java.sql.Date", voReg.getName());
|
assertNotEquals("VO.doctorTime 不得为 java.sql.Date",
|
"java.sql.Date", voDoc.getName());
|
assertEquals("VO 与 DO 的 registerDate 类型必须一致",
|
doReg.getName(), voReg.getName());
|
assertEquals("VO 与 DO 的 doctorTime 类型必须一致",
|
doDoc.getName(), voDoc.getName());
|
}
|
|
/** 往返转换不得丢失时间 */
|
@Test
|
public void testDoToVoToDo_keepsTime() throws Exception {
|
AppointmentDO source = new AppointmentDO();
|
source.setApplyNo("TEST_TRUNC_1");
|
source.setRegisterDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(SAMPLE));
|
source.setDoctorTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(SAMPLE));
|
|
AppointmentSaveReqVO vo = BeanUtils.toBean(source, AppointmentSaveReqVO.class);
|
AppointmentDO back = BeanUtils.toBean(vo, AppointmentDO.class);
|
|
String reg = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(back.getRegisterDate());
|
String doc = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(back.getDoctorTime());
|
System.out.println("[往返] registerDate = " + reg + " doctorTime = " + doc);
|
|
assertEquals("registerDate 经 VO 往返后不应丢失时间", SAMPLE, reg);
|
assertEquals("doctorTime 经 VO 往返后不应丢失时间", SAMPLE, doc);
|
}
|
|
/**
|
* 复现实验(需要测试库可访问):证明「java.sql.Date → DATETIME 列」必然截断。
|
* <p>若数据库不可达则跳过(不影响其它断言的价值)。
|
*/
|
@Test
|
public void testJdbcWrite_sqlDateTruncates_utilDateKeeps() throws Exception {
|
Connection c = null;
|
try {
|
c = DriverManager.getConnection(JDBC_URL, "smartor", "Smartor.2016");
|
} catch (Exception e) {
|
System.out.println("[跳过] 数据库不可达: " + e.getMessage());
|
return;
|
}
|
try {
|
c.setAutoCommit(false);
|
try (Statement s = c.createStatement()) {
|
s.execute("DROP TABLE IF EXISTS _probe_regdate_test");
|
s.execute("CREATE TABLE _probe_regdate_test (id INT PRIMARY KEY AUTO_INCREMENT, "
|
+ "d_util DATETIME NULL, d_sql DATETIME NULL)");
|
}
|
java.util.Date utilDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(SAMPLE);
|
java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
|
try (PreparedStatement ps = c.prepareStatement(
|
"INSERT INTO _probe_regdate_test (d_util, d_sql) VALUES (?,?)")) {
|
ps.setObject(1, utilDate);
|
ps.setObject(2, sqlDate);
|
ps.executeUpdate();
|
}
|
try (Statement s = c.createStatement();
|
ResultSet r = s.executeQuery("SELECT d_util, d_sql FROM _probe_regdate_test")) {
|
assertTrue(r.next());
|
String util = r.getString(1);
|
String sql = r.getString(2);
|
System.out.println("[实验] java.util.Date → " + util);
|
System.out.println("[实验] java.sql.Date → " + sql);
|
|
assertEquals("java.util.Date 应保留时分秒", SAMPLE, util);
|
assertEquals("java.sql.Date 必然丢成 00:00:00",
|
"2026-09-26 00:00:00", sql);
|
}
|
try (Statement s = c.createStatement()) { s.execute("DROP TABLE _probe_regdate_test"); }
|
c.rollback();
|
} finally {
|
if (c != null) { try { c.close(); } catch (Exception ignore) { } }
|
}
|
}
|
}
|