Open WangJincheng4869 opened 5 months ago
如何解决呢?
如何解决呢?
很抱歉,能力有限,研究一下午了,没有头绪,还在跟......
这种语法没法 if null
,用batch也不适合。
这种语法没法
if null
,用batch也不适合。
现在还不清楚问题是出在 mybatis 上还是,驱动上面。我在想在参数上添加 jdbcType 会不会解决这个问题。
我记得应该是可以在#{record.createTime}
里面指定类型,正在想办法测。
<bind name="listNotEmptyCheck"
value="@tk.mybatis.mapper.util.OGNL@notEmptyCollectionCheck(list, 'com.simperfect.bp.stat.dao.MdCologIbTestMapper.insertList 方法参数为空')" />
INSERT INTO md_colog_ib_test <trim prefix="(" suffix=")" suffixOverrides=",">id,create_time,release_time,</trim> VALUES
<foreach collection="list" item="record" separator=",">
<trim prefix="(" suffix=")" suffixOverrides=",">#{record.id},#{record.createTime},#{record.releaseTime},</trim>
</foreach>
在批量插入数据时,如果其中一个日期为null,可能会导致错误。这是因为SQL Server不允许将null插入到datetime2
类型的列中,除非该列已经被设置为允许null值。
解决这个问题的一种方法是在插入数据之前检查日期是否为null。如果日期为null,你可以选择跳过这条数据,或者将日期设置为一个默认值。
以下是如何在Java代码中实现这个逻辑的示例:
List<YourObject> objects = ...; // your list of objects to insert
objects = objects.stream()
.filter(obj -> obj.getCreateTime() != null && obj.getReleaseTime() != null)
.collect(Collectors.toList());
在这个示例中,我们使用Java 8的流(Stream)API来过滤掉那些createTime
或releaseTime
为null的对象。然后,你可以将过滤后的列表objects
用于批量插入。
如果你想为null的日期设置一个默认值,你可以使用Optional
类的orElse
方法:
for (YourObject obj : objects) {
LocalDateTime defaultTime = LocalDateTime.of(2000, 1, 1, 0, 0); // default date
obj.setCreateTime(Optional.ofNullable(obj.getCreateTime()).orElse(defaultTime));
obj.setReleaseTime(Optional.ofNullable(obj.getReleaseTime()).orElse(defaultTime));
}
在这个示例中,我们为createTime
和releaseTime
设置了一个默认的日期时间(2000年1月1日)。如果这些字段为null,它们将被设置为这个默认值。
设置默认值和排除null,都不太好,默认值肯定是不符合业务场景的。 我还有个想法就是,如果添加类型不行就拆分批量插入sql,手写个工具类,把为null的时间字段进行分类,然后再分别调用 insertList。当然,这样做非常恶心,因为不一定会有多少个时间字段,组合起来很恐怖.....
添加类型是可以的,但是不能存在主键字段,否则会报错不允许插入主键:当 IDENTITY_INSERT 设置为 OFF 时,不能为表 'md_colog_ib_test' 中的标识列插入显式值。
@Insert("<script>" +
"INSERT INTO md_colog_ib_test <trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">create_time,release_time,</trim> VALUES" +
"<foreach collection=\"list\" item=\"record\" separator=\",\">" +
"<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">#{record.createTime,jdbcType=TIMESTAMP},#{record.releaseTime,jdbcType=TIMESTAMP},</trim>" +
"</foreach>" +
"</script>")
void saveBatch(List<MdCologIbTest> list);
基于我之前自定义的 JakartaEntityResolve
完成强制适配,这个方案不适合全部人,也不适合提交 pr,因为它并不通用,简单粘贴一部分代码:
修改了 processField
中的部分代码,在 if (field.isAnnotationPresent(ColumnType.class))
添加 else 分支,强制为 date 设置默认值
if (field.isAnnotationPresent(ColumnType.class)) {
ColumnType columnType = field.getAnnotation(ColumnType.class);
// 是否为 blob 字段
entityColumn.setBlob(columnType.isBlob());
// column可以起到别名的作用
if (StringUtil.isEmpty(columnName) && StringUtil.isNotEmpty(columnType.column())) {
columnName = columnType.column();
}
if (columnType.jdbcType() != JdbcType.UNDEFINED) {
entityColumn.setJdbcType(columnType.jdbcType());
}
if (columnType.typeHandler() != UnknownTypeHandler.class) {
entityColumn.setTypeHandler(columnType.typeHandler());
}
} else {
// 如果是时间类型则设置类型,防止 SQL server 下报错 https://github.com/abel533/Mapper/issues/904
if (field.getJavaType() == Date.class || field.getJavaType() == java.sql.Date.class) {
entityColumn.setJdbcType(JdbcType.TIMESTAMP);
}
}
insertList
方法,强制不写入主键(仅适合我的场景)public String insertList(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//开始拼sql
StringBuilder sql = new StringBuilder();
sql.append("<bind name=\"listNotEmptyCheck\" value=\"@tk.mybatis.mapper.util.OGNL@notEmptyCollectionCheck(list, '").append(ms.getId()).append(" 方法参数为空')\"/>");
sql.append(SqlHelper.insertIntoTable(entityClass, tableName(entityClass), "list[0]"));
if (DataSourceUtils.isDm()) {
sql.append(DmSqlHelper.insertColumns(entityClass, true, false, false));
sql.append(" VALUES ");
sql.append("<foreach collection=\"list\" item=\"record\" separator=\",\" >");
sql.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
//获取全部列
Set<EntityColumn> columnList = EntityHelper.getColumns(entityClass);
//当某个列有主键策略时,不需要考虑他的属性是否为空,因为如果为空,一定会根据主键策略给他生成一个值
for (EntityColumn column : columnList) {
if (!column.isIdentity() && column.isInsertable()) {
sql.append(column.getColumnHolder("record")).append(",");
}
}
} else {
sql.append(SqlHelper.insertColumns(entityClass, true, false, false));
sql.append(" VALUES ");
sql.append("<foreach collection=\"list\" item=\"record\" separator=\",\" >");
sql.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
//获取全部列
Set<EntityColumn> columnList = EntityHelper.getColumns(entityClass);
//当某个列有主键策略时,不需要考虑他的属性是否为空,因为如果为空,一定会根据主键策略给他生成一个值
for (EntityColumn column : columnList) {
if (!column.isId() && column.isInsertable()) {
sql.append(column.getColumnHolder("record")).append(",");
}
}
}
sql.append("</trim>");
sql.append("</foreach>");
// 反射把MappedStatement中的设置主键名
EntityHelper.setKeyProperties(EntityHelper.getPKColumns(entityClass), ms);
return sql.toString();
}
关于“SQLServerException: 不允许从数据类型 varbinary 到 datetime2 的隐式转换”异常的解决办法,可以在实体类属性上添加字段注解@ColumnType(jdbcType = JdbcType.TIMESTAMP)
或者@ColumnType(jdbcType = JdbcType.DATE)
,也可以在实体类的XML映射文件上指明jdbcType类似这样<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
,能解决sqlserver数据库表中多个时间类型字段值为null在插入或更新报错的情况。
在 SQL server 2012 数据库中 insertList 方法会报错
不允许从数据类型 varbinary 到 datetime2 的隐式转换。请使用 CONVERT 函数来运行此查询。
触发条件
当多条数据中同时存在 datetime 类型,且其中有一个为 null 时就会触发。
以下情况不报错
以下情况报错
依赖环境
JDK 17、spring boot 3.0.9、mapper 4.2.3、mybatis 3.5.13、mssql-jdbc 11.2.3.jre17
堆栈信息