8

私は、Spring と Hibernate を使用した JSF プロジェクトに取り組んでいます。これConverterには、特に同じパターンに従う多数の s があります。

  • getAsObjectオブジェクト ID の文字列表現を受け取り、それを数値に変換し、指定された種類と指定された ID のエンティティを取得します

  • getAsStringエンティティを受け取り、変換されたオブジェクトの ID を返しますString

コードは基本的に次のとおりです (チェックは省略されています)。

@ManagedBean(name="myConverter")
@SessionScoped
public class MyConverter implements Converter {
    private MyService myService;

    /* ... */
    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) {
        int id = Integer.parseInt(value);
        return myService.getById(id);
    }

    @Override
    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) {
        return ((MyEntity)value).getId().toString();
    }
}

これとまったく同じ s が多数あることを考えるとConverter(当然のことながらの型を除いて)、単一の汎用コンバーターを使用する価値があるかどうか疑問に思っていました。ジェネリック自体の実装は難しくありませんが、Bean を宣言するための正しいアプローチについてはわかりません。MyServiceMyEntity

考えられる解決策は次のとおりです。

MyGenericConverter1 - Bean アノテーションなしで汎用実装を作成します。

2 - 特定のコンバーター広告をサブクラスMyGenericConverter<T>として記述し、必要に応じて注釈を付けます。

@ManagedBean(name="myFooConverter")
@SessionScoped
public class MyFooConverter implements MyGenericConverter<Foo> {
    /* ... */
}

これを書いているときに、ジェネリックは実際には必要ないかもしれないことに気付きました。そのため、2 つのメソッドを実装した基本クラスを単純に記述し、必要に応じてサブクラス化することができるかもしれません。

世話をしなければならない重要な詳細がいくつかあります(MyService何らかの方法でクラスを抽象化する必要があるという事実など)ので、私の最初の質問は次のとおりです。

もしそうなら、他のアプローチはありますか?

4

3 に答える 3

17

最も簡単なのは、次のようにすべての JPA エンティティを基本エンティティから拡張できるようにすることです。

public abstract class BaseEntity<T extends Number> implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract T getId();

    public abstract void setId(T id);

    @Override
    public int hashCode() {
        return (getId() != null) 
            ? (getClass().getSimpleName().hashCode() + getId().hashCode())
            : super.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        return (other != null && getId() != null
                && other.getClass().isAssignableFrom(getClass()) 
                && getClass().isAssignableFrom(other.getClass())) 
            ? getId().equals(((BaseEntity<?>) other).getId())
            : (other == this);
    }

    @Override
    public String toString() {
        return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
    }

}

equals()適切な(and hashCode())を持つことが重要であることに注意してください。そうしないと、 Validation Error: Value is not valid が発生します。テストは、Hibernate 固有のヘルパー メソッドClass#isAssignableFrom()にフォールバックする必要なく、Hibernate ベースのプロキシなどで失敗するテストを回避するためのものです。Hibernate#getClass(Object)

そして、このような基本サービスを用意します (はい、Spring を使用しているという事実は無視しています。基本的なアイデアを提供するためだけです)。

@Stateless
public class BaseService {

    @PersistenceContext
    private EntityManager em;

    public BaseEntity<? extends Number> find(Class<BaseEntity<? extends Number>> type, Number id) {
        return em.find(type, id);
    }

}

そして、次のようにコンバーターを実装します。

@ManagedBean
@ApplicationScoped
@SuppressWarnings({ "rawtypes", "unchecked" }) // We don't care about BaseEntity's actual type here.
public class BaseEntityConverter implements Converter {

    @EJB
    private BaseService baseService;

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value == null) {
            return "";
        }

        if (modelValue instanceof BaseEntity) {
            Number id = ((BaseEntity) modelValue).getId();
            return (id != null) ? id.toString() : null;
        } else {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
        }
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            Class<?> type = component.getValueExpression("value").getType(context.getELContext());
            return baseService.find((Class<BaseEntity<? extends Number>>) type, Long.valueOf(submittedValue));
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid ID of BaseEntity", submittedValue)), e);
        }
    }

}

@ManagedBeanではなくとして登録されていることに注意してください@FacesConverter。このトリックを使用すると、eg を介してコンバーターにサービスを挿入できます@EJB@FacesConverter で @EJB、@PersistenceContext、@Inject、@Autowired などを注入する方法も参照してください。converter="#{baseEntityConverter}"したがって、の代わりにとして参照する必要がありますconverter="baseEntityConverter"

このようなコンバーターをUISelectOne/UISelectManyコンポーネント (<h:selectOneMenu>およびその友人) に頻繁に使用する場合は、 OmniFaces SelectItemsConverterがはるかに便利であることに気付くかもしれません。<f:selectItems>毎回 (潜在的に高価な) DB 呼び出しを行う代わりに、で利用可能な値に基づいて変換します。

于 2013-06-27T12:46:41.613 に答える
0

