Enum列のwhere句でクエリを実行すると、例外がスローされます。
_org.hibernate.exception.SQLGrammarException: could not extract ResultSet
...
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = bytea
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
_
SQL:
_create type movedirection as enum (
'FORWARD', 'LEFT'
);
CREATE TABLE move
(
id serial NOT NULL PRIMARY KEY,
directiontomove movedirection NOT NULL
);
_
Hibernateマップクラス:
_@Entity
@Table(name = "move")
public class Move {
public enum Direction {
FORWARD, LEFT;
}
@Id
@Column(name = "id")
@GeneratedValue(generator = "sequenceGenerator", strategy=GenerationType.SEQUENCE)
@SequenceGenerator(name = "sequenceGenerator", sequenceName = "move_id_seq")
private long id;
@Column(name = "directiontomove", nullable = false)
@Enumerated(EnumType.STRING)
private Direction directionToMove;
...
// getters and setters
}
_
クエリを呼び出すJava:
_public List<Move> getMoves(Direction directionToMove) {
return (List<Direction>) sessionFactory.getCurrentSession()
.getNamedQuery("getAllMoves")
.setParameter("directionToMove", directionToMove)
.list();
}
_
Hibernate xmlクエリ:
_<query name="getAllMoves">
<![CDATA[
select move from Move move
where directiontomove = :directionToMove
]]>
</query>
_
id
によるクエリは期待どおりに機能します。データベースとの相互作用がないJavaは正常に動作します。
_public List<Move> getMoves(Direction directionToMove) {
List<Move> moves = new ArrayList<>();
Move move1 = new Move();
move1.setDirection(directionToMove);
moves.add(move1);
return moves;
}
_
createQuery
の例と同様に、XMLでクエリを作成する代わりにfindByRating
を使用すると、同じ例外が発生しました。select * from move where direction = 'LEFT';
_を使用したpsqlでのクエリは期待どおりに機能します。where direction = 'FORWARD'
_のハードコーディングは機能します。.setParameter("direction", direction.name())
は.setString()
および.setText()
とは異なり、例外が次のように変更されます。
_Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: movedirection = character varying
_
この受け入れられた回答で提案されているカスタムUserType
https://stackoverflow.com/a/1594020/1090474 とともに:
_@Column(name = "direction", nullable = false)
@Enumerated(EnumType.STRING) // tried with and without this line
@Type(type = "full.path.to.HibernateMoveDirectionUserType")
private Direction directionToMove;
_
評価が高いが受け入れられていない回答 https://stackoverflow.com/a/1604286/1090474 と同じ質問からのHibernateのEnumType
によるマッピング:
_@Type(type = "org.hibernate.type.EnumType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction"),
@Parameter(name = "type", value = "12"),
@Parameter(name = "useNamed", value = "true")
})
_
https://stackoverflow.com/a/13241410/1090474 を見た後の2つの2番目のパラメーターの有無
EnumType.ORDINAL
_を試したことはありません。脆弱性が低く、柔軟性の高い_EnumType.STRING
_を使い続けたいからです。今のところJPA 2.0を使用しているので、JPA 2.1タイプコンバーターは必要ではありませんが、オプションではありません。
正しいエイリアスと修飾されたプロパティ名の使用がソリューションの最初の部分でした。
_<query name="getAllMoves">
<![CDATA[
from Move as move
where move.directionToMove = :direction
]]>
</query>
_
@Enumerated(EnumType.STRING)
はまだ機能しなかったため、カスタムUserType
が必要でした。重要なのは、この回答のようにnullSafeSet
を正しくオーバーライドすることでした https://stackoverflow.com/a/7614642/1090474 および similarimplementations ウェブから。
_@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
}
else {
st.setObject(index, ((Enum) value).name(), Types.OTHER);
}
}
_
_implements ParameterizedType
_は協力していませんでした:
_org.hibernate.MappingException: type is not parameterized: full.path.to.PGEnumUserType
_
そのため、次のようにenumプロパティに注釈を付けることができませんでした。
_@Type(type = "full.path.to.PGEnumUserType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction")
}
)
_
代わりに、次のようにクラスを宣言しました。
_public class PGEnumUserType<E extends Enum<E>> implements UserType
_
コンストラクタを使って:
_public PGEnumUserType(Class<E> enumClass) {
this.enumClass = enumClass;
}
_
残念ながら、同様にマッピングされた他の列挙型プロパティには、次のようなクラスが必要です。
_public class HibernateDirectionUserType extends PGEnumUserType<Direction> {
public HibernateDirectionUserType() {
super(Direction.class);
}
}
_
プロパティに注釈を付ければ完了です。
_@Column(name = "directiontomove", nullable = false)
@Type(type = "full.path.to.HibernateDirectionUserType")
private Direction directionToMove;
_
EnhancedUserType
とそれが実装したい3つのメソッド
_public String objectToSQLString(Object value)
public String toXMLString(Object value)
public String objectToSQLString(Object value)
_
目に見える違いはなかったので、_implements UserType
_のままにしました。
nullSafeGet
をオーバーライドして、クラスをpostgres固有にする必要がない場合もあります。text
にすると、元のコードは余分な作業なしで機能します。以下のすべてのHibernateタイプを手動で作成する必要はありません。次の依存関係を使用して、Maven Centralから単純に取得できます。
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
詳しくは hibernate-typesオープンソースプロジェクト をご覧ください。
私が この記事で説明 のように、簡単にJava EnumをPostgreSQL Enum列タイプに次のカスタムタイプを使用してマップする場合:
public class PostgreSQLEnumType extends org.hibernate.type.EnumType {
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session)
throws HibernateException, SQLException {
if(value == null) {
st.setNull( index, Types.OTHER );
}
else {
st.setObject(
index,
value.toString(),
Types.OTHER
);
}
}
}
これを使用するには、次の例に示すように、フィールドにHibernate @Type
アノテーションを付ける必要があります。
@Entity(name = "Post")
@Table(name = "post")
@TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public static class Post {
@Id
private Long id;
private String title;
@Enumerated(EnumType.STRING)
@Column(columnDefinition = "post_status_info")
@Type( type = "pgsql_enum" )
private PostStatus status;
//Getters and setters omitted for brevity
}
このマッピングは、PostgreSQLにpost_status_info
列挙型があることを前提としています。
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
それだけです、それは魅力のように機能します。これが それを証明するGitHubのテスト です。
8.7.3。Postgresドキュメントのタイプセーフ で述べたように:
本当にそのようなことをする必要がある場合は、カスタム演算子を作成するか、クエリに明示的なキャストを追加できます。
したがって、迅速かつ簡単な回避策が必要な場合は、次のようにします。
<query name="getAllMoves">
<![CDATA[
select move from Move move
where cast(directiontomove as text) = cast(:directionToMove as text)
]]>
</query>
残念ながら、 2つのコロンだけでは実行できません :
永続化コンバーターを使用した別のアプローチがあります:
import javax.persistence.Convert;
@Column(name = "direction", nullable = false)
@Converter(converter = DirectionConverter.class)
private Direction directionToMove;
これはコンバーターの定義です。
import javax.persistence.Converter;
@Converter
public class DirectionConverter implements AttributeConverter<Direction, String> {
@Override
public String convertToDatabaseColumn(Direction direction) {
return direction.name();
}
@Override
public Direction convertToEntityAttribute(String string) {
return Diretion.valueOf(string);
}
}
Psql列挙型へのマッピングは解決しませんが、@ Enumerated(EnumType.STRING)または@Enumerated(EnumType.ORDINAL)を適切にシミュレートできます。
序数には、direction.ordinal()およびDirection.values()[number]を使用します。
まず、Hibernate 4.3.xとPostgres 9.xを使用してこれを行うことができたと言いましょう。
私はあなたがしたことと同じようなものに基づいて私の解決策を作りました。組み合わせれば
@Type(type = "org.hibernate.type.EnumType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction"),
@Parameter(name = "type", value = "12"),
@Parameter(name = "useNamed", value = "true")
})
この
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
}
else {
st.setObject(index, ((Enum) value).name(), Types.OTHER);
}
}
上記の変更を行わなくても、これに沿って何かを取得できるはずです。
@Type(type = "org.hibernate.type.EnumType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction"),
@Parameter(name = "type", value = "1111"),
@Parameter(name = "useNamed", value = "true")
})
列挙型を他の型(Types.OTHER == 1111
)にマップするように本質的にHibernateに指示しているので、これはうまくいくと思います。 Types.OTHER
の値が変更される可能性があるため、これはやや脆弱なソリューションである可能性があります。ただし、これによりコード全体が大幅に少なくなります。