可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have an existing database of a film rental system. Each film has a has a rating attribute. In SQL they used a constraint to limit the allowed values of this attribute.
CONSTRAINT film_rating_check CHECK
((((((((rating)::text = ''::text) OR
((rating)::text = 'G'::text)) OR
((rating)::text = 'PG'::text)) OR
((rating)::text = 'PG-13'::text)) OR
((rating)::text = 'R'::text)) OR
((rating)::text = 'NC-17'::text)))
I think it would be nice to use a Java enum to map the constraint into the object world. But it's not possible to simply take the allowed values because of the special char in "PG-13" and "NC-17". So I implemented the following enum:
public enum Rating {
UNRATED ( "" ),
G ( "G" ),
PG ( "PG" ),
PG13 ( "PG-13" ),
R ( "R" ),
NC17 ( "NC-17" );
private String rating;
private Rating(String rating) {
this.rating = rating;
}
@Override
public String toString() {
return rating;
}
}
@Entity
public class Film {
..
@Enumerated(EnumType.STRING)
private Rating rating;
..
With the toString() method the direction enum -> String works fine, but String -> enum does not work. I get the following exception:
[TopLink Warning]: 2008.12.09
01:30:57.434--ServerSession(4729123)--Exception [TOPLINK-116] (Oracle
TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))):
oracle.toplink.essentials.exceptions.DescriptorException Exception
Description: No conversion value provided for the value [NC-17] in
field [FILM.RATING]. Mapping:
oracle.toplink.essentials.mappings.DirectToFieldMapping[rating-->FILM.RATING]
Descriptor: RelationalDescriptor(de.fhw.nsdb.entities.Film -->
[DatabaseTable(FILM)])
cheers
timo
回答1:
Sounds like you need to add support for a custom type:
Extending OracleAS TopLink to Support Custom Type Conversions
回答2:
have you tried to store the ordinal value. Store the string value works fine if you don't have an associated String to the value:
@Enumerated(EnumType.ORDINAL)
回答3:
You have a problem here and that is the limited capabilities of JPA when it comes to handling enums. With enums you have two choices:
- Store them as a number equalling
Enum.ordinal()
, which is a terrible idea (imho); or
- Store them as a string equalling
Enum.name()
. Note: not toString()
as you might expect, especially since the default behaviourfor Enum.toString()
is to return name()
.
Personally I think the best option is (2).
Now you have a problem in that you're defining values that don't represent vailid instance names in Java (namely using a hyphen). So your choices are:
- Change your data;
- Persist String fields and implicitly convert them to or from enums in your objects; or
- Use nonstandard extensions like TypeConverters.
I would do them in that order (first to last) as an order of preference.
Someone suggested Oracle TopLink's converter but you're probably using Toplink Essentials, being the reference JPA 1.0 implementation, which is a subset of the commercial Oracle Toplink product.
As another suggestion, I'd strongly recommend switching to EclipseLink. It is a far more complete implementation than Toplink Essentials and Eclipselink will be the reference implementation of JPA 2.0 when released (expected by JavaOne mid next year).
回答4:
public enum Rating {
UNRATED ( "" ),
G ( "G" ),
PG ( "PG" ),
PG13 ( "PG-13" ),
R ( "R" ),
NC17 ( "NC-17" );
private String rating;
private static Map<String, Rating> ratings = new HashMap<String, Rating>();
static {
for (Rating r : EnumSet.allOf(Rating.class)) {
ratings.put(r.toString(), r);
}
}
private static Rating getRating(String rating) {
return ratings.get(rating);
}
private Rating(String rating) {
this.rating = rating;
}
@Override
public String toString() {
return rating;
}
}
I don't know how to do the mappings in the annotated TopLink side of things however.
回答5:
i don't know internals of toplink, but my educated guess is the following: it uses the Rating.valueOf(String s) method to map in the other direction. it is not possible to override valueOf(), so you must stick to the naming convention of java, to allow a correct valueOf method.
public enum Rating {
UNRATED,
G,
PG,
PG_13 ,
R ,
NC_17 ;
public String getRating() {
return name().replace("_","-");;
}
}
getRating produces the "human-readable" rating. note that the "-" chanracter is not allowed in the enum identifier.
of course you will have to store the values in the DB as NC_17.
回答6:
The problem is, I think, that JPA was never incepted with the idea in mind that we could have a complex preexisting Schema already in place.
I think there are two main shortcomings resulting from this, specific to Enum:
- The limitation of using name() and ordinal(). Why not just mark a getter with @Id, the way we do with @Entity?
- Enum's have usually representation in the database to allow association with all sorts of metadata, including a proper name, a descriptive name, maybe something with localization etc. We need the easy of use of an Enum combined with the flexibility of an Entity.
Help my cause and vote on JPA_SPEC-47
回答7:
What about this
public String getRating{
return rating.toString();
}
pubic void setRating(String rating){
//parse rating string to rating enum
//JPA will use this getter to set the values when getting data from DB
}
@Transient
public Rating getRatingValue(){
return rating;
}
@Transient
public Rating setRatingValue(Rating rating){
this.rating = rating;
}
with this you use the ratings as String both on your DB and entity, but use the enum for everything else.
回答8:
use this annotation
@Column(columnDefinition="ENUM('User', 'Admin')")
回答9:
Enum
public enum ParentalControlLevelsEnum {
U("U"), PG("PG"), _12("12"), _15("15"), _18("18");
private final String value;
ParentalControlLevelsEnum(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static ParentalControlLevelsEnum fromString(final String value) {
for (ParentalControlLevelsEnum level : ParentalControlLevelsEnum.values()) {
if (level.getValue().equalsIgnoreCase(value)) {
return level;
}
}
return null;
}
}
compare -> Enum
public class RatingComparator implements Comparator {
public int compare(final ParentalControlLevelsEnum o1, final ParentalControlLevelsEnum o2) {
if (o1.ordinal() < o2.ordinal()) {
return -1;
} else {
return 1;
}
}
}
回答10:
Resolved!!!
Where I found the answer: http://programming.itags.org/development-tools/65254/
Briefly, the convertion looks for the name of enum, not the value of attribute 'rating'.
In your case: If you have in the db values "NC-17", you need to have in your enum:
enum Rating {
(...)
NC-17 ( "NC-17" );
(...)