我有一个Web应用程序,使用EclipseLink和MySQL存储数据。 其中一些数据是字符串,即DB中的varchars。 在实体代码中,字符串具有如下属性:
@Column(name = "MODEL", nullable = true, length = 256)
private String model;
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
然后回滚事务。 虽然我知道这是默认行为,但这不是我想要的。 我希望数据被静默截断,并且要提交事务。
我是否可以在不向相关实体的每个字符串数据集方法添加子字符串调用的情况下执行此操作?
答案 0 :(得分:17)
可以根据相应字段的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;
}
注释本身应如下所示:
@Column(name = "x", length=100)
private String x;
(基于https://stackoverflow.com/a/1946901/16673)
如果数据库发生更改,可以从数据库重新创建注释,如https://stackoverflow.com/a/7648243/16673的注释中所示
答案 1 :(得分:10)
您有不同的解决方案和错误的解决方案。
使用触发器或任何数据库级技巧
这将在ORM中的对象与其在DB中的序列化表单之间产生不一致。如果你使用二级缓存:它可能会导致很多麻烦。在我看来,这不是一般用例的真正解决方案。
使用预插入,预更新挂钩
您将在保留之前静默修改用户数据。因此,根据对象已经持久化的事实,您的代码可能会有不同的行为。它也可能引起麻烦。另外,你必须小心钩子调用的顺序:确保你的“field-truncator-hook”是持久性提供者调用的第一个。
使用aop拦截对setter的调用
此解决方案或多或少会默默地修改用户/自动输入,但在使用它们执行某些业务逻辑后,您的对象将不会被修改。因此,这比之前的2个解决方案更容易接受,但是制定者不会遵循通常的制定者的合同。另外,如果你使用字段注入:它会绕过方面(取决于你的配置,jpa提供者可能会使用字段注入。大多数时候:Spring使用setter。我想其他一些框架可能会使用字段注入,所以即使你不喜欢明确地使用它会意识到你正在使用的框架的底层实现。
使用aop拦截字段修改
与之前的解决方案类似,不同之处在于场地注入也将被方面拦截。 (请注意,我从未写过这样做的一个方面,但我认为这是可行的)
在调用setter之前添加控制器层以检查字段长度
可能是数据完整性方面的最佳解决方案。但它可能需要大量的重构。对于一般用例,这是(在我看来)唯一可接受的解决方案。
根据您的使用情况,您可以选择其中任何一种解决方案。请注意这些缺点。
答案 2 :(得分:6)
还有另一种方法可以做到,可能更快(至少它适用于MySql的第5版):
首先,检查您的sql_mode设置:there is a detailed description how to do it。此设置对于Windows应具有值“”,对于Unix应具有“模式”。
这对我没有帮助,所以我找到了另一个设置,这次是在jdbc:
jdbcCompliantTruncation=false.
在我的情况下(我使用了持久性),它在persistence.xml中定义:
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbname?jdbcCompliantTruncation=false"/>
这两个设置只能一起使用,我试图单独使用它们并且没有效果。
注意:请记住,通过如上所述设置sql_mode,您可以关闭重要的数据库检查,所以请仔细检查。
答案 3 :(得分:5)
您可以为此使用Descriptor PreInsert / PreUpdate事件,或者可能只使用JPA PreInsert和PreUpdate事件。
只需检查字段的大小并在事件代码中截断它们。
如果需要,您可以从描述符的映射的DatabaseField获取字段大小,或使用Java反射从注释中获取字段大小。
在set方法中进行截断可能会更好。那你就不用担心事件了。
您也可以在数据库中截断它,检查MySQL设置,或者使用触发器。
答案 4 :(得分:5)
如果要逐个字段而不是全局地执行此操作,则可以创建自定义类型映射,以便在将值插入表之前将值截断为给定长度。然后,您可以通过以下注释将转换器附加到实体:
@Converter(name="myConverter", class="com.example.MyConverter")
通过以下相关领域:
@Convert("myConverter")
这实际上是为了支持自定义SQL类型,但它也适用于普通的varchar类型字段。 Here是制作其中一个转换器的教程。
答案 5 :(得分:2)
UI设计的两个非常重要的功能:
您的问题的答案非常简单。只需将UI输入字段限制为256个字符,即可匹配数据库字段长度:
<input type="text" name="widgetDescription" maxlength="256">
这是一个系统限制 - 用户不应该输入比这更多的数据。如果这不够,请更改数据库。
答案 6 :(得分:1)
我对EclipseLink一无所知,但在Hibernate中它是可行的 - 你可以创建一个org.hibernate.Interceptor,在onFlushDirty方法中使用实体元数据修改对象的currentState。
答案 7 :(得分:1)
您可以将数据库设置为非严格模式,如下所述: Automatically trimming length of string submitted to MySQL
请注意,它也可能取消其他验证,因此请注意
答案 8 :(得分:1)
由于在commit()过程中似乎在数据库级别引发了异常,因此目标表上的前插入触发器可能会在提交之前截断新值,从而绕过错误。
答案 9 :(得分:1)
也许AOP会有所帮助:
拦截JavaBean / POJO中的所有set方法,然后获取要设置的字段。检查该字段是否带有@Column
注释且字段类型为String
。如果该字段比length
太长,则截断该字段。
答案 10 :(得分:0)
另一种选择是声明一个常量,并在需要该长度的任何地方引用它,从@Column注释本身开始。
然后可以将此常量转发到truncate函数或验证函数,如果传递的字符串太长,则会抛出预防性异常。 此常量也可以在其他一些层(如UI)上重复使用。
例如:
@Entity
public class MyEntity {
public static final int NAME_LENGTH=32;
private Long id;
private String name;
@Id @GeneratedValue
public Long getId() {
return id;
}
protected void setId(Long id) {
this.id = id;
}
@Column(length=NAME_LENGTH)
public String getName() {
return name;
}
public void setName(String name) {
this.name = JpaUtil.truncate(name, NAME_LENGTH);
}
}
public class JpaUtil {
public static String truncate(String value, int length) {
return value != null && value.length() > length ? value.substring(0, length) : value;
}
}
答案 11 :(得分:0)
已经有answer mentioned Converters,但我想添加更多细节。我的回答还假设转换器来自JPA,而不是EclipseLink特定的。
首先创建这个类 - 特殊类型转换器,其职责是在持久性时刻截断值:
import javax.persistence.AttributeConverter;
import javax.persistence.Convert;
@Convert
public class TruncatedStringConverter implements AttributeConverter<String, String> {
private static final int LIMIT = 999;
@Override
public String convertToDatabaseColumn(String attribute) {
if (attribute == null) {
return null;
} else if (attribute.length() > LIMIT) {
return attribute.substring(0, LIMIT);
} else {
return attribute;
}
}
@Override
public String convertToEntityAttribute(String dbData) {
return dbData;
}
}
然后你可以在你的实体中使用它:
@Entity(name = "PersonTable")
public class MyEntity {
@Convert(converter = TruncatedStringConverter.class)
private String veryLongValueThatNeedToBeTruncated;
//...
}
有关JPA转换器的相关文章:http://www.baeldung.com/jpa-attribute-converters