当它们长于列长度定义时,如何在存储字符串时静默截断字符串?

Fab*_*ien 33 java jpa eclipselink

我有一个Web应用程序,使用EclipseLink和MySQL存储数据.其中一些数据是字符串,即DB中的varchars.在实体代码中,字符串具有如下属性:

@Column(name = "MODEL", nullable = true, length = 256)
private String model;
Run Code Online (Sandbox Code Playgroud)

eclipseLink不会从代码中创建数据库,但长度与DB中的varchar长度匹配.当这样的字符串数据的长度大于length属性时,在调用javax.persistence.EntityTransaction.commit()期间会引发异常:

javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.1.0.v20100614-r7608): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'MODEL' at row 1
Run Code Online (Sandbox Code Playgroud)

然后回滚事务.虽然我知道这是默认行为,但这不是我想要的.我希望数据被静默截断,并且要提交事务.

我是否可以在不向相关实体的每个字符串数据集方法添加子字符串调用的情况下执行此操作?

Sum*_*uma 18

可以根据相应字段的setter中的JPA注释截断字符串:

public void setX(String x) {
    try {
        int size = getClass().getDeclaredField("x").getAnnotation(Column.class).length();
        int inLength = x.length();
        if (inLength>size)
        {
            x = x.substring(0, size);
        }
    } catch (NoSuchFieldException ex) {
    } catch (SecurityException ex) {
    }
    this.x = x;
}
Run Code Online (Sandbox Code Playgroud)

注释本身应如下所示:

@Column(name = "x", length=100)
private String x;
Run Code Online (Sandbox Code Playgroud)

(基于/sf/answers/136283101/)

如果数据库发生更改,则可以从数据库重新创建注释,如/sf/answers/535377041/的注释中所示

  • 注意:我知道答案正是将原始海报作为可能的解决方案排除的原因("没有为每个set方法添加对子字符串的调用").尽管如此,我认为这样的解决方案也有好处,值得为其他人寻找如何解决这类问题的方法进行记录. (4认同)

ben*_*n75 11

您有不同的解决方案和错误解决方案

使用触发器或任何数据库级技巧

这将在ORM中的对象与其在DB中的序列化表单之间产生不一致.如果你使用二级缓存:它可能会导致很多麻烦.在我看来,这不是一般用例的真正解决方案.

使用预插入,预更新挂钩

您将在保留之前静默修改用户数据.因此,根据对象已经持久化的事实,您的代码可能会有不同的行为.它也可能引起麻烦.另外,你必须小心钩子调用的顺序:确保你的"field-truncator-hook"是持久性提供者调用的第一个.

使用aop来拦截对setter的调用

此解决方案或多或少会默默地修改用户/自动输入,但在使用它们执行某些业务逻辑后,您的对象将不会被修改.因此,这比之前的2个解决方案更容易接受,但是制定者不会遵循通常的制定者的合同.另外,如果你使用字段注入:它会绕过方面(取决于你的配置,jpa提供者可能会使用字段注入.大多数时候:Spring使用setter.我想其他一些框架可能会使用字段注入,所以即使你不喜欢明确地使用它会意识到你正在使用的框架的底层实现.

使用aop拦截字段修改

与之前的解决方案类似,不同之处在于场地注入也将被方面拦截.(请注意,我从未写过这样做的一个方面,但我认为这是可行的)

在调用setter之前添加控制器层以检查字段长度

可能是数据完整性方面的最佳解决方案.但它可能需要大量的重构.对于一般用例,这是(在我看来)唯一可接受的解决方案.

根据您的使用情况,您可以选择任何这些解决方案.注意缺点.


小智 7

还有另外一种方法,可能更快(至少它适用于MySql的第5版):

首先,检查你的sql_mode设置:有一个详细的描述如何做.对于Windows,此设置应具有值"",对于Unix,此设置应具有"模式".

这对我没有帮助,所以我找到了另一个设置,这次是在jdbc中:

jdbcCompliantTruncation=false. 
Run Code Online (Sandbox Code Playgroud)

在我的情况下(我使用持久性),它在persistence.xml中定义:

<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbname?jdbcCompliantTruncation=false"/>
Run Code Online (Sandbox Code Playgroud)

这两个设置只能一起工作,我试图单独使用它们并没有效果.

注意:请记住,通过如上所述设置sql_mode,您可以关闭重要的数据库检查,所以请仔细进行.

  • 因为绕过ORM,这似乎是一种危险的做事方式.假设您有一个二级缓存:它将包含非截断的字符串,而您的数据库将具有截断的值.我不会那样走! (4认同)

Jam*_*mes 5

您可以为此使用Descriptor PreInsert/PreUpdate事件,或者可能只使用JPA PreInsert和PreUpdate事件.

只需检查字段的大小并在事件代码中截断它们.

如果需要,您可以从描述符的映射的DatabaseField获取字段大小,或使用Java反射从注释中获取它.

在set方法中进行截断可能会更好.那你就不用担心事件了.

您也可以在数据库上截断它,检查MySQL设置,或者使用触发器.


Eri*_*zzo 5

如果要逐个字段而不是全局地执行此操作,则可以创建自定义类型映射,以便在将值插入表之前将值截断为给定长度.然后,您可以通过注释将转换器附加到实体,如:

@Converter(name="myConverter", class="com.example.MyConverter")
Run Code Online (Sandbox Code Playgroud)

以及相关领域:

@Convert("myConverter")
Run Code Online (Sandbox Code Playgroud)

这实际上是为了支持自定义SQL类型,但它也可以用于普通的varchar类型字段. 是一个关于制作其中一个转换器的教程.


Ale*_*rov 5

已经有答案提到 Converters,但我想添加更多细节。我的答案还假设转换器来自 JPA,而不是特定于 EclipseLink。

\n\n

首先创建此类 - 特殊类型转换器,其职责是在持久性时刻截断值:

\n\n
import javax.persistence.AttributeConverter;\nimport javax.persistence.Convert;\n\n@Convert\npublic class TruncatedStringConverter implements AttributeConverter<String, String> {\n  private static final int LIMIT = 999;\n\n  @Override\n  public String convertToDatabaseColumn(String attribute) {\n    if (attribute == null) {\n      return null;\n    } else if (attribute.length() > LIMIT) {\n      return attribute.substring(0, LIMIT);\n    } else {\n      return attribute;\n    }\n  }\n\n  @Override\n  public String convertToEntityAttribute(String dbData) {\n    return dbData;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后您可以在您的实体中使用它,如下所示:

\n\n
@Entity(name = "PersonTable")\npublic class MyEntity {\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\n    @Convert(converter = TruncatedStringConverter.class)\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0private String veryLongValueThatNeedToBeTruncated;\n\xc2\xa0\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0//...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

有关 JPA 转换器的相关文章:http://www.baeldung.com/jpa-attribute-converters

\n


kan*_*kan 1

我对 EclipseLink 一无所知,但在 Hibernate 中它是可行的 - 您可以创建一个 org.hibernate.Interceptor 并在 onFlushDirty 方法中使用实体元数据修改对象的 currentState 。