この考慮事項による私の解決策は次のとおりです。

  • JPA(Hibernateではない)に興味があると思います
  • 私のソリューションは、クラスを拡張する必要はなく、JPA エンティティ Bean で機能するはずです。これは、使用する単純なクラスにすぎず、サービスや DAO を実装する必要もありません。唯一の要件は、コンバーターが JPA ライブラリーに直接依存していることです。これはあまり洗練されていない可能性があります。
  • Bean の ID をシリアル化/逆シリアル化するための補助メソッドを使用します。エンティティ Bean の ID のみを変換し、文字列をクラス名と組み合わせて、シリアル化されて base64 に変換された ID を合成します。これは、jpa ではエンティティの ID がシリアライズ可能を実装する必要があるため可能です。このメソッドの実装は Java 1.7 にありますが、そこにある Java < 1.7 の別の実装を見つけることができます。
java.io.ByteArrayInputStream をインポートします。
java.io.ByteArrayOutputStream をインポートします。
import java.io.IOException;
java.io.ObjectInput をインポートします。
java.io.ObjectInputStream をインポートします。
java.io.ObjectOutput をインポートします。
java.io.ObjectOutputStream をインポートします。

javax.faces.bean.ManagedBean をインポートします。
javax.faces.bean.ManagedProperty をインポートします。
import javax.faces.bean.RequestScoped;
import javax.faces.component.UIComponent;
javax.faces.context.FacesContext をインポートします。
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
javax.persistence.EntityManagerFactory をインポートします。

/**
 * jsf の jpa エンティティの汎用コンバーター
 *
 * jpa インスタンスを次の形式の文字列に変換します: @ 文字列から ID で検索するインスタンスに変換します
 * データベース
 *
 * jpa がすべてのエンティティ ID を必要とするという事実のおかげで可能です。
 * シリアライズ可能を実装
 *
 * 必須: - 「entityManagerFactory」という名前のインスタンスを提供する必要があります。
 * 注入 - すべてのエンティティに equals と hashCode を実装することを忘れないでください
 * クラス !!
 *
 */
@ManagedBean
@RequestScoped
public class EntityConverter は Converter を実装します {

    private static final char CHARACTER_SEPARATOR = '@';

    @ManagedProperty(value = "#{entityManagerFactory}")
    private EntityManagerFactory entityManagerFactory;

    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    private static final String 空 = "";

    @オーバーライド
    public Object getAsObject(FacesContext context, UIComponent c, String value) {
        if (値 == null || value.isEmpty()) {
            null を返します。
        }

        int index = value.indexOf(CHARACTER_SEPARATOR);
        String clazz = value.substring(0, インデックス);
        文字列 idBase64String = value.substring(index + 1, value.length());
EntityManager entityManager=null;
        試す {
            Class entityClazz = Class.forName(clazz);
            オブジェクト ID = convertFromBase64String(idBase64String);

        entityManager = entityManagerFactory.createEntityManager();
        オブジェクト object = entityManager.find(entityClazz, id);

            オブジェクトを返します。

        } キャッチ (ClassNotFoundException e) {
            throw new ConverterException("Jpa エンティティが見つかりません" + clazz, e);
        } キャッチ (IOException e) {
            throw new ConverterException("JPA クラスの ID をデシリアライズできませんでした" + clazz, e);
        }最後に{
        if(entityManager!=null){
            entityManager.close();  
        }
    }

    }

    @オーバーライド
    public String getAsString(FacesContext context, UIComponent c, Object value) {
        if (値 == null) {
            空を返します。
        }
        String clazz = value.getClass().getName();
        文字列 idBase64String;
        試す {
            idBase64String = convertToBase64String(entityManagerFactory.getPersistenceUnitUtil().getIdentifier(値));
        } キャッチ (IOException e) {
            throw new ConverterException("クラスの ID をシリアル化できませんでした" + clazz, e);
        }

        clazz + CHARACTER_SEPARATOR + idBase64String を返します。
    }

    // UTILITY METHODS (別の場所に移動してリファクタリングできます)

    public static String convertToBase64String(Object o) throws IOException {
        return javax.xml.bind.DatatypeConverter.printBase64Binary(convertToBytes(o));
    }

    public static Object convertFromBase64String(String str) throws IOException, ClassNotFoundException {
        return convertFromBytes(javax.xml.bind.DatatypeConverter.parseBase64Binary(str));
    }

    public static byte[] convertToBytes(Object object) throws IOException {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos)) {
            out.writeObject(オブジェクト);
            bos.toByteArray(); を返します。
        }
    }

    public static Object convertFromBytes(byte[] bytes) throws IOException, ClassNotFoundException {
        try (ByteArrayInputStream bis = 新しい ByteArrayInputStream(バイト); ObjectInput in = 新しい ObjectInputStream(bis)) {
            in.readObject(); を返します。
        }
    }

}

別のコンバーターのように使用します

<h:selectOneMenu converter="#{entityConverter}" ...
于 2016-10-16T10:46:53.520 に答える