web-dev-qa-db-ja.com

文字列が列の長さの定義よりも長い場合、それらを格納するときに文字列をサイレントに切り捨てる方法は?

データの保存にEclipseLinkとMySQLを使用するWebアプリがあります。これらのデータの一部は文字列、つまりDB内のvarcharです。エンティティのコードでは、文字列には次のような属性があります。

@Column(name = "MODEL", nullable = true, length = 256)
private String model;

データベースはコードからのeclipseLinkによって作成されませんが、長さはDBのvarcharの長さと一致します。そのような文字列データの長さが長さ属性より大きい場合、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

その後、トランザクションはロールバックされます。これがデフォルトの動作であることは理解していますが、これは私が望む動作ではありません。データをサイレントに切り捨て、トランザクションをコミットしてください。

関係するエンティティの文字列データのすべてのsetメソッドにsubstringへの呼び出しを追加せずにこれを実行できますか?

33
Fabien

対応するフィールドのセッターの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/1667 に基づく)

コメントが https://stackoverflow.com/a/7648243/1667 に示唆されているように、データベースが変更された場合、注釈はデータベースから再作成できます。

18
Suma

さまざまな解決策と誤った解決策があります。

トリガーまたはデータベースレベルのトリックを使用

これにより、ORMのオブジェクトとDBのシリアル化されたフォームの間に不整合が生じます。 2次キャッシュを使用する場合:多くの問題が発生する可能性があります。私の意見では、これは一般的なユースケースの実際の解決策ではありません。

挿入前、更新前のフックを使用

永続化する直前にユーザーデータをサイレントに変更します。したがって、オブジェクトがすでに永続化されているかどうかによって、コードの動作が異なる場合があります。トラブルの原因にもなります。また、フックの呼び出し順序にも注意する必要があります。「field-truncator-hook」が永続性プロバイダーによって最初に呼び出されるものであることを確認してください。

aopを使用してセッターの呼び出しをインターセプト

このソリューションは、多かれ少なかれ静かにユーザー/自動入力を変更しますが、オブジェクトを使用してビジネスロジックを実行した後は、オブジェクトは変更されません。したがって、これは以前の2つのソリューションよりも許容範囲ですが、セッターは通常のセッターの契約に従いません。さらに、フィールドインジェクションを使用する場合:アスペクトをバイパスします(構成によっては、jpaプロバイダーがフィールドインジェクションを使用する場合があります。ほとんどの場合、Springはセッターを使用します。他の一部のフレームワークがフィールドインジェクションを使用する可能性があるため、明示的に使用しないでください。使用しているフレームワークの基本的な実装に注意してください)。

aopを使用してフィールドの変更をインターセプト

前のソリューションと同様ですが、フィールド注入もアスペクトによって阻止されます。 (このようなことをするアスペクトを書いたことは一度もありませんが、それは可能だと思います)

セッターを呼び出す前にフィールド長を確認するためにコントローラーレイヤーを追加する

データの整合性の観点から、おそらく最良のソリューションです。しかし、それは多くのリファクタリングを必要とするかもしれません。一般的な使用例では、これが(私の意見では)唯一の許容可能な解決策です。

ユースケースに応じて、これらのソリューションのいずれかを選択できます。欠点に注意してください。

11
ben75

それを行う別の方法があり、さらに高速かもしれません(少なくとも、MySqlの5番目のバージョンで動作します)。

まず、sql_modeの設定を確認します: 詳細な説明があります 。この設定には、Windowsの場合は ""、UNIXの場合は "modes"の値が必要です。

それでも役に立たなかったので、今回はjdbcで別の設定を見つけました。

jdbcCompliantTruncation=false. 

私の場合(私は永続性を使用しました)、それはpersistence.xmlで定義されています:

<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbname?jdbcCompliantTruncation=false"/>

これら2つの設定は一緒にしか機能しないため、個別に使用しようとしましたが効果がありませんでした。

通知:上記のようにsql_modeを設定すると、重要なデータベースチェックが無効になるので注意してください。

7
Choufler

これをグローバルではなくフィールドごとに行う場合は、値をテーブルに挿入する前に、指定された長さに値を切り捨てるカスタムタイプマッピングを作成できる場合があります。次に、次のようなアノテーションを使用してコンバータをエンティティにアタッチできます。

@Converter(name="myConverter", class="com.example.MyConverter")

と関連するフィールドに:

@Convert("myConverter")

これは、実際にはカスタムSQL型をサポートするためのものですが、通常のvarchar型フィールドでも機能する可能性があります。 ここ は、これらのコンバーターの1つを作成するためのチュートリアルです。

5
Eric Galluzzo

これには記述子のPreInsert/PreUpdateイベントを使用できますが、JPA PreInsertおよびPreUpdateイベントを使用することもできます。

フィールドのサイズを確認し、イベントコードで切り捨てるだけです。

必要に応じて、記述子のマッピングのDatabaseFieldからフィールドサイズを取得するか、Javaリフレクションを使用して注釈から取得することができます。

おそらく、setメソッドで切り捨てを行う方が良いでしょう。その後、イベントについて心配する必要はありません。

また、データベースで切り捨てたり、MySQL設定を確認したり、トリガーを使用したりできる場合もあります。

5
James

UIデザインの2つの非常に重要な機能:

  1. サイレントにユーザーデータを変更しないでください。ユーザーはそのような変更に気づき、制御し、同意する必要があります。
  2. 処理できないデータの入力を許可しないでください-UI/HTML属性と検証を使用して、データを正当な値に制限します

あなたの問題への答えは非常に簡単です。データベースのフィールド長に合わせて、UI入力フィールドを256文字に制限するだけです。

<input type="text" name="widgetDescription" maxlength="256">

これはシステムの制限です-ユーザーはこれ以上のデータを入力できません。これで不十分な場合は、データベースを変更してください。

2
Glen Best

たぶんAOPが役立つでしょう:

JavaBean/POJO内のすべてのsetメソッドをインターセプトしてから、設定するファイルを取得します。フィールドに@Columnの注釈が付けられており、フィールドタイプがStringであるかどうかを確認します。次に、フィールドがlengthより長すぎる場合は切り捨てます。

1
lichengwu

既に 回答済みのコンバーター がありましたが、詳細を追加したいと思います。私の答えは、EclipseLink固有ではなく、JPAからのコンバーターも想定しています。

最初にこのクラスを作成します-永続的な瞬間に値を切り捨てる責任がある特別な型コンバーター:

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

1
Aleksei Egorov

私はEclipseLinkについて何も知りませんが、Hibernateではそれが可能です-org.hibernate.Interceptorを作成し、onFlushDirtyメソッドでエンティティメタデータを使用してオブジェクトのcurrentStateを変更できます。

1
kan

ここで説明するように、非厳密モードで動作するようにデータベースを設定できます。 MySQLに送信された文字列の長さを自動的にトリミング

他の検証もキャンセルされる可能性があるので注意してください。

1
Doron Manor

例外はcommit()プロセス中にデータベースレベルで発生するように見えるため、ターゲットテーブルのbefore-insertトリガーが新しい値をコミットする前に切り捨て、エラーをバイパスする可能性があります。

1
Darrell Teague

別のオプションは、定数を宣言し、@ Columnアノテーション自体から始めて、その長さが必要なすべての場所で定数を参照することです。

この定数は、渡された文字列が長すぎる場合に予防例外をスローする切り捨て関数または検証関数に転送できます。この定数は、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;
    }
}
0
P. Cédric